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.

Files changed (126) hide show
  1. pyvale/__init__.py +18 -3
  2. pyvale/analyticmeshgen.py +1 -0
  3. pyvale/analyticsimdatafactory.py +18 -13
  4. pyvale/analyticsimdatagenerator.py +105 -72
  5. pyvale/blendercalibrationdata.py +15 -0
  6. pyvale/blenderlightdata.py +26 -0
  7. pyvale/blendermaterialdata.py +15 -0
  8. pyvale/blenderrenderdata.py +30 -0
  9. pyvale/blenderscene.py +488 -0
  10. pyvale/blendertools.py +420 -0
  11. pyvale/camera.py +6 -5
  12. pyvale/cameradata.py +25 -7
  13. pyvale/cameradata2d.py +6 -4
  14. pyvale/camerastereo.py +217 -0
  15. pyvale/cameratools.py +206 -11
  16. pyvale/cython/rastercyth.py +6 -2
  17. pyvale/data/cal_target.tiff +0 -0
  18. pyvale/dataset.py +73 -14
  19. pyvale/errorcalculator.py +8 -10
  20. pyvale/errordriftcalc.py +10 -9
  21. pyvale/errorintegrator.py +19 -21
  22. pyvale/errorrand.py +33 -39
  23. pyvale/errorsyscalib.py +134 -0
  24. pyvale/errorsysdep.py +19 -22
  25. pyvale/errorsysfield.py +49 -41
  26. pyvale/errorsysindep.py +79 -175
  27. pyvale/examples/basics/ex1_1_basicscalars_therm2d.py +131 -0
  28. pyvale/examples/basics/ex1_2_sensormodel_therm2d.py +158 -0
  29. pyvale/examples/basics/ex1_3_customsens_therm3d.py +216 -0
  30. pyvale/examples/basics/ex1_4_basicerrors_therm3d.py +153 -0
  31. pyvale/examples/basics/ex1_5_fielderrs_therm3d.py +168 -0
  32. pyvale/examples/basics/ex1_6_caliberrs_therm2d.py +133 -0
  33. pyvale/examples/basics/ex1_7_spatavg_therm2d.py +123 -0
  34. pyvale/examples/basics/ex2_1_basicvectors_disp2d.py +112 -0
  35. pyvale/examples/basics/ex2_2_vectorsens_disp2d.py +111 -0
  36. pyvale/examples/basics/ex2_3_sensangle_disp2d.py +139 -0
  37. pyvale/examples/basics/ex2_4_chainfielderrs_disp2d.py +196 -0
  38. pyvale/examples/basics/ex2_5_vectorfields3d_disp3d.py +109 -0
  39. pyvale/examples/basics/ex3_1_basictensors_strain2d.py +114 -0
  40. pyvale/examples/basics/ex3_2_tensorsens2d_strain2d.py +111 -0
  41. pyvale/examples/basics/ex3_3_tensorsens3d_strain3d.py +182 -0
  42. pyvale/examples/basics/ex4_1_expsim2d_thermmech2d.py +171 -0
  43. pyvale/examples/basics/ex4_2_expsim3d_thermmech3d.py +252 -0
  44. pyvale/examples/{analyticdatagen → genanalyticdata}/ex1_1_scalarvisualisation.py +6 -9
  45. pyvale/examples/{analyticdatagen → genanalyticdata}/ex1_2_scalarcasebuild.py +8 -11
  46. pyvale/examples/{analyticdatagen → genanalyticdata}/ex2_1_analyticsensors.py +9 -12
  47. pyvale/examples/imagedef2d/ex_imagedef2d_todisk.py +8 -15
  48. pyvale/examples/renderblender/ex1_1_blenderscene.py +121 -0
  49. pyvale/examples/renderblender/ex1_2_blenderdeformed.py +119 -0
  50. pyvale/examples/renderblender/ex2_1_stereoscene.py +128 -0
  51. pyvale/examples/renderblender/ex2_2_stereodeformed.py +131 -0
  52. pyvale/examples/renderblender/ex3_1_blendercalibration.py +120 -0
  53. pyvale/examples/{rasterisation → renderrasterisation}/ex_rastenp.py +3 -2
  54. pyvale/examples/{rasterisation → renderrasterisation}/ex_rastercyth_oneframe.py +2 -2
  55. pyvale/examples/{rasterisation → renderrasterisation}/ex_rastercyth_static_cypara.py +3 -8
  56. pyvale/examples/{rasterisation → renderrasterisation}/ex_rastercyth_static_pypara.py +6 -7
  57. pyvale/examples/{ex1_4_thermal2d.py → visualisation/ex1_1_plot_traces.py} +32 -16
  58. pyvale/examples/{features/ex_animation_tools_3dmonoblock.py → visualisation/ex2_1_animate_sim.py} +37 -31
  59. pyvale/experimentsimulator.py +107 -30
  60. pyvale/field.py +2 -9
  61. pyvale/fieldconverter.py +98 -22
  62. pyvale/fieldsampler.py +2 -2
  63. pyvale/fieldscalar.py +10 -10
  64. pyvale/fieldtensor.py +15 -17
  65. pyvale/fieldtransform.py +7 -2
  66. pyvale/fieldvector.py +6 -7
  67. pyvale/generatorsrandom.py +25 -47
  68. pyvale/imagedef2d.py +6 -2
  69. pyvale/integratorfactory.py +2 -2
  70. pyvale/integratorquadrature.py +50 -24
  71. pyvale/integratorrectangle.py +85 -7
  72. pyvale/integratorspatial.py +4 -4
  73. pyvale/integratortype.py +3 -3
  74. pyvale/output.py +17 -0
  75. pyvale/pyvaleexceptions.py +11 -0
  76. pyvale/raster.py +6 -5
  77. pyvale/rastercy.py +6 -4
  78. pyvale/rasternp.py +6 -4
  79. pyvale/rendermesh.py +6 -2
  80. pyvale/sensorarray.py +2 -2
  81. pyvale/sensorarrayfactory.py +52 -65
  82. pyvale/sensorarraypoint.py +29 -30
  83. pyvale/sensordata.py +2 -2
  84. pyvale/sensordescriptor.py +138 -25
  85. pyvale/sensortools.py +3 -3
  86. pyvale/simtools.py +67 -0
  87. pyvale/visualexpplotter.py +99 -57
  88. pyvale/visualimagedef.py +11 -7
  89. pyvale/visualimages.py +6 -4
  90. pyvale/visualopts.py +372 -58
  91. pyvale/visualsimanimator.py +42 -13
  92. pyvale/visualsimsensors.py +318 -0
  93. pyvale/visualtools.py +69 -13
  94. pyvale/visualtraceplotter.py +52 -165
  95. {pyvale-2025.4.1.dist-info → pyvale-2025.5.1.dist-info}/METADATA +17 -14
  96. pyvale-2025.5.1.dist-info/RECORD +172 -0
  97. {pyvale-2025.4.1.dist-info → pyvale-2025.5.1.dist-info}/WHEEL +1 -1
  98. pyvale/examples/analyticdatagen/__init__.py +0 -5
  99. pyvale/examples/ex1_1_thermal2d.py +0 -86
  100. pyvale/examples/ex1_2_thermal2d.py +0 -108
  101. pyvale/examples/ex1_3_thermal2d.py +0 -110
  102. pyvale/examples/ex1_5_thermal2d.py +0 -102
  103. pyvale/examples/ex2_1_thermal3d .py +0 -84
  104. pyvale/examples/ex2_2_thermal3d.py +0 -51
  105. pyvale/examples/ex2_3_thermal3d.py +0 -106
  106. pyvale/examples/ex3_1_displacement2d.py +0 -44
  107. pyvale/examples/ex3_2_displacement2d.py +0 -76
  108. pyvale/examples/ex3_3_displacement2d.py +0 -101
  109. pyvale/examples/ex3_4_displacement2d.py +0 -102
  110. pyvale/examples/ex4_1_strain2d.py +0 -54
  111. pyvale/examples/ex4_2_strain2d.py +0 -76
  112. pyvale/examples/ex4_3_strain2d.py +0 -97
  113. pyvale/examples/ex5_1_multiphysics2d.py +0 -75
  114. pyvale/examples/ex6_1_multiphysics2d_expsim.py +0 -115
  115. pyvale/examples/ex6_2_multiphysics3d_expsim.py +0 -160
  116. pyvale/examples/features/__init__.py +0 -5
  117. pyvale/examples/features/ex_area_avg.py +0 -89
  118. pyvale/examples/features/ex_calibration_error.py +0 -108
  119. pyvale/examples/features/ex_chain_field_errs.py +0 -141
  120. pyvale/examples/features/ex_field_errs.py +0 -78
  121. pyvale/examples/features/ex_sensor_single_angle_batch.py +0 -110
  122. pyvale/optimcheckfuncs.py +0 -153
  123. pyvale/visualsimplotter.py +0 -182
  124. pyvale-2025.4.1.dist-info/RECORD +0 -163
  125. {pyvale-2025.4.1.dist-info → pyvale-2025.5.1.dist-info}/licenses/LICENSE +0 -0
  126. {pyvale-2025.4.1.dist-info → pyvale-2025.5.1.dist-info}/top_level.txt +0 -0
pyvale/errorcalculator.py CHANGED
@@ -1,8 +1,8 @@
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
7
  import enum
8
8
  from abc import ABC, abstractmethod
@@ -27,14 +27,14 @@ class EErrType(enum.Enum):
27
27
  RANDOM = enum.auto()
28
28
 
29
29
 
30
- class EErrDependence(enum.Enum):
30
+ class EErrDep(enum.Enum):
31
31
  """Enumeration defining error dependence.
32
32
 
33
- EErrDependence.INDEPENDENT:
33
+ EErrDep.INDEPENDENT:
34
34
  Errors are calculated based on the ground truth sensor values
35
35
  interpolated from the input simulation.
36
36
 
37
- EErrDependence.DEPENDENT:
37
+ EErrDep.DEPENDENT:
38
38
  Errors are calculated based on the accumulated sensor reading due
39
39
  to all preceeding errors in the chain.
40
40
  """
@@ -46,6 +46,7 @@ class IErrCalculator(ABC):
46
46
  """Interface (abstract base class) for sensor error calculation allowing for
47
47
  chaining of errors.
48
48
  """
49
+
49
50
  @abstractmethod
50
51
  def get_error_type(self) -> EErrType:
51
52
  """Abstract method for getting the error type.
@@ -55,10 +56,9 @@ class IErrCalculator(ABC):
55
56
  EErrType
56
57
  Enumeration definining RANDOM or SYSTEMATIC error types.
57
58
  """
58
- pass
59
59
 
60
60
  @abstractmethod
61
- def get_error_dep(self) -> EErrDependence:
61
+ def get_error_dep(self) -> EErrDep:
62
62
  """Abstract method for getting the error dependence.
63
63
 
64
64
  Returns
@@ -66,10 +66,9 @@ class IErrCalculator(ABC):
66
66
  EErrDependence
67
67
  Enumeration definining RANDOM or SYSTEMATIC error types.
68
68
  """
69
- pass
70
69
 
71
70
  @abstractmethod
72
- def set_error_dep(self, dependence: EErrDependence) -> None:
71
+ def set_error_dep(self, dependence: EErrDep) -> None:
73
72
  """Abstract method for setting the error dependence.
74
73
 
75
74
  Parameters
@@ -104,7 +103,6 @@ class IErrCalculator(ABC):
104
103
  the error chain. Note that many errors do not modify the sensor data
105
104
  so the sensor data class is passed through this function unchanged.
106
105
  """
107
- pass
108
106
 
109
107
 
110
108
 
pyvale/errordriftcalc.py CHANGED
@@ -1,8 +1,8 @@
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
7
  from abc import ABC, abstractmethod
8
8
  import numpy as np
@@ -32,7 +32,6 @@ class IDriftCalculator(ABC):
32
32
  Array of drift errors having the same shape as the input drift_var
33
33
  array.
34
34
  """
35
- pass
36
35
 
37
36
 
38
37
  class DriftConstant(IDriftCalculator):
@@ -40,9 +39,10 @@ class DriftConstant(IDriftCalculator):
40
39
 
41
40
  Implements the IDriftCalculator interface.
42
41
  """
43
- def __init__(self, offset: float) -> None:
44
- """Initialiser for the `DriftConstant` class.
42
+ __slots__ = ("_offset",)
45
43
 
44
+ def __init__(self, offset: float) -> None:
45
+ """
46
46
  Parameters
47
47
  ----------
48
48
  offset : float
@@ -73,10 +73,10 @@ class DriftLinear(IDriftCalculator):
73
73
 
74
74
  Implements the IDriftCalculator interface.
75
75
  """
76
+ __slots__ = ("_slope","_offset")
76
77
 
77
78
  def __init__(self, slope: float, offset: float = 0.0) -> None:
78
- """Initialiser for the `DriftLinear` class.
79
-
79
+ """
80
80
  Parameters
81
81
  ----------
82
82
  slope : float
@@ -113,9 +113,10 @@ class DriftPolynomial(IDriftCalculator):
113
113
  Implements the IDriftCalculator interface.
114
114
  """
115
115
 
116
- def __init__(self, coeffs: np.ndarray) -> None:
117
- """Initialiser for the `DriftPolynomial` class.
116
+ __slots__ = ("_coeffs",)
118
117
 
118
+ def __init__(self, coeffs: np.ndarray) -> None:
119
+ """
119
120
  Parameters
120
121
  ----------
121
122
  coeffs : np.ndarray
pyvale/errorintegrator.py CHANGED
@@ -1,15 +1,15 @@
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
7
  import copy
8
8
  from dataclasses import dataclass
9
9
  import numpy as np
10
10
  from pyvale.errorcalculator import (IErrCalculator,
11
- EErrType,
12
- EErrDependence)
11
+ EErrType,
12
+ EErrDep)
13
13
  from pyvale.sensordata import SensorData
14
14
 
15
15
 
@@ -19,12 +19,9 @@ class ErrIntOpts:
19
19
  errors are calculated and stored in memory for later use.
20
20
  """
21
21
 
22
- force_dependence: bool = False
23
- """Forces all errors to be calculated as dependent if True. Otherwise errors
24
- will use individual dependence set in the errors initialiser. Independent
25
- errors are calculated based on the ground truth whereas dependent errors are
26
- calculated based on the accumulated sensor measurement at that stage in the
27
- error chain.
22
+ force_dependence: EErrDep | None = None
23
+ """Forces all errors to be calculated with the specified dependence. If set
24
+ to None then all errors will use their default/preset dependence.
28
25
 
29
26
  Note that some errors are inherently independent so will not change. For
30
27
  example: `ErrRandNormal` is purely independent whereas `ErrRandNormPercent`
@@ -71,8 +68,7 @@ class ErrIntegrator:
71
68
  sensor_data_initial: SensorData,
72
69
  meas_shape: tuple[int,int,int],
73
70
  err_int_opts: ErrIntOpts | None = None) -> None:
74
- """Initialiser for the `ErrIntegrator` class.
75
-
71
+ """
76
72
  Parameters
77
73
  ----------
78
74
  err_chain : list[IErrCalculator]
@@ -117,7 +113,7 @@ class ErrIntegrator:
117
113
  """Sets the error chain that will be looped over to calculate the sensor
118
114
  measurement errors. If the error integration options are forcing error
119
115
  dependence then all errors in the chain will have their dependence set
120
- to `EErrDependence.DEPENDENT`.
116
+ to the specified value.
121
117
 
122
118
  Parameters
123
119
  ----------
@@ -126,9 +122,9 @@ class ErrIntegrator:
126
122
  """
127
123
  self._err_chain = err_chain
128
124
 
129
- if self._err_int_opts.force_dependence:
125
+ if self._err_int_opts.force_dependence is not None:
130
126
  for ee in self._err_chain:
131
- ee.set_error_dep(EErrDependence.DEPENDENT)
127
+ ee.set_error_dep(self._err_int_opts.force_dependence)
132
128
 
133
129
 
134
130
  def calc_errors_from_chain(self, truth: np.ndarray) -> np.ndarray:
@@ -187,14 +183,15 @@ class ErrIntegrator:
187
183
 
188
184
  for ii,ee in enumerate(self._err_chain):
189
185
 
190
- if ee.get_error_dep() == EErrDependence.DEPENDENT:
186
+ if ee.get_error_dep() == EErrDep.DEPENDENT:
191
187
  (error_array,sens_data) = ee.calc_errs(truth+accumulated_error,
192
188
  self._sens_data_accumulated)
193
- self._sens_data_accumulated = sens_data
189
+
194
190
  else:
195
191
  (error_array,sens_data) = ee.calc_errs(truth,
196
192
  self._sens_data_initial)
197
193
 
194
+ self._sens_data_accumulated = sens_data
198
195
  self._sens_data_by_chain.append(sens_data)
199
196
 
200
197
  if ee.get_error_type() == EErrType.SYSTEMATIC:
@@ -233,10 +230,11 @@ class ErrIntegrator:
233
230
 
234
231
  for ee in self._err_chain:
235
232
 
236
- if ee.get_error_dep() == EErrDependence.DEPENDENT:
237
- (error_array,sens_data) = ee.calc_errs(truth+accumulated_error,
238
- self._sens_data_accumulated)
239
- self._sens_data_accumulated = sens_data
233
+ if ee.get_error_dep() == EErrDep.DEPENDENT:
234
+ (error_array,sens_data) = ee.calc_errs(
235
+ truth+accumulated_error,
236
+ self._sens_data_accumulated
237
+ )
240
238
  else:
241
239
  (error_array,sens_data) = ee.calc_errs(truth,
242
240
  self._sens_data_initial)
pyvale/errorrand.py CHANGED
@@ -1,18 +1,18 @@
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
7
  import numpy as np
8
8
  from pyvale.sensordata import SensorData
9
9
  from pyvale.errorcalculator import (IErrCalculator,
10
10
  EErrType,
11
- EErrDependence)
12
- from pyvale.generatorsrandom import IGeneratorRandom
11
+ EErrDep)
12
+ from pyvale.generatorsrandom import IGenRandom
13
13
 
14
14
 
15
- class ErrRandUniform(IErrCalculator):
15
+ class ErrRandUnif(IErrCalculator):
16
16
  """Random error calculator based on uniform sampling of an interval
17
17
  specified by its upper and lower bound.
18
18
 
@@ -23,10 +23,9 @@ class ErrRandUniform(IErrCalculator):
23
23
  def __init__(self,
24
24
  low: float,
25
25
  high: float,
26
- err_dep: EErrDependence = EErrDependence.INDEPENDENT,
26
+ err_dep: EErrDep = EErrDep.INDEPENDENT,
27
27
  seed: int | None = None) -> None:
28
- """Initialiser for `ErrRandUniform` class.
29
-
28
+ """
30
29
  Parameters
31
30
  ----------
32
31
  low : float
@@ -54,7 +53,7 @@ class ErrRandUniform(IErrCalculator):
54
53
  self.rng = np.random.default_rng(seed)
55
54
  self.err_dep = err_dep
56
55
 
57
- def get_error_dep(self) -> EErrDependence:
56
+ def get_error_dep(self) -> EErrDep:
58
57
  """Gets the error dependence state for this error calculator. An
59
58
  independent error is calculated based on the input truth values as the
60
59
  error basis. A dependent error is calculated based on the accumulated
@@ -69,7 +68,7 @@ class ErrRandUniform(IErrCalculator):
69
68
  """
70
69
  return self.err_dep
71
70
 
72
- def set_error_dep(self, dependence: EErrDependence) -> None:
71
+ def set_error_dep(self, dependence: EErrDep) -> None:
73
72
  """Sets the error dependence state for this error calculator. An
74
73
  independent error is calculated based on the input truth values as the
75
74
  error basis. A dependent error is calculated based on the accumulated
@@ -138,10 +137,9 @@ class ErrRandUnifPercent(IErrCalculator):
138
137
  def __init__(self,
139
138
  low_percent: float,
140
139
  high_percent: float,
141
- err_dep: EErrDependence = EErrDependence.INDEPENDENT,
140
+ err_dep: EErrDep = EErrDep.INDEPENDENT,
142
141
  seed: int | None = None) -> None:
143
- """Initialiser for the `ErrRandUnifPercent` class.
144
-
142
+ """
145
143
  Parameters
146
144
  ----------
147
145
  low_percent : float
@@ -170,7 +168,7 @@ class ErrRandUnifPercent(IErrCalculator):
170
168
  self.rng = np.random.default_rng(seed)
171
169
  self.err_dep = err_dep
172
170
 
173
- def get_error_dep(self) -> EErrDependence:
171
+ def get_error_dep(self) -> EErrDep:
174
172
  """Gets the error dependence state for this error calculator. An
175
173
  independent error is calculated based on the input truth values as the
176
174
  error basis. A dependent error is calculated based on the accumulated
@@ -183,7 +181,7 @@ class ErrRandUnifPercent(IErrCalculator):
183
181
  """
184
182
  return self.err_dep
185
183
 
186
- def set_error_dep(self, dependence: EErrDependence) -> None:
184
+ def set_error_dep(self, dependence: EErrDep) -> None:
187
185
  """Sets the error dependence state for this error calculator. An
188
186
  independent error is calculated based on the input truth values as the
189
187
  error basis. A dependent error is calculated based on the accumulated
@@ -234,7 +232,7 @@ class ErrRandUnifPercent(IErrCalculator):
234
232
  return (err_basis*norm_rand,sens_data)
235
233
 
236
234
 
237
- class ErrRandNormal(IErrCalculator):
235
+ class ErrRandNorm(IErrCalculator):
238
236
  """Random error calculator based on sampling of a normal (Gaussian)
239
237
  distribution specified using the standard deviation with an assumed zero
240
238
  mean. A non-zero mean is a systematic error and should be specified using
@@ -246,10 +244,9 @@ class ErrRandNormal(IErrCalculator):
246
244
 
247
245
  def __init__(self,
248
246
  std: float,
249
- err_dep: EErrDependence = EErrDependence.INDEPENDENT,
247
+ err_dep: EErrDep = EErrDep.INDEPENDENT,
250
248
  seed: int | None = None) -> None:
251
- """Initialiser for `ErrRandNormal` class.
252
-
249
+ """
253
250
  Parameters
254
251
  ----------
255
252
  std : float
@@ -264,7 +261,7 @@ class ErrRandNormal(IErrCalculator):
264
261
  self.rng = np.random.default_rng(seed)
265
262
  self.err_dep = err_dep
266
263
 
267
- def get_error_dep(self) -> EErrDependence:
264
+ def get_error_dep(self) -> EErrDep:
268
265
  """Gets the error dependence state for this error calculator. An
269
266
  independent error is calculated based on the input truth values as the
270
267
  error basis. A dependent error is calculated based on the accumulated
@@ -279,7 +276,7 @@ class ErrRandNormal(IErrCalculator):
279
276
  """
280
277
  return self.err_dep
281
278
 
282
- def set_error_dep(self, dependence: EErrDependence) -> None:
279
+ def set_error_dep(self, dependence: EErrDep) -> None:
283
280
  """Sets the error dependence state for this error calculator. An
284
281
  independent error is calculated based on the input truth values as the
285
282
  error basis. A dependent error is calculated based on the accumulated
@@ -349,10 +346,9 @@ class ErrRandNormPercent(IErrCalculator):
349
346
 
350
347
  def __init__(self,
351
348
  std_percent: float,
352
- err_dep: EErrDependence = EErrDependence.INDEPENDENT,
349
+ err_dep: EErrDep = EErrDep.INDEPENDENT,
353
350
  seed: int | None = None) -> None:
354
- """Initialiser for `ErrRandNormPercent` class.
355
-
351
+ """
356
352
  Parameters
357
353
  ----------
358
354
  std_percent : float
@@ -368,7 +364,7 @@ class ErrRandNormPercent(IErrCalculator):
368
364
  self._rng = np.random.default_rng(seed)
369
365
  self._err_dep = err_dep
370
366
 
371
- def get_error_dep(self) -> EErrDependence:
367
+ def get_error_dep(self) -> EErrDep:
372
368
  """Gets the error dependence state for this error calculator. An
373
369
  independent error is calculated based on the input truth values as the
374
370
  error basis. A dependent error is calculated based on the accumulated
@@ -381,7 +377,7 @@ class ErrRandNormPercent(IErrCalculator):
381
377
  """
382
378
  return self._err_dep
383
379
 
384
- def set_error_dep(self, dependence: EErrDependence) -> None:
380
+ def set_error_dep(self, dependence: EErrDep) -> None:
385
381
  """Sets the error dependence state for this error calculator. An
386
382
  independent error is calculated based on the input truth values as the
387
383
  error basis. A dependent error is calculated based on the accumulated
@@ -441,10 +437,9 @@ class ErrRandGenerator(IErrCalculator):
441
437
  __slots__ = ("_generator","_err_dep")
442
438
 
443
439
  def __init__(self,
444
- generator: IGeneratorRandom,
445
- err_dep: EErrDependence = EErrDependence.INDEPENDENT) -> None:
446
- """Initiliaser for the `ErrRandGenerator` class.
447
-
440
+ generator: IGenRandom,
441
+ err_dep: EErrDep = EErrDep.INDEPENDENT) -> None:
442
+ """
448
443
  Parameters
449
444
  ----------
450
445
  generator : IGeneratorRandom
@@ -455,7 +450,7 @@ class ErrRandGenerator(IErrCalculator):
455
450
  self._generator = generator
456
451
  self._err_dep = err_dep
457
452
 
458
- def get_error_dep(self) -> EErrDependence:
453
+ def get_error_dep(self) -> EErrDep:
459
454
  """Gets the error dependence state for this error calculator. An
460
455
  independent error is calculated based on the input truth values as the
461
456
  error basis. A dependent error is calculated based on the accumulated
@@ -470,7 +465,7 @@ class ErrRandGenerator(IErrCalculator):
470
465
  """
471
466
  return self._err_dep
472
467
 
473
- def set_error_dep(self, dependence: EErrDependence) -> None:
468
+ def set_error_dep(self, dependence: EErrDep) -> None:
474
469
  """Sets the error dependence state for this error calculator. An
475
470
  independent error is calculated based on the input truth values as the
476
471
  error basis. A dependent error is calculated based on the accumulated
@@ -536,10 +531,9 @@ class ErrRandGenPercent(IErrCalculator):
536
531
  __slots__ = ("_generator","_err_dep")
537
532
 
538
533
  def __init__(self,
539
- generator: IGeneratorRandom,
540
- err_dep: EErrDependence = EErrDependence.INDEPENDENT) -> None:
541
- """Initiliaser for the `ErrRandGenerator` class.
542
-
534
+ generator: IGenRandom,
535
+ err_dep: EErrDep = EErrDep.INDEPENDENT) -> None:
536
+ """
543
537
  Parameters
544
538
  ----------
545
539
  generator : IGeneratorRandom
@@ -550,7 +544,7 @@ class ErrRandGenPercent(IErrCalculator):
550
544
  self._generator = generator
551
545
  self._err_dep = err_dep
552
546
 
553
- def get_error_dep(self) -> EErrDependence:
547
+ def get_error_dep(self) -> EErrDep:
554
548
  """Gets the error dependence state for this error calculator. An
555
549
  independent error is calculated based on the input truth values as the
556
550
  error basis. A dependent error is calculated based on the accumulated
@@ -563,7 +557,7 @@ class ErrRandGenPercent(IErrCalculator):
563
557
  """
564
558
  return self._err_dep
565
559
 
566
- def set_error_dep(self, dependence: EErrDependence) -> None:
560
+ def set_error_dep(self, dependence: EErrDep) -> None:
567
561
  """Sets the error dependence state for this error calculator. An
568
562
  independent error is calculated based on the input truth values as the
569
563
  error basis. A dependent error is calculated based on the accumulated
@@ -608,6 +602,6 @@ class ErrRandGenPercent(IErrCalculator):
608
602
  error array has the same shape as the input error basis.
609
603
  """
610
604
  rand_errs = err_basis \
611
- * self._generator.generate(shape=err_basis.shape)/100
605
+ * self._generator.generate(shape=err_basis.shape)/100.0
612
606
 
613
607
  return (rand_errs,sens_data)
@@ -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
+