diff --git a/OMPython/ModelicaSystem.py b/OMPython/ModelicaSystem.py index bed1ed8f..79ef78a8 100644 --- a/OMPython/ModelicaSystem.py +++ b/OMPython/ModelicaSystem.py @@ -1631,7 +1631,7 @@ def convertMo2Fmu( fmuType: str = "me_cs", fileNamePrefix: Optional[str] = None, includeResources: bool = True, - ) -> str: + ) -> OMCPath: """Translate the model into a Functional Mockup Unit. Args: @@ -1658,15 +1658,19 @@ def convertMo2Fmu( properties = (f'version="{version}", fmuType="{fmuType}", ' f'fileNamePrefix="{fileNamePrefix}", includeResources={includeResourcesStr}') fmu = self._requestApi(apiName='buildModelFMU', entity=self._model_name, properties=properties) + fmu_path = self._session.omcpath(fmu) # report proper error message - if not os.path.exists(fmu): - raise ModelicaSystemError(f"Missing FMU file: {fmu}") + if not fmu_path.is_file(): + raise ModelicaSystemError(f"Missing FMU file: {fmu_path.as_posix()}") - return fmu + return fmu_path # to convert FMU to Modelica model - def convertFmu2Mo(self, fmuName): # 20 + def convertFmu2Mo( + self, + fmu: os.PathLike, + ) -> OMCPath: """ In order to load FMU, at first it needs to be translated into Modelica model. This method is used to generate Modelica model from the given FMU. It generates "fmuName_me_FMU.mo". @@ -1675,13 +1679,24 @@ def convertFmu2Mo(self, fmuName): # 20 >>> convertFmu2Mo("c:/BouncingBall.Fmu") """ - fileName = self._requestApi(apiName='importFMU', entity=fmuName) + fmu_path = self._session.omcpath(fmu) + + if not fmu_path.is_file(): + raise ModelicaSystemError(f"Missing FMU file: {fmu_path.as_posix()}") + + filename = self._requestApi(apiName='importFMU', entity=fmu_path.as_posix()) + filepath = self._work_dir / filename # report proper error message - if not os.path.exists(fileName): - raise ModelicaSystemError(f"Missing file {fileName}") + if not filepath.is_file(): + raise ModelicaSystemError(f"Missing file {filepath.as_posix()}") + + self.model( + name=f"{fmu_path.stem}_me_FMU", + file=filepath, + ) - return fileName + return filepath def optimize(self) -> dict[str, Any]: """Perform model-based optimization. diff --git a/tests/test_FMIImport.py b/tests/test_FMIImport.py new file mode 100644 index 00000000..81167a9e --- /dev/null +++ b/tests/test_FMIImport.py @@ -0,0 +1,57 @@ +import numpy as np +import os +import pytest +import shutil + +import OMPython + + +@pytest.fixture +def model_firstorder(tmp_path): + mod = tmp_path / "M.mo" + mod.write_text("""model M + Real x(start = 1, fixed = true); + parameter Real a = -1; +equation + der(x) = x*a; +end M; +""") + return mod + + +def test_FMIImport(model_firstorder): + filePath = model_firstorder.as_posix() + + # create model & simulate it + mod1 = OMPython.ModelicaSystem() + mod1.model(file=filePath, name="M") + mod1.simulate() + + # create FMU & check + fmu = mod1.convertMo2Fmu(fileNamePrefix="M") + assert os.path.exists(fmu) + + # import FMU & check & simulate + # TODO: why is '--allowNonStandardModelica=reinitInAlgorithms' needed? any example without this possible? + mod2 = OMPython.ModelicaSystem(commandLineOptions=['--allowNonStandardModelica=reinitInAlgorithms']) + mo = mod2.convertFmu2Mo(fmu=fmu) + assert os.path.exists(mo) + + mod2.simulate() + + # get and verify result + res1 = mod1.getSolutions(['time', 'x']) + res2 = mod2.getSolutions(['time', 'x']) + + # check last value for time + assert res1[0][-1] == res2[0][-1] == 1.0 + # check last value for x + assert np.isclose(res1[1][-1], 0.3678794515) # 0.36787945153397683 + assert np.isclose(res2[1][-1], 0.3678794515) # 0.3678794515707647 + + # cleanup + tmp2 = mod1.getWorkDirectory() + shutil.rmtree(tmp2, ignore_errors=True) + + tmp2 = mod2.getWorkDirectory() + shutil.rmtree(tmp2, ignore_errors=True)