-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add modelica project class with save_as method
- Loading branch information
Showing
28 changed files
with
64,527 additions
and
1 deletion.
There are no files selected for viewing
202 changes: 202 additions & 0 deletions
202
geojson_modelica_translator/modelica/modelica_project.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,202 @@ | ||
# :copyright (c) URBANopt, Alliance for Sustainable Energy, LLC, and other contributors. | ||
# See also https://github.com/urbanopt/geojson-modelica-translator/blob/develop/LICENSE.md | ||
import logging | ||
import os | ||
import time | ||
from pathlib import Path | ||
|
||
from modelica_builder.model import Model | ||
|
||
from geojson_modelica_translator.modelica.package_parser import PackageParser | ||
|
||
_log = logging.getLogger(__name__) | ||
|
||
|
||
class ModelicaFileObject: | ||
"""Class for storing a Modelica file object. Example is a '.mo' file that is | ||
lazily parsed into the AST using Modelica-Builder or a '.mos' file that reads in the | ||
header's values using the ModelicaMOS class.""" | ||
|
||
# enumerations for different file types | ||
FILE_TYPE_PACKAGE = 0 | ||
FILE_TYPE_MODEL = 1 | ||
FILE_TYPE_SCRIPT = 2 | ||
FILE_TYPE_TEXT = 3 | ||
|
||
def __init__(self, file_path): | ||
self.file_path = Path(file_path) | ||
self.object = None | ||
self.file_contents = None | ||
self.file_type = None | ||
|
||
# depending on the file type, parse the object when it is first accessed. | ||
if self.file_path.is_dir(): | ||
self.file_contents = None | ||
elif self.file_path.name == 'package.mo': | ||
# this parses both the .mo and .order files, so we | ||
# need to skip over the .order file. The PackageParser is | ||
# a directory, not the file itself. | ||
self.object = PackageParser(self.file_path.parent) | ||
self.file_type = self.FILE_TYPE_PACKAGE | ||
elif self.file_path.name == 'package.order': | ||
pass | ||
elif self.file_path.suffix == '.mo': | ||
self.file_type = self.FILE_TYPE_MODEL | ||
self._parse_mo_file() | ||
elif self.file_path.suffix == '.mos': | ||
self.file_type = self.FILE_TYPE_SCRIPT | ||
self.file_contents = self.file_path.read_text() | ||
elif self.file_path.suffix == '.txt': | ||
self.file_type = self.FILE_TYPE_TEXT | ||
self.file_contents = self.file_path.read_text() | ||
else: | ||
# not sure what to do with this | ||
_log.warning(f"Unknown file type {self.file_path}") | ||
|
||
def exists(self): | ||
self.file_path.exists() | ||
|
||
def _parse_mo_file(self): | ||
"""method to parse the mo file into a Modelica AST""" | ||
# time the loading of the file | ||
start = time.time() | ||
self.object = Model(self.file_path) | ||
end = time.time() | ||
|
||
print(f"Took {end - start} seconds to load {self.file_path.name}") | ||
|
||
@property | ||
def name(self): | ||
"""method to get the name of the file""" | ||
return self.file_path.name | ||
|
||
|
||
class ModelicaProject: | ||
"""Class for storing all the files in a Modelica project. This class should organically | ||
grow as more requirements are needed. | ||
The current functionality includes: | ||
* Load in a package.mo file and store all the related files in memory space.""" | ||
|
||
def __init__(self, package_file): | ||
self.root_directory = Path(package_file).parent | ||
self.file_types = ['.mo', '.txt', '.mos', '.order'] | ||
self.file_data = {} | ||
|
||
self._load_data() | ||
|
||
def _load_data(self) -> None: | ||
"""method to load all of the files into a data structure for processing""" | ||
# walk the tree and add in all the files | ||
for file_path in self.root_directory.rglob('*'): | ||
if file_path.suffix in self.file_types and file_path.is_file(): | ||
# only store the relative path that is in the package | ||
rel_path = file_path.relative_to(self.root_directory) | ||
self.file_data[str(rel_path)] = ModelicaFileObject(file_path) | ||
elif file_path.is_dir(): | ||
# this is a directory, just add in | ||
# a temp object for now to keep the path known | ||
rel_path = file_path.relative_to(self.root_directory) | ||
self.file_data[str(rel_path)] = ModelicaFileObject(file_path) | ||
else: | ||
print(f"Unknown file {file_path}") | ||
|
||
# now sort the file_data by the keys | ||
self.file_data = {key: self.file_data[key] for key in sorted(self.file_data)} | ||
|
||
# validate the data, extend as needed. | ||
if self.file_data.get('package.mo', None) is None: | ||
raise Exception('ModelicaPackage does not contain a /package.mo file') | ||
|
||
self.pretty_print_tree() | ||
|
||
def pretty_print_tree(self) -> None: | ||
"""Pretty print all the items in the directory structure | ||
""" | ||
# Print a couple lines, just because | ||
print() | ||
for key, obj in self.file_data.items(): | ||
# find how many indents we need based on the number of path separators | ||
indent = key.count(os.path.sep) | ||
print(" " * indent + f"{os.path.sep} {key.replace(os.path.sep, f' {os.path.sep} ')}") | ||
|
||
def save_as(self, new_package_name: str, output_dir: Path = None) -> None: | ||
"""method to save the ModelicaProject to a new location which | ||
requires a new path name and updating all of the within statement | ||
Args: | ||
new_package_name (str): Name of the new package, which will also be the directory name | ||
output_dir (Path, optional): Where to persist the new directory and package. Defaults to existing. | ||
""" | ||
if output_dir is None: | ||
output_dir = self.root_directory | ||
output_dir = output_dir / new_package_name | ||
|
||
# in the root package, rename the modelica package (there is not within statement) | ||
self.file_data['package.mo'].object.rename_package(new_package_name) | ||
|
||
# go through each of the package.mo files first and update the within statements | ||
for path, file in self.file_data.items(): | ||
if path == 'package.mo': | ||
# this file is handled above, so just skip | ||
continue | ||
|
||
if file.file_type == ModelicaFileObject.FILE_TYPE_PACKAGE: | ||
# this is a package, so update the within statement | ||
file.object.update_within_statement(new_package_name, element_index=0) | ||
|
||
elif file.file_type == ModelicaFileObject.FILE_TYPE_MODEL: | ||
new_within_statement = f"{new_package_name}.{str(Path(path).parent).replace(os.path.sep, '.')}" | ||
file.object.set_within_statement(new_within_statement) | ||
|
||
# there are a few very specific methods that exist when reading in weather files or | ||
# load files. I am not sure how to abstract out this logic at the moment. | ||
|
||
# IDF names - find the existing value and replace if found | ||
idf_name = file.object.get_parameter_value('String', 'idfName') | ||
if idf_name: | ||
# replace the previous model name with the new name | ||
idf_name = idf_name.replace(self.root_directory.name, new_package_name) | ||
file.object.update_parameter('String', 'idfName', idf_name) | ||
|
||
epw_name = file.object.get_parameter_value('String', 'epwName') | ||
if epw_name: | ||
# replace the previous model name with the new name | ||
epw_name = epw_name.replace(self.root_directory.name, new_package_name) | ||
file.object.update_parameter('String', 'epwName', epw_name) | ||
|
||
weather_filename = file.object.get_parameter_value('String', 'weaName') | ||
if weather_filename: | ||
# replace the previous model name with the new name | ||
weather_filename = weather_filename.replace(self.root_directory.name, new_package_name) | ||
file.object.update_parameter('String', 'weaName', weather_filename) | ||
|
||
filename = file.object.get_parameter_value('String', 'filNam') | ||
if filename: | ||
# replace the previous model name with the new name | ||
filename = filename.replace(self.root_directory.name, new_package_name) | ||
file.object.update_parameter('String', 'filNam', filename) | ||
|
||
# now persist all the files to the new location | ||
if not output_dir.exists(): | ||
output_dir.mkdir(parents=True, exist_ok=True) | ||
|
||
for path, file in self.file_data.items(): | ||
# create the new path | ||
new_path = output_dir / path | ||
if file.file_path.is_dir(): | ||
# this is a directory, so just create it | ||
new_path.mkdir(parents=True, exist_ok=True) | ||
|
||
elif file.file_type == ModelicaFileObject.FILE_TYPE_PACKAGE: | ||
file.object.save_as(new_path.parent) | ||
elif file.file_type == ModelicaFileObject.FILE_TYPE_MODEL: | ||
file.object.save_as(new_path) | ||
elif file.file_type == ModelicaFileObject.FILE_TYPE_SCRIPT: | ||
# just save the file as it is text (mos-based file) | ||
open(new_path, 'w').write(file.file_contents) | ||
elif file.file_type == ModelicaFileObject.FILE_TYPE_TEXT: | ||
# just save the file as it is text (all other files) | ||
open(new_path, 'w').write(file.file_contents) | ||
else: | ||
_log.warn("Unknown file type, not saving") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
173 changes: 173 additions & 0 deletions
173
tests/modelica/data/packages/teaser_single/Districts/DistrictEnergySystem.mo
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
within teaser_single.Districts; | ||
model DistrictEnergySystem | ||
extends Modelica.Icons.Example; | ||
// District Parameters | ||
package MediumW=Buildings.Media.Water | ||
"Source side medium"; | ||
package MediumA=Buildings.Media.Air | ||
"Load side medium"; | ||
|
||
// TODO: dehardcode these | ||
parameter Modelica.Units.SI.TemperatureDifference delChiWatTemDis(displayUnit="degC")=7; | ||
parameter Modelica.Units.SI.TemperatureDifference delChiWatTemBui(displayUnit="degC")=5; | ||
parameter Modelica.Units.SI.TemperatureDifference delHeaWatTemDis(displayUnit="degC")=12; | ||
parameter Modelica.Units.SI.TemperatureDifference delHeaWatTemBui(displayUnit="degC")=5; | ||
parameter Integer numberofchillers = 2; | ||
|
||
// Models | ||
|
||
// | ||
// Begin Model Instance for TeaserLoad_be9c7414 | ||
// Source template: /model_connectors/load_connectors/templates/Teaser_Instance.mopt | ||
// | ||
Loads.B5a6b99ec37f4de7f94020090.building TeaserLoad_be9c7414( | ||
nPorts_aHeaWat=1, | ||
nPorts_bHeaWat=1, | ||
nPorts_aChiWat=1, | ||
nPorts_bChiWat=1) | ||
"Building with thermal loads as TEASER zones" | ||
annotation (Placement(transformation(extent={{50.0,30.0},{70.0,50.0}}))); | ||
// | ||
// End Model Instance for TeaserLoad_be9c7414 | ||
// | ||
|
||
|
||
|
||
// | ||
// Begin Model Instance for etsHotWatStub_317cd3ed | ||
// Source template: /model_connectors/energy_transfer_systems/templates/EtsHotWaterStub_Instance.mopt | ||
// | ||
// TODO: move these components into a single component | ||
Buildings.Fluid.Sources.Boundary_pT supHeaWat_etsHotWatStub_317cd3ed( | ||
redeclare package Medium=MediumW, | ||
use_T_in=true, | ||
nPorts=1) | ||
"Heating water supply" | ||
annotation (Placement(transformation(extent={{10.0,-10.0},{30.0,10.0}}))); | ||
Buildings.Fluid.Sources.Boundary_pT sinHeaWat_etsHotWatStub_317cd3ed( | ||
redeclare package Medium=MediumW, | ||
nPorts=1) | ||
"Heating water sink" | ||
annotation (Placement(transformation(extent={{50.0,-10.0},{70.0,10.0}}))); | ||
// | ||
// End Model Instance for etsHotWatStub_317cd3ed | ||
// | ||
|
||
|
||
|
||
// | ||
// Begin Model Instance for etsColWatStub_f01390c3 | ||
// Source template: /model_connectors/energy_transfer_systems/templates/EtsColdWaterStub_Instance.mopt | ||
// | ||
// TODO: move these components into a single component | ||
Buildings.Fluid.Sources.Boundary_pT supChiWat_etsColWatStub_f01390c3( | ||
redeclare package Medium=MediumW, | ||
use_T_in=true, | ||
nPorts=1) | ||
"Chilled water supply" | ||
annotation (Placement(transformation(extent={{-70.0,-50.0},{-50.0,-30.0}}))); | ||
Buildings.Fluid.Sources.Boundary_pT sinChiWat_etsColWatStub_f01390c3( | ||
redeclare package Medium=MediumW, | ||
nPorts=1) | ||
"Chilled water sink" | ||
annotation (Placement(transformation(extent={{-30.0,-50.0},{-10.0,-30.0}}))); | ||
// | ||
// End Model Instance for etsColWatStub_f01390c3 | ||
// | ||
|
||
|
||
|
||
|
||
// Model dependencies | ||
|
||
// | ||
// Begin Component Definitions for a58ec2e9 | ||
// Source template: /model_connectors/couplings/templates/Teaser_EtsHotWaterStub/ComponentDefinitions.mopt | ||
// | ||
Modelica.Blocks.Sources.RealExpression THeaWatSup_a58ec2e9( | ||
y=max( | ||
TeaserLoad_be9c7414.terUni.T_aHeaWat_nominal)) | ||
"Heating water supply temperature" | ||
annotation (Placement(transformation(extent={{-70.0,-10.0},{-50.0,10.0}}))); | ||
|
||
// | ||
// End Component Definitions for a58ec2e9 | ||
// | ||
|
||
|
||
|
||
// | ||
// Begin Component Definitions for 8e55a31a | ||
// Source template: /model_connectors/couplings/templates/Teaser_EtsColdWaterStub/ComponentDefinitions.mopt | ||
// | ||
Modelica.Blocks.Sources.RealExpression TChiWatSup_8e55a31a( | ||
y=min( | ||
TeaserLoad_be9c7414.terUni.T_aChiWat_nominal)) | ||
"Chilled water supply temperature" | ||
annotation (Placement(transformation(extent={{-30.0,-10.0},{-10.0,10.0}}))); | ||
|
||
// | ||
// End Component Definitions for 8e55a31a | ||
// | ||
|
||
|
||
|
||
equation | ||
// Connections | ||
|
||
// | ||
// Begin Connect Statements for a58ec2e9 | ||
// Source template: /model_connectors/couplings/templates/Teaser_EtsHotWaterStub/ConnectStatements.mopt | ||
// | ||
|
||
// teaser, ets hot water stub connections | ||
connect(supHeaWat_etsHotWatStub_317cd3ed.T_in,THeaWatSup_a58ec2e9.y) | ||
annotation (Line(points={{28.591729349738088,19.769083940326155},{8.591729349738088,19.769083940326155},{-11.408270650261912,19.769083940326155},{-31.408270650261912,19.769083940326155},{-31.408270650261912,-0.2309160596738451},{-51.40827065026191,-0.2309160596738451}},color={0,0,127})); | ||
connect(supHeaWat_etsHotWatStub_317cd3ed.ports[1],TeaserLoad_be9c7414.ports_aHeaWat[1]) | ||
annotation (Line(points={{20.023987755774314,26.434106474132236},{20.023987755774314,46.434106474132236},{40.0239877557743,46.434106474132236},{60.0239877557743,46.434106474132236}},color={0,0,127})); | ||
connect(sinHeaWat_etsHotWatStub_317cd3ed.ports[1],TeaserLoad_be9c7414.ports_bHeaWat[1]) | ||
annotation (Line(points={{66.67138553607464,20.871302462969837},{66.67138553607464,40.87130246296984}},color={0,0,127})); | ||
|
||
// | ||
// End Connect Statements for a58ec2e9 | ||
// | ||
|
||
|
||
|
||
// | ||
// Begin Connect Statements for 8e55a31a | ||
// Source template: /model_connectors/couplings/templates/Teaser_EtsColdWaterStub/ConnectStatements.mopt | ||
// | ||
|
||
// teaser, ets cold water stub connections | ||
connect(TChiWatSup_8e55a31a.y,supChiWat_etsColWatStub_f01390c3.T_in) | ||
annotation (Line(points={{-13.044910860110875,-25.907712156325545},{-33.044910860110875,-25.907712156325545},{-33.044910860110875,-45.907712156325545},{-53.044910860110875,-45.907712156325545}},color={0,0,127})); | ||
connect(supChiWat_etsColWatStub_f01390c3.ports[1],TeaserLoad_be9c7414.ports_aChiWat[1]) | ||
annotation (Line(points={{-64.01769206593764,-11.316852504956202},{-44.01769206593764,-11.316852504956202},{-44.01769206593764,8.683147495043798},{-44.01769206593764,28.683147495043798},{-44.01769206593764,48.6831474950438},{-24.017692065937638,48.6831474950438},{-4.017692065937638,48.6831474950438},{15.982307934062362,48.6831474950438},{35.98230793406236,48.6831474950438},{55.98230793406236,48.6831474950438}},color={0,0,127})); | ||
connect(sinChiWat_etsColWatStub_f01390c3.ports[1],TeaserLoad_be9c7414.ports_bChiWat[1]) | ||
annotation (Line(points={{-22.217300978711407,-12.177140029189147},{-2.217300978711407,-12.177140029189147},{-2.217300978711407,7.822859970810853},{-2.217300978711407,27.822859970810853},{-2.217300978711407,47.82285997081085},{17.782699021288593,47.82285997081085},{37.78269902128859,47.82285997081085},{57.78269902128858,47.82285997081085}},color={0,0,127})); | ||
|
||
// | ||
// End Connect Statements for 8e55a31a | ||
// | ||
|
||
|
||
|
||
|
||
annotation( | ||
experiment( | ||
StopTime=86400, | ||
Interval=3600, | ||
Tolerance=1e-06), | ||
Diagram( | ||
coordinateSystem( | ||
preserveAspectRatio=false, | ||
extent={{-90.0,-70.0},{90.0,70.0}})), | ||
Documentation( | ||
revisions="<html> | ||
<li> | ||
May 10, 2020: Hagar Elarga<br/> | ||
Updated implementation to handle template needed for GeoJSON to Modelica. | ||
</li> | ||
</html>")); | ||
end DistrictEnergySystem; |
5 changes: 5 additions & 0 deletions
5
tests/modelica/data/packages/teaser_single/Districts/package.mo
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
within teaser_single; | ||
package Districts | ||
extends Modelica.Icons.Package; | ||
|
||
end Districts; |
1 change: 1 addition & 0 deletions
1
tests/modelica/data/packages/teaser_single/Districts/package.order
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
DistrictEnergySystem |
Oops, something went wrong.