pyvale 2025.4.1__py3-none-any.whl → 2025.5.1__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.
- pyvale/__init__.py +18 -3
- pyvale/analyticmeshgen.py +1 -0
- pyvale/analyticsimdatafactory.py +18 -13
- pyvale/analyticsimdatagenerator.py +105 -72
- pyvale/blendercalibrationdata.py +15 -0
- pyvale/blenderlightdata.py +26 -0
- pyvale/blendermaterialdata.py +15 -0
- pyvale/blenderrenderdata.py +30 -0
- pyvale/blenderscene.py +488 -0
- pyvale/blendertools.py +420 -0
- pyvale/camera.py +6 -5
- pyvale/cameradata.py +25 -7
- pyvale/cameradata2d.py +6 -4
- pyvale/camerastereo.py +217 -0
- pyvale/cameratools.py +206 -11
- pyvale/cython/rastercyth.py +6 -2
- pyvale/data/cal_target.tiff +0 -0
- pyvale/dataset.py +73 -14
- pyvale/errorcalculator.py +8 -10
- pyvale/errordriftcalc.py +10 -9
- pyvale/errorintegrator.py +19 -21
- pyvale/errorrand.py +33 -39
- pyvale/errorsyscalib.py +134 -0
- pyvale/errorsysdep.py +19 -22
- pyvale/errorsysfield.py +49 -41
- pyvale/errorsysindep.py +79 -175
- pyvale/examples/basics/ex1_1_basicscalars_therm2d.py +131 -0
- pyvale/examples/basics/ex1_2_sensormodel_therm2d.py +158 -0
- pyvale/examples/basics/ex1_3_customsens_therm3d.py +216 -0
- pyvale/examples/basics/ex1_4_basicerrors_therm3d.py +153 -0
- pyvale/examples/basics/ex1_5_fielderrs_therm3d.py +168 -0
- pyvale/examples/basics/ex1_6_caliberrs_therm2d.py +133 -0
- pyvale/examples/basics/ex1_7_spatavg_therm2d.py +123 -0
- pyvale/examples/basics/ex2_1_basicvectors_disp2d.py +112 -0
- pyvale/examples/basics/ex2_2_vectorsens_disp2d.py +111 -0
- pyvale/examples/basics/ex2_3_sensangle_disp2d.py +139 -0
- pyvale/examples/basics/ex2_4_chainfielderrs_disp2d.py +196 -0
- pyvale/examples/basics/ex2_5_vectorfields3d_disp3d.py +109 -0
- pyvale/examples/basics/ex3_1_basictensors_strain2d.py +114 -0
- pyvale/examples/basics/ex3_2_tensorsens2d_strain2d.py +111 -0
- pyvale/examples/basics/ex3_3_tensorsens3d_strain3d.py +182 -0
- pyvale/examples/basics/ex4_1_expsim2d_thermmech2d.py +171 -0
- pyvale/examples/basics/ex4_2_expsim3d_thermmech3d.py +252 -0
- pyvale/examples/{analyticdatagen → genanalyticdata}/ex1_1_scalarvisualisation.py +6 -9
- pyvale/examples/{analyticdatagen → genanalyticdata}/ex1_2_scalarcasebuild.py +8 -11
- pyvale/examples/{analyticdatagen → genanalyticdata}/ex2_1_analyticsensors.py +9 -12
- pyvale/examples/imagedef2d/ex_imagedef2d_todisk.py +8 -15
- pyvale/examples/renderblender/ex1_1_blenderscene.py +121 -0
- pyvale/examples/renderblender/ex1_2_blenderdeformed.py +119 -0
- pyvale/examples/renderblender/ex2_1_stereoscene.py +128 -0
- pyvale/examples/renderblender/ex2_2_stereodeformed.py +131 -0
- pyvale/examples/renderblender/ex3_1_blendercalibration.py +120 -0
- pyvale/examples/{rasterisation → renderrasterisation}/ex_rastenp.py +3 -2
- pyvale/examples/{rasterisation → renderrasterisation}/ex_rastercyth_oneframe.py +2 -2
- pyvale/examples/{rasterisation → renderrasterisation}/ex_rastercyth_static_cypara.py +3 -8
- pyvale/examples/{rasterisation → renderrasterisation}/ex_rastercyth_static_pypara.py +6 -7
- pyvale/examples/{ex1_4_thermal2d.py → visualisation/ex1_1_plot_traces.py} +32 -16
- pyvale/examples/{features/ex_animation_tools_3dmonoblock.py → visualisation/ex2_1_animate_sim.py} +37 -31
- pyvale/experimentsimulator.py +107 -30
- pyvale/field.py +2 -9
- pyvale/fieldconverter.py +98 -22
- pyvale/fieldsampler.py +2 -2
- pyvale/fieldscalar.py +10 -10
- pyvale/fieldtensor.py +15 -17
- pyvale/fieldtransform.py +7 -2
- pyvale/fieldvector.py +6 -7
- pyvale/generatorsrandom.py +25 -47
- pyvale/imagedef2d.py +6 -2
- pyvale/integratorfactory.py +2 -2
- pyvale/integratorquadrature.py +50 -24
- pyvale/integratorrectangle.py +85 -7
- pyvale/integratorspatial.py +4 -4
- pyvale/integratortype.py +3 -3
- pyvale/output.py +17 -0
- pyvale/pyvaleexceptions.py +11 -0
- pyvale/raster.py +6 -5
- pyvale/rastercy.py +6 -4
- pyvale/rasternp.py +6 -4
- pyvale/rendermesh.py +6 -2
- pyvale/sensorarray.py +2 -2
- pyvale/sensorarrayfactory.py +52 -65
- pyvale/sensorarraypoint.py +29 -30
- pyvale/sensordata.py +2 -2
- pyvale/sensordescriptor.py +138 -25
- pyvale/sensortools.py +3 -3
- pyvale/simtools.py +67 -0
- pyvale/visualexpplotter.py +99 -57
- pyvale/visualimagedef.py +11 -7
- pyvale/visualimages.py +6 -4
- pyvale/visualopts.py +372 -58
- pyvale/visualsimanimator.py +42 -13
- pyvale/visualsimsensors.py +318 -0
- pyvale/visualtools.py +69 -13
- pyvale/visualtraceplotter.py +52 -165
- {pyvale-2025.4.1.dist-info → pyvale-2025.5.1.dist-info}/METADATA +17 -14
- pyvale-2025.5.1.dist-info/RECORD +172 -0
- {pyvale-2025.4.1.dist-info → pyvale-2025.5.1.dist-info}/WHEEL +1 -1
- pyvale/examples/analyticdatagen/__init__.py +0 -5
- pyvale/examples/ex1_1_thermal2d.py +0 -86
- pyvale/examples/ex1_2_thermal2d.py +0 -108
- pyvale/examples/ex1_3_thermal2d.py +0 -110
- pyvale/examples/ex1_5_thermal2d.py +0 -102
- pyvale/examples/ex2_1_thermal3d .py +0 -84
- pyvale/examples/ex2_2_thermal3d.py +0 -51
- pyvale/examples/ex2_3_thermal3d.py +0 -106
- pyvale/examples/ex3_1_displacement2d.py +0 -44
- pyvale/examples/ex3_2_displacement2d.py +0 -76
- pyvale/examples/ex3_3_displacement2d.py +0 -101
- pyvale/examples/ex3_4_displacement2d.py +0 -102
- pyvale/examples/ex4_1_strain2d.py +0 -54
- pyvale/examples/ex4_2_strain2d.py +0 -76
- pyvale/examples/ex4_3_strain2d.py +0 -97
- pyvale/examples/ex5_1_multiphysics2d.py +0 -75
- pyvale/examples/ex6_1_multiphysics2d_expsim.py +0 -115
- pyvale/examples/ex6_2_multiphysics3d_expsim.py +0 -160
- pyvale/examples/features/__init__.py +0 -5
- pyvale/examples/features/ex_area_avg.py +0 -89
- pyvale/examples/features/ex_calibration_error.py +0 -108
- pyvale/examples/features/ex_chain_field_errs.py +0 -141
- pyvale/examples/features/ex_field_errs.py +0 -78
- pyvale/examples/features/ex_sensor_single_angle_batch.py +0 -110
- pyvale/optimcheckfuncs.py +0 -153
- pyvale/visualsimplotter.py +0 -182
- pyvale-2025.4.1.dist-info/RECORD +0 -163
- {pyvale-2025.4.1.dist-info → pyvale-2025.5.1.dist-info}/licenses/LICENSE +0 -0
- {pyvale-2025.4.1.dist-info → pyvale-2025.5.1.dist-info}/top_level.txt +0 -0
pyvale/errorsysindep.py
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
|
-
#
|
|
1
|
+
# ==============================================================================
|
|
2
2
|
# pyvale: the python validation engine
|
|
3
3
|
# License: MIT
|
|
4
4
|
# Copyright (C) 2025 The Computer Aided Validation Team
|
|
5
|
-
#
|
|
5
|
+
# ==============================================================================
|
|
6
6
|
|
|
7
|
-
from typing import Callable
|
|
8
7
|
import numpy as np
|
|
9
8
|
from pyvale.errorcalculator import (IErrCalculator,
|
|
10
9
|
EErrType,
|
|
11
|
-
|
|
12
|
-
from pyvale.generatorsrandom import
|
|
10
|
+
EErrDep)
|
|
11
|
+
from pyvale.generatorsrandom import IGenRandom
|
|
13
12
|
from pyvale.sensordata import SensorData
|
|
14
13
|
|
|
15
14
|
|
|
@@ -21,21 +20,20 @@ class ErrSysOffset(IErrCalculator):
|
|
|
21
20
|
|
|
22
21
|
def __init__(self,
|
|
23
22
|
offset: float,
|
|
24
|
-
err_dep:
|
|
25
|
-
"""
|
|
26
|
-
|
|
23
|
+
err_dep: EErrDep = EErrDep.INDEPENDENT) -> None:
|
|
24
|
+
"""
|
|
27
25
|
Parameters
|
|
28
26
|
----------
|
|
29
27
|
offset : float
|
|
30
28
|
Constant offset to apply to all simulated measurements from the
|
|
31
29
|
sensor array.
|
|
32
30
|
err_dep : EErrDependence, optional
|
|
33
|
-
Error , by default EErrDependence.INDEPENDENT
|
|
31
|
+
Error calculation dependence, by default EErrDependence.INDEPENDENT.
|
|
34
32
|
"""
|
|
35
33
|
self._offset = offset
|
|
36
34
|
self._err_dep = err_dep
|
|
37
35
|
|
|
38
|
-
def get_error_dep(self) ->
|
|
36
|
+
def get_error_dep(self) -> EErrDep:
|
|
39
37
|
"""Gets the error dependence state for this error calculator. An
|
|
40
38
|
independent error is calculated based on the input truth values as the
|
|
41
39
|
error basis. A dependent error is calculated based on the accumulated
|
|
@@ -51,7 +49,7 @@ class ErrSysOffset(IErrCalculator):
|
|
|
51
49
|
"""
|
|
52
50
|
return self._err_dep
|
|
53
51
|
|
|
54
|
-
def set_error_dep(self, dependence:
|
|
52
|
+
def set_error_dep(self, dependence: EErrDep) -> None:
|
|
55
53
|
"""Sets the error dependence state for this error calculator. An
|
|
56
54
|
independent error is calculated based on the input truth values as the
|
|
57
55
|
error basis. A dependent error is calculated based on the accumulated
|
|
@@ -110,9 +108,8 @@ class ErrSysOffsetPercent(IErrCalculator):
|
|
|
110
108
|
|
|
111
109
|
def __init__(self,
|
|
112
110
|
offset_percent: float,
|
|
113
|
-
err_dep:
|
|
114
|
-
"""
|
|
115
|
-
|
|
111
|
+
err_dep: EErrDep = EErrDep.INDEPENDENT) -> None:
|
|
112
|
+
"""
|
|
116
113
|
Parameters
|
|
117
114
|
----------
|
|
118
115
|
offset_percent : float
|
|
@@ -124,7 +121,7 @@ class ErrSysOffsetPercent(IErrCalculator):
|
|
|
124
121
|
self._offset_percent = offset_percent
|
|
125
122
|
self._err_dep = err_dep
|
|
126
123
|
|
|
127
|
-
def get_error_dep(self) ->
|
|
124
|
+
def get_error_dep(self) -> EErrDep:
|
|
128
125
|
"""Gets the error dependence state for this error calculator. An
|
|
129
126
|
independent error is calculated based on the input truth values as the
|
|
130
127
|
error basis. A dependent error is calculated based on the accumulated
|
|
@@ -137,7 +134,7 @@ class ErrSysOffsetPercent(IErrCalculator):
|
|
|
137
134
|
"""
|
|
138
135
|
return self._err_dep
|
|
139
136
|
|
|
140
|
-
def set_error_dep(self, dependence:
|
|
137
|
+
def set_error_dep(self, dependence: EErrDep) -> None:
|
|
141
138
|
"""Sets the error dependence state for this error calculator. An
|
|
142
139
|
independent error is calculated based on the input truth values as the
|
|
143
140
|
error basis. A dependent error is calculated based on the accumulated
|
|
@@ -187,7 +184,7 @@ class ErrSysOffsetPercent(IErrCalculator):
|
|
|
187
184
|
sens_data)
|
|
188
185
|
|
|
189
186
|
|
|
190
|
-
class
|
|
187
|
+
class ErrSysUnif(IErrCalculator):
|
|
191
188
|
"""Systematic error calculator for applying an offset to each sensor that is
|
|
192
189
|
sampled from a uniform probability distribution specified by its upper and
|
|
193
190
|
lower bounds. Implements the `IErrCalculator` interface.
|
|
@@ -197,10 +194,9 @@ class ErrSysUniform(IErrCalculator):
|
|
|
197
194
|
def __init__(self,
|
|
198
195
|
low: float,
|
|
199
196
|
high: float,
|
|
200
|
-
err_dep:
|
|
197
|
+
err_dep: EErrDep = EErrDep.INDEPENDENT,
|
|
201
198
|
seed: int | None = None) -> None:
|
|
202
|
-
"""
|
|
203
|
-
|
|
199
|
+
"""
|
|
204
200
|
Parameters
|
|
205
201
|
----------
|
|
206
202
|
low : float
|
|
@@ -220,7 +216,7 @@ class ErrSysUniform(IErrCalculator):
|
|
|
220
216
|
self._rng = np.random.default_rng(seed)
|
|
221
217
|
self._err_dep = err_dep
|
|
222
218
|
|
|
223
|
-
def get_error_dep(self) ->
|
|
219
|
+
def get_error_dep(self) -> EErrDep:
|
|
224
220
|
"""Gets the error dependence state for this error calculator. An
|
|
225
221
|
independent error is calculated based on the input truth values as the
|
|
226
222
|
error basis. A dependent error is calculated based on the accumulated
|
|
@@ -236,7 +232,7 @@ class ErrSysUniform(IErrCalculator):
|
|
|
236
232
|
"""
|
|
237
233
|
return self._err_dep
|
|
238
234
|
|
|
239
|
-
def set_error_dep(self, dependence:
|
|
235
|
+
def set_error_dep(self, dependence: EErrDep) -> None:
|
|
240
236
|
"""Sets the error dependence state for this error calculator. An
|
|
241
237
|
independent error is calculated based on the input truth values as the
|
|
242
238
|
error basis. A dependent error is calculated based on the accumulated
|
|
@@ -296,7 +292,7 @@ class ErrSysUniform(IErrCalculator):
|
|
|
296
292
|
return (sys_errs,sens_data)
|
|
297
293
|
|
|
298
294
|
|
|
299
|
-
class
|
|
295
|
+
class ErrSysUnifPercent(IErrCalculator):
|
|
300
296
|
"""Systematic error calculator for applying a percentage offset to each
|
|
301
297
|
sensor that is sampled from a uniform probability distribution specified by
|
|
302
298
|
its upper and lower bounds.
|
|
@@ -312,10 +308,9 @@ class ErrSysUniformPercent(IErrCalculator):
|
|
|
312
308
|
def __init__(self,
|
|
313
309
|
low_percent: float,
|
|
314
310
|
high_percent: float,
|
|
315
|
-
err_dep:
|
|
311
|
+
err_dep: EErrDep = EErrDep.INDEPENDENT,
|
|
316
312
|
seed: int | None = None) -> None:
|
|
317
|
-
"""
|
|
318
|
-
|
|
313
|
+
"""
|
|
319
314
|
Parameters
|
|
320
315
|
----------
|
|
321
316
|
low_percent : float
|
|
@@ -333,7 +328,7 @@ class ErrSysUniformPercent(IErrCalculator):
|
|
|
333
328
|
self._rng = np.random.default_rng(seed)
|
|
334
329
|
self._err_dep = err_dep
|
|
335
330
|
|
|
336
|
-
def get_error_dep(self) ->
|
|
331
|
+
def get_error_dep(self) -> EErrDep:
|
|
337
332
|
"""Gets the error dependence state for this error calculator. An
|
|
338
333
|
independent error is calculated based on the input truth values as the
|
|
339
334
|
error basis. A dependent error is calculated based on the accumulated
|
|
@@ -346,7 +341,7 @@ class ErrSysUniformPercent(IErrCalculator):
|
|
|
346
341
|
"""
|
|
347
342
|
return self._err_dep
|
|
348
343
|
|
|
349
|
-
def set_error_dep(self, dependence:
|
|
344
|
+
def set_error_dep(self, dependence: EErrDep) -> None:
|
|
350
345
|
"""Sets the error dependence state for this error calculator. An
|
|
351
346
|
independent error is calculated based on the input truth values as the
|
|
352
347
|
error basis. A dependent error is calculated based on the accumulated
|
|
@@ -403,7 +398,7 @@ class ErrSysUniformPercent(IErrCalculator):
|
|
|
403
398
|
return (err_basis*sys_errs,sens_data)
|
|
404
399
|
|
|
405
400
|
|
|
406
|
-
class
|
|
401
|
+
class ErrSysNorm(IErrCalculator):
|
|
407
402
|
"""Systematic error calculator for applying an offset to each individual
|
|
408
403
|
sensor in the array based on sampling from a normal distribution specified
|
|
409
404
|
by its standard deviation and mean. Note that the offset is constant for
|
|
@@ -414,10 +409,9 @@ class ErrSysNormal(IErrCalculator):
|
|
|
414
409
|
def __init__(self,
|
|
415
410
|
std: float,
|
|
416
411
|
mean: float = 0.0,
|
|
417
|
-
err_dep:
|
|
412
|
+
err_dep: EErrDep = EErrDep.INDEPENDENT,
|
|
418
413
|
seed: int | None = None) -> None:
|
|
419
|
-
"""
|
|
420
|
-
|
|
414
|
+
"""
|
|
421
415
|
Parameters
|
|
422
416
|
----------
|
|
423
417
|
std : float
|
|
@@ -435,7 +429,7 @@ class ErrSysNormal(IErrCalculator):
|
|
|
435
429
|
self._rng = np.random.default_rng(seed)
|
|
436
430
|
self._err_dep = err_dep
|
|
437
431
|
|
|
438
|
-
def get_error_dep(self) ->
|
|
432
|
+
def get_error_dep(self) -> EErrDep:
|
|
439
433
|
"""Gets the error dependence state for this error calculator. An
|
|
440
434
|
independent error is calculated based on the input truth values as the
|
|
441
435
|
error basis. A dependent error is calculated based on the accumulated
|
|
@@ -451,7 +445,7 @@ class ErrSysNormal(IErrCalculator):
|
|
|
451
445
|
"""
|
|
452
446
|
return self._err_dep
|
|
453
447
|
|
|
454
|
-
def set_error_dep(self, dependence:
|
|
448
|
+
def set_error_dep(self, dependence: EErrDep) -> None:
|
|
455
449
|
"""Sets the error dependence state for this error calculator. An
|
|
456
450
|
independent error is calculated based on the input truth values as the
|
|
457
451
|
error basis. A dependent error is calculated based on the accumulated
|
|
@@ -527,13 +521,24 @@ class ErrSysNormPercent(IErrCalculator):
|
|
|
527
521
|
|
|
528
522
|
def __init__(self,
|
|
529
523
|
std_percent: float,
|
|
530
|
-
err_dep:
|
|
524
|
+
err_dep: EErrDep = EErrDep.INDEPENDENT,
|
|
531
525
|
seed: int | None = None) -> None:
|
|
526
|
+
"""
|
|
527
|
+
Parameters
|
|
528
|
+
----------
|
|
529
|
+
std_percent : float
|
|
530
|
+
Standard deviation as a percentage in the range (0,100).
|
|
531
|
+
err_dep : EErrDependence, optional
|
|
532
|
+
Error calculation dependence, by default EErrDependence.INDEPENDENT.
|
|
533
|
+
seed : int | None, optional
|
|
534
|
+
Optional seed for the random generator to allow for replicable
|
|
535
|
+
behaviour, by default None.
|
|
536
|
+
"""
|
|
532
537
|
self._std = std_percent/100
|
|
533
538
|
self._rng = np.random.default_rng(seed)
|
|
534
539
|
self._err_dep = err_dep
|
|
535
540
|
|
|
536
|
-
def get_error_dep(self) ->
|
|
541
|
+
def get_error_dep(self) -> EErrDep:
|
|
537
542
|
"""Gets the error dependence state for this error calculator. An
|
|
538
543
|
independent error is calculated based on the input truth values as the
|
|
539
544
|
error basis. A dependent error is calculated based on the accumulated
|
|
@@ -546,7 +551,7 @@ class ErrSysNormPercent(IErrCalculator):
|
|
|
546
551
|
"""
|
|
547
552
|
return self._err_dep
|
|
548
553
|
|
|
549
|
-
def set_error_dep(self, dependence:
|
|
554
|
+
def set_error_dep(self, dependence: EErrDep) -> None:
|
|
550
555
|
"""Sets the error dependence state for this error calculator. An
|
|
551
556
|
independent error is calculated based on the input truth values as the
|
|
552
557
|
error basis. A dependent error is calculated based on the accumulated
|
|
@@ -573,12 +578,6 @@ class ErrSysNormPercent(IErrCalculator):
|
|
|
573
578
|
err_basis: np.ndarray,
|
|
574
579
|
sens_data: SensorData,
|
|
575
580
|
) -> tuple[np.ndarray, SensorData]:
|
|
576
|
-
|
|
577
|
-
err_shape = np.array(err_basis.shape)
|
|
578
|
-
err_shape[-1] = 1
|
|
579
|
-
sys_errs = self._rng.normal(loc=0.0,
|
|
580
|
-
scale=self._std,
|
|
581
|
-
size=err_shape)
|
|
582
581
|
"""Calculates the error array based on the size of the input.
|
|
583
582
|
|
|
584
583
|
Parameters
|
|
@@ -596,6 +595,13 @@ class ErrSysNormPercent(IErrCalculator):
|
|
|
596
595
|
sensor data object as it is not modified by this class. The returned
|
|
597
596
|
error array has the same shape as the input error basis.
|
|
598
597
|
"""
|
|
598
|
+
|
|
599
|
+
err_shape = np.array(err_basis.shape)
|
|
600
|
+
err_shape[-1] = 1
|
|
601
|
+
sys_errs = self._rng.normal(loc=0.0,
|
|
602
|
+
scale=self._std,
|
|
603
|
+
size=err_shape)
|
|
604
|
+
|
|
599
605
|
tile_shape = np.array(err_basis.shape)
|
|
600
606
|
tile_shape[0:-1] = 1
|
|
601
607
|
sys_errs = np.tile(sys_errs,tuple(tile_shape))
|
|
@@ -603,7 +609,7 @@ class ErrSysNormPercent(IErrCalculator):
|
|
|
603
609
|
return (err_basis*sys_errs,sens_data)
|
|
604
610
|
|
|
605
611
|
|
|
606
|
-
class
|
|
612
|
+
class ErrSysGen(IErrCalculator):
|
|
607
613
|
"""Systematic error calculator for applying a unique offset to each sensor
|
|
608
614
|
by sample from a user specified probability distribution (an implementation
|
|
609
615
|
of the `IGeneratorRandom` interface).
|
|
@@ -613,13 +619,21 @@ class ErrSysGenerator(IErrCalculator):
|
|
|
613
619
|
__slots__ = ("_generator","_err_dep")
|
|
614
620
|
|
|
615
621
|
def __init__(self,
|
|
616
|
-
generator:
|
|
617
|
-
err_dep:
|
|
618
|
-
|
|
622
|
+
generator: IGenRandom,
|
|
623
|
+
err_dep: EErrDep = EErrDep.INDEPENDENT) -> None:
|
|
624
|
+
"""
|
|
625
|
+
Parameters
|
|
626
|
+
----------
|
|
627
|
+
generator : IGenRandom
|
|
628
|
+
Random generator object used to calculate the systematic error in
|
|
629
|
+
simulation units.
|
|
630
|
+
err_dep : EErrDependence, optional
|
|
631
|
+
Error calculation dependence, by default EErrDependence.INDEPENDENT.
|
|
632
|
+
"""
|
|
619
633
|
self._generator = generator
|
|
620
634
|
self._err_dep = err_dep
|
|
621
635
|
|
|
622
|
-
def get_error_dep(self) ->
|
|
636
|
+
def get_error_dep(self) -> EErrDep:
|
|
623
637
|
"""Gets the error dependence state for this error calculator. An
|
|
624
638
|
independent error is calculated based on the input truth values as the
|
|
625
639
|
error basis. A dependent error is calculated based on the accumulated
|
|
@@ -632,7 +646,7 @@ class ErrSysGenerator(IErrCalculator):
|
|
|
632
646
|
"""
|
|
633
647
|
return self._err_dep
|
|
634
648
|
|
|
635
|
-
def set_error_dep(self, dependence:
|
|
649
|
+
def set_error_dep(self, dependence: EErrDep) -> None:
|
|
636
650
|
"""Sets the error dependence state for this error calculator. An
|
|
637
651
|
independent error is calculated based on the input truth values as the
|
|
638
652
|
error basis. A dependent error is calculated based on the accumulated
|
|
@@ -704,13 +718,21 @@ class ErrSysGenPercent(IErrCalculator):
|
|
|
704
718
|
__slots__ = ("_generator","_err_dep")
|
|
705
719
|
|
|
706
720
|
def __init__(self,
|
|
707
|
-
generator:
|
|
708
|
-
err_dep:
|
|
709
|
-
|
|
721
|
+
generator: IGenRandom,
|
|
722
|
+
err_dep: EErrDep = EErrDep.INDEPENDENT) -> None:
|
|
723
|
+
"""
|
|
724
|
+
Parameters
|
|
725
|
+
----------
|
|
726
|
+
generator : IGenRandom
|
|
727
|
+
Random generator which returns a percentage error in the range
|
|
728
|
+
(0,100)
|
|
729
|
+
err_dep : EErrDep, optional
|
|
730
|
+
Error calculation dependence, by default EErrDep.INDEPENDENT
|
|
731
|
+
"""
|
|
710
732
|
self._generator = generator
|
|
711
733
|
self._err_dep = err_dep
|
|
712
734
|
|
|
713
|
-
def get_error_dep(self) ->
|
|
735
|
+
def get_error_dep(self) -> EErrDep:
|
|
714
736
|
"""Gets the error dependence state for this error calculator. An
|
|
715
737
|
independent error is calculated based on the input truth values as the
|
|
716
738
|
error basis. A dependent error is calculated based on the accumulated
|
|
@@ -723,7 +745,7 @@ class ErrSysGenPercent(IErrCalculator):
|
|
|
723
745
|
"""
|
|
724
746
|
return self._err_dep
|
|
725
747
|
|
|
726
|
-
def set_error_dep(self, dependence:
|
|
748
|
+
def set_error_dep(self, dependence: EErrDep) -> None:
|
|
727
749
|
"""Sets the error dependence state for this error calculator. An
|
|
728
750
|
independent error is calculated based on the input truth values as the
|
|
729
751
|
error basis. A dependent error is calculated based on the accumulated
|
|
@@ -770,7 +792,9 @@ class ErrSysGenPercent(IErrCalculator):
|
|
|
770
792
|
err_shape = np.array(err_basis.shape)
|
|
771
793
|
err_shape[-1] = 1
|
|
772
794
|
|
|
773
|
-
sys_errs = self._generator.generate(
|
|
795
|
+
sys_errs = self._generator.generate(shape=err_shape)
|
|
796
|
+
# Convert percent to decimal
|
|
797
|
+
sys_errs = sys_errs/100.0
|
|
774
798
|
|
|
775
799
|
tile_shape = np.array(err_basis.shape)
|
|
776
800
|
tile_shape[0:-1] = 1
|
|
@@ -780,125 +804,5 @@ class ErrSysGenPercent(IErrCalculator):
|
|
|
780
804
|
return (sys_errs,sens_data)
|
|
781
805
|
|
|
782
806
|
|
|
783
|
-
class ErrSysCalibration(IErrCalculator):
|
|
784
|
-
"""Systematic error calculator for calibration errors. The user specifies an
|
|
785
|
-
assumed calibration and a ground truth calibration function. The ground
|
|
786
|
-
truth calibration function is inverted and linearly interpolated numerically
|
|
787
|
-
based on the number of divisions specified by the user.
|
|
788
|
-
|
|
789
|
-
Implements the `IErrCalculator` interface.
|
|
790
|
-
"""
|
|
791
|
-
__slots__ = ("_assumed_cali","_truth_calib","_cal_range","_n_cal_divs",
|
|
792
|
-
"_err_dep","_truth_calc_table")
|
|
793
|
-
|
|
794
|
-
def __init__(self,
|
|
795
|
-
assumed_calib: Callable[[np.ndarray],np.ndarray],
|
|
796
|
-
truth_calib: Callable[[np.ndarray],np.ndarray],
|
|
797
|
-
cal_range: tuple[float,float],
|
|
798
|
-
n_cal_divs: int = 10000,
|
|
799
|
-
err_dep: EErrDependence = EErrDependence.INDEPENDENT) -> None:
|
|
800
|
-
"""_summary_
|
|
801
|
-
|
|
802
|
-
Parameters
|
|
803
|
-
----------
|
|
804
|
-
assumed_calib : Callable[[np.ndarray],np.ndarray]
|
|
805
|
-
Assumed calibration function taking the input unitless 'signal' and
|
|
806
|
-
converting it to the same units as the physical field being sampled
|
|
807
|
-
by the sensor array.
|
|
808
|
-
truth_calib : Callable[[np.ndarray],np.ndarray]
|
|
809
|
-
Assumed calibration function taking the input unitless 'signal' and
|
|
810
|
-
converting it to the same units as the physical field being sampled
|
|
811
|
-
by the sensor array.
|
|
812
|
-
cal_range : tuple[float,float]
|
|
813
|
-
Range over which the calibration functions are valid. This is
|
|
814
|
-
normally based on a voltage range such as (0,10) volts.
|
|
815
|
-
n_cal_divs : int, optional
|
|
816
|
-
Number of divisions to discretise the the truth calibration function
|
|
817
|
-
for numerical inversion, by default 10000.
|
|
818
|
-
err_dep : EErrDependence, optional
|
|
819
|
-
Error calculation dependence, by default EErrDependence.INDEPENDENT.
|
|
820
|
-
"""
|
|
821
|
-
self._assumed_calib = assumed_calib
|
|
822
|
-
self._truth_calib = truth_calib
|
|
823
|
-
self._cal_range = cal_range
|
|
824
|
-
self._n_cal_divs = n_cal_divs
|
|
825
|
-
self._err_dep = err_dep
|
|
826
|
-
|
|
827
|
-
self._truth_cal_table = np.zeros((n_cal_divs,2))
|
|
828
|
-
self._truth_cal_table[:,0] = np.linspace(cal_range[0],
|
|
829
|
-
cal_range[1],
|
|
830
|
-
n_cal_divs)
|
|
831
|
-
self._truth_cal_table[:,1] = self._truth_calib(
|
|
832
|
-
self._truth_cal_table[:,0])
|
|
833
|
-
|
|
834
|
-
def get_error_dep(self) -> EErrDependence:
|
|
835
|
-
"""Gets the error dependence state for this error calculator. An
|
|
836
|
-
independent error is calculated based on the input truth values as the
|
|
837
|
-
error basis. A dependent error is calculated based on the accumulated
|
|
838
|
-
sensor reading from all preceeding errors in the chain.
|
|
839
|
-
|
|
840
|
-
Returns
|
|
841
|
-
-------
|
|
842
|
-
EErrDependence
|
|
843
|
-
Enumeration defining INDEPENDENT or DEPENDENT behaviour.
|
|
844
|
-
"""
|
|
845
|
-
return self._err_dep
|
|
846
|
-
|
|
847
|
-
def set_error_dep(self, dependence: EErrDependence) -> None:
|
|
848
|
-
"""Sets the error dependence state for this error calculator. An
|
|
849
|
-
independent error is calculated based on the input truth values as the
|
|
850
|
-
error basis. A dependent error is calculated based on the accumulated
|
|
851
|
-
sensor reading from all preceeding errors in the chain.
|
|
852
|
-
|
|
853
|
-
Parameters
|
|
854
|
-
----------
|
|
855
|
-
dependence : EErrDependence
|
|
856
|
-
Enumeration defining INDEPENDENT or DEPENDENT behaviour.
|
|
857
|
-
"""
|
|
858
|
-
self._err_dep = dependence
|
|
859
|
-
|
|
860
|
-
def get_error_type(self) -> EErrType:
|
|
861
|
-
"""Gets the error type.
|
|
862
|
-
|
|
863
|
-
Returns
|
|
864
|
-
-------
|
|
865
|
-
EErrType
|
|
866
|
-
Enumeration definining RANDOM or SYSTEMATIC error types.
|
|
867
|
-
"""
|
|
868
|
-
return EErrType.SYSTEMATIC
|
|
869
|
-
|
|
870
|
-
def calc_errs(self,
|
|
871
|
-
err_basis: np.ndarray,
|
|
872
|
-
sens_data: SensorData,
|
|
873
|
-
) -> tuple[np.ndarray, SensorData]:
|
|
874
|
-
"""Calculates the error array based on the size of the input.
|
|
875
|
-
|
|
876
|
-
Parameters
|
|
877
|
-
----------
|
|
878
|
-
err_basis : np.ndarray
|
|
879
|
-
Array of values with the same dimensions as the sensor measurement
|
|
880
|
-
matrix.
|
|
881
|
-
sens_data : SensorData
|
|
882
|
-
The accumulated sensor state data for all errors prior to this one.
|
|
883
|
-
|
|
884
|
-
Returns
|
|
885
|
-
-------
|
|
886
|
-
tuple[np.ndarray, SensorData]
|
|
887
|
-
Tuple containing the calculated error array and pass through of the
|
|
888
|
-
sensor data object as it is not modified by this class. The returned
|
|
889
|
-
error array has the same shape as the input error basis.
|
|
890
|
-
"""
|
|
891
|
-
# shape=(n_sens,n_comps,n_time_steps)
|
|
892
|
-
signal_from_field = np.interp(err_basis,
|
|
893
|
-
self._truth_cal_table[:,1],
|
|
894
|
-
self._truth_cal_table[:,0])
|
|
895
|
-
# shape=(n_sens,n_comps,n_time_steps)
|
|
896
|
-
field_from_assumed_calib = self._assumed_calib(signal_from_field)
|
|
897
|
-
|
|
898
|
-
sys_errs = field_from_assumed_calib - err_basis
|
|
899
|
-
|
|
900
|
-
return (sys_errs,sens_data)
|
|
901
|
-
|
|
902
|
-
|
|
903
807
|
|
|
904
808
|
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# ==============================================================================
|
|
2
|
+
# pyvale: the python validation engine
|
|
3
|
+
# License: MIT
|
|
4
|
+
# Copyright (C) 2025 The Computer Aided Validation Team
|
|
5
|
+
# ==============================================================================
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
Pyvale example: Basics of pyvale point sensor simualtion
|
|
9
|
+
--------------------------------------------------------------------------------
|
|
10
|
+
In this example we introduce the basic features of pyvale for point sensor
|
|
11
|
+
simulation. We demonstrate quick sensor array construction with defaults using
|
|
12
|
+
the pyvale sensor array factory. Finally we run a sensor simulation and display
|
|
13
|
+
the output.
|
|
14
|
+
|
|
15
|
+
Test case: Scalar field point sensors (thermocouples) on a 2D thermal simulation
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
import matplotlib.pyplot as plt
|
|
20
|
+
import mooseherder as mh
|
|
21
|
+
import pyvale as pyv
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def main() -> None:
|
|
25
|
+
|
|
26
|
+
# Here we load a pre-generated MOOSE finite element simulation dataset that
|
|
27
|
+
# comes packaged with pyvale. The simulation is a 2D rectangular plate with
|
|
28
|
+
# a bi-directional temperature gradient. You can replace this with the path
|
|
29
|
+
# to your own MOOSE simulation with exodus output (*.e). Note that the
|
|
30
|
+
# field_key must match the name of your variable in your MOOSE simulation.
|
|
31
|
+
data_path = pyv.DataSet.thermal_2d_path()
|
|
32
|
+
# We use `mooseherder` to load the exodus file into a `SimData` object.
|
|
33
|
+
sim_data = mh.ExodusReader(data_path).read_all_sim_data()
|
|
34
|
+
|
|
35
|
+
# Scale to mm to make 3D visualisation scaling easier as pyvista scales
|
|
36
|
+
# everything to unity
|
|
37
|
+
sim_data = pyv.scale_length_units(scale=1000.0,
|
|
38
|
+
sim_data=sim_data,
|
|
39
|
+
disp_comps=None)
|
|
40
|
+
|
|
41
|
+
# We now use a helper function to create a grid of sensor locations but we
|
|
42
|
+
# could have also manually built the numpy array of sensor locations which
|
|
43
|
+
# has the shape=(num_sensors,coord[x,y,z]).
|
|
44
|
+
n_sens = (3,2,1)
|
|
45
|
+
x_lims = (0.0,100.0)
|
|
46
|
+
y_lims = (0.0,50.0)
|
|
47
|
+
z_lims = (0.0,0.0)
|
|
48
|
+
sens_pos = pyv.create_sensor_pos_array(n_sens,x_lims,y_lims,z_lims)
|
|
49
|
+
|
|
50
|
+
# This dataclass contains the parameters to build our sensor array. We can
|
|
51
|
+
# also customise the output frequency, the sensor area and the sensor
|
|
52
|
+
# orientation. For now we will use the defaults which assumes an ideal point
|
|
53
|
+
# sensor sampling at the simulation time steps.
|
|
54
|
+
sens_data = pyv.SensorData(positions=sens_pos)
|
|
55
|
+
|
|
56
|
+
# Now that we have our sensor locations we can use the sensor factory to
|
|
57
|
+
# build a basic thermocouple array with some useful defaults. In later
|
|
58
|
+
# examples we will see how to customise sensor parameters and errors.
|
|
59
|
+
# This basic thermocouple array includes a 1% systematic and random error.
|
|
60
|
+
# If you want to remove the simulated errors and just interpolate at the
|
|
61
|
+
# sensor locations then user `.thermocouples_no_errs()`.
|
|
62
|
+
field_key: str = "temperature"
|
|
63
|
+
tc_array = pyv.SensorArrayFactory \
|
|
64
|
+
.thermocouples_basic_errs(sim_data,
|
|
65
|
+
sens_data,
|
|
66
|
+
elem_dims=2,
|
|
67
|
+
field_name=field_key)
|
|
68
|
+
|
|
69
|
+
# We have built our sensor array so now we can call `calc_measurements()` to
|
|
70
|
+
# generate simulated sensor traces.
|
|
71
|
+
measurements = tc_array.calc_measurements()
|
|
72
|
+
print(f"\nMeasurements for last sensor:\n{measurements[-1,0,:]}\n")
|
|
73
|
+
|
|
74
|
+
# We can now visualise the sensor locations on the simulation mesh and the
|
|
75
|
+
# simulated sensor traces using pyvale's visualisation tools which use
|
|
76
|
+
# pyvista for meshes and matplotlib for sensor traces. pyvale will return
|
|
77
|
+
# plot and axes objects to the user allowing additional customisation using
|
|
78
|
+
# pyvista and matplotlib. This also means that we need to call `.show()`
|
|
79
|
+
# ourselves to display the figure as pyvale does not do this for us.
|
|
80
|
+
|
|
81
|
+
# If we are going to save figures we need to make sure the path exists. Here
|
|
82
|
+
# we create a default output path based on your current working directory.
|
|
83
|
+
output_path = Path.cwd() / "pyvale-output"
|
|
84
|
+
if not output_path.is_dir():
|
|
85
|
+
output_path.mkdir(parents=True, exist_ok=True)
|
|
86
|
+
|
|
87
|
+
# This creates a pyvista visualisation of the sensor locations on the
|
|
88
|
+
# simulation mesh. The plot will can be shown in interactive mode by calling
|
|
89
|
+
# `pv_plot.show()` alternatively
|
|
90
|
+
pv_plot = pyv.plot_point_sensors_on_sim(tc_array,field_key)
|
|
91
|
+
|
|
92
|
+
# We determined manually by moving camera in interative mode and then
|
|
93
|
+
# printing camera position to console after window close, as below.
|
|
94
|
+
pv_plot.camera_position = [(-7.547, 59.753, 134.52),
|
|
95
|
+
(41.916, 25.303, 9.297),
|
|
96
|
+
(0.0810, 0.969, -0.234)]
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
# This allows us to save a vector graphic and raster graphic showing the
|
|
100
|
+
# sensor locations on the simulation mesh
|
|
101
|
+
save_render = output_path / "basics_ex1_1_sensorlocs.svg"
|
|
102
|
+
pv_plot.save_graphic(save_render) # only for .svg .eps .ps .pdf .tex
|
|
103
|
+
pv_plot.screenshot(save_render.with_suffix(".png"))
|
|
104
|
+
|
|
105
|
+
# We can also show the simulation and sensor locations in interative mode
|
|
106
|
+
# by calling `.show()`
|
|
107
|
+
pv_plot.show()
|
|
108
|
+
|
|
109
|
+
print(80*"-")
|
|
110
|
+
print("Camera position after interative view:")
|
|
111
|
+
print(pv_plot.camera_position)
|
|
112
|
+
print(80*"-"+"\n")
|
|
113
|
+
|
|
114
|
+
# This plots the time traces for all of our sensors. The solid line shows
|
|
115
|
+
# the 'truth' interpolated from the simulation and the dashed line with
|
|
116
|
+
# markers shows the simulated sensor traces. In later examples we will see
|
|
117
|
+
# how to configure this plot but for now we note we that we are returned a
|
|
118
|
+
# matplotlib figure and axes object which allows for further customisation.
|
|
119
|
+
(fig,ax) = pyv.plot_time_traces(tc_array,field_key)
|
|
120
|
+
|
|
121
|
+
# We can also save the sensor trace plot as a vector and raster graphic
|
|
122
|
+
save_traces = output_path/"basics_ex1_1_sensortraces.png"
|
|
123
|
+
fig.savefig(save_traces, dpi=300, bbox_inches="tight")
|
|
124
|
+
fig.savefig(save_traces.with_suffix(".svg"), dpi=300, bbox_inches="tight")
|
|
125
|
+
|
|
126
|
+
# The trace plot can also be shown in interactive mode using `plt.show()`
|
|
127
|
+
plt.show()
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
if __name__ == "__main__":
|
|
131
|
+
main()
|