Skip to content

Commit

Permalink
WIP; first go at adding FMI import
Browse files Browse the repository at this point in the history
  • Loading branch information
Peter Meisrimel authored and Peter Meisrimel committed Dec 3, 2024
1 parent 24b9447 commit 0021224
Show file tree
Hide file tree
Showing 9 changed files with 1,441 additions and 11 deletions.
2 changes: 2 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@ include CHANGELOG
include LICENSE
include src/pyfmi/*.pyx
include src/pyfmi/*.pxd
include src/pyfmi/fmi3/*.pyx
include src/pyfmi/fmi3/*.pxd
exclude src/pyfmi/*.dll
10 changes: 9 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,10 @@ def check_extensions():
ext_list[-1].extra_compile_args = ["-O2", "-fno-strict-aliasing"]
ext_list[-1].extra_link_args = extra_link_flags
"""
incl_path = [".", "src", os.path.join("src", "pyfmi")]
incl_path = [".",
"src",
os.path.join("src", "pyfmi"),
os.path.join("src", "pyfmi", "fmi3")]
#FMI PYX
ext_list += cythonize([os.path.join("src", "pyfmi", "fmi.pyx")],
include_path = incl_path,
Expand Down Expand Up @@ -251,6 +254,11 @@ def check_extensions():
ext_list += cythonize([os.path.join("src", "pyfmi", "test_util.pyx")],
include_path = incl_path,
compiler_directives={'language_level' : "3str"})

# FMI3
ext_list += cythonize([os.path.join("src", "pyfmi", "fmi3", "fmi3.pyx")],
include_path = incl_path,
compiler_directives={'language_level' : "3str"})

for i in range(len(ext_list)):

Expand Down
2 changes: 1 addition & 1 deletion src/pyfmi/fmi.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ cdef class ScalarVariable:
"""
cdef object _name
cdef FMIL.fmi1_value_reference_t _value_reference
cdef object _description #A characater pointer but we need an own reference and this is sufficient
cdef object _description #A character pointer but we need an own reference and this is sufficient
cdef FMIL.fmi1_base_type_enu_t _type
cdef FMIL.fmi1_variability_enu_t _variability
cdef FMIL.fmi1_causality_enu_t _causality
Expand Down
86 changes: 78 additions & 8 deletions src/pyfmi/fmi.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ cimport numpy as np
from numpy cimport PyArray_DATA

cimport pyfmi.fmil_import as FMIL
cimport pyfmi.fmi3.fmil3_import as FMIL3

# TODO: This looks a bit stupid
from pyfmi.fmi3.fmi3 cimport FMUModelCS3, FMUModelME3

from pyfmi.common.core import create_temp_dir

Expand Down Expand Up @@ -8783,8 +8787,10 @@ def load_fmu(fmu, log_file_name = "", kind = 'auto',
cdef FMIL.fmi_version_enu_t version
cdef FMIL.fmi1_import_t* fmu_1 = NULL
cdef FMIL.fmi2_import_t* fmu_2 = NULL
cdef FMIL3.fmi3_import_t* fmu_3 = NULL
cdef FMIL.fmi1_fmu_kind_enu_t fmu_1_kind
cdef FMIL.fmi2_fmu_kind_enu_t fmu_2_kind
cdef FMIL3.fmi3_fmu_kind_enu_t fmu_3_kind
cdef list log_data = []

#Variables for deallocation
Expand All @@ -8795,6 +8801,7 @@ def load_fmu(fmu, log_file_name = "", kind = 'auto',
check_fmu_args(allow_unzipped_fmu, fmu, fmu_full_path)

#Check that kind-argument is well-defined
# TODO: FMI3: SE
if not kind.lower() == 'auto':
if (kind.upper() != 'ME' and kind.upper() != 'CS'):
raise FMUException('Input-argument "kind" can only be "ME", "CS" or "auto" (default) and not: ' + kind)
Expand Down Expand Up @@ -8838,7 +8845,7 @@ def load_fmu(fmu, log_file_name = "", kind = 'auto',
_handle_load_fmu_exception(fmu, log_data)
raise InvalidVersionException("The FMU could not be loaded. The FMU version could not be determined. Enable logging for possibly more information.")

if version > 2:
if version >= FMIL.fmi_version_unsupported_enu:
#Delete the context
last_error = FMIL.jm_get_last_error(&callbacks)
FMIL.fmi_import_free_context(context)
Expand Down Expand Up @@ -8873,10 +8880,10 @@ def load_fmu(fmu, log_file_name = "", kind = 'auto',

#Compare fmu_kind with input-specified kind
if fmu_1_kind == FMI_ME and kind.upper() != 'CS':
model=FMUModelME1(fmu, log_file_name,log_level, _unzipped_dir=fmu_temp_dir,
model=FMUModelME1(fmu, log_file_name,log_level, _unzipped_dir = fmu_temp_dir,
allow_unzipped_fmu = allow_unzipped_fmu)
elif (fmu_1_kind == FMI_CS_STANDALONE or fmu_1_kind == FMI_CS_TOOL) and kind.upper() != 'ME':
model=FMUModelCS1(fmu, log_file_name,log_level, _unzipped_dir=fmu_temp_dir,
model=FMUModelCS1(fmu, log_file_name,log_level, _unzipped_dir = fmu_temp_dir,
allow_unzipped_fmu = allow_unzipped_fmu)
else:
FMIL.fmi1_import_free(fmu_1)
Expand Down Expand Up @@ -8922,18 +8929,18 @@ def load_fmu(fmu, log_file_name = "", kind = 'auto',
#FMU kind is known
if kind.lower() == 'auto':
if fmu_2_kind == FMIL.fmi2_fmu_kind_cs:
model = FMUModelCS2(fmu, log_file_name,log_level, _unzipped_dir=fmu_temp_dir,
model = FMUModelCS2(fmu, log_file_name,log_level, _unzipped_dir = fmu_temp_dir,
allow_unzipped_fmu = allow_unzipped_fmu)
elif fmu_2_kind == FMIL.fmi2_fmu_kind_me or fmu_2_kind == FMIL.fmi2_fmu_kind_me_and_cs:
model = FMUModelME2(fmu, log_file_name,log_level, _unzipped_dir=fmu_temp_dir,
model = FMUModelME2(fmu, log_file_name,log_level, _unzipped_dir = fmu_temp_dir,
allow_unzipped_fmu = allow_unzipped_fmu)
elif kind.upper() == 'CS':
if fmu_2_kind == FMIL.fmi2_fmu_kind_cs or fmu_2_kind == FMIL.fmi2_fmu_kind_me_and_cs:
model = FMUModelCS2(fmu, log_file_name,log_level, _unzipped_dir=fmu_temp_dir,
model = FMUModelCS2(fmu, log_file_name,log_level, _unzipped_dir = fmu_temp_dir,
allow_unzipped_fmu = allow_unzipped_fmu)
elif kind.upper() == 'ME':
if fmu_2_kind == FMIL.fmi2_fmu_kind_me or fmu_2_kind == FMIL.fmi2_fmu_kind_me_and_cs:
model = FMUModelME2(fmu, log_file_name,log_level, _unzipped_dir=fmu_temp_dir,
model = FMUModelME2(fmu, log_file_name,log_level, _unzipped_dir = fmu_temp_dir,
allow_unzipped_fmu = allow_unzipped_fmu)

#Could not match FMU kind with input-specified kind
Expand All @@ -8943,7 +8950,65 @@ def load_fmu(fmu, log_file_name = "", kind = 'auto',
if not allow_unzipped_fmu:
FMIL.fmi_import_rmdir(&callbacks, fmu_temp_dir)
_handle_load_fmu_exception(fmu, log_data)
raise FMUException("FMU is a {} and not a {}".format(decode(FMIL.fmi2_fmu_kind_to_string(fmu_2_kind)), decode(kind.upper())))
raise FMUException("FMU is a {} and not a {}".format(decode(FMIL.fmi2_fmu_kind_to_string(fmu_2_kind)), decode(kind.upper())))

elif version == FMIL.fmi_version_3_0_enu:
#Check fmu-kind and compare with input-specified kind
fmu_3 = FMIL3.fmi3_import_parse_xml(context, fmu_temp_dir, NULL)

if fmu_3 is NULL:
#Delete the context
last_error = FMIL.jm_get_last_error(&callbacks)
FMIL.fmi_import_free_context(context)
if not allow_unzipped_fmu:
FMIL.fmi_import_rmdir(&callbacks, fmu_temp_dir)
if callbacks.log_level >= FMIL.jm_log_level_error:
_handle_load_fmu_exception(fmu, log_data)
raise InvalidXMLException("The FMU could not be loaded. The model data from 'modelDescription.xml' within the FMU could not be read. "+decode(last_error))
else:
_handle_load_fmu_exception(fmu, log_data)
raise InvalidXMLException("The FMU could not be loaded. The model data from 'modelDescription.xml' within the FMU could not be read. Enable logging for possible nore information.")

fmu_3_kind = FMIL3.fmi3_import_get_fmu_kind(fmu_3)

#FMU kind is unknown
if fmu_3_kind == FMIL3.fmi3_fmu_kind_unknown:
last_error = FMIL.jm_get_last_error(&callbacks)
FMIL3.fmi3_import_free(fmu_3)
FMIL.fmi_import_free_context(context)
if not allow_unzipped_fmu:
FMIL.fmi_import_rmdir(&callbacks, fmu_temp_dir)
if callbacks.log_level >= FMIL.jm_log_level_error:
_handle_load_fmu_exception(fmu, log_data)
raise FMUException("The FMU kind could not be determined. "+decode(last_error))
else:
_handle_load_fmu_exception(fmu, log_data)
raise FMUException("The FMU kind could not be determined. Enable logging for possibly more information.")

#FMU kind is known
if kind.lower() == 'auto':
if fmu_3_kind & FMIL3.fmi3_fmu_kind_me:
model = FMUModelME3(fmu, log_file_name, log_level, _unzipped_dir = fmu_temp_dir,
allow_unzipped_fmu = allow_unzipped_fmu)
elif fmu_3_kind & FMIL3.fmi3_fmu_kind_cs:
model = FMUModelCS3(fmu, log_file_name, log_level, _unzipped_dir = fmu_temp_dir,
allow_unzipped_fmu = allow_unzipped_fmu)
# TODO: else: SE
elif kind.upper() == 'CS':
model = FMUModelCS3(fmu, log_file_name, log_level, _unzipped_dir = fmu_temp_dir,
allow_unzipped_fmu = allow_unzipped_fmu)
elif kind.upper() == 'ME':
model = FMUModelME3(fmu, log_file_name, log_level, _unzipped_dir = fmu_temp_dir,
allow_unzipped_fmu = allow_unzipped_fmu)

#Could not match FMU kind with input-specified kind
if model is None:
FMIL3.fmi3_import_free(fmu_3)
FMIL.fmi_import_free_context(context)
if not allow_unzipped_fmu:
FMIL.fmi_import_rmdir(&callbacks, fmu_temp_dir)
_handle_load_fmu_exception(fmu, log_data)
raise FMUException("FMU is a {} and not a {}".format(decode(FMIL3.fmi3_fmu_kind_to_string(fmu_3_kind)), decode(kind.upper())))

else:
#This else-statement ensures that the variables "context" and "version" are defined before proceeding
Expand Down Expand Up @@ -8971,6 +9036,11 @@ def load_fmu(fmu, log_file_name = "", kind = 'auto',
FMIL.fmi_import_free_context(context)
#FMIL.fmi_import_rmdir(&callbacks, fmu_temp_dir)

if version == FMIL.fmi_version_3_0_enu:
FMIL3.fmi3_import_free(fmu_3)
FMIL.fmi_import_free_context(context)
#FMIL.fmi_import_rmdir(&callbacks, fmu_temp_dir)

return model


Expand Down
16 changes: 16 additions & 0 deletions src/pyfmi/fmi3/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-

# Copyright (C) 2024 Modelon AB
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, version 3 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
82 changes: 82 additions & 0 deletions src/pyfmi/fmi3/fmi3.pxd
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-

# Copyright (C) 2024 Modelon AB
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, version 3 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

"""
Module containing the FMI3 interface Python wrappers.
"""
import numpy as np
cimport numpy as np

from pyfmi.fmi cimport ModelBase, WorkerClass2
cimport pyfmi.fmil_import as FMIL
cimport pyfmi.fmi3.fmil3_import as FMIL3

cdef class FMUModelBase3(ModelBase):
"""FMI3 Model loaded from a dll."""
# FMIL related variables
cdef FMIL.fmi_import_context_t* _context
# cdef FMIL3.fmi3_callback_functions_t callBackFunctions # TODO: const?
cdef FMIL3.fmi3_import_t* _fmu
cdef FMIL3.fmi3_fmu_kind_enu_t _fmu_kind
cdef FMIL.fmi_version_enu_t _version # TODO: To BASE?
cdef FMIL3.fmi3_instance_environment_t _instance_environment # TODO: What is this meant to do? # TODO: const?
cdef FMIL3.fmi3_log_message_callback_ft _log_message # TODO
# cdef FMIL.jm_string last_error
# cdef FMIL.size_t _nEventIndicators
# cdef FMIL.size_t _nContinuousStates
# cdef FMIL.fmi2_event_info_t _eventInfo

# Internal values
# cdef public float _last_accepted_time, _relative_tolerance
cdef double time
cdef object _fmu_full_path
cdef public object _enable_logging
cdef int _allow_unzipped_fmu # TODO: Move to FMUBase?
cdef int _allocated_dll, _allocated_context, _allocated_xml, _allocated_fmu, _initialized_fmu
cdef object _modelName
# cdef public list _save_real_variables_val
# cdef public list _save_int_variables_val
# cdef public list _save_bool_variables_val
cdef object _t
# cdef public object _pyEventInfo
cdef char* _fmu_temp_dir
# cdef object _states_references
# cdef object _inputs_references
# cdef object _outputs_references
# cdef object _derivatives_references
# cdef object _derivatives_states_dependencies
# cdef object _derivatives_inputs_dependencies
# cdef object _derivatives_states_dependencies_kind
# cdef object _derivatives_inputs_dependencies_kind
# cdef object _outputs_states_dependencies
# cdef object _outputs_inputs_dependencies
# cdef object _outputs_states_dependencies_kind
# cdef object _outputs_inputs_dependencies_kind
# cdef object _A, _B, _C, _D
# cdef public object _group_A, _group_B, _group_C, _group_D
# cdef object _mask_A
# cdef object _A_row_ind, _A_col_ind
cdef public object _has_entered_init_mode
# cdef WorkerClass2 _worker_object

cdef class FMUModelCS3(FMUModelBase3):
"""FMI3 CoSimulation Model loaded from a dll."""
pass

cdef class FMUModelME3(FMUModelBase3):
"""FMI3 ModelExchange Model loaded from a dll."""
pass
Loading

0 comments on commit 0021224

Please sign in to comment.