pyvale 2025.4.0__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 pyvale might be problematic. Click here for more details.

Files changed (157) hide show
  1. pyvale/__init__.py +75 -0
  2. pyvale/core/__init__.py +7 -0
  3. pyvale/core/analyticmeshgen.py +59 -0
  4. pyvale/core/analyticsimdatafactory.py +63 -0
  5. pyvale/core/analyticsimdatagenerator.py +160 -0
  6. pyvale/core/camera.py +146 -0
  7. pyvale/core/cameradata.py +64 -0
  8. pyvale/core/cameradata2d.py +82 -0
  9. pyvale/core/cameratools.py +328 -0
  10. pyvale/core/cython/rastercyth.c +32267 -0
  11. pyvale/core/cython/rastercyth.py +636 -0
  12. pyvale/core/dataset.py +250 -0
  13. pyvale/core/errorcalculator.py +112 -0
  14. pyvale/core/errordriftcalc.py +146 -0
  15. pyvale/core/errorintegrator.py +339 -0
  16. pyvale/core/errorrand.py +614 -0
  17. pyvale/core/errorsysdep.py +331 -0
  18. pyvale/core/errorsysfield.py +407 -0
  19. pyvale/core/errorsysindep.py +905 -0
  20. pyvale/core/experimentsimulator.py +99 -0
  21. pyvale/core/field.py +136 -0
  22. pyvale/core/fieldconverter.py +154 -0
  23. pyvale/core/fieldsampler.py +112 -0
  24. pyvale/core/fieldscalar.py +167 -0
  25. pyvale/core/fieldtensor.py +221 -0
  26. pyvale/core/fieldtransform.py +384 -0
  27. pyvale/core/fieldvector.py +215 -0
  28. pyvale/core/generatorsrandom.py +528 -0
  29. pyvale/core/imagedef2d.py +566 -0
  30. pyvale/core/integratorfactory.py +241 -0
  31. pyvale/core/integratorquadrature.py +192 -0
  32. pyvale/core/integratorrectangle.py +88 -0
  33. pyvale/core/integratorspatial.py +90 -0
  34. pyvale/core/integratortype.py +44 -0
  35. pyvale/core/optimcheckfuncs.py +153 -0
  36. pyvale/core/raster.py +31 -0
  37. pyvale/core/rastercy.py +76 -0
  38. pyvale/core/rasternp.py +604 -0
  39. pyvale/core/rendermesh.py +156 -0
  40. pyvale/core/sensorarray.py +179 -0
  41. pyvale/core/sensorarrayfactory.py +210 -0
  42. pyvale/core/sensorarraypoint.py +280 -0
  43. pyvale/core/sensordata.py +72 -0
  44. pyvale/core/sensordescriptor.py +101 -0
  45. pyvale/core/sensortools.py +143 -0
  46. pyvale/core/visualexpplotter.py +151 -0
  47. pyvale/core/visualimagedef.py +71 -0
  48. pyvale/core/visualimages.py +75 -0
  49. pyvale/core/visualopts.py +180 -0
  50. pyvale/core/visualsimanimator.py +83 -0
  51. pyvale/core/visualsimplotter.py +182 -0
  52. pyvale/core/visualtools.py +81 -0
  53. pyvale/core/visualtraceplotter.py +256 -0
  54. pyvale/data/__init__.py +7 -0
  55. pyvale/data/case13_out.e +0 -0
  56. pyvale/data/case16_out.e +0 -0
  57. pyvale/data/case17_out.e +0 -0
  58. pyvale/data/case18_1_out.e +0 -0
  59. pyvale/data/case18_2_out.e +0 -0
  60. pyvale/data/case18_3_out.e +0 -0
  61. pyvale/data/case25_out.e +0 -0
  62. pyvale/data/case26_out.e +0 -0
  63. pyvale/data/optspeckle_2464x2056px_spec5px_8bit_gblur1px.tiff +0 -0
  64. pyvale/examples/__init__.py +7 -0
  65. pyvale/examples/analyticdatagen/__init__.py +7 -0
  66. pyvale/examples/analyticdatagen/ex1_1_scalarvisualisation.py +38 -0
  67. pyvale/examples/analyticdatagen/ex1_2_scalarcasebuild.py +46 -0
  68. pyvale/examples/analyticdatagen/ex2_1_analyticsensors.py +83 -0
  69. pyvale/examples/ex1_1_thermal2d.py +89 -0
  70. pyvale/examples/ex1_2_thermal2d.py +111 -0
  71. pyvale/examples/ex1_3_thermal2d.py +113 -0
  72. pyvale/examples/ex1_4_thermal2d.py +89 -0
  73. pyvale/examples/ex1_5_thermal2d.py +105 -0
  74. pyvale/examples/ex2_1_thermal3d .py +87 -0
  75. pyvale/examples/ex2_2_thermal3d.py +51 -0
  76. pyvale/examples/ex2_3_thermal3d.py +109 -0
  77. pyvale/examples/ex3_1_displacement2d.py +47 -0
  78. pyvale/examples/ex3_2_displacement2d.py +79 -0
  79. pyvale/examples/ex3_3_displacement2d.py +104 -0
  80. pyvale/examples/ex3_4_displacement2d.py +105 -0
  81. pyvale/examples/ex4_1_strain2d.py +57 -0
  82. pyvale/examples/ex4_2_strain2d.py +79 -0
  83. pyvale/examples/ex4_3_strain2d.py +100 -0
  84. pyvale/examples/ex5_1_multiphysics2d.py +78 -0
  85. pyvale/examples/ex6_1_multiphysics2d_expsim.py +118 -0
  86. pyvale/examples/ex6_2_multiphysics3d_expsim.py +158 -0
  87. pyvale/examples/features/__init__.py +7 -0
  88. pyvale/examples/features/ex_animation_tools_3dmonoblock.py +83 -0
  89. pyvale/examples/features/ex_area_avg.py +89 -0
  90. pyvale/examples/features/ex_calibration_error.py +108 -0
  91. pyvale/examples/features/ex_chain_field_errs.py +141 -0
  92. pyvale/examples/features/ex_field_errs.py +78 -0
  93. pyvale/examples/features/ex_sensor_single_angle_batch.py +110 -0
  94. pyvale/examples/imagedef2d/ex_imagedef2d_todisk.py +86 -0
  95. pyvale/examples/rasterisation/ex_rastenp.py +154 -0
  96. pyvale/examples/rasterisation/ex_rastercyth_oneframe.py +220 -0
  97. pyvale/examples/rasterisation/ex_rastercyth_static_cypara.py +194 -0
  98. pyvale/examples/rasterisation/ex_rastercyth_static_pypara.py +193 -0
  99. pyvale/simcases/case00_HEX20.i +242 -0
  100. pyvale/simcases/case00_HEX27.i +242 -0
  101. pyvale/simcases/case00_TET10.i +242 -0
  102. pyvale/simcases/case00_TET14.i +242 -0
  103. pyvale/simcases/case01.i +101 -0
  104. pyvale/simcases/case02.i +156 -0
  105. pyvale/simcases/case03.i +136 -0
  106. pyvale/simcases/case04.i +181 -0
  107. pyvale/simcases/case05.i +234 -0
  108. pyvale/simcases/case06.i +305 -0
  109. pyvale/simcases/case07.geo +135 -0
  110. pyvale/simcases/case07.i +87 -0
  111. pyvale/simcases/case08.geo +144 -0
  112. pyvale/simcases/case08.i +153 -0
  113. pyvale/simcases/case09.geo +204 -0
  114. pyvale/simcases/case09.i +87 -0
  115. pyvale/simcases/case10.geo +204 -0
  116. pyvale/simcases/case10.i +257 -0
  117. pyvale/simcases/case11.geo +337 -0
  118. pyvale/simcases/case11.i +147 -0
  119. pyvale/simcases/case12.geo +388 -0
  120. pyvale/simcases/case12.i +329 -0
  121. pyvale/simcases/case13.i +140 -0
  122. pyvale/simcases/case14.i +159 -0
  123. pyvale/simcases/case15.geo +337 -0
  124. pyvale/simcases/case15.i +150 -0
  125. pyvale/simcases/case16.geo +391 -0
  126. pyvale/simcases/case16.i +357 -0
  127. pyvale/simcases/case17.geo +135 -0
  128. pyvale/simcases/case17.i +144 -0
  129. pyvale/simcases/case18.i +254 -0
  130. pyvale/simcases/case18_1.i +254 -0
  131. pyvale/simcases/case18_2.i +254 -0
  132. pyvale/simcases/case18_3.i +254 -0
  133. pyvale/simcases/case19.geo +252 -0
  134. pyvale/simcases/case19.i +99 -0
  135. pyvale/simcases/case20.geo +252 -0
  136. pyvale/simcases/case20.i +250 -0
  137. pyvale/simcases/case21.geo +74 -0
  138. pyvale/simcases/case21.i +155 -0
  139. pyvale/simcases/case22.geo +82 -0
  140. pyvale/simcases/case22.i +140 -0
  141. pyvale/simcases/case23.geo +164 -0
  142. pyvale/simcases/case23.i +140 -0
  143. pyvale/simcases/case24.geo +79 -0
  144. pyvale/simcases/case24.i +123 -0
  145. pyvale/simcases/case25.geo +82 -0
  146. pyvale/simcases/case25.i +140 -0
  147. pyvale/simcases/case26.geo +166 -0
  148. pyvale/simcases/case26.i +140 -0
  149. pyvale/simcases/run_1case.py +61 -0
  150. pyvale/simcases/run_all_cases.py +69 -0
  151. pyvale/simcases/run_build_case.py +64 -0
  152. pyvale/simcases/run_example_cases.py +69 -0
  153. pyvale-2025.4.0.dist-info/METADATA +140 -0
  154. pyvale-2025.4.0.dist-info/RECORD +157 -0
  155. pyvale-2025.4.0.dist-info/WHEEL +5 -0
  156. pyvale-2025.4.0.dist-info/licenses/LICENSE +21 -0
  157. pyvale-2025.4.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,905 @@
1
+ """
2
+ ================================================================================
3
+ pyvale: the python validation engine
4
+ License: MIT
5
+ Copyright (C) 2025 The Computer Aided Validation Team
6
+ ================================================================================
7
+ """
8
+ from typing import Callable
9
+ import numpy as np
10
+ from pyvale.core.errorcalculator import (IErrCalculator,
11
+ EErrType,
12
+ EErrDependence)
13
+ from pyvale.core.generatorsrandom import IGeneratorRandom
14
+ from pyvale.core.sensordata import SensorData
15
+
16
+
17
+ class ErrSysOffset(IErrCalculator):
18
+ """Systematic error calculator applying a constant offset to all simulated
19
+ sensor measurements. Implements the `IErrCalculator` interface.
20
+ """
21
+ __slots__ = ("_offset","_err_dep")
22
+
23
+ def __init__(self,
24
+ offset: float,
25
+ err_dep: EErrDependence = EErrDependence.INDEPENDENT) -> None:
26
+ """Initialiser for the `ErrSysOffset` class.
27
+
28
+ Parameters
29
+ ----------
30
+ offset : float
31
+ Constant offset to apply to all simulated measurements from the
32
+ sensor array.
33
+ err_dep : EErrDependence, optional
34
+ Error , by default EErrDependence.INDEPENDENT
35
+ """
36
+ self._offset = offset
37
+ self._err_dep = err_dep
38
+
39
+ def get_error_dep(self) -> EErrDependence:
40
+ """Gets the error dependence state for this error calculator. An
41
+ independent error is calculated based on the input truth values as the
42
+ error basis. A dependent error is calculated based on the accumulated
43
+ sensor reading from all preceeding errors in the chain.
44
+
45
+ NOTE: for this error the calculation is independent regardless of this
46
+ setting as the offset is constant.
47
+
48
+ Returns
49
+ -------
50
+ EErrDependence
51
+ Enumeration defining INDEPENDENT or DEPENDENT behaviour.
52
+ """
53
+ return self._err_dep
54
+
55
+ def set_error_dep(self, dependence: EErrDependence) -> None:
56
+ """Sets the error dependence state for this error calculator. An
57
+ independent error is calculated based on the input truth values as the
58
+ error basis. A dependent error is calculated based on the accumulated
59
+ sensor reading from all preceeding errors in the chain.
60
+
61
+ NOTE: for this error the calculation is independent regardless of this
62
+ setting as the offset is constant.
63
+
64
+ Parameters
65
+ ----------
66
+ dependence : EErrDependence
67
+ Enumeration defining INDEPENDENT or DEPENDENT behaviour.
68
+ """
69
+ self._err_dep = dependence
70
+
71
+ def get_error_type(self) -> EErrType:
72
+ """Gets the error type.
73
+
74
+ Returns
75
+ -------
76
+ EErrType
77
+ Enumeration definining RANDOM or SYSTEMATIC error types.
78
+ """
79
+ return EErrType.SYSTEMATIC
80
+
81
+ def calc_errs(self,
82
+ err_basis: np.ndarray,
83
+ sens_data: SensorData,
84
+ ) -> tuple[np.ndarray, SensorData]:
85
+ """Calculates the error array based on the size of the input.
86
+
87
+ Parameters
88
+ ----------
89
+ err_basis : np.ndarray
90
+ Array of values with the same dimensions as the sensor measurement
91
+ matrix.
92
+ sens_data : SensorData
93
+ The accumulated sensor state data for all errors prior to this one.
94
+
95
+ Returns
96
+ -------
97
+ tuple[np.ndarray, SensorData]
98
+ Tuple containing the calculated error array and pass through of the
99
+ sensor data object as it is not modified by this class. The returned
100
+ error array has the same shape as the input error basis.
101
+ """
102
+ return (self._offset*np.ones(shape=err_basis.shape),sens_data)
103
+
104
+
105
+ class ErrSysOffsetPercent(IErrCalculator):
106
+ """Systematic error calculator applying a constant offset as a percentage of
107
+ the sensor reading to each individual simulated sensor measurement.
108
+ Implements the `IErrCalculator` interface.
109
+ """
110
+ __slots__ = ("_offset_percent","_err_dep")
111
+
112
+ def __init__(self,
113
+ offset_percent: float,
114
+ err_dep: EErrDependence = EErrDependence.INDEPENDENT) -> None:
115
+ """Initialiser for the `ErrSysOffsetPercent` class.
116
+
117
+ Parameters
118
+ ----------
119
+ offset_percent : float
120
+ Percentage offset to apply to apply to all simulated measurements
121
+ from the sensor array.
122
+ err_dep : EErrDependence, optional
123
+ Error calculation dependence, by default EErrDependence.INDEPENDENT
124
+ """
125
+ self._offset_percent = offset_percent
126
+ self._err_dep = err_dep
127
+
128
+ def get_error_dep(self) -> EErrDependence:
129
+ """Gets the error dependence state for this error calculator. An
130
+ independent error is calculated based on the input truth values as the
131
+ error basis. A dependent error is calculated based on the accumulated
132
+ sensor reading from all preceeding errors in the chain.
133
+
134
+ Returns
135
+ -------
136
+ EErrDependence
137
+ Enumeration defining INDEPENDENT or DEPENDENT behaviour.
138
+ """
139
+ return self._err_dep
140
+
141
+ def set_error_dep(self, dependence: EErrDependence) -> None:
142
+ """Sets the error dependence state for this error calculator. An
143
+ independent error is calculated based on the input truth values as the
144
+ error basis. A dependent error is calculated based on the accumulated
145
+ sensor reading from all preceeding errors in the chain.
146
+
147
+ Parameters
148
+ ----------
149
+ dependence : EErrDependence
150
+ Enumeration defining INDEPENDENT or DEPENDENT behaviour.
151
+ """
152
+ self._err_dep = dependence
153
+
154
+ def get_error_type(self) -> EErrType:
155
+ """Gets the error type.
156
+
157
+ Returns
158
+ -------
159
+ EErrType
160
+ Enumeration definining RANDOM or SYSTEMATIC error types.
161
+ """
162
+ return EErrType.SYSTEMATIC
163
+
164
+ def calc_errs(self,
165
+ err_basis: np.ndarray,
166
+ sens_data: SensorData,
167
+ ) -> tuple[np.ndarray, SensorData]:
168
+ """Calculates the error array based on the size of the input.
169
+
170
+ Parameters
171
+ ----------
172
+ err_basis : np.ndarray
173
+ Array of values with the same dimensions as the sensor measurement
174
+ matrix.
175
+ sens_data : SensorData
176
+ The accumulated sensor state data for all errors prior to this one.
177
+
178
+ Returns
179
+ -------
180
+ tuple[np.ndarray, SensorData]
181
+ Tuple containing the calculated error array and pass through of the
182
+ sensor data object as it is not modified by this class. The returned
183
+ error array has the same shape as the input error basis.
184
+ """
185
+ return (self._offset_percent/100 *
186
+ err_basis *
187
+ np.ones(shape=err_basis.shape),
188
+ sens_data)
189
+
190
+
191
+ class ErrSysUniform(IErrCalculator):
192
+ """Systematic error calculator for applying an offset to each sensor that is
193
+ sampled from a uniform probability distribution specified by its upper and
194
+ lower bounds. Implements the `IErrCalculator` interface.
195
+ """
196
+ __slots__ = ("_low","_high","_rng","_err_dep")
197
+
198
+ def __init__(self,
199
+ low: float,
200
+ high: float,
201
+ err_dep: EErrDependence = EErrDependence.INDEPENDENT,
202
+ seed: int | None = None) -> None:
203
+ """Initialiser for the `ErrSysUniform` class.
204
+
205
+ Parameters
206
+ ----------
207
+ low : float
208
+ Lower bound of the uniform probability distribution in the same
209
+ units as the physical field the sensor array is sampling.
210
+ high : float
211
+ Upper bound of the uniform probability distribution in the same
212
+ units as the physical field the sensor array is sampling.
213
+ err_dep : EErrDependence, optional
214
+ Error calculation dependence, by default EErrDependence.INDEPENDENT.
215
+ seed : int | None, optional
216
+ Optional seed for the random generator to allow for replicable
217
+ behaviour, by default None.
218
+ """
219
+ self._low = low
220
+ self._high = high
221
+ self._rng = np.random.default_rng(seed)
222
+ self._err_dep = err_dep
223
+
224
+ def get_error_dep(self) -> EErrDependence:
225
+ """Gets the error dependence state for this error calculator. An
226
+ independent error is calculated based on the input truth values as the
227
+ error basis. A dependent error is calculated based on the accumulated
228
+ sensor reading from all preceeding errors in the chain.
229
+
230
+ NOTE: for this error the calculation is independent regardless of this
231
+ setting as the offset is constant.
232
+
233
+ Returns
234
+ -------
235
+ EErrDependence
236
+ Enumeration defining INDEPENDENT or DEPENDENT behaviour.
237
+ """
238
+ return self._err_dep
239
+
240
+ def set_error_dep(self, dependence: EErrDependence) -> None:
241
+ """Sets the error dependence state for this error calculator. An
242
+ independent error is calculated based on the input truth values as the
243
+ error basis. A dependent error is calculated based on the accumulated
244
+ sensor reading from all preceeding errors in the chain.
245
+
246
+ NOTE: for this error the calculation is independent regardless of this
247
+ setting as the offset is constant.
248
+
249
+ Parameters
250
+ ----------
251
+ dependence : EErrDependence
252
+ Enumeration defining INDEPENDENT or DEPENDENT behaviour.
253
+ """
254
+ self._err_dep = dependence
255
+
256
+ def get_error_type(self) -> EErrType:
257
+ """Gets the error type.
258
+
259
+ Returns
260
+ -------
261
+ EErrType
262
+ Enumeration definining RANDOM or SYSTEMATIC error types.
263
+ """
264
+ return EErrType.SYSTEMATIC
265
+
266
+ def calc_errs(self,
267
+ err_basis: np.ndarray,
268
+ sens_data: SensorData,
269
+ ) -> tuple[np.ndarray, SensorData]:
270
+ """Calculates the error array based on the size of the input.
271
+
272
+ Parameters
273
+ ----------
274
+ err_basis : np.ndarray
275
+ Array of values with the same dimensions as the sensor measurement
276
+ matrix.
277
+ sens_data : SensorData
278
+ The accumulated sensor state data for all errors prior to this one.
279
+
280
+ Returns
281
+ -------
282
+ tuple[np.ndarray, SensorData]
283
+ Tuple containing the calculated error array and pass through of the
284
+ sensor data object as it is not modified by this class. The returned
285
+ error array has the same shape as the input error basis.
286
+ """
287
+ err_shape = np.array(err_basis.shape)
288
+ err_shape[-1] = 1
289
+ sys_errs = self._rng.uniform(low=self._low,
290
+ high=self._high,
291
+ size=err_shape)
292
+
293
+ tile_shape = np.array(err_basis.shape)
294
+ tile_shape[0:-1] = 1
295
+ sys_errs = np.tile(sys_errs,tuple(tile_shape))
296
+
297
+ return (sys_errs,sens_data)
298
+
299
+
300
+ class ErrSysUniformPercent(IErrCalculator):
301
+ """Systematic error calculator for applying a percentage offset to each
302
+ sensor that is sampled from a uniform probability distribution specified by
303
+ its upper and lower bounds.
304
+
305
+ The percentage offset is calculated based on the ground truth if the error
306
+ dependence is `INDEPENDENT` or based on the accumulated sensor measurement
307
+ if the dependence is `DEPENDENT`.
308
+
309
+ Implements the `IErrCalculator` interface.
310
+ """
311
+ __slots__ = ("_low","_high","_rng","_err_dep")
312
+
313
+ def __init__(self,
314
+ low_percent: float,
315
+ high_percent: float,
316
+ err_dep: EErrDependence = EErrDependence.INDEPENDENT,
317
+ seed: int | None = None) -> None:
318
+ """_summary_
319
+
320
+ Parameters
321
+ ----------
322
+ low_percent : float
323
+ Lower percentage bound for the uniform probability distribution.
324
+ high_percent : float
325
+ Upper percentage bound for the uniform probability distribution.
326
+ err_dep : EErrDependence, optional
327
+ Error calculation dependence, by default EErrDependence.INDEPENDENT
328
+ seed : int | None, optional
329
+ Optional seed for the random generator to allow for replicable
330
+ behaviour, by default None.
331
+ """
332
+ self._low = low_percent/100
333
+ self._high = high_percent/100
334
+ self._rng = np.random.default_rng(seed)
335
+ self._err_dep = err_dep
336
+
337
+ def get_error_dep(self) -> EErrDependence:
338
+ """Gets the error dependence state for this error calculator. An
339
+ independent error is calculated based on the input truth values as the
340
+ error basis. A dependent error is calculated based on the accumulated
341
+ sensor reading from all preceeding errors in the chain.
342
+
343
+ Returns
344
+ -------
345
+ EErrDependence
346
+ Enumeration defining INDEPENDENT or DEPENDENT behaviour.
347
+ """
348
+ return self._err_dep
349
+
350
+ def set_error_dep(self, dependence: EErrDependence) -> None:
351
+ """Sets the error dependence state for this error calculator. An
352
+ independent error is calculated based on the input truth values as the
353
+ error basis. A dependent error is calculated based on the accumulated
354
+ sensor reading from all preceeding errors in the chain.
355
+
356
+ Parameters
357
+ ----------
358
+ dependence : EErrDependence
359
+ Enumeration defining INDEPENDENT or DEPENDENT behaviour.
360
+ """
361
+ self._err_dep = dependence
362
+
363
+ def get_error_type(self) -> EErrType:
364
+ """Gets the error type.
365
+
366
+ Returns
367
+ -------
368
+ EErrType
369
+ Enumeration definining RANDOM or SYSTEMATIC error types.
370
+ """
371
+ return EErrType.SYSTEMATIC
372
+
373
+ def calc_errs(self,
374
+ err_basis: np.ndarray,
375
+ sens_data: SensorData,
376
+ ) -> tuple[np.ndarray, SensorData]:
377
+ """Calculates the error array based on the size of the input.
378
+
379
+ Parameters
380
+ ----------
381
+ err_basis : np.ndarray
382
+ Array of values with the same dimensions as the sensor measurement
383
+ matrix.
384
+ sens_data : SensorData
385
+ The accumulated sensor state data for all errors prior to this one.
386
+
387
+ Returns
388
+ -------
389
+ tuple[np.ndarray, SensorData]
390
+ Tuple containing the calculated error array and pass through of the
391
+ sensor data object as it is not modified by this class. The returned
392
+ error array has the same shape as the input error basis.
393
+ """
394
+ err_shape = np.array(err_basis.shape)
395
+ err_shape[-1] = 1
396
+ sys_errs = self._rng.uniform(low=self._low,
397
+ high=self._high,
398
+ size=err_shape)
399
+
400
+ tile_shape = np.array(err_basis.shape)
401
+ tile_shape[0:-1] = 1
402
+ sys_errs = np.tile(sys_errs,tuple(tile_shape))
403
+
404
+ return (err_basis*sys_errs,sens_data)
405
+
406
+
407
+ class ErrSysNormal(IErrCalculator):
408
+ """Systematic error calculator for applying an offset to each individual
409
+ sensor in the array based on sampling from a normal distribution specified
410
+ by its standard deviation and mean. Note that the offset is constant for
411
+ each sensor over time. Implements the `IErrCalculator` interface.
412
+ """
413
+ __slots__ = ("_std","_rng","_err_dep")
414
+
415
+ def __init__(self,
416
+ std: float,
417
+ mean: float = 0.0,
418
+ err_dep: EErrDependence = EErrDependence.INDEPENDENT,
419
+ seed: int | None = None) -> None:
420
+ """Initialiser for the `ErrSysNormal` class.
421
+
422
+ Parameters
423
+ ----------
424
+ std : float
425
+ Standard deviation of the normal distribution to sample.
426
+ mean : float, optional
427
+ Mean of the normal distribution to sample, by default 0.0.
428
+ err_dep : EErrDependence, optional
429
+ Error calculation dependence, by default EErrDependence.INDEPENDENT.
430
+ seed : int | None, optional
431
+ Optional seed for the random generator to allow for replicable
432
+ behaviour, by default None.
433
+ """
434
+ self._std = std
435
+ self._mean = mean
436
+ self._rng = np.random.default_rng(seed)
437
+ self._err_dep = err_dep
438
+
439
+ def get_error_dep(self) -> EErrDependence:
440
+ """Gets the error dependence state for this error calculator. An
441
+ independent error is calculated based on the input truth values as the
442
+ error basis. A dependent error is calculated based on the accumulated
443
+ sensor reading from all preceeding errors in the chain.
444
+
445
+ NOTE: for this error the calculation is independent regardless of this
446
+ setting as the offset is constant.
447
+
448
+ Returns
449
+ -------
450
+ EErrDependence
451
+ Enumeration defining INDEPENDENT or DEPENDENT behaviour.
452
+ """
453
+ return self._err_dep
454
+
455
+ def set_error_dep(self, dependence: EErrDependence) -> None:
456
+ """Sets the error dependence state for this error calculator. An
457
+ independent error is calculated based on the input truth values as the
458
+ error basis. A dependent error is calculated based on the accumulated
459
+ sensor reading from all preceeding errors in the chain.
460
+
461
+ NOTE: for this error the calculation is independent regardless of this
462
+ setting as the offset is constant.
463
+
464
+ Parameters
465
+ ----------
466
+ dependence : EErrDependence
467
+ Enumeration defining INDEPENDENT or DEPENDENT behaviour.
468
+ """
469
+ self._err_dep = dependence
470
+
471
+ def get_error_type(self) -> EErrType:
472
+ """Gets the error type.
473
+
474
+ Returns
475
+ -------
476
+ EErrType
477
+ Enumeration definining RANDOM or SYSTEMATIC error types.
478
+ """
479
+ return EErrType.SYSTEMATIC
480
+
481
+ def calc_errs(self,
482
+ err_basis: np.ndarray,
483
+ sens_data: SensorData,
484
+ ) -> tuple[np.ndarray, SensorData]:
485
+ """Calculates the error array based on the size of the input.
486
+
487
+ Parameters
488
+ ----------
489
+ err_basis : np.ndarray
490
+ Array of values with the same dimensions as the sensor measurement
491
+ matrix.
492
+ sens_data : SensorData
493
+ The accumulated sensor state data for all errors prior to this one.
494
+
495
+ Returns
496
+ -------
497
+ tuple[np.ndarray, SensorData]
498
+ Tuple containing the calculated error array and pass through of the
499
+ sensor data object as it is not modified by this class. The returned
500
+ error array has the same shape as the input error basis.
501
+ """
502
+ err_shape = np.array(err_basis.shape)
503
+ err_shape[-1] = 1
504
+ sys_errs = self._rng.normal(loc=self._mean,
505
+ scale=self._std,
506
+ size=err_shape)
507
+
508
+ tile_shape = np.array(err_basis.shape)
509
+ tile_shape[0:-1] = 1
510
+ sys_errs = np.tile(sys_errs,tuple(tile_shape))
511
+
512
+ return (sys_errs,sens_data)
513
+
514
+
515
+ class ErrSysNormPercent(IErrCalculator):
516
+ """Systematic error calculator for applying a percentage offset to each
517
+ individual sensor in the array based on sampling from a normal distribution
518
+ specified by its standard deviation and mean. Note that the offset is
519
+ constant for each sensor over time.
520
+
521
+ The percentage offset is calculated based on the ground truth if the error
522
+ dependence is `INDEPENDENT` or based on the accumulated sensor measurement
523
+ if the dependence is `DEPENDENT`.
524
+
525
+ Implements the `IErrCalculator` interface.
526
+ """
527
+ __slots__ = ("_std","_rng","_err_dep")
528
+
529
+ def __init__(self,
530
+ std_percent: float,
531
+ err_dep: EErrDependence = EErrDependence.INDEPENDENT,
532
+ seed: int | None = None) -> None:
533
+ self._std = std_percent/100
534
+ self._rng = np.random.default_rng(seed)
535
+ self._err_dep = err_dep
536
+
537
+ def get_error_dep(self) -> EErrDependence:
538
+ """Gets the error dependence state for this error calculator. An
539
+ independent error is calculated based on the input truth values as the
540
+ error basis. A dependent error is calculated based on the accumulated
541
+ sensor reading from all preceeding errors in the chain.
542
+
543
+ Returns
544
+ -------
545
+ EErrDependence
546
+ Enumeration defining INDEPENDENT or DEPENDENT behaviour.
547
+ """
548
+ return self._err_dep
549
+
550
+ def set_error_dep(self, dependence: EErrDependence) -> None:
551
+ """Sets the error dependence state for this error calculator. An
552
+ independent error is calculated based on the input truth values as the
553
+ error basis. A dependent error is calculated based on the accumulated
554
+ sensor reading from all preceeding errors in the chain.
555
+
556
+ Parameters
557
+ ----------
558
+ dependence : EErrDependence
559
+ Enumeration defining INDEPENDENT or DEPENDENT behaviour.
560
+ """
561
+ self._err_dep = dependence
562
+
563
+ def get_error_type(self) -> EErrType:
564
+ """Gets the error type.
565
+
566
+ Returns
567
+ -------
568
+ EErrType
569
+ Enumeration definining RANDOM or SYSTEMATIC error types.
570
+ """
571
+ return EErrType.SYSTEMATIC
572
+
573
+ def calc_errs(self,
574
+ err_basis: np.ndarray,
575
+ sens_data: SensorData,
576
+ ) -> tuple[np.ndarray, SensorData]:
577
+
578
+ err_shape = np.array(err_basis.shape)
579
+ err_shape[-1] = 1
580
+ sys_errs = self._rng.normal(loc=0.0,
581
+ scale=self._std,
582
+ size=err_shape)
583
+ """Calculates the error array based on the size of the input.
584
+
585
+ Parameters
586
+ ----------
587
+ err_basis : np.ndarray
588
+ Array of values with the same dimensions as the sensor measurement
589
+ matrix.
590
+ sens_data : SensorData
591
+ The accumulated sensor state data for all errors prior to this one.
592
+
593
+ Returns
594
+ -------
595
+ tuple[np.ndarray, SensorData]
596
+ Tuple containing the calculated error array and pass through of the
597
+ sensor data object as it is not modified by this class. The returned
598
+ error array has the same shape as the input error basis.
599
+ """
600
+ tile_shape = np.array(err_basis.shape)
601
+ tile_shape[0:-1] = 1
602
+ sys_errs = np.tile(sys_errs,tuple(tile_shape))
603
+
604
+ return (err_basis*sys_errs,sens_data)
605
+
606
+
607
+ class ErrSysGenerator(IErrCalculator):
608
+ """Systematic error calculator for applying a unique offset to each sensor
609
+ by sample from a user specified probability distribution (an implementation
610
+ of the `IGeneratorRandom` interface).
611
+
612
+ Implements the `IErrCalculator` interface.
613
+ """
614
+ __slots__ = ("_generator","_err_dep")
615
+
616
+ def __init__(self,
617
+ generator: IGeneratorRandom,
618
+ err_dep: EErrDependence = EErrDependence.INDEPENDENT) -> None:
619
+
620
+ self._generator = generator
621
+ self._err_dep = err_dep
622
+
623
+ def get_error_dep(self) -> EErrDependence:
624
+ """Gets the error dependence state for this error calculator. An
625
+ independent error is calculated based on the input truth values as the
626
+ error basis. A dependent error is calculated based on the accumulated
627
+ sensor reading from all preceeding errors in the chain.
628
+
629
+ Returns
630
+ -------
631
+ EErrDependence
632
+ Enumeration defining INDEPENDENT or DEPENDENT behaviour.
633
+ """
634
+ return self._err_dep
635
+
636
+ def set_error_dep(self, dependence: EErrDependence) -> None:
637
+ """Sets the error dependence state for this error calculator. An
638
+ independent error is calculated based on the input truth values as the
639
+ error basis. A dependent error is calculated based on the accumulated
640
+ sensor reading from all preceeding errors in the chain.
641
+
642
+ Parameters
643
+ ----------
644
+ dependence : EErrDependence
645
+ Enumeration defining INDEPENDENT or DEPENDENT behaviour.
646
+ """
647
+ self._err_dep = dependence
648
+
649
+ def get_error_type(self) -> EErrType:
650
+ """Gets the error type.
651
+
652
+ Returns
653
+ -------
654
+ EErrType
655
+ Enumeration definining RANDOM or SYSTEMATIC error types.
656
+ """
657
+ return EErrType.SYSTEMATIC
658
+
659
+ def calc_errs(self,
660
+ err_basis: np.ndarray,
661
+ sens_data: SensorData,
662
+ ) -> tuple[np.ndarray, SensorData]:
663
+ """Calculates the error array based on the size of the input.
664
+
665
+ Parameters
666
+ ----------
667
+ err_basis : np.ndarray
668
+ Array of values with the same dimensions as the sensor measurement
669
+ matrix.
670
+ sens_data : SensorData
671
+ The accumulated sensor state data for all errors prior to this one.
672
+
673
+ Returns
674
+ -------
675
+ tuple[np.ndarray, SensorData]
676
+ Tuple containing the calculated error array and pass through of the
677
+ sensor data object as it is not modified by this class. The returned
678
+ error array has the same shape as the input error basis.
679
+ """
680
+ err_shape = np.array(err_basis.shape)
681
+ err_shape[-1] = 1
682
+
683
+ sys_errs = self._generator.generate(shape=err_shape)
684
+
685
+ tile_shape = np.array(err_basis.shape)
686
+ tile_shape[0:-1] = 1
687
+ sys_errs = np.tile(sys_errs,tuple(tile_shape))
688
+
689
+ return (sys_errs,sens_data)
690
+
691
+
692
+ class ErrSysGenPercent(IErrCalculator):
693
+ """Systematic error calculator for applying a unique percentage offset to
694
+ each sensor by sample from a user specified probability distribution (an
695
+ implementation of the `IGeneratorRandom` interface). This class assumes the
696
+ random generator is for a percentage error based on the input error basis
697
+ and therefore it supports error dependence.
698
+
699
+ The percentage error is calculated based on the ground truth if the error
700
+ dependence is `INDEPENDENT` or based on the accumulated sensor measurement
701
+ if the dependence is `DEPENDENT`.
702
+
703
+ Implements the `IErrCalculator` interface.
704
+ """
705
+ __slots__ = ("_generator","_err_dep")
706
+
707
+ def __init__(self,
708
+ generator: IGeneratorRandom,
709
+ err_dep: EErrDependence = EErrDependence.INDEPENDENT) -> None:
710
+
711
+ self._generator = generator
712
+ self._err_dep = err_dep
713
+
714
+ def get_error_dep(self) -> EErrDependence:
715
+ """Gets the error dependence state for this error calculator. An
716
+ independent error is calculated based on the input truth values as the
717
+ error basis. A dependent error is calculated based on the accumulated
718
+ sensor reading from all preceeding errors in the chain.
719
+
720
+ Returns
721
+ -------
722
+ EErrDependence
723
+ Enumeration defining INDEPENDENT or DEPENDENT behaviour.
724
+ """
725
+ return self._err_dep
726
+
727
+ def set_error_dep(self, dependence: EErrDependence) -> None:
728
+ """Sets the error dependence state for this error calculator. An
729
+ independent error is calculated based on the input truth values as the
730
+ error basis. A dependent error is calculated based on the accumulated
731
+ sensor reading from all preceeding errors in the chain.
732
+
733
+ Parameters
734
+ ----------
735
+ dependence : EErrDependence
736
+ Enumeration defining INDEPENDENT or DEPENDENT behaviour.
737
+ """
738
+ self._err_dep = dependence
739
+
740
+ def get_error_type(self) -> EErrType:
741
+ """Gets the error type.
742
+
743
+ Returns
744
+ -------
745
+ EErrType
746
+ Enumeration definining RANDOM or SYSTEMATIC error types.
747
+ """
748
+ return EErrType.SYSTEMATIC
749
+
750
+ def calc_errs(self,
751
+ err_basis: np.ndarray,
752
+ sens_data: SensorData,
753
+ ) -> tuple[np.ndarray, SensorData]:
754
+ """Calculates the error array based on the size of the input.
755
+
756
+ Parameters
757
+ ----------
758
+ err_basis : np.ndarray
759
+ Array of values with the same dimensions as the sensor measurement
760
+ matrix.
761
+ sens_data : SensorData
762
+ The accumulated sensor state data for all errors prior to this one.
763
+
764
+ Returns
765
+ -------
766
+ tuple[np.ndarray, SensorData]
767
+ Tuple containing the calculated error array and pass through of the
768
+ sensor data object as it is not modified by this class. The returned
769
+ error array has the same shape as the input error basis.
770
+ """
771
+ err_shape = np.array(err_basis.shape)
772
+ err_shape[-1] = 1
773
+
774
+ sys_errs = self._generator.generate(size=err_shape)
775
+
776
+ tile_shape = np.array(err_basis.shape)
777
+ tile_shape[0:-1] = 1
778
+ sys_errs = np.tile(sys_errs,tuple(tile_shape))
779
+ sys_errs = err_basis * sys_errs
780
+
781
+ return (sys_errs,sens_data)
782
+
783
+
784
+ class ErrSysCalibration(IErrCalculator):
785
+ """Systematic error calculator for calibration errors. The user specifies an
786
+ assumed calibration and a ground truth calibration function. The ground
787
+ truth calibration function is inverted and linearly interpolated numerically
788
+ based on the number of divisions specified by the user.
789
+
790
+ Implements the `IErrCalculator` interface.
791
+ """
792
+ __slots__ = ("_assumed_cali","_truth_calib","_cal_range","_n_cal_divs",
793
+ "_err_dep","_truth_calc_table")
794
+
795
+ def __init__(self,
796
+ assumed_calib: Callable[[np.ndarray],np.ndarray],
797
+ truth_calib: Callable[[np.ndarray],np.ndarray],
798
+ cal_range: tuple[float,float],
799
+ n_cal_divs: int = 10000,
800
+ err_dep: EErrDependence = EErrDependence.INDEPENDENT) -> None:
801
+ """_summary_
802
+
803
+ Parameters
804
+ ----------
805
+ assumed_calib : Callable[[np.ndarray],np.ndarray]
806
+ Assumed calibration function taking the input unitless 'signal' and
807
+ converting it to the same units as the physical field being sampled
808
+ by the sensor array.
809
+ truth_calib : Callable[[np.ndarray],np.ndarray]
810
+ Assumed calibration function taking the input unitless 'signal' and
811
+ converting it to the same units as the physical field being sampled
812
+ by the sensor array.
813
+ cal_range : tuple[float,float]
814
+ Range over which the calibration functions are valid. This is
815
+ normally based on a voltage range such as (0,10) volts.
816
+ n_cal_divs : int, optional
817
+ Number of divisions to discretise the the truth calibration function
818
+ for numerical inversion, by default 10000.
819
+ err_dep : EErrDependence, optional
820
+ Error calculation dependence, by default EErrDependence.INDEPENDENT.
821
+ """
822
+ self._assumed_calib = assumed_calib
823
+ self._truth_calib = truth_calib
824
+ self._cal_range = cal_range
825
+ self._n_cal_divs = n_cal_divs
826
+ self._err_dep = err_dep
827
+
828
+ self._truth_cal_table = np.zeros((n_cal_divs,2))
829
+ self._truth_cal_table[:,0] = np.linspace(cal_range[0],
830
+ cal_range[1],
831
+ n_cal_divs)
832
+ self._truth_cal_table[:,1] = self._truth_calib(
833
+ self._truth_cal_table[:,0])
834
+
835
+ def get_error_dep(self) -> EErrDependence:
836
+ """Gets the error dependence state for this error calculator. An
837
+ independent error is calculated based on the input truth values as the
838
+ error basis. A dependent error is calculated based on the accumulated
839
+ sensor reading from all preceeding errors in the chain.
840
+
841
+ Returns
842
+ -------
843
+ EErrDependence
844
+ Enumeration defining INDEPENDENT or DEPENDENT behaviour.
845
+ """
846
+ return self._err_dep
847
+
848
+ def set_error_dep(self, dependence: EErrDependence) -> None:
849
+ """Sets the error dependence state for this error calculator. An
850
+ independent error is calculated based on the input truth values as the
851
+ error basis. A dependent error is calculated based on the accumulated
852
+ sensor reading from all preceeding errors in the chain.
853
+
854
+ Parameters
855
+ ----------
856
+ dependence : EErrDependence
857
+ Enumeration defining INDEPENDENT or DEPENDENT behaviour.
858
+ """
859
+ self._err_dep = dependence
860
+
861
+ def get_error_type(self) -> EErrType:
862
+ """Gets the error type.
863
+
864
+ Returns
865
+ -------
866
+ EErrType
867
+ Enumeration definining RANDOM or SYSTEMATIC error types.
868
+ """
869
+ return EErrType.SYSTEMATIC
870
+
871
+ def calc_errs(self,
872
+ err_basis: np.ndarray,
873
+ sens_data: SensorData,
874
+ ) -> tuple[np.ndarray, SensorData]:
875
+ """Calculates the error array based on the size of the input.
876
+
877
+ Parameters
878
+ ----------
879
+ err_basis : np.ndarray
880
+ Array of values with the same dimensions as the sensor measurement
881
+ matrix.
882
+ sens_data : SensorData
883
+ The accumulated sensor state data for all errors prior to this one.
884
+
885
+ Returns
886
+ -------
887
+ tuple[np.ndarray, SensorData]
888
+ Tuple containing the calculated error array and pass through of the
889
+ sensor data object as it is not modified by this class. The returned
890
+ error array has the same shape as the input error basis.
891
+ """
892
+ # shape=(n_sens,n_comps,n_time_steps)
893
+ signal_from_field = np.interp(err_basis,
894
+ self._truth_cal_table[:,1],
895
+ self._truth_cal_table[:,0])
896
+ # shape=(n_sens,n_comps,n_time_steps)
897
+ field_from_assumed_calib = self._assumed_calib(signal_from_field)
898
+
899
+ sys_errs = field_from_assumed_calib - err_basis
900
+
901
+ return (sys_errs,sens_data)
902
+
903
+
904
+
905
+