pyvale 2025.4.0__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 +78 -64
- pyvale/analyticmeshgen.py +102 -0
- pyvale/{core/analyticsimdatafactory.py → analyticsimdatafactory.py} +44 -16
- pyvale/analyticsimdatagenerator.py +323 -0
- 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/{core/camera.py → camera.py} +15 -15
- pyvale/{core/cameradata.py → cameradata.py} +27 -22
- pyvale/{core/cameradata2d.py → cameradata2d.py} +8 -6
- pyvale/camerastereo.py +217 -0
- pyvale/{core/cameratools.py → cameratools.py} +220 -26
- pyvale/{core/cython → cython}/rastercyth.py +11 -7
- pyvale/data/__init__.py +5 -7
- pyvale/data/cal_target.tiff +0 -0
- pyvale/data/case00_HEX20_out.e +0 -0
- pyvale/data/case00_HEX27_out.e +0 -0
- pyvale/data/case00_HEX8_out.e +0 -0
- pyvale/data/case00_TET10_out.e +0 -0
- pyvale/data/case00_TET14_out.e +0 -0
- pyvale/data/case00_TET4_out.e +0 -0
- pyvale/{core/dataset.py → dataset.py} +91 -16
- pyvale/{core/errorcalculator.py → errorcalculator.py} +13 -16
- pyvale/{core/errordriftcalc.py → errordriftcalc.py} +14 -14
- pyvale/{core/errorintegrator.py → errorintegrator.py} +25 -28
- pyvale/{core/errorrand.py → errorrand.py} +39 -46
- pyvale/errorsyscalib.py +134 -0
- pyvale/{core/errorsysdep.py → errorsysdep.py} +25 -29
- pyvale/{core/errorsysfield.py → errorsysfield.py} +59 -52
- pyvale/{core/errorsysindep.py → errorsysindep.py} +85 -182
- pyvale/examples/__init__.py +5 -7
- 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 +6 -7
- pyvale/examples/{rasterisation → renderrasterisation}/ex_rastercyth_oneframe.py +5 -7
- pyvale/examples/{rasterisation → renderrasterisation}/ex_rastercyth_static_cypara.py +6 -13
- pyvale/examples/{rasterisation → renderrasterisation}/ex_rastercyth_static_pypara.py +9 -12
- pyvale/examples/{ex1_4_thermal2d.py → visualisation/ex1_1_plot_traces.py} +33 -20
- pyvale/examples/{features/ex_animation_tools_3dmonoblock.py → visualisation/ex2_1_animate_sim.py} +37 -31
- pyvale/experimentsimulator.py +175 -0
- pyvale/{core/field.py → field.py} +6 -14
- pyvale/fieldconverter.py +351 -0
- pyvale/{core/fieldsampler.py → fieldsampler.py} +9 -10
- pyvale/{core/fieldscalar.py → fieldscalar.py} +17 -18
- pyvale/{core/fieldtensor.py → fieldtensor.py} +23 -26
- pyvale/{core/fieldtransform.py → fieldtransform.py} +9 -5
- pyvale/{core/fieldvector.py → fieldvector.py} +14 -16
- pyvale/{core/generatorsrandom.py → generatorsrandom.py} +29 -52
- pyvale/{core/imagedef2d.py → imagedef2d.py} +11 -8
- pyvale/{core/integratorfactory.py → integratorfactory.py} +12 -13
- pyvale/{core/integratorquadrature.py → integratorquadrature.py} +57 -32
- pyvale/integratorrectangle.py +165 -0
- pyvale/{core/integratorspatial.py → integratorspatial.py} +9 -10
- pyvale/{core/integratortype.py → integratortype.py} +7 -8
- pyvale/output.py +17 -0
- pyvale/pyvaleexceptions.py +11 -0
- pyvale/{core/raster.py → raster.py} +8 -8
- pyvale/{core/rastercy.py → rastercy.py} +11 -10
- pyvale/{core/rasternp.py → rasternp.py} +12 -13
- pyvale/{core/rendermesh.py → rendermesh.py} +10 -19
- pyvale/{core/sensorarray.py → sensorarray.py} +7 -8
- pyvale/{core/sensorarrayfactory.py → sensorarrayfactory.py} +64 -78
- pyvale/{core/sensorarraypoint.py → sensorarraypoint.py} +39 -41
- pyvale/{core/sensordata.py → sensordata.py} +7 -8
- pyvale/sensordescriptor.py +213 -0
- pyvale/{core/sensortools.py → sensortools.py} +8 -9
- pyvale/simcases/case00_HEX20.i +5 -5
- pyvale/simcases/case00_HEX27.i +5 -5
- pyvale/simcases/case00_HEX8.i +242 -0
- pyvale/simcases/case00_TET10.i +2 -2
- pyvale/simcases/case00_TET14.i +2 -2
- pyvale/simcases/case00_TET4.i +242 -0
- pyvale/simcases/run_1case.py +1 -1
- pyvale/simtools.py +67 -0
- pyvale/visualexpplotter.py +191 -0
- pyvale/{core/visualimagedef.py → visualimagedef.py} +13 -10
- pyvale/{core/visualimages.py → visualimages.py} +10 -9
- pyvale/visualopts.py +493 -0
- pyvale/{core/visualsimanimator.py → visualsimanimator.py} +47 -19
- pyvale/visualsimsensors.py +318 -0
- pyvale/visualtools.py +136 -0
- pyvale/visualtraceplotter.py +142 -0
- {pyvale-2025.4.0.dist-info → pyvale-2025.5.1.dist-info}/METADATA +17 -14
- pyvale-2025.5.1.dist-info/RECORD +172 -0
- {pyvale-2025.4.0.dist-info → pyvale-2025.5.1.dist-info}/WHEEL +1 -1
- pyvale/core/__init__.py +0 -7
- pyvale/core/analyticmeshgen.py +0 -59
- pyvale/core/analyticsimdatagenerator.py +0 -160
- pyvale/core/cython/rastercyth.c +0 -32267
- pyvale/core/experimentsimulator.py +0 -99
- pyvale/core/fieldconverter.py +0 -154
- pyvale/core/integratorrectangle.py +0 -88
- pyvale/core/optimcheckfuncs.py +0 -153
- pyvale/core/sensordescriptor.py +0 -101
- pyvale/core/visualexpplotter.py +0 -151
- pyvale/core/visualopts.py +0 -180
- pyvale/core/visualsimplotter.py +0 -182
- pyvale/core/visualtools.py +0 -81
- pyvale/core/visualtraceplotter.py +0 -256
- pyvale/examples/analyticdatagen/__init__.py +0 -7
- pyvale/examples/ex1_1_thermal2d.py +0 -89
- pyvale/examples/ex1_2_thermal2d.py +0 -111
- pyvale/examples/ex1_3_thermal2d.py +0 -113
- pyvale/examples/ex1_5_thermal2d.py +0 -105
- pyvale/examples/ex2_1_thermal3d .py +0 -87
- pyvale/examples/ex2_2_thermal3d.py +0 -51
- pyvale/examples/ex2_3_thermal3d.py +0 -109
- pyvale/examples/ex3_1_displacement2d.py +0 -47
- pyvale/examples/ex3_2_displacement2d.py +0 -79
- pyvale/examples/ex3_3_displacement2d.py +0 -104
- pyvale/examples/ex3_4_displacement2d.py +0 -105
- pyvale/examples/ex4_1_strain2d.py +0 -57
- pyvale/examples/ex4_2_strain2d.py +0 -79
- pyvale/examples/ex4_3_strain2d.py +0 -100
- pyvale/examples/ex5_1_multiphysics2d.py +0 -78
- pyvale/examples/ex6_1_multiphysics2d_expsim.py +0 -118
- pyvale/examples/ex6_2_multiphysics3d_expsim.py +0 -158
- pyvale/examples/features/__init__.py +0 -7
- 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-2025.4.0.dist-info/RECORD +0 -157
- {pyvale-2025.4.0.dist-info → pyvale-2025.5.1.dist-info}/licenses/LICENSE +0 -0
- {pyvale-2025.4.0.dist-info → pyvale-2025.5.1.dist-info}/top_level.txt +0 -0
pyvale/errorsyscalib.py
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# ==============================================================================
|
|
2
|
+
# pyvale: the python validation engine
|
|
3
|
+
# License: MIT
|
|
4
|
+
# Copyright (C) 2025 The Computer Aided Validation Team
|
|
5
|
+
# ==============================================================================
|
|
6
|
+
|
|
7
|
+
from typing import Callable
|
|
8
|
+
import numpy as np
|
|
9
|
+
from pyvale.errorcalculator import (IErrCalculator,
|
|
10
|
+
EErrType,
|
|
11
|
+
EErrDep)
|
|
12
|
+
from pyvale.sensordata import SensorData
|
|
13
|
+
|
|
14
|
+
# TODO: add option to use Newton's method for function inversion instead of a
|
|
15
|
+
# cal table.
|
|
16
|
+
|
|
17
|
+
class ErrSysCalibration(IErrCalculator):
|
|
18
|
+
"""Systematic error calculator for calibration errors. The user specifies an
|
|
19
|
+
assumed calibration and a ground truth calibration function. The ground
|
|
20
|
+
truth calibration function is inverted and linearly interpolated numerically
|
|
21
|
+
based on the number of divisions specified by the user.
|
|
22
|
+
|
|
23
|
+
Implements the `IErrCalculator` interface.
|
|
24
|
+
"""
|
|
25
|
+
__slots__ = ("_assumed_cali","_truth_calib","_cal_range","_n_cal_divs",
|
|
26
|
+
"_err_dep","_truth_calc_table")
|
|
27
|
+
|
|
28
|
+
def __init__(self,
|
|
29
|
+
assumed_calib: Callable[[np.ndarray],np.ndarray],
|
|
30
|
+
truth_calib: Callable[[np.ndarray],np.ndarray],
|
|
31
|
+
cal_range: tuple[float,float],
|
|
32
|
+
n_cal_divs: int = 10000,
|
|
33
|
+
err_dep: EErrDep = EErrDep.INDEPENDENT) -> None:
|
|
34
|
+
"""
|
|
35
|
+
Parameters
|
|
36
|
+
----------
|
|
37
|
+
assumed_calib : Callable[[np.ndarray],np.ndarray]
|
|
38
|
+
Assumed calibration function taking the input unitless 'signal' and
|
|
39
|
+
converting it to the same units as the physical field being sampled
|
|
40
|
+
by the sensor array.
|
|
41
|
+
truth_calib : Callable[[np.ndarray],np.ndarray]
|
|
42
|
+
Assumed calibration function taking the input unitless 'signal' and
|
|
43
|
+
converting it to the same units as the physical field being sampled
|
|
44
|
+
by the sensor array.
|
|
45
|
+
cal_range : tuple[float,float]
|
|
46
|
+
Range over which the calibration functions are valid. This is
|
|
47
|
+
normally based on a voltage range such as (0,10) volts.
|
|
48
|
+
n_cal_divs : int, optional
|
|
49
|
+
Number of divisions to discretise the the truth calibration function
|
|
50
|
+
for numerical inversion, by default 10000.
|
|
51
|
+
err_dep : EErrDependence, optional
|
|
52
|
+
Error calculation dependence, by default EErrDependence.INDEPENDENT.
|
|
53
|
+
"""
|
|
54
|
+
self._assumed_calib = assumed_calib
|
|
55
|
+
self._truth_calib = truth_calib
|
|
56
|
+
self._cal_range = cal_range
|
|
57
|
+
self._n_cal_divs = n_cal_divs
|
|
58
|
+
self._err_dep = err_dep
|
|
59
|
+
|
|
60
|
+
self._truth_cal_table = np.zeros((n_cal_divs,2))
|
|
61
|
+
self._truth_cal_table[:,0] = np.linspace(cal_range[0],
|
|
62
|
+
cal_range[1],
|
|
63
|
+
n_cal_divs)
|
|
64
|
+
self._truth_cal_table[:,1] = self._truth_calib(
|
|
65
|
+
self._truth_cal_table[:,0])
|
|
66
|
+
|
|
67
|
+
def get_error_dep(self) -> EErrDep:
|
|
68
|
+
"""Gets the error dependence state for this error calculator. An
|
|
69
|
+
independent error is calculated based on the input truth values as the
|
|
70
|
+
error basis. A dependent error is calculated based on the accumulated
|
|
71
|
+
sensor reading from all preceeding errors in the chain.
|
|
72
|
+
|
|
73
|
+
Returns
|
|
74
|
+
-------
|
|
75
|
+
EErrDependence
|
|
76
|
+
Enumeration defining INDEPENDENT or DEPENDENT behaviour.
|
|
77
|
+
"""
|
|
78
|
+
return self._err_dep
|
|
79
|
+
|
|
80
|
+
def set_error_dep(self, dependence: EErrDep) -> None:
|
|
81
|
+
"""Sets the error dependence state for this error calculator. An
|
|
82
|
+
independent error is calculated based on the input truth values as the
|
|
83
|
+
error basis. A dependent error is calculated based on the accumulated
|
|
84
|
+
sensor reading from all preceeding errors in the chain.
|
|
85
|
+
|
|
86
|
+
Parameters
|
|
87
|
+
----------
|
|
88
|
+
dependence : EErrDependence
|
|
89
|
+
Enumeration defining INDEPENDENT or DEPENDENT behaviour.
|
|
90
|
+
"""
|
|
91
|
+
self._err_dep = dependence
|
|
92
|
+
|
|
93
|
+
def get_error_type(self) -> EErrType:
|
|
94
|
+
"""Gets the error type.
|
|
95
|
+
|
|
96
|
+
Returns
|
|
97
|
+
-------
|
|
98
|
+
EErrType
|
|
99
|
+
Enumeration definining RANDOM or SYSTEMATIC error types.
|
|
100
|
+
"""
|
|
101
|
+
return EErrType.SYSTEMATIC
|
|
102
|
+
|
|
103
|
+
def calc_errs(self,
|
|
104
|
+
err_basis: np.ndarray,
|
|
105
|
+
sens_data: SensorData,
|
|
106
|
+
) -> tuple[np.ndarray, SensorData]:
|
|
107
|
+
"""Calculates the error array based on the size of the input.
|
|
108
|
+
|
|
109
|
+
Parameters
|
|
110
|
+
----------
|
|
111
|
+
err_basis : np.ndarray
|
|
112
|
+
Array of values with the same dimensions as the sensor measurement
|
|
113
|
+
matrix.
|
|
114
|
+
sens_data : SensorData
|
|
115
|
+
The accumulated sensor state data for all errors prior to this one.
|
|
116
|
+
|
|
117
|
+
Returns
|
|
118
|
+
-------
|
|
119
|
+
tuple[np.ndarray, SensorData]
|
|
120
|
+
Tuple containing the calculated error array and pass through of the
|
|
121
|
+
sensor data object as it is not modified by this class. The returned
|
|
122
|
+
error array has the same shape as the input error basis.
|
|
123
|
+
"""
|
|
124
|
+
# shape=(n_sens,n_comps,n_time_steps)
|
|
125
|
+
signal_from_field = np.interp(err_basis,
|
|
126
|
+
self._truth_cal_table[:,1],
|
|
127
|
+
self._truth_cal_table[:,0])
|
|
128
|
+
# shape=(n_sens,n_comps,n_time_steps)
|
|
129
|
+
field_from_assumed_calib = self._assumed_calib(signal_from_field)
|
|
130
|
+
|
|
131
|
+
sys_errs = field_from_assumed_calib - err_basis
|
|
132
|
+
|
|
133
|
+
return (sys_errs,sens_data)
|
|
134
|
+
|
|
@@ -1,17 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
"""
|
|
1
|
+
# ==============================================================================
|
|
2
|
+
# pyvale: the python validation engine
|
|
3
|
+
# License: MIT
|
|
4
|
+
# Copyright (C) 2025 The Computer Aided Validation Team
|
|
5
|
+
# ==============================================================================
|
|
6
|
+
|
|
8
7
|
import enum
|
|
9
8
|
from typing import Callable
|
|
10
9
|
import numpy as np
|
|
11
|
-
from pyvale.
|
|
12
|
-
from pyvale.
|
|
10
|
+
from pyvale.sensordata import SensorData
|
|
11
|
+
from pyvale.errorcalculator import (IErrCalculator,
|
|
13
12
|
EErrType,
|
|
14
|
-
|
|
13
|
+
EErrDep)
|
|
15
14
|
|
|
16
15
|
|
|
17
16
|
class ERoundMethod(enum.Enum):
|
|
@@ -54,9 +53,8 @@ class ErrSysRoundOff(IErrCalculator):
|
|
|
54
53
|
def __init__(self,
|
|
55
54
|
method: ERoundMethod = ERoundMethod.ROUND,
|
|
56
55
|
base: float = 1.0,
|
|
57
|
-
err_dep:
|
|
58
|
-
"""
|
|
59
|
-
|
|
56
|
+
err_dep: EErrDep = EErrDep.DEPENDENT) -> None:
|
|
57
|
+
"""
|
|
60
58
|
Parameters
|
|
61
59
|
----------
|
|
62
60
|
method : ERoundMethod, optional
|
|
@@ -71,7 +69,7 @@ class ErrSysRoundOff(IErrCalculator):
|
|
|
71
69
|
self._method = _select_round_method(method)
|
|
72
70
|
self._err_dep = err_dep
|
|
73
71
|
|
|
74
|
-
def get_error_dep(self) ->
|
|
72
|
+
def get_error_dep(self) -> EErrDep:
|
|
75
73
|
"""Gets the error dependence state for this error calculator. An
|
|
76
74
|
independent error is calculated based on the input truth values as the
|
|
77
75
|
error basis. A dependent error is calculated based on the accumulated
|
|
@@ -84,7 +82,7 @@ class ErrSysRoundOff(IErrCalculator):
|
|
|
84
82
|
"""
|
|
85
83
|
return self._err_dep
|
|
86
84
|
|
|
87
|
-
def set_error_dep(self, dependence:
|
|
85
|
+
def set_error_dep(self, dependence: EErrDep) -> None:
|
|
88
86
|
"""Sets the error dependence state for this error calculator. An
|
|
89
87
|
independent error is calculated based on the input truth values as the
|
|
90
88
|
error basis. A dependent error is calculated based on the accumulated
|
|
@@ -143,9 +141,8 @@ class ErrSysDigitisation(IErrCalculator):
|
|
|
143
141
|
def __init__(self,
|
|
144
142
|
bits_per_unit: float,
|
|
145
143
|
method: ERoundMethod = ERoundMethod.ROUND,
|
|
146
|
-
err_dep:
|
|
147
|
-
"""
|
|
148
|
-
|
|
144
|
+
err_dep: EErrDep = EErrDep.DEPENDENT) -> None:
|
|
145
|
+
"""
|
|
149
146
|
Parameters
|
|
150
147
|
----------
|
|
151
148
|
bits_per_unit : float
|
|
@@ -160,7 +157,7 @@ class ErrSysDigitisation(IErrCalculator):
|
|
|
160
157
|
self._method = _select_round_method(method)
|
|
161
158
|
self._err_dep = err_dep
|
|
162
159
|
|
|
163
|
-
def get_error_dep(self) ->
|
|
160
|
+
def get_error_dep(self) -> EErrDep:
|
|
164
161
|
"""Gets the error dependence state for this error calculator. An
|
|
165
162
|
independent error is calculated based on the input truth values as the
|
|
166
163
|
error basis. A dependent error is calculated based on the accumulated
|
|
@@ -173,7 +170,7 @@ class ErrSysDigitisation(IErrCalculator):
|
|
|
173
170
|
"""
|
|
174
171
|
return self._err_dep
|
|
175
172
|
|
|
176
|
-
def set_error_dep(self, dependence:
|
|
173
|
+
def set_error_dep(self, dependence: EErrDep) -> None:
|
|
177
174
|
"""Sets the error dependence state for this error calculator. An
|
|
178
175
|
independent error is calculated based on the input truth values as the
|
|
179
176
|
error basis. A dependent error is calculated based on the accumulated
|
|
@@ -227,18 +224,17 @@ class ErrSysSaturation(IErrCalculator):
|
|
|
227
224
|
"""Systematic error calculator for saturation error base on user specified
|
|
228
225
|
minimum and maximum measurement values. Implements the `IErrCalculator`
|
|
229
226
|
interface.
|
|
227
|
+
|
|
228
|
+
NOTE: For this error to function as expected and clamp the measurement
|
|
229
|
+
within the specified range it must be placed last in the error chain and
|
|
230
|
+
the behaviour must be set to: EErrDependence.DEPENDENT.
|
|
230
231
|
"""
|
|
231
232
|
__slots__ = ("_min","_max","_err_dep")
|
|
232
233
|
|
|
233
234
|
def __init__(self,
|
|
234
235
|
meas_min: float,
|
|
235
236
|
meas_max: float) -> None:
|
|
236
|
-
"""
|
|
237
|
-
|
|
238
|
-
NOTE: For this error to function as expected and clamp the measurement
|
|
239
|
-
within the specified range it must be placed last in the error chain and
|
|
240
|
-
the behaviour must be set to: EErrDependence.DEPENDENT.
|
|
241
|
-
|
|
237
|
+
"""
|
|
242
238
|
Parameters
|
|
243
239
|
----------
|
|
244
240
|
meas_min : float
|
|
@@ -258,9 +254,9 @@ class ErrSysSaturation(IErrCalculator):
|
|
|
258
254
|
|
|
259
255
|
self._min = meas_min
|
|
260
256
|
self._max = meas_max
|
|
261
|
-
self._err_dep =
|
|
257
|
+
self._err_dep = EErrDep.DEPENDENT
|
|
262
258
|
|
|
263
|
-
def get_error_dep(self) ->
|
|
259
|
+
def get_error_dep(self) -> EErrDep:
|
|
264
260
|
"""Gets the error dependence state for this error calculator. An
|
|
265
261
|
independent error is calculated based on the input truth values as the
|
|
266
262
|
error basis. A dependent error is calculated based on the accumulated
|
|
@@ -273,7 +269,7 @@ class ErrSysSaturation(IErrCalculator):
|
|
|
273
269
|
"""
|
|
274
270
|
return self._err_dep
|
|
275
271
|
|
|
276
|
-
def set_error_dep(self, dependence:
|
|
272
|
+
def set_error_dep(self, dependence: EErrDep) -> None:
|
|
277
273
|
"""Sets the error dependence state for this error calculator. An
|
|
278
274
|
independent error is calculated based on the input truth values as the
|
|
279
275
|
error basis. A dependent error is calculated based on the accumulated
|
|
@@ -1,24 +1,30 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
"""
|
|
1
|
+
# ==============================================================================
|
|
2
|
+
# pyvale: the python validation engine
|
|
3
|
+
# License: MIT
|
|
4
|
+
# Copyright (C) 2025 The Computer Aided Validation Team
|
|
5
|
+
# ==============================================================================
|
|
6
|
+
|
|
8
7
|
import copy
|
|
9
8
|
from dataclasses import dataclass
|
|
10
9
|
import numpy as np
|
|
11
10
|
from scipy.spatial.transform import Rotation
|
|
12
11
|
|
|
13
|
-
from pyvale.
|
|
14
|
-
from pyvale.
|
|
15
|
-
from pyvale.
|
|
16
|
-
from pyvale.
|
|
17
|
-
from pyvale.
|
|
12
|
+
from pyvale.field import IField
|
|
13
|
+
from pyvale.fieldsampler import sample_field_with_sensor_data
|
|
14
|
+
from pyvale.sensordata import SensorData
|
|
15
|
+
from pyvale.integratortype import EIntSpatialType
|
|
16
|
+
from pyvale.errorcalculator import (IErrCalculator,
|
|
18
17
|
EErrType,
|
|
19
|
-
|
|
20
|
-
from pyvale.
|
|
21
|
-
from pyvale.
|
|
18
|
+
EErrDep)
|
|
19
|
+
from pyvale.errordriftcalc import IDriftCalculator
|
|
20
|
+
from pyvale.generatorsrandom import IGenRandom
|
|
21
|
+
|
|
22
|
+
# TODO:
|
|
23
|
+
# - Implement different perturbed sampling times for each sensor or allow all
|
|
24
|
+
# to lock to the same time step as it works now.
|
|
25
|
+
# - Need to check that we perform field rotations correctly for sensor angles.
|
|
26
|
+
# - This needs to be updated to take rotation objects for offsets and to build
|
|
27
|
+
# and compose rotations
|
|
22
28
|
|
|
23
29
|
|
|
24
30
|
@dataclass(slots=True)
|
|
@@ -46,25 +52,26 @@ class ErrFieldData:
|
|
|
46
52
|
num_time_steps,). If None then no time offset is applied.
|
|
47
53
|
"""
|
|
48
54
|
|
|
49
|
-
pos_rand_xyz: tuple[
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
"""Tuple of random generators (implementations of `
|
|
55
|
+
pos_rand_xyz: tuple[IGenRandom | None,
|
|
56
|
+
IGenRandom | None,
|
|
57
|
+
IGenRandom | None] = (None,None,None)
|
|
58
|
+
"""Tuple of random generators (implementations of `IGenRandom`
|
|
53
59
|
interface) for perturbing the sensor positions. The generators perturb the
|
|
54
60
|
X, Y and Z coordinates in order. If None then that axis is not randomly
|
|
55
|
-
perturbed from the nominal sensor position.
|
|
61
|
+
perturbed from the nominal sensor position. Note that the random generators
|
|
62
|
+
should return position perturbations consistent with the simulation units.
|
|
56
63
|
"""
|
|
57
64
|
|
|
58
|
-
ang_rand_zyx: tuple[
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
"""Tuple of random generators (implementations of `
|
|
65
|
+
ang_rand_zyx: tuple[IGenRandom | None,
|
|
66
|
+
IGenRandom | None,
|
|
67
|
+
IGenRandom | None] = (None,None,None)
|
|
68
|
+
"""Tuple of random generators (implementations of `IGenRandom`
|
|
62
69
|
interface) for perturbing the sensor angles. The generators perturb
|
|
63
70
|
rotations about the the Z, Y and X axis in order. If None then that axis is
|
|
64
71
|
not randomly perturbed from the nominal sensor position.
|
|
65
72
|
"""
|
|
66
73
|
|
|
67
|
-
time_rand:
|
|
74
|
+
time_rand: IGenRandom | None = None
|
|
68
75
|
"""Random generator for perturbing sensor array sampling times for the
|
|
69
76
|
purpose of calculating field based errors. If None then sensor sampling
|
|
70
77
|
times will not be perturbed from the nominal times.
|
|
@@ -82,11 +89,13 @@ class ErrFieldData:
|
|
|
82
89
|
specified above. shape=(3,)
|
|
83
90
|
"""
|
|
84
91
|
|
|
85
|
-
# DEV FEATURE: locks the coordinate even if offsets and random generators
|
|
92
|
+
# DEV FEATURE: locks the coordinate even if offsets and random generators
|
|
93
|
+
# are specified. These allow individual sensors to be locked when we only
|
|
94
|
+
# specify a random generator for each axis not each sensor.
|
|
86
95
|
pos_lock_xyz: np.ndarray | None = None
|
|
87
96
|
ang_lock_zyx: np.ndarray | None = None
|
|
88
97
|
|
|
89
|
-
#TODO: implement drift for other dimensions, pos/angle
|
|
98
|
+
# TODO: implement drift for other dimensions, pos/angle
|
|
90
99
|
time_drift: IDriftCalculator | None = None
|
|
91
100
|
"""Temporal drift calculation
|
|
92
101
|
"""
|
|
@@ -108,9 +117,8 @@ class ErrSysField(IErrCalculator):
|
|
|
108
117
|
def __init__(self,
|
|
109
118
|
field: IField,
|
|
110
119
|
field_err_data: ErrFieldData,
|
|
111
|
-
err_dep:
|
|
112
|
-
"""
|
|
113
|
-
|
|
120
|
+
err_dep: EErrDep = EErrDep.INDEPENDENT) -> None:
|
|
121
|
+
"""
|
|
114
122
|
Parameters
|
|
115
123
|
----------
|
|
116
124
|
field : IField
|
|
@@ -121,15 +129,15 @@ class ErrSysField(IErrCalculator):
|
|
|
121
129
|
Dataclass specifying which sensor array parameters will be perturbed
|
|
122
130
|
and how they will be perturbed. See the `ErrFieldData` class for
|
|
123
131
|
more detail
|
|
124
|
-
err_dep :
|
|
125
|
-
Error calculation dependence, by default
|
|
132
|
+
err_dep : EErrDep, optional
|
|
133
|
+
Error calculation dependence, by default EErrDep.DEPENDENT.
|
|
126
134
|
"""
|
|
127
135
|
self._field = field
|
|
128
136
|
self._field_err_data = field_err_data
|
|
129
137
|
self._err_dep = err_dep
|
|
130
138
|
self._sensor_data_perturbed = SensorData()
|
|
131
139
|
|
|
132
|
-
def get_error_dep(self) ->
|
|
140
|
+
def get_error_dep(self) -> EErrDep:
|
|
133
141
|
"""Gets the error dependence state for this error calculator. An
|
|
134
142
|
independent error is calculated based on the input truth values as the
|
|
135
143
|
error basis. A dependent error is calculated based on the accumulated
|
|
@@ -137,12 +145,12 @@ class ErrSysField(IErrCalculator):
|
|
|
137
145
|
|
|
138
146
|
Returns
|
|
139
147
|
-------
|
|
140
|
-
|
|
148
|
+
EErrDep
|
|
141
149
|
Enumeration defining INDEPENDENT or DEPENDENT behaviour.
|
|
142
150
|
"""
|
|
143
151
|
return self._err_dep
|
|
144
152
|
|
|
145
|
-
def set_error_dep(self, dependence:
|
|
153
|
+
def set_error_dep(self, dependence: EErrDep) -> None:
|
|
146
154
|
"""Sets the error dependence state for this error calculator. An
|
|
147
155
|
independent error is calculated based on the input truth values as the
|
|
148
156
|
error basis. A dependent error is calculated based on the accumulated
|
|
@@ -150,7 +158,7 @@ class ErrSysField(IErrCalculator):
|
|
|
150
158
|
|
|
151
159
|
Parameters
|
|
152
160
|
----------
|
|
153
|
-
dependence :
|
|
161
|
+
dependence : EErrDep
|
|
154
162
|
Enumeration defining INDEPENDENT or DEPENDENT behaviour.
|
|
155
163
|
"""
|
|
156
164
|
self._err_dep = dependence
|
|
@@ -232,9 +240,9 @@ class ErrSysField(IErrCalculator):
|
|
|
232
240
|
|
|
233
241
|
def _perturb_sensor_positions(sens_pos_nominal: np.ndarray,
|
|
234
242
|
pos_offset_xyz: np.ndarray | None,
|
|
235
|
-
pos_rand_xyz: tuple[
|
|
236
|
-
|
|
237
|
-
|
|
243
|
+
pos_rand_xyz: tuple[IGenRandom | None,
|
|
244
|
+
IGenRandom | None,
|
|
245
|
+
IGenRandom | None] | None,
|
|
238
246
|
pos_loc_xyz: np.ndarray | None,
|
|
239
247
|
) -> np.ndarray:
|
|
240
248
|
"""Helper function for perturbing the sensor positions from their nominal
|
|
@@ -250,9 +258,9 @@ def _perturb_sensor_positions(sens_pos_nominal: np.ndarray,
|
|
|
250
258
|
Offsets to apply to the sensor positions as an array with shape=
|
|
251
259
|
(num_sensors,3) wherethe columns represent the position in the X, Y and
|
|
252
260
|
Z axes. If None then no offset is applied.
|
|
253
|
-
pos_rand_xyz : tuple[
|
|
254
|
-
|
|
255
|
-
|
|
261
|
+
pos_rand_xyz : tuple[IGenRandom | None,
|
|
262
|
+
IGenRandom | None,
|
|
263
|
+
IGenRandom | None] | None
|
|
256
264
|
Random generators for sensor position perturbations along the the X, Y
|
|
257
265
|
and Z axes. If None then no perturbation is applied.
|
|
258
266
|
pos_loc_xyz : np.ndarray | None
|
|
@@ -285,7 +293,7 @@ def _perturb_sensor_positions(sens_pos_nominal: np.ndarray,
|
|
|
285
293
|
def _perturb_sample_times(sim_time: np.ndarray,
|
|
286
294
|
time_nominal: np.ndarray | None,
|
|
287
295
|
time_offset: np.ndarray | None,
|
|
288
|
-
time_rand:
|
|
296
|
+
time_rand: IGenRandom | None,
|
|
289
297
|
time_drift: IDriftCalculator | None
|
|
290
298
|
) -> np.ndarray | None:
|
|
291
299
|
"""Helper function for calculating perturbed sensor sampling times for the
|
|
@@ -301,7 +309,7 @@ def _perturb_sample_times(sim_time: np.ndarray,
|
|
|
301
309
|
time_offset : np.ndarray | None
|
|
302
310
|
Array of time offsets to apply to all sensors. If None then no offsets
|
|
303
311
|
are applied.
|
|
304
|
-
time_rand :
|
|
312
|
+
time_rand : IGenRandom | None
|
|
305
313
|
Random generator for perturbing the sampling times of all sensors. If
|
|
306
314
|
None then no random perturbation of sampling times occurs.
|
|
307
315
|
time_drift : IDriftCalculator | None
|
|
@@ -337,9 +345,9 @@ def _perturb_sample_times(sim_time: np.ndarray,
|
|
|
337
345
|
def _perturb_sensor_angles(n_sensors: int,
|
|
338
346
|
angles_nominal: tuple[Rotation,...] | None,
|
|
339
347
|
angle_offsets_zyx: np.ndarray | None,
|
|
340
|
-
rand_ang_zyx: tuple[
|
|
341
|
-
|
|
342
|
-
|
|
348
|
+
rand_ang_zyx: tuple[IGenRandom | None,
|
|
349
|
+
IGenRandom | None,
|
|
350
|
+
IGenRandom | None] | None,
|
|
343
351
|
angle_loc_zyx: np.ndarray | None,
|
|
344
352
|
) -> tuple[Rotation,...] | None:
|
|
345
353
|
"""Helper function for perturbing sensor angles for the purpose of
|
|
@@ -357,9 +365,9 @@ def _perturb_sensor_angles(n_sensors: int,
|
|
|
357
365
|
Angle offsets to apply to the sensor array as an array with shape=(
|
|
358
366
|
num_sensors,3) where the columns are the rotations about Z, Y and X in
|
|
359
367
|
degrees. If None then no offsets are applied.
|
|
360
|
-
rand_ang_zyx : tuple[
|
|
361
|
-
|
|
362
|
-
|
|
368
|
+
rand_ang_zyx : tuple[IGenRandom | None,
|
|
369
|
+
IGenRandom | None,
|
|
370
|
+
IGenRandom | None] | None
|
|
363
371
|
Random generators for perturbing sensor angles about the Z, Y and X axis
|
|
364
372
|
respectively. If None then no random perturbation to the sensor angle
|
|
365
373
|
occurs.
|
|
@@ -403,5 +411,4 @@ def _perturb_sensor_angles(n_sensors: int,
|
|
|
403
411
|
sensor_rot = Rotation.from_euler("zyx",sensor_rot_angs, degrees=True)
|
|
404
412
|
angles_perturbed[ii] = sensor_rot*rot_nom
|
|
405
413
|
|
|
406
|
-
return tuple(angles_perturbed)
|
|
407
|
-
|
|
414
|
+
return tuple(angles_perturbed)
|