From 8cbbfe4802d68be72b8deea6a63e9b835f87a61e Mon Sep 17 00:00:00 2001 From: caiming100 <365215504@qq.com> Date: Tue, 10 Feb 2026 10:00:15 +0800 Subject: [PATCH 1/3] Add type definitions and enumeration support Key changes: - Added new `type.py` module with SimpleType, Real, Integer, Item and Enumeration classes for FMI type definitions - Enhanced variable classes (Real, Integer, Boolean, String) with extended attributes including declared_type, quantity, units, min/max bounds, etc. - Added Enumeration variable type support in variables.py - Modified Fmi2Slave to: - Track registered types via `self.types` list - Generate TypeDefinitions XML section when types are registered - Handle Enumeration variables using integer getters - Add abstract decorators for enter_initialization_mode and exit_initialization_mode methods - Add register_type() method for registering custom types - Updated test example to demonstrate enumeration usage with Enumeration type and Enumeration variables The changes enable full FMI 2.0 type system support including custom types and enumerations, improving FMU compliance and usability. --- pythonfmu/__init__.py | 3 +- pythonfmu/fmi2slave.py | 23 ++- pythonfmu/tests/slaves/pythonslave.py | 17 +- pythonfmu/type.py | 231 ++++++++++++++++++++++++ pythonfmu/variables.py | 242 ++++++++++++++++++++++++-- 5 files changed, 495 insertions(+), 21 deletions(-) create mode 100644 pythonfmu/type.py diff --git a/pythonfmu/__init__.py b/pythonfmu/__init__.py index 010886db..a5a976f0 100644 --- a/pythonfmu/__init__.py +++ b/pythonfmu/__init__.py @@ -2,5 +2,6 @@ from .builder import FmuBuilder from .enums import Fmi2Causality, Fmi2Initial, Fmi2Variability from .fmi2slave import Fmi2Slave -from .variables import Boolean, Integer, Real, String +from .variables import Boolean, Integer, Real, String, Enumeration +from .type import Real as TypeReal, Integer as TypeInteger, Item, Enumeration as TypeEnum from .default_experiment import DefaultExperiment diff --git a/pythonfmu/fmi2slave.py b/pythonfmu/fmi2slave.py index 15991422..a7fba3e4 100644 --- a/pythonfmu/fmi2slave.py +++ b/pythonfmu/fmi2slave.py @@ -12,7 +12,8 @@ from .default_experiment import DefaultExperiment from ._version import __version__ as VERSION from .enums import Fmi2Type, Fmi2Status, Fmi2Causality, Fmi2Initial, Fmi2Variability -from .variables import Boolean, Integer, Real, ScalarVariable, String +from .variables import Boolean, Integer, Real, ScalarVariable, String, Enumeration +from .type import SimpleType, Real as TypeReal, Integer as TypeInteger, Item, Enumeration as TypeEnum ModelOptions = namedtuple("ModelOptions", ["name", "value", "cli"]) @@ -40,6 +41,7 @@ class Fmi2Slave(ABC): def __init__(self, **kwargs): self.vars = OrderedDict() + self.types = [] self.instance_name = kwargs["instance_name"] self.resources = kwargs.get("resources", None) self.visible = kwargs.get("visible", False) @@ -92,6 +94,11 @@ def to_xml(self, model_options: Dict[str, str] = dict()) -> Element: SubElement(root, "CoSimulation", attrib=options) + if len(self.types) > 0: + types = SubElement(root, "TypeDefinitions") + for type_ in self.types: + types.append(type_.to_xml()) + if len(self.log_categories) > 0: categories = SubElement(root, "LogCategories") for category, description in self.log_categories.items(): @@ -145,6 +152,8 @@ def __apply_start_value(self, var: ScalarVariable): refs = self.get_boolean(vrs) elif isinstance(var, String): refs = self.get_string(vrs) + elif isinstance(var, Enumeration): + refs = self.get_integer(vrs) else: raise Exception(f"Unsupported type!") @@ -172,12 +181,22 @@ def register_variable(self, var: ScalarVariable, nested: bool = True): if var.setter is None and hasattr(owner, var.local_name) and var.variability != Fmi2Variability.constant: var.setter = lambda v: setattr(owner, var.local_name, v) + def register_type(self, type: SimpleType): + """Register a type as FMU interface. + + Args: + type (SimpleType): The type to be registered + """ + self.types.append(type) + def setup_experiment(self, start_time: float, stop_time: Optional[float], tolerance: Optional[float]): pass + @abstractmethod def enter_initialization_mode(self): pass + @abstractmethod def exit_initialization_mode(self): pass @@ -192,7 +211,7 @@ def get_integer(self, vrs: List[int]) -> List[int]: refs = list() for vr in vrs: var = self.vars[vr] - if isinstance(var, Integer): + if isinstance(var, Integer) or isinstance(var, Enumeration): refs.append(int(var.getter())) else: raise TypeError( diff --git a/pythonfmu/tests/slaves/pythonslave.py b/pythonfmu/tests/slaves/pythonslave.py index 8014d0a3..8b4379c1 100644 --- a/pythonfmu/tests/slaves/pythonslave.py +++ b/pythonfmu/tests/slaves/pythonslave.py @@ -1,4 +1,4 @@ -from pythonfmu.fmi2slave import Fmi2Slave, Fmi2Causality, Fmi2Variability, Integer, Real, Boolean, String +from pythonfmu.fmi2slave import Fmi2Slave, Fmi2Causality, Fmi2Variability, Integer, Real, Boolean, String, Enumeration, TypeEnum, Item class Container: @@ -19,15 +19,26 @@ def __init__(self, **kwargs): self.booleanVariable = True self.stringVariable = "Hello World!" self.realIn = 2. / 3. + self.realIn2 = 2. / 3. self.booleanParameter = False self.stringParameter = "dog" + self.enumParameter = 0 + + self.register_type(TypeEnum("enumType", item=[Item("red", 0), Item("green", 1), Item("blue", 2)])) + self.register_variable( Integer("intParam", causality=Fmi2Causality.parameter, variability=Fmi2Variability.tunable)) self.register_variable(Real("realIn", causality=Fmi2Causality.input)) + self.register_variable(Real("realIn2", causality=Fmi2Causality.input, start=30.0, min_=-100.0, max_=100.0, + unit="m", display_unit="ft", relative_quantity=True, nominal=10.0, unbounded=False, + derivative=1, reinit=True)) self.register_variable( Boolean("booleanParameter", causality=Fmi2Causality.parameter, variability=Fmi2Variability.tunable)) self.register_variable( String("stringParameter", causality=Fmi2Causality.parameter, variability=Fmi2Variability.tunable)) + self.register_variable( + Enumeration("enumParameter", causality=Fmi2Causality.parameter, variability=Fmi2Variability.tunable, + declared_type="enumType")) self.register_variable(Integer("intOut", causality=Fmi2Causality.output)) self.register_variable(Real("realOut", causality=Fmi2Causality.output)) @@ -47,3 +58,7 @@ def __init__(self, **kwargs): def do_step(self, current_time, step_size): self.realOut = current_time + step_size return True + + def enter_initialization_mode(self): + self.intOut = self.intParam + self.container.someReal = self.intParam diff --git a/pythonfmu/type.py b/pythonfmu/type.py new file mode 100644 index 00000000..9d5ba6e5 --- /dev/null +++ b/pythonfmu/type.py @@ -0,0 +1,231 @@ +"""Classes describing interface type.""" +from abc import ABC +from typing import Any, Optional +from xml.etree.ElementTree import Element, SubElement + + +class SimpleType(ABC): + """Abstract FMI simple type definition. + + Args: + name (str): Type name + description (str, optional): Type description + """ + + def __init__( + self, + name: str, + description: Optional[str] = None, + getter: Any = None, + setter: Any = None + ): + self.getter = getter + self.setter = setter + self.local_name = name.split(".")[-1] + self.__attrs = { + "name": name, + "valueReference": None, + "description": description, # Only for ME + } + + @property + def name(self) -> str: + """str: Type name""" + return self.__attrs["name"] + + @property + def description(self) -> Optional[str]: + """str or None: Type description - None if not set""" + return self.__attrs["description"] + + def to_xml(self) -> Element: + """Convert the type to XML node. + + Returns + xml.etree.ElementTree.Element: XML node + """ + attrib = dict() + for key, value in self.__attrs.items(): + if value is not None: + attrib[key] = str(value) + return Element("TypeDefinitions", attrib) + + def __repr__(self) -> str: + return f"{self.__class__.__name__}(name={self.name})" + + +class Real(SimpleType): + def __init__(self, name: str, quantity: Optional[str] = None, unit: Optional[str] = None, + display_unit: Optional[str] = None, relative_quantity: Optional[bool] = None, + min_: Optional[float] = None, max_: Optional[float] = None, nominal: Optional[float] = None, + unbounded: Optional[bool] = None, **kwargs): + super().__init__(name, **kwargs) + self.__attrs = {"quantity": quantity, "unit": unit, "displayUnit": display_unit, + "relativeQuantity": relative_quantity, "min": min_, "max": max_, "nominal": nominal, + "unbounded": unbounded} + + @property + def quantity(self) -> Optional[str]: + return self.__attrs["quantity"] + + @property + def unit(self) -> Optional[str]: + return self.__attrs["unit"] + + @property + def display_unit(self) -> Optional[str]: + return self.__attrs["displayUnit"] + + @property + def relative_quantity(self) -> Optional[bool]: + return self.__attrs["relativeQuantity"] + + @property + def min(self) -> Optional[float]: + return self.__attrs["min"] + + @property + def max(self) -> Optional[float]: + return self.__attrs["max"] + + @property + def nominal(self) -> Optional[float]: + return self.__attrs["nominal"] + + @property + def unbounded(self) -> Optional[bool]: + return self.__attrs["unbounded"] + + @quantity.setter + def quantity(self, value: str): + self.__attrs["quantity"] = value + + @unit.setter + def unit(self, value: str): + self.__attrs["unit"] = value + + @display_unit.setter + def display_unit(self, value: str): + self.__attrs["displayUnit"] = value + + @relative_quantity.setter + def relative_quantity(self, value: bool): + self.__attrs["relativeQuantity"] = value + + @min.setter + def min(self, value: float): + self.__attrs["min"] = value + + @max.setter + def max(self, value: float): + self.__attrs["max"] = value + + @nominal.setter + def nominal(self, value: float): + self.__attrs["nominal"] = value + + @unbounded.setter + def unbounded(self, value: bool): + self.__attrs["unbounded"] = value + + def to_xml(self) -> Element: + attrib = dict() + for key, value in self.__attrs.items(): + if value is not None: + # In order to not loose precision, a number of this type should be + # stored on an XML file with at least 16 significant digits + if key in ["min", "max", "nominal"]: + attrib[key] = f"{value:.16g}" + elif key in ["relativeQuantity", "unbounded"]: + attrib[key] = str(value).lower() + else: + attrib[key] = str(value) + parent = super().to_xml() + SubElement(parent, "Real", attrib) + + return parent + + +class Integer(SimpleType): + def __init__(self, name: str, quantity: Optional[str] = None, min_: Optional[int] = None, + max_: Optional[int] = None, **kwargs): + super().__init__(name, **kwargs) + self.__attrs = {"quantity": quantity, "min": min_, "max": max_} + + @property + def quantity(self) -> Optional[str]: + return self.__attrs["quantity"] + + @property + def min(self) -> Optional[int]: + return self.__attrs["min"] + + @property + def max(self) -> Optional[int]: + return self.__attrs["max"] + + @quantity.setter + def quantity(self, value: str): + self.__attrs["quantity"] = value + + @min.setter + def min(self, value: int): + self.__attrs["min"] = value + + @max.setter + def max(self, value: int): + self.__attrs["max"] = value + + def to_xml(self) -> Element: + attrib = dict() + for key, value in self.__attrs.items(): + if value is not None: + attrib[key] = str(value) + parent = super().to_xml() + SubElement(parent, "Integer", attrib) + + return parent + + +class Item: + def __init__(self, name: str, value: int, description: Optional[str] = None): + self.name = name + self.value = value + self.description = description + + +class Enumeration(SimpleType): + def __init__(self, name: str, item: list[Item], quantity: Optional[str] = None, **kwargs): + super().__init__(name, **kwargs) + self.__attrs = {"quantity": quantity, "Item": item} + + @property + def quantity(self) -> Optional[str]: + return self.__attrs["quantity"] + + @property + def item(self) -> list[Item]: + return self.__attrs["Item"] + + @quantity.setter + def quantity(self, value: str): + self.__attrs["quantity"] = value + + @item.setter + def item(self, value: list[Item]): + self.__attrs["Item"] = value + + def to_xml(self) -> Element: + attrib = dict() + if self.quantity is not None: + attrib["quantity"] = self.quantity + parent = super().to_xml() + sub = SubElement(parent, "Enumeration", attrib) + + for item in self.item: + item_attrib = {"name": item.name, "value": str(item.value)} + if item.description is not None: + item_attrib["description"] = item.description + SubElement(sub, "Item", item_attrib) + + return parent diff --git a/pythonfmu/variables.py b/pythonfmu/variables.py index 086a2d92..08a00eb3 100644 --- a/pythonfmu/variables.py +++ b/pythonfmu/variables.py @@ -111,25 +111,124 @@ def __repr__(self) -> str: class Real(ScalarVariable): - def __init__(self, name: str, start: Optional[Any] = None, **kwargs): + def __init__(self, name: str, start: Optional[float] = None, declared_type: Optional[str] = None, + quantity: Optional[str] = None, unit: Optional[str] = None, display_unit: Optional[str] = None, + relative_quantity: Optional[bool] = None, min_: Optional[float] = None, max_: Optional[float] = None, + nominal: Optional[float] = None, unbounded: Optional[bool] = None, derivative: Optional[int] = None, + reinit: Optional[bool] = None, **kwargs): super().__init__(name, **kwargs) - self.__attrs = {"start": start} + self.__attrs = {"start": start, "declaredType": declared_type, "quantity": quantity, "unit": unit, + "displayUnit": display_unit, "relativeQuantity": relative_quantity, "min": min_, "max": max_, + "nominal": nominal, "unbounded": unbounded, "derivative": derivative, "reinit": reinit} @property - def start(self) -> Optional[Any]: + def start(self) -> Optional[float]: return self.__attrs["start"] + @property + def declared_type(self) -> Optional[str]: + return self.__attrs["declaredType"] + + @property + def quantity(self) -> Optional[str]: + return self.__attrs["quantity"] + + @property + def unit(self) -> Optional[str]: + return self.__attrs["unit"] + + @property + def display_unit(self) -> Optional[str]: + return self.__attrs["displayUnit"] + + @property + def relative_quantity(self) -> Optional[bool]: + return self.__attrs["relativeQuantity"] + + @property + def min(self) -> Optional[float]: + return self.__attrs["min"] + + @property + def max(self) -> Optional[float]: + return self.__attrs["max"] + + @property + def nominal(self) -> Optional[float]: + return self.__attrs["nominal"] + + @property + def unbounded(self) -> Optional[bool]: + return self.__attrs["unbounded"] + + @property + def derivative(self) -> Optional[int]: + return self.__attrs["derivative"] + + @property + def reinit(self) -> Optional[bool]: + return self.__attrs["reinit"] + @start.setter def start(self, value: float): self.__attrs["start"] = value + @declared_type.setter + def declared_type(self, value: str): + self.__attrs["declaredType"] = value + + @quantity.setter + def quantity(self, value: str): + self.__attrs["quantity"] = value + + @unit.setter + def unit(self, value: str): + self.__attrs["unit"] = value + + @display_unit.setter + def display_unit(self, value: str): + self.__attrs["displayUnit"] = value + + @relative_quantity.setter + def relative_quantity(self, value: bool): + self.__attrs["relativeQuantity"] = value + + @min.setter + def min(self, value: float): + self.__attrs["min"] = value + + @max.setter + def max(self, value: float): + self.__attrs["max"] = value + + @nominal.setter + def nominal(self, value: float): + self.__attrs["nominal"] = value + + @unbounded.setter + def unbounded(self, value: bool): + self.__attrs["unbounded"] = value + + @derivative.setter + def derivative(self, value: int): + self.__attrs["derivative"] = value + + @reinit.setter + def reinit(self, value: bool): + self.__attrs["reinit"] = value + def to_xml(self) -> Element: attrib = dict() for key, value in self.__attrs.items(): if value is not None: # In order to not loose precision, a number of this type should be # stored on an XML file with at least 16 significant digits - attrib[key] = f"{value:.16g}" + if key in ["start", "min", "max", "nominal"]: + attrib[key] = f"{value:.16g}" + elif key in ["relativeQuantity", "unbounded", "reinit"]: + attrib[key] = str(value).lower() + else: + attrib[key] = str(value) parent = super().to_xml() SubElement(parent, "Real", attrib) @@ -137,18 +236,51 @@ def to_xml(self) -> Element: class Integer(ScalarVariable): - def __init__(self, name: str, start: Optional[Any] = None, **kwargs): + def __init__(self, name: str, start: Optional[int] = None, declared_type: Optional[str] = None, + quantity: Optional[str] = None, min_: Optional[int] = None, max_: Optional[int] = None, **kwargs): super().__init__(name, **kwargs) - self.__attrs = {"start": start} + self.__attrs = {"start": start, "declaredType": declared_type, "quantity": quantity, "min": min_, "max": max_} @property - def start(self) -> Optional[Any]: + def start(self) -> Optional[int]: return self.__attrs["start"] + @property + def declared_type(self) -> Optional[str]: + return self.__attrs["declaredType"] + + @property + def quantity(self) -> Optional[str]: + return self.__attrs["quantity"] + + @property + def min(self) -> Optional[Any]: + return self.__attrs["min"] + + @property + def max(self) -> Optional[Any]: + return self.__attrs["max"] + @start.setter - def start(self, value: float): + def start(self, value: int): self.__attrs["start"] = value + @declared_type.setter + def declared_type(self, value: str): + self.__attrs["declaredType"] = value + + @quantity.setter + def quantity(self, value: str): + self.__attrs["quantity"] = value + + @min.setter + def min(self, value: int): + self.__attrs["min"] = value + + @max.setter + def max(self, value: int): + self.__attrs["max"] = value + def to_xml(self) -> Element: attrib = dict() for key, value in self.__attrs.items(): @@ -161,23 +293,34 @@ def to_xml(self) -> Element: class Boolean(ScalarVariable): - def __init__(self, name: str, start: Optional[Any] = None, **kwargs): + def __init__(self, name: str, start: Optional[bool] = None, declared_type: Optional[str] = None, **kwargs): super().__init__(name, **kwargs) - self.__attrs = {"start": start} + self.__attrs = {"start": start, "declaredType": declared_type} @property - def start(self) -> Optional[Any]: + def start(self) -> Optional[bool]: return self.__attrs["start"] + @property + def declared_type(self) -> Optional[str]: + return self.__attrs["declaredType"] + @start.setter - def start(self, value: float): + def start(self, value: bool): self.__attrs["start"] = value + @declared_type.setter + def declared_type(self, value: str): + self.__attrs["declaredType"] = value + def to_xml(self) -> Element: attrib = dict() for key, value in self.__attrs.items(): if value is not None: - attrib[key] = str(value).lower() + if key == "start": + attrib[key] = str(value).lower() + else: + attrib[key] = str(value) parent = super().to_xml() SubElement(parent, "Boolean", attrib) @@ -185,18 +328,26 @@ def to_xml(self) -> Element: class String(ScalarVariable): - def __init__(self, name: str, start: Optional[Any] = None, **kwargs): + def __init__(self, name: str, start: Optional[str] = None, declared_type: Optional[str] = None, **kwargs): super().__init__(name, **kwargs) - self.__attrs = {"start": start} + self.__attrs = {"start": start, "declaredType": declared_type} @property - def start(self) -> Optional[Any]: + def start(self) -> Optional[str]: return self.__attrs["start"] + @property + def declared_type(self) -> Optional[str]: + return self.__attrs["declaredType"] + @start.setter - def start(self, value: float): + def start(self, value: str): self.__attrs["start"] = value + @declared_type.setter + def declared_type(self, value: str): + self.__attrs["declaredType"] = value + def to_xml(self) -> Element: attrib = dict() for key, value in self.__attrs.items(): @@ -206,3 +357,60 @@ def to_xml(self) -> Element: SubElement(parent, "String", attrib) return parent + + +class Enumeration(ScalarVariable): + def __init__(self, name: str, start: Optional[int] = None, declared_type: Optional[str] = None, + quantity: Optional[str] = None, min_: Optional[int] = None, max_: Optional[int] = None, **kwargs): + super().__init__(name, **kwargs) + self.__attrs = {"start": start, "declaredType": declared_type, "quantity": quantity, "min": min_, "max": max_} + + @property + def start(self) -> Optional[int]: + return self.__attrs["start"] + + @property + def declared_type(self) -> Optional[str]: + return self.__attrs["declaredType"] + + @property + def quantity(self) -> Optional[str]: + return self.__attrs["quantity"] + + @property + def min(self) -> Optional[int]: + return self.__attrs["min"] + + @property + def max(self) -> Optional[int]: + return self.__attrs["max"] + + @start.setter + def start(self, value: int): + self.__attrs["start"] = value + + @declared_type.setter + def declared_type(self, value: str): + self.__attrs["declaredType"] = value + + @quantity.setter + def quantity(self, value: str): + self.__attrs["quantity"] = value + + @min.setter + def min(self, value: int): + self.__attrs["min"] = value + + @max.setter + def max(self, value: int): + self.__attrs["max"] = value + + def to_xml(self) -> Element: + attrib = dict() + for key, value in self.__attrs.items(): + if value is not None: + attrib[key] = str(value) + parent = super().to_xml() + SubElement(parent, "Enumeration", attrib) + + return parent From 8f21d6bce74f5e00901bb50176719c2464e914b9 Mon Sep 17 00:00:00 2001 From: caiming100 <365215504@qq.com> Date: Tue, 3 Mar 2026 14:13:31 +0800 Subject: [PATCH 2/3] refactor: Remove abstract methods and property setters to enforce immutability - Drop @abstractmethod from `enter_initialization_mode` and `exit_initialization_mode` in Fmi2Slave, making these methods optional for subclasses. - Delete all property setters in type (Real, Integer, Enumeration) and variable classes (Real, Integer, Boolean, String, Enumeration) to prevent modification of instances after creation. Attributes must now be set exclusively through constructors. - This change improves consistency and reduces the risk of unintended state changes. --- pythonfmu/fmi2slave.py | 2 - pythonfmu/type.py | 52 -------------------------- pythonfmu/variables.py | 84 ------------------------------------------ 3 files changed, 138 deletions(-) diff --git a/pythonfmu/fmi2slave.py b/pythonfmu/fmi2slave.py index a7fba3e4..7ba98317 100644 --- a/pythonfmu/fmi2slave.py +++ b/pythonfmu/fmi2slave.py @@ -192,11 +192,9 @@ def register_type(self, type: SimpleType): def setup_experiment(self, start_time: float, stop_time: Optional[float], tolerance: Optional[float]): pass - @abstractmethod def enter_initialization_mode(self): pass - @abstractmethod def exit_initialization_mode(self): pass diff --git a/pythonfmu/type.py b/pythonfmu/type.py index 9d5ba6e5..109a6e39 100644 --- a/pythonfmu/type.py +++ b/pythonfmu/type.py @@ -96,38 +96,6 @@ def nominal(self) -> Optional[float]: def unbounded(self) -> Optional[bool]: return self.__attrs["unbounded"] - @quantity.setter - def quantity(self, value: str): - self.__attrs["quantity"] = value - - @unit.setter - def unit(self, value: str): - self.__attrs["unit"] = value - - @display_unit.setter - def display_unit(self, value: str): - self.__attrs["displayUnit"] = value - - @relative_quantity.setter - def relative_quantity(self, value: bool): - self.__attrs["relativeQuantity"] = value - - @min.setter - def min(self, value: float): - self.__attrs["min"] = value - - @max.setter - def max(self, value: float): - self.__attrs["max"] = value - - @nominal.setter - def nominal(self, value: float): - self.__attrs["nominal"] = value - - @unbounded.setter - def unbounded(self, value: bool): - self.__attrs["unbounded"] = value - def to_xml(self) -> Element: attrib = dict() for key, value in self.__attrs.items(): @@ -164,18 +132,6 @@ def min(self) -> Optional[int]: def max(self) -> Optional[int]: return self.__attrs["max"] - @quantity.setter - def quantity(self, value: str): - self.__attrs["quantity"] = value - - @min.setter - def min(self, value: int): - self.__attrs["min"] = value - - @max.setter - def max(self, value: int): - self.__attrs["max"] = value - def to_xml(self) -> Element: attrib = dict() for key, value in self.__attrs.items(): @@ -207,14 +163,6 @@ def quantity(self) -> Optional[str]: def item(self) -> list[Item]: return self.__attrs["Item"] - @quantity.setter - def quantity(self, value: str): - self.__attrs["quantity"] = value - - @item.setter - def item(self, value: list[Item]): - self.__attrs["Item"] = value - def to_xml(self) -> Element: attrib = dict() if self.quantity is not None: diff --git a/pythonfmu/variables.py b/pythonfmu/variables.py index 08a00eb3..97e99388 100644 --- a/pythonfmu/variables.py +++ b/pythonfmu/variables.py @@ -173,50 +173,6 @@ def reinit(self) -> Optional[bool]: def start(self, value: float): self.__attrs["start"] = value - @declared_type.setter - def declared_type(self, value: str): - self.__attrs["declaredType"] = value - - @quantity.setter - def quantity(self, value: str): - self.__attrs["quantity"] = value - - @unit.setter - def unit(self, value: str): - self.__attrs["unit"] = value - - @display_unit.setter - def display_unit(self, value: str): - self.__attrs["displayUnit"] = value - - @relative_quantity.setter - def relative_quantity(self, value: bool): - self.__attrs["relativeQuantity"] = value - - @min.setter - def min(self, value: float): - self.__attrs["min"] = value - - @max.setter - def max(self, value: float): - self.__attrs["max"] = value - - @nominal.setter - def nominal(self, value: float): - self.__attrs["nominal"] = value - - @unbounded.setter - def unbounded(self, value: bool): - self.__attrs["unbounded"] = value - - @derivative.setter - def derivative(self, value: int): - self.__attrs["derivative"] = value - - @reinit.setter - def reinit(self, value: bool): - self.__attrs["reinit"] = value - def to_xml(self) -> Element: attrib = dict() for key, value in self.__attrs.items(): @@ -265,22 +221,6 @@ def max(self) -> Optional[Any]: def start(self, value: int): self.__attrs["start"] = value - @declared_type.setter - def declared_type(self, value: str): - self.__attrs["declaredType"] = value - - @quantity.setter - def quantity(self, value: str): - self.__attrs["quantity"] = value - - @min.setter - def min(self, value: int): - self.__attrs["min"] = value - - @max.setter - def max(self, value: int): - self.__attrs["max"] = value - def to_xml(self) -> Element: attrib = dict() for key, value in self.__attrs.items(): @@ -309,10 +249,6 @@ def declared_type(self) -> Optional[str]: def start(self, value: bool): self.__attrs["start"] = value - @declared_type.setter - def declared_type(self, value: str): - self.__attrs["declaredType"] = value - def to_xml(self) -> Element: attrib = dict() for key, value in self.__attrs.items(): @@ -344,10 +280,6 @@ def declared_type(self) -> Optional[str]: def start(self, value: str): self.__attrs["start"] = value - @declared_type.setter - def declared_type(self, value: str): - self.__attrs["declaredType"] = value - def to_xml(self) -> Element: attrib = dict() for key, value in self.__attrs.items(): @@ -389,22 +321,6 @@ def max(self) -> Optional[int]: def start(self, value: int): self.__attrs["start"] = value - @declared_type.setter - def declared_type(self, value: str): - self.__attrs["declaredType"] = value - - @quantity.setter - def quantity(self, value: str): - self.__attrs["quantity"] = value - - @min.setter - def min(self, value: int): - self.__attrs["min"] = value - - @max.setter - def max(self, value: int): - self.__attrs["max"] = value - def to_xml(self) -> Element: attrib = dict() for key, value in self.__attrs.items(): From 012e23f7f4be665bc9e9d8e7e57d727c3c5882a7 Mon Sep 17 00:00:00 2001 From: caiming100 <365215504@qq.com> Date: Wed, 4 Mar 2026 13:58:38 +0800 Subject: [PATCH 3/3] fix: correct XML tag for SimpleType element in modelDescription --- pythonfmu/type.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pythonfmu/type.py b/pythonfmu/type.py index 109a6e39..4a38a844 100644 --- a/pythonfmu/type.py +++ b/pythonfmu/type.py @@ -48,7 +48,7 @@ def to_xml(self) -> Element: for key, value in self.__attrs.items(): if value is not None: attrib[key] = str(value) - return Element("TypeDefinitions", attrib) + return Element("SimpleType", attrib) def __repr__(self) -> str: return f"{self.__class__.__name__}(name={self.name})"