Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue500 openmodelica simulator #525

Merged
merged 44 commits into from
Aug 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
127961f
Started adding simulator for OpenModelica
mwetter Dec 22, 2022
50c0456
Merge branch 'master' into issue500_openmodelicaSimulator
mwetter Jan 18, 2023
98e4d48
Further worked on openmodelica implementation
mwetter Jan 27, 2023
81fadfb
Merge branch 'master' into issue500_openmodelicaSimulator
mwetter Mar 31, 2023
6bc87b2
Further edits for OpenModelica integration
mwetter Apr 1, 2023
842f2b8
Updated url
mwetter Apr 1, 2023
5498cf2
Further integrated omc simulation
mwetter Apr 3, 2023
56f20b8
Merge branch 'master' into issue500_openmodelicaSimulator
mwetter Apr 5, 2023
205bcb8
Further worked on OpenModelica simulator
mwetter Apr 5, 2023
51856b4
Further worked on OpenModelica integration
mwetter Apr 5, 2023
5fd9c14
Corrected result file name in delete function
mwetter Apr 12, 2023
53d9ca0
Integrated OpenModelica simulator
mwetter Apr 13, 2023
c3e02af
Added support for models with modifiers, and set start/end time
mwetter May 23, 2023
fd0ecb5
merged master
JayHuLBL Jul 27, 2023
82a11ab
reverted the change caused by merging master
JayHuLBL Jul 27, 2023
c42869a
Merge branch 'master' into issue500_openmodelicaSimulator
mwetter Aug 9, 2023
17682d7
Corrected bugs for OpenModelica simulator
mwetter Aug 9, 2023
cd6375c
Corrected output directory
mwetter Aug 9, 2023
a7e68ff
Corrected access to dictionary element
mwetter Aug 10, 2023
2287010
Corrected test of OpenModelica regression tests
mwetter Aug 12, 2023
65bfef9
Merge branch 'master' into issue500_openmodelicaSimulator
mwetter Aug 14, 2023
bad8172
Corrected test for timeout
mwetter Aug 14, 2023
8898284
Corrected test for timeout
mwetter Aug 14, 2023
45e9c6a
Enabled run
mwetter Aug 14, 2023
690654c
Added removal of output files in tests
mwetter Aug 14, 2023
534bbb6
Added deletion of html dir
mwetter Aug 14, 2023
8b10df6
Removed temporary files
mwetter Aug 15, 2023
4af468e
Formatted files
mwetter Aug 15, 2023
1b1d5ce
Updated copyright year
mwetter Aug 16, 2023
665ff00
Updated documentation
mwetter Aug 16, 2023
c153eee
Merge branch 'master' into issue500_openmodelicaSimulator
mwetter Aug 16, 2023
d2b982c
Changed output file name
mwetter Aug 16, 2023
0050eea
Added return value
mwetter Aug 16, 2023
109fd1d
Updated filter and added tests
mwetter Aug 16, 2023
1f6d4b7
Corrected bug that lead to wrong stopTime in regression tests of Opti…
mwetter Aug 18, 2023
502e4b0
Improved error message
mwetter Aug 20, 2023
101ba76
Added model modifier
mwetter Aug 20, 2023
03f54d8
Run pep8
mwetter Aug 20, 2023
585e9fe
Corrected result filtering
mwetter Aug 23, 2023
bdb6bc9
Set filter for simulator class
mwetter Aug 23, 2023
0d656e0
Formatted file
mwetter Aug 23, 2023
8fff28f
Corrected filter for OpenModelica simulation
mwetter Aug 24, 2023
d672d6b
Reverted translateModel as is used on master
mwetter Aug 24, 2023
0380a67
Updated change log
mwetter Aug 25, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,16 @@ unittest_io_postprocess:
unittest_simulate_Dymola:
python3 buildingspy/tests/test_simulate_Dymola.py

unittest_simulate_OpenModelica:
python3 buildingspy/tests/test_simulate_OpenModelica.py

singleTest:
python3 buildingspy/tests/test_simulate_OpenModelica.py Test_simulate_Simulator.test_addMethods
# python3 buildingspy/tests/test_simulate_OpenModelica.py Test_simulate_Simulator.test_setBooleanParameterValues
# python3 buildingspy/tests/test_simulate_OpenModelica.py Test_simulate_Simulator.test_addVectorOfParameterValues



unittest_simulate_Optimica:
python3 buildingspy/tests/test_simulate_Optimica.py

Expand Down
3 changes: 3 additions & 0 deletions buildingspy/CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ BuildingsPy Changelog

Version 4.x.x, xxx -- Release 4.0
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Added buildingspy.simulate.OpenModelica to support simulation with OpenModelica, and refactored
modules for simulation with Dymola and Optimica.
(https://github.com/lbl-srg/BuildingsPy/issues/500)
- In buildingspy/simulate/base_simulator.py, updated separator for MODELICAPATH on Windows.
- In buildingspy/simulate/base_simulator.py, added support for libraries with encrypted top-level package.moe file.
- Corrected bug in buildingspy/development/refactor.py that causes renames to fail on Windows.
Expand Down
65 changes: 37 additions & 28 deletions buildingspy/development/openmodelica_run.template
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ def _add_exception(return_dict, e, cmd):
import subprocess

return_dict['success'] = False

if isinstance(e, subprocess.CalledProcessError):
# Check if simulation terminated, and if so, get the error
return_dict['stdout'] = e.output.decode("utf-8")
Expand Down Expand Up @@ -50,9 +49,13 @@ def _translate(model, timeout):

# Write translation script
try:
filter = "{{ filter_translate }}"
worDir = r"{{ working_directory }}"
scr_nam = f"{model}_translate.mos"
with open(scr_nam, 'w') as f:
src_nam = model + "_translate.mos"
with open(src_nam, 'w') as f:
# Replace { xxx } with {{ xxx }} which is needed because of the write(f"xxxx") statement
model_modifier_patched = "{{ model_modifier }}".replace(
'\\{', '\\{\\{').replace('\\}', '\\}\\}')
f.write(f"""setCommandLineOptions("-d=nogen");
setCommandLineOptions("-d=initialization");
setCommandLineOptions("-d=backenddaeinfo");
Expand All @@ -61,21 +64,25 @@ setCommandLineOptions("-d=stateselection");
installPackage(Modelica, "");
setMatchingAlgorithm("PFPlusExt");
setIndexReductionMethod("dynamicStateSelection");
loadFile("{{ library_name }}/package.mo");
translated := translateModel({model}, method="{{ solver }}", tolerance={{ rtol }}, numberOfIntervals={{ ncp }}, variableFilter="{{ filter }}");
getErrorString();

if translated then
retVal := system("make -f {model}.makefile");
else
print("Translation failed.");
retVal := 10;
end if;

print("Loading library");
loadFile("{{ package_path }}/package.mo");
print("Loading model");
{{ commentStringNonModifiedModel }}print("Translating model");
{{ commentStringNonModifiedModel }}translateModel({{ model }}, method="{{ solver }}", tolerance={{ rtol }}, numberOfIntervals={{ ncp }}, variableFilter=\"({filter})\");
{{ commentStringNonModifiedModel }}getErrorString();
{{ commentStringModifiedModel }}loadString("model {{ modifiedModelName }} extends {{ model }}({model_modifier_patched}); end {{ modifiedModelName }};");
{{ commentStringModifiedModel }}getErrorString();
{{ commentStringModifiedModel }}print("Translating model");
{{ commentStringModifiedModel }}buildModel({{ modifiedModelName }}, fileNamePrefix=\"{{ model }}\", method="{{ solver }}", tolerance={{ rtol }}, numberOfIntervals={{ ncp }}, variableFilter=\"({filter})\");
{{ commentStringModifiedModel }}getErrorString();
print("Compiling model");
retVal := system("make -f {{ model }}.makefile");
exit(retVal);
""")

# Translate and compile model
cmd = ["omc", scr_nam]
cmd = ["omc", src_nam]
return_dict['cmd'] = ' '.join(cmd)
_run_process(return_dict, cmd, worDir, timeout)

Expand All @@ -91,25 +98,27 @@ def _simulate(model, timeout):

return_dict = {}

# Simulate the model. The CI tests use DOCKER. Hence, we don't invoke directly the executable,
# as the executable links for example to
# /usr/bin/../lib/x86_64-linux-gnu/omc/libsundials_nvecserial.so.5
# Rather, we invoke an OpenModelica script (thereby invoking the Docker) and then simulate
# the executable from this script.
filter = "{{ filter_simulate }}"
worDir = r"{{ working_directory }}"
src_nam = f"{model}_simulate.mos"
cmd = ["omc", src_nam]
return_dict['cmd'] = ' '.join(cmd)

try:
# Simulate the model. The CI tests use DOCKER. Hence, we don't invoke directly the executable,
# as the executable links for example to
# /usr/bin/../lib/x86_64-linux-gnu/omc/libsundials_nvecserial.so.5
# Rather, we invoke an OpenModelica script (thereby invoking the Docker) and then simulate
# the executable from this script.
worDir = r"{{ working_directory }}"
scr_nam = f"{model}_simulate.mos"
with open(scr_nam, 'w') as f:
with open(src_nam, 'w') as f:
if platform.system() == "Windows":
f.write(f"""retVal := system("{model} -s {{ solver }} -steps -cpu -lv LOG_STATS");
exit(retVal);
""")
esc = ""
else:
f.write(f"""retVal := system("./{model} -s {{ solver }} -steps -cpu -lv LOG_STATS");
esc = "./"
f.write(
f"""retVal := system("{esc}{model} -override=startTime={{ start_time }},stopTime={{ final_time }},tolerance={{ rtol }},solver={{ solver }},outputFormat=mat,variableFilter=\\\"({filter})\\\" -steps -cpu -lv LOG_STATS");
exit(retVal);
""")
cmd = ["omc", scr_nam]
return_dict['cmd'] = ' '.join(cmd)
output = _run_process(return_dict, cmd, worDir, timeout)

except Exception as e:
Expand Down
11 changes: 8 additions & 3 deletions buildingspy/development/optimica_run.template
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def process_with_timeout(target, timeout):
p.daemon = True
start = time.time()
p.start()
if timeout > 0:
if (timeout is not None) and (timeout > 0):
p.join(timeout)
else:
p.join()
Expand Down Expand Up @@ -49,6 +49,11 @@ def _translate(proc_num, return_dict):
compiler_options = {"generate_html_diagnostics": generate_html_diagnostics,
"nle_solver_tol_factor": 1e-2} # 1e-2 is the default

# If an FMU name ends with (), then it overides the start and stop time declared
# in the .mo file. Hence we make sure not to add brackets if there is no
# model modifier.
model_name_modifier = "{{ model }}({{ model_modifier }})".replace("()", "")

sig = signature(compile_fmu)
if "modelicapath" in str(sig):
# This is the new API that uses modelicapath as a function argument.
Expand All @@ -61,13 +66,13 @@ def _translate(proc_num, return_dict):
else:
modelicapath = os.path.abspath('.')

fmu_name = compile_fmu("{{ model }}{{ model_modifier }}",
fmu_name = compile_fmu(model_name_modifier,
modelicapath=modelicapath,
version=version,
compiler_log_level=compiler_log_level,
compiler_options=compiler_options)
else:
fmu_name = compile_fmu("{{ model }}{{ model_modifier }}",
fmu_name = compile_fmu(model_name_modifier,
version=version,
compiler_log_level=compiler_log_level,
compiler_options=compiler_options)
Expand Down
63 changes: 49 additions & 14 deletions buildingspy/development/regressiontest.py
Original file line number Diff line number Diff line change
Expand Up @@ -411,11 +411,14 @@ def report(self, timeout=600, browser=None, autoraise=True, comp_file=None):

server.browse(browser=browser, timeout=timeout)

def get_unit_test_log_file(self):
""" Return the name of the log file of the unit tests,
such as ``unitTests-openmodelica.log``, ``unitTests-optimica.log`` or ``unitTests-dymola.log``.
def get_unit_test_log_files(self):
""" Return the name of the logs files of the unit tests,
such as ``unitTests-openmodelica.log``, ``unitTests-optimica.log`` or ``unitTests-dymola.log`` and
``simulator-openmodelica.log`` etc.
"""
return "unitTests-{}.log".format(self._modelica_tool)
return ["comparison-{}.log".format(self._modelica_tool),
"simulator-{}.log".format(self._modelica_tool),
"unitTests-{}.log".format(self._modelica_tool)]

def _initialize_error_dict(self):
""" Initialize the error dictionary.
Expand Down Expand Up @@ -933,7 +936,7 @@ def _set_data_attributes_from_mos(mos_content):

# Set the startTime, if present
for key in ["startTime", "stopTime"]:
regex = fr"simulateModel\(.*({key})\s*=\s*(?P<value>[-+]?\ *[0-9]+\.?[0-9]*(?:[Ee]\ *-?\ *[0-9]+)?)"
regex = fr"simulateModel\(.*({key})\s*=\s*(?P<value>[-+]?\ *[0-9]+\.?[0-9]*(?:[Ee]\ *-?\ *[-+]?[0-9]+)?)"
val = re.search(regex, mos_content, re.DOTALL)
if val:
try:
Expand Down Expand Up @@ -2885,13 +2888,12 @@ def _checkSimulationError(self, errorFile):
# Check for errors
hasTranslationErrors = False
for ele in stat:
hasTranslationError = False
if 'check' in ele and ele['check']['result'] is False:
hasTranslationError = True
hasTranslationErrors = True
iChe = iChe + 1
self._reporter.writeError("Model check failed for '%s'." % ele["model"])
if 'simulate' in ele and ele['simulate']['result'] is False:
hasTranslationError = True
hasTranslationErrors = True
iSim = iSim + 1
self._reporter.writeError("Simulation failed for '%s'." %
ele["simulate"]["command"])
Expand Down Expand Up @@ -2919,7 +2921,7 @@ def _checkSimulationError(self, errorFile):
v["model_message"].format(ele[key]["command"]))
self._error_dict.increment_counter(k)

if hasTranslationError and logFil is not None:
if hasTranslationErrors and logFil is not None:
with open(self._failed_simulator_log_file, "a") as f:
f.write("===============================\n")
f.write("=====START OF NEW LOG FILE=====\n")
Expand All @@ -2944,6 +2946,9 @@ def _checkSimulationError(self, errorFile):
print(
"Check or simulation failed, see {} for more details about the failed models.".format(
self._failed_simulator_log_file))
else:
os.remove(self._failed_simulator_log_file)

return self._writeSummaryMessages()

def _writeSummaryMessages(self, silent=True):
Expand Down Expand Up @@ -3535,7 +3540,8 @@ def _write_runscripts(self):
self._write_run_all_script(iPro, tra_data_pro)

if nUniTes == 0:
raise RuntimeError(f"Wrong invocation, generated {nUniTes} unit tests.")
raise RuntimeError(
f"Wrong invocation, generated {nUniTes} unit tests. There seem to be no model to translate.")

print("Generated {} regression tests.\n".format(nUniTes))

Expand Down Expand Up @@ -3609,19 +3615,48 @@ def _write_python_runscripts(self, iPro, tra_data_pro):
# filter argument must respect glob syntax ([ is escaped with []]) + OPTIMICA mat file
# stores matrix variables with no space e.g. [1,1].
if self._modelica_tool == 'openmodelica':
filter = '(' + '|'.join([re.sub(r'\[|\]',
lambda m: '[{}]'.format(m.group()),
re.sub(' ', '', x)) for x in result_variables]) + ')'

def _getStartStopTime(key, dat):
# Get the startTime or StopTime. If not set, return a default, unless the model must be simulated,
# in which case the method raises a ValueError
retVal = -9999
if key in dat:
retVal = dat[key]
elif not dat[self._modelica_tool]['simulate']:
# We don't have a start time and need not to simulate, such as for testing FMU export.
# Set a default value for the startTime
retVal = 0
else:
raise ValueError(
"Missing entry for 'startTime' for model '{dat[model_name]}'.")
return retVal

startTime = _getStartStopTime('startTime', dat)
stopTime = _getStartStopTime('stopTime', dat)
model_modifier = ""

txt = tem_mod.render(
package_path=self.getLibraryName(),
library_name=self.getLibraryName(),
model=model,
modifiedModelName=f"{model}_modified".replace('.', '_'),
commentStringNonModifiedModel="//" if len(model_modifier) > 0 else "",
commentStringModifiedModel="//" if len(model_modifier) == 0 else "",
model_modifier=model_modifier,
working_directory=directory,
ncp=dat[self._modelica_tool]['ncp'],
rtol=dat[self._modelica_tool]['rtol'],
solver=dat[self._modelica_tool]['solver'],
start_time=startTime,
final_time=stopTime,
simulate=dat[self._modelica_tool]['simulate'],
time_out=dat[self._modelica_tool]['time_out'],
filter=filter
filter_translate='|'.join([re.sub(r'\[|\]',
lambda m: '[{}]'.format(m.group()),
re.sub(' ', '', x)) for x in result_variables]),
filter_simulate='|'.join([re.sub(r'\[|\]',
lambda m: '\\{}'.format(m.group()),
re.sub(' ', '', x)) for x in result_variables])
)
elif self._modelica_tool == 'optimica':
txt = tem_mod.render(
Expand Down
2 changes: 1 addition & 1 deletion buildingspy/license.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Copyright (c) 2011-2022, The Regents of the University of California, Department
Copyright (c) 2011-2023, The Regents of the University of California, Department
of Energy contract-operators of the Lawrence Berkeley National Laboratory.
All rights reserved.

Expand Down
11 changes: 10 additions & 1 deletion buildingspy/simulate/Dymola.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ def __init__(self, modelName, outputDirectory='.', packagePath=None):
packagePath=packagePath,
outputFileList=['run_simulate.mos', 'run_translate.mos', 'run.mos',
'dsfinal.txt', 'dsin.txt',
"buildlog.txt",
"request", "status", "success",
'dsmodel*', 'dymosim', 'dymosim.exe',
'BuildingsPy.log', 'run.mos', 'run_simulate.mos',
'run_translate.mos', 'simulator.log', 'translator.log', 'dslog.txt'])
Expand Down Expand Up @@ -448,4 +450,11 @@ def setResultFile(self, resultFile):

def deleteOutputFiles(self):
super().deleteOutputFiles()
super()._deleteFiles([self._simulator_.get('resultFile') + "_result.mat"])
model_name = self.modelName.replace('.', '_')
super()._deleteFiles([
self._simulator_.get('resultFile') + ".mat",
f"{model_name}_buildingspy.json",
f"{model_name}_compile.log"
f"{model_name}_log.txt",
f"{self.modelName}_JacA.bin",
f"{model_name}.py"])
Loading