aesoptparam 0.3.7__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.
@@ -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, constant=False)
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,415 @@
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 p.constant:
115
+ continue
116
+ if onlychanged:
117
+ if (val is None) or (val is p.default):
118
+ continue
119
+ elif (
120
+ isinstance(val, np.ndarray)
121
+ and isinstance(p.default, np.ndarray)
122
+ and np.all(p.default.shape == val.shape)
123
+ and np.allclose(val, p.default)
124
+ ):
125
+ continue
126
+ if hasattr(val, "_as_dict"):
127
+ out[key] = val._as_dict(
128
+ onlychanged=onlychanged,
129
+ serialize=serialize,
130
+ as_refs=as_refs,
131
+ as_funcs=as_funcs,
132
+ )
133
+ if not out[key]:
134
+ out.pop(key)
135
+ elif isinstance(val, list) and len(val) > 0 and hasattr(val[0], "_as_dict"):
136
+ out[key] = val.copy()
137
+ for iel, el in enumerate(val):
138
+ out[key][iel] = el._as_dict(
139
+ onlychanged=onlychanged,
140
+ serialize=serialize,
141
+ as_refs=as_refs,
142
+ as_funcs=as_funcs,
143
+ )
144
+ if not out[key][iel]:
145
+ out[key][iel] = None
146
+ if all([el is None for el in out[key]]):
147
+ out.pop(key)
148
+ elif isinstance(val, Reference) and as_refs:
149
+ out[key] = val
150
+ elif isinstance(val, Function) and as_funcs:
151
+ out[key] = val
152
+ else:
153
+ _val = self[key]
154
+ out[key] = _val.copy() if hasattr(_val, "copy") else _val
155
+
156
+ if serialize and (key in out):
157
+ if isinstance(out[key], np.ndarray):
158
+ out[key] = out[key].tolist()
159
+ elif isinstance(out[key], (Reference, Function)):
160
+ out[key] = str(out[key])
161
+ return out
162
+
163
+ def validate_array_shapes(self):
164
+ """Validate that all arrays with a .shape attribute is consistent with this shape. Will also check nested objects."""
165
+ for name, param in self.param.objects().items():
166
+ if hasattr(param, "shape") and (param.shape is not None):
167
+ shape_ref = self.get_shape_ref(param)
168
+ if not np.all(self[name].shape == shape_ref):
169
+ raise ValueError(
170
+ f"For {self.nested_instance_name} the shape of {name} do not match {param.shape} ({name}.shape={self[name].shape}, shape_ref={shape_ref})"
171
+ )
172
+ elif isinstance(param, ListOfParameterized):
173
+ for el in self[name]:
174
+ if hasattr(el, "validate_array_shapes"):
175
+ el.validate_array_shapes()
176
+ elif hasattr(self[name], "validate_array_shapes"):
177
+ self[name].validate_array_shapes()
178
+
179
+ def get_shape_ref(self, param):
180
+ if isinstance(param.shape, tuple):
181
+ shape_ref = tuple()
182
+ for el in param.shape:
183
+ if isinstance(el, (Reference, str)):
184
+ if not isinstance(self[el], np.ndarray):
185
+ return None
186
+ shape_ref += self[el].shape
187
+ else:
188
+ shape_ref += (el,)
189
+ elif isinstance(param.shape, Reference):
190
+ if isinstance(self[param.shape], np.ndarray):
191
+ shape_ref = self[param.shape].shape
192
+ else:
193
+ return None
194
+ else:
195
+ # Default to int
196
+ shape_ref = (param.shape,)
197
+ return shape_ref
198
+
199
+ @property
200
+ def nested_instance_name(self):
201
+ """Nested instance name"""
202
+ if self.has_parent():
203
+ return self.parent_object.nested_instance_name + "." + self.name
204
+ return self.name
205
+
206
+ def interp(self, x, xp, yp, **interp_kwargs):
207
+ """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."""
208
+ _x = x if isinstance(x, np.ndarray) else self[x]
209
+ _xp = xp if isinstance(xp, np.ndarray) else self[xp]
210
+ _yp = yp if isinstance(yp, np.ndarray) else self[yp]
211
+ if any([el is None for el in [_x, _xp, _yp]]):
212
+ return
213
+ return self.interpolator(_xp, _yp, **interp_kwargs)(_x)
214
+
215
+ def _repr_html_(self):
216
+ return parameterized_repr_html(self, True, max_arr_size=50)
217
+
218
+ def __getitem__(self, key):
219
+ if isinstance(key, str):
220
+ if ("." in key) or ("[" in key):
221
+ path_list = path_to_path_list(key)
222
+ out = self
223
+ previous_empty = False
224
+ for _key in path_list:
225
+ if isinstance(_key, str) and _key == "":
226
+ if previous_empty:
227
+ out = out.parent_object
228
+ previous_empty = False
229
+ else:
230
+ previous_empty = True
231
+ else:
232
+ if isinstance(_key, int) and len(out) <= _key:
233
+ # Return none if array is shorter
234
+ return None
235
+ out = out[_key]
236
+ previous_empty = False
237
+ return out
238
+ return getattr(self, key)
239
+ elif isinstance(key, Reference):
240
+ return self[key.path]
241
+ super().__getattribute__(key)
242
+
243
+ def __setitem__(self, key, val):
244
+ if isinstance(key, str):
245
+ return setattr(self, key, val)
246
+ super().__setattr__(key, val)
247
+
248
+ def get_val(self, name, units=None):
249
+ """Get a value with other units"""
250
+ self._validate_has_units(name)
251
+ return convert_units(self[name], self.param[name].units, units)
252
+
253
+ def set_val(self, name, val, units=None):
254
+ """Get a value with other units"""
255
+ self._validate_has_units(name)
256
+ self[name] = convert_units(val, units, self.param[name].units)
257
+
258
+ def _validate_has_units(self, name):
259
+ if not hasattr(self.param[name], "units"):
260
+ raise RuntimeError(
261
+ f"parameter with name: {name} do not have units (the parameter do not have `units` attribute, type(.param[name])={type(self.param[name])})"
262
+ )
263
+
264
+ def add_ListOfParameterized_item(self, LOP_name, item_type, **kwargs):
265
+ """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."""
266
+ # Instantiate item_type
267
+ item = item_type(parent_object=self, **kwargs)
268
+ # Append the item to the list of ListOfParameterized
269
+ if not LOP_name in self._param__private.values:
270
+ self._param__private.values[LOP_name] = []
271
+ self._param__private.values[LOP_name].append(item)
272
+ # Return the item that is added
273
+ return item
274
+
275
+ def as_json(
276
+ self, onlychanged=True, as_refs=True, as_funcs=True, **kwargs_json_dumps
277
+ ):
278
+ """Get data as a JSON string. `onlychanged` is a flag for only getting non default values."""
279
+ return json.dumps(
280
+ self.as_serial(onlychanged=onlychanged, as_refs=as_refs, as_funcs=as_funcs),
281
+ **kwargs_json_dumps,
282
+ )
283
+
284
+ def write_json(
285
+ self,
286
+ filename,
287
+ onlychanged=True,
288
+ as_refs=True,
289
+ as_funcs=True,
290
+ **kwargs_json_dump,
291
+ ):
292
+ """Write data to a JSON file. `onlychanged` is a flag for only getting non default values."""
293
+ with open(filename, "w") as file:
294
+ json.dump(
295
+ self.as_serial(
296
+ onlychanged=onlychanged, as_refs=as_refs, as_funcs=as_funcs
297
+ ),
298
+ file,
299
+ **kwargs_json_dump,
300
+ )
301
+ return self
302
+
303
+ def read_json(self, filename, asarray=True):
304
+ """Read data from a JSON file."""
305
+ data = read_json(filename, asarray)
306
+ data.pop("$schema", None)
307
+ self.from_dict(data)
308
+ return self
309
+
310
+ def as_json_schema(self, safe=False, subset=None, skip_default_name=True) -> dict:
311
+ """Get object as a JSON schema
312
+
313
+ Parameters
314
+ ----------
315
+ safe : bool, optional
316
+ Flag for getting safe schema, by default False
317
+ subset : list, optional
318
+ List of names to skip, by default None
319
+ skip_default_name : bool, optional
320
+ Flag for skipping the default name when creating the schema, by default True
321
+
322
+ Returns
323
+ -------
324
+ dict
325
+ JSON Schema
326
+ """
327
+ return ASEOptJSONSerialization.schema(self, safe, subset, skip_default_name)
328
+
329
+ def write_json_schema(
330
+ self,
331
+ filename,
332
+ safe=False,
333
+ subset=None,
334
+ skip_default_name=True,
335
+ **kwargs_json_dump,
336
+ ):
337
+ """Write object as a JSON schema file
338
+
339
+ Parameters
340
+ ----------
341
+ filename : str,Path
342
+ Filename for the JSON schema
343
+ safe : bool, optional
344
+ Flag for getting safe schema, by default False
345
+ subset : list, optional
346
+ List of names to skip, by default None
347
+ skip_default_name : bool, optional
348
+ Flag for skipping the default name when creating the schema, by default True
349
+
350
+ Returns
351
+ -------
352
+ self
353
+ """
354
+ with open(filename, "w") as file:
355
+ json.dump(
356
+ self.as_json_schema(safe, subset, skip_default_name),
357
+ file,
358
+ **kwargs_json_dump,
359
+ )
360
+ return self
361
+
362
+ def has_parent(self):
363
+ return not self.parent_object is None
364
+
365
+ def display(self, open=True, title=None, max_arr_size=50):
366
+ """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
367
+
368
+ Parameters
369
+ ----------
370
+ open : bool, optional
371
+ Flag for opening or closing root the HTML table, by default True
372
+ title : str, optional
373
+ The string that will be shown as the root of the object, by default None
374
+ max_arr_size : int, optional
375
+ 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.
376
+ """
377
+ from IPython.display import display
378
+
379
+ display(parameterized_repr_html_class(self, open, title, max_arr_size))
380
+
381
+
382
+ def path_to_path_list(path):
383
+ """Converts an object path string to a `path_list`.
384
+
385
+ Parameters
386
+ ----------
387
+ path : str
388
+ Object path (e.g. `"key1.key2.key3[0].key4"`)
389
+
390
+ Returns
391
+ -------
392
+ list
393
+ List of keys and indices (e.g. `["key1","key2","key3",0,"key4"]`)
394
+
395
+ Raises
396
+ ------
397
+ ValueError
398
+ If indices are not integers (e.g. `key1[key3]` will fail)
399
+ """
400
+ path_list = []
401
+ for name in path.split(".") if path.split(".") else [path]:
402
+ if "[" in name:
403
+ names = re.findall(r"([^\[\]]+)", name)
404
+ if len(names) > 1:
405
+ path_list.append(names[0])
406
+ names = names[1:]
407
+ for index in names:
408
+ if not index.replace("-", "").replace("+", "").isdigit():
409
+ raise ValueError(
410
+ f"Indices need to integers: index={index} (name={name}, path={path})"
411
+ )
412
+ path_list.append(int(index))
413
+ else:
414
+ path_list.append(name)
415
+ return path_list