Coverage for pydelica/model.py: 84%
75 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-05-19 07:38 +0000
« prev ^ index » next coverage.py v7.8.0, created at 2025-05-19 07:38 +0000
1import collections.abc
2import os
3import pathlib
5from typing import Any, Iterator
7import defusedxml.ElementTree as ET
10class Model(collections.abc.MutableMapping):
11 """Connects a model source file with its associated XML parameter file"""
13 def __init__(
14 self,
15 modelica_file: pathlib.Path,
16 xml_model_file: pathlib.Path | None = None,
17 ) -> None:
18 """Initialise a new model instance from a modelica file
20 Parameters
21 ----------
22 modelica_file : str
23 Modelica model source file
24 xml_model_file : str, optional
25 XML file containing model parameters, by default None
27 Raises
28 ------
29 FileNotFoundError
30 if specified XML file does not exist
31 """
32 self._model_source = modelica_file
33 if xml_model_file:
34 self._model_xml = xml_model_file
36 if not os.path.exists(xml_model_file):
37 raise FileNotFoundError(
38 "Could not extract parameters, " f"no such file '{xml_model_file}"
39 )
41 self._parameters = {}
43 _xml_obj = ET.parse(xml_model_file).getroot()
44 _vars = list(_xml_obj.iterfind("ModelVariables"))[0]
46 for i, var in enumerate(_vars):
47 # Do not store derivatives etc
48 if "(" in var.attrib["name"]:
49 continue
50 _type_info = list(var)[0].attrib
51 _om_type = list(var)[0].tag
53 _value = _type_info["start"] if "start" in _type_info else None
55 _type, _value = self._get_type(_value, _om_type)
57 self._parameters[var.attrib["name"]] = {
58 "id": i,
59 "value": _value,
60 "type": _type,
61 }
63 def _get_type(self, value, om_type):
64 if om_type == "Boolean":
65 return bool, value.title() == "True" if value else value
66 elif om_type == "Integer":
67 return int, int(value) if value else value
68 elif om_type == "Real":
69 return float, float(value) if value else value
70 elif om_type == "String":
71 return str, value or value
72 else:
73 # TODO: Definitely other types
74 return None, value
76 def get_source_path(self) -> pathlib.Path:
77 """Retrieve the Modelica source file path
79 Returns
80 -------
81 pathlib.Path
82 path to the Modelica source file
83 """
84 return self._model_source
86 def write_params(self) -> None:
87 """Write parameter values to the XML file"""
88 _xml_obj = ET.parse(self._model_xml)
89 _iter_obj = _xml_obj.findall("ModelVariables/ScalarVariable")
91 for i, item in enumerate(_iter_obj):
92 # Do not write derivatives/functions
93 if "(" in item.attrib["name"]:
94 continue
96 _name = item.attrib["name"]
98 if (
99 not self._parameters[_name]["value"]
100 and self._parameters[_name]["value"] != 0
101 ):
102 continue
104 # Booleans are lower case in the XML
105 if self._parameters[_name]["type"] == bool:
106 _new_val = str(self._parameters[_name]["value"]).lower()
107 else:
108 _new_val = str(self._parameters[_name]["value"])
110 if _new_val:
111 _xml_obj.findall("ModelVariables/ScalarVariable")[i][0].set(
112 "start", _new_val
113 )
114 _xml_obj.write(self._model_xml)
116 def set_parameter(self, param_name: str, value: Any) -> None:
117 """Set parameter in XML to a given value
119 Parameters
120 ----------
121 param_name : str
122 name of parameter within XML file
123 value : Any
124 new value for the parameter
125 """
126 self._parameters[param_name]["value"] = value
128 def get_parameter(self, param_name: str) -> Any:
129 """Retrieve a parameter value by name
131 Parameters
132 ----------
133 param_name : str
134 name of the parameter to retrieve the value of
136 Returns
137 -------
138 Any
139 value of the given parameter
141 Raises
142 ------
143 AssertionError
144 If the returned value is a dictionary as opposed to numeric/string
145 """
146 if isinstance(self._parameters[param_name]["value"], dict):
147 raise AssertionError(
148 "Expected non-mutable value for requested parameter"
149 f"'{param_name}' but got type 'dict'"
150 )
151 return self._parameters[param_name]["value"]
153 def get_om_parameter_type(self, param_name: str):
154 """Returns parameter OM Type as Python type.
156 E.g. "Boolean" -> bool
158 Parameters
159 ----------
160 param_name : str
161 name of parameter to search for
162 """
163 return self._parameters[param_name]["type"]
165 def __iter__(self) -> Iterator:
166 return iter(self._parameters)
168 def __len__(self) -> int:
169 return len(self._parameters)
171 def __getitem__(self, key) -> Any:
172 return self._parameters[key]
174 def __setitem__(self, key, value) -> None:
175 self._parameters[key] = value
177 def __str__(self) -> str:
178 return self.__repr__()
180 def __delitem__(self, key) -> None:
181 del self._parameters[key]
183 def __repr__(self) -> str:
184 _params = {k: v["value"] for k, v in self._parameters.items()}
185 return f"OMModelProperties(params={_params})"
187 def update(self, other: "Model") -> None:
188 self._parameters.update(other._parameters)