def post_process(self, scaffold, building_names): """ Cleanup the export of Spawn files into a format suitable for the district-based analysis. This includes the following: * Add a Loads project * Add a project level project :param scaffold: Scaffold object, Scaffold of the entire directory of the project. :param building_names: list, names of the buildings that need to be cleaned up after export :return: None """ for b in building_names: b_modelica_path = os.path.join(scaffold.loads_path.files_dir, b) new_package = PackageParser.new_from_template( b_modelica_path, b, ["building", "coupling"], within=f"{scaffold.project_name}.Loads") new_package.save() # now create the Loads level package. This (for now) will create the package without considering any existing # files in the Loads directory. package = PackageParser.new_from_template( scaffold.loads_path.files_dir, "Loads", building_names, within=f"{scaffold.project_name}") package.save() # now create the Package level package. This really needs to happen at the GeoJSON to modelica stage, but # do it here for now to aid in testing. pp = PackageParser.new_from_template(scaffold.project_path, scaffold.project_name, ["Loads"]) pp.save()
def test_heating_indirect(self): project_name = "heating_indirect" self.data_dir, self.output_dir = self.set_up(os.path.dirname(__file__), project_name) # load in the example geojson with a single office building filename = os.path.join(self.data_dir, "time_series_ex1.json") self.gj = UrbanOptGeoJson(filename) # scaffold the project ourselves scaffold = Scaffold(self.output_dir, project_name) scaffold.create() # load system parameter data filename = os.path.join(self.data_dir, "time_series_system_params_ets.json") sys_params = SystemParameters(filename) # currently we must setup the root project before we can run to_modelica package = PackageParser.new_from_template(scaffold.project_path, scaffold.project_name, order=[]) package.save() # now test the connector (independent of the larger geojson translator) geojson_load_id = self.gj.buildings[0].feature.properties["id"] self.heating_indirect = HeatingIndirect(sys_params, geojson_load_id) self.heating_indirect.to_modelica(scaffold) root_path = os.path.abspath( os.path.join(scaffold.substations_path.files_dir)) geojson_id = self.gj.buildings[0].feature.properties["id"] model_filepath = os.path.join(root_path, f'HeatingIndirect_{geojson_id}.mo') self.assertTrue(os.path.exists(model_filepath), f"File does not exist: {model_filepath}")
def test_new_from_template(self): package = PackageParser.new_from_template(self.output_dir, 'new_model_name', ["model_a", "model_b"], within="SomeWithin") package.save() self.assertTrue( os.path.exists(os.path.join(self.output_dir, 'package.mo'))) self.assertTrue( os.path.exists(os.path.join(self.output_dir, 'package.order'))) # check for strings in files with open(os.path.join(self.output_dir, 'package.mo')) as f: file_data = f.read() self.assertTrue('within SomeWithin;' in file_data, 'Incorrect within clause') self.assertTrue('package new_model_name' in file_data, 'Incorrect package definition') self.assertTrue('end new_model_name;' in file_data, 'Incorrect package ending') with open(os.path.join(self.output_dir, 'package.order')) as f: self.assertTrue('model_a\nmodel_b' in f.read(), 'Incorrect package order')
def test_rename_model(self): package = PackageParser.new_from_template(self.output_dir, 'rename_model', ["model_1", "model_2"], within="RenameWithin") package.save() package.rename_model('model_1', 'my_super_new_model') self.assertEqual(len(package.order), 2) self.assertIn('my_super_new_model', package.order)
def test_round_trip(self): package = PackageParser.new_from_template(self.output_dir, 'another_model', ["model_x", "model_y"], within="DifferentWithin") package.save() # Read in the package package = PackageParser(self.output_dir) self.assertListEqual(package.order, ["model_x", "model_y"])
def post_process(self, scaffold): """ Cleanup the export of TimeSeries files into a format suitable for the district-based analysis. This includes the following: * Add a Loads project * Add a project level project :param scaffold: Scaffold object, Scaffold of the entire directory of the project. :return: None """ order_files = [Path(mo).stem for mo in self.required_mo_files] order_files += self.template_files_to_include b_modelica_path = Path( scaffold.loads_path.files_dir) / self.building_name new_package = PackageParser.new_from_template( path=b_modelica_path, name=self.building_name, order=order_files, within=f"{scaffold.project_name}.Loads") new_package.save() # now create the Loads level package and package.order. if not os.path.exists( os.path.join(scaffold.loads_path.files_dir, 'package.mo')): load_package = PackageParser.new_from_template( scaffold.loads_path.files_dir, "Loads", [self.building_name], within=f"{scaffold.project_name}") load_package.save() else: load_package = PackageParser( os.path.join(scaffold.loads_path.files_dir)) load_package.add_model(self.building_name) load_package.save() # now create the Package level package. This really needs to happen at the GeoJSON to modelica stage, but # do it here for now to aid in testing. pp = PackageParser.new_from_template(scaffold.project_path, scaffold.project_name, ["Loads"]) pp.save()
def test_add_model(self): package = PackageParser.new_from_template( self.output_dir, 'so_many_models', ["model_beta", "model_gamma"], within="SoMany") package.save() package.add_model('model_delta') package.add_model('model_alpha', 0) self.assertEqual(len(package.order), 4) self.assertListEqual( ['model_alpha', 'model_beta', 'model_gamma', 'model_delta'], package.order)
def test_no_ets_and_run(self): project_name = "time_series_no_ets" self.data_dir, self.output_dir = self.set_up(os.path.dirname(__file__), project_name) # load in the example geojson with a single office building filename = os.path.join(self.data_dir, "time_series_ex1.json") self.gj = UrbanOptGeoJson(filename) # scaffold the project ourselves scaffold = Scaffold(self.output_dir, project_name) scaffold.create() # load system parameter data filename = os.path.join(self.data_dir, "time_series_system_params_no_ets.json") sys_params = SystemParameters(filename) # now test the connector (independent of the larger geojson translator) self.time_series = TimeSeries(sys_params, self.gj.buildings[0]) self.assertIsNotNone(self.time_series) self.assertIsNotNone(self.time_series.building) self.assertEqual( "time_series", self.time_series.system_parameters.get_param("buildings.custom")[0] ["load_model"]) # currently we must setup the root project before we can run to_modelica package = PackageParser.new_from_template(scaffold.project_path, scaffold.project_name, order=[]) package.save() self.time_series.to_modelica(scaffold) root_path = os.path.abspath( os.path.join(scaffold.loads_path.files_dir, 'B5a6b99ec37f4de7f94020090')) files = [ os.path.join(root_path, 'building.mo'), ] # verify that there are only 2 files that matter (coupling and building) for file in files: self.assertTrue(os.path.exists(file), f"File does not exist: {file}")
def to_modelica(self, project_name, save_dir, model_connector_str="TeaserConnector"): """ Convert the data in the GeoJSON to modelica based-objects :param save_dir: str, directory where the exported project will be stored. The name of the project will be {save_dir}/{project_name} :param model_connector_str: str, which model_connector to use """ self.scaffold_directory(save_dir, project_name) # TODO: Create a map to load the required model_connectors import geojson_modelica_translator.model_connectors.teaser import geojson_modelica_translator.model_connectors.spawn mc_klass = getattr(geojson_modelica_translator.model_connectors.teaser, model_connector_str) model_connector = mc_klass(self.system_parameters) _log.info("Exporting to Modelica") for building in self.buildings: # print("Jing2: ", building.feature.properties["type"]) _log.info( f"Adding building to model connector: {mc_klass.__class__}") model_connector.add_building(building) _log.info(f"Translating building to model {building}") model_connector.to_modelica(self.scaffold, keep_original_models=False) # add in Substations # TODO: YL, where are the substations/ETSs? # add in Districts # add in Plants # now add in the top level package. pp = PackageParser.new_from_template(self.scaffold.project_path, project_name, ["Loads"]) pp.save()
def to_modelica(self, scaffold): """ Create indirect cooling models based on the data in the buildings and geojsons :param scaffold: Scaffold object, Scaffold of the entire directory of the project. """ cooling_indirect_template = self.template_env.get_template( "CoolingIndirect.mot") ets_data = self.system_parameters.get_param_by_building_id( self._geojson_load_id, 'ets_model_parameters.indirect') combined_template_data = {**ets_data, **self.district_template_data} self.run_template( template=cooling_indirect_template, save_file_name=os.path.join(scaffold.project_path, 'Substations', f'{self._model_filename}.mo'), project_name=scaffold.project_name, model_filename=self._model_filename, ets_data=combined_template_data, ) # now create the Package level package. This really needs to happen at the GeoJSON to modelica stage, but # do it here for now to aid in testing. package = PackageParser(scaffold.project_path) if 'Substations' not in package.order: package.add_model('Substations') package.save() package_models = [self._model_filename] ets_package = PackageParser(scaffold.substations_path.files_dir) if ets_package.order_data is None: ets_package = PackageParser.new_from_template( path=scaffold.substations_path.files_dir, name="Substations", order=package_models, within=scaffold.project_name) else: for model_name in package_models: if model_name not in ets_package.order: ets_package.add_model(model_name) ets_package.save()
def post_process(self, scaffold, building_names, keep_original_models=False): """ Cleanup the export of the TEASER files into a format suitable for the district-based analysis. This includes the following: * Update the partial to inherit from the GeojsonExport class defined in MBL. * Rename the files to remove the names of the buildings * Move the files to the Loads level and remove the Project folder (default export method from TEASER) * Add heat port * Add return temperature * Remove weaDat and rely on weaBus :param project_name: string, name of the project which will be used to set the package.mo file :param root_building_dir: string, where the project will be ultimately saved :param building_names: list, names of the buildings that need to be cleaned up after export :return: None """ for b in building_names: # create a list of strings that we need to replace in all the file as we go along string_replace_list = [] # create a new modelica based path for the buildings # TODO: make this work at the toplevel, somehow. b_modelica_path = ModelicaPath(f"B{b}", scaffold.loads_path.files_dir, True) # copy over the entire model to the new location copytree( os.path.join(scaffold.loads_path.files_dir, f"Project/B{b}/B{b}_Models"), b_modelica_path.files_dir, ) # read in the package to apply the changes as they other files are processed # TODO: these should be linked, so a rename method should act across the model and the package.order package = PackageParser(os.path.join(scaffold.loads_path.files_dir, f"B{b}")) # move the internal gains files to a new resources folder mat_files = glob.glob(os.path.join(scaffold.loads_path.files_dir, f"B{b}/*.txt")) for f in mat_files: new_file_name = os.path.basename(f).replace(f"B{b}", "") os.rename(f, f"{b_modelica_path.resources_dir}/{new_file_name}") string_replace_list.append( ( f"Project/B{b}/B{b}_Models/{os.path.basename(f)}", f"Loads/{b_modelica_path.resources_relative_dir}/{new_file_name}", ) ) # process each of the building models mo_files = glob.glob(os.path.join(scaffold.loads_path.files_dir, f"B{b}/*.mo")) for f in mo_files: # ignore the package.mo file if os.path.basename(f) == "package.mo": continue mofile = InputParser(f) # previous paths and replace with the new one. # Make sure to update the names of any resources as well. mofile.replace_within_string(f"{scaffold.project_name}.Loads.B{b}") # remove ReaderTMY3 mofile.remove_object("ReaderTMY3") # updating path to internal loads for s in string_replace_list: mofile.replace_model_string("Modelica.Blocks.Sources.CombiTimeTable", "internalGains", s[0], s[1]) # add heat port data = [ "annotation (Placement(transformation(extent={{-10,90},{10,110}}), " "iconTransformation(extent={{-10,90},{10,110}})));" ] mofile.add_model_object( "Modelica.Thermal.HeatTransfer.Interfaces.HeatPort_a", "port_a", data, ) # add TAir output # TODO: read in the object by name -- parse the parenthetic content instance = 'TAir(\n quantity="ThermodynamicTemperature", unit="K", displayUnit="degC")' data = [ '"Room air temperature"', "annotation (Placement(transformation(extent={{100,-10},{120,10}})));", ] mofile.add_model_object( "Buildings.Controls.OBC.CDL.Interfaces.RealOutput", instance, data ) # All existing weaDat.weaBus connections need to be updated to simply weaBus mofile.replace_connect_string('weaDat.weaBus', None, 'weaBus', None, True) # Now remove the redundant weaBus -> weaBus connection mofile.remove_connect_string('weaBus', 'weaBus') # add new port connections if self.system_parameters.get_param( "buildings.default.load_model_parameters.rc.order", default=2) == 1: data = "annotation (Line(points={{0,100},{96,100},{96,20},{92,20}}, color={191,0,0}))" mofile.add_connect("port_a", "thermalZoneOneElement.intGainsConv", data) data = "annotation (Line(points={{93,32},{98,32},{98,0},{110,0}}, color={0,0,127}))" mofile.add_connect("thermalZoneOneElement.TAir", "TAir", data) elif self.system_parameters.get_param( "buildings.default.load_model_parameters.rc.order", default=2) == 2: data = "annotation (Line(points={{0,100},{96,100},{96,20},{92,20}}, color={191,0,0}))" mofile.add_connect("port_a", "thermalZoneTwoElements.intGainsConv", data) data = "annotation (Line(points={{93,32},{98,32},{98,0},{110,0}}, color={0,0,127}))" mofile.add_connect("thermalZoneTwoElements.TAir", "TAir", data) elif self.system_parameters.get_param( "buildings.default.load_model_parameters.rc.order", default=2) == 4: data = "annotation (Line(points={{0,100},{96,100},{96,20},{92,20}}, color={191,0,0}))" mofile.add_connect("port_a", "thermalZoneFourElements.intGainsConv", data) data = "annotation (Line(points={{93,32},{98,32},{98,0},{110,0}}, color={0,0,127}))" mofile.add_connect("thermalZoneFourElements.TAir", "TAir", data) # change the name of the modelica model to remove the building id, update in package too! new_model_name = mofile.model["name"].split("_")[1] package.rename_model(mofile.model["name"], new_model_name) mofile.model["name"] = new_model_name # Save as the new filename (without building ID) new_filename = os.path.join( scaffold.loads_path.files_dir, f'B{b}/{os.path.basename(f).split("_")[1]}' ) mofile.save_as(new_filename) os.remove(f) # save the updated package.mo and package.order. new_package = PackageParser.new_from_template( package.path, f"B{b}", package.order, within=f"{scaffold.project_name}.Loads" ) new_package.save() # remaining clean up tasks across the entire exported project if not keep_original_models: shutil.rmtree(os.path.join(scaffold.loads_path.files_dir, "Project")) # now create the Loads level package. This (for now) will create the package without considering any existing # files in the Loads directory. # add in the silly 'B' before the building names package = PackageParser.new_from_template( scaffold.loads_path.files_dir, "Loads", ["B" + b for b in building_names], within=f"{scaffold.project_name}" ) package.save()
def post_process(self, scaffold, keep_original_models=False): """Cleanup the export of the TEASER files into a format suitable for the district-based analysis. This includes the following: * Update the partial to inherit from the GeojsonExport class defined in MBL. * Rename the files to remove the names of the buildings * Move the files to the Loads level and remove the Project folder (default export method from TEASER) * Add heat port * Add return temperature * Add fluid ports for the indoor air volume. * Remove weaDat and rely on weaBus * Add latent fraction multiplier * Add TAir output * Add TRad output * Add nPorts variable * Propagate use of moisture balance * Wrap the thermal zones into a single model :param scaffold: Scaffold object, Scaffold of the entire directory of the project. :param keep_original_models: boolean, do not delete the original models generated by TEASER :return: None """ teaser_building = self.template_env.get_template("TeaserBuilding.mot") teaser_coupling = self.template_env.get_template( "TeaserCouplingBuilding.mot") run_coupling_template = self.template_env.get_template( "RunTeaserCouplingBuilding.most") # This for loop does *a lot* of work to make the models compatible for the project structure. # Need to investigate moving this into a more testable location. # create a list of strings that we need to replace in all the file as we go along string_replace_list = [] mos_weather_filename = self.system_parameters.get_param_by_building_id( self.building_id, "load_model_parameters.rc.mos_weather_filename") # create a new modelica based path for the buildings # TODO: make this work at the toplevel, somehow. b_modelica_path = ModelicaPath(self.building_name, scaffold.loads_path.files_dir, True) # copy over the entire model to the new location copytree( os.path.join( scaffold.loads_path.files_dir, f"Project/{self.building_name}/{self.building_name}_Models"), b_modelica_path.files_dir, ) # read in the package to apply the changes as they other files are processed # TODO: these should be linked, so a rename method should act across the model and the package.order package = PackageParser( os.path.join(scaffold.loads_path.files_dir, self.building_name)) # move the internal gains files to a new resources folder mat_files = glob.glob( os.path.join(scaffold.loads_path.files_dir, f"{self.building_name}/*.txt")) for f in mat_files: new_file_name = os.path.basename(f).replace(self.building_name, "") os.rename(f, f"{b_modelica_path.resources_dir}/{new_file_name}") # The main branch of teaser has yet to merge in the changes to support the fixes to the # internal gain files. The next method can be removed once the TEASER development branch is # merged into master/main and released. self.fix_gains_file( f"{b_modelica_path.resources_dir}/{new_file_name}") string_replace_list.append(( f"Project/{self.building_name}/{self.building_name}_Models/{os.path.basename(f)}", f"{scaffold.project_name}/Loads/{b_modelica_path.resources_relative_dir}/{new_file_name}", )) # process each of the thermal zones thermal_zone_files = [] mo_files = glob.glob( os.path.join(scaffold.loads_path.files_dir, f"{self.building_name}/*.mo")) for f in mo_files: # ignore the package.mo file if os.path.basename(f) in ["package.mo"]: continue mofile = Model(f) # previous paths and replace with the new one. # Make sure to update the names of any resources as well. mofile.set_within_statement( f'{scaffold.project_name}.Loads.{self.building_name}') # remove ReaderTMY3 mofile.remove_component( "Buildings.BoundaryConditions.WeatherData.ReaderTMY3", "weaDat") # updating path to internal loads for s in string_replace_list: new_file_path = s[1] new_resource_arg = f'''Modelica.Utilities.Files.loadResource("modelica://{new_file_path}")''' old_file_path = s[0] old_resource_arg = f'''Modelica.Utilities.Files.loadResource("modelica://{old_file_path}")''' mofile.update_component_modification( "Modelica.Blocks.Sources.CombiTimeTable", "internalGains", "fileName", new_resource_arg, if_value=old_resource_arg) # add heat port convective heat flow. mofile.insert_component( "Modelica.Thermal.HeatTransfer.Interfaces.HeatPort_a", "port_a", string_comment='Heat port for convective heat flow.', annotations=[ "Placement(transformation(extent={{-10,90},{10,110}}), " "iconTransformation(extent={{-10,90},{10,110}}))" ]) # add heat port radiative heat flow. mofile.insert_component( "Modelica.Thermal.HeatTransfer.Interfaces.HeatPort_a", "port_b", string_comment='Heat port for radiative heat flow.', annotations=[ "Placement(transformation(extent={{30,-110},{50,-90}}, " "iconTransformation(extent={{40,-112},{60,-92}})))" ]) # add fluid ports for the indoor air volume. mofile.insert_component( "Modelica.Fluid.Vessels.BaseClasses.VesselFluidPorts_b", "ports[nPorts]", string_comment= 'Auxiliary fluid inlets and outlets to indoor air volume.', modifications={ 'redeclare each final package Medium': 'Buildings.Media.Air' }, annotations=[ "Placement(transformation(extent={{-30, -8}, {30, 8}},origin={0, -100}), " "iconTransformation(extent={{-23.25, -7.25}, {23.25, 7.25}}," "origin={-0.75, -98.75}))" ]) fraction_latent_person = self.system_parameters.get_param( "buildings.default.load_model_parameters.rc.fraction_latent_person", default=1.25) use_moisture_balance = self.system_parameters.get_param( "buildings.default.load_model_parameters.rc.use_moisture_balance", default='false') n_ports = self.system_parameters.get_param( "buildings.default.load_model_parameters.rc.nPorts", default=0) # create a new parameter for fraction latent person mofile.add_parameter( 'Real', 'fraLat', assigned_value=fraction_latent_person, string_comment= 'Fraction latent of sensible persons load = 0.8 for home, 1.25 for office.' ) # create a new Boolean parameter to evaluate the persons latent loads. mofile.add_parameter( 'Boolean', 'use_moisture_balance', assigned_value=use_moisture_balance, string_comment= 'If true, input connector QLat_flow is enabled and room air computes' ' moisture balance.') # create a integer parameter to evaluate number of connected ports. mofile.add_parameter('Integer', 'nPorts', assigned_value=n_ports, string_comment='Number of fluid ports.', annotations=['connectorSizing=true']) # Set the fraction latent person in the template by simply replacing the value mofile.insert_component( 'Modelica.Blocks.Sources.RealExpression', 'perLatLoa', modifications={ 'y': 'internalGains.y[2]*fraLat', }, conditional='if use_moisture_balance', string_comment='Latent person loads', annotations=[ 'Placement(transformation(extent={{-80,-60},{-60,-40}}))' ]) # add TRad output mofile.insert_component( 'Buildings.Controls.OBC.CDL.Interfaces.RealOutput', 'TRad', modifications={ 'quantity': '"ThermodynamicTemperature"', 'unit': '"K"', 'displayUnit': '"degC"', }, string_comment='Mean indoor radiation temperature', annotations=[ 'Placement(transformation(extent={{100,-10},{120,10}}))' ]) # All existing weaDat.weaBus connections need to be updated to simply weaBus # (except for the connections where 'weaBus' is port_b, we will just delete these) mofile.edit_connect('weaDat.weaBus', '!weaBus', new_port_a='weaBus') # Now remove the unnecessary weaDat.weaBus -> weaBus connection mofile.remove_connect('weaDat.weaBus', 'weaBus') # add new port connections rc_order = self.system_parameters.get_param_by_building_id( self.building_id, "load_model_parameters.rc.order", default=2) thermal_zone_name = None thermal_zone_type = None if rc_order == 1: thermal_zone_type = 'OneElement' thermal_zone_name = 'thermalZoneOneElement' elif rc_order == 2: thermal_zone_type = 'TwoElements' thermal_zone_name = 'thermalZoneTwoElements' elif rc_order == 3: thermal_zone_type = 'ThreeElements' thermal_zone_name = 'thermalZoneThreeElements' elif rc_order == 4: thermal_zone_type = 'FourElements' thermal_zone_name = 'thermalZoneFourElements' if thermal_zone_name is not None and thermal_zone_type is not None: # add TAir output - This has been moved away from the other insert_component blocks # to use thermal_zone_name mofile.insert_component( 'Buildings.Controls.OBC.CDL.Interfaces.RealOutput', 'TAir', modifications={ 'quantity': '"ThermodynamicTemperature"', 'unit': '"K"', 'displayUnit': '"degC"', }, conditional= f'if {thermal_zone_name}.ATot > 0 or {thermal_zone_name}.VAir > 0', string_comment='Room air temperature', annotations=[ 'Placement(transformation(extent={{100,38},{120,58}}))' ]) # In TEASER 0.7.5 the hConvWinOut, hConvExt, hConvWin, hConvInt, hConvFloor, hConvRoof in various of # the ReducedOrder models should be hCon* not hConv*. This has been fixed on the development branch # of TEASER, but the team doesn't appear to be releasing nor merging the development branch (yet). mofile.rename_component_argument( "Buildings.ThermalZones.ReducedOrder.EquivalentAirTemperature.VDI6007WithWindow", "eqAirTemp", "hConvWallOut", "hConWallOut") mofile.rename_component_argument( "Buildings.ThermalZones.ReducedOrder.EquivalentAirTemperature.VDI6007WithWindow", "eqAirTemp", "hConvWinOut", "hConWinOut") mofile.rename_component_argument( "Buildings.ThermalZones.ReducedOrder.EquivalentAirTemperature.VDI6007", "eqAirTempVDI", "hConvWallOut", "hConWallOut") renames = { "hConvExt": "hConExt", "hConvFloor": "hConFloor", "hConvRoof": "hConRoof", "hConvWinOut": "hConWinOut", "hConvWin": "hConWin", "hConvInt": "hConInt", } for from_, to_ in renames.items(): mofile.rename_component_argument( f"Buildings.ThermalZones.ReducedOrder.RC.{thermal_zone_type}", thermal_zone_name, from_, to_) mofile.update_component_modifications( f"Buildings.ThermalZones.ReducedOrder.RC.{thermal_zone_type}", thermal_zone_name, {"use_moisture_balance": "use_moisture_balance"}) mofile.update_component_modifications( f"Buildings.ThermalZones.ReducedOrder.RC.{thermal_zone_type}", thermal_zone_name, {"nPorts": "nPorts"}) mofile.add_connect( 'port_a', f'{thermal_zone_name}.intGainsConv', annotations=[ 'Line(points={{0,100},{96,100},{96,20},{92,20}}, color={191,0,0})' ]) mofile.add_connect( f'{thermal_zone_name}.TAir', 'TAir', annotations=[ 'Line(points={{93,32},{98,32},{98,48},{110,48}}, color={0,0,127})' ]) mofile.add_connect( f'{thermal_zone_name}.TRad', 'TRad', annotations=[ 'Line(points={{93,28},{98,28},{98,-20},{110,-20}}, color={0,0,127})' ]) mofile.add_connect( f'{thermal_zone_name}.QLat_flow', 'perLatLoa.y', annotations=[ 'Line(points={{43,4},{40,4},{40,-28},{-40,-28},{-40,-50},{-59,-50}}, color={0, 0,127})' ]) mofile.add_connect( f'{thermal_zone_name}.intGainsRad', 'port_b', annotations=[ 'Line(points={{92, 24}, {98, 24}, {98, -100}, {40, -100}}, color={191, 0, 0})' ]) mofile.insert_equation_for_loop( index_identifier="i", expression_raw="1:nPorts", loop_body_raw_list=[ "connect(ports[i], thermalZoneFourElements.ports[i])", "\tannotation (Line(", "\tpoints={{-18,-102},{-18,-84},{83,-84},{83,-1.95}},", "\tcolor={0,127,255},", "\tsmooth=Smooth.None));", ], ) # change the name of the modelica model to remove the building id, update in package too! original_model_name = mofile.get_name() new_model_name = original_model_name.split("_")[1] thermal_zone_files.append(new_model_name) package.rename_model(original_model_name, new_model_name) mofile.set_name(new_model_name) # Save as the new filename (without building ID) new_filename = os.path.join( scaffold.loads_path.files_dir, f'{self.building_name}/{os.path.basename(f).split("_")[1]}') mofile.save_as(new_filename) os.remove(f) # Now connect all the thermal zone files into the teaser building # 1. Need to a map of thermal zone names and instances zone_list = [] for index, tz in enumerate(thermal_zone_files): # take /a/file/Meeting.mo -> zone_map["Meeting"] = "meeting" tz_process = os.path.splitext(os.path.basename(tz))[0] zone_list.append({ "index": index, "model_name": tz_process, "instance_name": tz_process.lower(), # process where this will be stored in python otherwise too many {{}}, yes ridiculous. # This needs to result in {{a,b},{x,y}} "placement": f"{{{{{-160 + index * 40},-20}},{{{-140 + index * 40},0}}}}" }) # Handle setting nominal load for IT room zone nom_cool_flow = np.array([-10000] * len(zone_list)) for i, dic in enumerate(zone_list): if dic["instance_name"] == "ict": print("setting coo flow") nom_cool_flow[ i - 1] = -50000 # Need to offset for different indexing nom_heat_flow = np.array([10000] * len(zone_list)) building_template_data = { "thermal_zones": zone_list, "nominal_heat_flow": str(repr(nom_heat_flow))[1:-1].replace("[", "{").replace( "]", "}").split("rray(", 1)[-1], "nominal_cool_flow": str(repr(nom_cool_flow))[1:-1].replace("[", "{").replace( "]", "}").split("rray(", 1)[-1], "load_resources_path": b_modelica_path.resources_relative_dir, "mos_weather": { "mos_weather_filename": mos_weather_filename, "filename": os.path.basename(mos_weather_filename), "path": os.path.dirname(mos_weather_filename), }, "nominal_values": { # Adding 273.15 to convert from C to K (for absolute temps, not relative temps) "chw_supply_temp": self.system_parameters.get_param_by_building_id( self.building_id, "load_model_parameters.rc.temp_chw_supply") + 273.15, "chw_return_temp": self.system_parameters.get_param_by_building_id( self.building_id, "load_model_parameters.rc.temp_chw_return") + 273.15, "hhw_supply_temp": self.system_parameters.get_param_by_building_id( self.building_id, "load_model_parameters.rc.temp_hw_supply") + 273.15, "hhw_return_temp": self.system_parameters.get_param_by_building_id( self.building_id, "load_model_parameters.rc.temp_hw_return") + 273.15, "temp_setpoint_heating": self.system_parameters.get_param_by_building_id( self.building_id, "load_model_parameters.rc.temp_setpoint_heating") + 273.15, "temp_setpoint_cooling": self.system_parameters.get_param_by_building_id( self.building_id, "load_model_parameters.rc.temp_setpoint_cooling") + 273.15 } } # merge ets template values from load_base.py into the building nominal values # If there is no ets defined in sys-param file, use the building template data alone try: nominal_values = { **building_template_data['nominal_values'], **self.ets_template_data } combined_template_data = { **building_template_data, **nominal_values } except AttributeError: combined_template_data = building_template_data self.run_template(teaser_building, os.path.join(b_modelica_path.files_dir, "building.mo"), project_name=scaffold.project_name, model_name=self.building_name, data=combined_template_data) self.run_template(teaser_coupling, os.path.join( os.path.join(b_modelica_path.files_dir, "coupling.mo")), project_name=scaffold.project_name, model_name=self.building_name, data=combined_template_data) full_model_name = os.path.join(scaffold.project_name, scaffold.loads_path.files_relative_dir, self.building_name, "coupling").replace(os.path.sep, '.') self.run_template( run_coupling_template, os.path.join( os.path.join(b_modelica_path.scripts_dir, "RunTeaserCouplingBuilding.mos")), full_model_name=full_model_name, model_name="coupling", ) # copy over the required mo files and add the other models to the package order mo_files = self.copy_required_mo_files( b_modelica_path.files_dir, within=f'{scaffold.project_name}.Loads') for f in mo_files: package.add_model(os.path.splitext(os.path.basename(f))[0]) package.add_model('building') package.add_model('coupling') # save the updated package.mo and package.order in the Loads.B{} folder new_package = PackageParser.new_from_template( package.path, self.building_name, package.order, within=f"{scaffold.project_name}.Loads") new_package.save() # AA added this 9/24 if os.path.exists( building_template_data["mos_weather"]["mos_weather_filename"]): shutil.copy( building_template_data["mos_weather"]["mos_weather_filename"], os.path.join( b_modelica_path.resources_dir, building_template_data["mos_weather"]["filename"])) else: raise Exception( f"Missing MOS weather file for Spawn: {building_template_data['mos_weather']['mos_weather_filename']}" ) # end of what AA added 9/24 # remaining clean up tasks across the entire exported project if not keep_original_models: shutil.rmtree( os.path.join(scaffold.loads_path.files_dir, "Project")) # now create the Loads level package and package.order. if not os.path.exists( os.path.join(scaffold.loads_path.files_dir, 'package.mo')): load_package = PackageParser.new_from_template( scaffold.loads_path.files_dir, "Loads", [self.building_name], within=f"{scaffold.project_name}") load_package.save() else: load_package = PackageParser( os.path.join(scaffold.loads_path.files_dir)) load_package.add_model(self.building_name) load_package.save() # now create the Package level package. This really needs to happen at the GeoJSON to modelica stage, but # do it here for now to aid in testing. package = PackageParser(scaffold.project_path) if 'Loads' not in package.order: package.add_model('Loads') package.save()
def to_modelica(self, scaffold): """ Create timeSeries models based on the data in the buildings and geojsons :param scaffold: Scaffold object, Scaffold of the entire directory of the project. """ if self.chp_installed: template_data = { "nominal_values": { "heat_flow_nominal": self.system_parameters.get_param( "$.district_system.default.central_heating_plant_parameters.heat_flow_nominal" ), "mass_hhw_flow_nominal": self.system_parameters.get_param( "$.district_system.default.central_heating_plant_parameters.mass_hhw_flow_nominal" ), "boiler_water_flow_minimum": self.system_parameters.get_param( "$.district_system.default.central_heating_plant_parameters.boiler_water_flow_minimum" ), "pressure_drop_hhw_nominal": self.system_parameters.get_param( "$.district_system.default.central_heating_plant_parameters.pressure_drop_hhw_nominal" ), "pressure_drop_setpoint": self.system_parameters.get_param( "$.district_system.default.central_heating_plant_parameters.pressure_drop_setpoint" ), "temp_setpoint_hhw": self.system_parameters.get_param( "$.district_system.default.central_heating_plant_parameters.temp_setpoint_hhw" ) + 273.15, "pressure_drop_hhw_valve_nominal": self.system_parameters.get_param( "$.district_system.default.central_heating_plant_parameters.pressure_drop_hhw_valve_nominal" ), }, "signals": { "thermal_following": str( self.system_parameters.get_param( "$.district_system.default.central_heating_plant_parameters.chp_thermal_following" )). lower( ), # Booleans in Python start with a capital letter. Modelica wants it lowercase, hence this. }, } plant_template = self.template_env.get_template( "HeatingPlantWithCHP.mot") self.run_template( template=plant_template, save_file_name=Path(scaffold.plants_path.files_dir) / "CentralHeatingPlant.mo", project_name=scaffold.project_name, data=template_data) self.copy_required_mo_files(dest_folder=scaffold.plants_path.files_dir, within=f'{scaffold.project_name}.Plants') package = PackageParser(scaffold.project_path) if 'Plants' not in package.order: package.add_model('Plants') package.save() package_models = ['CentralHeatingPlant' ] + [Path(mo).stem for mo in self.required_mo_files] plants_package = PackageParser(scaffold.plants_path.files_dir) if plants_package.order_data is None: plants_package = PackageParser.new_from_template( path=scaffold.plants_path.files_dir, name="Plants", order=package_models, within=scaffold.project_name) else: for model_name in package_models: plants_package.add_model(model_name) plants_package.save()
def to_modelica(self, scaffold): """ Create timeSeries models based on the data in the buildings and geojsons :param scaffold: Scaffold object, Scaffold of the entire directory of the project. """ mos_wet_bulb_filename = self.system_parameters.get_param( "$.district_system.default.central_cooling_plant_parameters.mos_wet_bulb_filename" ) if mos_wet_bulb_filename is None: raise FileNotFoundError( 'Cooling plant\'s mos_wet_bulb_filename was not provided') template_data = { "nominal_values": { "delta_temp": self.system_parameters.get_param( "$.district_system.default.central_cooling_plant_parameters.cooling_tower_water_temperature_difference_nominal" ), "fan_power": self.system_parameters.get_param( "$.district_system.default.central_cooling_plant_parameters.cooling_tower_fan_power_nominal" ), "chilled_water_pump_pressure_head": self.system_parameters.get_param( "$.district_system.default.central_cooling_plant_parameters.chw_pump_head" ), "condenser_water_pump_pressure_head": self.system_parameters.get_param( "$.district_system.default.central_cooling_plant_parameters.cw_pump_head" ), "heat_flow_nominal": self.system_parameters.get_param( "$.district_system.default.central_cooling_plant_parameters.heat_flow_nominal" ), "mass_chw_flow_nominal": self.system_parameters.get_param( "$.district_system.default.central_cooling_plant_parameters.mass_chw_flow_nominal" ), "mass_cw_flow_nominal": self.system_parameters.get_param( "$.district_system.default.central_cooling_plant_parameters.mass_cw_flow_nominal" ), "chiller_water_flow_minimum": self.system_parameters.get_param( "$.district_system.default.central_cooling_plant_parameters.chiller_water_flow_minimum" ), "pressure_drop_chw_nominal": self.system_parameters.get_param( "$.district_system.default.central_cooling_plant_parameters.pressure_drop_chw_nominal" ), "pressure_drop_cw_nominal": self.system_parameters.get_param( "$.district_system.default.central_cooling_plant_parameters.pressure_drop_cw_nominal" ), "pressure_drop_setpoint": self.system_parameters.get_param( "$.district_system.default.central_cooling_plant_parameters.pressure_drop_setpoint" ), "temp_setpoint_chw": self.system_parameters.get_param( "$.district_system.default.central_cooling_plant_parameters.temp_setpoint_chw" ), "pressure_drop_chw_valve_nominal": self.system_parameters.get_param( "$.district_system.default.central_cooling_plant_parameters.pressure_drop_chw_valve_nominal" ), "pressure_drop_cw_pum_nominal": self.system_parameters.get_param( "$.district_system.default.central_cooling_plant_parameters.pressure_drop_cw_pum_nominal" ), "temp_air_wb_nominal": self.system_parameters.get_param( "$.district_system.default.central_cooling_plant_parameters.temp_air_wb_nominal" ), "temp_cw_in_nominal": self.system_parameters.get_param( "$.district_system.default.central_cooling_plant_parameters.temp_cw_in_nominal" ), "delta_temp_approach": self.system_parameters.get_param( "$.district_system.default.central_cooling_plant_parameters.delta_temp_approach" ), "ratio_water_air_nominal": self.system_parameters.get_param( "$.district_system.default.central_cooling_plant_parameters.ratio_water_air_nominal" ), }, "wet_bulb_calc": { "mos_wet_bulb_filename": mos_wet_bulb_filename, "filename": Path(mos_wet_bulb_filename).name, "path": Path(mos_wet_bulb_filename).parent, "modelica_path": self.modelica_path(mos_wet_bulb_filename), }, } plant_template = self.template_env.get_template( "CentralCoolingPlant.mot") self.run_template(plant_template, os.path.join(scaffold.plants_path.files_dir, "CentralCoolingPlant.mo"), project_name=scaffold.project_name, data=template_data) self.copy_required_mo_files(dest_folder=scaffold.plants_path.files_dir, within=f'{scaffold.project_name}.Plants') package = PackageParser(scaffold.project_path) if 'Plants' not in package.order: package.add_model('Plants') package.save() package_models = ['CentralCoolingPlant' ] + [Path(mo).stem for mo in self.required_mo_files] plants_package = PackageParser(scaffold.plants_path.files_dir) if plants_package.order_data is None: plants_package = PackageParser.new_from_template( path=scaffold.plants_path.files_dir, name="Plants", order=package_models, within=scaffold.project_name) else: for model_name in package_models: plants_package.add_model(model_name) plants_package.save()
def to_modelica(self): """Generate modelica files for the models as well as the modelica file for the entire district system. """ # scaffold the project self._scaffold.create() self.district_model_filepath = Path( self._scaffold.districts_path.files_dir ) / 'DistrictEnergySystem.mo' # create the root package root_package = PackageParser.new_from_template( self._scaffold.project_path, self._scaffold.project_name, order=[]) root_package.save() # generate model modelica files for model in self._coupling_graph.models: model.to_modelica(self._scaffold) # construct graph of visual components diagram = Diagram(self._coupling_graph) district_template_params = { "district_within_path": '.'.join([self._scaffold.project_name, 'Districts']), "diagram": diagram, "couplings": [], "models": [] } common_template_params = { 'globals': { 'medium_w': 'MediumW', 'delChiWatTemBui': 'delChiWatTemBui', 'delChiWatTemDis': 'delChiWatTemDis', 'delHeaWatTemBui': 'delHeaWatTemBui', 'delHeaWatTemDis': 'delHeaWatTemDis', }, 'graph': self._coupling_graph, 'sys_params': { 'district_system': self.system_parameters.get_param('$.district_system') } } # render each coupling for coupling in self._coupling_graph.couplings: template_context = { 'diagram': diagram.to_dict(coupling.id, is_coupling=True), } template_context.update(**common_template_params) coupling_load = coupling.get_load() if coupling_load is not None: building_sys_params = self.system_parameters.get_param_by_building_id( coupling_load.building_id, '$') template_context['sys_params'][ 'building'] = building_sys_params templated_result = coupling.render_templates(template_context) district_template_params['couplings'].append({ 'id': coupling.id, 'component_definitions': templated_result['component_definitions'], 'connect_statements': templated_result['connect_statements'], 'coupling_definitions_template_path': templated_result['component_definitions_template_path'], 'connect_statements_template_path': templated_result['connect_statements_template_path'], }) # render each model instance for model in self._coupling_graph.models: template_params = { 'model': model.to_dict(self._scaffold), 'couplings': self._coupling_graph.couplings_by_type(model.id), 'diagram': diagram.to_dict(model.id, is_coupling=False), } template_params.update(**common_template_params) if issubclass(type(model), LoadBase): building_sys_params = self.system_parameters.get_param_by_building_id( model.building_id, '$') template_params['sys_params']['building'] = building_sys_params templated_instance, instance_template_path = model.render_instance( template_params) district_template_params['models'].append({ 'id': model.id, 'instance_template_path': instance_template_path, 'instance': templated_instance }) # render the full district file final_result = render_template('DistrictEnergySystem.mot', district_template_params) with open(self.district_model_filepath, 'w') as f: f.write(final_result) districts_package = PackageParser.new_from_template( self._scaffold.districts_path.files_dir, "Districts", ['DistrictEnergySystem'], within=f"{self._scaffold.project_name}") districts_package.save() root_package = PackageParser(self._scaffold.project_path) root_package.add_model('Districts') root_package.save()