diff --git a/conanfile.py b/conanfile.py index 57f2071..060fa30 100644 --- a/conanfile.py +++ b/conanfile.py @@ -9,6 +9,10 @@ class LibCosimpyConanDependency(ConanFile): "libcosim/*:proxyfmu": True, } + def configure(self): + self.options["*"].shared = False + self.options["libcosimc/*"].shared = True + def generate(self): for dep in self.dependencies.values(): for dep_bin_dir in dep.cpp_info.bindirs: diff --git a/src/libcosimpy/CosimAlgorithm.py b/src/libcosimpy/CosimAlgorithm.py index adb9842..6a6d80a 100644 --- a/src/libcosimpy/CosimAlgorithm.py +++ b/src/libcosimpy/CosimAlgorithm.py @@ -11,8 +11,7 @@ from dataclasses import dataclass from typing import Optional -from . import CosimLibrary -from ._internal import wrap_function, get_last_error_message +from ._internal import wrap_function, get_last_error_message, libcosimc if typing.TYPE_CHECKING: from ctypes import _Pointer # pyright: ignore[reportPrivateUsage] @@ -69,7 +68,7 @@ def __init__( ) self.__ecco_add_power_bond = wrap_function( - lib=CosimLibrary.lib, + lib=libcosimc(), funcname="cosim_ecco_add_power_bond", argtypes=[POINTER(CosimAlgorithm), c_int, c_uint32, c_uint32, c_int, c_uint32, c_uint32], restype=c_int, @@ -78,7 +77,7 @@ def __init__( @classmethod def create_ecco_algorithm(cls, param: EccoParams) -> CosimAlgorithm: ecco_algorithm_create = wrap_function( - lib=CosimLibrary.lib, + lib=libcosimc(), funcname="cosim_ecco_algorithm_create", argtypes=[ c_double, @@ -156,7 +155,7 @@ def __del__(self): # Release object in C when object is removed (if pointer exists) if self.__ptr is not None: algorithm_destroy = wrap_function( - lib=CosimLibrary.lib, + lib=libcosimc(), funcname="cosim_algorithm_destroy", argtypes=[POINTER(CosimAlgorithm)], restype=c_int, diff --git a/src/libcosimpy/CosimExecution.py b/src/libcosimpy/CosimExecution.py index 9f01637..ef23f6e 100644 --- a/src/libcosimpy/CosimExecution.py +++ b/src/libcosimpy/CosimExecution.py @@ -13,8 +13,8 @@ ) from typing import Optional -from . import CosimConstants, CosimEnums, CosimLibrary, CosimManipulator, CosimObserver, CosimSlave -from ._internal import wrap_function +from . import CosimConstants, CosimEnums, CosimManipulator, CosimObserver, CosimSlave +from ._internal import wrap_function, libcosimc, get_last_error_message from .CosimAlgorithm import CosimAlgorithm if typing.TYPE_CHECKING: @@ -57,55 +57,55 @@ def __init__(self, create_key: object = None, execution_ptr: Optional[CosimExecu self.__execution_status_ptr = pointer(self.execution_status) self.__multiple_steps = wrap_function( - lib=CosimLibrary.lib, + lib=libcosimc(), funcname="cosim_execution_step", argtypes=[POINTER(CosimExecution), c_int64], restype=c_int, ) self.__add_local_slave = wrap_function( - lib=CosimLibrary.lib, + lib=libcosimc(), funcname="cosim_execution_add_slave", argtypes=[POINTER(CosimExecution), POINTER(CosimSlave.CosimLocalSlave)], restype=c_int, ) self.__simulate_until = wrap_function( - lib=CosimLibrary.lib, + lib=libcosimc(), funcname="cosim_execution_simulate_until", argtypes=[POINTER(CosimExecution), c_int64], restype=c_int, ) self.__stop = wrap_function( - lib=CosimLibrary.lib, + lib=libcosimc(), funcname="cosim_execution_stop", argtypes=[POINTER(CosimExecution)], restype=c_int, ) self.__enable_real_time_simulation = wrap_function( - lib=CosimLibrary.lib, + lib=libcosimc(), funcname="cosim_execution_enable_real_time_simulation", argtypes=[POINTER(CosimExecution)], restype=c_int, ) self.__disable_real_time_simulation = wrap_function( - lib=CosimLibrary.lib, + lib=libcosimc(), funcname="cosim_execution_disable_real_time_simulation", argtypes=[POINTER(CosimExecution)], restype=c_int, ) self.__real_time_factor_target = wrap_function( - lib=CosimLibrary.lib, + lib=libcosimc(), funcname="cosim_execution_set_real_time_factor_target", argtypes=[POINTER(CosimExecution), c_double], restype=c_int, ) self.__steps_to_monitor = wrap_function( - lib=CosimLibrary.lib, + lib=libcosimc(), funcname="cosim_execution_set_steps_to_monitor", argtypes=[POINTER(CosimExecution), c_int], restype=c_int, ) self.__add_manipulator = wrap_function( - lib=CosimLibrary.lib, + lib=libcosimc(), funcname="cosim_execution_add_manipulator", argtypes=[ POINTER(CosimExecution), @@ -114,31 +114,31 @@ def __init__(self, create_key: object = None, execution_ptr: Optional[CosimExecu restype=c_int, ) self.__add_observer = wrap_function( - lib=CosimLibrary.lib, + lib=libcosimc(), funcname="cosim_execution_add_observer", argtypes=[POINTER(CosimExecution), POINTER(CosimObserver.CosimObserver)], restype=c_int, ) self.__status = wrap_function( - lib=CosimLibrary.lib, + lib=libcosimc(), funcname="cosim_execution_get_status", argtypes=[POINTER(CosimExecution), POINTER(CosimExecutionStatus)], restype=c_int, ) self.__slave_num_variables = wrap_function( - lib=CosimLibrary.lib, + lib=libcosimc(), funcname="cosim_slave_get_num_variables", argtypes=[POINTER(CosimExecution), c_int], restype=c_int, ) self.__num_modified_variables = wrap_function( - lib=CosimLibrary.lib, + lib=libcosimc(), funcname="cosim_get_num_modified_variables", argtypes=[POINTER(CosimExecution)], restype=c_int, ) self.__load_scenario = wrap_function( - lib=CosimLibrary.lib, + lib=libcosimc(), funcname="cosim_execution_load_scenario", argtypes=[ POINTER(CosimExecution), @@ -148,55 +148,55 @@ def __init__(self, create_key: object = None, execution_ptr: Optional[CosimExecu restype=c_int, ) self.__real_initial = wrap_function( - lib=CosimLibrary.lib, + lib=libcosimc(), funcname="cosim_execution_set_real_initial_value", argtypes=[POINTER(CosimExecution), c_int, c_uint32, c_double], restype=c_int, ) self.__integer_initial = wrap_function( - lib=CosimLibrary.lib, + lib=libcosimc(), funcname="cosim_execution_set_integer_initial_value", argtypes=[POINTER(CosimExecution), c_int, c_uint32, c_int], restype=c_int, ) self.__boolean_initial = wrap_function( - lib=CosimLibrary.lib, + lib=libcosimc(), funcname="cosim_execution_set_boolean_initial_value", argtypes=[POINTER(CosimExecution), c_int, c_uint32, c_bool], restype=c_int, ) self.__string_initial = wrap_function( - lib=CosimLibrary.lib, + lib=libcosimc(), funcname="cosim_execution_set_string_initial_value", argtypes=[POINTER(CosimExecution), c_int, c_uint32, c_char_p], restype=c_int, ) self.__connect_real_variables = wrap_function( - lib=CosimLibrary.lib, + lib=libcosimc(), funcname="cosim_execution_connect_real_variables", argtypes=[POINTER(CosimExecution), c_int, c_uint32, c_int, c_uint32], restype=c_int, ) self.__connect_int_variables = wrap_function( - lib=CosimLibrary.lib, + lib=libcosimc(), funcname="cosim_execution_connect_integer_variables", argtypes=[POINTER(CosimExecution), c_int, c_uint32, c_int, c_uint32], restype=c_int, ) self.__connect_string_variables = wrap_function( - lib=CosimLibrary.lib, + lib=libcosimc(), funcname="cosim_execution_connect_string_variables", argtypes=[POINTER(CosimExecution), c_int, c_uint32, c_int, c_uint32], restype=c_int, ) self.__connect_boolean_variables = wrap_function( - lib=CosimLibrary.lib, + lib=libcosimc(), funcname="cosim_execution_connect_boolean_variables", argtypes=[POINTER(CosimExecution), c_int, c_uint32, c_int, c_uint32], restype=c_int, @@ -210,7 +210,7 @@ def from_algorithm(cls, algorithm: CosimAlgorithm): :return: CosimExecution object """ execution_create = wrap_function( - lib=CosimLibrary.lib, + lib=libcosimc(), funcname="cosim_execution_create_with_algorithm", argtypes=[c_int64, POINTER(CosimAlgorithm)], restype=POINTER(CosimExecution), @@ -243,7 +243,7 @@ def from_step_size(cls, step_size: int | float): assert step_size > 0, "Step size must be a positive and non-zero integer" execution_create = wrap_function( - lib=CosimLibrary.lib, + lib=libcosimc(), funcname="cosim_execution_create", argtypes=[c_int64, c_int64], restype=POINTER(CosimExecution), @@ -260,7 +260,7 @@ def from_osp_config_file(cls, osp_path: str): :return: CosimExecution object """ osp_execution_create = wrap_function( - lib=CosimLibrary.lib, + lib=libcosimc(), funcname="cosim_osp_config_execution_create", argtypes=[c_char_p, c_bool, c_int64], restype=POINTER(CosimExecution), @@ -272,7 +272,7 @@ def from_osp_config_file(cls, osp_path: str): execution_ptr = osp_execution_create(encoded_osp_path, False, 0) - assert execution_ptr, "Unable to create execution from path. Please check if path is correct." + assert execution_ptr, f"Unable to create execution from path: {get_last_error_message()}" return cls(cls.__create_key, execution_ptr) @@ -288,7 +288,7 @@ def from_ssp_file(cls, ssp_path: str, step_size: Optional[int | float] = None): if step_size is None: # Create simulation without defined step size ssp_execution_create = wrap_function( - lib=CosimLibrary.lib, + lib=libcosimc(), funcname="cosim_ssp_execution_create", argtypes=[c_char_p, c_bool, c_int64], restype=POINTER(CosimExecution), @@ -309,7 +309,7 @@ def from_ssp_file(cls, ssp_path: str, step_size: Optional[int | float] = None): # Create simulation with defined step size ssp_fixed_step_execution_create = wrap_function( - lib=CosimLibrary.lib, + lib=libcosimc(), funcname="cosim_ssp_fixed_step_execution_create", argtypes=[c_char_p, c_bool, c_int64, c_int64], restype=POINTER(CosimExecution), @@ -327,7 +327,7 @@ def num_slaves(self): :return: int Number of currently connected slaves """ execution_get_num_slaves = wrap_function( - lib=CosimLibrary.lib, + lib=libcosimc(), funcname="cosim_execution_get_num_slaves", argtypes=[POINTER(CosimExecution)], restype=c_int, @@ -341,7 +341,7 @@ def start(self): :return: bool Successful start of execution """ execution_start = wrap_function( - lib=CosimLibrary.lib, + lib=libcosimc(), funcname="cosim_execution_start", argtypes=[POINTER(CosimExecution)], restype=c_int, @@ -468,7 +468,7 @@ def slave_infos(self): slave_count = self.num_slaves() slave_infos_list = (CosimSlave.CosimSlaveInfo * slave_count)() slave_infos = wrap_function( - lib=CosimLibrary.lib, + lib=libcosimc(), funcname="cosim_execution_get_slave_infos", argtypes=[ POINTER(CosimExecution), @@ -521,7 +521,7 @@ def slave_variables(self, slave_index: int): slave_variables_count = self.num_slave_variables(slave_index) slave_variables_list = (CosimSlave.CosimSlaveVariableDescription * slave_variables_count)() slave_variables = wrap_function( - lib=CosimLibrary.lib, + lib=libcosimc(), funcname="cosim_slave_get_variables", argtypes=[ POINTER(CosimExecution), @@ -675,7 +675,7 @@ def __del__(self): # Release object in C when object is removed (if pointer exists) if self.__ptr is not None: execution_destroy = wrap_function( - lib=CosimLibrary.lib, + lib=libcosimc(), funcname="cosim_execution_destroy", argtypes=[POINTER(CosimExecution)], restype=c_int, diff --git a/src/libcosimpy/CosimLibrary.py b/src/libcosimpy/CosimLibrary.py deleted file mode 100644 index d6ba2ac..0000000 --- a/src/libcosimpy/CosimLibrary.py +++ /dev/null @@ -1,17 +0,0 @@ -import warnings -from ctypes import cdll -import os - -# Path of libcosimc .dll (Windows) or .so (Linux) files -lib_path = os.path.dirname(os.path.realpath(__file__)) -try: - if os.name == "nt": - lib = cdll.LoadLibrary(f"{lib_path}/libcosimc/cosimc.dll") - else: - lib = cdll.LoadLibrary(f"{lib_path}/libcosimc/libcosimc.so") -except FileNotFoundError: - warnings.warn("Unable to load cosimc library, searching in the default search paths..") - if os.name == "nt": - lib = cdll.LoadLibrary("cosimc.dll") - else: - lib = cdll.LoadLibrary("libcosimc.so") diff --git a/src/libcosimpy/CosimLogging.py b/src/libcosimpy/CosimLogging.py index 40a873d..8be5f8e 100644 --- a/src/libcosimpy/CosimLogging.py +++ b/src/libcosimpy/CosimLogging.py @@ -1,6 +1,5 @@ from ctypes import c_int -from ._internal import wrap_function -from . import CosimLibrary +from ._internal import wrap_function, libcosimc from enum import Enum @@ -25,7 +24,7 @@ def log_output_level(log_level: CosimLogLevel): :param CosimLogLevel log_level: """ log_output_level_set = wrap_function( - lib=CosimLibrary.lib, + lib=libcosimc(), funcname="cosim_log_set_output_level", argtypes=[c_int], restype=None, diff --git a/src/libcosimpy/CosimManipulator.py b/src/libcosimpy/CosimManipulator.py index d98277a..e61dd8e 100644 --- a/src/libcosimpy/CosimManipulator.py +++ b/src/libcosimpy/CosimManipulator.py @@ -11,9 +11,9 @@ ) from typing import TYPE_CHECKING, Optional, Any, Sequence -from . import CosimConstants, CosimLibrary +from . import CosimConstants from .CosimEnums import CosimVariableType -from ._internal import wrap_function +from ._internal import wrap_function, libcosimc if TYPE_CHECKING: from ctypes import _Pointer # pyright: ignore[reportPrivateUsage] @@ -49,7 +49,7 @@ def __init__(self, create_key: object = None, manipulator_ptr: Optional[CosimMan # Store the pointer used by the C library self.__ptr = manipulator_ptr self.__abort = wrap_function( - lib=CosimLibrary.lib, + lib=libcosimc(), funcname="cosim_scenario_abort", argtypes=[POINTER(CosimManipulator)], restype=c_int, @@ -63,7 +63,7 @@ def create_override(cls): :return: CosimManipulator object from constructor """ override_manipulator_create = wrap_function( - lib=CosimLibrary.lib, + lib=libcosimc(), funcname="cosim_override_manipulator_create", argtypes=[], restype=POINTER(CosimManipulator), @@ -79,7 +79,7 @@ def create_scenario_manager(cls): :return CosimManipulator object from constructor """ scenario_manager_create = wrap_function( - lib=CosimLibrary.lib, + lib=libcosimc(), funcname="cosim_scenario_manager_create", argtypes=[], restype=POINTER(CosimManipulator), @@ -126,7 +126,7 @@ def __slave_values( value_array = (c_type * variable_count)(*values) slave_values = wrap_function( - lib=CosimLibrary.lib, + lib=libcosimc(), funcname=funcname, argtypes=[ POINTER(CosimManipulator), @@ -220,7 +220,7 @@ def reset_variables(self, slave_index: int, variable_type: CosimVariableType, va variable_array = (c_uint32 * variable_count)(*variable_references) slave_reset = wrap_function( - lib=CosimLibrary.lib, + lib=libcosimc(), funcname="cosim_manipulator_slave_reset", argtypes=[ POINTER(CosimManipulator), @@ -256,7 +256,7 @@ def __del__(self): """ if self.__ptr is not None: manipulator_destroy = wrap_function( - lib=CosimLibrary.lib, + lib=libcosimc(), funcname="cosim_manipulator_destroy", argtypes=[POINTER(CosimManipulator)], restype=c_int64, diff --git a/src/libcosimpy/CosimObserver.py b/src/libcosimpy/CosimObserver.py index 779a855..46c6e11 100644 --- a/src/libcosimpy/CosimObserver.py +++ b/src/libcosimpy/CosimObserver.py @@ -13,9 +13,8 @@ ) from typing import Optional, TYPE_CHECKING, Any -from . import CosimLibrary from .CosimEnums import CosimVariableType -from ._internal import wrap_function +from ._internal import wrap_function, libcosimc from . import CosimConstants @@ -53,19 +52,19 @@ def __init__(self, create_key: object = None, observer_ptr: Optional[CosimObserv # Store the pointer used by the C library self.__ptr = observer_ptr self.__step_numbers = wrap_function( - lib=CosimLibrary.lib, + lib=libcosimc(), funcname="cosim_observer_get_step_numbers", argtypes=[POINTER(CosimObserver), c_longlong, c_longlong, c_uint32], restype=c_int, ) self.__start_observing = wrap_function( - lib=CosimLibrary.lib, + lib=libcosimc(), funcname="cosim_observer_start_observing", argtypes=[POINTER(CosimObserver), c_int, c_int, c_uint32], restype=c_int, ) self.__stop_observing = wrap_function( - lib=CosimLibrary.lib, + lib=libcosimc(), funcname="cosim_observer_stop_observing", argtypes=[POINTER(CosimObserver), c_int, c_int, c_uint32], restype=c_int, @@ -79,7 +78,7 @@ def create_last_value(cls): :return: CosimObserver object from constructor """ last_value_observer_create = wrap_function( - lib=CosimLibrary.lib, + lib=libcosimc(), funcname="cosim_last_value_observer_create", argtypes=[], restype=POINTER(CosimObserver), @@ -96,7 +95,7 @@ def create_to_dir(cls, log_dir: str): :return: CosimObserver object from constructor """ file_observer_create = wrap_function( - lib=CosimLibrary.lib, + lib=libcosimc(), funcname="cosim_file_observer_create", argtypes=[c_char_p], restype=POINTER(CosimObserver), @@ -113,7 +112,7 @@ def from_cfg(cls, cfg_path, log_dir): :param str cfg_path: Directory of the LogConfig.xml file :return: CosimObserver object from constructor - file_observer_create = wrap_function(lib=CosimLibrary.lib, funcname='cosim_file_observer_create_from_cfg', + file_observer_create = wrap_function(lib=libcosimc(), funcname='cosim_file_observer_create_from_cfg', argtypes=[c_char_p, c_char_p], restype=POINTER(CosimObserver)) @@ -130,7 +129,7 @@ def create_time_series(cls, buffer_size: Optional[int] = None): """ if buffer_size is None: time_series_create = wrap_function( - lib=CosimLibrary.lib, + lib=libcosimc(), funcname="cosim_time_series_observer_create", argtypes=[], restype=POINTER(CosimObserver), @@ -139,7 +138,7 @@ def create_time_series(cls, buffer_size: Optional[int] = None): return cls(cls.__create_key, observer_ptr) else: buffered_time_series_create = wrap_function( - lib=CosimLibrary.lib, + lib=libcosimc(), funcname="cosim_buffered_time_series_observer_create", argtypes=[c_uint64], restype=POINTER(CosimObserver), @@ -192,7 +191,7 @@ def __time_series_samples( samples_array = (c_type * sample_count)() real_samples = wrap_function( - lib=CosimLibrary.lib, + lib=libcosimc(), funcname=funcname, argtypes=[ POINTER(CosimObserver), @@ -289,7 +288,7 @@ def __last_values(self, slave_index: int, variable_references: list[int], c_type value_array = (c_type * variable_count)() real_values = wrap_function( - lib=CosimLibrary.lib, + lib=libcosimc(), funcname=funcname, argtypes=[ POINTER(CosimObserver), @@ -383,7 +382,7 @@ def __del__(self): """ if self.__ptr is not None: observer_destroy = wrap_function( - lib=CosimLibrary.lib, + lib=libcosimc(), funcname="cosim_observer_destroy", argtypes=[POINTER(CosimObserver)], restype=c_int, diff --git a/src/libcosimpy/CosimSlave.py b/src/libcosimpy/CosimSlave.py index 84cb63e..c32e019 100644 --- a/src/libcosimpy/CosimSlave.py +++ b/src/libcosimpy/CosimSlave.py @@ -1,6 +1,5 @@ from ctypes import c_char, c_char_p, POINTER, c_int, Structure, c_uint32 -from ._internal import wrap_function -from . import CosimLibrary +from ._internal import wrap_function, libcosimc from . import CosimConstants from . import CosimEnums @@ -46,7 +45,7 @@ class CosimLocalSlave(Structure): def __init__(self, fmu_path: str, instance_name: str): local_slave_create = wrap_function( - lib=CosimLibrary.lib, + lib=libcosimc(), funcname="cosim_local_slave_create", argtypes=[c_char_p, c_char_p], restype=POINTER(CosimLocalSlave), @@ -66,7 +65,7 @@ def __del__(self): Releases C objects when CosimObserver is deleted in python """ local_slave_destroy = wrap_function( - lib=CosimLibrary.lib, + lib=libcosimc(), funcname="cosim_local_slave_destroy", argtypes=[POINTER(CosimLocalSlave)], restype=c_int, diff --git a/src/libcosimpy/_internal.py b/src/libcosimpy/_internal.py index 9e45483..3ba20a9 100644 --- a/src/libcosimpy/_internal.py +++ b/src/libcosimpy/_internal.py @@ -1,8 +1,11 @@ import ctypes from typing import Any +import warnings +from ctypes import cdll +import os -from libcosimpy import CosimLibrary +__lib = None def wrap_function(lib: ctypes.CDLL, funcname: str, restype: Any, argtypes: list[Any]): @@ -13,6 +16,25 @@ def wrap_function(lib: ctypes.CDLL, funcname: str, restype: Any, argtypes: list[ return func +def libcosimc(): + # Path of libcosimc .dll (Windows) or .so (Linux) files + global __lib + if __lib is None: + lib_path = os.path.dirname(os.path.realpath(__file__)) + try: + if os.name == "nt": + __lib = cdll.LoadLibrary(f"{lib_path}/libcosimc/cosimc.dll") + else: + __lib = cdll.LoadLibrary(f"{lib_path}/libcosimc/libcosimc.so") + except FileNotFoundError: + warnings.warn("Unable to load cosimc library, searching in the default search paths..") + if os.name == "nt": + __lib = cdll.LoadLibrary("cosimc.dll") + else: + __lib = cdll.LoadLibrary("libcosimc.so") + return __lib + + class CTypeMeta(type(ctypes.Structure)): def __new__(cls, name: str, bases: tuple[type, ...], namespace: dict[str, Any]): annotations = namespace.get("__annotations__", {}) @@ -22,7 +44,7 @@ def __new__(cls, name: str, bases: tuple[type, ...], namespace: dict[str, Any]): def get_last_error_message() -> str: cosim_last_error_message = wrap_function( - lib=CosimLibrary.lib, + lib=libcosimc(), funcname="cosim_last_error_message", argtypes=[], restype=ctypes.c_char_p, diff --git a/tests/test_load_library.py b/tests/test_load_library.py index 05a023d..0578542 100644 --- a/tests/test_load_library.py +++ b/tests/test_load_library.py @@ -1,5 +1,5 @@ -import libcosimpy.CosimLibrary +from libcosimpy._internal import libcosimc def test_load_libray(): - assert libcosimpy.CosimLibrary is not None + assert libcosimc() is not None