diff --git a/README.md b/README.md index a88693e01..cdf990f95 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ Efficient Buildings and Indoor Climate](https://www.ebc.eonerc.rwth-aachen.de/cms/~dmzz/E-ON-ERC-EBC/?lidx=1). * [AixLib](https://github.com/RWTH-EBC/AixLib) + * [BESMod](https://github.com/RWTH-EBC/BESMod) * [Buildings](https://github.com/lbl-srg/modelica-buildings) * [BuildingSystems](https://github.com/UdK-VPT/BuildingSystems) * [IDEAS](https://github.com/open-ideas/IDEAS). @@ -67,7 +68,7 @@ If you just want to read the example on github, check them here: docs/examples. ### Dependencies -TEASER is currently tested against Python 3.6 and 3.7. Older versions of Python may +TEASER is currently tested against Python 3.7 up to 3.11. Older versions of Python may still work, but are no longer actively supported. Using a Python distribution is recommended as they already contain (or easily support installation of) many Python packages (e.g. SciPy, NumPy, pip, PyQT, diff --git a/docs/jupyter_notebooks/e11_export_besmod_models.ipynb b/docs/jupyter_notebooks/e11_export_besmod_models.ipynb new file mode 100644 index 000000000..2e83c9fd8 --- /dev/null +++ b/docs/jupyter_notebooks/e11_export_besmod_models.ipynb @@ -0,0 +1,213 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": "# Example 11: Export Modelica models for BESMod library using TEASER API\nThis module demonstrates how to export building models from a TEASER project\nto ready-to-run simulation models for the Modelica BESMod library.\nBESMod enables seamless integration with state-of-the-art energy systems,\nsuch as heat pumps and photovoltaic systems. These systems can be utilized\nto generate primary energy demand curves (e.g., for electricity or gas) or\nto conduct in-depth analyses of building energy systems. In contrast,\nAixLib focuses on ideal heat demand calculation, and IBPSA on\nfree floating temperature without an ideal heater.\nYou can execute this example using\n[jupyter-notebook](https://mybinder.org/v2/gh/RWTH-EBC/TEASER/main?labpath=docs%2Fjupyter_notebooks)\n" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": "import teaser.examples.e1_generate_archetype as e1\nimport teaser.logic.utilities as utilities\nimport os\n" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "## Standard export\nIn e1_generate_archetype we created a Project with three archetype\nbuildings to get this Project we rerun this example\n" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": "prj = e1.example_generate_archetype()\n" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "Configure project settings to ensure compatibility with BESMod. The BESMod\nlibrary uses the AixLib.ThermalZones.ReducedOrder.ThermalZone.ThermalZone model\nwith 4 elements for the demand building model. Other numbers of elements are possible,\nbut compatability with ARoof for PV and AFloor for UFH systems must be checked first.\nSet these parameters in the project:\n" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": "prj.used_library_calc = 'AixLib'\nprj.number_of_elements_calc = 4\n" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "BESMod allows building models to be included in predefined example energy systems:\n" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": "examples = [\n \"TEASERHeatLoadCalculation\", # Ideal electric heater for heat load calculations\n \"HeatPumpMonoenergetic\", # Heat pump with radiators, buffer and DHW storage, and PV\n \"GasBoilerBuildingOnly\" # Gas boiler with radiators\n]\n" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "For the hydraulic systems, you have to specify a nominal supply temperature\nfor heat transfer, e.g. in radiators.\nMultiple options are available:\n" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "Option 1: Set a single value for all buildings and zones.\n" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": "THydSup_nominal = 55 + 273.15\n" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "Option 2: Set values for each building or thermal zone.\n" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": "THydSup_nominal = {\"ResidentialBuilding\": 328.15,\n \"OfficeBuilding\": 328.15,\n \"InstituteBuilding\": {\"Office\": 343.15,\n \"Floor\": 343.15,\n \"Storage\": 343.15,\n \"Meeting\": 343.15,\n \"Restroom\": 343.15,\n \"ICT\": 343.15,\n \"Laboratory\": 328.15},\n \"InstituteBuildingMoisture\": 343.15,\n \"ResidentialBuildingTabula\": 328.15,\n \"ResidentialBuildingTabulaMulti\": 328.15}\n" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "Option 3: Specify values based on construction year.\nHere, the value of the next higher specified year is set to the building.\nThe classification here is taken from:\nhttps://www.ffe.de/projekte/waermepumpen-fahrplan-finanzielle-kipppunkte-zur-modernisierung-mit-waermepumpen-im-wohngebaeudebestand/\n" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": "THydSup_nominal = {\n 1950: 90 + 273.15,\n 1980: 70 + 273.15,\n 2010: 55 + 273.15,\n 2024: 35 + 273.15\n}\n" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "In the examples, the parameters for BESMod.Systems.UserProfiles.TEASERProfiles are configured,\nincluding internal gains and heating profiles for each zone.\nBESMod requires 24-hour heating profiles, which are used\nto define the parameters of the `setBakTSetZone` Pulse block.\nBy default, the TEASER profiles are applied, but these can be customized if needed.\n" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "Additionally, location-specific parameters must be set, which can be achieved using the following function.\nThe default values provided here correspond to Mannheim.\n" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": "weather_file_path = utilities.get_full_path(\n os.path.join(\n \"data\",\n \"input\",\n \"inputdata\",\n \"weatherdata\",\n \"DEU_BW_Mannheim_107290_TRY2010_12_Jahr_BBSR.mos\"))\n\nprj.set_location_parameters(t_outside=262.65,\n t_ground=286.15,\n weather_file_path=weather_file_path,\n calc_all_buildings=True)\n" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "To make sure the parameters are calculated correctly we recommend to\nrun prj.calc_all_buildings() function which is here already done in the set_location_parameters function.\n" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "Export all buildings to BESMod and include them in predefined example systems.\n" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": "path = prj.export_besmod(\n THydSup_nominal=THydSup_nominal,\n path=None,\n examples=examples\n)\n" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "## Partial retrofit export\nThe partial retrofit option of the energy system in BESMod can also be utilized.\nFor more information on this see BESMod.UsersGuide.GettingStarted.Parameterization.\nTo enable this here, the nominal heat flow of each zone in the building must be extracted prior to the retrofit.\n" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": "QBuiOld_flow_design = {\n bldg.name: {\n tz.name: tz.model_attr.heat_load for tz in bldg.thermal_zones\n }\n for bldg in prj.buildings\n}\n" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "Retrofit project buildings and recalculate parameters.\n" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": "prj.name = \"ArchetypeExample_partial_retrofit\"\nprj.retrofit_all_buildings(\n year_of_retrofit=2015,\n type_of_retrofit=\"adv_retrofit\",\n window_type='Alu- oder Stahlfenster, Isolierverglasung',\n material='EPS_perimeter_insulation_top_layer'\n)\nprj.calc_all_buildings()\n" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "By default, radiator transfer systems are not retrofitted when the\nQBuiOld_flow_design parameter is provided and differs from the new nominal heat flow.\nAdditionally, new THydSup_nominal temperatures can be specified alongside\nTHydSupOld_design values, which are used for radiator sizing but not for control settings.\n" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": "path = prj.export_besmod(\n THydSup_nominal=THydSup_nominal,\n QBuiOld_flow_design=QBuiOld_flow_design,\n path=None,\n examples=examples\n)\n" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "## Custom export\nAdditionally, we have the flexibility to define custom templates for including buildings in specific setups.\nFor instance, a custom template is defined here to include the building in the\nModelicaConferencePaper example from BESMod, which features an integrated battery system.\n" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "Custom template\n```\n< %namespace file = \"/modelica_language/\" import=\"get_list\" / >\nwithin ${bldg.parent.name}.${bldg.name};\nmodel ModelicaConferencePaper${bldg.name}\n extends BESMod.Examples.ModelicaConferencePaper.PartialModelicaConferenceUseCase(\n redeclare ${bldg.name} building,\n redeclare BESMod.Systems.UserProfiles.TEASERProfiles\n userProfiles(fileNameIntGains=Modelica.Utilities.Files.loadResource(\n \"modelica://${bldg.parent.name}/${bldg.name}/InternalGains_${bldg.name}.txt\"),\n setBakTSetZone(amplitude=${get_list(setBakTSetZone_amplitude)},\n width =${get_list(setBakTSetZone_width)},\n startTime =${get_list(setBakTSetZone_startTime)})),\n systemParameters(nZones=${len(bldg.thermal_zones)},\n QBui_flow_nominal = building.QRec_flow_nominal,\n TOda_nominal =${TOda_nominal},\n TSetZone_nominal =${get_list(TSetZone_nominal)},\n THydSup_nominal =${THydSup_nominal},\n QBuiOld_flow_design =${QBuiOld_flow_design},\n THydSupOld_design =${THydSupOld_design},\n filNamWea = Modelica.Utilities.Files.loadResource(\n \"modelica://${bldg.parent.name}/Resources/${bldg.parent.weather_file_name}\")));\n\n extends Modelica.Icons.Example;\n\n annotation(experiment(StopTime=172800,\n Interval=600,\n Tolerance=1e-06),\n __Dymola_Commands(file=\n \"Resources/Scripts/Dymola/${bldg.name}/ModelicaConferencePaper${bldg.name}.mos\"\n \"Simulate and plot\"));\nend\nModelicaConferencePaper${bldg.name};\n```\n" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": "prj.name = \"ArchetypeExample_custom\"\n\ncustom_template_path = os.path.join(\n os.path.dirname(__file__), \"examplefiles\", \"custom_besmod_templates\"\n)\ncustom_example_template = {\"ModelicaConferencePaper\": os.path.join(custom_template_path, \"custom_template.txt\")}\n" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "The template also includes a .mos script as part of its annotation.\nBy default, the provided examples export a basic \"simulate and plot\" script,\nwhich is incorporated into their annotation, as shown in the custom example.\nAdditionally, you have the flexibility to modify the template for existing examples\nand define custom scripts for your tailored examples.\n" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": "custom_script = {\"HeatPumpMonoenergetic\": os.path.join(custom_template_path, \"custom_script_hp_mono.txt\"),\n \"ModelicaConferencePaper\": os.path.join(custom_template_path, \"custom_script.txt\")}\n\npath = prj.export_besmod(\n THydSup_nominal=THydSup_nominal,\n path=None,\n examples=examples,\n custom_examples=custom_example_template,\n custom_script=custom_script\n)\n" + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.4" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/docs/jupyter_notebooks/e2_export_aixlib_models.ipynb b/docs/jupyter_notebooks/e2_export_aixlib_models.ipynb index 3141d8592..365f7ec9e 100644 --- a/docs/jupyter_notebooks/e2_export_aixlib_models.ipynb +++ b/docs/jupyter_notebooks/e2_export_aixlib_models.ipynb @@ -3,12 +3,7 @@ { "cell_type": "markdown", "metadata": {}, - "source": "# Example 2: Export Modelica models for AixLib library using TEASER API\n" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": "This module contains an example how to export buildings from a TEASER\nproject to ready-to-run simulation models for Modelica library AixLib. These\nmodels will only simulate using Dymola, the reason for this are state\nmachines that are used in one AixLib specific AHU model.\nYou can run this example using the [jupyter-notebook](https://mybinder.org/v2/gh/RWTH-EBC/TEASER/main?labpath=docs%2Fjupyter_notebooks)\n" + "source": "# Example 2: Export Modelica models for AixLib library using TEASER API\nThis module contains an example how to export buildings from a TEASER\nproject to ready-to-run simulation models for Modelica library AixLib.\nAixLib focuses on ideal hat demand calculation. In contrast,\nIBPSA focuses on the free floating temperature and has no ideal heater,\nand BESMod on the coupling to state-of-the-art energy systems.\nThese models will only simulate using Dymola, the reason for this are state\nmachines that are used in one AixLib specific AHU model.\nYou can run this example using the [jupyter-notebook](https://mybinder.org/v2/gh/RWTH-EBC/TEASER/main?labpath=docs%2Fjupyter_notebooks)\n" }, { "cell_type": "code", diff --git a/docs/jupyter_notebooks/e3_export_ibpsa_models.ipynb b/docs/jupyter_notebooks/e3_export_ibpsa_models.ipynb index 833a70d62..8842b2aa4 100644 --- a/docs/jupyter_notebooks/e3_export_ibpsa_models.ipynb +++ b/docs/jupyter_notebooks/e3_export_ibpsa_models.ipynb @@ -3,7 +3,7 @@ { "cell_type": "markdown", "metadata": {}, - "source": "# Example 3: Export Modelica models for IBPSA library using TEASER API\nThis module contains an example how to export buildings from a TEASER\nproject to ready-to-run simulation models for Modelica library IBPSA. These\nmodels should simulate in Dymola, OpenModelica and JModelica.\nYou can run this example using the [jupyter-notebook](https://mybinder.org/v2/gh/RWTH-EBC/TEASER/main?labpath=docs%2Fjupyter_notebooks)\n" + "source": "# Example 3: Export Modelica models for IBPSA library using TEASER API\nThis module contains an example how to export buildings from a TEASER\nproject to ready-to-run simulation models for Modelica library IBPSA.\nIBPSA focuses on free floating temperature without an ideal heater.\nIn contrast, AixLib focuses on ideal heat demand calculation, and\nBESMod on the coupling to state-of-the-art energy systems.\nThese models should simulate in Dymola, OpenModelica and JModelica.\nYou can run this example using the [jupyter-notebook](https://mybinder.org/v2/gh/RWTH-EBC/TEASER/main?labpath=docs%2Fjupyter_notebooks)\n" }, { "cell_type": "code", diff --git a/docs/jupyter_notebooks/e6_generate_building.ipynb b/docs/jupyter_notebooks/e6_generate_building.ipynb index dea14bf6b..936f644fd 100644 --- a/docs/jupyter_notebooks/e6_generate_building.ipynb +++ b/docs/jupyter_notebooks/e6_generate_building.ipynb @@ -51,7 +51,7 @@ "execution_count": null, "metadata": {}, "outputs": [], - "source": "from teaser.logic.buildingobjects.thermalzone import ThermalZone\n\ntz = ThermalZone(parent=bldg)\ntz.name = \"LivingRoom\"\ntz.area = 140.0\ntz.volume = tz.area * bldg.number_of_floors * bldg.height_of_floors\ntz.infiltration_rate = 0.5\n" + "source": "from teaser.logic.buildingobjects.thermalzone import ThermalZone\n\ntz = ThermalZone(parent=bldg)\ntz.name = \"LivingRoom\"\ntz.area = 140.0\ntz.volume = tz.area * bldg.number_of_floors * bldg.height_of_floors\n" }, { "cell_type": "markdown", diff --git a/docs/source/examples/e11_export_besmod_models.md b/docs/source/examples/e11_export_besmod_models.md new file mode 100644 index 000000000..a94e5f42d --- /dev/null +++ b/docs/source/examples/e11_export_besmod_models.md @@ -0,0 +1,234 @@ + +# Example 11: Export Modelica models for BESMod library using TEASER API +This module demonstrates how to export building models from a TEASER project +to ready-to-run simulation models for the Modelica BESMod library. +BESMod enables seamless integration with state-of-the-art energy systems, +such as heat pumps and photovoltaic systems. These systems can be utilized +to generate primary energy demand curves (e.g., for electricity or gas) or +to conduct in-depth analyses of building energy systems. In contrast, +AixLib focuses on ideal heat demand calculation, and IBPSA on +free floating temperature without an ideal heater. +You can execute this example using +[jupyter-notebook](https://mybinder.org/v2/gh/RWTH-EBC/TEASER/main?labpath=docs%2Fjupyter_notebooks) + +```python +import teaser.examples.e1_generate_archetype as e1 +import teaser.logic.utilities as utilities +import os +``` + +## Standard export +In e1_generate_archetype we created a Project with three archetype +buildings to get this Project we rerun this example + +```python +prj = e1.example_generate_archetype() +``` + +Configure project settings to ensure compatibility with BESMod. The BESMod +library uses the AixLib.ThermalZones.ReducedOrder.ThermalZone.ThermalZone model +with 4 elements for the demand building model. Other numbers of elements are possible, +but compatability with ARoof for PV and AFloor for UFH systems must be checked first. +Set these parameters in the project: + +```python +prj.used_library_calc = 'AixLib' +prj.number_of_elements_calc = 4 +``` + +BESMod allows building models to be included in predefined example energy systems: + +```python +examples = [ + "TEASERHeatLoadCalculation", # Ideal electric heater for heat load calculations + "HeatPumpMonoenergetic", # Heat pump with radiators, buffer and DHW storage, and PV + "GasBoilerBuildingOnly" # Gas boiler with radiators +] +``` + +For the hydraulic systems, you have to specify a nominal supply temperature +for heat transfer, e.g. in radiators. +Multiple options are available: + +Option 1: Set a single value for all buildings and zones. + +```python +THydSup_nominal = 55 + 273.15 +``` + +Option 2: Set values for each building or thermal zone. + +```python +THydSup_nominal = {"ResidentialBuilding": 328.15, + "OfficeBuilding": 328.15, + "InstituteBuilding": {"Office": 343.15, + "Floor": 343.15, + "Storage": 343.15, + "Meeting": 343.15, + "Restroom": 343.15, + "ICT": 343.15, + "Laboratory": 328.15}, + "InstituteBuildingMoisture": 343.15, + "ResidentialBuildingTabula": 328.15, + "ResidentialBuildingTabulaMulti": 328.15} +``` + +Option 3: Specify values based on construction year. +Here, the value of the next higher specified year is set to the building. +The classification here is taken from: +https://www.ffe.de/projekte/waermepumpen-fahrplan-finanzielle-kipppunkte-zur-modernisierung-mit-waermepumpen-im-wohngebaeudebestand/ + +```python +THydSup_nominal = { + 1950: 90 + 273.15, + 1980: 70 + 273.15, + 2010: 55 + 273.15, + 2024: 35 + 273.15 +} +``` + +In the examples, the parameters for BESMod.Systems.UserProfiles.TEASERProfiles are configured, +including internal gains and heating profiles for each zone. +BESMod requires 24-hour heating profiles, which are used +to define the parameters of the `setBakTSetZone` Pulse block. +By default, the TEASER profiles are applied, but these can be customized if needed. + +Additionally, location-specific parameters must be set, which can be achieved using the following function. +The default values provided here correspond to Mannheim. + +```python +weather_file_path = utilities.get_full_path( + os.path.join( + "data", + "input", + "inputdata", + "weatherdata", + "DEU_BW_Mannheim_107290_TRY2010_12_Jahr_BBSR.mos")) + +prj.set_location_parameters(t_outside=262.65, + t_ground=286.15, + weather_file_path=weather_file_path, + calc_all_buildings=True) +``` + +To make sure the parameters are calculated correctly we recommend to +run prj.calc_all_buildings() function which is here already done in the set_location_parameters function. + +Export all buildings to BESMod and include them in predefined example systems. + +```python +path = prj.export_besmod( + THydSup_nominal=THydSup_nominal, + path=None, + examples=examples +) +``` + +## Partial retrofit export +The partial retrofit option of the energy system in BESMod can also be utilized. +For more information on this see BESMod.UsersGuide.GettingStarted.Parameterization. +To enable this here, the nominal heat flow of each zone in the building must be extracted prior to the retrofit. + +```python +QBuiOld_flow_design = { + bldg.name: { + tz.name: tz.model_attr.heat_load for tz in bldg.thermal_zones + } + for bldg in prj.buildings +} +``` + +Retrofit project buildings and recalculate parameters. + +```python +prj.name = "ArchetypeExample_partial_retrofit" +prj.retrofit_all_buildings( + year_of_retrofit=2015, + type_of_retrofit="adv_retrofit", + window_type='Alu- oder Stahlfenster, Isolierverglasung', + material='EPS_perimeter_insulation_top_layer' +) +prj.calc_all_buildings() +``` + +By default, radiator transfer systems are not retrofitted when the +QBuiOld_flow_design parameter is provided and differs from the new nominal heat flow. +Additionally, new THydSup_nominal temperatures can be specified alongside +THydSupOld_design values, which are used for radiator sizing but not for control settings. + +```python +path = prj.export_besmod( + THydSup_nominal=THydSup_nominal, + QBuiOld_flow_design=QBuiOld_flow_design, + path=None, + examples=examples +) +``` + +## Custom export +Additionally, we have the flexibility to define custom templates for including buildings in specific setups. +For instance, a custom template is defined here to include the building in the +ModelicaConferencePaper example from BESMod, which features an integrated battery system. + +Custom template +``` +< %namespace file = "/modelica_language/" import="get_list" / > +within ${bldg.parent.name}.${bldg.name}; +model ModelicaConferencePaper${bldg.name} + extends BESMod.Examples.ModelicaConferencePaper.PartialModelicaConferenceUseCase( + redeclare ${bldg.name} building, + redeclare BESMod.Systems.UserProfiles.TEASERProfiles + userProfiles(fileNameIntGains=Modelica.Utilities.Files.loadResource( + "modelica://${bldg.parent.name}/${bldg.name}/InternalGains_${bldg.name}.txt"), + setBakTSetZone(amplitude=${get_list(setBakTSetZone_amplitude)}, + width =${get_list(setBakTSetZone_width)}, + startTime =${get_list(setBakTSetZone_startTime)})), + systemParameters(nZones=${len(bldg.thermal_zones)}, + QBui_flow_nominal = building.QRec_flow_nominal, + TOda_nominal =${TOda_nominal}, + TSetZone_nominal =${get_list(TSetZone_nominal)}, + THydSup_nominal =${THydSup_nominal}, + QBuiOld_flow_design =${QBuiOld_flow_design}, + THydSupOld_design =${THydSupOld_design}, + filNamWea = Modelica.Utilities.Files.loadResource( + "modelica://${bldg.parent.name}/Resources/${bldg.parent.weather_file_name}"))); + + extends Modelica.Icons.Example; + + annotation(experiment(StopTime=172800, + Interval=600, + Tolerance=1e-06), + __Dymola_Commands(file= + "Resources/Scripts/Dymola/${bldg.name}/ModelicaConferencePaper${bldg.name}.mos" + "Simulate and plot")); +end +ModelicaConferencePaper${bldg.name}; +``` + +```python +prj.name = "ArchetypeExample_custom" + +custom_template_path = os.path.join( + os.path.dirname(__file__), "examplefiles", "custom_besmod_templates" +) +custom_example_template = {"ModelicaConferencePaper": os.path.join(custom_template_path, "custom_template.txt")} +``` + +The template also includes a .mos script as part of its annotation. +By default, the provided examples export a basic "simulate and plot" script, +which is incorporated into their annotation, as shown in the custom example. +Additionally, you have the flexibility to modify the template for existing examples +and define custom scripts for your tailored examples. + +```python +custom_script = {"HeatPumpMonoenergetic": os.path.join(custom_template_path, "custom_script_hp_mono.txt"), + "ModelicaConferencePaper": os.path.join(custom_template_path, "custom_script.txt")} + +path = prj.export_besmod( + THydSup_nominal=THydSup_nominal, + path=None, + examples=examples, + custom_examples=custom_example_template, + custom_script=custom_script +) +``` diff --git a/docs/source/examples/e2_export_aixlib_models.md b/docs/source/examples/e2_export_aixlib_models.md index b05643f89..c31c45196 100644 --- a/docs/source/examples/e2_export_aixlib_models.md +++ b/docs/source/examples/e2_export_aixlib_models.md @@ -1,9 +1,11 @@ # Example 2: Export Modelica models for AixLib library using TEASER API - This module contains an example how to export buildings from a TEASER -project to ready-to-run simulation models for Modelica library AixLib. These -models will only simulate using Dymola, the reason for this are state +project to ready-to-run simulation models for Modelica library AixLib. +AixLib focuses on ideal hat demand calculation. In contrast, +IBPSA focuses on the free floating temperature and has no ideal heater, +and BESMod on the coupling to state-of-the-art energy systems. +These models will only simulate using Dymola, the reason for this are state machines that are used in one AixLib specific AHU model. You can run this example using the [jupyter-notebook](https://mybinder.org/v2/gh/RWTH-EBC/TEASER/main?labpath=docs%2Fjupyter_notebooks) diff --git a/docs/source/examples/e3_export_ibpsa_models.md b/docs/source/examples/e3_export_ibpsa_models.md index 81d9d0485..9eef48991 100644 --- a/docs/source/examples/e3_export_ibpsa_models.md +++ b/docs/source/examples/e3_export_ibpsa_models.md @@ -1,8 +1,11 @@ # Example 3: Export Modelica models for IBPSA library using TEASER API This module contains an example how to export buildings from a TEASER -project to ready-to-run simulation models for Modelica library IBPSA. These -models should simulate in Dymola, OpenModelica and JModelica. +project to ready-to-run simulation models for Modelica library IBPSA. +IBPSA focuses on free floating temperature without an ideal heater. +In contrast, AixLib focuses on ideal heat demand calculation, and +BESMod on the coupling to state-of-the-art energy systems. +These models should simulate in Dymola, OpenModelica and JModelica. You can run this example using the [jupyter-notebook](https://mybinder.org/v2/gh/RWTH-EBC/TEASER/main?labpath=docs%2Fjupyter_notebooks) ```python diff --git a/docs/source/examples/e6_generate_building.md b/docs/source/examples/e6_generate_building.md index 27bde30d7..e8274307b 100644 --- a/docs/source/examples/e6_generate_building.md +++ b/docs/source/examples/e6_generate_building.md @@ -54,7 +54,6 @@ tz = ThermalZone(parent=bldg) tz.name = "LivingRoom" tz.area = 140.0 tz.volume = tz.area * bldg.number_of_floors * bldg.height_of_floors -tz.infiltration_rate = 0.5 ``` Instantiate BoundaryConditions and load conditions for `Living`. diff --git a/setup.py b/setup.py index f31793cad..a60b9c7f2 100644 --- a/setup.py +++ b/setup.py @@ -48,6 +48,7 @@ "teaser.data.output.modelicatemplate", "teaser.data.output.reports", "teaser.data.output.modelicatemplate.AixLib", + "teaser.data.output.modelicatemplate.BESMod", "teaser.data.output.modelicatemplate.IBPSA", "teaser.examples", "teaser.examples.verification", @@ -73,6 +74,15 @@ "AixLib_ThermalZoneRecord_ThreeElement", "AixLib_ThermalZoneRecord_FourElement", ], + "teaser.data.output.modelicatemplate.BESMod": [ + "Building", + "Example_GasBoilerBuildingOnly", + "Example_HeatPumpMonoenergetic", + "Example_TEASERHeatLoadCalculation", + "Script_GasBoilerBuildingOnly", + "Script_HeatPumpMonoenergetic", + "Script_TEASERHeatLoadCalculation", + ], "teaser.data.output.modelicatemplate.IBPSA": [ "IBPSA_OneElement", "IBPSA_TwoElements", diff --git a/teaser/__init__.py b/teaser/__init__.py index 75c88998a..10c33c456 100644 --- a/teaser/__init__.py +++ b/teaser/__init__.py @@ -7,7 +7,7 @@ import sys import os -__version__ = "1.0.1" +__version__ = "1.1.0" new_path = os.path.join(os.path.expanduser('~'), ("TEASEROutput")) diff --git a/teaser/data/input/inputdata/UseConditions.json b/teaser/data/input/inputdata/UseConditions.json index 662ec799d..6474cdb4d 100644 --- a/teaser/data/input/inputdata/UseConditions.json +++ b/teaser/data/input/inputdata/UseConditions.json @@ -19,7 +19,7 @@ "lighting_efficiency_lumen": 150, "ratio_conv_rad_lighting": 0.9, "use_constant_infiltration": false, - "infiltration_rate": 0.2, + "base_infiltration": 0.2, "max_user_infiltration": 1.0, "max_overheating_infiltration": [ 3.0, @@ -190,7 +190,7 @@ "maintained_illuminance": 500.0, "lighting_efficiency_lumen": 150, "use_constant_infiltration": false, - "infiltration_rate": 0.2, + "base_infiltration": 0.2, "max_user_infiltration": 1.0, "max_overheating_infiltration": [ 3.0, @@ -361,7 +361,7 @@ "lighting_efficiency_lumen": 150, "ratio_conv_rad_lighting": 0.9, "use_constant_infiltration": false, - "infiltration_rate": 0.2, + "base_infiltration": 0.2, "max_user_infiltration": 1.0, "max_overheating_infiltration": [ 3.0, @@ -532,7 +532,7 @@ "lighting_efficiency_lumen": 150, "ratio_conv_rad_lighting": 0.9, "use_constant_infiltration": false, - "infiltration_rate": 0.2, + "base_infiltration": 0.2, "max_user_infiltration": 1.0, "max_overheating_infiltration": [ 3.0, @@ -703,7 +703,7 @@ "lighting_efficiency_lumen": 150, "ratio_conv_rad_lighting": 0.9, "use_constant_infiltration": false, - "infiltration_rate": 0.2, + "base_infiltration": 0.2, "max_user_infiltration": 1.0, "max_overheating_infiltration": [ 3.0, @@ -874,7 +874,7 @@ "lighting_efficiency_lumen": 150, "ratio_conv_rad_lighting": 0.9, "use_constant_infiltration": false, - "infiltration_rate": 0.2, + "base_infiltration": 0.2, "max_user_infiltration": 1.0, "max_overheating_infiltration": [ 3.0, @@ -1045,7 +1045,7 @@ "lighting_efficiency_lumen": 150, "ratio_conv_rad_lighting": 0.9, "use_constant_infiltration": false, - "infiltration_rate": 0.2, + "base_infiltration": 0.2, "max_user_infiltration": 1.0, "max_overheating_infiltration": [ 3.0, @@ -1216,7 +1216,7 @@ "lighting_efficiency_lumen": 150, "ratio_conv_rad_lighting": 0.9, "use_constant_infiltration": false, - "infiltration_rate": 0.2, + "base_infiltration": 0.2, "max_user_infiltration": 1.0, "max_overheating_infiltration": [ 3.0, @@ -1387,7 +1387,7 @@ "lighting_efficiency_lumen": 150, "ratio_conv_rad_lighting": 0.9, "use_constant_infiltration": false, - "infiltration_rate": 0.2, + "base_infiltration": 0.2, "max_user_infiltration": 1.0, "max_overheating_infiltration": [ 3.0, @@ -1558,7 +1558,7 @@ "lighting_efficiency_lumen": 150, "ratio_conv_rad_lighting": 0.5, "use_constant_infiltration": false, - "infiltration_rate": 0.2, + "base_infiltration": 0.2, "max_user_infiltration": 1.0, "max_overheating_infiltration": [ 3.0, @@ -1729,7 +1729,7 @@ "lighting_efficiency_lumen": 150, "ratio_conv_rad_lighting": 0.5, "use_constant_infiltration": false, - "infiltration_rate": 0.2, + "base_infiltration": 0.2, "max_user_infiltration": 1.0, "max_overheating_infiltration": [ 3.0, @@ -1900,7 +1900,7 @@ "lighting_efficiency_lumen": 150, "ratio_conv_rad_lighting": 0.9, "use_constant_infiltration": false, - "infiltration_rate": 0.2, + "base_infiltration": 0.2, "max_user_infiltration": 1.0, "max_overheating_infiltration": [ 3.0, @@ -2071,7 +2071,7 @@ "lighting_efficiency_lumen": 150, "ratio_conv_rad_lighting": 0.9, "use_constant_infiltration": false, - "infiltration_rate": 0.2, + "base_infiltration": 0.2, "max_user_infiltration": 1.0, "max_overheating_infiltration": [ 3.0, @@ -2242,7 +2242,7 @@ "lighting_efficiency_lumen": 150, "ratio_conv_rad_lighting": 0.9, "use_constant_infiltration": false, - "infiltration_rate": 0.2, + "base_infiltration": 0.2, "max_user_infiltration": 1.0, "max_overheating_infiltration": [ 3.0, @@ -2413,7 +2413,7 @@ "lighting_efficiency_lumen": 150, "ratio_conv_rad_lighting": 0.9, "use_constant_infiltration": false, - "infiltration_rate": 0.2, + "base_infiltration": 0.2, "max_user_infiltration": 1.0, "max_overheating_infiltration": [ 3.0, @@ -2584,7 +2584,7 @@ "lighting_efficiency_lumen": 150, "ratio_conv_rad_lighting": 0.9, "use_constant_infiltration": false, - "infiltration_rate": 0.2, + "base_infiltration": 0.2, "max_user_infiltration": 1.0, "max_overheating_infiltration": [ 3.0, @@ -2755,7 +2755,7 @@ "lighting_efficiency_lumen": 150, "ratio_conv_rad_lighting": 0.9, "use_constant_infiltration": false, - "infiltration_rate": 0.2, + "base_infiltration": 0.2, "max_user_infiltration": 1.0, "max_overheating_infiltration": [ 3.0, @@ -2926,7 +2926,7 @@ "lighting_efficiency_lumen": 150, "ratio_conv_rad_lighting": 0.9, "use_constant_infiltration": false, - "infiltration_rate": 0.2, + "base_infiltration": 0.2, "max_user_infiltration": 1.0, "max_overheating_infiltration": [ 3.0, @@ -3097,7 +3097,7 @@ "lighting_efficiency_lumen": 150, "ratio_conv_rad_lighting": 0.9, "use_constant_infiltration": false, - "infiltration_rate": 0.2, + "base_infiltration": 0.2, "max_user_infiltration": 1.0, "max_overheating_infiltration": [ 3.0, @@ -3268,7 +3268,7 @@ "lighting_efficiency_lumen": 150, "ratio_conv_rad_lighting": 0.9, "use_constant_infiltration": false, - "infiltration_rate": 0.2, + "base_infiltration": 0.2, "max_user_infiltration": 1.0, "max_overheating_infiltration": [ 3.0, @@ -3439,7 +3439,7 @@ "lighting_efficiency_lumen": 150, "ratio_conv_rad_lighting": 0.9, "use_constant_infiltration": false, - "infiltration_rate": 0.2, + "base_infiltration": 0.2, "max_user_infiltration": 1.0, "max_overheating_infiltration": [ 3.0, @@ -3610,7 +3610,7 @@ "lighting_efficiency_lumen": 150, "ratio_conv_rad_lighting": 0.9, "use_constant_infiltration": false, - "infiltration_rate": 0.2, + "base_infiltration": 0.2, "max_user_infiltration": 1.0, "max_overheating_infiltration": [ 3.0, @@ -3781,7 +3781,7 @@ "lighting_efficiency_lumen": 150, "ratio_conv_rad_lighting": 0.9, "use_constant_infiltration": false, - "infiltration_rate": 0.2, + "base_infiltration": 0.2, "max_user_infiltration": 1.0, "max_overheating_infiltration": [ 3.0, @@ -3952,7 +3952,7 @@ "lighting_efficiency_lumen": 150, "ratio_conv_rad_lighting": 0.9, "use_constant_infiltration": false, - "infiltration_rate": 0.2, + "base_infiltration": 0.2, "max_user_infiltration": 1.0, "max_overheating_infiltration": [ 3.0, @@ -4121,7 +4121,7 @@ "fixed_lighting_power": 10.8, "ratio_conv_rad_lighting": 0.9, "use_constant_infiltration": false, - "infiltration_rate": 0.2, + "base_infiltration": 0.2, "max_user_infiltration": 1.0, "max_overheating_infiltration": [ 3.0, @@ -4292,7 +4292,7 @@ "lighting_efficiency_lumen": 150, "ratio_conv_rad_lighting": 0.9, "use_constant_infiltration": false, - "infiltration_rate": 0.2, + "base_infiltration": 0.2, "max_user_infiltration": 1.0, "max_overheating_infiltration": [ 3.0, @@ -4463,7 +4463,7 @@ "lighting_efficiency_lumen": 150, "ratio_conv_rad_lighting": 0.9, "use_constant_infiltration": false, - "infiltration_rate": 0.2, + "base_infiltration": 0.2, "max_user_infiltration": 1.0, "max_overheating_infiltration": [ 3.0, @@ -4634,7 +4634,7 @@ "lighting_efficiency_lumen": 150, "ratio_conv_rad_lighting": 0.9, "use_constant_infiltration": false, - "infiltration_rate": 0.2, + "base_infiltration": 0.2, "max_user_infiltration": 1.0, "max_overheating_infiltration": [ 3.0, @@ -4805,7 +4805,7 @@ "lighting_efficiency_lumen": 150, "ratio_conv_rad_lighting": 0.9, "use_constant_infiltration": false, - "infiltration_rate": 0.2, + "base_infiltration": 0.2, "max_user_infiltration": 1.0, "max_overheating_infiltration": [ 3.0, @@ -4976,7 +4976,7 @@ "lighting_efficiency_lumen": 150, "ratio_conv_rad_lighting": 0.9, "use_constant_infiltration": false, - "infiltration_rate": 0.2, + "base_infiltration": 0.2, "max_user_infiltration": 1.0, "max_overheating_infiltration": [ 3.0, @@ -5147,7 +5147,7 @@ "lighting_efficiency_lumen": 150, "ratio_conv_rad_lighting": 0.9, "use_constant_infiltration": false, - "infiltration_rate": 0.2, + "base_infiltration": 0.2, "max_user_infiltration": 1.0, "max_overheating_infiltration": [ 3.0, @@ -5318,7 +5318,7 @@ "lighting_efficiency_lumen": 150, "ratio_conv_rad_lighting": 0.9, "use_constant_infiltration": false, - "infiltration_rate": 0.2, + "base_infiltration": 0.2, "max_user_infiltration": 1.0, "max_overheating_infiltration": [ 3.0, @@ -5489,7 +5489,7 @@ "lighting_efficiency_lumen": 150, "ratio_conv_rad_lighting": 0.9, "use_constant_infiltration": false, - "infiltration_rate": 0.2, + "base_infiltration": 0.2, "max_user_infiltration": 1.0, "max_overheating_infiltration": [ 3.0, @@ -5660,7 +5660,7 @@ "lighting_efficiency_lumen": 150, "ratio_conv_rad_lighting": 0.9, "use_constant_infiltration": false, - "infiltration_rate": 0.2, + "base_infiltration": 0.2, "max_user_infiltration": 1.0, "max_overheating_infiltration": [ 3.0, @@ -5831,7 +5831,7 @@ "lighting_efficiency_lumen": 150, "ratio_conv_rad_lighting": 0.9, "use_constant_infiltration": false, - "infiltration_rate": 0.2, + "base_infiltration": 0.2, "max_user_infiltration": 1.0, "max_overheating_infiltration": [ 3.0, @@ -6002,7 +6002,7 @@ "lighting_efficiency_lumen": 150, "ratio_conv_rad_lighting": 0.9, "use_constant_infiltration": false, - "infiltration_rate": 0.2, + "base_infiltration": 0.2, "max_user_infiltration": 1.0, "max_overheating_infiltration": [ 3.0, @@ -6173,7 +6173,7 @@ "lighting_efficiency_lumen": 150, "ratio_conv_rad_lighting": 0.9, "use_constant_infiltration": false, - "infiltration_rate": 0.2, + "base_infiltration": 0.2, "max_user_infiltration": 1.0, "max_overheating_infiltration": [ 3.0, @@ -6344,7 +6344,7 @@ "lighting_efficiency_lumen": 150, "ratio_conv_rad_lighting": 0.9, "use_constant_infiltration": false, - "infiltration_rate": 0.2, + "base_infiltration": 0.2, "max_user_infiltration": 1.0, "max_overheating_infiltration": [ 3.0, @@ -6515,7 +6515,7 @@ "lighting_efficiency_lumen": 150, "ratio_conv_rad_lighting": 0.9, "use_constant_infiltration": false, - "infiltration_rate": 0.2, + "base_infiltration": 0.2, "max_user_infiltration": 1.0, "max_overheating_infiltration": [ 3.0, @@ -6686,7 +6686,7 @@ "lighting_efficiency_lumen": 150, "ratio_conv_rad_lighting": 0.9, "use_constant_infiltration": false, - "infiltration_rate": 0.2, + "base_infiltration": 0.2, "max_user_infiltration": 1.0, "max_overheating_infiltration": [ 3.0, @@ -6857,7 +6857,7 @@ "lighting_efficiency_lumen": 150, "ratio_conv_rad_lighting": 0.9, "use_constant_infiltration": false, - "infiltration_rate": 0.2, + "base_infiltration": 0.2, "max_user_infiltration": 1.0, "max_overheating_infiltration": [ 3.0, @@ -7028,7 +7028,7 @@ "lighting_efficiency_lumen": 150, "ratio_conv_rad_lighting": 0.9, "use_constant_infiltration": false, - "infiltration_rate": 0.2, + "base_infiltration": 0.2, "max_user_infiltration": 1.0, "max_overheating_infiltration": [ 3.0, @@ -7199,7 +7199,7 @@ "lighting_efficiency_lumen": 150, "ratio_conv_rad_lighting": 0.9, "use_constant_infiltration": false, - "infiltration_rate": 0.2, + "base_infiltration": 0.2, "max_user_infiltration": 1.0, "max_overheating_infiltration": [ 3.0, @@ -7370,7 +7370,7 @@ "lighting_efficiency_lumen": 150, "ratio_conv_rad_lighting": 0.5, "use_constant_infiltration": false, - "infiltration_rate": 0.2, + "base_infiltration": 0.2, "max_user_infiltration": 1.0, "max_overheating_infiltration": [ 3.0, @@ -7541,7 +7541,7 @@ "lighting_efficiency_lumen": 150, "ratio_conv_rad_lighting": 0.5, "use_constant_infiltration": false, - "infiltration_rate": 0.2, + "base_infiltration": 0.2, "max_user_infiltration": 1.0, "max_overheating_infiltration": [ 3.0, diff --git a/teaser/data/input/teaserjson_input.py b/teaser/data/input/teaserjson_input.py index 258e4e96f..9aef45660 100644 --- a/teaser/data/input/teaserjson_input.py +++ b/teaser/data/input/teaserjson_input.py @@ -195,8 +195,8 @@ def load_teaser_json(path, project): tz.use_conditions.use_constant_infiltration = zone_in["use_conditions"][ "use_constant_infiltration" ] - tz.use_conditions.infiltration_rate = zone_in["use_conditions"][ - "infiltration_rate" + tz.use_conditions.base_infiltration = zone_in["use_conditions"][ + "base_infiltration" ] tz.use_conditions.max_user_infiltration = zone_in["use_conditions"][ "max_user_infiltration" diff --git a/teaser/data/input/usecond_input.py b/teaser/data/input/usecond_input.py index f9ff45ce9..779b4a2a6 100644 --- a/teaser/data/input/usecond_input.py +++ b/teaser/data/input/usecond_input.py @@ -59,8 +59,8 @@ def load_use_conditions(use_cond, zone_usage, data_class): use_cond.use_constant_infiltration = conditions_bind[zone_usage][ "use_constant_infiltration" ] - use_cond.infiltration_rate = conditions_bind[zone_usage][ - "infiltration_rate" + use_cond.base_infiltration = conditions_bind[zone_usage][ + "base_infiltration" ] use_cond.max_user_infiltration = conditions_bind[zone_usage][ "max_user_infiltration" diff --git a/teaser/data/output/aixlib_output.py b/teaser/data/output/aixlib_output.py index d6310812b..c7458641f 100644 --- a/teaser/data/output/aixlib_output.py +++ b/teaser/data/output/aixlib_output.py @@ -6,6 +6,7 @@ from mako.template import Template from mako.lookup import TemplateLookup import teaser.logic.utilities as utilities +import teaser.data.output.modelica_output as modelica_output def export_multizone( @@ -99,20 +100,22 @@ def export_multizone( "data/output/modelicatemplate/modelica_test_script"), lookup=lookup) + dir_resources = utilities.create_path(os.path.join(path, "Resources")) + dir_scripts = utilities.create_path(os.path.join(dir_resources, "Scripts")) + dir_dymola = utilities.create_path(os.path.join(dir_scripts, "Dymola")) + uses = [ 'Modelica(version="' + prj.modelica_info.version + '")', 'AixLib(version="' + prj.buildings[-1].library_attr.version + '")'] - _help_package( + modelica_output.create_package( path=path, name=prj.name, - uses=uses, - within=None) - _help_package_order( + uses=uses) + modelica_output.create_package_order( path=path, package_list=buildings, - addition=None, extra=None) - _copy_weather_data(prj.weather_file_path, path) + modelica_output.copy_weather_data(prj.weather_file_path, path) for i, bldg in enumerate(buildings): @@ -134,12 +137,11 @@ def export_multizone( bldg.library_attr.modelica_gains_boundary( path=bldg_path) - _help_package(path=bldg_path, name=bldg.name, within=bldg.parent.name) - _help_package_order( + modelica_output.create_package(path=bldg_path, name=bldg.name, within=bldg.parent.name) + modelica_output.create_package_order( path=bldg_path, package_list=[bldg], - addition=None, - extra=bldg.name + "_DataBase") + extra=[bldg.name + "_DataBase"]) if bldg.building_id is None: bldg.building_id = i @@ -164,15 +166,6 @@ def export_multizone( out_file.close() - dir_resources = os.path.join(path, "Resources") - if not os.path.exists(dir_resources): - os.mkdir(dir_resources) - dir_scripts = os.path.join(dir_resources, "Scripts") - if not os.path.exists(dir_scripts): - os.mkdir(dir_scripts) - dir_dymola = os.path.join(dir_scripts, "Dymola") - if not os.path.exists(dir_dymola): - os.mkdir(dir_dymola) _help_test_script(bldg, dir_dymola, test_script_template) zone_path = os.path.join(bldg_path, bldg.name + "_DataBase") @@ -193,11 +186,11 @@ def export_multizone( out_file.close() - _help_package( + modelica_output.create_package( path=zone_path, name=bldg.name + '_DataBase', within=prj.name + '.' + bldg.name) - _help_package_order( + modelica_output.create_package_order( path=zone_path, package_list=bldg.thermal_zones, addition=bldg.name + "_", @@ -274,80 +267,6 @@ def _help_test_script(bldg, dir_dymola, test_script_template): out_file.close() -def _help_package(path, name, uses=None, within=None): - """creates a package.mo file - - private function, do not call - - Parameters - ---------- - - path : string - path of where the package.mo should be placed - name : string - name of the Modelica package - within : string - path of Modelica package containing this package - - """ - - package_template = Template(filename=utilities.get_full_path( - "data/output/modelicatemplate/package")) - with open(utilities.get_full_path(os.path.join( - path, "package.mo")), 'w') as out_file: - - out_file.write(package_template.render_unicode( - name=name, - within=within, - uses=uses)) - out_file.close() - - -def _help_package_order(path, package_list, addition=None, extra=None): - """creates a package.order file - - private function, do not call - - Parameters - ---------- - - path : string - path of where the package.order should be placed - package_list : [string] - name of all models or packages contained in the package - addition : string - if there should be a suffix in front of package_list.string it can - be specified - extra : string - an extra package or model not contained in package_list can be - specified - - """ - - order_template = Template(filename=utilities.get_full_path( - "data/output/modelicatemplate/package_order")) - with open(utilities.get_full_path( - path + "/" + "package" + ".order"), 'w') as out_file: - - out_file.write(order_template.render_unicode - (list=package_list, addition=addition, extra=extra)) - out_file.close() - - -def _copy_weather_data(source_path, destination_path): - """Copies the imported .mos weather file to the results folder. - - Parameters - ---------- - source_path : str - path of local weather file - destination_path : str - path of where the weather file should be placed - """ - - shutil.copy2(source_path, destination_path) - - def _copy_script_unit_tests(destination_path): """Copies the script to run the unit tests. diff --git a/teaser/data/output/besmod_output.py b/teaser/data/output/besmod_output.py new file mode 100644 index 000000000..ddf11c9d8 --- /dev/null +++ b/teaser/data/output/besmod_output.py @@ -0,0 +1,465 @@ +"""This module contains function for BESMod model generation""" + +import os +from typing import Optional, Union, List, Dict +from mako.template import Template +from mako.lookup import TemplateLookup +import teaser.logic.utilities as utilities +import teaser.data.output.modelica_output as modelica_output +from teaser.logic.buildingobjects.building import Building + + +def export_besmod( + buildings: Union[List[Building], Building], + prj: 'Project', + path: Optional[str] = None, + examples: Optional[List[str]] = None, + THydSup_nominal: Optional[Union[float, Dict[str, float]]] = None, + QBuiOld_flow_design: Optional[Dict[str, Dict[str, float]]] = None, + THydSupOld_design: Optional[Union[float, Dict[str, float]]] = None, + custom_examples: Optional[Dict[str, str]] = None, + custom_script: Optional[Dict[str, str]] = None +) -> None: + """ + Export building models for BESMod simulations. + + This function generates BESMod.Systems.Demand.Building.TEASERThermalZone models + for one or more TEASER buildings. It also allows exporting examples from + BESMod.Examples, including the building models. + + Parameters + ---------- + buildings : Union[List[Building], Building] + TEASER Building instances to export as BESMod models. Can be a single + Building or a list of Buildings. + prj : Project + TEASER Project instance containing project metadata such as library + versions and weather file paths. + examples : Optional[List[str]] + Names of BESMod examples to export alongside the building models. + Supported Examples: "TEASERHeatLoadCalculation", "HeatPumpMonoenergetic", and "GasBoilerBuildingOnly". + path : Optional[str] + Alternative output path for storing the exported files. If None, the default TEASER output path is used. + THydSup_nominal : Optional[Union[float, Dict[str, float]]] + Nominal supply temperature(s) for the hydraulic system. Required for + certain examples (e.g., HeatPumpMonoenergetic, GasBoilerBuildingOnly). + See docstring of teaser.data.output.besmod_output.convert_input() for further information. + QBuiOld_flow_design : Optional[Dict[str, Dict[str, float]]] + For partially retrofitted systems specify the old nominal heat flow + of all zones in the Buildings in a nested dictionary with + the building names and in a level below the zone names as keys. + By default, only the radiator transfer system is not retrofitted in BESMod. + THydSupOld_design : Optional[Union[float, Dict[str, float]]] + Design supply temperatures for old, non-retrofitted hydraulic systems. + custom_examples: Optional[Dict[str, str]] + Specify custom examples with a dictionary containing the example name as the key and + the path to the corresponding custom mako template as the value. + custom_script: Optional[Dict[str, str]] + Specify custom .mos scripts for the existing and custom examples with a dictionary + containing the example name as the key and the path to the corresponding custom mako template as the value. + + Raises + ------ + ValueError + If given example is not supported. + ValueError + If `THydSup_nominal` is not provided for examples requiring it. + AssertionError + If the used library for calculations is not AixLib. + NotImplementedError + If a building uses a thermal zone model other than the four-element model. + + Notes + ----- + The function uses Mako templates for generating Modelica models. + """ + + if prj.used_library_calc != "AixLib": + raise AttributeError("BESMod export is only implemented for AixLib calculation.") + + if examples is None: + examples = [] + + if not isinstance(examples, list): + examples = [examples] + + supported_examples = [ + "TEASERHeatLoadCalculation", + "HeatPumpMonoenergetic", + "GasBoilerBuildingOnly", + ] + + for exp in examples: + if exp not in supported_examples: + raise ValueError( + f"Example {exp} is not supported. " + f"Supported examples are {supported_examples}." + ) + + if THydSup_nominal is None and any( + example in examples for example in ["HeatPumpMonoenergetic", "GasBoilerBuildingOnly"] + ): + raise ValueError( + "Examples 'HeatPumpMonoenergetic' and 'GasBoilerBuildingOnly' " + "require the `THydSup_nominal` parameter." + ) + + t_hyd_sup_nominal_bldg = convert_input(THydSup_nominal, buildings) + t_hyd_sup_old_design_bldg = ( + convert_input(THydSupOld_design, buildings) + if THydSupOld_design + else {bldg.name: "systemParameters.THydSup_nominal" for bldg in buildings} + ) + + if QBuiOld_flow_design is None: + QBuiOld_flow_design = { + bldg.name: "systemParameters.QBui_flow_nominal" for bldg in buildings + } + else: + QBuiOld_flow_design = { + bldg.name: _convert_to_zone_array(bldg, QBuiOld_flow_design[bldg.name]) + for bldg in buildings + } + + if custom_script is None: + custom_script = {} + + dir_resources = utilities.create_path(os.path.join(path, "Resources")) + dir_scripts = utilities.create_path(os.path.join(dir_resources, "Scripts")) + dir_dymola = utilities.create_path(os.path.join(dir_scripts, "Dymola")) + template_path = utilities.get_full_path("data/output/modelicatemplate") + lookup = TemplateLookup(directories=[template_path]) + + zone_template_4 = Template( + filename=os.path.join(template_path, "AixLib/AixLib_ThermalZoneRecord_FourElement"), + lookup=lookup) + building_template = Template( + filename=os.path.join(template_path, "BESMod/Building"), + lookup=lookup) + + uses = [ + 'Modelica(version="' + prj.modelica_info.version + '")', + 'AixLib(version="' + prj.buildings[-1].library_attr.version + '")', + 'BESMod(version="' + prj.buildings[-1].library_attr.besmod_version + '")'] + modelica_output.create_package( + path=path, + name=prj.name, + uses=uses) + modelica_output.create_package_order( + path=path, + package_list=buildings) + modelica_output.copy_weather_data(prj.weather_file_path, dir_resources) + + for i, bldg in enumerate(buildings): + bldg.bldg_height = bldg.number_of_floors * bldg.height_of_floors + start_time_zones = [] + width_zones = [] + amplitude_zones = [] + t_set_zone_nominal = [] + for tz in bldg.thermal_zones: + heating_profile = tz.use_conditions.heating_profile + t_set_nominal, start_time, width, amplitude = _convert_heating_profile(heating_profile) + t_set_zone_nominal.append(t_set_nominal) + amplitude_zones.append(amplitude) + start_time_zones.append(start_time) + width_zones.append(width) + + bldg_path = os.path.join(path, bldg.name) + utilities.create_path(bldg_path) + utilities.create_path(os.path.join(bldg_path, bldg.name + "_DataBase")) + bldg.library_attr.modelica_gains_boundary(path=bldg_path) + + with open(utilities.get_full_path( + os.path.join(bldg_path, bldg.name + ".mo")), 'w') as out_file: + out_file.write(building_template.render_unicode( + bldg=bldg)) + out_file.close() + + def write_example_mo(example_template, example): + with open(utilities.get_full_path( + os.path.join(bldg_path, example + bldg.name + ".mo")), 'w') as model_file: + model_file.write(example_template.render_unicode( + bldg=bldg, + project=prj, + TOda_nominal=bldg.thermal_zones[0].t_outside, + THydSup_nominal=t_hyd_sup_nominal_bldg[bldg.name], + TSetZone_nominal=t_set_zone_nominal, + QBuiOld_flow_design=QBuiOld_flow_design[bldg.name], + THydSupOld_design=t_hyd_sup_old_design_bldg[bldg.name], + setBakTSetZone_amplitude=amplitude_zones, + setBakTSetZone_startTime=start_time_zones, + setBakTSetZone_width=width_zones)) + model_file.close() + + for exp in examples: + exp_template = Template( + filename=utilities.get_full_path( + "data/output/modelicatemplate/BESMod/Example_" + exp), + lookup=lookup) + if exp in custom_script.keys(): + example_sim_plot_script = Template( + filename=custom_script[exp], + lookup=lookup) + else: + example_sim_plot_script = Template( + filename=utilities.get_full_path( + "data/output/modelicatemplate/BESMod/Script_" + exp), + lookup=lookup) + _help_example_script(bldg, dir_dymola, example_sim_plot_script, exp) + write_example_mo(exp_template, exp) + bldg_package = [exp + bldg.name for exp in examples] + if custom_examples: + for exp, c_path in custom_examples.items(): + bldg_package.append(exp + bldg.name) + exp_template = Template( + filename=c_path, + lookup=lookup) + write_example_mo(exp_template, exp) + if exp in custom_script.keys(): + example_sim_plot_script = Template( + filename=custom_script[exp], + lookup=lookup) + _help_example_script(bldg, dir_dymola, example_sim_plot_script, exp) + + bldg_package.append(bldg.name + "_DataBase") + modelica_output.create_package(path=bldg_path, name=bldg.name, within=bldg.parent.name) + modelica_output.create_package_order( + path=bldg_path, + package_list=[bldg], + extra=bldg_package) + + zone_path = os.path.join(bldg_path, bldg.name + "_DataBase") + + for zone in bldg.thermal_zones: + zone.use_conditions.with_heating = False + with open(utilities.get_full_path(os.path.join( + zone_path, + bldg.name + '_' + zone.name + '.mo')), 'w') as out_file: + if type(zone.model_attr).__name__ == "FourElement": + out_file.write(zone_template_4.render_unicode(zone=zone)) + else: + raise NotImplementedError("BESMod export is only implemented for four elements.") + out_file.close() + + modelica_output.create_package( + path=zone_path, + name=bldg.name + '_DataBase', + within=prj.name + '.' + bldg.name) + modelica_output.create_package_order( + path=zone_path, + package_list=bldg.thermal_zones, + addition=bldg.name + "_") + + print("Exports can be found here:") + print(path) + + +def convert_input(building_zones_input: Union[float, Dict[Union[int, str], Union[float, Dict[str, float]]]], + buildings: List[Building]) -> Dict[str, str]: + """ + Convert input values for BESMod zone specific parameters to a dictionary. + + Supports single values, dictionaries keyed by construction year, or + dictionaries keyed by building names. + If single values are given then all buildings and zones get this values set. + If a dictionary keyed by construction year is given then all zones of a building get the + value set of the next higher year corresponding to the construction year of the building. + If a dictionary keyed by building name is given the value must be a single value for all zones + or another dictionary specifying for each zone name a value. + + Parameters + ---------- + building_zones_input : Union[float, Dict[Union[int, str], Union[float, Dict[str, float]]]] + Input value(s) for BESMod parameters. Can be a single value, a dictionary keyed by construction year, + or a dictionary keyed by building name. + Example: + - Single value: 328.15 + - Dictionary keyed by construction year: {1970: 348.15, 1990: 328.15} + - Dictionary keyed by building name: { + "Building1": 328.15, + "Building2": { + "Zone1": 328.15, + "Zone2": 308.15 + } + } + buildings : List[Building] + List of TEASER Building instances. + + Returns + ------- + Dict[str, str] + Dictionary mapping building names to BESMod parameter input strings. + + Raises + ------ + ValueError + If the input dictionary has invalid values. + KeyError + If the input dictionary is missing required keys. + """ + bldg_names = [bldg.name for bldg in buildings] + if isinstance(building_zones_input, (float, int)): + return {bldg.name: f"fill({building_zones_input},systemParameters.nZones)" for bldg in buildings} + elif isinstance(building_zones_input, dict): + t_hyd_sup_nominal_bldg = {} + if isinstance(list(building_zones_input.keys())[0], int): + for bldg in buildings: + temperature = _get_next_higher_year_value(building_zones_input, bldg.year_of_construction) + t_hyd_sup_nominal_bldg[bldg.name] = f"fill({temperature},systemParameters.nZones)" + elif set(list(building_zones_input.keys())) == set(bldg_names): + for bldg in buildings: + if isinstance(building_zones_input[bldg.name], (int, float)): + t_hyd_sup_nominal_bldg[ + bldg.name] = f"fill({building_zones_input[bldg.name]},systemParameters.nZones)" + elif isinstance(building_zones_input[bldg.name], dict): + t_hyd_sup_nominal_bldg[bldg.name] = _convert_to_zone_array(bldg, building_zones_input[bldg.name]) + else: + raise ValueError("If THydSup_nominal is specified for all buildings in a dictionary " + "the values must be either a single value for all thermal zones or " + "a dict with all building.thermal_zones.name as keys.") + else: + raise KeyError("If THydSup_nominal is given by a dictionary " + "the keys must be all building names or construction years.") + return t_hyd_sup_nominal_bldg + + +def _convert_to_zone_array(bldg, zone_dict): + """ + Convert a dictionary of zone values to a BESMod-compatible array string. + + Parameters + ---------- + bldg : Building + TEASER Building instance. + zone_dict : dict + Dictionary with zone names as keys and zone parameter values as values. + + Returns + ------- + str + Array string for BESMod parameter input. + + Raises + ------ + KeyError + If the dictionary is missing zone names present in the building. + """ + tz_names = [tz.name for tz in bldg.thermal_zones] + if set(tz_names) == set(list(zone_dict.keys())): + array_string = "{" + for tz in tz_names: + array_string += str(zone_dict[tz]) + "," + return array_string[:-1] + "}" + else: + raise KeyError(f"{set(tz_names) - set(list(zone_dict.keys()))} thermal zones missing in given dictionary.") + + +def _convert_heating_profile(heating_profile): + """ + Convert a 24-hour heating profile for BESMod export. + + This function analyzes a 24-hour heating profile to extract: + - The nominal temperature. + - Start time of setbacks (if any). + - Width of setback intervals. + - Amplitude of the heating variation. + + Parameters + ---------- + heating_profile : list[float] + List of 24 hourly heating temperatures. + + Returns + ------- + t_set_zone_nominal : float + Maximum temperature in the profile, used as the nominal set point. + start_time : int + Start time of the setback interval in seconds. + width : float + Width of the setback interval as a percentage of the day. + amplitude : float + Difference between the minimum and nominal temperatures. + + Raises + ------ + ValueError + If the profile has more than two distinct intervals or does not have 24 values. + """ + + if len(heating_profile) != 24: + raise ValueError("Only 24 hours heating profiles can be used for BESMod export.") + change_count = 0 + change_indexes = [] + for i in range(1, len(heating_profile)): + if heating_profile[i] != heating_profile[i - 1]: + change_count += 1 + change_indexes.append(i) + t_set_zone_nominal = max(heating_profile) + amplitude = min(heating_profile) - t_set_zone_nominal + if change_count == 0: + amplitude = 0 + start_time = 0 + width = 1e-50 + elif change_count == 1: + if heating_profile[0] < heating_profile[-1]: + start_time = 0 + width = 100 * change_indexes[0] / 24 + else: + start_time = change_indexes[0] * 3600 + width = 100 * (24 - change_indexes[0]) / 24 + elif change_count == 2: + start_time = change_indexes[1] * 3600 + width = 100 * (24 - change_indexes[1] + change_indexes[0]) / 24 + else: + raise ValueError("You have more than two temperature intervals in the heating profile." + "BESMod can only handel one heating set back.") + return t_set_zone_nominal, start_time, width, amplitude + + +def _get_next_higher_year_value(years_dict, given_year): + """ + Get the next higher value for a given year from a dictionary. + + Parameters + ---------- + years_dict : dict + Dictionary with years as keys and corresponding values. + given_year : int + Year to find the next higher value for. + + Returns + ------- + float or int + Value corresponding to the next higher year. If no higher year is found, + returns the value of the latest year. + """ + years = sorted(years_dict.keys()) + for year in years: + if year > given_year: + return years_dict[year] + return years_dict[years[-1]] + + +def _help_example_script(bldg, dir_dymola, test_script_template, example): + """ + Create a .mos script for simulating and plotting BESMod examples from a Mako template. + + Parameters + ---------- + bldg : Building + TEASER Building instance for which the script is created. + dir_dymola : str + Output directory for Dymola scripts. + test_script_template : Template + Mako template for the simulation script. + example : str + Name of the BESMod example. + """ + + dir_building = utilities.create_path(os.path.join(dir_dymola, bldg.name)) + with open(os.path.join(dir_building, example + bldg.name + ".mos"), 'w') as out_file: + out_file.write(test_script_template.render_unicode( + project=bldg.parent, + bldg=bldg + )) + out_file.close() diff --git a/teaser/data/output/ibpsa_output.py b/teaser/data/output/ibpsa_output.py index f4f9a9456..5d7947789 100644 --- a/teaser/data/output/ibpsa_output.py +++ b/teaser/data/output/ibpsa_output.py @@ -1,12 +1,12 @@ # Created May 2016 # TEASER Development Team -"""ibpsa_output +"""modelica_output This module contains function to call Templates for IBPSA model generation """ -import teaser.data.output.aixlib_output as ibpsa_output +import teaser.data.output.modelica_output as modelica_output import os.path import teaser.logic.utilities as utilities from mako.template import Template @@ -92,17 +92,15 @@ def export_ibpsa( "data/output/modelicatemplate/IBPSA/IBPSA_FourElements"), lookup=lookup) - ibpsa_output._help_package( + modelica_output.create_package( path=path, name=prj.name, - uses=uses, - within=None) - ibpsa_output._help_package_order( + uses=uses) + modelica_output.create_package_order( path=path, package_list=buildings, - addition=None, extra=None) - ibpsa_output._copy_weather_data(prj.weather_file_path, path) + modelica_output.copy_weather_data(prj.weather_file_path, path) for i, bldg in enumerate(buildings): @@ -118,16 +116,15 @@ def export_ibpsa( utilities.create_path(utilities.get_full_path( os.path.join(bldg_path, bldg.name + "_Models"))) - ibpsa_output._help_package( + modelica_output.create_package( path=bldg_path, name=bldg.name, within=bldg.parent.name) - ibpsa_output._help_package_order( + modelica_output.create_package_order( path=bldg_path, package_list=[], - addition=None, - extra=bldg.name + "_Models") + extra=[bldg.name + "_Models"]) zone_path = os.path.join( bldg_path, @@ -158,12 +155,12 @@ def export_ibpsa( library=library)) - ibpsa_output._help_package( + modelica_output.create_package( path=zone_path, name=bldg.name + "_Models", within=prj.name + '.' + bldg.name) - ibpsa_output._help_package_order( + modelica_output.create_package_order( path=zone_path, package_list=bldg.thermal_zones, addition=bldg.name + "_") diff --git a/teaser/data/output/modelica_output.py b/teaser/data/output/modelica_output.py new file mode 100644 index 000000000..11c019d59 --- /dev/null +++ b/teaser/data/output/modelica_output.py @@ -0,0 +1,82 @@ +"""This module contains functions for all modelica model generations with AixLib, IBPSA and BESMod""" + +import os +import shutil +from mako.template import Template +import teaser.logic.utilities as utilities + + +def create_package(path, name, uses=None, within=None): + """creates a package.mo file + + private function, do not call + + Parameters + ---------- + + path : string + path of where the package.mo should be placed + name : string + name of the Modelica package + uses : [string] + list of used versions for the package in the form 'Library_name(version="x.x.x")' + within : string + path of Modelica package containing this package + + """ + + package_template = Template(filename=utilities.get_full_path( + "data/output/modelicatemplate/package")) + with open(utilities.get_full_path(os.path.join( + path, "package.mo")), 'w') as out_file: + + out_file.write(package_template.render_unicode( + name=name, + within=within, + uses=uses)) + out_file.close() + + +def create_package_order(path, package_list, addition=None, extra=None): + """creates a package.order file + + private function, do not call + + Parameters + ---------- + + path : string + path of where the package.order should be placed + package_list : [buildings or thermal_zones] + objects with the attribute name of all models or packages contained in the package + addition : string + if there should be a prefix of package_list.string it can + be specified + extra : [string] + list of extra packages or models not contained in package_list can be + specified + + """ + + order_template = Template(filename=utilities.get_full_path( + "data/output/modelicatemplate/package_order")) + with open(utilities.get_full_path( + path + "/" + "package" + ".order"), 'w') as out_file: + + out_file.write(order_template.render_unicode + (list=package_list, addition=addition, extra=extra)) + out_file.close() + + +def copy_weather_data(source_path, destination_path): + """Copies the imported .mos weather file to the results folder. + + Parameters + ---------- + source_path : str + path of local weather file + destination_path : str + path of where the weather file should be placed + """ + + shutil.copy2(source_path, destination_path) diff --git a/teaser/data/output/modelicatemplate/AixLib/AixLib_ThermalZoneRecord_FourElement b/teaser/data/output/modelicatemplate/AixLib/AixLib_ThermalZoneRecord_FourElement index 40c0cf30a..a64db1799 100644 --- a/teaser/data/output/modelicatemplate/AixLib/AixLib_ThermalZoneRecord_FourElement +++ b/teaser/data/output/modelicatemplate/AixLib/AixLib_ThermalZoneRecord_FourElement @@ -67,7 +67,7 @@ record ${zone.parent.name}_${zone.name} "${zone.parent.name}_${zone.name}" lightingPowerSpecific = ${zone.use_conditions.lighting_power}, ratioConvectiveHeatLighting = ${zone.use_conditions.ratio_conv_rad_lighting}, useConstantACHrate = ${get_true_false(zone.use_conditions.use_constant_infiltration)}, - baseACH = ${zone.use_conditions.infiltration_rate}, + baseACH = ${zone.use_conditions.base_infiltration}, maxUserACH = ${zone.use_conditions.max_user_infiltration}, maxOverheatingACH = ${get_list(zone.use_conditions.max_overheating_infiltration)}, maxSummerACH = ${get_list(zone.use_conditions.max_summer_infiltration)}, diff --git a/teaser/data/output/modelicatemplate/AixLib/AixLib_ThermalZoneRecord_OneElement b/teaser/data/output/modelicatemplate/AixLib/AixLib_ThermalZoneRecord_OneElement index 8373993ad..80d2d4fef 100644 --- a/teaser/data/output/modelicatemplate/AixLib/AixLib_ThermalZoneRecord_OneElement +++ b/teaser/data/output/modelicatemplate/AixLib/AixLib_ThermalZoneRecord_OneElement @@ -66,7 +66,7 @@ record ${zone.parent.name}_${zone.name} "${zone.parent.name}_${zone.name}" lightingPowerSpecific = ${zone.use_conditions.lighting_power}, ratioConvectiveHeatLighting = ${zone.use_conditions.ratio_conv_rad_lighting}, useConstantACHrate = ${get_true_false(zone.use_conditions.use_constant_infiltration)}, - baseACH = ${zone.use_conditions.infiltration_rate}, + baseACH = ${zone.use_conditions.base_infiltration}, maxUserACH = ${zone.use_conditions.max_user_infiltration}, maxOverheatingACH = ${get_list(zone.use_conditions.max_overheating_infiltration)}, maxSummerACH = ${get_list(zone.use_conditions.max_summer_infiltration)}, diff --git a/teaser/data/output/modelicatemplate/AixLib/AixLib_ThermalZoneRecord_ThreeElement b/teaser/data/output/modelicatemplate/AixLib/AixLib_ThermalZoneRecord_ThreeElement index 71b64c65f..56daf94e3 100644 --- a/teaser/data/output/modelicatemplate/AixLib/AixLib_ThermalZoneRecord_ThreeElement +++ b/teaser/data/output/modelicatemplate/AixLib/AixLib_ThermalZoneRecord_ThreeElement @@ -66,7 +66,7 @@ record ${zone.parent.name}_${zone.name} "${zone.parent.name}_${zone.name}" lightingPowerSpecific = ${zone.use_conditions.lighting_power}, ratioConvectiveHeatLighting = ${zone.use_conditions.ratio_conv_rad_lighting}, useConstantACHrate = ${get_true_false(zone.use_conditions.use_constant_infiltration)}, - baseACH = ${zone.use_conditions.infiltration_rate}, + baseACH = ${zone.use_conditions.base_infiltration}, maxUserACH = ${zone.use_conditions.max_user_infiltration}, maxOverheatingACH = ${get_list(zone.use_conditions.max_overheating_infiltration)}, maxSummerACH = ${get_list(zone.use_conditions.max_summer_infiltration)}, diff --git a/teaser/data/output/modelicatemplate/AixLib/AixLib_ThermalZoneRecord_TwoElement b/teaser/data/output/modelicatemplate/AixLib/AixLib_ThermalZoneRecord_TwoElement index 3d40f705f..664c89e6a 100644 --- a/teaser/data/output/modelicatemplate/AixLib/AixLib_ThermalZoneRecord_TwoElement +++ b/teaser/data/output/modelicatemplate/AixLib/AixLib_ThermalZoneRecord_TwoElement @@ -66,7 +66,7 @@ record ${zone.parent.name}_${zone.name} "${zone.parent.name}_${zone.name}" lightingPowerSpecific = ${zone.use_conditions.lighting_power}, ratioConvectiveHeatLighting = ${zone.use_conditions.ratio_conv_rad_lighting}, useConstantACHrate = ${get_true_false(zone.use_conditions.use_constant_infiltration)}, - baseACH = ${zone.use_conditions.infiltration_rate}, + baseACH = ${zone.use_conditions.base_infiltration}, maxUserACH = ${zone.use_conditions.max_user_infiltration}, maxOverheatingACH = ${get_list(zone.use_conditions.max_overheating_infiltration)}, maxSummerACH = ${get_list(zone.use_conditions.max_summer_infiltration)}, diff --git a/teaser/data/output/modelicatemplate/BESMod/Building b/teaser/data/output/modelicatemplate/BESMod/Building new file mode 100644 index 000000000..1d003429b --- /dev/null +++ b/teaser/data/output/modelicatemplate/BESMod/Building @@ -0,0 +1,15 @@ +<%namespace file="/modelica_language/" import="get_true_false"/> +within ${bldg.parent.name}.${bldg.name}; +model ${bldg.name} + extends BESMod.Systems.Demand.Building.TEASERThermalZone( + zoneParam = { + %for zone in bldg.thermal_zones: + ${bldg.name}_DataBase.${bldg.name}_${zone.name}()${',' if not loop.last else ''} + %endfor + }, + hBui=${bldg.bldg_height}, + ABui=${bldg.area_gf}, + ARoo=${bldg.area_rt}, + nZones=${len(bldg.thermal_zones)}); + +end ${bldg.name}; diff --git a/teaser/data/output/modelicatemplate/BESMod/Example_GasBoilerBuildingOnly b/teaser/data/output/modelicatemplate/BESMod/Example_GasBoilerBuildingOnly new file mode 100644 index 000000000..4b731ef6a --- /dev/null +++ b/teaser/data/output/modelicatemplate/BESMod/Example_GasBoilerBuildingOnly @@ -0,0 +1,29 @@ +<%namespace file="/modelica_language/" import="get_list"/> +within ${bldg.parent.name}.${bldg.name}; +model GasBoilerBuildingOnly${bldg.name} + extends BESMod.Systems.BaseClasses.TEASERExport.PartialGasBoilerBuildingOnly( + redeclare ${bldg.name} building, + userProfiles(fileNameIntGains=Modelica.Utilities.Files.loadResource( + "modelica://${bldg.parent.name}/${bldg.name}/InternalGains_${bldg.name}.txt"), + setBakTSetZone( + amplitude=${get_list(setBakTSetZone_amplitude)}, + width=${get_list(setBakTSetZone_width)}, + startTime=${get_list(setBakTSetZone_startTime)})), + systemParameters(nZones=${len(bldg.thermal_zones)}, + TSetZone_nominal=${get_list(TSetZone_nominal)}, + TOda_nominal=${TOda_nominal}, + THydSup_nominal=${THydSup_nominal}, + QBuiOld_flow_design=${QBuiOld_flow_design}, + THydSupOld_design=${THydSupOld_design}, + filNamWea=Modelica.Utilities.Files.loadResource("modelica://${bldg.parent.name}/Resources/${bldg.parent.weather_file_name}"))); + + extends Modelica.Icons.Example; + + annotation (experiment(StopTime=172800, + Interval=600, + Tolerance=1e-06), + __Dymola_Commands(file= + "Resources/Scripts/Dymola/${bldg.name}/GasBoilerBuildingOnly${bldg.name}.mos" + "Simulate and plot")); + +end GasBoilerBuildingOnly${bldg.name}; \ No newline at end of file diff --git a/teaser/data/output/modelicatemplate/BESMod/Example_HeatPumpMonoenergetic b/teaser/data/output/modelicatemplate/BESMod/Example_HeatPumpMonoenergetic new file mode 100644 index 000000000..31d3dd846 --- /dev/null +++ b/teaser/data/output/modelicatemplate/BESMod/Example_HeatPumpMonoenergetic @@ -0,0 +1,31 @@ +<%namespace file="/modelica_language/" import="get_list"/> +within ${bldg.parent.name}.${bldg.name}; +model HeatPumpMonoenergetic${bldg.name} + extends BESMod.Systems.BaseClasses.TEASERExport.PartialHeatPumpMonoenergetic( + redeclare ${bldg.name} building( + use_verboseEnergyBalance=false, + energyDynamics=Modelica.Fluid.Types.Dynamics.FixedInitial), + userProfiles(fileNameIntGains=Modelica.Utilities.Files.loadResource( + "modelica://${bldg.parent.name}/${bldg.name}/InternalGains_${bldg.name}.txt"), + setBakTSetZone( + amplitude=${get_list(setBakTSetZone_amplitude)}, + width=${get_list(setBakTSetZone_width)}, + startTime=${get_list(setBakTSetZone_startTime)})), + systemParameters( + nZones=${len(bldg.thermal_zones)}, + TSetZone_nominal=${get_list(TSetZone_nominal)}, + TOda_nominal=${TOda_nominal}, + THydSup_nominal=${THydSup_nominal}, + QBuiOld_flow_design=${QBuiOld_flow_design}, + THydSupOld_design=${THydSupOld_design}, + filNamWea=Modelica.Utilities.Files.loadResource("modelica://${bldg.parent.name}/Resources/${bldg.parent.weather_file_name}"))); + + extends Modelica.Icons.Example; + +annotation(experiment(StopTime=172800, + Interval=600, + Tolerance=1e-06), + __Dymola_Commands(file= + "Resources/Scripts/Dymola/${bldg.name}/HeatPumpMonoenergetic${bldg.name}.mos" + "Simulate and plot")); +end HeatPumpMonoenergetic${bldg.name}; \ No newline at end of file diff --git a/teaser/data/output/modelicatemplate/BESMod/Example_TEASERHeatLoadCalculation b/teaser/data/output/modelicatemplate/BESMod/Example_TEASERHeatLoadCalculation new file mode 100644 index 000000000..b26306d4e --- /dev/null +++ b/teaser/data/output/modelicatemplate/BESMod/Example_TEASERHeatLoadCalculation @@ -0,0 +1,28 @@ +<%namespace file="/modelica_language/" import="get_list"/> +within ${bldg.parent.name}.${bldg.name}; +model TEASERHeatLoadCalculation${bldg.name} + extends BESMod.Systems.BaseClasses.TEASERExport.PartialTEASERHeatLoadCalculation( + redeclare ${bldg.name} building, + userProfiles( + fileNameIntGains=Modelica.Utilities.Files.loadResource( + "modelica://${bldg.parent.name}/${bldg.name}/InternalGains_${bldg.name}.txt"), + setBakTSetZone( + amplitude=${get_list(setBakTSetZone_amplitude)}, + width=${get_list(setBakTSetZone_width)}, + startTime=${get_list(setBakTSetZone_startTime)})), + systemParameters(nZones=${len(bldg.thermal_zones)}, + TSetZone_nominal=${get_list(TSetZone_nominal)}, + TOda_nominal=${TOda_nominal}, + filNamWea=Modelica.Utilities.Files.loadResource("modelica://${bldg.parent.name}/Resources/${bldg.parent.weather_file_name}"))); + + extends Modelica.Icons.Example; + + annotation ( + experiment(StopTime=172800, + Interval=600, + Tolerance=1e-06), + __Dymola_Commands(file= + "Resources/Scripts/Dymola/${bldg.name}/TEASERHeatLoadCalculation${bldg.name}.mos" + "Simulate and plot")); + +end TEASERHeatLoadCalculation${bldg.name}; \ No newline at end of file diff --git a/teaser/data/output/modelicatemplate/BESMod/Script_GasBoilerBuildingOnly b/teaser/data/output/modelicatemplate/BESMod/Script_GasBoilerBuildingOnly new file mode 100644 index 000000000..a6c442508 --- /dev/null +++ b/teaser/data/output/modelicatemplate/BESMod/Script_GasBoilerBuildingOnly @@ -0,0 +1,7 @@ + +simulateModel("${project.name}.${bldg.name}.GasBoilerBuildingOnly${bldg.name}", stopTime=172800, method="dassl", tolerance=1e-06, resultFile="GasBoilerBuildingOnly${bldg.name}"); + +createPlot(id=1, position={75, 70, 1210, 600}, y={"weaDat.weaBus.TDryBul"}, grid=true, subPlot=1, colors={{28,108,200}}); +createPlot(id=1, y={"hydraulic.generation.sigBusGen.TBoiOut"}, grid=true, subPlot=2, colors={{238,46,47}}); +createPlot(id=1, y={"outputs.building.TZone[1]"}, grid=true, subPlot=3, colors={{0,140,72}}); +createPlot(id=1, y={"electricalGrid.PElecGen","electricalGrid.PElecLoa"}, grid=true, subPlot=4, colors={{28,108,200},{238,46,47}}); diff --git a/teaser/data/output/modelicatemplate/BESMod/Script_HeatPumpMonoenergetic b/teaser/data/output/modelicatemplate/BESMod/Script_HeatPumpMonoenergetic new file mode 100644 index 000000000..9aecdacfd --- /dev/null +++ b/teaser/data/output/modelicatemplate/BESMod/Script_HeatPumpMonoenergetic @@ -0,0 +1,4 @@ +simulateModel("${project.name}.${bldg.name}.HeatPumpMonoenergetic${bldg.name}", stopTime=172800, method="dassl", tolerance=1e-06, resultFile="HeatPumpMonoenergetic${bldg.name}"); +createPlot(id=2, position={75, 70, 1210, 240}, y={"electrical.distribution.OutputDistr.PEleGen.value","electrical.distribution.OutputDistr.PEleLoa.value"}, grid=true, subPlot=1, colors={{28,108,200},{238,46,47}}); +createPlot(id=2, position={75, 70, 1210, 240}, y={"hydraulic.distribution.sigBusDistr.TStoDHWTopMea","hydraulic.distribution.sigBusDistr.TStoBufTopMea","hydraulic.generation.sigBusGen.TGenOutMea"}, grid=true, subPlot=2, colors={{28,108,200},{238,46,47},{0,140,72}}); +createPlot(id=2, position={75, 70, 1210, 240}, y={"ventilation.generation.TSup.T","ventilation.generation.weaBus.TDryBul","building.buiMeaBus.TZoneMea[1]"}, grid=true, subPlot=3, colors={{28,108,200},{238,46,47},{0,140,72}}); diff --git a/teaser/data/output/modelicatemplate/BESMod/Script_TEASERHeatLoadCalculation b/teaser/data/output/modelicatemplate/BESMod/Script_TEASERHeatLoadCalculation new file mode 100644 index 000000000..a216927ea --- /dev/null +++ b/teaser/data/output/modelicatemplate/BESMod/Script_TEASERHeatLoadCalculation @@ -0,0 +1,6 @@ + +simulateModel("${project.name}.${bldg.name}.TEASERHeatLoadCalculation${bldg.name}", stopTime=172800, method="dassl", tolerance=1e-06, resultFile="TEASERHeatLoadCalculation${bldg.name}"); + +createPlot(id=1, position={75, 70, 1210, 600}, y={"weaDat.weaBus.TDryBul"}, grid=true, subPlot=1, colors={{28,108,200}}); +createPlot(id=1, y={"outputs.building.TZone[1]"}, grid=true, subPlot=2, colors={{0,140,72}}); +createPlot(id=1, y={"electricalGrid.PElecGen","electricalGrid.PElecLoa"}, grid=true, subPlot=3, colors={{28,108,200},{238,46,47}}); diff --git a/teaser/data/output/modelicatemplate/package_order b/teaser/data/output/modelicatemplate/package_order index 0dc1fe77a..984d33ccc 100644 --- a/teaser/data/output/modelicatemplate/package_order +++ b/teaser/data/output/modelicatemplate/package_order @@ -1,5 +1,7 @@ -%if extra != None: -${extra} +%if extra != None: +%for name in extra: +${name} +%endfor %endif %for i in list: %if addition != None: diff --git a/teaser/data/output/teaserjson_output.py b/teaser/data/output/teaserjson_output.py index e29ee214d..d6dc133c9 100644 --- a/teaser/data/output/teaserjson_output.py +++ b/teaser/data/output/teaserjson_output.py @@ -190,8 +190,8 @@ def save_teaser_json(path, project): "use_constant_infiltration" ] = zone.use_conditions.use_constant_infiltration zone_out["use_conditions"][ - "infiltration_rate" - ] = zone.use_conditions.infiltration_rate + "base_infiltration" + ] = zone.use_conditions.base_infiltration zone_out["use_conditions"][ "max_user_infiltration" ] = zone.use_conditions.max_user_infiltration diff --git a/teaser/data/output/usecond_output.py b/teaser/data/output/usecond_output.py index 22d1be850..35624f779 100644 --- a/teaser/data/output/usecond_output.py +++ b/teaser/data/output/usecond_output.py @@ -102,8 +102,8 @@ def save_use_conditions(use_cond, data_class): "use_constant_infiltration" ] = use_cond.use_constant_infiltration data_class.conditions_bind[use_cond.usage][ - "infiltration_rate" - ] = use_cond.infiltration_rate + "base_infiltration" + ] = use_cond.base_infiltration data_class.conditions_bind[use_cond.usage][ "max_user_infiltration" ] = use_cond.max_user_infiltration diff --git a/teaser/examples/converter.toml b/teaser/examples/converter.toml index 81babb677..a8f2632ef 100644 --- a/teaser/examples/converter.toml +++ b/teaser/examples/converter.toml @@ -39,3 +39,9 @@ filename = "teaser/examples/e7_retrofit.py" func_name = "example_retrofit_building" md_path = "docs/source/examples" ipynb_path = "docs/jupyter_notebooks" + +[[ExampleFile]] +filename = "teaser/examples/e11_export_besmod_models.py" +func_name = "example_export_besmod" +md_path = "docs/source/examples" +ipynb_path = "docs/jupyter_notebooks" diff --git a/teaser/examples/e11_export_besmod_models.py b/teaser/examples/e11_export_besmod_models.py new file mode 100644 index 000000000..e9a3d9135 --- /dev/null +++ b/teaser/examples/e11_export_besmod_models.py @@ -0,0 +1,209 @@ +# # Example 11: Export Modelica models for BESMod library using TEASER API +# This module demonstrates how to export building models from a TEASER project +# to ready-to-run simulation models for the Modelica BESMod library. +# BESMod enables seamless integration with state-of-the-art energy systems, +# such as heat pumps and photovoltaic systems. These systems can be utilized +# to generate primary energy demand curves (e.g., for electricity or gas) or +# to conduct in-depth analyses of building energy systems. In contrast, +# AixLib focuses on ideal heat demand calculation, and IBPSA on +# free floating temperature without an ideal heater. +# You can execute this example using +# [jupyter-notebook](https://mybinder.org/v2/gh/RWTH-EBC/TEASER/main?labpath=docs%2Fjupyter_notebooks) + +import teaser.examples.e1_generate_archetype as e1 +import teaser.logic.utilities as utilities +import os + + +def example_export_besmod(): + """This function demonstrates the export to Modelica library BESMod using + the API function of TEASER""" + + # ## Standard export + # In e1_generate_archetype we created a Project with three archetype + # buildings to get this Project we rerun this example + + prj = e1.example_generate_archetype() + + # Configure project settings to ensure compatibility with BESMod. The BESMod + # library uses the AixLib.ThermalZones.ReducedOrder.ThermalZone.ThermalZone model + # with 4 elements for the demand building model. Other numbers of elements are possible, + # but compatability with ARoof for PV and AFloor for UFH systems must be checked first. + # Set these parameters in the project: + prj.used_library_calc = 'AixLib' + prj.number_of_elements_calc = 4 + + # BESMod allows building models to be included in predefined example energy systems: + examples = [ + "TEASERHeatLoadCalculation", # Ideal electric heater for heat load calculations + "HeatPumpMonoenergetic", # Heat pump with radiators, buffer and DHW storage, and PV + "GasBoilerBuildingOnly" # Gas boiler with radiators + ] + + # For the hydraulic systems, you have to specify a nominal supply temperature + # for heat transfer, e.g. in radiators. + # Multiple options are available: + + # Option 1: Set a single value for all buildings and zones. + THydSup_nominal = 55 + 273.15 + + # Option 2: Set values for each building or thermal zone. + THydSup_nominal = {"ResidentialBuilding": 328.15, + "OfficeBuilding": 328.15, + "InstituteBuilding": {"Office": 343.15, + "Floor": 343.15, + "Storage": 343.15, + "Meeting": 343.15, + "Restroom": 343.15, + "ICT": 343.15, + "Laboratory": 328.15}, + "InstituteBuildingMoisture": 343.15, + "ResidentialBuildingTabula": 328.15, + "ResidentialBuildingTabulaMulti": 328.15} + + # Option 3: Specify values based on construction year. + # Here, the value of the next higher specified year is set to the building. + # The classification here is taken from: + # https://www.ffe.de/projekte/waermepumpen-fahrplan-finanzielle-kipppunkte-zur-modernisierung-mit-waermepumpen-im-wohngebaeudebestand/ + THydSup_nominal = { + 1950: 90 + 273.15, + 1980: 70 + 273.15, + 2010: 55 + 273.15, + 2024: 35 + 273.15 + } + + # In the examples, the parameters for BESMod.Systems.UserProfiles.TEASERProfiles are configured, + # including internal gains and heating profiles for each zone. + # BESMod requires 24-hour heating profiles, which are used + # to define the parameters of the `setBakTSetZone` Pulse block. + # By default, the TEASER profiles are applied, but these can be customized if needed. + + # Additionally, location-specific parameters must be set, which can be achieved using the following function. + # The default values provided here correspond to Mannheim. + weather_file_path = utilities.get_full_path( + os.path.join( + "data", + "input", + "inputdata", + "weatherdata", + "DEU_BW_Mannheim_107290_TRY2010_12_Jahr_BBSR.mos")) + + prj.set_location_parameters(t_outside=262.65, + t_ground=286.15, + weather_file_path=weather_file_path, + calc_all_buildings=True) + + # To make sure the parameters are calculated correctly we recommend to + # run prj.calc_all_buildings() function which is here already done in the set_location_parameters function. + + # Export all buildings to BESMod and include them in predefined example systems. + path = prj.export_besmod( + THydSup_nominal=THydSup_nominal, + path=None, + examples=examples + ) + + # ## Partial retrofit export + # The partial retrofit option of the energy system in BESMod can also be utilized. + # For more information on this see BESMod.UsersGuide.GettingStarted.Parameterization. + # To enable this here, the nominal heat flow of each zone in the building must be extracted prior to the retrofit. + QBuiOld_flow_design = { + bldg.name: { + tz.name: tz.model_attr.heat_load for tz in bldg.thermal_zones + } + for bldg in prj.buildings + } + + # Retrofit project buildings and recalculate parameters. + prj.name = "ArchetypeExample_partial_retrofit" + prj.retrofit_all_buildings( + year_of_retrofit=2015, + type_of_retrofit="adv_retrofit", + window_type='Alu- oder Stahlfenster, Isolierverglasung', + material='EPS_perimeter_insulation_top_layer' + ) + prj.calc_all_buildings() + + # By default, radiator transfer systems are not retrofitted when the + # QBuiOld_flow_design parameter is provided and differs from the new nominal heat flow. + # Additionally, new THydSup_nominal temperatures can be specified alongside + # THydSupOld_design values, which are used for radiator sizing but not for control settings. + + path = prj.export_besmod( + THydSup_nominal=THydSup_nominal, + QBuiOld_flow_design=QBuiOld_flow_design, + path=None, + examples=examples + ) + + # ## Custom export + # Additionally, we have the flexibility to define custom templates for including buildings in specific setups. + # For instance, a custom template is defined here to include the building in the + # ModelicaConferencePaper example from BESMod, which features an integrated battery system. + + # Custom template + # ``` + # < %namespace file = "/modelica_language/" import="get_list" / > + # within ${bldg.parent.name}.${bldg.name}; + # model ModelicaConferencePaper${bldg.name} + # extends BESMod.Examples.ModelicaConferencePaper.PartialModelicaConferenceUseCase( + # redeclare ${bldg.name} building, + # redeclare BESMod.Systems.UserProfiles.TEASERProfiles + # userProfiles(fileNameIntGains=Modelica.Utilities.Files.loadResource( + # "modelica://${bldg.parent.name}/${bldg.name}/InternalGains_${bldg.name}.txt"), + # setBakTSetZone(amplitude=${get_list(setBakTSetZone_amplitude)}, + # width =${get_list(setBakTSetZone_width)}, + # startTime =${get_list(setBakTSetZone_startTime)})), + # systemParameters(nZones=${len(bldg.thermal_zones)}, + # QBui_flow_nominal = building.QRec_flow_nominal, + # TOda_nominal =${TOda_nominal}, + # TSetZone_nominal =${get_list(TSetZone_nominal)}, + # THydSup_nominal =${THydSup_nominal}, + # QBuiOld_flow_design =${QBuiOld_flow_design}, + # THydSupOld_design =${THydSupOld_design}, + # filNamWea = Modelica.Utilities.Files.loadResource( + # "modelica://${bldg.parent.name}/Resources/${bldg.parent.weather_file_name}"))); + # + # extends Modelica.Icons.Example; + # + # annotation(experiment(StopTime=172800, + # Interval=600, + # Tolerance=1e-06), + # __Dymola_Commands(file= + # "Resources/Scripts/Dymola/${bldg.name}/ModelicaConferencePaper${bldg.name}.mos" + # "Simulate and plot")); + # end + # ModelicaConferencePaper${bldg.name}; + # ``` + + prj.name = "ArchetypeExample_custom" + + custom_template_path = os.path.join( + os.path.dirname(__file__), "examplefiles", "custom_besmod_templates" + ) + custom_example_template = {"ModelicaConferencePaper": os.path.join(custom_template_path, "custom_template.txt")} + + # The template also includes a .mos script as part of its annotation. + # By default, the provided examples export a basic "simulate and plot" script, + # which is incorporated into their annotation, as shown in the custom example. + # Additionally, you have the flexibility to modify the template for existing examples + # and define custom scripts for your tailored examples. + + custom_script = {"HeatPumpMonoenergetic": os.path.join(custom_template_path, "custom_script_hp_mono.txt"), + "ModelicaConferencePaper": os.path.join(custom_template_path, "custom_script.txt")} + + path = prj.export_besmod( + THydSup_nominal=THydSup_nominal, + path=None, + examples=examples, + custom_examples=custom_example_template, + custom_script=custom_script + ) + + return path + + +if __name__ == '__main__': + example_export_besmod() + + print("Example 11: That's it! :)") diff --git a/teaser/examples/e2_export_aixlib_models.py b/teaser/examples/e2_export_aixlib_models.py index 898cc394d..5a67f1f09 100644 --- a/teaser/examples/e2_export_aixlib_models.py +++ b/teaser/examples/e2_export_aixlib_models.py @@ -1,8 +1,10 @@ # # Example 2: Export Modelica models for AixLib library using TEASER API - # This module contains an example how to export buildings from a TEASER -# project to ready-to-run simulation models for Modelica library AixLib. These -# models will only simulate using Dymola, the reason for this are state +# project to ready-to-run simulation models for Modelica library AixLib. +# AixLib focuses on ideal hat demand calculation. In contrast, +# IBPSA focuses on the free floating temperature and has no ideal heater, +# and BESMod on the coupling to state-of-the-art energy systems. +# These models will only simulate using Dymola, the reason for this are state # machines that are used in one AixLib specific AHU model. # You can run this example using the [jupyter-notebook](https://mybinder.org/v2/gh/RWTH-EBC/TEASER/main?labpath=docs%2Fjupyter_notebooks) diff --git a/teaser/examples/e3_export_ibpsa_models.py b/teaser/examples/e3_export_ibpsa_models.py index 180e20666..aa8c75c56 100644 --- a/teaser/examples/e3_export_ibpsa_models.py +++ b/teaser/examples/e3_export_ibpsa_models.py @@ -1,7 +1,10 @@ # # Example 3: Export Modelica models for IBPSA library using TEASER API # This module contains an example how to export buildings from a TEASER -# project to ready-to-run simulation models for Modelica library IBPSA. These -# models should simulate in Dymola, OpenModelica and JModelica. +# project to ready-to-run simulation models for Modelica library IBPSA. +# IBPSA focuses on free floating temperature without an ideal heater. +# In contrast, AixLib focuses on ideal heat demand calculation, and +# BESMod on the coupling to state-of-the-art energy systems. +# These models should simulate in Dymola, OpenModelica and JModelica. # You can run this example using the [jupyter-notebook](https://mybinder.org/v2/gh/RWTH-EBC/TEASER/main?labpath=docs%2Fjupyter_notebooks) import teaser.examples.e1_generate_archetype as e1 diff --git a/teaser/examples/e6_generate_building.py b/teaser/examples/e6_generate_building.py index 895b2e676..bbb47076a 100644 --- a/teaser/examples/e6_generate_building.py +++ b/teaser/examples/e6_generate_building.py @@ -52,7 +52,6 @@ def example_create_building(): tz.name = "LivingRoom" tz.area = 140.0 tz.volume = tz.area * bldg.number_of_floors * bldg.height_of_floors - tz.infiltration_rate = 0.5 # Instantiate BoundaryConditions and load conditions for `Living`. diff --git a/teaser/examples/examplefiles/ASHRAE140_600.json b/teaser/examples/examplefiles/ASHRAE140_600.json index aace59e3e..a7425d677 100644 --- a/teaser/examples/examplefiles/ASHRAE140_600.json +++ b/teaser/examples/examplefiles/ASHRAE140_600.json @@ -63,7 +63,7 @@ "lighting_efficiency_lumen": 150, "ratio_conv_rad_lighting": 0.5, "use_constant_infiltration": false, - "infiltration_rate": 0.2, + "base_infiltration": 0.2, "max_user_infiltration": 1.0, "max_overheating_infiltration": [ 3.0, diff --git a/teaser/examples/examplefiles/ASHRAE140_620.json b/teaser/examples/examplefiles/ASHRAE140_620.json index 51ac664ec..db5e8797c 100644 --- a/teaser/examples/examplefiles/ASHRAE140_620.json +++ b/teaser/examples/examplefiles/ASHRAE140_620.json @@ -64,7 +64,7 @@ "lighting_efficiency_lumen": 150, "ratio_conv_rad_lighting": 0.5, "use_constant_infiltration": false, - "infiltration_rate": 0.2, + "base_infiltration": 0.2, "max_user_infiltration": 1.0, "max_overheating_infiltration": [ 3.0, diff --git a/teaser/examples/examplefiles/ASHRAE140_900.json b/teaser/examples/examplefiles/ASHRAE140_900.json index 63ac74f23..e7f79dd00 100644 --- a/teaser/examples/examplefiles/ASHRAE140_900.json +++ b/teaser/examples/examplefiles/ASHRAE140_900.json @@ -63,7 +63,7 @@ "lighting_efficiency_lumen": 150, "ratio_conv_rad_lighting": 0.5, "use_constant_infiltration": false, - "infiltration_rate": 0.2, + "base_infiltration": 0.2, "max_user_infiltration": 1.0, "max_overheating_infiltration": [ 3.0, diff --git a/teaser/examples/examplefiles/ASHRAE140_920.json b/teaser/examples/examplefiles/ASHRAE140_920.json index 12949585e..a4fe5f604 100644 --- a/teaser/examples/examplefiles/ASHRAE140_920.json +++ b/teaser/examples/examplefiles/ASHRAE140_920.json @@ -64,7 +64,7 @@ "lighting_efficiency_lumen": 150, "ratio_conv_rad_lighting": 0.5, "use_constant_infiltration": false, - "infiltration_rate": 0.2, + "base_infiltration": 0.2, "max_user_infiltration": 1.0, "max_overheating_infiltration": [ 3.0, diff --git a/teaser/examples/examplefiles/VDI6007_Room1.json b/teaser/examples/examplefiles/VDI6007_Room1.json index b14695cb1..f0ea4267b 100644 --- a/teaser/examples/examplefiles/VDI6007_Room1.json +++ b/teaser/examples/examplefiles/VDI6007_Room1.json @@ -59,7 +59,7 @@ "lighting_efficiency_lumen": 150, "ratio_conv_rad_lighting": 0.5, "use_constant_infiltration": false, - "infiltration_rate": 0.2, + "base_infiltration": 0.2, "max_user_infiltration": 1.0, "max_overheating_infiltration": [ 3.0, diff --git a/teaser/examples/examplefiles/VDI6007_Room10.json b/teaser/examples/examplefiles/VDI6007_Room10.json index 744c9ee9a..9cc5031e1 100644 --- a/teaser/examples/examplefiles/VDI6007_Room10.json +++ b/teaser/examples/examplefiles/VDI6007_Room10.json @@ -60,7 +60,7 @@ "lighting_efficiency_lumen": 150, "ratio_conv_rad_lighting": 0.5, "use_constant_infiltration": false, - "infiltration_rate": 0.2, + "base_infiltration": 0.2, "max_user_infiltration": 1.0, "max_overheating_infiltration": [ 3.0, diff --git a/teaser/examples/examplefiles/VDI6007_Room3.json b/teaser/examples/examplefiles/VDI6007_Room3.json index 2fdcca436..49ba33226 100644 --- a/teaser/examples/examplefiles/VDI6007_Room3.json +++ b/teaser/examples/examplefiles/VDI6007_Room3.json @@ -59,7 +59,7 @@ "lighting_efficiency_lumen": 150, "ratio_conv_rad_lighting": 0.5, "use_constant_infiltration": false, - "infiltration_rate": 0.2, + "base_infiltration": 0.2, "max_user_infiltration": 1.0, "max_overheating_infiltration": [ 3.0, diff --git a/teaser/examples/examplefiles/VDI6007_Room8.json b/teaser/examples/examplefiles/VDI6007_Room8.json index d0492dcf3..739641cdf 100644 --- a/teaser/examples/examplefiles/VDI6007_Room8.json +++ b/teaser/examples/examplefiles/VDI6007_Room8.json @@ -61,7 +61,7 @@ "lighting_efficiency_lumen": 150, "ratio_conv_rad_lighting": 0.5, "use_constant_infiltration": false, - "infiltration_rate": 0.2, + "base_infiltration": 0.2, "max_user_infiltration": 1.0, "max_overheating_infiltration": [ 3.0, diff --git a/teaser/examples/examplefiles/custom_besmod_templates/custom_script.txt b/teaser/examples/examplefiles/custom_besmod_templates/custom_script.txt new file mode 100644 index 000000000..4bc68d605 --- /dev/null +++ b/teaser/examples/examplefiles/custom_besmod_templates/custom_script.txt @@ -0,0 +1,4 @@ +simulateModel("${project.name}.${bldg.name}.ModelicaConferencePaper${bldg.name}", stopTime=864000, method="dassl", tolerance=1e-06, resultFile="ModelicaConferencePaper${bldg.name}"); +createPlot(id=2, position={75, 70, 1210, 240}, y={"electrical.distribution.batterySimple.PLoad","electrical.distribution.batterySimple.PCharge"}, grid=true, subPlot=1, colors={{28,108,200},{238,46,47}}); +createPlot(id=2, position={75, 70, 1210, 240}, y={"hydraulic.distribution.sigBusDistr.TStoDHWTopMea","hydraulic.distribution.sigBusDistr.TStoBufTopMea","hydraulic.generation.sigBusGen.TGenOutMea"}, grid=true, subPlot=2, colors={{28,108,200},{238,46,47},{0,140,72}}); +createPlot(id=2, position={75, 70, 1210, 240}, y={"ventilation.generation.TSup.T","ventilation.generation.weaBus.TDryBul","building.buiMeaBus.TZoneMea[1]"}, grid=true, subPlot=3, colors={{28,108,200},{238,46,47},{0,140,72}}); diff --git a/teaser/examples/examplefiles/custom_besmod_templates/custom_script_hp_mono.txt b/teaser/examples/examplefiles/custom_besmod_templates/custom_script_hp_mono.txt new file mode 100644 index 000000000..dbd47755a --- /dev/null +++ b/teaser/examples/examplefiles/custom_besmod_templates/custom_script_hp_mono.txt @@ -0,0 +1,4 @@ +simulateModel("${project.name}.${bldg.name}.HeatPumpMonoenergetic${bldg.name}", stopTime=864000, method="dassl", tolerance=1e-06, resultFile="ModelicaConferencePaper${bldg.name}"); +createPlot(id=2, position={75, 70, 1210, 240}, y={"electrical.distribution.batterySimple.PLoad","electrical.distribution.batterySimple.PCharge"}, grid=true, subPlot=1, colors={{28,108,200},{238,46,47}}); +createPlot(id=2, position={75, 70, 1210, 240}, y={"hydraulic.distribution.sigBusDistr.TStoDHWTopMea","hydraulic.distribution.sigBusDistr.TStoBufTopMea","hydraulic.generation.sigBusGen.TGenOutMea"}, grid=true, subPlot=2, colors={{28,108,200},{238,46,47},{0,140,72}}); +createPlot(id=2, position={75, 70, 1210, 240}, y={"ventilation.generation.TSup.T","ventilation.generation.weaBus.TDryBul","building.buiMeaBus.TZoneMea[1]"}, grid=true, subPlot=3, colors={{28,108,200},{238,46,47},{0,140,72}}); diff --git a/teaser/examples/examplefiles/custom_besmod_templates/custom_template.txt b/teaser/examples/examplefiles/custom_besmod_templates/custom_template.txt new file mode 100644 index 000000000..90b9aa0a9 --- /dev/null +++ b/teaser/examples/examplefiles/custom_besmod_templates/custom_template.txt @@ -0,0 +1,30 @@ +<%namespace file="/modelica_language/" import="get_list"/> +within ${bldg.parent.name}.${bldg.name}; +model ModelicaConferencePaper${bldg.name} + extends BESMod.Examples.ModelicaConferencePaper.PartialModelicaConferenceUseCase( + redeclare ${bldg.name} building, + redeclare BESMod.Systems.UserProfiles.TEASERProfiles userProfiles(fileNameIntGains=Modelica.Utilities.Files.loadResource( + "modelica://${bldg.parent.name}/${bldg.name}/InternalGains_${bldg.name}.txt"), + setBakTSetZone( + amplitude=${get_list(setBakTSetZone_amplitude)}, + width=${get_list(setBakTSetZone_width)}, + startTime=${get_list(setBakTSetZone_startTime)})), + systemParameters(nZones=${len(bldg.thermal_zones)}, + QBui_flow_nominal=building.QRec_flow_nominal, + TOda_nominal=${TOda_nominal}, + TSetZone_nominal=${get_list(TSetZone_nominal)}, + THydSup_nominal=${THydSup_nominal}, + QBuiOld_flow_design=${QBuiOld_flow_design}, + THydSupOld_design=${THydSupOld_design}, + filNamWea=Modelica.Utilities.Files.loadResource("modelica://${bldg.parent.name}/Resources/${bldg.parent.weather_file_name}"))); + + extends Modelica.Icons.Example; + + annotation (experiment(StopTime=172800, + Interval=600, + Tolerance=1e-06), + __Dymola_Commands(file= + "Resources/Scripts/Dymola/${bldg.name}/ModelicaConferencePaper${bldg.name}.mos" + "Simulate and plot")); + +end ModelicaConferencePaper${bldg.name}; \ No newline at end of file diff --git a/teaser/examples/examplefiles/unitTest.json b/teaser/examples/examplefiles/unitTest.json index 3d003a252..706773204 100644 --- a/teaser/examples/examplefiles/unitTest.json +++ b/teaser/examples/examplefiles/unitTest.json @@ -47,7 +47,7 @@ "Office": { "area": 994.0, "volume": 994.0, - "infiltration_rate": null, + "base_infiltration": null, "typical_length": 6.0, "typical_width": 6.0, "use_conditions": { @@ -71,7 +71,7 @@ "lighting_efficiency_lumen": 150, "ratio_conv_rad_lighting": 0.9, "use_constant_infiltration": false, - "infiltration_rate": 0.2, + "base_infiltration": 0.2, "max_user_infiltration": 1.0, "max_overheating_infiltration": [ 3.0, @@ -785,7 +785,7 @@ "Floor": { "area": 497.0, "volume": 497.0, - "infiltration_rate": null, + "base_infiltration": null, "typical_length": 2.0, "typical_width": 12.0, "use_conditions": { @@ -809,7 +809,7 @@ "lighting_efficiency_lumen": 150, "ratio_conv_rad_lighting": 0.9, "use_constant_infiltration": false, - "infiltration_rate": 0.2, + "base_infiltration": 0.2, "max_user_infiltration": 1.0, "max_overheating_infiltration": [ 3.0, @@ -1523,7 +1523,7 @@ "Storage": { "area": 298.2, "volume": 298.2, - "infiltration_rate": null, + "base_infiltration": null, "typical_length": 6.0, "typical_width": 6.0, "use_conditions": { @@ -1547,7 +1547,7 @@ "lighting_efficiency_lumen": 150, "ratio_conv_rad_lighting": 0.9, "use_constant_infiltration": false, - "infiltration_rate": 0.2, + "base_infiltration": 0.2, "max_user_infiltration": 1.0, "max_overheating_infiltration": [ 3.0, @@ -2261,7 +2261,7 @@ "Meeting": { "area": 79.52, "volume": 79.52, - "infiltration_rate": null, + "base_infiltration": null, "typical_length": 6.0, "typical_width": 6.0, "use_conditions": { @@ -2285,7 +2285,7 @@ "lighting_efficiency_lumen": 150, "ratio_conv_rad_lighting": 0.9, "use_constant_infiltration": false, - "infiltration_rate": 0.2, + "base_infiltration": 0.2, "max_user_infiltration": 1.0, "max_overheating_infiltration": [ 3.0, @@ -2999,7 +2999,7 @@ "Restroom": { "area": 79.52, "volume": 79.52, - "infiltration_rate": null, + "base_infiltration": null, "typical_length": 3.0, "typical_width": 6.0, "use_conditions": { @@ -3023,7 +3023,7 @@ "lighting_efficiency_lumen": 150, "ratio_conv_rad_lighting": 0.9, "use_constant_infiltration": false, - "infiltration_rate": 0.2, + "base_infiltration": 0.2, "max_user_infiltration": 1.0, "max_overheating_infiltration": [ 3.0, @@ -3737,7 +3737,7 @@ "ICT": { "area": 39.76, "volume": 39.76, - "infiltration_rate": null, + "base_infiltration": null, "typical_length": 6.0, "typical_width": 6.0, "use_conditions": { @@ -3761,7 +3761,7 @@ "lighting_efficiency_lumen": 150, "ratio_conv_rad_lighting": 0.9, "use_constant_infiltration": false, - "infiltration_rate": 0.2, + "base_infiltration": 0.2, "max_user_infiltration": 1.0, "max_overheating_infiltration": [ 3.0, diff --git a/teaser/examples/examplefiles/unitTestCalc.json b/teaser/examples/examplefiles/unitTestCalc.json index e1e710a2b..294e7e171 100644 --- a/teaser/examples/examplefiles/unitTestCalc.json +++ b/teaser/examples/examplefiles/unitTestCalc.json @@ -67,7 +67,7 @@ "lighting_efficiency_lumen": 150, "ratio_conv_rad_lighting": 0.5, "use_constant_infiltration": false, - "infiltration_rate": 0.2, + "base_infiltration": 0.2, "max_user_infiltration": 1.0, "max_overheating_infiltration": [ 3.0, diff --git a/teaser/examples/verification/verification_ASHRAE_140_600.py b/teaser/examples/verification/verification_ASHRAE_140_600.py index a5bc96ebc..1d71d3228 100644 --- a/teaser/examples/verification/verification_ASHRAE_140_600.py +++ b/teaser/examples/verification/verification_ASHRAE_140_600.py @@ -77,7 +77,7 @@ def from_scratch(number_of_elements, save=False, path=utilities.get_default_path tz.volume = tz.area * 2.7 tz.use_conditions = UseConditions(parent=tz) - tz.use_conditions.infiltration_rate = 0.41 + tz.use_conditions.base_infiltration = 0.41 roof = Rooftop(parent=tz) roof.name = "Roof" diff --git a/teaser/examples/verification/verification_ASHRAE_140_620.py b/teaser/examples/verification/verification_ASHRAE_140_620.py index dff5a1a5e..a3a6bf2e0 100644 --- a/teaser/examples/verification/verification_ASHRAE_140_620.py +++ b/teaser/examples/verification/verification_ASHRAE_140_620.py @@ -77,7 +77,7 @@ def from_scratch(number_of_elements, save=False, path=utilities.get_default_path tz.area = 8.0 * 6.0 tz.volume = tz.area * 2.7 tz.use_conditions = UseConditions(parent=tz) - tz.use_conditions.infiltration_rate = 0.41 + tz.use_conditions.base_infiltration = 0.41 roof = Rooftop(parent=tz) roof.name = "Roof" diff --git a/teaser/examples/verification/verification_ASHRAE_140_900.py b/teaser/examples/verification/verification_ASHRAE_140_900.py index d2ad8646a..62a7e31e0 100644 --- a/teaser/examples/verification/verification_ASHRAE_140_900.py +++ b/teaser/examples/verification/verification_ASHRAE_140_900.py @@ -76,7 +76,7 @@ def from_scratch(number_of_elements, save=False, path=utilities.get_default_path tz.area = 8.0 * 6.0 tz.volume = tz.area * 2.7 tz.use_conditions = UseConditions(parent=tz) - tz.use_conditions.infiltration_rate = 0.41 + tz.use_conditions.base_infiltration = 0.41 roof = Rooftop(parent=tz) roof.name = "Roof" diff --git a/teaser/examples/verification/verification_ASHRAE_140_920.py b/teaser/examples/verification/verification_ASHRAE_140_920.py index 00f24ac90..0fa1e2a82 100644 --- a/teaser/examples/verification/verification_ASHRAE_140_920.py +++ b/teaser/examples/verification/verification_ASHRAE_140_920.py @@ -76,7 +76,7 @@ def from_scratch(number_of_elements, save=False, path=utilities.get_default_path tz.area = 8.0 * 6.0 tz.volume = tz.area * 2.7 tz.use_conditions = UseConditions(parent=tz) - tz.use_conditions.infiltration_rate = 0.41 + tz.use_conditions.base_infiltration = 0.41 roof = Rooftop(parent=tz) roof.name = "Roof" diff --git a/teaser/logic/buildingobjects/building.py b/teaser/logic/buildingobjects/building.py index 0376ac2d2..8628b4317 100644 --- a/teaser/logic/buildingobjects/building.py +++ b/teaser/logic/buildingobjects/building.py @@ -93,6 +93,10 @@ class Building(object): that direction as value. bldg_height : float [m] Total building height. + area_rt : float [m2] + Total roof area of all thermal zones. + area_gf : float [m2] + Total ground floor area of all thermal zones. volume : float [m3] Total volume of all thermal zones. sum_heat_load : float [W] @@ -161,6 +165,8 @@ def __init__( self._window_area = {} self.bldg_height = None + self.area_rt = None + self.area_gf = None self.volume = 0 self.sum_heat_load = 0 self.sum_cooling_load = 0 @@ -374,6 +380,8 @@ def calc_building_parameter( self._merge_windows_calc = merge_windows self._used_library_calc = used_library + self.area_rt = 0 + self.area_gf = 0 for zone in self.thermal_zones: zone.calc_zone_parameters( number_of_elements=number_of_elements, @@ -381,6 +389,8 @@ def calc_building_parameter( t_bt=5, ) self.sum_heat_load += zone.model_attr.heat_load + self.area_rt += sum(rf.area for rf in zone.rooftops) + self.area_gf += sum(gf.area for gf in zone.ground_floors) if self.used_library_calc == self.library_attr.__class__.__name__: if self.used_library_calc == "AixLib": diff --git a/teaser/logic/buildingobjects/calculation/aixlib.py b/teaser/logic/buildingobjects/calculation/aixlib.py index e7c24e6f0..95de6ca69 100644 --- a/teaser/logic/buildingobjects/calculation/aixlib.py +++ b/teaser/logic/buildingobjects/calculation/aixlib.py @@ -32,6 +32,9 @@ class AixLib(object): version : str Used AixLib version, default should always be current main version of GitHub + besmod_version : str + Used BESMod version vor export_besmod, default should always be current main version + of GitHub total_surface_area : float [m2] This is the total surface area of the building for interior and exterior surfaces. That includes: OuterWalls, Rooftops, GroundFloors, @@ -61,7 +64,8 @@ def __init__(self, parent): self.file_set_t_cool = "TsetCool_" + self.parent.name + ".txt" self.file_ahu = "AHU_" + self.parent.name + ".txt" self.file_internal_gains = "InternalGains_" + self.parent.name + ".txt" - self.version = "2.1.0" + self.version = "2.1.1" + self.besmod_version = "0.6.0" self.total_surface_area = None self.consider_heat_capacity = True self.use_set_back = True diff --git a/teaser/logic/buildingobjects/calculation/four_element.py b/teaser/logic/buildingobjects/calculation/four_element.py index 16beaf47e..1e764dde8 100644 --- a/teaser/logic/buildingobjects/calculation/four_element.py +++ b/teaser/logic/buildingobjects/calculation/four_element.py @@ -1549,13 +1549,17 @@ def _calc_heat_load(self): ua_value_gf_temp : float [W/(m2*K)] UA Value of all GroundFloors """ + if self.thermal_zone.use_conditions.base_infiltration > 0.5: + raise warnings.warn("The base_infiltration is larger than 0.5, " + "which could lead to ideal heaters being too small.") + self.heat_load = 0.0 ua_value_ow_temp = self.ua_value_rt + self.ua_value_ow self.heat_load_outside_factor = ( (ua_value_ow_temp + self.ua_value_win) + self.thermal_zone.volume - * self.thermal_zone.use_conditions.infiltration_rate + * self.thermal_zone.use_conditions.normative_infiltration * 1 / 3600 * self.thermal_zone.heat_capac_air diff --git a/teaser/logic/buildingobjects/calculation/one_element.py b/teaser/logic/buildingobjects/calculation/one_element.py index 2a544681a..aa94f1192 100644 --- a/teaser/logic/buildingobjects/calculation/one_element.py +++ b/teaser/logic/buildingobjects/calculation/one_element.py @@ -979,6 +979,10 @@ def _calc_heat_load(self): ua_value_gf_temp : float [W/(m2*K)] UA Value of all GroundFloors """ + if self.thermal_zone.use_conditions.base_infiltration > 0.5: + raise warnings.warn("The base_infiltration is larger than 0.5, " + "which could lead to ideal heaters being too small.") + self.heat_load = 0.0 ua_value_gf_temp = sum( ground.ua_value for ground in self.thermal_zone.ground_floors @@ -987,7 +991,7 @@ def _calc_heat_load(self): self.heat_load_outside_factor = ( (ua_value_ow_temp + self.ua_value_win) + self.thermal_zone.volume - * self.thermal_zone.use_conditions.infiltration_rate + * self.thermal_zone.use_conditions.normative_infiltration * 1 / 3600 * self.thermal_zone.heat_capac_air diff --git a/teaser/logic/buildingobjects/calculation/three_element.py b/teaser/logic/buildingobjects/calculation/three_element.py index cf4f9f019..28b38adcf 100644 --- a/teaser/logic/buildingobjects/calculation/three_element.py +++ b/teaser/logic/buildingobjects/calculation/three_element.py @@ -1282,12 +1282,16 @@ def _calc_heat_load(self): ua_value_gf_temp : float [W/(m2*K)] UA Value of all GroundFloors """ + if self.thermal_zone.use_conditions.base_infiltration > 0.5: + raise warnings.warn("The base_infiltration is larger than 0.5, " + "which could lead to ideal heaters being too small.") + self.heat_load = 0.0 ua_value_ow_temp = self.ua_value_ow self.heat_load_outside_factor = ( (ua_value_ow_temp + self.ua_value_win) + self.thermal_zone.volume - * self.thermal_zone.use_conditions.infiltration_rate + * self.thermal_zone.use_conditions.normative_infiltration * 1 / 3600 * self.thermal_zone.heat_capac_air diff --git a/teaser/logic/buildingobjects/calculation/two_element.py b/teaser/logic/buildingobjects/calculation/two_element.py index 893cd628d..383162081 100644 --- a/teaser/logic/buildingobjects/calculation/two_element.py +++ b/teaser/logic/buildingobjects/calculation/two_element.py @@ -1171,6 +1171,10 @@ def _calc_heat_load(self): ua_value_gf_temp : float [W/(m2*K)] UA Value of all GroundFloors """ + if self.thermal_zone.use_conditions.base_infiltration > 0.5: + raise warnings.warn("The base_infiltration is larger than 0.5, " + "which could lead to ideal heaters being too small.") + self.heat_load = 0.0 ua_value_gf_temp = sum( ground.ua_value for ground in self.thermal_zone.ground_floors @@ -1179,7 +1183,7 @@ def _calc_heat_load(self): self.heat_load_outside_factor = ( (ua_value_ow_temp + self.ua_value_win) + self.thermal_zone.volume - * self.thermal_zone.use_conditions.infiltration_rate + * self.thermal_zone.use_conditions.normative_infiltration * 1 / 3600 * self.thermal_zone.heat_capac_air diff --git a/teaser/logic/buildingobjects/thermalzone.py b/teaser/logic/buildingobjects/thermalzone.py index 560c60e49..ac6a45e13 100644 --- a/teaser/logic/buildingobjects/thermalzone.py +++ b/teaser/logic/buildingobjects/thermalzone.py @@ -89,7 +89,6 @@ def __init__(self, parent=None): self.name = None self._area = None self._volume = None - self._infiltration_rate = 0.4 self._outer_walls = [] self._doors = [] self._rooftops = [] @@ -645,18 +644,6 @@ def volume(self, value): else: self._volume = value - @property - def infiltration_rate(self): - warnings.warn( - "Deprecated for ThermalZone, moved to UseConditions", - DeprecationWarning) - - @infiltration_rate.setter - def infiltration_rate(self, value): - warnings.warn( - "Deprecated for ThermalZone, moved to UseConditions", - DeprecationWarning) - @property def t_inside(self): return self._t_inside diff --git a/teaser/logic/buildingobjects/useconditions.py b/teaser/logic/buildingobjects/useconditions.py index a801d1e8a..0aa904528 100644 --- a/teaser/logic/buildingobjects/useconditions.py +++ b/teaser/logic/buildingobjects/useconditions.py @@ -178,6 +178,9 @@ class UseConditions(object): false = natural infiltration + ventilation due to a AHU + window infiltration calculated by window opening model AixLib: Used on Zone level for ventilation. + normative_infiltration: float [1/h] + Infiltration rate for static heat load calculation. + Default is 0.5 based on the DIN EN 12831-1:2017 minimal air exchange rate reference value. base_infiltration : float [1/h] base value for the natural infiltration without window openings AixLib: Used on Zone level for ventilation. @@ -266,7 +269,8 @@ def __init__(self, parent=None): self.lighting_efficiency_lumen = 100 # lighting efficiency in lm/W_el self.use_constant_infiltration = False - self.infiltration_rate = 0.2 + self.normative_infiltration = 0.5 + self.base_infiltration = 0.2 self.max_user_infiltration = 1.0 self.max_overheating_infiltration = [3.0, 2.0] self.max_summer_infiltration = [1.0, 273.15 + 10, 273.15 + 17] @@ -824,3 +828,22 @@ def lighting_power(self, value): ) self._use_maintained_illuminance = False self._lighting_power = value + + @property + def infiltration_rate(self): + warnings.warn( + "'infiltration_rate' is deprecated and will be removed in a future release. " + "Use 'base_infiltration' instead.", + DeprecationWarning, + stacklevel=2) + return self.base_infiltration + + @infiltration_rate.setter + def infiltration_rate(self, value): + self.base_infiltration = value + warnings.warn( + "'infiltration_rate' is deprecated and will be removed in a future release. " + "Use 'base_infiltration' instead.", + DeprecationWarning, + stacklevel=2) + diff --git a/teaser/project.py b/teaser/project.py index 2d012ee60..3d610e3eb 100644 --- a/teaser/project.py +++ b/teaser/project.py @@ -3,11 +3,13 @@ import warnings import os import re +from typing import Optional, Union, List, Dict import teaser.logic.utilities as utilities import teaser.data.utilities as datahandling import teaser.data.input.teaserjson_input as tjson_in import teaser.data.output.teaserjson_output as tjson_out import teaser.data.output.aixlib_output as aixlib_output +import teaser.data.output.besmod_output as besmod_output import teaser.data.output.ibpsa_output as ibpsa_output from teaser.data.dataclass import DataClass from teaser.logic.archetypebuildings.tabula.de.singlefamilyhouse import SingleFamilyHouse @@ -758,18 +760,101 @@ def export_aixlib( ) if report: - try: - from teaser.data.output.reports import model_report - except ImportError: - raise ImportError( - "To create the model report, you have to install TEASER " - "using the option report: pip install teaser[report] or install " - "plotly manually." - ) - report_path = os.path.join(path, "Resources", "ModelReport") - model_report.create_model_report(prj=self, path=report_path) + self._write_report(path) + return path + + def export_besmod( + self, + internal_id: Optional[float] = None, + examples: Optional[List[str]] = None, + path: Optional[str] = None, + THydSup_nominal: Optional[Union[float, Dict[str, float]]] = None, + QBuiOld_flow_design: Optional[Dict[str, Dict[str, float]]] = None, + THydSupOld_design: Optional[Union[float, Dict[str, float]]] = None, + custom_examples: Optional[Dict[str, str]] = None, + custom_script: Optional[Dict[str, str]] = None, + report: bool = False + ) -> str: + """Exports buildings for BESMod simulation + + Exports one (if internal_id is not None) or all buildings as + BESMod.Systems.Demand.Building.TEASERThermalZone models. Additionally, + BESMod.Examples can be specified and directly exported including the building. + + Parameters + ---------- + + internal_id : Optional[float] + Specifies a specific building to export by its internal ID. If None, all buildings are exported. + examples : Optional[List[str]] + Names of BESMod examples to export alongside the building models. + Supported Examples: "TEASERHeatLoadCalculation", "HeatPumpMonoenergetic", and "GasBoilerBuildingOnly". + path : Optional[str] + Alternative output path for storing the exported files. If None, the default TEASER output path is used. + THydSup_nominal : Optional[Union[float, Dict[str, float]]] + Nominal supply temperature(s) for the hydraulic system. Required for + certain examples (e.g., HeatPumpMonoenergetic, GasBoilerBuildingOnly). + See docstring of teaser.data.output.besmod_output.convert_input() for further information. + QBuiOld_flow_design : Optional[Dict[str, Dict[str, float]]] + For partially retrofitted systems specify the old nominal heat flow + of all zones in the Buildings in a nested dictionary with + the building names and in a level below the zone names as keys. + By default, only the radiator transfer system is not retrofitted in BESMod. + THydSupOld_design : Optional[Union[float, Dict[str, float]]] + Design supply temperatures for old, non-retrofitted hydraulic systems. + custom_examples: Optional[Dict[str, str]] + Specify custom examples with a dictionary containing the example name as the key and + the path to the corresponding custom mako template as the value. + custom_script: Optional[Dict[str, str]] + Specify custom .mos scripts for the existing and custom examples with a dictionary + containing the example name as the key and the path to the corresponding custom mako template as the value. + report : bool + If True, generates a model report in HTML and CSV format for the exported project. Default is False. + + Returns + ------- + str + The path where the exported files are stored. + """ + + if path is None: + path = os.path.join(utilities.get_default_path(), self.name) + else: + path = os.path.join(path, self.name) + + utilities.create_path(path) + + if internal_id is None: + besmod_output.export_besmod( + buildings=self.buildings, prj=self, path=path, examples=examples, THydSup_nominal=THydSup_nominal, + QBuiOld_flow_design=QBuiOld_flow_design, THydSupOld_design=THydSupOld_design, + custom_examples=custom_examples, custom_script=custom_script + ) + else: + for bldg in self.buildings: + if bldg.internal_id == internal_id: + besmod_output.export_besmod( + buildings=[bldg], prj=self, path=path, examples=examples, THydSup_nominal=THydSup_nominal, + QBuiOld_flow_design=QBuiOld_flow_design, THydSupOld_design=THydSupOld_design, + custom_examples=custom_examples, custom_script=custom_script + ) + + if report: + self._write_report(path) return path + def _write_report(self, path): + try: + from teaser.data.output.reports import model_report + except ImportError: + raise ImportError( + "To create the model report, you have to install TEASER " + "using the option report: pip install teaser[report] or install " + "plotly manually." + ) + report_path = os.path.join(path, "Resources", "ModelReport") + model_report.create_model_report(prj=self, path=report_path) + def export_ibpsa(self, library="AixLib", internal_id=None, path=None): """Exports values to a record file for Modelica simulation @@ -860,6 +945,41 @@ def set_default(self, load_data=None): self._merge_windows_calc = False self._used_library_calc = "AixLib" + def set_location_parameters(self, + t_outside=262.65, + t_ground=286.15, + weather_file_path=None, + calc_all_buildings=True): + """ Set location specific parameters + + Temperatures are used for static heat load calculation + and as parameters in the exports. + Default location is Mannheim. + + Parameters + ---------- + t_outside: float [K] + Normative outdoor temperature for static heat load calculation. + The input of t_inside is ALWAYS in Kelvin + t_ground: float [K] + Temperature directly at the outer side of ground floors for static + heat load calculation. + The input of t_ground is ALWAYS in Kelvin + weather_file_path : str + Absolute path to weather file used for Modelica simulation. Default + weather file can be find in inputdata/weatherdata. + calc_all_buildings: boolean + If True, calculates all buildings new. Default is True. + Important for new calculation of static heat load. + """ + self.weather_file_path = weather_file_path + for bldg in self.buildings: + for tz in bldg.thermal_zones: + tz.t_outside = t_outside + tz.t_ground = t_ground + if calc_all_buildings: + self.calc_all_buildings() + @staticmethod def process_export_vars(export_vars): """Process export vars to fit __Dymola_selections syntax.""" diff --git a/tests/helptest.py b/tests/helptest.py index 7e0af6123..07dd13a89 100644 --- a/tests/helptest.py +++ b/tests/helptest.py @@ -32,7 +32,6 @@ def building_test2(prj): tz.name = "Living Room" tz.area = 140.0 tz.volume = tz.area * bldg.number_of_floors * bldg.height_of_floors - tz.infiltration_rate = 0.5 tz.use_conditions = UseConditions(tz) tz.use_conditions.usage = "Living" diff --git a/tests/test_besmod_output.py b/tests/test_besmod_output.py new file mode 100644 index 000000000..44661f067 --- /dev/null +++ b/tests/test_besmod_output.py @@ -0,0 +1,259 @@ +import os +import unittest +from teaser.logic import utilities +from teaser.project import Project +from teaser.data.output.besmod_output import _convert_heating_profile + + +class Test_besmod_output(unittest.TestCase): + + def test_export_besmod(self): + """test of export_besmod, no calculation verification""" + + prj = Project() + + prj.add_residential( + construction_data='iwu_heavy', + geometry_data='iwu_single_family_dwelling', + name="ResidentialBuilding", + year_of_construction=1988, + number_of_floors=2, + height_of_floors=3.2, + net_leased_area=200.0) + + prj.add_non_residential( + construction_data='iwu_heavy', + geometry_data='bmvbs_institute', + name="InstituteBuilding", + year_of_construction=1952, + number_of_floors=5, + height_of_floors=4.0, + net_leased_area=3400.0) + + prj.number_of_elements_calc = 1 + prj.used_library_calc = "IBPSA" + with self.assertRaises(AttributeError): + prj.export_besmod() + prj.used_library_calc = "AixLib" + with self.assertRaises(NotImplementedError): + prj.export_besmod() + prj.number_of_elements_calc = 4 + prj.calc_all_buildings() + prj.export_besmod() + with self.assertRaises(ValueError): + prj.export_besmod(examples="wrong_example") + examples = ["TEASERHeatLoadCalculation", + "HeatPumpMonoenergetic", + "GasBoilerBuildingOnly"] + with self.assertRaises(ValueError): + prj.export_besmod(examples=examples) + + prj.export_besmod(examples=examples, + THydSup_nominal=55 + 273.15) + + t_hyd_sup_nominal = {"ResidentialBuilding": 328.15, + "WrongBuilding": {"Office": 343.15, + "Floor": 343.15, + "Storage": 343.15, + "Meeting": 343.15, + "Restroom": 343.15, + "ICT": 343.15, + "Laboratory": 328.15}} + with self.assertRaises(KeyError): + prj.export_besmod(examples=examples, + THydSup_nominal=t_hyd_sup_nominal) + + t_hyd_sup_nominal = {"ResidentialBuilding": 328.15, + "InstituteBuilding": {"WrongZone": 343.15, + "Floor": 343.15, + "Storage": 343.15, + "Meeting": 343.15, + "Restroom": 343.15, + "ICT": 343.15, + "Laboratory": 328.15}} + with self.assertRaises(KeyError): + prj.export_besmod(examples=examples, + THydSup_nominal=t_hyd_sup_nominal) + + t_hyd_sup_nominal = {"ResidentialBuilding": 328.15, + "InstituteBuilding": "WrongValue"} + with self.assertRaises(ValueError): + prj.export_besmod(examples=examples, + THydSup_nominal=t_hyd_sup_nominal) + + t_hyd_sup_nominal = {"ResidentialBuilding": 328.15, + "InstituteBuilding": {"Office": 343.15, + "Floor": 343.15, + "Storage": 343.15, + "Meeting": 343.15, + "Restroom": 343.15, + "ICT": 343.15, + "Laboratory": 328.15}} + prj.export_besmod(examples=examples, + THydSup_nominal=t_hyd_sup_nominal) + + q_bui_old_flow_design = { + bldg.name: { + tz.name: tz.model_attr.heat_load for tz in bldg.thermal_zones + } + for bldg in prj.buildings + } + custom_template_path = utilities.get_full_path("examples/examplefiles/custom_besmod_templates") + custom_example_template = {"ModelicaConferencePaper": os.path.join(custom_template_path, "custom_template.txt")} + custom_script = {"HeatPumpMonoenergetic": os.path.join(custom_template_path, "custom_script_hp_mono.txt"), + "ModelicaConferencePaper": os.path.join(custom_template_path, "custom_script.txt")} + t_hyd_sup_nominal_old = {1950: 90 + 273.15, + 1980: 70 + 273.15} + prj.export_besmod(examples=examples, + THydSup_nominal=t_hyd_sup_nominal, + QBuiOld_flow_design=q_bui_old_flow_design, + THydSupOld_design=t_hyd_sup_nominal_old, + custom_examples=custom_example_template, + custom_script=custom_script) + + def test_convert_heating_profile(self): + """Test the conversion of heating profiles for BESMod""" + with self.assertRaises(ValueError): + _convert_heating_profile([293.15]) + heating_profile = [290.15, + 290.15, + 290.15, + 290.15, + 293.15, + 293.15, + 293.15, + 293.15, + 293.15, + 293.15, + 293.15, + 290.15, + 290.15, + 290.15, + 293.15, + 293.15, + 293.15, + 293.15, + 293.15, + 293.15, + 293.15, + 290.15, + 290.15, + 290.15] + with self.assertRaises(ValueError): + _convert_heating_profile(heating_profile) + heating_profile = [293.15, + 293.15, + 293.15, + 293.15, + 293.15, + 293.15, + 293.15, + 293.15, + 293.15, + 293.15, + 293.15, + 293.15, + 293.15, + 293.15, + 293.15, + 293.15, + 293.15, + 293.15, + 293.15, + 293.15, + 293.15, + 293.15, + 293.15, + 293.15] + t_set_zone_nominal, start_time, width, amplitude = _convert_heating_profile(heating_profile) + self.assertEqual(t_set_zone_nominal, 293.15) + self.assertAlmostEqual(start_time, 0) + self.assertAlmostEqual(width, 1e-50) + self.assertEqual(amplitude, 0) + heating_profile = [290.15, + 290.15, + 290.15, + 290.15, + 290.15, + 290.15, + 293.15, + 293.15, + 293.15, + 293.15, + 293.15, + 293.15, + 293.15, + 293.15, + 293.15, + 293.15, + 293.15, + 293.15, + 293.15, + 293.15, + 293.15, + 293.15, + 293.15, + 293.15] + t_set_zone_nominal, start_time, width, amplitude = _convert_heating_profile(heating_profile) + self.assertEqual(t_set_zone_nominal, 293.15) + self.assertAlmostEqual(start_time, 0) + self.assertAlmostEqual(width, 6 / 24 * 100) + self.assertEqual(amplitude, -3) + heating_profile = [293.15, + 293.15, + 293.15, + 293.15, + 293.15, + 293.15, + 293.15, + 293.15, + 293.15, + 293.15, + 293.15, + 293.15, + 293.15, + 293.15, + 293.15, + 293.15, + 293.15, + 293.15, + 293.15, + 293.15, + 290.15, + 290.15, + 290.15, + 290.15] + t_set_zone_nominal, start_time, width, amplitude = _convert_heating_profile(heating_profile) + self.assertEqual(t_set_zone_nominal, 293.15) + self.assertAlmostEqual(start_time, 20 * 3600) + self.assertAlmostEqual(width, 4 / 24 * 100) + self.assertEqual(amplitude, -3) + heating_profile = [290.15, + 293.15, + 293.15, + 293.15, + 293.15, + 293.15, + 293.15, + 293.15, + 293.15, + 293.15, + 293.15, + 293.15, + 293.15, + 293.15, + 293.15, + 293.15, + 293.15, + 293.15, + 293.15, + 293.15, + 293.15, + 293.15, + 293.15, + 290.15] + t_set_zone_nominal, start_time, width, amplitude = _convert_heating_profile(heating_profile) + self.assertEqual(t_set_zone_nominal, 293.15) + self.assertAlmostEqual(start_time, 23 * 3600) + self.assertAlmostEqual(width, 2 / 24 * 100) + self.assertEqual(amplitude, -3) diff --git a/tests/test_data.py b/tests/test_data.py index c5d97bc2e..7329e2ac6 100644 --- a/tests/test_data.py +++ b/tests/test_data.py @@ -1173,7 +1173,7 @@ def test_calc_building_parameter(self): ) assert round(prj.buildings[-1].volume, 1) == 490.0 - assert round(prj.buildings[-1].sum_heat_load, 4) == 5023.0256 + assert round(prj.buildings[-1].sum_heat_load, 4) == 6659.6256 # methods in therm_zone @@ -1191,7 +1191,7 @@ def test_heat_load(self): """test of heating_load""" prj.set_default() helptest.building_test2(prj) - prj.buildings[-1].thermal_zones[-1].use_conditions.infiltration_rate = 0.5 + prj.buildings[-1].thermal_zones[-1].use_conditions.base_infiltration = 0.5 prj.buildings[-1].thermal_zones[-1].calc_zone_parameters( number_of_elements=2, merge_windows=True ) @@ -2405,7 +2405,6 @@ def test_export_aixlib_only_iw(self): tz.name = "LivingRoom" tz.area = 140.0 tz.volume = tz.area * bldg.number_of_floors * bldg.height_of_floors - tz.infiltration_rate = 0.5 from teaser.logic.buildingobjects.useconditions import UseConditions @@ -2498,7 +2497,6 @@ def test_export_only_ow(self): tz.name = "LivingRoom" tz.area = 140.0 tz.volume = tz.area * bldg.number_of_floors * bldg.height_of_floors - tz.infiltration_rate = 0.5 from teaser.logic.buildingobjects.useconditions import UseConditions @@ -2619,7 +2617,6 @@ def test_export_only_win(self): tz.name = "LivingRoom" tz.area = 140.0 tz.volume = tz.area * bldg.number_of_floors * bldg.height_of_floors - tz.infiltration_rate = 0.5 from teaser.logic.buildingobjects.useconditions import UseConditions @@ -2755,7 +2752,6 @@ def test_export_only_rt(self): tz.name = "LivingRoom" tz.area = 140.0 tz.volume = tz.area * bldg.number_of_floors * bldg.height_of_floors - tz.infiltration_rate = 0.5 from teaser.logic.buildingobjects.useconditions import UseConditions @@ -2893,7 +2889,6 @@ def test_export_only_gf(self): tz.name = "LivingRoom" tz.area = 140.0 tz.volume = tz.area * bldg.number_of_floors * bldg.height_of_floors - tz.infiltration_rate = 0.5 from teaser.logic.buildingobjects.useconditions import UseConditions diff --git a/tests/test_examples.py b/tests/test_examples.py index 8e516ef4c..7b67d145d 100644 --- a/tests/test_examples.py +++ b/tests/test_examples.py @@ -67,3 +67,9 @@ def test_e8_example_change_boundary_conditions(self): from teaser.examples import e8_change_boundary_conditions as e8 prj = e8.example_change_boundary_conditions() + + def test_e11_example_export_besmod(self): + """Tests the executability of example 11""" + from teaser.examples import e11_export_besmod_models as e11 + + prj = e11.example_export_besmod()