aesoptparam 0.3.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of aesoptparam might be problematic. Click here for more details.

@@ -0,0 +1,13 @@
1
+ from param import *
2
+
3
+ from .parameterized import AESOptParameterized
4
+ from .parameters import (
5
+ AESOptArray,
6
+ AESOptBoolean,
7
+ AESOptInteger,
8
+ AESOptNumber,
9
+ AESOptString,
10
+ ListOfParameterized,
11
+ SubParameterized,
12
+ )
13
+ from .utils import copy_param, copy_param_ref
aesoptparam/example.py ADDED
@@ -0,0 +1,110 @@
1
+ import numpy as np
2
+
3
+ from aesoptparam import (
4
+ AESOptArray,
5
+ AESOptNumber,
6
+ AESOptParameterized,
7
+ AESOptString,
8
+ Dict,
9
+ ListOfParameterized,
10
+ String,
11
+ SubParameterized,
12
+ copy_param,
13
+ copy_param_ref,
14
+ )
15
+
16
+
17
+ class sub1_class(AESOptParameterized):
18
+ """Docs sub1"""
19
+
20
+ a = AESOptNumber(5.0, doc="Docs for sub1.a", bounds=(0, 10))
21
+ b = AESOptArray(np.linspace(0, 10, 10), doc="Docs for sub1.b")
22
+ c = AESOptArray(default_ref=".b", doc="Docs for sub1.c")
23
+ e = AESOptArray(
24
+ lambda self: np.full_like(self.b, self.a), shape=".b", doc="Docs for sub1.e"
25
+ )
26
+ f = AESOptArray(default_full=(".b", ".a"), doc="Docs for sub1.d")
27
+ g = AESOptArray(default_full=(".b", 6.0), doc="Docs for sub1.g")
28
+ h = AESOptArray(
29
+ default_interp=(np.linspace(0, 10, 5), ".b", ".c"), doc="Docs for sub1.h"
30
+ )
31
+ i = AESOptString("sub1.Dummy", doc="Docs for sub1.i")
32
+ j = AESOptString(".i", doc="Docs for sub1.j")
33
+ k = AESOptString(lambda self: self.i + "2", doc="Docs for sub1.k")
34
+
35
+
36
+ class sub2_class(AESOptParameterized):
37
+ """Docs sub2"""
38
+
39
+ a = copy_param_ref(
40
+ sub1_class.param.a, "..sub1.a", update=dict(doc="Docs for sub2.a")
41
+ )
42
+ b = AESOptNumber(default_ref="..a", doc="Docs for sub2.b")
43
+ c = AESOptNumber(default_ref="..sub_list[0].a", doc="Docs for sub2.c")
44
+ d = copy_param_ref(
45
+ sub1_class.param.b, "..sub1.b", update=dict(doc="Docs for sub2.d")
46
+ )
47
+ e = AESOptArray(
48
+ lambda self: (
49
+ None if self.parent_object.sub1.b is None else self.parent_object.sub1.b + 1
50
+ ),
51
+ doc="Docs for sub2.e",
52
+ )
53
+ f = AESOptString("..f", doc="Docs for sub2.f")
54
+ g = copy_param_ref(
55
+ sub1_class.param.i, "..sub1.i", update=dict(doc="Docs for sub2.g")
56
+ )
57
+ h = AESOptString("..sub_list[0].f", doc="Docs for sub2.h")
58
+
59
+
60
+ class sub_list_class(AESOptParameterized):
61
+ """Docs sub_list"""
62
+
63
+ a = AESOptNumber(6.0, doc="Docs for sub_list.a")
64
+ b = AESOptNumber(default_ref="..a", doc="Docs for sub_list.b")
65
+ c = AESOptNumber(default_ref="..sub1.a", doc="Docs for sub_list.c")
66
+ d = AESOptArray(default_ref="..d", doc="Docs for sub_list.d")
67
+ e = AESOptArray(default_ref="..sub1.b", doc="Docs for sub_list.e")
68
+ f = AESOptString(default_ref="..f", doc="Docs for sub_list.f")
69
+ g = AESOptString(default_ref="..sub1.i", doc="Docs for sub_list.g")
70
+
71
+
72
+ class main(AESOptParameterized):
73
+ """Main object"""
74
+
75
+ version = String("0.0.0", readonly=True, precedence=0.0)
76
+ name = String("Dummy", doc="Main dummy", precedence=0.01)
77
+ a = AESOptNumber(4.0, units="rad/s", doc="Docs for .a", bounds=(0, 10))
78
+ b = AESOptNumber(default_ref=".sub1.a", units="m/s", doc="Docs for .b")
79
+ c = AESOptNumber(default_ref=".sub_list[0].a", doc="Docs for .c")
80
+ d = AESOptArray(
81
+ default_ref=".sub1.b", units="mm/s", doc="Docs for .d", bounds=(0, 10)
82
+ )
83
+ e = AESOptArray(
84
+ np.array([0, 1, 2, 3]),
85
+ shape=4,
86
+ units="mm/s",
87
+ doc="Docs for .d",
88
+ bounds=(0, 10),
89
+ dtype=int,
90
+ )
91
+ f = AESOptString("Dummy", doc="Docs for .f")
92
+ g = AESOptString(".f", doc="Docs for .g")
93
+ h = AESOptString(lambda self: self.f + "2", doc="Docs for .h")
94
+ i = Dict(doc="Docs for .i")
95
+ sub1 = SubParameterized(sub1_class)
96
+ sub2 = SubParameterized(sub2_class)
97
+ sub_list = ListOfParameterized(sub_list_class)
98
+ sub_list2 = ListOfParameterized(
99
+ sub_list_class, default_call=lambda self: [self.add_sub_list2()]
100
+ )
101
+
102
+ def add_sub_list(self, **params):
103
+ return self.add_ListOfParameterized_item(
104
+ "sub_list", self.param.sub_list.item_type, **params
105
+ )
106
+
107
+ def add_sub_list2(self, **params):
108
+ return self.add_ListOfParameterized_item(
109
+ "sub_list2", self.param.sub_list2.item_type, **params
110
+ )
@@ -0,0 +1,417 @@
1
+ import json
2
+ import re
3
+
4
+ import numpy as np
5
+ import param as pm
6
+ from scipy.interpolate import PchipInterpolator
7
+
8
+ from .parameters import Function, ListOfParameterized, Reference
9
+ from .serializer import ASEOptJSONSerialization
10
+ from .utils import read_json, write_json
11
+ from .utils.html_repr import parameterized_repr_html, parameterized_repr_html_class
12
+ from .utils.units import convert_units
13
+
14
+
15
+ class AESOptParameterized(pm.Parameterized):
16
+ """Base object inheriting from `param.Parameterized`. Adds specific methods for interacting with data in AESOpt models."""
17
+
18
+ interpolator = PchipInterpolator
19
+
20
+ def __init__(self, parent_object=None, **params):
21
+ """Initialize object with parameters. **params are passed with the .from_dict. The parent_object argument allows nested objects to access data across the full data model"""
22
+ self.parent_object = parent_object
23
+ super().__init__()
24
+ self.from_dict(params)
25
+
26
+ def from_dict(self, dict_in):
27
+ """Set parameters from `dict`"""
28
+ for key, val in dict_in.items():
29
+ if key in self.param:
30
+ p = self.param[key]
31
+ if hasattr(p, "readonly") and p.readonly:
32
+ if np.all(self[key] != val):
33
+ raise ValueError(
34
+ f"A parameter with readonly has a different value in the dict than currently in the object (given: self.{key}={self[key]}, dict_in['{key}']={val})"
35
+ )
36
+ continue
37
+ if hasattr(self[key], "from_dict"):
38
+ self[key].from_dict(val)
39
+ elif (
40
+ isinstance(self.param[key], pm.List)
41
+ and (self.param[key].item_type is not None)
42
+ and hasattr(self.param[key].item_type, "from_dict")
43
+ ):
44
+ for i, el in enumerate(val):
45
+ if len(self[key]) > i:
46
+ self[key][i].from_dict(el)
47
+ else:
48
+ self[key].append(
49
+ self.param[key].item_type(self).from_dict(el)
50
+ )
51
+ elif isinstance(self.param[key], pm.Array) and isinstance(val, list):
52
+ arr = np.asarray(val)
53
+ self[key] = arr
54
+ else:
55
+ self[key] = val
56
+ return self
57
+
58
+ def as_dict(self, onlychanged=True, as_refs=False, as_funcs=False) -> dict:
59
+ """Get object as dict. It also works for nested data structure, references and functions.
60
+
61
+ Parameters
62
+ ----------
63
+ onlychanged : bool, optional
64
+ Flag for only returning data that has been changed compared to the default, by default True
65
+ as_refs : bool, optional
66
+ Flag for returning the `Reference` instance instead of the values, by default False
67
+ as_funcs : bool, optional
68
+ Flag for returning the `Function` instance instead of the values, by default False
69
+
70
+ Returns
71
+ -------
72
+ dict
73
+ Object data as a dict
74
+ """
75
+ return self._as_dict(
76
+ onlychanged=onlychanged, serialize=False, as_refs=as_refs, as_funcs=as_funcs
77
+ )
78
+
79
+ def as_serial(self, onlychanged=True, as_refs=True, as_funcs=True) -> dict:
80
+ """Get instance as a serializable dict. It returns a `dict` with native python data types (e.g. `list`, `float`, `int`, `str`), which can be written to file with `json`, `yaml` and `toml`.
81
+ It will also convert references (to `$ref *ref*`) and functions (to `$function *function string*`)
82
+
83
+ Parameters
84
+ ----------
85
+ onlychanged : bool, optional
86
+ Flag for only returning data that has been changed compared to the default, by default True
87
+ as_refs : bool, optional
88
+ Flag for returning the value from references or functions, by default True
89
+ as_funcs : bool, optional
90
+ Flag for returning the `Function` instance instead of the values, by default False
91
+
92
+ Returns
93
+ -------
94
+ dict
95
+ Object data as a dict
96
+ """
97
+ return self._as_dict(
98
+ onlychanged=onlychanged, serialize=True, as_refs=as_refs, as_funcs=as_funcs
99
+ )
100
+
101
+ def _as_dict(
102
+ self, onlychanged=True, serialize=False, as_refs=False, as_funcs=False
103
+ ):
104
+ out = dict()
105
+
106
+ if onlychanged:
107
+ vals = self._param__private.values
108
+ else:
109
+ vals = self.param.values()
110
+ vals.update(self._param__private.values)
111
+
112
+ for key, val in vals.items():
113
+ p = self.param[key] # Parameter instance
114
+ if (key == "name") and (
115
+ p.doc == "\n String identifier for this object."
116
+ ):
117
+ continue
118
+ if onlychanged:
119
+ if (val is None) or (val is p.default):
120
+ continue
121
+ elif (
122
+ isinstance(val, np.ndarray)
123
+ and isinstance(p.default, np.ndarray)
124
+ and np.all(p.default.shape == val.shape)
125
+ and np.allclose(val, p.default)
126
+ ):
127
+ continue
128
+ if hasattr(val, "_as_dict"):
129
+ out[key] = val._as_dict(
130
+ onlychanged=onlychanged,
131
+ serialize=serialize,
132
+ as_refs=as_refs,
133
+ as_funcs=as_funcs,
134
+ )
135
+ if not out[key]:
136
+ out.pop(key)
137
+ elif isinstance(val, list) and len(val) > 0 and hasattr(val[0], "_as_dict"):
138
+ out[key] = val.copy()
139
+ for iel, el in enumerate(val):
140
+ out[key][iel] = el._as_dict(
141
+ onlychanged=onlychanged,
142
+ serialize=serialize,
143
+ as_refs=as_refs,
144
+ as_funcs=as_funcs,
145
+ )
146
+ if not out[key][iel]:
147
+ out[key][iel] = None
148
+ if all([el is None for el in out[key]]):
149
+ out.pop(key)
150
+ elif isinstance(val, Reference) and as_refs:
151
+ out[key] = val
152
+ elif isinstance(val, Function) and as_funcs:
153
+ out[key] = val
154
+ else:
155
+ _val = self[key]
156
+ out[key] = _val.copy() if hasattr(_val, "copy") else _val
157
+
158
+ if serialize and (key in out):
159
+ if isinstance(out[key], np.ndarray):
160
+ out[key] = out[key].tolist()
161
+ elif isinstance(out[key], (Reference, Function)):
162
+ out[key] = str(out[key])
163
+ return out
164
+
165
+ def validate_array_shapes(self):
166
+ """Validate that all arrays with a .shape attribute is consistent with this shape. Will also check nested objects."""
167
+ for name, param in self.param.objects().items():
168
+ if hasattr(param, "shape") and (param.shape is not None):
169
+ shape_ref = self.get_shape_ref(param)
170
+ if not np.all(self[name].shape == shape_ref):
171
+ raise ValueError(
172
+ f"For {self.nested_instance_name} the shape of {name} do not match {param.shape} ({name}.shape={self[name].shape}, shape_ref={shape_ref})"
173
+ )
174
+ elif isinstance(param, ListOfParameterized):
175
+ for el in self[name]:
176
+ if hasattr(el, "validate_array_shapes"):
177
+ el.validate_array_shapes()
178
+ elif hasattr(self[name], "validate_array_shapes"):
179
+ self[name].validate_array_shapes()
180
+
181
+ def get_shape_ref(self, param):
182
+ if isinstance(param.shape, tuple):
183
+ shape_ref = tuple()
184
+ for el in param.shape:
185
+ if isinstance(el, (Reference, str)):
186
+ if not isinstance(self[el], np.ndarray):
187
+ return None
188
+ shape_ref += self[el].shape
189
+ else:
190
+ shape_ref += (el,)
191
+ elif isinstance(param.shape, Reference):
192
+ if isinstance(self[param.shape], np.ndarray):
193
+ shape_ref = self[param.shape].shape
194
+ else:
195
+ return None
196
+ else:
197
+ # Default to int
198
+ shape_ref = (param.shape,)
199
+ return shape_ref
200
+
201
+ @property
202
+ def nested_instance_name(self):
203
+ """Nested instance name"""
204
+ if self.has_parent():
205
+ return self.parent_object.nested_instance_name + "." + self.name
206
+ return self.name
207
+
208
+ def interp(self, x, xp, yp, **interp_kwargs):
209
+ """1D interpolation using SciPy interpolator (default to [`PChipInterpolator`](https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.PchipInterpolator.html#scipy.interpolate.PchipInterpolator)). Arguments (`x`, `xp`, `yp`) can either be strings (object-paths) or numpy arrays."""
210
+ _x = x if isinstance(x, np.ndarray) else self[x]
211
+ _xp = xp if isinstance(xp, np.ndarray) else self[xp]
212
+ _yp = yp if isinstance(yp, np.ndarray) else self[yp]
213
+ if any([el is None for el in [_x, _xp, _yp]]):
214
+ return
215
+ return self.interpolator(_xp, _yp, **interp_kwargs)(_x)
216
+
217
+ def _repr_html_(self):
218
+ return parameterized_repr_html(self, True, max_arr_size=50)
219
+
220
+ def __getitem__(self, key):
221
+ if isinstance(key, str):
222
+ if ("." in key) or ("[" in key):
223
+ path_list = path_to_path_list(key)
224
+ out = self
225
+ previous_empty = False
226
+ for _key in path_list:
227
+ if isinstance(_key, str) and _key == "":
228
+ if previous_empty:
229
+ out = out.parent_object
230
+ previous_empty = False
231
+ else:
232
+ previous_empty = True
233
+ else:
234
+ if isinstance(_key, int) and len(out) <= _key:
235
+ # Return none if array is shorter
236
+ return None
237
+ out = out[_key]
238
+ previous_empty = False
239
+ return out
240
+ return getattr(self, key)
241
+ elif isinstance(key, Reference):
242
+ return self[key.path]
243
+ super().__getattribute__(key)
244
+
245
+ def __setitem__(self, key, val):
246
+ if isinstance(key, str):
247
+ return setattr(self, key, val)
248
+ super().__setattr__(key, val)
249
+
250
+ def get_val(self, name, units=None):
251
+ """Get a value with other units"""
252
+ self._validate_has_units(name)
253
+ return convert_units(self[name], self.param[name].units, units)
254
+
255
+ def set_val(self, name, val, units=None):
256
+ """Get a value with other units"""
257
+ self._validate_has_units(name)
258
+ self[name] = convert_units(val, units, self.param[name].units)
259
+
260
+ def _validate_has_units(self, name):
261
+ if not hasattr(self.param[name], "units"):
262
+ raise RuntimeError(
263
+ f"parameter with name: {name} do not have units (the parameter do not have `units` attribute, type(.param[name])={type(self.param[name])})"
264
+ )
265
+
266
+ def add_ListOfParameterized_item(self, LOP_name, item_type, **kwargs):
267
+ """Utility method to add Parameterized entries to a ListOfParameterized. It insure that the parent object is added. It is recommenced that this method is wrapped by a more specific method like `add_XX(self, **kwargs)` where the `LOP_name` and `item_type` is provided statically."""
268
+ # Instantiate item_type
269
+ item = item_type(parent_object=self, **kwargs)
270
+ # Append the item to the list of ListOfParameterized
271
+ if not LOP_name in self._param__private.values:
272
+ self._param__private.values[LOP_name] = []
273
+ self._param__private.values[LOP_name].append(item)
274
+ # Return the item that is added
275
+ return item
276
+
277
+ def as_json(
278
+ self, onlychanged=True, as_refs=True, as_funcs=True, **kwargs_json_dumps
279
+ ):
280
+ """Get data as a JSON string. `onlychanged` is a flag for only getting non default values."""
281
+ return json.dumps(
282
+ self.as_serial(onlychanged=onlychanged, as_refs=as_refs, as_funcs=as_funcs),
283
+ **kwargs_json_dumps,
284
+ )
285
+
286
+ def write_json(
287
+ self,
288
+ filename,
289
+ onlychanged=True,
290
+ as_refs=True,
291
+ as_funcs=True,
292
+ **kwargs_json_dump,
293
+ ):
294
+ """Write data to a JSON file. `onlychanged` is a flag for only getting non default values."""
295
+ with open(filename, "w") as file:
296
+ json.dump(
297
+ self.as_serial(
298
+ onlychanged=onlychanged, as_refs=as_refs, as_funcs=as_funcs
299
+ ),
300
+ file,
301
+ **kwargs_json_dump,
302
+ )
303
+ return self
304
+
305
+ def read_json(self, filename, asarray=True):
306
+ """Read data from a JSON file."""
307
+ data = read_json(filename, asarray)
308
+ data.pop("$schema", None)
309
+ self.from_dict(data)
310
+ return self
311
+
312
+ def as_json_schema(self, safe=False, subset=None, skip_default_name=True) -> dict:
313
+ """Get object as a JSON schema
314
+
315
+ Parameters
316
+ ----------
317
+ safe : bool, optional
318
+ Flag for getting safe schema, by default False
319
+ subset : list, optional
320
+ List of names to skip, by default None
321
+ skip_default_name : bool, optional
322
+ Flag for skipping the default name when creating the schema, by default True
323
+
324
+ Returns
325
+ -------
326
+ dict
327
+ JSON Schema
328
+ """
329
+ return ASEOptJSONSerialization.schema(self, safe, subset, skip_default_name)
330
+
331
+ def write_json_schema(
332
+ self,
333
+ filename,
334
+ safe=False,
335
+ subset=None,
336
+ skip_default_name=True,
337
+ **kwargs_json_dump,
338
+ ):
339
+ """Write object as a JSON schema file
340
+
341
+ Parameters
342
+ ----------
343
+ filename : str,Path
344
+ Filename for the JSON schema
345
+ safe : bool, optional
346
+ Flag for getting safe schema, by default False
347
+ subset : list, optional
348
+ List of names to skip, by default None
349
+ skip_default_name : bool, optional
350
+ Flag for skipping the default name when creating the schema, by default True
351
+
352
+ Returns
353
+ -------
354
+ self
355
+ """
356
+ with open(filename, "w") as file:
357
+ json.dump(
358
+ self.as_json_schema(safe, subset, skip_default_name),
359
+ file,
360
+ **kwargs_json_dump,
361
+ )
362
+ return self
363
+
364
+ def has_parent(self):
365
+ return not self.parent_object is None
366
+
367
+ def display(self, open=True, title=None, max_arr_size=50):
368
+ """Method to display the settings and documentation of the current instance. Similar to just rendering the by last entry of the cell but allows a little more control
369
+
370
+ Parameters
371
+ ----------
372
+ open : bool, optional
373
+ Flag for opening or closing root the HTML table, by default True
374
+ title : str, optional
375
+ The string that will be shown as the root of the object, by default None
376
+ max_arr_size : int, optional
377
+ Maximum size of numeric arrays to render. Using `np.size(array) > max_arr_size`. Default will be determined based on the size of the data, `max_arr_size=[5, 10, 100]` for data with approx. size of [1000, 100, 10] size 100 arrays. Otherwise `max_array_size=-1` which means to render all arrays.
378
+ """
379
+ from IPython.display import display
380
+
381
+ display(parameterized_repr_html_class(self, open, title, max_arr_size))
382
+
383
+
384
+ def path_to_path_list(path):
385
+ """Converts an object path string to a `path_list`.
386
+
387
+ Parameters
388
+ ----------
389
+ path : str
390
+ Object path (e.g. `"key1.key2.key3[0].key4"`)
391
+
392
+ Returns
393
+ -------
394
+ list
395
+ List of keys and indices (e.g. `["key1","key2","key3",0,"key4"]`)
396
+
397
+ Raises
398
+ ------
399
+ ValueError
400
+ If indices are not integers (e.g. `key1[key3]` will fail)
401
+ """
402
+ path_list = []
403
+ for name in path.split(".") if path.split(".") else [path]:
404
+ if "[" in name:
405
+ names = re.findall(r"([^\[\]]+)", name)
406
+ if len(names) > 1:
407
+ path_list.append(names[0])
408
+ names = names[1:]
409
+ for index in names:
410
+ if not index.replace("-", "").replace("+", "").isdigit():
411
+ raise ValueError(
412
+ f"Indices need to integers: index={index} (name={name}, path={path})"
413
+ )
414
+ path_list.append(int(index))
415
+ else:
416
+ path_list.append(name)
417
+ return path_list