diff --git a/.travis.yml b/.travis.yml index 32075f2c..9c6419eb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,7 @@ cache: pip env: global: - OMC_VERSION=ubuntu-2004-omc:1.19.0_dev-539-gb76366f-1 - - OPTIMICA_VERSION=travis-ubuntu-1804-optimica:r26446 + - OPTIMICA_VERSION=travis-ubuntu-1804-optimica:2022-05-09-master-4b0cd2bf71 - DYMOLA_VERSION=travis_ubuntu-2004_dymola:2022x-x86_64 - MPLBACKEND=agg @@ -73,7 +73,6 @@ script: - make unittest_development_error_dictionary - make unittest_development_merger - make unittest_development_refactor - - make unittest_development_regressiontest_jmodelica - make unittest_development_regressiontest_openmodelica - make unittest_development_regressiontest_optimica - make unittest_development_regressiontest diff --git a/Makefile b/Makefile index febea549..a7de321c 100644 --- a/Makefile +++ b/Makefile @@ -41,9 +41,6 @@ unittest_development_merger: unittest_development_refactor: python3 buildingspy/tests/test_development_refactor.py -unittest_development_regressiontest_jmodelica: - python3 buildingspy/tests/test_development_regressiontest_jmodelica.py - unittest_development_regressiontest_openmodelica: python3 buildingspy/tests/test_development_regressiontest_openmodelica.py diff --git a/README.rst b/README.rst index cc8f70dd..4dc789b2 100644 --- a/README.rst +++ b/README.rst @@ -6,8 +6,8 @@ BuildingsPy BuildingsPy is a Python package that can be used to -* run Modelica simulation using Dymola or JModelica -* process ``*.mat`` output files that were generated by Dymola, JModelica or OpenModelica. +* run Modelica simulation using Dymola or OPTIMICA +* process ``*.mat`` output files that were generated by Dymola, OPTIMICA or OpenModelica. * run unit tests as part of the library development. The package provides functions to extract data series from diff --git a/buildingspy/CHANGES.txt b/buildingspy/CHANGES.txt index 67434837..68ebb278 100644 --- a/buildingspy/CHANGES.txt +++ b/buildingspy/CHANGES.txt @@ -1,5 +1,8 @@ BuildingsPy Changelog --------------------- +- Removed JModelica support, and added support for new OPTIMICA compile_fmu API. +- For simulation and unit tests, updated the API for OPTIMICA to the one used in oct-2022-05-09-master-4b0cd2bf71 + (https://github.com/lbl-srg/BuildingsPy/issues/479) - For simulation, corrected a bug that led to an error message when a model from the Modelica Standard Library is simulated (https://github.com/lbl-srg/BuildingsPy/issues/472) - For unit tests, enabled option to run tests with OpenModelica. diff --git a/buildingspy/README.rst b/buildingspy/README.rst index cc8f70dd..4dc789b2 100644 --- a/buildingspy/README.rst +++ b/buildingspy/README.rst @@ -6,8 +6,8 @@ BuildingsPy BuildingsPy is a Python package that can be used to -* run Modelica simulation using Dymola or JModelica -* process ``*.mat`` output files that were generated by Dymola, JModelica or OpenModelica. +* run Modelica simulation using Dymola or OPTIMICA +* process ``*.mat`` output files that were generated by Dymola, OPTIMICA or OpenModelica. * run unit tests as part of the library development. The package provides functions to extract data series from diff --git a/buildingspy/development/error_dictionary_jmodelica.py b/buildingspy/development/error_dictionary_jmodelica.py deleted file mode 100644 index 760ce657..00000000 --- a/buildingspy/development/error_dictionary_jmodelica.py +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -####################################################### -# Class that contains data fields needed for the -# error checking of the regression tests for JModelica -# -# -# MWetter@lbl.gov 2019-01-04 -####################################################### -# -import buildingspy.development.error_dictionary_optimica as ed - - -class ErrorDictionary(ed.ErrorDictionary): - """ Class that contains data fields needed for the - error checking of the regression tests. - - If additional error messages need to be checked, - then they should be added to the constructor of this class. - """ diff --git a/buildingspy/development/jmodelica_run.template b/buildingspy/development/jmodelica_run.template deleted file mode 100644 index 35c6be47..00000000 --- a/buildingspy/development/jmodelica_run.template +++ /dev/null @@ -1,161 +0,0 @@ -############################################################### -# Script to test the model {{ model }} -# with JModelica. -# This script will create a json file that contains translation -# and simulation information. -############################################################### - -# Import the class that grabs stdout -import OutputGrabber as og - -def process_with_timeout(target, timeout): - import multiprocessing - import time - import copy - - manager = multiprocessing.Manager() - return_dict = manager.dict() - p = multiprocessing.Process(target=target, args=(0, return_dict)) - p.daemon = True - start = time.time() - p.start() - if timeout > 0: - p.join(timeout) - else: - p.join() - - cpu_time = time.time() - start - - error_msg = None - if p.is_alive(): - error_msg = "Process timeout" - p.terminate() - elif p.exitcode != 0: - error_msg = "Process terminated by signal {}".format(-p.exitcode) - if error_msg is not None: - raise RuntimeError(error_msg) - - ret = copy.copy(return_dict[0]) - ret.update({'cpu_time': cpu_time}) - return ret - -def _translate(proc_num, return_dict): - from pymodelica import compile_fmu - - try: - # Grab the stdoutput - out = og.OutputGrabber() - out.start() - fmu_name = compile_fmu("{{ model }}", - version="2.0", - compiler_log_level='warning', - compiler_options = {"generate_html_diagnostics" : False, - "nle_solver_tol_factor": 1e-2}) - out.stop() - # The standard output is returned as a list, with each line being an element - return_dict[proc_num] = {'success': True, 'fmu_name': str(fmu_name), 'stdout': out.capturedtext.split('\n')} - - except Exception as e: - return_dict[proc_num] = {'success': False, - 'exception': '{}: {}'.format(type(e).__name__, e)} - return - -def _simulate(proc_num, return_dict): - from pyfmi import load_fmu - - if not {{ simulate }}: - return_dict[proc_num] = {'success': False, - 'message': 'No simulation requested.'} - return return_dict - - # Simulate the model -# - - try: - fmu_name = "{{ model }}".replace(".", "_") + ".fmu" - mod = load_fmu(fmu_name) - x_nominal = mod.nominal_continuous_states - - opts = mod.simulate_options() #Retrieve the default options - opts['logging'] = False - opts['solver'] = '{{ solver }}' - opts['ncp'] = {{ ncp }} - - rtol = {{ rtol }} - - if len(x_nominal) > 0: - atol = rtol*x_nominal - else: - atol = rtol - - if opts['solver'].lower() == 'cvode': - # Set user-specified tolerance if it is smaller than the tolerance in the .mo file - opts['CVode_options']['external_event_detection'] = False - opts['CVode_options']['maxh'] = (mod.get_default_experiment_stop_time()-mod.get_default_experiment_start_time())/float(opts['ncp']) - opts['CVode_options']['iter'] = 'Newton' - opts['CVode_options']['discr'] = 'BDF' - opts['CVode_options']['rtol'] = rtol - opts['CVode_options']['atol'] = atol - opts['CVode_options']['store_event_points'] = True # True is default, set to false if many events - - opts['filter'] = {{ filter }} - # Grab the stdoutput - out = og.OutputGrabber() - out.start() - res = mod.simulate(options=opts) - out.stop() - start_time = res['time'][0] - final_time = res['time'][-1] - return_dict[proc_num] = {'success': True, 'start_time': start_time, 'final_time': final_time, 'stdout': out.capturedtext.split('\n')} - - except Exception as e: - return_dict[proc_num] = {'success': False, - 'exception': '{}: {}'.format(type(e).__name__, e)} - return return_dict - -def run(): - import os - import json - import traceback - import sys - - import pymodelica - # Increase memory - pymodelica.environ['JVM_ARGS'] = '-Xmx4096m' - - time_out = {{ time_out }} - model = "{{ model }}" - result = {"model": model, - "translation": {"success": False}, - "simulation": {"success": False}} - - # Compile model - log_file = "{}_buildingspy.json".format(model.replace(".", "_")) - try: - os.remove(log_file) - except OSError: - pass - - try: - ret_dic = process_with_timeout(target=_translate, timeout=time_out) - result["translation"] = ret_dic - - except Exception as e: - result["translation"]["exception"] = "{}: {}".format(type(e).__name__, e) - result["translation"]["traceback"] = traceback.format_exc() - - # Load model if translation was successful - if result["translation"]["success"]: - try: - ret_dic = process_with_timeout(target=_simulate, timeout=time_out) - result["simulation"] = ret_dic - - except Exception as e: - result["simulation"]["exception"] = "{}: {}".format(type(e).__name__, e) - result["simulation"]["traceback"] = traceback.format_exc() - - with open(log_file, "w") as log: - log.write("{}\n".format(json.dumps(result, indent=4, sort_keys=False)) ) - -if __name__=="__main__": - run() diff --git a/buildingspy/development/jmodelica_run_all.template b/buildingspy/development/jmodelica_run_all.template deleted file mode 100644 index 78503680..00000000 --- a/buildingspy/development/jmodelica_run_all.template +++ /dev/null @@ -1,11 +0,0 @@ -{% for model in models_underscore %} -def run_{{ model }}(): - import {{ model }} as m - m.run() -{% endfor %} - - -if __name__=="__main__": - {% for model in models_underscore %} - run_{{ model }}() - {% endfor %} diff --git a/buildingspy/development/merger.py b/buildingspy/development/merger.py index 414568ac..2024597e 100755 --- a/buildingspy/development/merger.py +++ b/buildingspy/development/merger.py @@ -82,8 +82,6 @@ def isValidLibrary(lib_home): "Scripts", "Conversion", "ConvertIBPSA_from_*.mos"), os.path.join(ibpsa_dir, "Resources", "Scripts", "travis", "Makefile"), - os.path.join(ibpsa_dir, "Resources", - "Scripts", "github-actions", "jmodelica", "jm_ipython.sh"), os.path.join(ibpsa_dir, "Resources", "Scripts", "BuildingsPy", "conf.json"), os.path.join(ibpsa_dir, "Resources", diff --git a/buildingspy/development/optimica_run.template b/buildingspy/development/optimica_run.template index 9c39007c..fd67c224 100644 --- a/buildingspy/development/optimica_run.template +++ b/buildingspy/development/optimica_run.template @@ -1,6 +1,3 @@ -# Import the class that grabs stdout -import OutputGrabber as og - def process_with_timeout(target, timeout): import multiprocessing import time @@ -35,31 +32,65 @@ def process_with_timeout(target, timeout): def _translate(proc_num, return_dict): import os from pymodelica import compile_fmu + from inspect import signature generate_html_diagnostics={{ generate_html_diagnostics }} try: - # Grab the stdoutput - out = og.OutputGrabber() - out.start() - fmu_name = compile_fmu("{{ model }}{{ model_modifier }}", - version="2.0", - compiler_log_level='warning', - compiler_options = {"generate_html_diagnostics" : generate_html_diagnostics, - "nle_solver_tol_factor": 1e-2}) - out.stop() + # OCT r28242 ignores MODELICAPATH and instead needs to have it set through a function argument. + compilation_log = "{{ model }}".replace('.', '_') + "_compile.log" + version="2.0" + compiler_log_level=f"warning:{compilation_log}" + compiler_options = {"generate_html_diagnostics" : generate_html_diagnostics, + "nle_solver_tol_factor": 1e-2} # 1e-2 is the default + + sig = signature(compile_fmu) + if "modelicapath" in str(sig): + # This is the new API that uses modelicapath as a function argument. + + removed_modelica_path = False + if 'MODELICAPATH' in os.environ: + modelicapath=os.environ['MODELICAPATH'] + del os.environ['MODELICAPATH'] + removed_modelica_path = True + else: + modelicapath=os.path.abspath('.') + + fmu_name = compile_fmu("{{ model }}{{ model_modifier }}", + modelicapath=modelicapath, + version=version, + compiler_log_level=compiler_log_level, + compiler_options=compiler_options) + else: + fmu_name = compile_fmu("{{ model }}{{ model_modifier }}", + version=version, + compiler_log_level=compiler_log_level, + compiler_options=compiler_options) + + if removed_modelica_path: + os.environ['MODELICAPATH'] = modelicapath # Copy style sheets. # This is a hack to get the css and js files to render the html diagnostics. htm_dir = os.path.splitext(os.path.basename(fmu_name))[0] + "_html_diagnostics" if generate_html_diagnostics and os.path.exists(htm_dir): - for fil in ["scripts.js", "style.css", "zepto.min.js"]: + for fil in ["scripts.js", "style.css", "html-diagnostics.css", "zepto.min.js"]: src = os.path.join(".jmodelica_html", fil) if os.path.exists(src): des = os.path.join(htm_dir, fil) shutil.copyfile(src, des) + + # Read log file + out = None + if os.path.isfile(compilation_log): + with open(compilation_log, 'r') as f: + out = f.readlines() + else: + out = f"Error: Log file {compilation_log} does not exist." + + # The standard output is returned as a list, with each line being an element - return_dict[proc_num] = {'success': True, 'fmu_name': str(fmu_name), 'stdout': out.capturedtext.split('\n')} + return_dict[proc_num] = {'success': True, 'fmu_name': str(fmu_name), 'stdout': out} except Exception as e: return_dict[proc_num] = {'success': False, @@ -69,6 +100,9 @@ def _translate(proc_num, return_dict): def _simulate(proc_num, return_dict): from pyfmi import load_fmu + # Import the class that grabs stdout + import OutputGrabber as og + if not {{ simulate }}: return_dict[proc_num] = {'success': False, 'message': 'No simulation requested.'} diff --git a/buildingspy/development/regressiontest.py b/buildingspy/development/regressiontest.py index 315a0698..5c711c55 100644 --- a/buildingspy/development/regressiontest.py +++ b/buildingspy/development/regressiontest.py @@ -31,7 +31,6 @@ # Code repository sub-package imports. import pyfunnel from buildingspy.development import error_dictionary_openmodelica -from buildingspy.development import error_dictionary_jmodelica from buildingspy.development import error_dictionary_optimica from buildingspy.development import error_dictionary_dymola from buildingspy.io.outputfile import Reader @@ -50,7 +49,7 @@ def runSimulation(worDir, cmd): .. note:: This method is outside the class definition to allow parallel computing. """ - # JModelica requires the working directory to be part of MODELICAPATH + # OPTIMICA requires the working directory to be part of MODELICAPATH env = os.environ.copy() # will be passed to the subprocess.Popen call if 'MODELICAPATH' in os.environ: env['MODELICAPATH'] = "{}:{}".format(worDir, os.environ['MODELICAPATH']) @@ -125,7 +124,7 @@ class Tester(object): :param check_html: Boolean (default ``True``). Specify whether to load tidylib and perform validation of html documentation. - :param tool: string {``'dymola'``, ``'openmodelica'``, ``'optimica'``, ``'jmodelica'``}. + :param tool: string {``'dymola'``, ``'openmodelica'``, ``'optimica'``}. Default is ``'dymola'``, specifies the tool to use for running the regression test with :func:`~buildingspy.development.Tester.run`. :param cleanup: Boolean (default ``True``). Specify whether to delete temporary directories. @@ -202,17 +201,17 @@ class Tester(object): To run regression tests only for a single package, call :func:`setSinglePackage` prior to :func:`run`. - *Regression testing using OpenModelica, OPTIMICA or JModelica* + *Regression testing using OpenModelica or OPTIMICA* - For OpenModelica, OPTIMICA and JModelica, the selection of test cases is done the same + For OpenModelica and OPTIMICA, the selection of test cases is done the same way as for Dymola. However, the solver tolerance is obtained from the `.mo` file by reading the annotation `Tolerance="value"`. - For OpenModelica, OPTIMICA and JModelica, a JSON file stored as + For OpenModelica and OPTIMICA, a JSON file stored as ``Resources/Scripts/BuildingsPy/conf.yml`` (or for backward compatibility, in `conf.json`) can be used to further configure tests. The file has the syntax below, - where ``openmodelica``, ``optimica`` or ``jmodelica`` specifies the tool. + where ``openmodelica`` or ``optimica`` specifies the tool. .. code-block:: javascript @@ -253,8 +252,6 @@ def __init__( """ Constructor.""" if tool == 'optimica': e = error_dictionary_optimica - elif tool == 'jmodelica': - e = error_dictionary_jmodelica elif tool == 'openmodelica': e = error_dictionary_openmodelica else: @@ -273,11 +270,11 @@ def __init__( self._rootPackage = os.path.join(self._libHome, 'Resources', 'Scripts', 'Dymola') # Set the tool - if tool in ['dymola', 'openmodelica', 'optimica', 'jmodelica']: + if tool in ['dymola', 'openmodelica', 'optimica']: self._modelica_tool = tool else: raise ValueError( - "Value of 'tool' of constructor 'Tester' must be 'dymola', 'openmodelica', 'optimica' or 'jmodelica'. Received '{}'.".format(tool)) + "Value of 'tool' of constructor 'Tester' must be 'dymola', 'openmodelica' or 'optimica'. Received '{}'.".format(tool)) # File to which the console output of the simulator is written self._simulator_log_file = "simulator-{}.log".format(tool) # File to which the console output of the simulator of failed simulations is written @@ -415,7 +412,7 @@ def report(self, timeout=600, browser=None, autoraise=True, comp_file=None): 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``, ``unitTests-jmodelica.log`` or ``unitTests-dymola.log``. + such as ``unitTests-openmodelica.log``, ``unitTests-optimica.log`` or ``unitTests-dymola.log``. """ return "unitTests-{}.log".format(self._modelica_tool) @@ -427,8 +424,6 @@ def _initialize_error_dict(self): import buildingspy.development.error_dictionary_openmodelica as e elif self._modelica_tool == 'optimica': import buildingspy.development.error_dictionary_optimica as e - elif self._modelica_tool == 'jmodelica': - import buildingspy.development.error_dictionary_jmodelica as e else: import buildingspy.development.error_dictionary_dymola as e @@ -1030,7 +1025,7 @@ def _set_attribute_value(line, keyword, dat): # raise ValueError(msg) dat['dymola']['TranslationLogFile'] = dat['model_name'] + ".translation.log" # Get tolerance from mo file. This is used to set the tolerance - # for OpenModelica, OPTIMICA and JModelica. + # for OpenModelica and OPTIMICA. # Only get the tolerance for the models that need to be simulated, # because those that are only exported as FMU don't need this setting. if not dat['dymola']['exportFMU']: @@ -1059,8 +1054,6 @@ def _set_attribute_value(line, keyword, dat): "_", "_0").replace(".", "_") dat['dymola']['FMUName'] = dat['dymola']['FMUName'] + ".fmu" # Plot variables are only used for those models that need to be simulated. - # For JModelica, if dat['jmodelica']['simulate'] == False: - # dat['ResultVariables'] is reset to [] in _add_experiment_specifications if not dat['dymola']['exportFMU']: plotVars = [] iLin = 0 @@ -1383,7 +1376,7 @@ def _getSimulationResults(self, data, warnings, errors): val = [] try: var_mat = var - # Matrix variables in OpenModelica, OPTIMICA and JModelica are stored in mat file with + # Matrix variables in OpenModelica and OPTIMICA are stored in mat file with # no space e.g. [1,1]. if self._modelica_tool != 'dymola': var_mat = re.sub(' ', '', var_mat) @@ -2449,8 +2442,8 @@ def _check_fmu_statistics(self, ans): self._reporter.writeError(em) return retVal - def _get_jmodelica_warnings(self, error_text, model): - """ Return a list with all JModelica warnings + def _get_optimica_warnings(self, error_text, model): + """ Return a list with all OPTIMICA warnings """ import re @@ -2514,7 +2507,7 @@ def _get_openmodelica_simulation_record(self, simulation_text): def _get_optimica_simulation_record(self, simulation_text): """ Return total number of Jacobian evaluations, state events, and elapsed cpu time - when unit tests are run with OPTIMICA or JModelica + when unit tests are run with OPTIMICA. """ jacobianNumber = 0 stateEvents = 0 @@ -2536,7 +2529,7 @@ def _get_optimica_simulation_record(self, simulation_text): return res def _verify_non_dymola_runs(self): - """ Check the results of the OPTIMICA and JModelica tests. + """ Check the results of the OPTIMICA tests. This function returns 0 if no errors occurred, or a positive non-zero number otherwise. @@ -2567,7 +2560,7 @@ def _verify_non_dymola_runs(self): res = json.load(json_file) # Get warnings from stdout that was captured from the compilation if 'stdout' in res['translation']: - warnings = self._get_jmodelica_warnings( + warnings = self._get_optimica_warnings( error_text=res['translation']['stdout'], model=res['model']) res['translation']['warnings'] = warnings @@ -2904,7 +2897,7 @@ def _checkSimulationError(self, errorFile): logFil = ele[key]["translationLog"] ele[key] = self._performTranslationErrorChecks(logFil, ele[key]) for k, v in list(self._error_dict.get_dictionary().items()): - # For OPTIMICA and JModelica, we neither have simulate nor FMUExport + # For OPTIMICA, we neither have simulate nor FMUExport if ele[key][k] > 0: self._reporter.writeWarning(v["model_message"].format(ele[key]["command"])) self._error_dict.increment_counter(k) @@ -3405,7 +3398,7 @@ def _print_end_of_json(isLastItem, fileHandle, logFileName): def _write_runscripts(self): """Create the runAll.mos scripts, one per processor (self._nPro). - The commands in the script depend on the tool: 'openmodelica', 'dymola', 'optimica', or 'jmodelica' + The commands in the script depend on the tool: 'openmodelica', 'dymola' or 'optimica' """ nUniTes = 0 @@ -3480,7 +3473,7 @@ def _get_set_of_result_variables(list_of_result_variables): return s def _write_runscript_non_dymola(self, iPro, tra_data_pro): - """ Write the OpenModelica, OPTIMICA or JModelica runfile for all experiments in tra_data_pro. + """ Write the OpenModelica or OPTIMICA runfile for all experiments in tra_data_pro. :param iPro: The number of the processor. :param tra_data_pro: A list with the data for the experiments that require translation, for this processor only. @@ -3523,7 +3516,7 @@ def _write_runscript_non_dymola(self, iPro, tra_data_pro): dat[self._modelica_tool]['rtol'] = 1E-6 # Note that if dat[self._modelica_tool]['simulate'] == false, then only the FMU export is tested, but no # simulation should be done. - # filter argument must respect glob syntax ([ is escaped with []]) + JModelica mat file + # 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'\[|\]', @@ -3562,7 +3555,7 @@ def _write_runscript_non_dymola(self, iPro, tra_data_pro): fil.write(txt) # Copy python file that grabs the console output - if self._modelica_tool == 'optimica' or self._modelica_tool == 'jmodelica': + if self._modelica_tool == 'optimica': shutil.copyfile( os.path.join( os.path.dirname(__file__), @@ -3849,7 +3842,7 @@ def run(self): retVal = temp if not self._skip_verification: - # For OpenModelica, OPTIMICA and JModelica: store available translation and simulation info + # For OpenModelica and OPTIMICA: store available translation and simulation info # into self._comp_info used for reporting. with open(self._simulator_log_file, 'r') as f: self._comp_info = simplejson.loads(f.read()) diff --git a/buildingspy/development/validator.py b/buildingspy/development/validator.py index dda87a5d..e7f7eafa 100644 --- a/buildingspy/development/validator.py +++ b/buildingspy/development/validator.py @@ -206,7 +206,7 @@ def _check_experiment(self, name, val, value, model_path, mos_file): ".\n" + self._capitalize_first(name) + " contains invalid expressions such as x * y. Only literal expressions are allowed " + - "by OPTIMICA, JModelica and OpenModelica unit tests.\n") + "by OPTIMICA and OpenModelica unit tests.\n") raise ValueError(s) delta = abs(eval(val) - eval(value)) @@ -338,7 +338,7 @@ def _separate_mos_files(self, mos_files): if (found_sim and not found_tol): s = ( "Found mos file={!s} without tolerance defined.\n" + - "A minimum tolerance of 1e-6 is required for OPTIMICA and JModelica.\n").format(itr) + "A minimum tolerance of 1e-6 is required for OPTIMICA.\n").format(itr) raise ValueError(s) return n_tols, mos_non_fmus, mos_fmus @@ -371,13 +371,13 @@ def _wrong_parameter(self, mos_file, name, value): if value is None: s = ( "Found mos file={!s} without tolerance specified.\n" + - "A minimum tolerance of 1e-6 is required for OPTIMICA and JModelica for unit tests.\n").format(mos_file) + "A minimum tolerance of 1e-6 is required for OPTIMICA for unit tests.\n").format(mos_file) raise ValueError(s) else: if(float(value) > 1e-6): s = ("Found mos file={!s} with tolerance={!s}.\n" "The tolerance found is bigger than 1e-6, the maximum required by " - "OPTIMICA and JModelica for unit tests.\n").format(mos_file, value) + "OPTIMICA for unit tests.\n").format(mos_file, value) raise ValueError(s) if (name + "=" == "stopTime="): @@ -411,7 +411,7 @@ def _getValue(self, name, line, fil_nam): if name == "StartTime": # If it is smaller than -2147483648 and bigger than 2147483647, which are # the minimum and maximum 32 bit integers. These are used in - # the CI testing of JModelica. Exceeding them will cause an integer overflow + # the CI testing of OPTIMICA. Exceeding them will cause an integer overflow if isinstance(ev, int): if ev < -2147483648: err = ( @@ -422,7 +422,7 @@ def _getValue(self, name, line, fil_nam): if name == "StopTime": # If it is smaller than -2147483648 and bigger than 2147483647, which are # the minimum and maximum 32 bit integers. These are used in - # the CI testing of JModelica. Exceeding them will cause an integer overflow + # the CI testing of OPTIMICA. Exceeding them will cause an integer overflow if isinstance(ev, int): if ev > 2147483647: err = ( @@ -444,7 +444,7 @@ def _wrong_literal(self, mos_file, name): s = ( "Found mos file={!s} with invalid expression={!s}.\n" + - "This is not allowed for cross validation with OPTIMICA and JModelica.\n").format( + "This is not allowed for cross validation with OPTIMICA.\n").format( mos_file, name + '=' + diff --git a/buildingspy/io/outputfile.py b/buildingspy/io/outputfile.py index f87a5c07..ab638100 100644 --- a/buildingspy/io/outputfile.py +++ b/buildingspy/io/outputfile.py @@ -168,9 +168,9 @@ class Reader(object): def __init__(self, fileName, simulator): import os - if simulator not in ['openmodelica', 'dymola', 'optimica', 'jmodelica']: + if simulator not in ['openmodelica', 'dymola', 'optimica']: raise ValueError( - 'Argument "simulator" needs to be set to "openmodelica", "dymola", "optimica" or "jmodelica".') + 'Argument "simulator" needs to be set to "openmodelica", "dymola" or "optimica".') if not os.path.isfile(fileName): raise FileNotFoundError(f"File {os.path.abspath(fileName)} does not exist.") diff --git a/buildingspy/simulate/Optimica.py b/buildingspy/simulate/Optimica.py index b4ce44e2..be8d5150 100644 --- a/buildingspy/simulate/Optimica.py +++ b/buildingspy/simulate/Optimica.py @@ -4,10 +4,6 @@ Class that translates and simulates a Modelica model with OPTIMICA. - Note that because OPTIMICA and JModelica have a similar API, and because - they are invoked by the same script, this class - should also work with JModelica. - For a similar class that uses Dymola, see :func:`buildingspy.simulate.Dymola`. """ @@ -205,7 +201,7 @@ def _translate_and_simulate(self, simulate): template = env.get_template("optimica_run.template") - # Note that filter argument must respect glob syntax ([ is escaped with []]) + JModelica mat file + # Note that filter argument must respect glob syntax ([ is escaped with []]) + OPTIMICA mat file # stores matrix variables with no space e.g. [1,1]. txt = template.render( model=self.modelName, diff --git a/buildingspy/simulate/__init__.py b/buildingspy/simulate/__init__.py index d7d1c223..84980821 100644 --- a/buildingspy/simulate/__init__.py +++ b/buildingspy/simulate/__init__.py @@ -13,8 +13,4 @@ class may have methods that are only applicable for :func:`buildingspy.simulate.Optimica.Simulator.generateHtmlDiagnostics` is only available for Optimica. -Note that :mod:`buildingspy.simulate.Optimica` -should also work with JModelica.org, -but the latter is not officially supported. - """ diff --git a/buildingspy/simulate/base_simulator.py b/buildingspy/simulate/base_simulator.py index 15d8be0b..b6869572 100644 --- a/buildingspy/simulate/base_simulator.py +++ b/buildingspy/simulate/base_simulator.py @@ -407,7 +407,7 @@ def _runSimulation(self, cmd, timeout, directory, env=None): if timeout_exceeded: # For Dymola only: manage process termination. - # (For Optimica and JModelica this is managed at the lower level + # (For Optimica this is managed at the lower level # in `*_run.template`.) if self._MODELICA_EXE == 'dymola': # On unixlike systems, give the process a chance to close gracefully diff --git a/buildingspy/tests/MyModelicaLibrary/Examples/FMUs/IntegratorGain.mo b/buildingspy/tests/MyModelicaLibrary/Examples/FMUs/IntegratorGain.mo index 22205e20..86dc1edf 100644 --- a/buildingspy/tests/MyModelicaLibrary/Examples/FMUs/IntegratorGain.mo +++ b/buildingspy/tests/MyModelicaLibrary/Examples/FMUs/IntegratorGain.mo @@ -1,6 +1,6 @@ within MyModelicaLibrary.Examples.FMUs; block IntegratorGain "Block to demonstrate the FMU export" - extends Modelica.Blocks.Interfaces.BlockIcon; + extends Modelica.Blocks.Icons.Block; parameter Real k = -1 "Gain"; diff --git a/buildingspy/tests/MyModelicaLibrary/Examples/FMUs/Integrator_Underscore.mo b/buildingspy/tests/MyModelicaLibrary/Examples/FMUs/Integrator_Underscore.mo index 6ce1be11..ab0057cb 100644 --- a/buildingspy/tests/MyModelicaLibrary/Examples/FMUs/Integrator_Underscore.mo +++ b/buildingspy/tests/MyModelicaLibrary/Examples/FMUs/Integrator_Underscore.mo @@ -1,6 +1,6 @@ within MyModelicaLibrary.Examples.FMUs; block Integrator_Underscore "Block to demonstrate the FMU export" - extends Modelica.Blocks.Interfaces.BlockIcon; + extends Modelica.Blocks.Icons.Block; parameter Real k = -1 "Gain"; diff --git a/buildingspy/tests/MyModelicaLibrary/Examples/ParameterEvaluation.mo b/buildingspy/tests/MyModelicaLibrary/Examples/ParameterEvaluation.mo index 88db97b1..19e81e87 100644 --- a/buildingspy/tests/MyModelicaLibrary/Examples/ParameterEvaluation.mo +++ b/buildingspy/tests/MyModelicaLibrary/Examples/ParameterEvaluation.mo @@ -5,7 +5,8 @@ model ParameterEvaluation parameter Integer n = integer(1/x) "Dimension"; Real T[n] "Vector"; equation - der(T) = ones(n) + der(T) = ones(n); + annotation (Documentation(info="

This model is used in the Python regression tests to ensure that BuildingsPy @@ -13,5 +14,4 @@ throws an exception if it attempts to change a structural parameter after the compilation.

")); - end ParameterEvaluation; diff --git a/buildingspy/tests/MyModelicaLibrary/Resources/Scripts/travis/bin/jm_ipython.sh b/buildingspy/tests/MyModelicaLibrary/Resources/Scripts/travis/bin/jm_ipython.sh index b334f036..a289f416 100755 --- a/buildingspy/tests/MyModelicaLibrary/Resources/Scripts/travis/bin/jm_ipython.sh +++ b/buildingspy/tests/MyModelicaLibrary/Resources/Scripts/travis/bin/jm_ipython.sh @@ -57,7 +57,7 @@ if [ -z ${MODELICAPATH+x} ]; then else # Add the current directory to the front of the Modelica path. # This will export the directory to the docker, and also set - # it in the MODELICAPATH so that JModelica finds it. + # it in the MODELICAPATH so that OPTIMICA finds it. MODELICAPATH=`pwd`:${MODELICAPATH} fi @@ -107,7 +107,7 @@ DOCKER_FLAGS="\ ${NAME}" docker run ${DOCKER_FLAGS} /bin/bash -c \ - "export MODELICAPATH=${DOCKER_MODELICAPATH}:/opt/oct/ThirdParty/MSL/MSL323:/opt/oct/ThirdParty/MSL/MSL400:/opt/oct/ThirdParty/MSL && \ + "export MODELICAPATH=${DOCKER_MODELICAPATH} && \ export PYTHONPATH=${DOCKER_PYTHONPATH} && \ export IPYTHONDIR=/mnt/shared && alias ipython=ipython3 && \ diff --git a/buildingspy/tests/MyModelicaLibrary/package.mo b/buildingspy/tests/MyModelicaLibrary/package.mo index ab6e059f..fb8fb4d6 100644 --- a/buildingspy/tests/MyModelicaLibrary/package.mo +++ b/buildingspy/tests/MyModelicaLibrary/package.mo @@ -18,5 +18,5 @@ See buildingspy/tests/Test_development_refactor_Annex60.

")); - annotation (uses(Modelica(version="3.2.3"))); + annotation (uses(Modelica(version="4.0.0"))); end MyModelicaLibrary; diff --git a/buildingspy/tests/MyModelicaLibrary/package.order b/buildingspy/tests/MyModelicaLibrary/package.order index 602ef997..f3ebf8d9 100644 --- a/buildingspy/tests/MyModelicaLibrary/package.order +++ b/buildingspy/tests/MyModelicaLibrary/package.order @@ -1,8 +1,8 @@ MyModel MyModelTimeOut MyStep -Reset one two +Reset Examples Obsolete diff --git a/buildingspy/tests/test_development_Validator.py b/buildingspy/tests/test_development_Validator.py index 49ac5dbb..363444b7 100644 --- a/buildingspy/tests/test_development_Validator.py +++ b/buildingspy/tests/test_development_Validator.py @@ -97,12 +97,12 @@ def test_validateExperimentSetup(self): ########################################### # Checking missing tolerance in mos file self.run_case(val, myMoLib, "Test2", "experiment(Tolerance=1e-6, StopTime=1.0),", - "", "A minimum tolerance of 1e-6 is required for OPTIMICA and JModelica.") + "", "A minimum tolerance of 1e-6 is required for OPTIMICA.") ########################################### # Checking missing tolerance in mo file self.run_case(val, myMoLib, "Test3", "experiment(StopTime=1.0),", "stopTime=1.0,", - "A minimum tolerance of 1e-6 is required for OPTIMICA and JModelica.") + "A minimum tolerance of 1e-6 is required for OPTIMICA.") ########################################### # Checking tolerances mismatch @@ -168,7 +168,7 @@ def test_validateExperimentSetup(self): ########################################### # Checking wrong data type that can cause an overflow - # In JModelica's CI testing, the maximum integer is 2147483647 + # In OPTIMICA's CI testing, the maximum integer is 2147483647 self.run_case( val, myMoLib, diff --git a/buildingspy/tests/test_development_regressiontest_jmodelica.py b/buildingspy/tests/test_development_regressiontest_jmodelica.py deleted file mode 100644 index 013182d9..00000000 --- a/buildingspy/tests/test_development_regressiontest_jmodelica.py +++ /dev/null @@ -1,149 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -import unittest -import os - -# To run this test, navigate to the BuildingsPy folder, then type -# python buildingspy/tests/test_development_regressiontest.py - - -class Test_regressiontest_jmodelica_Tester(unittest.TestCase): - """ - This class contains the unit tests for - :mod:`buildingspy.regressiontest.Tester` for jmodelica. - """ - - def test_unit_test_log_file(self): - import buildingspy.development.regressiontest as r - rt = r.Tester(check_html=False, tool="jmodelica") - self.assertEqual('unitTests-jmodelica.log', rt.get_unit_test_log_file()) - - @staticmethod - def _write_test(content): - """ Write a unit test for a model with the content `content` - in a temporary directory and return the name of this directory. - """ - import os - import tempfile - - dir_name = os.path.join(tempfile.mkdtemp(prefix='tmp-BuildingsPy-unittests-'), "TestLib") - script_dir = os.path.join(dir_name, "Resources", "Scripts", "Dymola") - mo_name = "Test" - mo_content = """within TestLib; - model Test - {} - annotation (experiment(Tolerance=1e-6, StopTime=3600)); - end Test; - """.format(content) - - # Create directory for mos scripts - os.makedirs(script_dir) - # Write mos file - with open(os.path.join(script_dir, mo_name + ".mos"), mode="w", encoding="utf-8") as fil: - con = """ -simulateModel("TestLib.{}", tolerance=1e-6, stopTime=1.0, method="CVode", resultFile="test");""".format(mo_name) - con = con + """ -createPlot(id=1, y={"Test.x"}); -""" - fil.write(con) - # Write mo file - with open(os.path.join(dir_name, mo_name + ".mo"), mode="w", encoding="utf-8") as fil: - fil.write(mo_content) - # Write top-level package - with open(os.path.join(dir_name, 'package.mo'), mode="w", encoding="utf-8") as fil: - mo = """ - within; - package TestLib - end TestLib; -""" - fil.write(mo) - # Write top-level package.order - with open(os.path.join(dir_name, 'package.order'), mode="w", encoding="utf-8") as fil: - mo = """TestLib""" - fil.write(mo) - return dir_name - - def test_regressiontest_diagnostics(self): - """ Test that warnings and errors reported by JModelica are reported. - """ - import shutil - import buildingspy.development.regressiontest as r - - tests = [ - {'ret_val': 0, - 'mo_content': """parameter Real x = 0;""", - 'description': "Correct model."}, - {'ret_val': 2, - 'mo_content': """parameter Real[2] x(unit="m") = {0, 0};""", - 'description': "Missing each on variable."}, - {'ret_val': 2, - 'mo_content': """parameter Real x(each unit="m") = 0;""", - 'description': "Wrong each on scalar."}, - {'ret_val': 2, - 'mo_content': """Modelica.Blocks.Sources.Constant b(each k=0) ;""", - 'description': "Wrong each on scalar component."}, - {'ret_val': 2, - 'mo_content': """Modelica.Blocks.Sources.Constant b[2](k=0) ;""", - 'description': "Missing each on array of components."}, - {'ret_val': 0, - 'mo_content': """ - Real x; - equation - Modelica.Math.exp(x)=1;""", - 'description': "Missing start value, which should be ignored."}, - {'ret_val': 0, - 'mo_content': """ - Real x(start=0); - equation - der(x)^3 = 0;""", - 'description': "Missing start value for der(x), which should be ignored."}, - {'ret_val': 2, - 'mo_content': """parameter Real[2] x(unit="m") = {0, 0}; - parameter Real y(each unit="m") = 0;""", - 'description': "Two errors."}, - {'ret_val': 1, - 'mo_content': """x; """, - 'description': "Syntax error that should cause a failure in translation."}, - {'ret_val': 1, - 'mo_content': """Real x(start=0); - equation - Modelica.Math.exp(x)=-1;""", - 'description': "Model that has no solution."} - ] - # Run all test cases - for test in tests: - des = test['description'] - print("*** Running test for '{}'".format(des)) - mo_content = test['mo_content'] - dir_name = self._write_test(mo_content) - rt = r.Tester(skip_verification=True, check_html=False, tool="jmodelica") - rt.setLibraryRoot(dir_name) - ret_val = rt.run() - # Check return value to see if test suceeded - self.assertEqual( - test['ret_val'], - ret_val, - "Test for '{}' failed, return value {}".format( - des, - ret_val)) # Delete temporary files - # Get parent dir of dir_name, because dir_name contains the Modelica library name - par = os.path.split(dir_name)[0] - os.remove(rt.get_unit_test_log_file()) - shutil.rmtree(par) - - def test_regressiontest(self): - import buildingspy.development.regressiontest as r - rt = r.Tester(skip_verification=True, check_html=False, tool="jmodelica") - myMoLib = os.path.join("buildingspy", "tests", "MyModelicaLibrary") - rt.deleteTemporaryDirectories(True) - rt.setLibraryRoot(myMoLib) - ret_val = rt.run() - # Check return value to see if test suceeded - self.assertEqual(0, ret_val, "Test failed with return value {}".format(ret_val)) - # Delete temporary files - os.remove(rt.get_unit_test_log_file()) - - -if __name__ == '__main__': - unittest.main() diff --git a/doc/Makefile b/doc/Makefile index 9cedbd6c..e1133c04 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -34,6 +34,8 @@ clean: html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo "Deleting stray html pull down menu" + sed -e 's|
  • buildingspy
  • ||g' -i $(BUILDDIR)/html/index.html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." diff --git a/doc/source/_static/lbl-logo.png b/doc/source/_static/lbl-logo.png index 6ad8a608..16fae5f7 100644 Binary files a/doc/source/_static/lbl-logo.png and b/doc/source/_static/lbl-logo.png differ diff --git a/doc/source/_static/sphinxdoc.css b/doc/source/_static/sphinxdoc.css index 78cae1a7..262ec988 100644 --- a/doc/source/_static/sphinxdoc.css +++ b/doc/source/_static/sphinxdoc.css @@ -75,6 +75,11 @@ div.headerStyle{ box-shadow: 2px -2px 10px 0px rgba(0,0,0,0.4); } +div.headerStyle img{ + left: 10px; + height: 80px; +} + div.document { background-color: white; text-align: left; diff --git a/doc/source/index.rst b/doc/source/index.rst index 34c2c456..c2c1e58b 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -13,9 +13,8 @@ package that can be used to: `OPTIMICA `_ or `Dymola `_. - JModelica.org should also work as it has a similar API than OPTIMICA. - Process ``*.mat`` output files that were generated by - OPTIMICA, JModelica.org, Dymola or + OPTIMICA, Dymola or `OpenModelica `_. - Run unit tests as part of the library development. - Refactor Modelica libraries.