diff --git a/hisim/modular_household/interface_configs/modular_household_config.py b/hisim/modular_household/interface_configs/modular_household_config.py index f8d78a35c..82f255b29 100644 --- a/hisim/modular_household/interface_configs/modular_household_config.py +++ b/hisim/modular_household/interface_configs/modular_household_config.py @@ -12,13 +12,20 @@ @dataclass -class ModularHouseholdConfig(SystemSetupConfigBase): +class Options: + + """ Set options for the system setup.""" + + pass + +@dataclass +class ModularHouseholdConfig(SystemSetupConfigBase): """Modular Household Config class.""" - #: configuration of the technological equipment of the household + # configuration of the technological equipment of the household system_config_: Optional[system_config.SystemConfig] = None - #: configuration of the framework of the household (climate, house type, mobility behaviour, heating system, etc. ) + # configuration of the framework of the household (climate, house type, mobility behaviour, heating system, etc. ) archetype_config_: Optional[archetype_config.ArcheTypeConfig] = None @classmethod @@ -43,7 +50,8 @@ def read_in_configs(pathname: str) -> ModularHouseholdConfig: try: with open(pathname, encoding="utf8") as config_file: household_config_dict = json.load(config_file) # type: ignore - household_config: ModularHouseholdConfig = ModularHouseholdConfig.from_dict(household_config_dict.get("system_setup_config")) # type: ignore + household_config: ModularHouseholdConfig = ModularHouseholdConfig.from_dict( + household_config_dict.get("system_setup_config")) # type: ignore log.information(f"Read modular household config from {pathname}") if (household_config.system_config_ is None) and (household_config.archetype_config_ is None): raise ValueError() diff --git a/hisim/system_setup_configuration.py b/hisim/system_setup_configuration.py index de2ceebb6..7313fbdaf 100644 --- a/hisim/system_setup_configuration.py +++ b/hisim/system_setup_configuration.py @@ -2,6 +2,7 @@ # clean +from typing import Any import json from dataclasses import dataclass from dataclass_wizard import JSONWizard @@ -25,7 +26,7 @@ def load_from_json(cls, module_config_path: str) -> Self: with open(module_config_path, "r", encoding="utf8") as file: module_config_dict = json.loads(file.read()) - # Read building config + # Read building config overwrites. It is used to scale the system setup. building_config_dict = module_config_dict.pop("building_config", {}) if building_config_dict: log.information("Using `building_config` for scaling.") @@ -33,15 +34,25 @@ def load_from_json(cls, module_config_path: str) -> Self: else: building_config = None - # Load (scaled) default values for system setup configuration + # Load option overwrites. + options_dict = module_config_dict.pop("options", {}) + options = cls.get_default_options() + if options_dict: + log.information("Using `options`.") + utils.set_attributes_of_dataclass_from_dict(options, options_dict) + + # Load (scaled) default values for system setup configuration. + if options_dict and not building_config: + raise ValueError("Options for default setup not yet implemented.") if building_config: my_config = cls.get_scaled_default( building_config=building_config, + options=options ) else: my_config = cls.get_default() - # Read setup config + # Read setup config overwrites setup_config_dict = module_config_dict.pop("system_setup_config", {}) if setup_config_dict: log.information("Using `system_setup_config` to overwrite defaults.") @@ -53,6 +64,11 @@ def load_from_json(cls, module_config_path: str) -> Self: return my_config + @classmethod + def get_default_options(cls) -> Any: + """Get default options.""" + raise NotImplementedError + @classmethod def get_default(cls) -> Self: """Get default.""" @@ -62,6 +78,7 @@ def get_default(cls) -> Self: def get_scaled_default( cls, building_config: building.BuildingConfig, + options: Any ) -> Self: """Get scaled default.""" raise NotImplementedError diff --git a/hisim/system_setup_starter.py b/hisim/system_setup_starter.py index 440f246c1..45dad0ad5 100644 --- a/hisim/system_setup_starter.py +++ b/hisim/system_setup_starter.py @@ -1,11 +1,11 @@ """Build and simulate a system setup for a specific system setup that is defined in a JSON file. Result files are stored in `/results`. -See `tests/test_system_setup_starter.py` for an system setup. +See `tests/test_system_setup_starter.py` for a system setup. Run `hisim/system_setup_starter.py ` to start a simulation. Required fields in the JSON file are: `path_to_module`, `function_in_module` and -`simulation_parameters`. SimulationParameters from the system_setups is not used. Instead the +`simulation_parameters`. SimulationParameters from the system_setups is not used. Instead, the parameters from the JSON are set. Optional field: `building_config` @@ -16,7 +16,6 @@ The values from `system_setup_config` overwrite specific values of the configuration object. Arguments that are not present keep the (scaled) default value. """ -# clean import json import datetime @@ -29,13 +28,6 @@ from hisim.utils import set_attributes_of_dataclass_from_dict from hisim.simulator import SimulationParameters -# System setups need to use `create_configuration()` and their config class needs to implement -# `get_default()` to run with the system setup starter. -SUPPORTED_MODULES = [ - "system_setups.modular_example", - "system_setups.household_1_advanced_hp_diesel_car", -] - def make_system_setup( parameters_json: Union[dict, list], result_directory: str @@ -52,18 +44,14 @@ def make_system_setup( _parameters_json = deepcopy(parameters_json) Path(result_directory).mkdir(parents=True, exist_ok=True) # pylint: disable=unexpected-keyword-arg path_to_module = _parameters_json.pop("path_to_module") - setup_module_name = "system_setups." + path_to_module.split("/")[-1].replace(".py", "") - if setup_module_name not in SUPPORTED_MODULES: - raise NotImplementedError( - f"System setup starter can only be used with one of {', '.join(SUPPORTED_MODULES)}" - ) simulation_parameters_dict = _parameters_json.pop("simulation_parameters") module_config_path = str(Path(result_directory).joinpath("module_config.json")) simulation_parameters_path = str(Path(result_directory).joinpath("simulation_parameters.json")) + options = _parameters_json.pop("options", {}) building_config = _parameters_json.pop("building_config", {}) system_setup_config = _parameters_json.pop("system_setup_config", {}) - module_config_dict = {"building_config": building_config, "system_setup_config": system_setup_config} + module_config_dict = {"options": options, "building_config": building_config, "system_setup_config": system_setup_config} if _parameters_json: raise AttributeError( diff --git a/system_setups/household_1_advanced_hp_diesel_car.py b/system_setups/household_1_advanced_hp_diesel_car.py index 2bb442cf1..226c49f38 100644 --- a/system_setups/household_1_advanced_hp_diesel_car.py +++ b/system_setups/household_1_advanced_hp_diesel_car.py @@ -41,6 +41,14 @@ __status__ = "development" +@dataclass +class Options: + + """ Set options for the system setup.""" + + pass + + @dataclass class HouseholdAdvancedHPDieselCarConfig(SystemSetupConfigBase): @@ -61,6 +69,11 @@ class HouseholdAdvancedHPDieselCarConfig(SystemSetupConfigBase): car_config: generic_car.CarConfig electricity_meter_config: electricity_meter.ElectricityMeterConfig + @classmethod + def get_default_options(cls): + """Get default options.""" + return Options() + @classmethod def get_default(cls) -> "HouseholdAdvancedHPDieselCarConfig": """Get default HouseholdAdvancedHPDieselCarConfig.""" @@ -86,6 +99,7 @@ def get_default(cls) -> "HouseholdAdvancedHPDieselCarConfig": def get_scaled_default( cls, building_config: building.BuildingConfig, + options: Options = Options() ) -> "HouseholdAdvancedHPDieselCarConfig": """Get scaled default HouseholdAdvancedHPDieselCarConfig.""" diff --git a/system_setups/household_gas_heater.py b/system_setups/household_gas_heater.py new file mode 100644 index 000000000..9799931f6 --- /dev/null +++ b/system_setups/household_gas_heater.py @@ -0,0 +1,324 @@ +""" Household system setup with gas heater. """ + +from dataclasses import dataclass +from os import listdir +from typing import List, Optional, Any +from pathlib import Path +from dataclasses_json import dataclass_json +from utspclient.helpers.lpgdata import ( + ChargingStationSets, + Households, + TransportationDeviceSets, + TravelRouteSets, + EnergyIntensityType, +) +from hisim.system_setup_configuration import SystemSetupConfigBase +from hisim.simulator import SimulationParameters +from hisim.components import loadprofilegenerator_utsp_connector +from hisim.components import weather +from hisim.components import generic_gas_heater +from hisim.components import controller_l1_generic_gas_heater +from hisim.components import heat_distribution_system +from hisim.components import building +from hisim.components import simple_hot_water_storage +from hisim.components import generic_car +from hisim.components import generic_heat_pump_modular +from hisim.components import controller_l1_heatpump +from hisim.components import generic_hot_water_storage_modular +from hisim.components import electricity_meter +from hisim import utils + +from system_setups.modular_example import cleanup_old_lpg_requests + +__authors__ = ["Kevin Knosala", "Markus Blasberg"] +__copyright__ = "Copyright 2023, FZJ-IEK-3" +__credits__ = ["Noah Pflugradt"] +__license__ = "MIT" +__version__ = "1.0" +__maintainer__ = "Kevin Knosala" +__status__ = "development" + + +@dataclass +class Options: + + """Set options for the system setup.""" + + # photovoltaic: bool = False + car: bool = True + + +@dataclass_json +@dataclass +class HouseholdGasHeaterConfig(SystemSetupConfigBase): + + """Configuration for household with gas heater.""" + + building_type: str + number_of_apartments: int + + options: Options + building_config: building.BuildingConfig + + occupancy_config: loadprofilegenerator_utsp_connector.UtspLpgConnectorConfig + hds_controller_config: heat_distribution_system.HeatDistributionControllerConfig + hds_config: heat_distribution_system.HeatDistributionConfig + gas_heater_controller_config: controller_l1_generic_gas_heater.GenericGasHeaterControllerL1Config + gas_heater_config: generic_gas_heater.GenericGasHeaterConfig + simple_hot_water_storage_config: simple_hot_water_storage.SimpleHotWaterStorageConfig + dhw_heatpump_config: generic_heat_pump_modular.HeatPumpConfig + dhw_heatpump_controller_config: controller_l1_heatpump.L1HeatPumpConfig + dhw_storage_config: generic_hot_water_storage_modular.StorageConfig + electricity_meter_config: electricity_meter.ElectricityMeterConfig + + car_config: Optional[generic_car.CarConfig] + + @classmethod + def get_default_options(cls): + """Get default options.""" + return Options() + + @classmethod + def get_default(cls) -> "HouseholdGasHeaterConfig": + """Get default HouseholdGasHeaterConfig.""" + building_config = building.BuildingConfig.get_default_german_single_family_home() + household_config = cls.get_scaled_default(building_config, options=Options()) + return household_config + + @classmethod + def get_scaled_default(cls, building_config: building.BuildingConfig, options: Options = Options()) -> "HouseholdGasHeaterConfig": + """Get scaled HouseholdGasHeaterConfig. + + - Simulation Parameters + - Components + - Occupancy (Residents' Demands) + - Weather + - Building + - Electricity Meter + - Gas Heater + - Gas Heater Controller + - Heat Distribution System + - Heat Distribution System Controller + - Simple Hot Water Storage + - Car (Diesel or EV) + """ + set_heating_threshold_outside_temperature_in_celsius: float = 16.0 + + my_building_information = building.BuildingInformation(config=building_config) + + hds_controller_config = heat_distribution_system.HeatDistributionControllerConfig.get_default_heat_distribution_controller_config( + set_heating_temperature_for_building_in_celsius=my_building_information.set_heating_temperature_for_building_in_celsius, + set_cooling_temperature_for_building_in_celsius=my_building_information.set_cooling_temperature_for_building_in_celsius, + heating_load_of_building_in_watt=my_building_information.max_thermal_building_demand_in_watt, + heating_reference_temperature_in_celsius=my_building_information.heating_reference_temperature_in_celsius, + ) + my_hds_controller_information = heat_distribution_system.HeatDistributionControllerInformation(config=hds_controller_config) + + household_config = HouseholdGasHeaterConfig( + building_type="residential", + number_of_apartments=my_building_information.number_of_apartments, + occupancy_config=loadprofilegenerator_utsp_connector.UtspLpgConnectorConfig( + url=utils.get_environment_variable("UTSP_URL"), + api_key=utils.get_environment_variable("UTSP_API_KEY"), + household=Households.CHR01_Couple_both_at_Work, + energy_intensity=EnergyIntensityType.EnergySaving, + result_dir_path=utils.HISIMPATH["results"], + travel_route_set=TravelRouteSets.Travel_Route_Set_for_10km_Commuting_Distance, + transportation_device_set=TransportationDeviceSets.Bus_and_one_30_km_h_Car, + charging_station_set=ChargingStationSets.Charging_At_Home_with_11_kW, + name="UTSPConnector", + consumption=0.0, + profile_with_washing_machine_and_dishwasher=True, + predictive_control=False, + ), + options=options, + building_config=building_config, + hds_controller_config=hds_controller_config, + hds_config=( + heat_distribution_system.HeatDistributionConfig.get_default_heatdistributionsystem_config( + temperature_difference_between_flow_and_return_in_celsius=my_hds_controller_information.temperature_difference_between_flow_and_return_in_celsius, + water_mass_flow_rate_in_kg_per_second=my_hds_controller_information.water_mass_flow_rate_in_kp_per_second, + ) + ), + gas_heater_controller_config=( + controller_l1_generic_gas_heater.GenericGasHeaterControllerL1Config.get_scaled_generic_gas_heater_controller_config( + heating_load_of_building_in_watt=my_building_information.max_thermal_building_demand_in_watt + ) + ), + gas_heater_config=generic_gas_heater.GenericGasHeaterConfig.get_scaled_gasheater_config( + heating_load_of_building_in_watt=my_building_information.max_thermal_building_demand_in_watt + ), + simple_hot_water_storage_config=( + simple_hot_water_storage.SimpleHotWaterStorageConfig.get_scaled_hot_water_storage( + max_thermal_power_in_watt_of_heating_system=my_building_information.max_thermal_building_demand_in_watt, + heating_system_name="GasHeater", + water_mass_flow_rate_from_hds_in_kg_per_second=my_hds_controller_information.water_mass_flow_rate_in_kp_per_second, + ) + ), + dhw_heatpump_config=( + generic_heat_pump_modular.HeatPumpConfig.get_scaled_waterheating_to_number_of_apartments( + number_of_apartments=my_building_information.number_of_apartments + ) + ), + dhw_heatpump_controller_config=controller_l1_heatpump.L1HeatPumpConfig.get_default_config_heat_source_controller_dhw( + name="DHWHeatpumpController" + ), + dhw_storage_config=( + generic_hot_water_storage_modular.StorageConfig.get_scaled_config_for_boiler_to_number_of_apartments( + number_of_apartments=my_building_information.number_of_apartments + ) + ), + car_config=generic_car.CarConfig.get_default_diesel_config() if options.car else None, + electricity_meter_config=electricity_meter.ElectricityMeterConfig.get_electricity_meter_default_config(), + ) + + # set same heating threshold + household_config.hds_controller_config.set_heating_threshold_outside_temperature_in_celsius = ( + set_heating_threshold_outside_temperature_in_celsius + ) + household_config.gas_heater_controller_config.set_heating_threshold_outside_temperature_in_celsius = ( + set_heating_threshold_outside_temperature_in_celsius + ) + + return household_config + + +def setup_function(my_sim: Any, my_simulation_parameters: Optional[SimulationParameters] = None) -> None: # noqa: too-many-statements + """Generates a household with gas heater.""" + + # cleanup old lpg requests, mandatory to change number of cars + # Todo: change cleanup-function if result_path from occupancy is not utils.HISIMPATH["results"] + if Path(utils.HISIMPATH["utsp_results"]).exists(): + cleanup_old_lpg_requests() + + """ + Load system setup parameters from json or take defaults. + """ + if my_sim.my_module_config_path: + my_config = HouseholdGasHeaterConfig.load_from_json(my_sim.my_module_config_path) + else: + my_config = HouseholdGasHeaterConfig.get_default() + + """ + Set simulation parameters + """ + year = 2021 + seconds_per_timestep = 60 + if my_simulation_parameters is None: + my_simulation_parameters = SimulationParameters.full_year_all_options(year=year, seconds_per_timestep=seconds_per_timestep) + my_sim.set_simulation_parameters(my_simulation_parameters) + + """ + Build system + """ + # Heat Distribution System Controller + my_heat_distribution_controller = heat_distribution_system.HeatDistributionController( + config=my_config.hds_controller_config, + my_simulation_parameters=my_simulation_parameters, + ) + + # Occupancy + my_occupancy_config = my_config.occupancy_config + my_occupancy = loadprofilegenerator_utsp_connector.UtspLpgConnector(config=my_occupancy_config, my_simulation_parameters=my_simulation_parameters) + + # Weather + my_weather = weather.Weather( + config=weather.WeatherConfig.get_default(weather.LocationEnum.AACHEN), + my_simulation_parameters=my_simulation_parameters, + ) + + # Building + my_building = building.Building( + config=my_config.building_config, + my_simulation_parameters=my_simulation_parameters, + ) + + # Gas Heater Controller + my_gas_heater_controller = controller_l1_generic_gas_heater.GenericGasHeaterControllerL1( + my_simulation_parameters=my_simulation_parameters, + config=my_config.gas_heater_controller_config, + ) + + # Gas heater + my_gas_heater = generic_gas_heater.GasHeater( + config=my_config.gas_heater_config, + my_simulation_parameters=my_simulation_parameters, + ) + + # BHeat Distribution System + my_heat_distribution = heat_distribution_system.HeatDistribution(my_simulation_parameters=my_simulation_parameters, config=my_config.hds_config) + + # Heat Water Storage + my_simple_hot_water_storage = simple_hot_water_storage.SimpleHotWaterStorage( + config=my_config.simple_hot_water_storage_config, + my_simulation_parameters=my_simulation_parameters, + ) + + # DHW + my_dhw_heatpump_config = my_config.dhw_heatpump_config + my_dhw_heatpump_controller_config = my_config.dhw_heatpump_controller_config + my_dhw_storage_config = my_config.dhw_storage_config + my_dhw_storage_config.name = "DHWStorage" + my_dhw_storage_config.compute_default_cycle( + temperature_difference_in_kelvin=my_dhw_heatpump_controller_config.t_max_heating_in_celsius + - my_dhw_heatpump_controller_config.t_min_heating_in_celsius + ) + my_domestic_hot_water_storage = generic_hot_water_storage_modular.HotWaterStorage( + my_simulation_parameters=my_simulation_parameters, config=my_dhw_storage_config + ) + my_domestic_hot_water_heatpump_controller = controller_l1_heatpump.L1HeatPumpController( + my_simulation_parameters=my_simulation_parameters, + config=my_dhw_heatpump_controller_config, + ) + my_domestic_hot_water_heatpump = generic_heat_pump_modular.ModularHeatPump( + config=my_dhw_heatpump_config, my_simulation_parameters=my_simulation_parameters + ) + + if my_config.car_config: + # Diesel-Car(s) + # get names of all available cars + filepaths = listdir(utils.HISIMPATH["utsp_results"]) + filepaths_location = [elem for elem in filepaths if "CarLocation." in elem] + names = [elem.partition(",")[0].partition(".")[2] for elem in filepaths_location] + + my_car_config = my_config.car_config + my_car_config.name = "DieselCar" + + # create all cars + my_cars: List[generic_car.Car] = [] + for car in names: + my_car_config.name = car + my_cars.append( + generic_car.Car( + my_simulation_parameters=my_simulation_parameters, + config=my_car_config, + occupancy_config=my_occupancy_config, + ) + ) + else: + my_cars = [] + + # Build Electricity Meter + my_electricity_meter = electricity_meter.ElectricityMeter( + my_simulation_parameters=my_simulation_parameters, + config=my_config.electricity_meter_config, + ) + + # ================================================================================================================================= + # Add Components to Simulation Parameters + my_sim.add_component(my_occupancy) + my_sim.add_component(my_weather) + my_sim.add_component(my_building, connect_automatically=True) + my_sim.add_component(my_gas_heater, connect_automatically=True) + my_sim.add_component(my_gas_heater_controller, connect_automatically=True) + my_sim.add_component(my_heat_distribution, connect_automatically=True) + my_sim.add_component(my_heat_distribution_controller, connect_automatically=True) + my_sim.add_component(my_simple_hot_water_storage, connect_automatically=True) + my_sim.add_component(my_domestic_hot_water_storage, connect_automatically=True) + my_sim.add_component(my_domestic_hot_water_heatpump_controller, connect_automatically=True) + my_sim.add_component(my_domestic_hot_water_heatpump, connect_automatically=True) + my_sim.add_component(my_electricity_meter, connect_automatically=True) + + if my_config.options.car: + for my_car in my_cars: + my_sim.add_component(my_car) diff --git a/tests/test_system_setups/__init__.py b/tests/test_system_setups/__init__.py new file mode 100644 index 000000000..00de565ea --- /dev/null +++ b/tests/test_system_setups/__init__.py @@ -0,0 +1 @@ +"""Init module.""" diff --git a/tests/test_system_setups/configs/household_gas_heater.json b/tests/test_system_setups/configs/household_gas_heater.json new file mode 100644 index 000000000..2b74a090a --- /dev/null +++ b/tests/test_system_setups/configs/household_gas_heater.json @@ -0,0 +1,31 @@ +{ + "path_to_module": "../../system_setups/household_gas_heater.py", + "simulation_parameters": { + "start_date": "2021-01-01T00:00:00", + "end_date": "2021-01-02T00:00:00", + "seconds_per_timestep": 900, + "post_processing_options": [ + 18, + 19, + 20, + 22 + ] + }, + "options": {"car": false}, + "building_config": { + "name": "Building", + "building_code": "DE.N.SFH.05.Gen.ReEx.001.002", + "building_heat_capacity_class": "medium", + "initial_internal_temperature_in_celsius": 23, + "heating_reference_temperature_in_celsius": -7.0, + "absolute_conditioned_floor_area_in_m2": 121.2, + "total_base_area_in_m2": null, + "number_of_apartments": 1, + "predictive": false, + "set_heating_temperature_in_celsius": 19.0, + "set_cooling_temperature_in_celsius": 24.0 + }, + "system_setup_config": { + "building_type": "some_building" + } +} diff --git a/tests/test_system_setups/test_household_gas_heater.py b/tests/test_system_setups/test_household_gas_heater.py new file mode 100644 index 000000000..7da0cb2d2 --- /dev/null +++ b/tests/test_system_setups/test_household_gas_heater.py @@ -0,0 +1,40 @@ +""" Tests for the household with gas heater. """ +import os +import subprocess +import shutil +from pathlib import Path +import pytest + +from hisim import utils, hisim_main, log +from hisim.simulationparameters import SimulationParameters +from hisim.postprocessingoptions import PostProcessingOptions + + +@pytest.mark.system_setups +@utils.measure_execution_time +def test_household_gas_heater_main(): + """Execute setup with default values with hisim main.""" + + path = "../../system_setups/household_gas_heater.py" + + mysimpar = SimulationParameters.one_day_only(year=2019, seconds_per_timestep=60) + mysimpar.post_processing_options.append(PostProcessingOptions.MAKE_NETWORK_CHARTS) + hisim_main.main(path, mysimpar) + log.information(os.getcwd()) + + +@pytest.mark.system_setups +@utils.measure_execution_time +def test_household_gas_heater_system_setup_starter(): + """Execute setup with hisim system setup starter.""" + config_json = "configs/household_gas_heater.json" + result_directory = "results/test_household_gas_heater_with_system_setup_starter" + if Path(result_directory).is_dir(): + shutil.rmtree(result_directory) + Path(result_directory).mkdir(parents=True, exist_ok=True) + + subprocess.check_output(["python", "../../hisim/system_setup_starter.py", config_json, result_directory]) + + assert Path(result_directory).joinpath("finished.flag").is_file() + + shutil.rmtree(result_directory)