Skip to content

Commit

Permalink
Use filNam parameter for TEASER loads and add within parsing to Pac…
Browse files Browse the repository at this point in the history
…kageParser (#574)

* use filNam for teaser loads

* fix types

* remove unused exists method
  • Loading branch information
nllong authored Aug 14, 2023
1 parent a527672 commit 09fbea2
Show file tree
Hide file tree
Showing 17 changed files with 182 additions and 73 deletions.
6 changes: 6 additions & 0 deletions .cspell/custom-dictionary-workspace.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Custom Dictionary Words
autoload
Combi
linecount
mfrt
mofile
Reparse
setpoint
timeseries
timestep
urbanopt
8 changes: 6 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,12 @@ tests/output
tests/*/microgrid_output
tests/output_ets
tests/*/output
tests/modelica/data/packages/*/*.mat
tests/modelica/data/packages/*/*.txt
tests/modelica/data/packages/*/*.c
tests/modelica/data/packages/*/dymosim
tests/management/data/sdk_project_scraps/run/baseline_scenario/system_parameter.json
/geojson_modelica_translator/modelica/buildingslibrary/
geojson_modelica_translator/modelica/buildingslibrary/
not/a/real/**
# TODO: this file shoud not be writtent out, but it is... fix this eventually
/geojson_modelica_translator/modelica/ets_cooling_indirect_templated.mo
geojson_modelica_translator/modelica/ets_cooling_indirect_templated.mo
6 changes: 3 additions & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ repos:
- id: pretty-format-json
args: ['--autofix', '--no-sort-keys']
- repo: https://github.com/pre-commit/mirrors-autopep8
rev: v2.0.1
rev: v2.0.2
hooks:
- id: autopep8
args:
Expand All @@ -37,7 +37,7 @@ repos:
"--ignore=E501,E402,W503,W504,E731"
]
- repo: https://github.com/PyCQA/autoflake
rev: v2.0.0
rev: v2.2.0
hooks:
- id: autoflake
args:
Expand All @@ -49,7 +49,7 @@ repos:
"--ignore-init-module-imports"
]
- repo: https://github.com/pycqa/flake8
rev: 6.0.0
rev: 6.1.0
hooks:
- id: flake8
args: ["--ignore=E501,E402,W503,W504,E731,F401"]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,4 @@ def to_modelica(self, scaffold):
ets_package.save()

def get_modelica_type(self, scaffold):
return f'{scaffold.project_name}.Substations.{self._model_filename}'
return f'Substations.{self._model_filename}'
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,4 @@ def to_modelica(self, scaffold):
ets_package.save()

def get_modelica_type(self, scaffold):
return f'{scaffold.project_name}.Substations.{self._model_filename}'
return f'Substations.{self._model_filename}'
Original file line number Diff line number Diff line change
Expand Up @@ -241,4 +241,4 @@ def post_process(self, scaffold, keep_original_models=False):
package.save()

def get_modelica_type(self, scaffold):
return f'{scaffold.project_name}.Loads.{self.building_name}.building'
return f'Loads.{self.building_name}.building'
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ def to_modelica(self, scaffold, keep_original_models=False):
def fix_gains_file(self, f):
"""Temporary hack to fix the gains files in TEASER. This method does the following:
* makes the dimension of the matrix to 8761,4
* addes in a timestep for t=0
* adds in a timestep for t=0
:param f: string, fully qualified path to file
:return: None
Expand Down Expand Up @@ -171,7 +171,6 @@ def post_process(self, scaffold, keep_original_models=False):
# 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("$.weather")
# 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)
Expand All @@ -196,13 +195,7 @@ def post_process(self, scaffold, keep_original_models=False):
# 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}",
)
)
self.internal_gains_file = f"{scaffold.project_name}/Loads/{b_modelica_path.resources_relative_dir}/{new_file_name}"

# process each of the thermal zones
thermal_zone_files = []
Expand All @@ -228,19 +221,13 @@ def post_process(self, scaffold, keep_original_models=False):
mofile.remove_component_argument("Buildings.BoundaryConditions.SolarIrradiation.DirectTiltedSurface", "HDirTilRoof", "lat")

# 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
)

mofile.update_component_modification(
"Modelica.Blocks.Sources.CombiTimeTable",
"internalGains",
"fileName",
"Modelica.Utilities.Files.loadResource(filNam)"
)

# add heat port convective heat flow.
mofile.insert_component(
Expand Down Expand Up @@ -306,6 +293,13 @@ def post_process(self, scaffold, keep_original_models=False):
string_comment='Number of fluid ports.',
annotations=['connectorSizing=true']
)
# Add a parameter to put the load filename at the top of the model
mofile.add_parameter(
'String', 'filNam',
assigned_value=f'"modelica://{self.internal_gains_file}"',
string_comment='modelica path to the internal gains file used in the model CombiTimeTable'
)

# Set the fraction latent person in the template by simply replacing the value
mofile.insert_component(
'Modelica.Blocks.Sources.RealExpression', 'perLatLoa',
Expand Down Expand Up @@ -634,4 +628,4 @@ def post_process(self, scaffold, keep_original_models=False):
package.save()

def get_modelica_type(self, scaffold):
return f'{scaffold.project_name}.Loads.{self.building_name}.building'
return f'Loads.{self.building_name}.building'
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,6 @@ def post_process(self, scaffold):
def get_modelica_type(self, scaffold):
district_params = self.system_parameters.get_param("district_system")
if 'fifth_generation' not in district_params:
return f'{scaffold.project_name}.Loads.{self.building_name}.TimeSeriesBuilding'
return f'Loads.{self.building_name}.TimeSeriesBuilding'
else:
return f'{scaffold.project_name}.Loads.{self.building_name}.building'
return f'Loads.{self.building_name}.building'
Original file line number Diff line number Diff line change
Expand Up @@ -148,4 +148,4 @@ def post_process(self, scaffold):
pp.save()

def get_modelica_type(self, scaffold):
return f'{scaffold.project_name}.Loads.{self.building_name}.building'
return f'Loads.{self.building_name}.building'
4 changes: 2 additions & 2 deletions geojson_modelica_translator/model_connectors/model_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,8 +165,8 @@ def to_dict(self, scaffold):
output_dict['is_5g_district'] = ''
return output_dict

# TODO: this should be implemented here, not in individual classes
# This method needs to be defined in each of the derived model connectors
# def get_modelica_type(self, scaffold)

# These methods need to be defined in each of the derived model connectors
# This method needs to be defined in each of the derived model connectors
# def to_modelica(self):
Original file line number Diff line number Diff line change
Expand Up @@ -207,4 +207,4 @@ def to_modelica(self, scaffold):
plants_package.save()

def get_modelica_type(self, scaffold):
return f'{scaffold.project_name}.Plants.{self.borefield_name}.Borefield'
return f'Plants.{self.borefield_name}.Borefield'
2 changes: 1 addition & 1 deletion geojson_modelica_translator/model_connectors/plants/chp.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,4 +101,4 @@ def to_modelica(self, scaffold):
plants_package.save()

def get_modelica_type(self, scaffold):
return f'{scaffold.project_name}.Plants.CentralHeatingPlant'
return 'Plants.CentralHeatingPlant'
Original file line number Diff line number Diff line change
Expand Up @@ -140,4 +140,4 @@ def to_modelica(self, scaffold):
plants_package.save()

def get_modelica_type(self, scaffold):
return f'{scaffold.project_name}.Plants.CentralCoolingPlant'
return 'Plants.CentralCoolingPlant'
88 changes: 84 additions & 4 deletions geojson_modelica_translator/modelica/package_parser.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
# :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 copy
import os
import re
from pathlib import Path
from typing import Any, Union
from typing import Any, List, Optional, Union

from jinja2 import Environment, FileSystemLoader

Expand All @@ -26,8 +28,17 @@ class method.
self.path: Union[str, Path, None] = path
self.order_data: Any = None
self.package_data: Any = None
self.package_name: Union[str, None] = None
self.within: Union[list, None] = None

self.load()

# read in the path if it was given and pull out the package name
if self.path is not None:
self.path = Path(self.path)
# The package name is also the name of the directory
self.package_name = self.path.name

self.template_env = Environment(
loader=FileSystemLoader(
searchpath=os.path.join(
Expand All @@ -37,6 +48,26 @@ class method.
)
self.template_env.filters.update(ALL_CUSTOM_FILTERS)

def parse_within_statement(self) -> Optional[List[str]]:
"""Read in the package_data and parse out the within statement. The result will
be returns, but will also set the within attribute.
"""
# use a regex to pull out the string between within and ;
if self.package_data is None:
self.within = None
return None

matches = re.findall(r'within\s(.*?);', self.package_data)
# it is possible that there is no "within" block in the root package file.
if len(matches) == 0 or matches is None:
self.within = None
elif len(matches) == 1:
self.within = matches[0].split(".")
else:
raise ValueError("There are more than on within statements in the package.mo file")

return self.within

@classmethod
def new_from_template(cls, path: Union[str, Path], name: str, order: list[str], within: Union[str, None] = None) -> "PackageParser":
"""Create new package data based on the package.mo template. If within is not specified, then it is
Expand All @@ -59,6 +90,8 @@ def new_from_template(cls, path: Union[str, Path], name: str, order: list[str],

klass.package_data = template.render(within=within, name=name, order=order)
klass.order_data = "\n".join(order)
klass.package_name = name
klass.parse_within_statement()
return klass

def load(self) -> None:
Expand All @@ -74,6 +107,8 @@ def load(self) -> None:
with open(filename, "r") as f:
self.order_data = f.read()

self.parse_within_statement()

def save(self) -> None:
"""Save the updated files to the same location
"""
Expand All @@ -84,6 +119,23 @@ def save(self) -> None:
f.write(self.order_data)
f.write("\n")

def save_as(self, new_path: Union[str, Path]) -> None:
"""Save the package.mo and package.order file to the new path. Be
careful just running this method without also updating all the related
<model>.mo and package.mo files with the new within statement. Look at the
ModelicaProject class for more support.
Args:
new_path (Union[str, Path]): Fully qualified path to save files to.
"""
new_path = Path(new_path)
with open(new_path / "package.mo", "w") as f:
f.write(self.package_data)

with open(new_path / "package.order", "w") as f:
f.write(self.order_data)
f.write("\n")

@property
def order(self) -> list[str]:
"""Return the order of the packages from the package.order file
Expand All @@ -96,14 +148,42 @@ def order(self) -> list[str]:
data.remove("")
return data

def rename_model(self, old_model: str, new_model: str):
def rename_package(self, new_model: str):
"""Rename the model name in the package.mo file.
Args:
new_model (str): new name
"""
if self.package_name is None:
raise ValueError("Package name is not set, verify that it was parsed correctly in the source code")

self.package_data = self.package_data.replace(self.package_name, new_model)
self.package_name = new_model

def rename_model(self, previous_model: str, new_model: str):
"""Rename the model name in the package.order file.
Args:
old_model (str): existing name
new_model (str): new name
"""
self.order_data = self.order_data.replace(old_model, new_model)
self.order_data = self.order_data.replace(previous_model, new_model)

def update_within_statement(self, new_within: str, element_index: Union[int, None] = None):
"""Update the within statement in the package.mo file
Args:
new_within (str): Full string of the new within statement, e.g., a.b.c.d
element_index (int): index of the element to replace, None is the entire string
"""
# new within
new_within_list = copy.deepcopy(self.within)
if element_index is not None:
new_within_list[element_index] = new_within # type: ignore
else:
new_within_list = new_within.split(".")

self.package_data = self.package_data.replace(f"within {'.'.join(self.within)};", f"within {'.'.join(new_within_list)};") # type: ignore
self.within = new_within_list

def add_model(self, new_model_name: str, insert_at: int = -1) -> None:
"""Insert a new model into the package. Note that the order_data is stored as a string right now,
Expand Down
Loading

0 comments on commit 09fbea2

Please sign in to comment.