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,641 @@
1
+ import typing
2
+ from inspect import getsource, signature
3
+
4
+ import numpy as np
5
+ import param as pm
6
+
7
+ from .utils.html_repr import value_ref_default_repr_html
8
+ from .utils.units import valid_units
9
+
10
+
11
+ class SubParameterized(pm.ClassSelector):
12
+ @typing.overload
13
+ def __init__(
14
+ self,
15
+ class_,
16
+ *,
17
+ default=None,
18
+ instantiate=True,
19
+ is_instance=True,
20
+ allow_None=False,
21
+ doc=None,
22
+ label=None,
23
+ precedence=None,
24
+ constant=False,
25
+ readonly=False,
26
+ pickle_default_value=True,
27
+ per_instance=True,
28
+ allow_refs=False,
29
+ nested_refs=False,
30
+ ): ...
31
+
32
+ def __init__(self, class_, *, default=pm.Undefined, doc=pm.Undefined, **params):
33
+ """Include a Parameterized class as a parameter. A direct child `param.ClassSelector` for including nested Parameterized model in a Parameterized model. Default to an empty instance of the class and adding the doc string from the class."""
34
+ if default is pm.Undefined:
35
+ default = lambda self: class_(parent_object=self)
36
+ if (doc is pm.Undefined) and (class_.__doc__):
37
+ doc = class_.__doc__
38
+ super().__init__(class_=class_, default=default, doc=doc, **params)
39
+ self._validate(self.default)
40
+
41
+ def __get__(self, obj, objtype):
42
+ out = super().__get__(obj, objtype)
43
+ if callable(out):
44
+ if len(signature(out).parameters) == 1:
45
+ obj._param__private.values[self.name] = out(obj)
46
+ else:
47
+ obj._param__private.values[self.name] = out()
48
+ return super().__get__(obj, objtype)
49
+ return out
50
+
51
+ def _validate_class_(self, val, class_, is_instance):
52
+ if val is None:
53
+ return
54
+ if not callable(val):
55
+ if not (isinstance(val, class_)):
56
+ class_name = class_.__name__
57
+ raise ValueError(
58
+ f"{pm.parameters._validate_error_prefix(self)} value must be an instance of {class_name}, not {val!r}."
59
+ )
60
+
61
+
62
+ class ListOfParameterized(pm.List):
63
+ __slots__ = ["identifier", "default_call"]
64
+ _slot_defaults = dict(pm.List._slot_defaults, identifier="name", default_call=None)
65
+
66
+ @typing.overload
67
+ def __init__(
68
+ self,
69
+ item_type,
70
+ identifier,
71
+ *,
72
+ default=[],
73
+ instantiate=True,
74
+ bounds=(0, None),
75
+ allow_None=False,
76
+ doc=None,
77
+ label=None,
78
+ precedence=None,
79
+ constant=False,
80
+ readonly=False,
81
+ pickle_default_value=True,
82
+ per_instance=True,
83
+ allow_refs=False,
84
+ nested_refs=False,
85
+ default_call=None,
86
+ ): ...
87
+
88
+ def __init__(self, item_type, identifier="name", *, default_call=None, **params):
89
+ """List of Parameterized objects. Adds a html render and unique object identifier. A direct child of `param.List`"""
90
+ self.identifier = identifier
91
+ if default_call is not None and not callable(default_call):
92
+ raise RuntimeError(
93
+ f"`default_call` need to be a callable or None (given: {default_call})"
94
+ )
95
+ self.default_call = default_call
96
+ super().__init__(item_type=item_type, **params)
97
+ if (self.doc is None) and (self.item_type.__doc__):
98
+ self.doc = self.item_type.__doc__
99
+
100
+ def __get__(self, obj, objtype):
101
+ out = super().__get__(obj, objtype)
102
+ if not out and self.default_call is not None:
103
+ val = self.default_call(obj)
104
+ if not isinstance(val, list):
105
+ raise ValueError(
106
+ f"`.default_call` need to return a list (gives: `type(default_call(obj))`={type(self.default_call(obj))})"
107
+ )
108
+ obj._param__private.values[self.name] = self.default_call(obj)
109
+ return super().__get__(obj, objtype)
110
+ return out
111
+
112
+
113
+ class BaseRefFunc:
114
+ def __init__(
115
+ self,
116
+ default=pm.Undefined,
117
+ *,
118
+ default_ref=pm.Undefined,
119
+ **params,
120
+ ):
121
+ if self._is_ref(default):
122
+ default = Reference(default)
123
+ elif self._is_func(default):
124
+ default = Function(default)
125
+ if not (default_ref is pm.Undefined or default_ref is None) and (
126
+ not isinstance(default_ref, (str, Reference))
127
+ ):
128
+ raise ValueError(
129
+ f"`default_ref` need to be of type string (given: type(default_ref)={type(default_ref)})"
130
+ )
131
+ self.default_ref = None
132
+ if not (default_ref is pm.Undefined or default_ref is None):
133
+ self.default_ref = (
134
+ default_ref
135
+ if isinstance(default_ref, Reference)
136
+ else Reference(default_ref)
137
+ )
138
+ super().__init__(default=default, **params)
139
+
140
+ def __get__(self, obj, objtype):
141
+ out = super().__get__(obj, objtype)
142
+ if isinstance(out, Function):
143
+ return out.method(obj)
144
+ elif isinstance(out, Reference):
145
+ return obj[out]
146
+ elif out is None:
147
+ if isinstance(self.default_ref, Reference):
148
+ return obj[self.default_ref]
149
+ return out
150
+
151
+ def __set__(self, obj, val):
152
+ if not isinstance(val, Function) and self._is_func(val):
153
+ val = Function(val)
154
+ elif not isinstance(val, Reference) and self._is_ref(val):
155
+ val = Reference(val)
156
+ return super().__set__(obj, val)
157
+
158
+ def _validate_value(self, val, allow_None):
159
+ if isinstance(val, (Function, Reference)):
160
+ return
161
+ return super()._validate_value(val, allow_None)
162
+
163
+ def _is_func(
164
+ self,
165
+ func,
166
+ ):
167
+ return (
168
+ callable(func)
169
+ or isinstance(func, Function)
170
+ or (isinstance(func, str) and func.startswith("$function"))
171
+ )
172
+
173
+ def _is_ref(self, ref):
174
+ return isinstance(ref, Reference) or (
175
+ isinstance(ref, str)
176
+ and ((ref.startswith(".") and not ref[1] == "/") or ref.startswith("$ref"))
177
+ )
178
+
179
+ def repr_html(self, val, val_pp, max_arr_size=50):
180
+ return value_ref_default_repr_html(self, val, val_pp, max_arr_size)
181
+
182
+
183
+ class BaseRefFuncUnits(BaseRefFunc):
184
+ def __init__(
185
+ self,
186
+ default=pm.Undefined,
187
+ *,
188
+ units=pm.Undefined,
189
+ **params,
190
+ ):
191
+ if (not units is pm.Undefined) and (not valid_units(units)):
192
+ raise ValueError(f"units is not valid (given: {units})")
193
+ self.units = units
194
+ super().__init__(default=default, **params)
195
+
196
+
197
+ class AESOptString(BaseRefFunc, pm.String):
198
+ __slots__ = ["default_ref"]
199
+
200
+ _slot_defaults = dict(
201
+ pm.String._slot_defaults,
202
+ default=None,
203
+ allow_None=True,
204
+ default_ref=None,
205
+ )
206
+
207
+ @typing.overload
208
+ def __init__(
209
+ self,
210
+ default="",
211
+ *,
212
+ regex=None,
213
+ doc=None,
214
+ label=None,
215
+ precedence=None,
216
+ instantiate=False,
217
+ constant=False,
218
+ readonly=False,
219
+ pickle_default_value=True,
220
+ allow_None=False,
221
+ per_instance=True,
222
+ allow_refs=False,
223
+ nested_refs=False,
224
+ default_ref=None,
225
+ ): ...
226
+
227
+ def __init__(self, default=None, **param):
228
+ super().__init__(default=default, **param)
229
+
230
+
231
+ class AESOptNumber(BaseRefFuncUnits, pm.Number):
232
+ __slots__ = ["units", "default_ref"]
233
+
234
+ _slot_defaults = dict(
235
+ pm.Number._slot_defaults,
236
+ default=None,
237
+ units=None,
238
+ allow_None=True,
239
+ default_ref=None,
240
+ )
241
+
242
+ @typing.overload
243
+ def __init__(
244
+ self,
245
+ default=None,
246
+ *,
247
+ bounds=None,
248
+ softbounds=None,
249
+ inclusive_bounds=(True, True),
250
+ is_instance=True,
251
+ allow_None=False,
252
+ doc=None,
253
+ label=None,
254
+ precedence=None,
255
+ instantiate=True,
256
+ constant=False,
257
+ readonly=False,
258
+ pickle_default_value=True,
259
+ per_instance=True,
260
+ allow_refs=False,
261
+ nested_refs=False,
262
+ units=None,
263
+ default_ref=None,
264
+ ): ...
265
+
266
+ def __init__(self, default=None, **param):
267
+ super().__init__(default=default, **param)
268
+
269
+ def _validate_value(self, val, allow_None):
270
+ if isinstance(val, np.ndarray):
271
+ raise ValueError(
272
+ f"{pm._utils._validate_error_prefix(self)} only takes numeric values, "
273
+ f"not {type(val)}."
274
+ )
275
+ return super()._validate_value(val, allow_None)
276
+
277
+ def _validate_bounds(self, val, bounds, inclusive_bounds):
278
+ if isinstance(val, (Reference, Function)):
279
+ return
280
+ return super()._validate_bounds(val, bounds, inclusive_bounds)
281
+
282
+
283
+ class AESOptInteger(AESOptNumber, pm.Integer):
284
+ _slot_defaults = dict(
285
+ pm.Integer._slot_defaults,
286
+ default=None,
287
+ units=None,
288
+ allow_None=True,
289
+ default_ref=None,
290
+ )
291
+
292
+
293
+ class AESOptArray(BaseRefFuncUnits, pm.Array):
294
+ __slots__ = [
295
+ "bounds",
296
+ "softbounds",
297
+ "inclusive_bounds",
298
+ "units",
299
+ "dtype",
300
+ "shape",
301
+ "default_full",
302
+ "default_interp",
303
+ "default_ref",
304
+ ]
305
+
306
+ _slot_defaults = dict(
307
+ pm.Array._slot_defaults,
308
+ default=None,
309
+ bounds=None,
310
+ softbounds=None,
311
+ inclusive_bounds=(True, True),
312
+ units=None,
313
+ dtype=np.number,
314
+ shape=None,
315
+ default_full=None,
316
+ default_interp=None,
317
+ default_ref=None,
318
+ )
319
+
320
+ @typing.overload
321
+ def __init__(
322
+ self,
323
+ default=None,
324
+ *,
325
+ bounds=None,
326
+ softbounds=None,
327
+ inclusive_bounds=(True, True),
328
+ is_instance=True,
329
+ allow_None=False,
330
+ doc=None,
331
+ label=None,
332
+ precedence=None,
333
+ instantiate=True,
334
+ constant=False,
335
+ readonly=False,
336
+ pickle_default_value=True,
337
+ per_instance=True,
338
+ allow_refs=False,
339
+ nested_refs=False,
340
+ units=None,
341
+ dtype=np.number,
342
+ shape=None,
343
+ default_full=None,
344
+ default_interp=None,
345
+ default_ref=None,
346
+ ): ...
347
+
348
+ def __init__(
349
+ self,
350
+ default=pm.Undefined,
351
+ *,
352
+ bounds=pm.Undefined,
353
+ softbounds=pm.Undefined,
354
+ inclusive_bounds=pm.Undefined,
355
+ dtype=np.number,
356
+ shape=pm.Undefined,
357
+ default_full=pm.Undefined,
358
+ default_interp=pm.Undefined,
359
+ default_ref=pm.Undefined,
360
+ **params,
361
+ ):
362
+ """Numeric numpy array. Adds bound checking, shape, dtype, units, etc. A direct child of `param.Array`
363
+ For `bounds`, `softbounds` and `inclusive_bounds` see `param.Number`.
364
+ `default` can either be a string with the variable to default to or it can a a callable with one argument which is going to be the Parameterized instance (e.g. `lambda self: self._param__private.values["x"]` will make the variable default to the parameter "x")
365
+ `units` should be a `str` following [OpenMDAO: Specifying Units for Variables](https://openmdao.org/newdocs/versions/latest/features/core_features/working_with_components/units.html?highlight=units)
366
+ `dtype` should be a numpy dtype class. Default to `numpy.number`.
367
+ `shape` should be one of `int`: static shape, `str`: shape like another parameter (using object-path), `tuple`: multi-dim, where each dim can be either `int` or `str`.
368
+ `default_full` should be a tuple of length 2 with items: 1: shape-like input (`int`, `str` or `tuple`) see above, 2: value to fill with (`float`, `int`)
369
+ `default_interp` should be a tuple of length 3 with items (each can be either `numpy.ndarray` or `str`): 1: new grid (`x`), 2: old grid (`xp`), 3: old values (`yp`).
370
+ """
371
+ self.bounds = bounds
372
+ self.softbounds = softbounds
373
+ self.inclusive_bounds = inclusive_bounds
374
+ self.dtype = dtype
375
+ if (not default_full is pm.Undefined) and (
376
+ not isinstance(default_full, tuple)
377
+ or not self._validate_shape(default_full[0])
378
+ or not isinstance(default_full[1], (float, int, str))
379
+ ):
380
+ raise ValueError(
381
+ f"default_full must be a tuple of length 2, with the first element should be shape compliant (tuple, str, int) and the second the value to fill with (given: {default_full})"
382
+ )
383
+ if not default_full is pm.Undefined and isinstance(default_full, tuple):
384
+ self.default_full = [
385
+ Reference(el) if isinstance(el, str) else el for el in default_full
386
+ ]
387
+ if isinstance(self.default_full[0], tuple):
388
+ self.default_full[0] = tuple(
389
+ Reference(el) if isinstance(el, str) else el
390
+ for el in default_full[0]
391
+ )
392
+ self.default_full = tuple(self.default_full)
393
+ else:
394
+ self.default_full = default_full
395
+ if (not default_interp is pm.Undefined) and (
396
+ not isinstance(default_interp, tuple)
397
+ or not all([isinstance(el, (str, np.ndarray)) for el in default_interp])
398
+ ):
399
+ raise ValueError(
400
+ f"default_interp must be a tuple of length 3, with elements of either str, numpy.ndarray (given: {default_interp})"
401
+ )
402
+ if not default_interp is pm.Undefined and isinstance(default_interp, tuple):
403
+ self.default_interp = tuple(
404
+ Reference(el) if isinstance(el, str) else el for el in default_interp
405
+ )
406
+ else:
407
+ self.default_interp = default_interp
408
+ if shape is pm.Undefined:
409
+ if self._is_ref(default):
410
+ shape = default
411
+ elif isinstance(self.default_full, tuple):
412
+ shape = self.default_full[0]
413
+ elif isinstance(self.default_interp, tuple):
414
+ if isinstance(self.default_interp[0], Reference):
415
+ shape = self.default_interp[0]
416
+ else:
417
+ shape = self.default_interp[0].shape
418
+ elif not (default_ref is pm.Undefined or default_ref is None):
419
+ shape = default_ref
420
+ if isinstance(shape, str):
421
+ shape = Reference(shape)
422
+ elif isinstance(shape, tuple):
423
+ shape = tuple(Reference(el) if isinstance(el, str) else el for el in shape)
424
+ self._validate_shape(shape)
425
+ self.shape = shape
426
+ if self._is_ref(default) or self._is_func(default):
427
+ params["instantiate"] = False
428
+ super().__init__(default=default, default_ref=default_ref, **params)
429
+
430
+ def _validate_shape(self, shape):
431
+ if not (shape is pm.Undefined or shape is None):
432
+ if isinstance(shape, tuple):
433
+ if not all([isinstance(el, (Reference, str, int)) for el in shape]):
434
+ raise ValueError(
435
+ f"shape as a tuple need to have item-types of either str,int. ([type(el) for el in shape])={[type(el) for el in shape]})"
436
+ )
437
+ elif not isinstance(shape, (Reference, str, int)):
438
+ raise ValueError(
439
+ f"shape need to be of type tuple, str, int (given: type(shape)={type(shape)})"
440
+ )
441
+ return True
442
+
443
+ def _validate_bounds(self, val, bounds, inclusive_bounds):
444
+ if (
445
+ bounds is None
446
+ or (val is None and self.allow_None)
447
+ or callable(val)
448
+ or isinstance(val, str)
449
+ ):
450
+ return
451
+ vmin, vmax = bounds
452
+ incmin, incmax = inclusive_bounds
453
+ if vmax is not None:
454
+ if incmax is True:
455
+ if np.any(val > vmax):
456
+ raise ValueError(
457
+ f"{pm.parameters._validate_error_prefix(self)} must be at most "
458
+ f"{vmax}, not {val}."
459
+ )
460
+ else:
461
+ if np.any(val >= vmax):
462
+ raise ValueError(
463
+ f"{pm.parameters._validate_error_prefix(self)} must be less than "
464
+ f"{vmax}, not {val}."
465
+ )
466
+
467
+ if vmin is not None:
468
+ if incmin is True:
469
+ if np.any(val < vmin):
470
+ raise ValueError(
471
+ f"{pm.parameters._validate_error_prefix(self)} must be at least "
472
+ f"{vmin}, not {val}."
473
+ )
474
+ else:
475
+ if np.any(val <= vmin):
476
+ raise ValueError(
477
+ f"{pm.parameters._validate_error_prefix(self)} must be greater than "
478
+ f"{vmin}, not {val}."
479
+ )
480
+
481
+ def _validate_dtype(self, val, dtype):
482
+ if isinstance(val, np.ndarray):
483
+ if not np.issubdtype(val.dtype, dtype):
484
+ raise ValueError(
485
+ f"Array dtype must be a subclass of {self.dtype} but it is {val.dtype} (is validated with `numpy.issubdtype`)"
486
+ )
487
+
488
+ def _validate_class_(self, val, class_, is_instance):
489
+ if val is None:
490
+ return
491
+ elif isinstance(val, (Reference, Function)):
492
+ return
493
+ else:
494
+ if not (isinstance(val, class_)):
495
+ class_name = class_.__name__
496
+ raise ValueError(
497
+ f"{pm.parameters._validate_error_prefix(self)} value must be an instance of {class_name}, not {val!r}."
498
+ )
499
+
500
+ def _validate(self, val):
501
+ super()._validate(val)
502
+ self._validate_dtype(val, self.dtype)
503
+ self._validate_bounds(val, self.bounds, self.inclusive_bounds)
504
+
505
+ def __get__(self, obj, objtype):
506
+ out = super().__get__(obj, objtype)
507
+ if out is None:
508
+ if not self.default_full is None:
509
+ shape_ref = obj.get_shape_ref(self)
510
+ if not shape_ref is None:
511
+ if isinstance(self.default_full[1], Reference):
512
+ val = obj[self.default_full[1]]
513
+ else:
514
+ val = self.default_full[1]
515
+ return np.full(shape_ref, val)
516
+ elif not self.default_interp is None:
517
+ return obj.interp(*self.default_interp)
518
+ return out
519
+
520
+ def __set__(self, obj, val):
521
+ if not isinstance(val, str) and np.isscalar(val):
522
+ if self.shape is not None:
523
+ val = np.full(obj.get_shape_ref(self), val)
524
+ else:
525
+ raise ValueError(
526
+ f"Scaler values can only be assigned to AESOptArray if it has a defined shape (given: val={val})"
527
+ )
528
+ return super().__set__(obj, val)
529
+
530
+
531
+ class AESOptBoolean(BaseRefFunc, pm.Boolean):
532
+
533
+ __slots__ = ["default_ref"]
534
+
535
+ _slot_defaults = dict(
536
+ pm.Boolean._slot_defaults,
537
+ default=None,
538
+ allow_None=True,
539
+ default_ref=None,
540
+ )
541
+
542
+ @typing.overload
543
+ def __init__(
544
+ self,
545
+ default=None,
546
+ *,
547
+ allow_None=False,
548
+ doc=None,
549
+ label=None,
550
+ precedence=None,
551
+ instantiate=False,
552
+ constant=False,
553
+ readonly=False,
554
+ pickle_default_value=True,
555
+ per_instance=True,
556
+ allow_refs=False,
557
+ nested_refs=False,
558
+ default_ref=None,
559
+ ): ...
560
+
561
+ def __init__(self, default=None, **param):
562
+ super().__init__(default=default, **param)
563
+
564
+ def _validate_value(self, val, allow_None):
565
+ if (
566
+ not (
567
+ val is None
568
+ or isinstance(val, bool)
569
+ or self._is_ref(val)
570
+ or self._is_func(val)
571
+ )
572
+ and self.default_ref is None
573
+ ):
574
+ raise ValueError(
575
+ f"Either `value`, `default` or `default_ref` need to be set (given: {val})"
576
+ )
577
+ if self._is_ref(val) or self._is_func(val) or self.default_ref is not None:
578
+ return
579
+ super()._validate_value(val, allow_None)
580
+
581
+
582
+ class Reference:
583
+ def __init__(self, path: str) -> None:
584
+ if path.startswith("$ref "):
585
+ path = path[5:]
586
+ self.is_valid_object_path(path)
587
+ self.path = path
588
+
589
+ def is_valid_object_path(self, path):
590
+ if not path.startswith("."):
591
+ raise ValueError(
592
+ f"Object path need to start with `.` or `$ref ` (given: path={path})"
593
+ )
594
+ if " " in path or "\t" in path:
595
+ raise ValueError(
596
+ f"Object path can not contain spaces or tabs (given: path={path})"
597
+ )
598
+ return True
599
+
600
+ def __str__(self):
601
+ return f"$ref {self.path}"
602
+
603
+ def __repr__(self):
604
+ return self.path
605
+
606
+ def __eq__(self, val):
607
+ return self.path == val
608
+
609
+
610
+ class Function:
611
+ def __init__(self, method) -> None:
612
+ if isinstance(method, str) and method.startswith("$function "):
613
+ method_source = method[10:]
614
+ if method_source.startswith("def "):
615
+ method_name = method_source[4:].split("(")[0]
616
+ exec(method_source)
617
+ method = eval(method_name)
618
+ else:
619
+ method = eval(method_source)
620
+ method.method_source = method_source
621
+
622
+ if not callable(method):
623
+ raise ValueError(
624
+ f"`method` need to be a `callable` or string starting with `$function ` (given: method={method})"
625
+ )
626
+ self.method = method
627
+
628
+ @property
629
+ def source_str(self):
630
+ if hasattr(self.method, "method_source"):
631
+ return self.method.method_source
632
+ method_source = getsource(self.method).strip()
633
+ if "lambda" in method_source:
634
+ method_source = "lambda" + "lambda".join(method_source.split("lambda")[1:])
635
+ return method_source
636
+
637
+ def __str__(self):
638
+ return f"$function {self.source_str}"
639
+
640
+ def __repr__(self) -> str:
641
+ return f"<$function id={id(self.method)}>"