Skip to content

Commit

Permalink
Added instantiation and test for instantiation
Browse files Browse the repository at this point in the history
  • Loading branch information
modelonrobinandersson committed Feb 18, 2025
1 parent 111307d commit 6ec402d
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 6 deletions.
2 changes: 1 addition & 1 deletion src/pyfmi/fmi3.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ cdef class FMUModelBase3(FMI_BASE.ModelBase):
cdef object _fmu_full_path
cdef public object _enable_logging
cdef int _allow_unzipped_fmu
cdef int _allocated_dll, _allocated_context, _allocated_xml
cdef int _allocated_context, _allocated_dll, _allocated_fmu, _allocated_xml
cdef object _modelName
cdef char* _fmu_temp_dir

Expand Down
70 changes: 66 additions & 4 deletions src/pyfmi/fmi3.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -193,15 +193,15 @@ cdef class FMUModelBase3(FMI_BASE.ModelBase):
raise InvalidBinaryException("The FMU could not be loaded. Error loading the binary. Enable logging for possibly more information.")
self._allocated_dll = 1

# Note that below, values are retrieved from XML (via FMIL) if .dll/.so is not connected
if self._fmu_kind & FMIL3.fmi3_fmu_kind_me:
self._modelId= pyfmi_util.decode(FMIL3.fmi3_import_get_model_identifier_ME(self._fmu))
else:
raise NotImplementedError(f"FMUModelBase3 only supports FMU type 'Model Exchange'")

#Connect the DLL
self._modelName = pyfmi_util.decode(FMIL3.fmi3_import_get_model_name(self._fmu))

# TODO Check status and error handling
# TODO Check status and error handling?
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)
Expand All @@ -214,7 +214,11 @@ cdef class FMUModelBase3(FMI_BASE.ModelBase):
if not isinstance(log_file_name, str):
self._set_log_stream(log_file_name)
for i in range(len(self._log)):
self._log_stream.write("FMIL: module = %s, log level = %d: %s\n"%(self._log[i][0], self._log[i][1], self._log[i][2]))
self._log_stream.write(
"FMIL: module = %s, log level = %d: %s\n" % (
self._log[i][0], self._log[i][1], self._log[i][2]
)
)
else:
fmu_log_name = pyfmi_util.encode((self._modelId + "_log.txt") if log_file_name=="" else log_file_name)
self._fmu_log_name = <char*>FMIL.malloc((FMIL.strlen(fmu_log_name)+1)*sizeof(char))
Expand All @@ -223,7 +227,10 @@ cdef class FMUModelBase3(FMI_BASE.ModelBase):
#Create the log file
with open(self._fmu_log_name,'w') as file:
for i in range(len(self._log)):
file.write("FMIL: module = %s, log level = %d: %s\n"%(self._log[i][0], self._log[i][1], self._log[i][2]))
file.write("FMIL: module = %s, log level = %d: %s\n" % (
self._log[i][0], self._log[i][1], self._log[i][2]
)
)

self._log = []

Expand Down Expand Up @@ -255,6 +262,11 @@ cdef class FMUModelBase3(FMI_BASE.ModelBase):
def _get_fmu_kind(self):
raise FMUException("FMUModelBase3 cannot be used directly, use FMUModelME3.")


def instantiate(self, name: str = 'Model', visible: bool = False) -> None:
raise NotImplementedError


def get_fmil_log_level(self):
"""
Returns::
Expand Down Expand Up @@ -333,6 +345,8 @@ 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)
if _connect_dll:
self.instantiate()

def _get_fmu_kind(self):
if self._fmu_kind & FMIL3.fmi3_fmu_kind_me:
Expand All @@ -341,6 +355,54 @@ cdef class FMUModelME3(FMUModelBase3):
raise InvalidVersionException('The FMU could not be loaded. This class only supports FMI 3.0 for Model Exchange.')


def instantiate(self, name: str = 'Model', visible: bool = False) -> None:
"""
Instantiate the model.
Parameters::
name --
The name of the instance.
Default: 'Model'
visible --
Defines if the simulator application window should be visible or not.
Default: False, not visible.
Calls the respective low-level FMI function: fmi3InstantiateX where X is any of
ModelExchange, CoSimulation or ScheduledExecution.
"""

cdef FMIL3.fmi3_boolean_t log
cdef FMIL3.fmi3_boolean_t vis
cdef FMIL.jm_status_enu_t status

log = self._enable_logging
vis = visible

#if isinstance(self, FMUModelME3):
#
#else:
# raise FMUException('The instance is not curent an instance of an ME-model or a CS-model. Use load_fmu for correct loading.')

name_as_bytes = pyfmi_util.encode(name)
self._log_handler.capi_start_callback(self._max_log_size_msg_sent, self._current_log_size)
status = FMIL3.fmi3_import_instantiate_model_exchange(
self._fmu,
name_as_bytes,
NULL,
vis,
log,
NULL,
FMIL3.fmi3_log_forwarding
)
self._log_handler.capi_end_callback(self._max_log_size_msg_sent, self._current_log_size)

if status != FMIL.jm_status_success:
raise FMUException('Failed to instantiate the model. See the log for possibly more information.')

self._allocated_fmu = 1

cdef void _cleanup_on_load_error(
FMIL3.fmi3_import_t* fmu_3,
FMIL.fmi_import_context_t* context,
Expand Down
11 changes: 10 additions & 1 deletion src/pyfmi/fmil3_import.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ cimport pyfmi.fmil_import as FMIL
from libcpp cimport bool # TODO: Possible issue due to https://github.com/cython/cython/issues/5730 ??


# XXX: Copy more header content from FMI3 as it becomes necessary

cdef extern from 'fmilib.h':
# FMI VARIABLE TYPE DEFINITIONS
ctypedef void* fmi3_instance_environment_t
ctypedef char* fmi3_string_t
ctypedef bool fmi3_boolean_t

# STATUS
cdef enum fmi3_fmu_kind_enu_t:
Expand Down Expand Up @@ -68,6 +68,15 @@ cdef extern from 'fmilib.h':
# FMI SPECIFICATION METHODS (3.0)
# BASIC
int fmi3_import_create_dllfmu(fmi3_import_t*, fmi3_fmu_kind_enu_t, fmi3_instance_environment_t, fmi3_log_message_callback_ft )
FMIL.jm_status_enu_t fmi3_import_instantiate_model_exchange(
fmi3_import_t* fmu,
fmi3_string_t instanceName,
fmi3_string_t resourcePath,
fmi3_boolean_t visible,
fmi3_boolean_t loggingOn,
fmi3_instance_environment_t instanceEnvironment,
fmi3_log_message_callback_ft logMessage
)
# modes

# misc
Expand Down
28 changes: 28 additions & 0 deletions tests/test_fmi3.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@
FMI3_REF_FMU_PATH = Path(this_dir) / 'files' / 'reference_fmus' / '3.0'


import contextlib
from pathlib import Path

@contextlib.contextmanager
def temp_dir_context(tmpdir):
"""Provides a temporary directory as a context."""
yield Path(tmpdir)

class TestFMI3LoadFMU:
"""Basic unit tests for FMI3 loading via 'load_fmu'."""
@pytest.mark.parametrize("ref_fmu", [
Expand Down Expand Up @@ -91,6 +99,26 @@ def test_get_get_version(self):
fmu = load_fmu(FMI3_REF_FMU_PATH / "VanDerPol.fmu") # any FMI3 ME would suffice
assert fmu.get_version() == '3.0'

def test_instantiation(self, tmpdir):
""" Test that instantion works by verifying the output in the log.
"""
found_substring = False
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
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}'"

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"])
Expand Down

0 comments on commit 6ec402d

Please sign in to comment.