pyvale 2025.4.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of pyvale might be problematic. Click here for more details.

Files changed (157) hide show
  1. pyvale/__init__.py +75 -0
  2. pyvale/core/__init__.py +7 -0
  3. pyvale/core/analyticmeshgen.py +59 -0
  4. pyvale/core/analyticsimdatafactory.py +63 -0
  5. pyvale/core/analyticsimdatagenerator.py +160 -0
  6. pyvale/core/camera.py +146 -0
  7. pyvale/core/cameradata.py +64 -0
  8. pyvale/core/cameradata2d.py +82 -0
  9. pyvale/core/cameratools.py +328 -0
  10. pyvale/core/cython/rastercyth.c +32267 -0
  11. pyvale/core/cython/rastercyth.py +636 -0
  12. pyvale/core/dataset.py +250 -0
  13. pyvale/core/errorcalculator.py +112 -0
  14. pyvale/core/errordriftcalc.py +146 -0
  15. pyvale/core/errorintegrator.py +339 -0
  16. pyvale/core/errorrand.py +614 -0
  17. pyvale/core/errorsysdep.py +331 -0
  18. pyvale/core/errorsysfield.py +407 -0
  19. pyvale/core/errorsysindep.py +905 -0
  20. pyvale/core/experimentsimulator.py +99 -0
  21. pyvale/core/field.py +136 -0
  22. pyvale/core/fieldconverter.py +154 -0
  23. pyvale/core/fieldsampler.py +112 -0
  24. pyvale/core/fieldscalar.py +167 -0
  25. pyvale/core/fieldtensor.py +221 -0
  26. pyvale/core/fieldtransform.py +384 -0
  27. pyvale/core/fieldvector.py +215 -0
  28. pyvale/core/generatorsrandom.py +528 -0
  29. pyvale/core/imagedef2d.py +566 -0
  30. pyvale/core/integratorfactory.py +241 -0
  31. pyvale/core/integratorquadrature.py +192 -0
  32. pyvale/core/integratorrectangle.py +88 -0
  33. pyvale/core/integratorspatial.py +90 -0
  34. pyvale/core/integratortype.py +44 -0
  35. pyvale/core/optimcheckfuncs.py +153 -0
  36. pyvale/core/raster.py +31 -0
  37. pyvale/core/rastercy.py +76 -0
  38. pyvale/core/rasternp.py +604 -0
  39. pyvale/core/rendermesh.py +156 -0
  40. pyvale/core/sensorarray.py +179 -0
  41. pyvale/core/sensorarrayfactory.py +210 -0
  42. pyvale/core/sensorarraypoint.py +280 -0
  43. pyvale/core/sensordata.py +72 -0
  44. pyvale/core/sensordescriptor.py +101 -0
  45. pyvale/core/sensortools.py +143 -0
  46. pyvale/core/visualexpplotter.py +151 -0
  47. pyvale/core/visualimagedef.py +71 -0
  48. pyvale/core/visualimages.py +75 -0
  49. pyvale/core/visualopts.py +180 -0
  50. pyvale/core/visualsimanimator.py +83 -0
  51. pyvale/core/visualsimplotter.py +182 -0
  52. pyvale/core/visualtools.py +81 -0
  53. pyvale/core/visualtraceplotter.py +256 -0
  54. pyvale/data/__init__.py +7 -0
  55. pyvale/data/case13_out.e +0 -0
  56. pyvale/data/case16_out.e +0 -0
  57. pyvale/data/case17_out.e +0 -0
  58. pyvale/data/case18_1_out.e +0 -0
  59. pyvale/data/case18_2_out.e +0 -0
  60. pyvale/data/case18_3_out.e +0 -0
  61. pyvale/data/case25_out.e +0 -0
  62. pyvale/data/case26_out.e +0 -0
  63. pyvale/data/optspeckle_2464x2056px_spec5px_8bit_gblur1px.tiff +0 -0
  64. pyvale/examples/__init__.py +7 -0
  65. pyvale/examples/analyticdatagen/__init__.py +7 -0
  66. pyvale/examples/analyticdatagen/ex1_1_scalarvisualisation.py +38 -0
  67. pyvale/examples/analyticdatagen/ex1_2_scalarcasebuild.py +46 -0
  68. pyvale/examples/analyticdatagen/ex2_1_analyticsensors.py +83 -0
  69. pyvale/examples/ex1_1_thermal2d.py +89 -0
  70. pyvale/examples/ex1_2_thermal2d.py +111 -0
  71. pyvale/examples/ex1_3_thermal2d.py +113 -0
  72. pyvale/examples/ex1_4_thermal2d.py +89 -0
  73. pyvale/examples/ex1_5_thermal2d.py +105 -0
  74. pyvale/examples/ex2_1_thermal3d .py +87 -0
  75. pyvale/examples/ex2_2_thermal3d.py +51 -0
  76. pyvale/examples/ex2_3_thermal3d.py +109 -0
  77. pyvale/examples/ex3_1_displacement2d.py +47 -0
  78. pyvale/examples/ex3_2_displacement2d.py +79 -0
  79. pyvale/examples/ex3_3_displacement2d.py +104 -0
  80. pyvale/examples/ex3_4_displacement2d.py +105 -0
  81. pyvale/examples/ex4_1_strain2d.py +57 -0
  82. pyvale/examples/ex4_2_strain2d.py +79 -0
  83. pyvale/examples/ex4_3_strain2d.py +100 -0
  84. pyvale/examples/ex5_1_multiphysics2d.py +78 -0
  85. pyvale/examples/ex6_1_multiphysics2d_expsim.py +118 -0
  86. pyvale/examples/ex6_2_multiphysics3d_expsim.py +158 -0
  87. pyvale/examples/features/__init__.py +7 -0
  88. pyvale/examples/features/ex_animation_tools_3dmonoblock.py +83 -0
  89. pyvale/examples/features/ex_area_avg.py +89 -0
  90. pyvale/examples/features/ex_calibration_error.py +108 -0
  91. pyvale/examples/features/ex_chain_field_errs.py +141 -0
  92. pyvale/examples/features/ex_field_errs.py +78 -0
  93. pyvale/examples/features/ex_sensor_single_angle_batch.py +110 -0
  94. pyvale/examples/imagedef2d/ex_imagedef2d_todisk.py +86 -0
  95. pyvale/examples/rasterisation/ex_rastenp.py +154 -0
  96. pyvale/examples/rasterisation/ex_rastercyth_oneframe.py +220 -0
  97. pyvale/examples/rasterisation/ex_rastercyth_static_cypara.py +194 -0
  98. pyvale/examples/rasterisation/ex_rastercyth_static_pypara.py +193 -0
  99. pyvale/simcases/case00_HEX20.i +242 -0
  100. pyvale/simcases/case00_HEX27.i +242 -0
  101. pyvale/simcases/case00_TET10.i +242 -0
  102. pyvale/simcases/case00_TET14.i +242 -0
  103. pyvale/simcases/case01.i +101 -0
  104. pyvale/simcases/case02.i +156 -0
  105. pyvale/simcases/case03.i +136 -0
  106. pyvale/simcases/case04.i +181 -0
  107. pyvale/simcases/case05.i +234 -0
  108. pyvale/simcases/case06.i +305 -0
  109. pyvale/simcases/case07.geo +135 -0
  110. pyvale/simcases/case07.i +87 -0
  111. pyvale/simcases/case08.geo +144 -0
  112. pyvale/simcases/case08.i +153 -0
  113. pyvale/simcases/case09.geo +204 -0
  114. pyvale/simcases/case09.i +87 -0
  115. pyvale/simcases/case10.geo +204 -0
  116. pyvale/simcases/case10.i +257 -0
  117. pyvale/simcases/case11.geo +337 -0
  118. pyvale/simcases/case11.i +147 -0
  119. pyvale/simcases/case12.geo +388 -0
  120. pyvale/simcases/case12.i +329 -0
  121. pyvale/simcases/case13.i +140 -0
  122. pyvale/simcases/case14.i +159 -0
  123. pyvale/simcases/case15.geo +337 -0
  124. pyvale/simcases/case15.i +150 -0
  125. pyvale/simcases/case16.geo +391 -0
  126. pyvale/simcases/case16.i +357 -0
  127. pyvale/simcases/case17.geo +135 -0
  128. pyvale/simcases/case17.i +144 -0
  129. pyvale/simcases/case18.i +254 -0
  130. pyvale/simcases/case18_1.i +254 -0
  131. pyvale/simcases/case18_2.i +254 -0
  132. pyvale/simcases/case18_3.i +254 -0
  133. pyvale/simcases/case19.geo +252 -0
  134. pyvale/simcases/case19.i +99 -0
  135. pyvale/simcases/case20.geo +252 -0
  136. pyvale/simcases/case20.i +250 -0
  137. pyvale/simcases/case21.geo +74 -0
  138. pyvale/simcases/case21.i +155 -0
  139. pyvale/simcases/case22.geo +82 -0
  140. pyvale/simcases/case22.i +140 -0
  141. pyvale/simcases/case23.geo +164 -0
  142. pyvale/simcases/case23.i +140 -0
  143. pyvale/simcases/case24.geo +79 -0
  144. pyvale/simcases/case24.i +123 -0
  145. pyvale/simcases/case25.geo +82 -0
  146. pyvale/simcases/case25.i +140 -0
  147. pyvale/simcases/case26.geo +166 -0
  148. pyvale/simcases/case26.i +140 -0
  149. pyvale/simcases/run_1case.py +61 -0
  150. pyvale/simcases/run_all_cases.py +69 -0
  151. pyvale/simcases/run_build_case.py +64 -0
  152. pyvale/simcases/run_example_cases.py +69 -0
  153. pyvale-2025.4.0.dist-info/METADATA +140 -0
  154. pyvale-2025.4.0.dist-info/RECORD +157 -0
  155. pyvale-2025.4.0.dist-info/WHEEL +5 -0
  156. pyvale-2025.4.0.dist-info/licenses/LICENSE +21 -0
  157. pyvale-2025.4.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,339 @@
1
+ """
2
+ ================================================================================
3
+ pyvale: the python validation engine
4
+ License: MIT
5
+ Copyright (C) 2025 The Computer Aided Validation Team
6
+ ================================================================================
7
+ """
8
+ import copy
9
+ from dataclasses import dataclass
10
+ import numpy as np
11
+ from pyvale.core.errorcalculator import (IErrCalculator,
12
+ EErrType,
13
+ EErrDependence)
14
+ from pyvale.core.sensordata import SensorData
15
+
16
+
17
+ @dataclass(slots=True)
18
+ class ErrIntOpts:
19
+ """Error integration options dataclass. Allows the user to control how
20
+ errors are calculated and stored in memory for later use.
21
+ """
22
+
23
+ force_dependence: bool = False
24
+ """Forces all errors to be calculated as dependent if True. Otherwise errors
25
+ will use individual dependence set in the errors initialiser. Independent
26
+ errors are calculated based on the ground truth whereas dependent errors are
27
+ calculated based on the accumulated sensor measurement at that stage in the
28
+ error chain.
29
+
30
+ Note that some errors are inherently independent so will not change. For
31
+ example: `ErrRandNormal` is purely independent whereas `ErrRandNormPercent`
32
+ can have the percentage error calculated based on the ground truth
33
+ (independent) or based on the accumulated sensor measurement (dependent).
34
+ """
35
+
36
+ store_all_errs: bool = False
37
+ """Stores all errors for individual error in the chain if True. Also stores
38
+ a list of SensorData objects showing perturbations to the sensor array
39
+ parameters caused by individual errors. Consumes significantly more memory
40
+ but is useful for finding which errors contribute most to the total
41
+ measurement error. For large sensor arrays (>100 sensors)
42
+ """
43
+
44
+
45
+ class ErrIntegrator:
46
+ """Class for managing sensor error integration. Takes a list of objects that
47
+ implement the `IErrCalculator` interface (i.e. the error chain) and loops
48
+ through them calculating each errors contribution to the total measurement
49
+ error and sums this over all errors in the chain. In addition to the total
50
+ error a sum of the random and systematic errors (see `EErrType`) is
51
+ calculated and stored.
52
+
53
+ This class also accumulates perturbations to the sensor array parameters due
54
+ to errors (i.e. sensor positioning error or temporal drift). The accumulated
55
+ sensor array parameters are stored as a `SensorData` object.
56
+
57
+ The errors are calculated in the order specified in the list. For dependent
58
+ errors (`EErrDependence.DEPENDENT`) the position of the error within the
59
+ error chain determines the accumulated sensor measurement that will be used
60
+ to calculate the error.
61
+
62
+ The user can control how the errors are calculated using the `ErrIntOpts`
63
+ dataclass.
64
+ """
65
+ __slots__ = ("_err_chain","_meas_shape","_errs_by_chain",
66
+ "_errs_systematic","_errs_random","_errs_total",
67
+ "_sens_data_by_chain","_err_int_opts","_sens_data_accumulated",
68
+ "_sens_data_initial")
69
+
70
+ def __init__(self,
71
+ err_chain: list[IErrCalculator],
72
+ sensor_data_initial: SensorData,
73
+ meas_shape: tuple[int,int,int],
74
+ err_int_opts: ErrIntOpts | None = None) -> None:
75
+ """Initialiser for the `ErrIntegrator` class.
76
+
77
+ Parameters
78
+ ----------
79
+ err_chain : list[IErrCalculator]
80
+ List of error objects implementing the IErrCalculator interface.
81
+ sensor_data_initial : SensorData
82
+ Object holding the initial sensor array parameters before they are
83
+ modified by the error chain.
84
+ meas_shape : tuple[int,int,int]
85
+ Shape of the sensor measurement array. shape=(num_sensors,
86
+ num_field_components,num_time_steps)
87
+ err_int_opts : ErrIntOpts | None, optional
88
+ Options for controlling how errors are calculated/summed and how
89
+ they are store in memory, by default None. If None then the default
90
+ options dataclass is used.
91
+ """
92
+
93
+ if err_int_opts is None:
94
+ self._err_int_opts = ErrIntOpts()
95
+ else:
96
+ self._err_int_opts = err_int_opts
97
+
98
+ self.set_error_chain(err_chain)
99
+ self._meas_shape = meas_shape
100
+
101
+ self._sens_data_initial = copy.deepcopy(sensor_data_initial)
102
+ self._sens_data_accumulated = copy.deepcopy(sensor_data_initial)
103
+
104
+ if self._err_int_opts.store_all_errs:
105
+ self._sens_data_by_chain = []
106
+ self._errs_by_chain = np.zeros((len(self._err_chain),)+ \
107
+ self._meas_shape)
108
+ else:
109
+ self._sens_data_by_chain = None
110
+ self._errs_by_chain = None
111
+
112
+ self._errs_systematic = np.zeros(meas_shape)
113
+ self._errs_random = np.zeros(meas_shape)
114
+ self._errs_total = np.zeros(meas_shape)
115
+
116
+
117
+ def set_error_chain(self, err_chain: list[IErrCalculator]) -> None:
118
+ """Sets the error chain that will be looped over to calculate the sensor
119
+ measurement errors. If the error integration options are forcing error
120
+ dependence then all errors in the chain will have their dependence set
121
+ to `EErrDependence.DEPENDENT`.
122
+
123
+ Parameters
124
+ ----------
125
+ err_chain : list[IErrCalculator]
126
+ List of error calculators implementing the IErrCalculator interface.
127
+ """
128
+ self._err_chain = err_chain
129
+
130
+ if self._err_int_opts.force_dependence:
131
+ for ee in self._err_chain:
132
+ ee.set_error_dep(EErrDependence.DEPENDENT)
133
+
134
+
135
+ def calc_errors_from_chain(self, truth: np.ndarray) -> np.ndarray:
136
+ """Calculates all errors by looping over the error chain. The total
137
+ measurement error is summed as each error is calculated in order. Note
138
+ that this causes all errors based on probability distributions to be
139
+ resampled and any required interpolations to be performed (e.g. from
140
+ randomly perturbing the sensor positions). Accumulated errors are also
141
+ stored for random and systematic errors separately (see `EErrType`).
142
+
143
+ If the `store_all_errs = True` in the `ErrIntOpts` dataclass then each
144
+ individual error is stored in a numpy array (see `get_errs_by_chain()`)
145
+ along with the accumulated errors in another numpy array.
146
+
147
+ Parameters
148
+ ----------
149
+ truth : np.ndarray
150
+ Array of ground truth sensor measurements interpolated from the
151
+ simulated physical field. shape=(num_sensors,num_field_components,
152
+ num_time_steps).
153
+
154
+ Returns
155
+ -------
156
+ np.ndarray
157
+ Array of total errors summed over all errors in the chain. shape=(
158
+ num_sensors,num_field_components,num_time_steps).
159
+ """
160
+ if self._err_int_opts.store_all_errs:
161
+ return self._calc_errors_store_by_chain(truth)
162
+
163
+ return self._calc_errors_mem_eff(truth)
164
+
165
+
166
+ def _calc_errors_store_by_chain(self, truth: np.ndarray) -> np.ndarray:
167
+ """Helper function for calculating all errors in the chain and summing
168
+ them. Returns the total error and stores sums of the random and
169
+ systematic errors in member variables. This function also stores each
170
+ individual error calculation in a separate numpy array for analysis.
171
+
172
+ Parameters
173
+ ----------
174
+ truth : np.ndarray
175
+ Array of ground truth sensor measurements interpolated from the
176
+ simulated physical field. shape=(num_sensors,num_field_components,
177
+ num_time_steps).
178
+
179
+ Returns
180
+ -------
181
+ np.ndarray
182
+ Array of total errors summed over all errors in the chain. shape=(
183
+ num_sensors,num_field_components,num_time_steps).
184
+ """
185
+ accumulated_error = np.zeros_like(truth)
186
+ self._errs_by_chain = np.zeros((len(self._err_chain),) + \
187
+ self._meas_shape)
188
+
189
+ for ii,ee in enumerate(self._err_chain):
190
+
191
+ if ee.get_error_dep() == EErrDependence.DEPENDENT:
192
+ (error_array,sens_data) = ee.calc_errs(truth+accumulated_error,
193
+ self._sens_data_accumulated)
194
+ self._sens_data_accumulated = sens_data
195
+ else:
196
+ (error_array,sens_data) = ee.calc_errs(truth,
197
+ self._sens_data_initial)
198
+
199
+ self._sens_data_by_chain.append(sens_data)
200
+
201
+ if ee.get_error_type() == EErrType.SYSTEMATIC:
202
+ self._errs_systematic = self._errs_systematic + error_array
203
+ else:
204
+ self._errs_random = self._errs_random + error_array
205
+
206
+ accumulated_error = accumulated_error + error_array
207
+ self._errs_by_chain[ii,:,:,:] = error_array
208
+
209
+ self._errs_total = accumulated_error
210
+ return self._errs_total
211
+
212
+
213
+ def _calc_errors_mem_eff(self, truth: np.ndarray) -> np.ndarray:
214
+ """Helper function for calculating all errors in the chain and summing
215
+ them. Returns the total error and stores sums of the random and
216
+ systematic errors in member variables. The individual error
217
+ contributions are not stored in this case for memory efficiency, only
218
+ the summed total, random and systematic error arrays are stored.
219
+
220
+ Parameters
221
+ ----------
222
+ truth : np.ndarray
223
+ Array of ground truth sensor measurements interpolated from the
224
+ simulated physical field. shape=(num_sensors,num_field_components,
225
+ num_time_steps).
226
+
227
+ Returns
228
+ -------
229
+ np.ndarray
230
+ Array of total errors summed over all errors in the chain. shape=(
231
+ num_sensors,num_field_components,num_time_steps).
232
+ """
233
+ accumulated_error = np.zeros_like(truth)
234
+
235
+ for ee in self._err_chain:
236
+
237
+ if ee.get_error_dep() == EErrDependence.DEPENDENT:
238
+ (error_array,sens_data) = ee.calc_errs(truth+accumulated_error,
239
+ self._sens_data_accumulated)
240
+ self._sens_data_accumulated = sens_data
241
+ else:
242
+ (error_array,sens_data) = ee.calc_errs(truth,
243
+ self._sens_data_initial)
244
+
245
+ self._sens_data_accumulated = sens_data
246
+
247
+ if ee.get_error_type() == EErrType.SYSTEMATIC:
248
+ self._errs_systematic = self._errs_systematic + error_array
249
+ else:
250
+ self._errs_random = self._errs_random + error_array
251
+
252
+ accumulated_error = accumulated_error + error_array
253
+
254
+ self._errs_total = accumulated_error
255
+ return self._errs_total
256
+
257
+
258
+ def get_errs_by_chain(self) -> np.ndarray | None:
259
+ """Gets the array of errors for each error in chain. If `store_all_errs`
260
+ is False in `ErrIntOpts` then this will return None.
261
+
262
+ Returns
263
+ -------
264
+ np.ndarray | None
265
+ Array of all errors in the chain. shape=(num_errs_in_chain,
266
+ num_sensors,num_field_components,num_time_steps). Returns None if
267
+ `ErrIntOpts.store_all_errs=False`.
268
+ """
269
+ return self._errs_by_chain
270
+
271
+ def get_sens_data_by_chain(self) -> list[SensorData] | None:
272
+ """Gets the list of sensor data objects storing how each error in the
273
+ chain has perturbed the underlying sensor parameters. If
274
+ `store_all_errs` is False in `ErrIntOpts` then this will return None.
275
+ If no sensor array parameters are modified by the error chain then all
276
+ SensorData objects in the list will be identical to the SensorData
277
+ object used to create the sensor array.
278
+
279
+ Returns
280
+ -------
281
+ list[SensorData] | None
282
+ List of perturbed sensors array parameters for each error in the
283
+ chain. Returns None if `ErrIntOpts.store_all_errs=False`.
284
+ """
285
+ return self._sens_data_by_chain
286
+
287
+ def get_sens_data_accumulated(self) -> SensorData:
288
+ """Gets the final accumulated sensor array parameters based on all
289
+ errors in the chain as a SensorData object. If no errors modify the
290
+ sensor array parameters then the SensorData object returns will be
291
+ identical to the SensorData object used to create the sensor array.
292
+
293
+ Returns
294
+ -------
295
+ SensorData
296
+ The final sensor array parameters based on accumulating all
297
+ perturbations from all errors in the error chain.
298
+ """
299
+ return self._sens_data_accumulated
300
+
301
+ def get_errs_systematic(self) -> np.ndarray:
302
+ """Gets the array of summed systematic errors over the error chain. If
303
+ the errors have not been calculated then an array of zeros is returned.
304
+
305
+ Returns
306
+ -------
307
+ np.ndarray
308
+ Array of total systematic errors. shape=(num_sensors,
309
+ num_field_components,num_time_steps)
310
+ """
311
+ return self._errs_systematic
312
+
313
+ def get_errs_random(self) -> np.ndarray:
314
+ """Gets the array of summed random errors over the error chain. If the
315
+ errors have not been calculated then an array of zeros is returned.
316
+
317
+ Returns
318
+ -------
319
+ np.ndarray
320
+ Array of total random errors. shape=(num_sensors,
321
+ num_field_components,num_time_steps)
322
+ """
323
+ return self._errs_random
324
+
325
+ def get_errs_total(self) -> np.ndarray:
326
+ """Gets the array of total errors. If the errors have not been
327
+ calculated then an array of zeros is returned. Note that this function
328
+ just returns the most recently calculated errors and will not resample
329
+ from probability distributions.
330
+
331
+ Returns
332
+ -------
333
+ np.ndarray
334
+ Array of total errors. shape=(num_sensors,num_field_components,
335
+ num_time_steps)
336
+ """
337
+ return self._errs_total
338
+
339
+