diff --git a/CHANGELOG b/CHANGELOG index 55518c7b..009dccbe 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,10 @@ --- CHANGELOG --- --- PyFMI-FUTURE --- +--- PyFMI-2.18.3 --- + * Fixed a bug introduced in PyFMI 2.18.0 causing incorrect result storing for `boolean` and `enum` variables + with `result_handling = "binary"` (default). + --- PyFMI-2.18.2 --- * Fixed an issue with `ResultDymolaBinary` result retrieval if `dynamic_diagnostics = True` and there is only a single solution point. diff --git a/doc/sphinx/source/changelog.rst b/doc/sphinx/source/changelog.rst index 32bbdf1c..bf4d2b25 100644 --- a/doc/sphinx/source/changelog.rst +++ b/doc/sphinx/source/changelog.rst @@ -2,6 +2,10 @@ ========== Changelog ========== +--- PyFMI-2.18.3 --- + * Fixed a bug introduced in PyFMI 2.18.0 causing incorrect result storage for `boolean` and `enum` variables + with `result_handling = "binary"` (default). + --- PyFMI-2.18.2 --- * Fixed an issue with `ResultDymolaBinary` result retrieval if `dynamic_diagnostics = True` and there is only a single solution point. diff --git a/doc/sphinx/source/conf.py b/doc/sphinx/source/conf.py index 97812dd2..50e2edf3 100644 --- a/doc/sphinx/source/conf.py +++ b/doc/sphinx/source/conf.py @@ -51,9 +51,9 @@ # built documents. # # The short X.Y version. -version = '2.18.2' +version = '2.18.3' # The full version, including alpha/beta/rc tags. -release = '2.18.2' +release = '2.18.3' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/src/common/io.py b/src/common/io.py index 7fb7fa48..e38ae92e 100644 --- a/src/common/io.py +++ b/src/common/io.py @@ -2896,8 +2896,8 @@ def _get_sorted_vars(self): data_types = [ fmi.FMI_REAL, fmi.FMI_INTEGER, + fmi.FMI_ENUMERATION, fmi.FMI_BOOLEAN, - fmi.FMI_ENUMERATION ] for data_type in data_types: diff --git a/tests/conftest.py b/tests/conftest.py index e1cdc60e..da82f183 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -48,8 +48,30 @@ def download_url(url, save_file_to): with ZipFile(zip_path, 'r') as zf: for fobj in zf.filelist: - # For now, only unpack FMI 3.0 FMUs - if (fobj.filename.startswith('3.0') or fobj.filename.startswith('2.0')) and fobj.filename.endswith('.fmu'): + if fobj.filename.startswith('3.0') and fobj.filename.endswith('.fmu'): + zf.extract(fobj, zip_unzip_to) + with open(md5_file, 'w') as f: + f.write(md5) + # NOTE: FMI2 Feedthrough.fmu is broken in v0.0.39; use v0.0.38 for FMI2 instead + # Remove the code below with new reference FMU version + zip_file_url = "https://github.com/modelica/Reference-FMUs/releases/download/v0.0.38/Reference-FMUs-0.0.38.zip" + zip_file_name = 'reference_fmus.zip' + zip_unzip_to = files_directory / 'reference_fmus' + md5 = hashlib.md5(zip_file_url.encode("utf-8")).hexdigest() + md5_file = Path(zip_unzip_to) / 'metadata_0_0_38' # Expected file + use_already_existing = False + if md5_file.exists(): + with open(md5_file, 'r') as f: + use_already_existing = md5 == f.read() + + if not use_already_existing: + with TemporaryDirectory() as tmpdirname: + zip_path = Path(tmpdirname) / zip_file_name + download_url(zip_file_url, zip_path) + + with ZipFile(zip_path, 'r') as zf: + for fobj in zf.filelist: + if fobj.filename.startswith('2.0') and fobj.filename.endswith('.fmu'): zf.extract(fobj, zip_unzip_to) with open(md5_file, 'w') as f: f.write(md5) diff --git a/tests/test_io.py b/tests/test_io.py index c25cc495..d3ce8828 100644 --- a/tests/test_io.py +++ b/tests/test_io.py @@ -24,6 +24,8 @@ from io import StringIO, BytesIO from collections import OrderedDict +from pyfmi import load_fmu + from pyfmi.fmi import ( FMUException, FMUModelME2, @@ -63,6 +65,7 @@ this_dir = Path(__file__).parent.absolute() FMI3_REF_FMU_PATH = Path(this_dir) / 'files' / 'reference_fmus' / '3.0' +FMI2_REF_FMU_PATH = Path(this_dir) / 'files' / 'reference_fmus' / '2.0' file_path = os.path.dirname(os.path.abspath(__file__)) def _run_negated_alias(model, result_type, result_file_name=""): @@ -2299,3 +2302,56 @@ def test_basic_class_functions(get_fmu, result_handling, expected_result_class): assert isinstance(trajs, dict) traj = trajs.get("time") assert isinstance(traj, Trajectory) + +@pytest.mark.parametrize("fmu_path, result_handling", + [ + (FMI2_REF_FMU_PATH / "Feedthrough.fmu", "file"), + (FMI2_REF_FMU_PATH / "Feedthrough.fmu", "binary"), + (FMI2_REF_FMU_PATH / "Feedthrough.fmu", "memory"), + (FMI2_REF_FMU_PATH / "Feedthrough.fmu", "csv"), + + (FMI3_REF_FMU_PATH / "Feedthrough.fmu", "binary"), + ] +) +def test_basic_variable_get_set_result_correctness(fmu_path, result_handling): + """Test that basic get/set and result values align for the different variable types.""" + fmu = load_fmu(fmu_path) + fmu.initialize() + # non-default values + bool_val = True + int32_val = 15 + float64_val = 3.14 + enum_val = 2 + + # verify values are not the defaults + assert fmu.get("Boolean_input")[0] != bool_val + assert fmu.get("Int32_input")[0] != int32_val + assert fmu.get("Float64_continuous_input")[0] != float64_val + assert fmu.get("Enumeration_input")[0] != enum_val + + fmu.set("Boolean_input", bool_val) + fmu.set("Int32_input", int32_val) + fmu.set("Float64_continuous_input", float64_val) + fmu.set("Enumeration_input", enum_val) + + # verify get/set + assert fmu.get("Boolean_input")[0] == bool_val + assert fmu.get("Int32_input")[0] == int32_val + assert fmu.get("Float64_continuous_input")[0] == float64_val + assert fmu.get("Enumeration_input")[0] == enum_val + + fmu.event_update() + fmu.enter_continuous_time_mode() + opts = {"ncp": 2, "initialize": False, "result_handling": result_handling} + res = fmu.simulate(options = opts) + + # verify simulation didn't change value + assert fmu.get("Boolean_input")[0] == bool_val + assert fmu.get("Int32_input")[0] == int32_val + assert fmu.get("Float64_continuous_input")[0] == float64_val + assert fmu.get("Enumeration_input")[0] == enum_val + + assert res["Boolean_input"][-1] == bool_val + assert res["Int32_input"][-1] == int32_val + assert res["Float64_continuous_input"][-1] == float64_val + assert res["Enumeration_input"][-1] == enum_val