diff --git a/src/pyfmi/fmi3.pxd b/src/pyfmi/fmi3.pxd index 4d7a8444..057d5be0 100644 --- a/src/pyfmi/fmi3.pxd +++ b/src/pyfmi/fmi3.pxd @@ -27,8 +27,7 @@ cdef class FMUModelBase3(FMI_BASE.ModelBase): cdef FMIL3.fmi3_import_t* _fmu cdef FMIL3.fmi3_fmu_kind_enu_t _fmu_kind cdef FMIL.fmi_version_enu_t _version - cdef FMIL.size_t _nEventIndicators - cdef FMIL.size_t _nContinuousStates + # Internal values cdef public float _last_accepted_time @@ -39,8 +38,12 @@ cdef class FMUModelBase3(FMI_BASE.ModelBase): cdef int _allow_unzipped_fmu cdef int _allocated_context, _allocated_dll, _allocated_fmu, _allocated_xml + cdef int _initialized_fmu + cdef object _has_entered_init_mode # this is public in FMI2 but I don't see why + cdef class FMUModelME3(FMUModelBase3): - pass + cdef FMIL.size_t _nEventIndicators + cdef FMIL.size_t _nContinuousStates cdef void _cleanup_on_load_error( FMIL3.fmi3_import_t* fmu_3, diff --git a/src/pyfmi/fmi3.pyx b/src/pyfmi/fmi3.pyx index 881531ac..32293d2f 100644 --- a/src/pyfmi/fmi3.pyx +++ b/src/pyfmi/fmi3.pyx @@ -206,18 +206,6 @@ cdef class FMUModelBase3(FMI_BASE.ModelBase): self._modelName = pyfmi_util.decode(FMIL3.fmi3_import_get_model_name(self._fmu)) - self._log_handler.capi_start_callback(self._max_log_size_msg_sent, self._current_log_size) - status = FMIL3.fmi3_import_get_number_of_event_indicators(self._fmu, &self._nEventIndicators) - self._log_handler.capi_end_callback(self._max_log_size_msg_sent, self._current_log_size) - if status != FMIL3.fmi3_status_ok: - raise InvalidFMUException("The FMU could not be instantiated, error retrieving number of event indicators.") - - self._log_handler.capi_start_callback(self._max_log_size_msg_sent, self._current_log_size) - status = FMIL3.fmi3_import_get_number_of_continuous_states(self._fmu, &self._nContinuousStates) - self._log_handler.capi_end_callback(self._max_log_size_msg_sent, self._current_log_size) - if status != FMIL3.fmi3_status_ok: - raise InvalidFMUException("The FMU could not be instantiated, error retrieving number of continuous states.") - # TODO: The code below is identical between FMUModelBase2 and FMUModelBase3, perhaps we can refactor this if not isinstance(log_file_name, str): self._set_log_stream(log_file_name) @@ -268,8 +256,26 @@ cdef class FMUModelBase3(FMI_BASE.ModelBase): self._log_stream = None def reset(self): - """ TODO """ + """ Resets the FMU back to its original state. Note that the environment + has to initialize the FMU again after this function-call. """ + cdef FMIL3.fmi3_status_t status + + self._log_handler.capi_start_callback(self._max_log_size_msg_sent, self._current_log_size) + status = FMIL3.fmi3_import_reset(self._fmu) + self._log_handler.capi_end_callback(self._max_log_size_msg_sent, self._current_log_size) + if status != 0: + raise FMUException('An error occured when reseting the model, see the log for possible more information') + + + #Default values self._t = None + self._has_entered_init_mode = False + + #Reseting the allocation flags + self._initialized_fmu = 0 + + #Internal values + self._log = [] def _get_fmu_kind(self): raise FMUException("FMUModelBase3 cannot be used directly, use FMUModelME3.") @@ -366,6 +372,19 @@ cdef class FMUModelME3(FMUModelBase3): # Call super on base class FMUModelBase3.__init__(self, fmu, log_file_name, log_level, _unzipped_dir, _connect_dll, allow_unzipped_fmu) + + self._log_handler.capi_start_callback(self._max_log_size_msg_sent, self._current_log_size) + status = FMIL3.fmi3_import_get_number_of_event_indicators(self._fmu, &self._nEventIndicators) + self._log_handler.capi_end_callback(self._max_log_size_msg_sent, self._current_log_size) + if status != FMIL3.fmi3_status_ok: + raise InvalidFMUException("The FMU could not be instantiated, error retrieving number of event indicators.") + + self._log_handler.capi_start_callback(self._max_log_size_msg_sent, self._current_log_size) + status = FMIL3.fmi3_import_get_number_of_continuous_states(self._fmu, &self._nContinuousStates) + self._log_handler.capi_end_callback(self._max_log_size_msg_sent, self._current_log_size) + if status != FMIL3.fmi3_status_ok: + raise InvalidFMUException("The FMU could not be instantiated, error retrieving number of continuous states.") + if _connect_dll: self.instantiate() @@ -427,7 +446,6 @@ cdef class FMUModelME3(FMUModelBase3): ): """ Initializes the model and computes initial values for all variables. - Additionally calls the setup experiment, if not already called. Args: tolerance_defined -- @@ -477,6 +495,8 @@ cdef class FMUModelME3(FMUModelBase3): if not log_open and self.get_log_level() > 2: self._close_log_file() + self._initialized_fmu = 1 + def enter_initialization_mode( self, tolerance_defined=True, @@ -523,6 +543,8 @@ cdef class FMUModelME3(FMUModelBase3): if status != FMIL3.fmi3_status_ok: raise FMUException("Failed to enter initialization mode") + self._has_entered_init_mode = True + def exit_initialization_mode(self): """ Exit initialization mode by calling the low level FMI function diff --git a/src/pyfmi/fmil3_import.pxd b/src/pyfmi/fmil3_import.pxd index ab420ce0..59fdcad9 100644 --- a/src/pyfmi/fmil3_import.pxd +++ b/src/pyfmi/fmil3_import.pxd @@ -92,9 +92,9 @@ cdef extern from 'fmilib.h': fmi3_status_t fmi3_import_exit_initialization_mode(fmi3_import_t* fmu) fmi3_status_t fmi3_import_enter_event_mode(fmi3_import_t* fmu) fmi3_status_t fmi3_import_enter_continuous_time_mode(fmi3_import_t* fmu) - fmi3_status_t fmi3_import_enter_event_mode(fmi3_import_t* fmu) # misc char* fmi3_import_get_version(fmi3_import_t*) + fmi3_status_t fmi3_import_reset(fmi3_import_t* fmu) # setting fmi3_status_t fmi3_import_set_time(fmi3_import_t *, fmi3_float64_t) diff --git a/tests/test_fmi3.py b/tests/test_fmi3.py index 68ac8292..10168452 100644 --- a/tests/test_fmi3.py +++ b/tests/test_fmi3.py @@ -92,12 +92,12 @@ def test_load_kind_SE(self, ref_fmu): def test_get_model_identifier(self): """Test that model identifier is retrieved as expected.""" - fmu = load_fmu(FMI3_REF_FMU_PATH / "VanDerPol.fmu") # any FMI3 ME would suffice + fmu = load_fmu(FMI3_REF_FMU_PATH / "VanDerPol.fmu") assert fmu.get_identifier() == 'VanDerPol' def test_get_get_version(self): """Test that FMI version is retrieved as expected.""" - fmu = load_fmu(FMI3_REF_FMU_PATH / "VanDerPol.fmu") # any FMI3 ME would suffice + fmu = load_fmu(FMI3_REF_FMU_PATH / "VanDerPol.fmu") assert fmu.get_version() == '3.0' def test_instantiation(self, tmpdir): @@ -107,18 +107,12 @@ def test_instantiation(self, tmpdir): substring_to_find = 'Successfully loaded all the interface functions' with temp_dir_context(tmpdir) as temp_path: - # any FMI3 ME would suffice, log_level set to 5 required by test + # log_level set to 5 required by test fmu = load_fmu(FMI3_REF_FMU_PATH / "VanDerPol.fmu", log_level=5) with open(fmu.get_log_filename(), 'r') as f: contents = f.readlines() - for line in contents: - if substring_to_find in line: - found_substring = True - break - - log_file = ''.join(contents) - assert found_substring, f"Unable to locate substring '{substring_to_find}' in file with contents '{log_file}'" + assert any(substring_to_find in line for line in fmu.get_log()) @pytest.mark.parametrize("ref_fmu", [ FMI3_REF_FMU_PATH / "BouncingBall.fmu", @@ -129,11 +123,45 @@ def test_instantiation(self, tmpdir): FMI3_REF_FMU_PATH / "Stair.fmu", FMI3_REF_FMU_PATH / "VanDerPol.fmu", ]) - def test_load_kind_auto(self, ref_fmu): + def test_initialize(self, ref_fmu): """Test initialize all the ME reference FMUs. """ fmu = load_fmu(ref_fmu) fmu.initialize() # Should simply pass without any exceptions + + @pytest.mark.parametrize("ref_fmu", [ + FMI3_REF_FMU_PATH / "BouncingBall.fmu", + FMI3_REF_FMU_PATH / "Dahlquist.fmu", + FMI3_REF_FMU_PATH / "Resource.fmu", + FMI3_REF_FMU_PATH / "StateSpace.fmu", + FMI3_REF_FMU_PATH / "Feedthrough.fmu", + FMI3_REF_FMU_PATH / "Stair.fmu", + FMI3_REF_FMU_PATH / "VanDerPol.fmu", + ]) + def test_initialize_manually(self, ref_fmu): + """Test initialize all the ME reference FMUs by entering/exiting initialization mode manually. """ + fmu = load_fmu(ref_fmu) + # Should simply pass without any exceptions + fmu.enter_initialization_mode() + fmu.exit_initialization_mode() + + def test_get_default_experiment_start_time(self): + """Test retrieving default experiment start time. """ + fmu = load_fmu(FMI3_REF_FMU_PATH / "VanDerPol.fmu") + assert fmu.get_default_experiment_start_time() == 0.0 + + + def test_get_default_experiment_stop_time(self): + """Test retrieving default experiment stop time. """ + fmu = load_fmu(FMI3_REF_FMU_PATH / "VanDerPol.fmu") + assert fmu.get_default_experiment_stop_time() == 20.0 + + + def test_get_default_experiment_tolerance(self): + """Test retrieving default experiment tolerance. """ + fmu = load_fmu(FMI3_REF_FMU_PATH / "VanDerPol.fmu") + assert fmu.get_default_experiment_tolerance() == 0.0001 + class Test_FMI3ME: """Basic unit tests for FMI3 import directly via the FMUModelME3 class.""" @pytest.mark.parametrize("ref_fmu", [FMI3_REF_FMU_PATH / "VanDerPol.fmu"])