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,369 @@
1
+ import os
2
+
3
+ import numpy as np
4
+ import pytest
5
+
6
+ from aesoptparam.utils.units import (
7
+ NumberDict,
8
+ PhysicalUnit,
9
+ _find_unit,
10
+ add_offset_unit,
11
+ add_unit,
12
+ convert_units,
13
+ import_library,
14
+ simplify_unit,
15
+ unit_conversion,
16
+ )
17
+
18
+
19
+ def test_UnknownKeyGives0():
20
+ # a NumberDict instance should initilize using integer and non-integer indices
21
+ # a NumberDict instance should initilize all entries with an initial
22
+ # value of 0
23
+ x = NumberDict()
24
+
25
+ # integer test
26
+ assert x[0] == 0
27
+
28
+ # string test
29
+ assert x["t"] == 0
30
+
31
+
32
+ def test__add__KnownValues():
33
+ # __add__ should give known result with known input
34
+ # for non-string data types, addition must be commutative
35
+
36
+ x = NumberDict()
37
+ y = NumberDict()
38
+ x["t1"], x["t2"] = 1, 2
39
+ y["t1"], y["t2"] = 2, 1
40
+
41
+ result1, result2 = x + y, y + x
42
+ assert np.all((3, 3) == (result1["t1"], result1["t2"]))
43
+ assert np.all((3, 3) == (result2["t1"], result2["t2"]))
44
+
45
+
46
+ def test__sub__KnownValues():
47
+ # __sub__ should give known result with known input
48
+ # commuting the input should result in equal magnitude, opposite sign
49
+
50
+ x = NumberDict()
51
+ y = NumberDict()
52
+ x["t1"], x["t2"] = 1, 2
53
+ y["t1"], y["t2"] = 2, 1
54
+
55
+ result1, result2 = x - y, y - x
56
+ assert np.all((-1, 1) == (result1["t1"], result1["t2"]))
57
+ assert np.all((1, -1) == (result2["t1"], result2["t2"]))
58
+
59
+
60
+ def test__mul__KnownValues():
61
+ # __mul__ should give known result with known input
62
+
63
+ x = NumberDict([("t1", 1), ("t2", 2)])
64
+ y = 10
65
+
66
+ result1, result2 = x * y, y * x
67
+ assert np.all((10, 20) == (result1["t1"], result1["t2"]))
68
+ assert np.all((10, 20) == (result2["t1"], result2["t2"]))
69
+
70
+
71
+ def test__div__KnownValues():
72
+ # __div__ should give known result with known input
73
+
74
+ x = NumberDict()
75
+ x = NumberDict([("t1", 1), ("t2", 2)])
76
+ y = 10.0
77
+ result1 = x / y
78
+ assert np.all((0.1, 0.20) == (result1["t1"], result1["t2"]))
79
+
80
+
81
+ with open(
82
+ os.path.join(os.path.dirname(__file__), "../utils/unit_library.ini")
83
+ ) as default_lib:
84
+ _unitLib = import_library(default_lib)
85
+
86
+
87
+ def _get_powers(**powdict):
88
+ powers = [0] * len(_unitLib.base_types)
89
+ for name, power in powdict.items():
90
+ powers[_unitLib.base_types[name]] = power
91
+ return powers
92
+
93
+
94
+ def test_repr_str():
95
+ # __repr__should return a string which could be used to contruct the
96
+ # unit instance, __str__ should return a string with just the unit
97
+ # name for str
98
+
99
+ u = _find_unit("d")
100
+
101
+ assert repr(u) == "PhysicalUnit({'d': 1},86400.0,%s,0.0)" % _get_powers(time=1)
102
+ assert str(u) == "<PhysicalUnit d>"
103
+
104
+
105
+ def test_cmp():
106
+ # should error for incompatible units, if they are compatible then it
107
+ # should cmp on their factors
108
+
109
+ x = _find_unit("d")
110
+ y = _find_unit("s")
111
+ z = _find_unit("ft")
112
+
113
+ assert x > y
114
+ assert x == x
115
+ assert y < x
116
+
117
+ with pytest.raises(TypeError, match="Units 'd' and 'ft' are incompatible."):
118
+ x < z
119
+
120
+
121
+ known__mul__Values = (
122
+ ("1m", "5m", 5),
123
+ ("1cm", "1cm", 1),
124
+ ("1cm", "5m", 5),
125
+ ("7km", "1m", 7),
126
+ )
127
+
128
+
129
+ def test_multiply():
130
+ # multiplication should error for units with offsets
131
+
132
+ x = _find_unit("g")
133
+ y = _find_unit("s")
134
+ z = _find_unit("degC")
135
+
136
+ assert x * y == PhysicalUnit(
137
+ {"s": 1, "kg": 1}, 0.001, _get_powers(mass=1, time=1), 0
138
+ )
139
+ assert y * x == PhysicalUnit(
140
+ {"s": 1, "kg": 1}, 0.001, _get_powers(mass=1, time=1), 0
141
+ )
142
+
143
+ with pytest.raises(
144
+ TypeError,
145
+ match="Can't multiply units: either 'g' or 'degC' has a non-zero offset.",
146
+ ):
147
+ x * z
148
+
149
+
150
+ def test_division():
151
+ # division should error when working with offset units
152
+
153
+ w = _find_unit("kg")
154
+ x = _find_unit("g")
155
+ y = _find_unit("s")
156
+ z = _find_unit("degC")
157
+
158
+ quo = w / x
159
+ quo2 = x / y
160
+
161
+ assert np.all(quo == PhysicalUnit({"kg": 1, "g": -1}, 1000.0, _get_powers(), 0))
162
+ assert np.all(
163
+ quo2 == PhysicalUnit({"s": -1, "g": 1}, 0.001, _get_powers(mass=1, time=-1), 0),
164
+ )
165
+ quo = y / 2.0
166
+ assert np.all(quo == PhysicalUnit({"s": 1, "2.0": -1}, 0.5, _get_powers(time=1), 0))
167
+ quo = 2.0 / y
168
+ assert np.all(quo == PhysicalUnit({"s": -1, "2.0": 1}, 2, _get_powers(time=-1), 0))
169
+ with pytest.raises(
170
+ TypeError,
171
+ match="Can't divide units: either 'g' or 'degC' has a non-zero offset.",
172
+ ):
173
+ x / z
174
+
175
+
176
+ known__pow__Values = (("1V", 3), ("1m", 2), ("1.1m", 2))
177
+
178
+
179
+ def test_pow():
180
+ # power should error for offest units and for non-integer powers
181
+
182
+ x = _find_unit("m")
183
+ y = _find_unit("degF")
184
+
185
+ z = x**3
186
+ assert np.all(z == _find_unit("m**3"))
187
+ x = z ** (1.0 / 3.0) # checks inverse integer units
188
+ assert np.all(x == _find_unit("m"))
189
+ z = 5.2 * x**2 # Test with value
190
+ x = z ** (1.0 / 2.0)
191
+ assert np.all(x == (np.sqrt(5.2) * _find_unit("m")))
192
+ x = _find_unit("m")
193
+
194
+ # test offset units:
195
+ with pytest.raises(
196
+ TypeError,
197
+ match="Can't exponentiate unit 'degF' because it has a non-zero offset.",
198
+ ):
199
+ y**17
200
+
201
+ # test non-integer powers
202
+ with pytest.raises(
203
+ TypeError,
204
+ match="Can't exponentiate unit 'm': only integer and inverse integer exponents are allowed.",
205
+ ):
206
+ x**1.2
207
+
208
+ with pytest.raises(
209
+ TypeError,
210
+ match="Can't exponentiate unit 'm': only integer and inverse integer exponents are allowed.",
211
+ ):
212
+ x ** (5.0 / 2.0)
213
+
214
+
215
+ def test_compare():
216
+ x = _find_unit("m")
217
+ y = _find_unit("degF")
218
+
219
+ with pytest.raises(TypeError, match="Units 'm' and 'degF' are incompatible."):
220
+ x > y
221
+
222
+
223
+ known__conversion_factor_to__Values = (
224
+ ("1m", "1cm", 100),
225
+ ("1s", "1ms", 1000),
226
+ ("1ms", "1s", 0.001),
227
+ )
228
+
229
+
230
+ def test_conversion_tuple_to():
231
+ # test_conversion_tuple_to shoudl error when units have different power
232
+ # lists
233
+
234
+ w = _find_unit("cm")
235
+ x = _find_unit("m")
236
+ y = _find_unit("degF")
237
+ z1 = _find_unit("degC")
238
+
239
+ # check for non offset units
240
+ assert np.all(w.conversion_tuple_to(x) == (1 / 100.0, 0))
241
+
242
+ # check for offset units
243
+ result = y.conversion_tuple_to(z1)
244
+ np.testing.assert_almost_equal(result[0], 0.556, 3)
245
+ np.testing.assert_almost_equal(result[1], -32.0, 3)
246
+ np.testing.assert_almost_equal
247
+
248
+ # check for incompatible units
249
+ with pytest.raises(
250
+ TypeError,
251
+ match="Units 'm' and 'degC' are incompatible.",
252
+ ):
253
+ x.conversion_tuple_to(z1)
254
+
255
+
256
+ def test_name():
257
+ # name should return a mathematically correct representation of the
258
+ # unit
259
+ x1 = _find_unit("m")
260
+ x2 = _find_unit("kg")
261
+ y = 1 / x1
262
+ assert np.all(y.name() == "1/m")
263
+ y = 1 / x1 / x1
264
+ assert np.all(y.name() == "1/m**2")
265
+ y = x1**2
266
+ assert np.all(y.name() == "m**2")
267
+ y = x2 / (x1**2)
268
+ assert np.all(y.name() == "kg/m**2")
269
+
270
+
271
+ def test_unit_conversion():
272
+ assert np.all(unit_conversion("km", "m") == (1000.0, 0.0))
273
+
274
+ with pytest.raises(
275
+ ValueError,
276
+ match="The units '1.0' are invalid.",
277
+ ):
278
+ unit_conversion("km", 1.0)
279
+
280
+
281
+ def test_unit_simplification():
282
+ test_strings = [
283
+ "ft/s*s",
284
+ "m/s*s",
285
+ "m * ft * cm / km / m",
286
+ "s/s",
287
+ "m ** 7 / m ** 5",
288
+ ]
289
+
290
+ correct_strings = ["ft", "m", "ft*cm/km", None, "m**2"]
291
+
292
+ for test_str, correct_str in zip(test_strings, correct_strings):
293
+ simplified_str = simplify_unit(test_str)
294
+ assert np.all(simplified_str == correct_str)
295
+
296
+
297
+ def test_atto_seconds():
298
+ # The unit 'as' was bugged because it is a python keyword.
299
+
300
+ fact = unit_conversion("s", "as")
301
+ assert abs(fact[0] - 1e18) / 1e18 < 1e-15
302
+
303
+ # Make sure regex for 'as' doesn't pick up partial words.
304
+ fact = unit_conversion("aslug*as*as", "aslug*zs*zs")
305
+ assert abs(fact[0] - 1e6) / 1e6 < 1e-15
306
+
307
+ # Make sure simplification works.
308
+ simple = simplify_unit("m*as/as")
309
+ assert np.all(simple == "m")
310
+
311
+ simple = simplify_unit("as**6/as**4")
312
+ assert np.all(simple == "as**2")
313
+
314
+
315
+ def test_add_unit():
316
+ with pytest.raises(
317
+ KeyError,
318
+ match="Unit 'ft' already defined with different factor or powers.",
319
+ ):
320
+ add_unit("ft", "20*m")
321
+
322
+ with pytest.raises(
323
+ KeyError,
324
+ match="Unit 'degR' already defined with different factor or powers.",
325
+ ):
326
+ add_offset_unit("degR", "degK", 20, 10)
327
+
328
+
329
+ def test_various():
330
+ # Test .in_base_units
331
+ x = _find_unit("kg*Pa")
332
+ assert x.in_base_units() == _find_unit("kg**2/(m*s**2)")
333
+ x = _find_unit("m**-1")
334
+ assert x.in_base_units() == _find_unit("1/m")
335
+
336
+ # .is_dimensionless
337
+ x = _find_unit("Pa")
338
+ assert not x.is_dimensionless()
339
+ assert (x / _find_unit("N/m**2")).is_dimensionless()
340
+
341
+ # .is_angle
342
+ assert not x.is_angle()
343
+ x = _find_unit("rad")
344
+ assert x.is_angle()
345
+ x = _find_unit("rad/s")
346
+ assert not x.is_angle()
347
+
348
+ # Double letter unit prefix
349
+ x = _find_unit("dam")
350
+ assert x == 10 * _find_unit("m")
351
+
352
+ # Invalid prefix
353
+ units = "xxm"
354
+ with pytest.raises(ValueError, match=f"The units '{units}' are invalid."):
355
+ x = _find_unit(units, True)
356
+ # Should not fail without flag
357
+ x = _find_unit(units)
358
+
359
+ # convert_unit
360
+ ounits = "Pa"
361
+ val = 4 * _find_unit(ounits)
362
+ nunits = "hPa"
363
+ # Return current val
364
+ nval = convert_units(val, None)
365
+ assert nval == val
366
+
367
+ # simplify_units
368
+ out = simplify_unit(None)
369
+ assert out is None
@@ -0,0 +1,147 @@
1
+ import os
2
+
3
+ import numpy as np
4
+ import pytest
5
+
6
+ from aesoptparam.utils.html_repr import json_data_render
7
+ from aesoptparam.utils.json_utils import is_valid_json, read_json, write_json
8
+
9
+ file_path = os.path.dirname(__file__)
10
+
11
+
12
+ def test_read_json():
13
+ data = read_json(os.path.join(file_path, "dummy_instance.json"))
14
+
15
+ assert isinstance(data, dict)
16
+ assert len(data) == 15
17
+ assert data["version"] == "0.0.0"
18
+ assert isinstance(data["d"], np.ndarray)
19
+ assert isinstance(data["e"], np.ndarray)
20
+ assert isinstance(data["sub1"], dict)
21
+ assert len(data["sub1"]) == 5
22
+ assert isinstance(data["sub1"]["b"], np.ndarray)
23
+
24
+ data = read_json(os.path.join(file_path, "dummy_instance.json"), False)
25
+
26
+ assert isinstance(data, dict)
27
+ assert len(data) == 15
28
+ assert data["version"] == "0.0.0"
29
+ assert isinstance(data["d"], list)
30
+ assert isinstance(data["e"], list)
31
+ assert isinstance(data["sub1"], dict)
32
+ assert len(data["sub1"]) == 5
33
+ assert isinstance(data["sub1"]["b"], list)
34
+
35
+
36
+ def test_write_json(tmp_path):
37
+ # Input data
38
+ data = dict(
39
+ a="a",
40
+ b=np.int16(2),
41
+ c=np.float16(5.1),
42
+ d=np.array([0, 2.0, 3.1]),
43
+ e=dict(a="a", b=2, c=5.1, d=np.array([0, 2.0, 3.1])),
44
+ f=[
45
+ dict(a="a", b=2, c=5.1, d=np.array([0, 2.0, 3.1])),
46
+ dict(a="a", b=2, c=5.1, d=np.array([0, 2.0, 3.1])),
47
+ ],
48
+ g=np.bool_(True),
49
+ )
50
+ filename = os.path.join(tmp_path, "dummy.json")
51
+
52
+ # Write data file
53
+ write_json(filename, data)
54
+
55
+ # Read data back in
56
+ _data = read_json(filename)
57
+ assert isinstance(_data["a"], str)
58
+ assert isinstance(_data["b"], int)
59
+ assert isinstance(_data["c"], float)
60
+ assert isinstance(_data["d"], np.ndarray)
61
+ assert isinstance(_data["e"]["a"], str)
62
+ assert isinstance(_data["e"]["b"], int)
63
+ assert isinstance(_data["e"]["c"], float)
64
+ assert isinstance(_data["e"]["d"], np.ndarray)
65
+ assert isinstance(_data["f"][0]["a"], str)
66
+ assert isinstance(_data["f"][0]["b"], int)
67
+ assert isinstance(_data["f"][0]["c"], float)
68
+ assert isinstance(_data["f"][0]["d"], np.ndarray)
69
+ assert isinstance(_data["f"][1]["a"], str)
70
+ assert isinstance(_data["f"][1]["b"], int)
71
+ assert isinstance(_data["f"][1]["c"], float)
72
+ assert isinstance(_data["f"][1]["d"], np.ndarray)
73
+
74
+ _data = read_json(filename, False)
75
+
76
+ # Raise if not converting numpy
77
+ with pytest.raises(TypeError, match="is not JSON serializable"):
78
+ write_json(filename, data, False)
79
+
80
+ # Raise if not converting numpy
81
+ with pytest.raises(TypeError, match="is not JSON serializable"):
82
+ write_json(filename, object())
83
+
84
+ # Write data file
85
+ write_json(filename, _data, False)
86
+
87
+
88
+ def test_is_valid_json():
89
+ data = dict(
90
+ a="a",
91
+ b=2,
92
+ c=5.1,
93
+ d=np.array([0, 2.0, 3.1]),
94
+ e=dict(a="a", b=2, c=5.1, d=np.array([0, 2.0, 3.1])),
95
+ f=[
96
+ dict(a="a", b=2, c=5.1, d=np.array([0, 2.0, 3.1])),
97
+ dict(a="a", b=2, c=5.1, d=np.array([0, 2.0, 3.1])),
98
+ ],
99
+ )
100
+
101
+ assert is_valid_json(data, True)
102
+
103
+ assert is_valid_json(data) is False
104
+
105
+ data["d"] = data["d"].tolist()
106
+ data["e"]["d"] = data["e"]["d"].tolist()
107
+ data["f"][0]["d"] = data["f"][0]["d"].tolist()
108
+ data["f"][1]["d"] = data["f"][1]["d"].tolist()
109
+
110
+ assert is_valid_json(data)
111
+
112
+
113
+ def test_json2html():
114
+ data = dict(
115
+ a=0.2,
116
+ b=[0.1, 0.2],
117
+ c=np.array([0.2, 0.3]),
118
+ d=dict(a=0.1, b=dict(a=0.1)),
119
+ e=[
120
+ dict(a=0.2),
121
+ dict(a=0.3),
122
+ dict(a=[0.1, 0.2, 0.3]),
123
+ dict(a=[dict(a=0.1), 0.5, "str"]),
124
+ ],
125
+ )
126
+ json_data_render(data)._repr_html_()
127
+ json_data_render(data, True)._repr_html_()
128
+ json_data_render(data, fields=["e", 3, "a", 0])._repr_html_()
129
+ json_data_render(data, fields=[["e", 2, "a"], ["e", 3, "a", 0]])._repr_html_()
130
+ json_data_render(data, True, fields=["e", 3, "a", 0])._repr_html_()
131
+ json_data_render(data, True, fields=[["d", "b"], ["e", 3, "a", 0]])._repr_html_()
132
+
133
+ json_data_render([data])._repr_html_()
134
+ json_data_render(5.0)._repr_html_()
135
+
136
+ big_array_dict = {
137
+ "a" + str(i): np.random.rand(*size)
138
+ for i, size in enumerate(
139
+ [(5,), (50,), (100,), (1000,), (100, 100), (10, 10, 10)]
140
+ )
141
+ }
142
+ out1 = json_data_render(big_array_dict)._repr_html_()
143
+ out2 = json_data_render(big_array_dict, max_arr_size=-1)._repr_html_()
144
+ assert out1.__sizeof__() < out2.__sizeof__()
145
+
146
+ with pytest.raises(ValueError, match="`fields` has to be"):
147
+ json_data_render(data, fields="test")._repr_html_()
@@ -0,0 +1,63 @@
1
+ from .html_repr import display_json_data
2
+ from .json_utils import read_json, write_json
3
+
4
+
5
+ def copy_param(param, exclude=None, update=None):
6
+ """Copy a Parameter, either a standalone `Parameter` (`AESOptNumber`, `AESOptArray`, ..) or from a `Parametrized` class (using `par_class.param.param_name`)
7
+
8
+ Parameters
9
+ ----------
10
+ param : `Parameter`
11
+ Parameter instance
12
+ exclude : list, optional
13
+ List of entries to exclude, by default None
14
+ update : dict, optional
15
+ Dict with values to update, by default None
16
+
17
+ Returns
18
+ -------
19
+ `Parameter`
20
+ Copy of parameter instance. Optionally without entries from `exclude` or updated values from `update`
21
+ """
22
+ if exclude is None:
23
+ exclude = []
24
+ if update is None:
25
+ update = dict()
26
+ kwargs = dict()
27
+ for name in param._slot_defaults:
28
+ val = getattr(param, name)
29
+ if not name in exclude and val is not None:
30
+ kwargs[name] = val
31
+ for name, val in update.items():
32
+ kwargs[name] = val
33
+
34
+ return type(param)(**kwargs)
35
+
36
+
37
+ def copy_param_ref(param, ref, exclude=None, update=None):
38
+ """Similar to `copy_param` but adding "default" to `exclude` and "default_ref" to `update` with value of `ref`
39
+
40
+ Parameters
41
+ ----------
42
+ param : `Parameter`
43
+ Parameter instance
44
+ ref : str
45
+ String with the reference path
46
+ exclude : list, optional
47
+ List of entries to exclude, by default None
48
+ update : dict, optional
49
+ Dict with values to update, by default None
50
+
51
+ Returns
52
+ -------
53
+ `Parameter`
54
+ Copy of parameter instance. Optionally without entries from `exclude` or updated values from `update`
55
+ """
56
+ if exclude is None:
57
+ exclude = []
58
+ if update is None:
59
+ update = dict()
60
+ if not "default" in exclude:
61
+ exclude.append("default")
62
+ update["default_ref"] = ref
63
+ return copy_param(param, exclude, update)