Coverage for pydelica/model.py: 84%

75 statements  

« prev     ^ index     » next       coverage.py v7.8.0, created at 2025-05-19 07:38 +0000

1import collections.abc 

2import os 

3import pathlib 

4 

5from typing import Any, Iterator 

6 

7import defusedxml.ElementTree as ET 

8 

9 

10class Model(collections.abc.MutableMapping): 

11 """Connects a model source file with its associated XML parameter file""" 

12 

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 

19 

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 

26 

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 

35 

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 ) 

40 

41 self._parameters = {} 

42 

43 _xml_obj = ET.parse(xml_model_file).getroot() 

44 _vars = list(_xml_obj.iterfind("ModelVariables"))[0] 

45 

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 

52 

53 _value = _type_info["start"] if "start" in _type_info else None 

54 

55 _type, _value = self._get_type(_value, _om_type) 

56 

57 self._parameters[var.attrib["name"]] = { 

58 "id": i, 

59 "value": _value, 

60 "type": _type, 

61 } 

62 

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 

75 

76 def get_source_path(self) -> pathlib.Path: 

77 """Retrieve the Modelica source file path 

78 

79 Returns 

80 ------- 

81 pathlib.Path 

82 path to the Modelica source file 

83 """ 

84 return self._model_source 

85 

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") 

90 

91 for i, item in enumerate(_iter_obj): 

92 # Do not write derivatives/functions 

93 if "(" in item.attrib["name"]: 

94 continue 

95 

96 _name = item.attrib["name"] 

97 

98 if ( 

99 not self._parameters[_name]["value"] 

100 and self._parameters[_name]["value"] != 0 

101 ): 

102 continue 

103 

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

109 

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) 

115 

116 def set_parameter(self, param_name: str, value: Any) -> None: 

117 """Set parameter in XML to a given value 

118 

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 

127 

128 def get_parameter(self, param_name: str) -> Any: 

129 """Retrieve a parameter value by name 

130 

131 Parameters 

132 ---------- 

133 param_name : str 

134 name of the parameter to retrieve the value of 

135 

136 Returns 

137 ------- 

138 Any 

139 value of the given parameter 

140 

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"] 

152 

153 def get_om_parameter_type(self, param_name: str): 

154 """Returns parameter OM Type as Python type. 

155 

156 E.g. "Boolean" -> bool 

157 

158 Parameters 

159 ---------- 

160 param_name : str 

161 name of parameter to search for 

162 """ 

163 return self._parameters[param_name]["type"] 

164 

165 def __iter__(self) -> Iterator: 

166 return iter(self._parameters) 

167 

168 def __len__(self) -> int: 

169 return len(self._parameters) 

170 

171 def __getitem__(self, key) -> Any: 

172 return self._parameters[key] 

173 

174 def __setitem__(self, key, value) -> None: 

175 self._parameters[key] = value 

176 

177 def __str__(self) -> str: 

178 return self.__repr__() 

179 

180 def __delitem__(self, key) -> None: 

181 del self._parameters[key] 

182 

183 def __repr__(self) -> str: 

184 _params = {k: v["value"] for k, v in self._parameters.items()} 

185 return f"OMModelProperties(params={_params})" 

186 

187 def update(self, other: "Model") -> None: 

188 self._parameters.update(other._parameters)