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.

Files changed (153) hide show
  1. pyvale/__init__.py +78 -64
  2. pyvale/analyticmeshgen.py +102 -0
  3. pyvale/{core/analyticsimdatafactory.py → analyticsimdatafactory.py} +44 -16
  4. pyvale/analyticsimdatagenerator.py +323 -0
  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/{core/camera.py → camera.py} +15 -15
  12. pyvale/{core/cameradata.py → cameradata.py} +27 -22
  13. pyvale/{core/cameradata2d.py → cameradata2d.py} +8 -6
  14. pyvale/camerastereo.py +217 -0
  15. pyvale/{core/cameratools.py → cameratools.py} +220 -26
  16. pyvale/{core/cython → cython}/rastercyth.py +11 -7
  17. pyvale/data/__init__.py +5 -7
  18. pyvale/data/cal_target.tiff +0 -0
  19. pyvale/data/case00_HEX20_out.e +0 -0
  20. pyvale/data/case00_HEX27_out.e +0 -0
  21. pyvale/data/case00_HEX8_out.e +0 -0
  22. pyvale/data/case00_TET10_out.e +0 -0
  23. pyvale/data/case00_TET14_out.e +0 -0
  24. pyvale/data/case00_TET4_out.e +0 -0
  25. pyvale/{core/dataset.py → dataset.py} +91 -16
  26. pyvale/{core/errorcalculator.py → errorcalculator.py} +13 -16
  27. pyvale/{core/errordriftcalc.py → errordriftcalc.py} +14 -14
  28. pyvale/{core/errorintegrator.py → errorintegrator.py} +25 -28
  29. pyvale/{core/errorrand.py → errorrand.py} +39 -46
  30. pyvale/errorsyscalib.py +134 -0
  31. pyvale/{core/errorsysdep.py → errorsysdep.py} +25 -29
  32. pyvale/{core/errorsysfield.py → errorsysfield.py} +59 -52
  33. pyvale/{core/errorsysindep.py → errorsysindep.py} +85 -182
  34. pyvale/examples/__init__.py +5 -7
  35. pyvale/examples/basics/ex1_1_basicscalars_therm2d.py +131 -0
  36. pyvale/examples/basics/ex1_2_sensormodel_therm2d.py +158 -0
  37. pyvale/examples/basics/ex1_3_customsens_therm3d.py +216 -0
  38. pyvale/examples/basics/ex1_4_basicerrors_therm3d.py +153 -0
  39. pyvale/examples/basics/ex1_5_fielderrs_therm3d.py +168 -0
  40. pyvale/examples/basics/ex1_6_caliberrs_therm2d.py +133 -0
  41. pyvale/examples/basics/ex1_7_spatavg_therm2d.py +123 -0
  42. pyvale/examples/basics/ex2_1_basicvectors_disp2d.py +112 -0
  43. pyvale/examples/basics/ex2_2_vectorsens_disp2d.py +111 -0
  44. pyvale/examples/basics/ex2_3_sensangle_disp2d.py +139 -0
  45. pyvale/examples/basics/ex2_4_chainfielderrs_disp2d.py +196 -0
  46. pyvale/examples/basics/ex2_5_vectorfields3d_disp3d.py +109 -0
  47. pyvale/examples/basics/ex3_1_basictensors_strain2d.py +114 -0
  48. pyvale/examples/basics/ex3_2_tensorsens2d_strain2d.py +111 -0
  49. pyvale/examples/basics/ex3_3_tensorsens3d_strain3d.py +182 -0
  50. pyvale/examples/basics/ex4_1_expsim2d_thermmech2d.py +171 -0
  51. pyvale/examples/basics/ex4_2_expsim3d_thermmech3d.py +252 -0
  52. pyvale/examples/{analyticdatagen → genanalyticdata}/ex1_1_scalarvisualisation.py +6 -9
  53. pyvale/examples/{analyticdatagen → genanalyticdata}/ex1_2_scalarcasebuild.py +8 -11
  54. pyvale/examples/{analyticdatagen → genanalyticdata}/ex2_1_analyticsensors.py +9 -12
  55. pyvale/examples/imagedef2d/ex_imagedef2d_todisk.py +8 -15
  56. pyvale/examples/renderblender/ex1_1_blenderscene.py +121 -0
  57. pyvale/examples/renderblender/ex1_2_blenderdeformed.py +119 -0
  58. pyvale/examples/renderblender/ex2_1_stereoscene.py +128 -0
  59. pyvale/examples/renderblender/ex2_2_stereodeformed.py +131 -0
  60. pyvale/examples/renderblender/ex3_1_blendercalibration.py +120 -0
  61. pyvale/examples/{rasterisation → renderrasterisation}/ex_rastenp.py +6 -7
  62. pyvale/examples/{rasterisation → renderrasterisation}/ex_rastercyth_oneframe.py +5 -7
  63. pyvale/examples/{rasterisation → renderrasterisation}/ex_rastercyth_static_cypara.py +6 -13
  64. pyvale/examples/{rasterisation → renderrasterisation}/ex_rastercyth_static_pypara.py +9 -12
  65. pyvale/examples/{ex1_4_thermal2d.py → visualisation/ex1_1_plot_traces.py} +33 -20
  66. pyvale/examples/{features/ex_animation_tools_3dmonoblock.py → visualisation/ex2_1_animate_sim.py} +37 -31
  67. pyvale/experimentsimulator.py +175 -0
  68. pyvale/{core/field.py → field.py} +6 -14
  69. pyvale/fieldconverter.py +351 -0
  70. pyvale/{core/fieldsampler.py → fieldsampler.py} +9 -10
  71. pyvale/{core/fieldscalar.py → fieldscalar.py} +17 -18
  72. pyvale/{core/fieldtensor.py → fieldtensor.py} +23 -26
  73. pyvale/{core/fieldtransform.py → fieldtransform.py} +9 -5
  74. pyvale/{core/fieldvector.py → fieldvector.py} +14 -16
  75. pyvale/{core/generatorsrandom.py → generatorsrandom.py} +29 -52
  76. pyvale/{core/imagedef2d.py → imagedef2d.py} +11 -8
  77. pyvale/{core/integratorfactory.py → integratorfactory.py} +12 -13
  78. pyvale/{core/integratorquadrature.py → integratorquadrature.py} +57 -32
  79. pyvale/integratorrectangle.py +165 -0
  80. pyvale/{core/integratorspatial.py → integratorspatial.py} +9 -10
  81. pyvale/{core/integratortype.py → integratortype.py} +7 -8
  82. pyvale/output.py +17 -0
  83. pyvale/pyvaleexceptions.py +11 -0
  84. pyvale/{core/raster.py → raster.py} +8 -8
  85. pyvale/{core/rastercy.py → rastercy.py} +11 -10
  86. pyvale/{core/rasternp.py → rasternp.py} +12 -13
  87. pyvale/{core/rendermesh.py → rendermesh.py} +10 -19
  88. pyvale/{core/sensorarray.py → sensorarray.py} +7 -8
  89. pyvale/{core/sensorarrayfactory.py → sensorarrayfactory.py} +64 -78
  90. pyvale/{core/sensorarraypoint.py → sensorarraypoint.py} +39 -41
  91. pyvale/{core/sensordata.py → sensordata.py} +7 -8
  92. pyvale/sensordescriptor.py +213 -0
  93. pyvale/{core/sensortools.py → sensortools.py} +8 -9
  94. pyvale/simcases/case00_HEX20.i +5 -5
  95. pyvale/simcases/case00_HEX27.i +5 -5
  96. pyvale/simcases/case00_HEX8.i +242 -0
  97. pyvale/simcases/case00_TET10.i +2 -2
  98. pyvale/simcases/case00_TET14.i +2 -2
  99. pyvale/simcases/case00_TET4.i +242 -0
  100. pyvale/simcases/run_1case.py +1 -1
  101. pyvale/simtools.py +67 -0
  102. pyvale/visualexpplotter.py +191 -0
  103. pyvale/{core/visualimagedef.py → visualimagedef.py} +13 -10
  104. pyvale/{core/visualimages.py → visualimages.py} +10 -9
  105. pyvale/visualopts.py +493 -0
  106. pyvale/{core/visualsimanimator.py → visualsimanimator.py} +47 -19
  107. pyvale/visualsimsensors.py +318 -0
  108. pyvale/visualtools.py +136 -0
  109. pyvale/visualtraceplotter.py +142 -0
  110. {pyvale-2025.4.0.dist-info → pyvale-2025.5.1.dist-info}/METADATA +17 -14
  111. pyvale-2025.5.1.dist-info/RECORD +172 -0
  112. {pyvale-2025.4.0.dist-info → pyvale-2025.5.1.dist-info}/WHEEL +1 -1
  113. pyvale/core/__init__.py +0 -7
  114. pyvale/core/analyticmeshgen.py +0 -59
  115. pyvale/core/analyticsimdatagenerator.py +0 -160
  116. pyvale/core/cython/rastercyth.c +0 -32267
  117. pyvale/core/experimentsimulator.py +0 -99
  118. pyvale/core/fieldconverter.py +0 -154
  119. pyvale/core/integratorrectangle.py +0 -88
  120. pyvale/core/optimcheckfuncs.py +0 -153
  121. pyvale/core/sensordescriptor.py +0 -101
  122. pyvale/core/visualexpplotter.py +0 -151
  123. pyvale/core/visualopts.py +0 -180
  124. pyvale/core/visualsimplotter.py +0 -182
  125. pyvale/core/visualtools.py +0 -81
  126. pyvale/core/visualtraceplotter.py +0 -256
  127. pyvale/examples/analyticdatagen/__init__.py +0 -7
  128. pyvale/examples/ex1_1_thermal2d.py +0 -89
  129. pyvale/examples/ex1_2_thermal2d.py +0 -111
  130. pyvale/examples/ex1_3_thermal2d.py +0 -113
  131. pyvale/examples/ex1_5_thermal2d.py +0 -105
  132. pyvale/examples/ex2_1_thermal3d .py +0 -87
  133. pyvale/examples/ex2_2_thermal3d.py +0 -51
  134. pyvale/examples/ex2_3_thermal3d.py +0 -109
  135. pyvale/examples/ex3_1_displacement2d.py +0 -47
  136. pyvale/examples/ex3_2_displacement2d.py +0 -79
  137. pyvale/examples/ex3_3_displacement2d.py +0 -104
  138. pyvale/examples/ex3_4_displacement2d.py +0 -105
  139. pyvale/examples/ex4_1_strain2d.py +0 -57
  140. pyvale/examples/ex4_2_strain2d.py +0 -79
  141. pyvale/examples/ex4_3_strain2d.py +0 -100
  142. pyvale/examples/ex5_1_multiphysics2d.py +0 -78
  143. pyvale/examples/ex6_1_multiphysics2d_expsim.py +0 -118
  144. pyvale/examples/ex6_2_multiphysics3d_expsim.py +0 -158
  145. pyvale/examples/features/__init__.py +0 -7
  146. pyvale/examples/features/ex_area_avg.py +0 -89
  147. pyvale/examples/features/ex_calibration_error.py +0 -108
  148. pyvale/examples/features/ex_chain_field_errs.py +0 -141
  149. pyvale/examples/features/ex_field_errs.py +0 -78
  150. pyvale/examples/features/ex_sensor_single_angle_batch.py +0 -110
  151. pyvale-2025.4.0.dist-info/RECORD +0 -157
  152. {pyvale-2025.4.0.dist-info → pyvale-2025.5.1.dist-info}/licenses/LICENSE +0 -0
  153. {pyvale-2025.4.0.dist-info → pyvale-2025.5.1.dist-info}/top_level.txt +0 -0
@@ -1,17 +1,15 @@
1
- """
2
- ================================================================================
3
- pyvale: the python validation engine
4
- License: MIT
5
- Copyright (C) 2025 The Computer Aided Validation Team
6
- ================================================================================
7
- """
8
- from typing import Callable
1
+ # ==============================================================================
2
+ # pyvale: the python validation engine
3
+ # License: MIT
4
+ # Copyright (C) 2025 The Computer Aided Validation Team
5
+ # ==============================================================================
6
+
9
7
  import numpy as np
10
- from pyvale.core.errorcalculator import (IErrCalculator,
8
+ from pyvale.errorcalculator import (IErrCalculator,
11
9
  EErrType,
12
- EErrDependence)
13
- from pyvale.core.generatorsrandom import IGeneratorRandom
14
- from pyvale.core.sensordata import SensorData
10
+ EErrDep)
11
+ from pyvale.generatorsrandom import IGenRandom
12
+ from pyvale.sensordata import SensorData
15
13
 
16
14
 
17
15
  class ErrSysOffset(IErrCalculator):
@@ -22,21 +20,20 @@ class ErrSysOffset(IErrCalculator):
22
20
 
23
21
  def __init__(self,
24
22
  offset: float,
25
- err_dep: EErrDependence = EErrDependence.INDEPENDENT) -> None:
26
- """Initialiser for the `ErrSysOffset` class.
27
-
23
+ err_dep: EErrDep = EErrDep.INDEPENDENT) -> None:
24
+ """
28
25
  Parameters
29
26
  ----------
30
27
  offset : float
31
28
  Constant offset to apply to all simulated measurements from the
32
29
  sensor array.
33
30
  err_dep : EErrDependence, optional
34
- Error , by default EErrDependence.INDEPENDENT
31
+ Error calculation dependence, by default EErrDependence.INDEPENDENT.
35
32
  """
36
33
  self._offset = offset
37
34
  self._err_dep = err_dep
38
35
 
39
- def get_error_dep(self) -> EErrDependence:
36
+ def get_error_dep(self) -> EErrDep:
40
37
  """Gets the error dependence state for this error calculator. An
41
38
  independent error is calculated based on the input truth values as the
42
39
  error basis. A dependent error is calculated based on the accumulated
@@ -52,7 +49,7 @@ class ErrSysOffset(IErrCalculator):
52
49
  """
53
50
  return self._err_dep
54
51
 
55
- def set_error_dep(self, dependence: EErrDependence) -> None:
52
+ def set_error_dep(self, dependence: EErrDep) -> None:
56
53
  """Sets the error dependence state for this error calculator. An
57
54
  independent error is calculated based on the input truth values as the
58
55
  error basis. A dependent error is calculated based on the accumulated
@@ -111,9 +108,8 @@ class ErrSysOffsetPercent(IErrCalculator):
111
108
 
112
109
  def __init__(self,
113
110
  offset_percent: float,
114
- err_dep: EErrDependence = EErrDependence.INDEPENDENT) -> None:
115
- """Initialiser for the `ErrSysOffsetPercent` class.
116
-
111
+ err_dep: EErrDep = EErrDep.INDEPENDENT) -> None:
112
+ """
117
113
  Parameters
118
114
  ----------
119
115
  offset_percent : float
@@ -125,7 +121,7 @@ class ErrSysOffsetPercent(IErrCalculator):
125
121
  self._offset_percent = offset_percent
126
122
  self._err_dep = err_dep
127
123
 
128
- def get_error_dep(self) -> EErrDependence:
124
+ def get_error_dep(self) -> EErrDep:
129
125
  """Gets the error dependence state for this error calculator. An
130
126
  independent error is calculated based on the input truth values as the
131
127
  error basis. A dependent error is calculated based on the accumulated
@@ -138,7 +134,7 @@ class ErrSysOffsetPercent(IErrCalculator):
138
134
  """
139
135
  return self._err_dep
140
136
 
141
- def set_error_dep(self, dependence: EErrDependence) -> None:
137
+ def set_error_dep(self, dependence: EErrDep) -> None:
142
138
  """Sets the error dependence state for this error calculator. An
143
139
  independent error is calculated based on the input truth values as the
144
140
  error basis. A dependent error is calculated based on the accumulated
@@ -188,7 +184,7 @@ class ErrSysOffsetPercent(IErrCalculator):
188
184
  sens_data)
189
185
 
190
186
 
191
- class ErrSysUniform(IErrCalculator):
187
+ class ErrSysUnif(IErrCalculator):
192
188
  """Systematic error calculator for applying an offset to each sensor that is
193
189
  sampled from a uniform probability distribution specified by its upper and
194
190
  lower bounds. Implements the `IErrCalculator` interface.
@@ -198,10 +194,9 @@ class ErrSysUniform(IErrCalculator):
198
194
  def __init__(self,
199
195
  low: float,
200
196
  high: float,
201
- err_dep: EErrDependence = EErrDependence.INDEPENDENT,
197
+ err_dep: EErrDep = EErrDep.INDEPENDENT,
202
198
  seed: int | None = None) -> None:
203
- """Initialiser for the `ErrSysUniform` class.
204
-
199
+ """
205
200
  Parameters
206
201
  ----------
207
202
  low : float
@@ -221,7 +216,7 @@ class ErrSysUniform(IErrCalculator):
221
216
  self._rng = np.random.default_rng(seed)
222
217
  self._err_dep = err_dep
223
218
 
224
- def get_error_dep(self) -> EErrDependence:
219
+ def get_error_dep(self) -> EErrDep:
225
220
  """Gets the error dependence state for this error calculator. An
226
221
  independent error is calculated based on the input truth values as the
227
222
  error basis. A dependent error is calculated based on the accumulated
@@ -237,7 +232,7 @@ class ErrSysUniform(IErrCalculator):
237
232
  """
238
233
  return self._err_dep
239
234
 
240
- def set_error_dep(self, dependence: EErrDependence) -> None:
235
+ def set_error_dep(self, dependence: EErrDep) -> None:
241
236
  """Sets the error dependence state for this error calculator. An
242
237
  independent error is calculated based on the input truth values as the
243
238
  error basis. A dependent error is calculated based on the accumulated
@@ -297,7 +292,7 @@ class ErrSysUniform(IErrCalculator):
297
292
  return (sys_errs,sens_data)
298
293
 
299
294
 
300
- class ErrSysUniformPercent(IErrCalculator):
295
+ class ErrSysUnifPercent(IErrCalculator):
301
296
  """Systematic error calculator for applying a percentage offset to each
302
297
  sensor that is sampled from a uniform probability distribution specified by
303
298
  its upper and lower bounds.
@@ -313,10 +308,9 @@ class ErrSysUniformPercent(IErrCalculator):
313
308
  def __init__(self,
314
309
  low_percent: float,
315
310
  high_percent: float,
316
- err_dep: EErrDependence = EErrDependence.INDEPENDENT,
311
+ err_dep: EErrDep = EErrDep.INDEPENDENT,
317
312
  seed: int | None = None) -> None:
318
- """_summary_
319
-
313
+ """
320
314
  Parameters
321
315
  ----------
322
316
  low_percent : float
@@ -334,7 +328,7 @@ class ErrSysUniformPercent(IErrCalculator):
334
328
  self._rng = np.random.default_rng(seed)
335
329
  self._err_dep = err_dep
336
330
 
337
- def get_error_dep(self) -> EErrDependence:
331
+ def get_error_dep(self) -> EErrDep:
338
332
  """Gets the error dependence state for this error calculator. An
339
333
  independent error is calculated based on the input truth values as the
340
334
  error basis. A dependent error is calculated based on the accumulated
@@ -347,7 +341,7 @@ class ErrSysUniformPercent(IErrCalculator):
347
341
  """
348
342
  return self._err_dep
349
343
 
350
- def set_error_dep(self, dependence: EErrDependence) -> None:
344
+ def set_error_dep(self, dependence: EErrDep) -> None:
351
345
  """Sets the error dependence state for this error calculator. An
352
346
  independent error is calculated based on the input truth values as the
353
347
  error basis. A dependent error is calculated based on the accumulated
@@ -404,7 +398,7 @@ class ErrSysUniformPercent(IErrCalculator):
404
398
  return (err_basis*sys_errs,sens_data)
405
399
 
406
400
 
407
- class ErrSysNormal(IErrCalculator):
401
+ class ErrSysNorm(IErrCalculator):
408
402
  """Systematic error calculator for applying an offset to each individual
409
403
  sensor in the array based on sampling from a normal distribution specified
410
404
  by its standard deviation and mean. Note that the offset is constant for
@@ -415,10 +409,9 @@ class ErrSysNormal(IErrCalculator):
415
409
  def __init__(self,
416
410
  std: float,
417
411
  mean: float = 0.0,
418
- err_dep: EErrDependence = EErrDependence.INDEPENDENT,
412
+ err_dep: EErrDep = EErrDep.INDEPENDENT,
419
413
  seed: int | None = None) -> None:
420
- """Initialiser for the `ErrSysNormal` class.
421
-
414
+ """
422
415
  Parameters
423
416
  ----------
424
417
  std : float
@@ -436,7 +429,7 @@ class ErrSysNormal(IErrCalculator):
436
429
  self._rng = np.random.default_rng(seed)
437
430
  self._err_dep = err_dep
438
431
 
439
- def get_error_dep(self) -> EErrDependence:
432
+ def get_error_dep(self) -> EErrDep:
440
433
  """Gets the error dependence state for this error calculator. An
441
434
  independent error is calculated based on the input truth values as the
442
435
  error basis. A dependent error is calculated based on the accumulated
@@ -452,7 +445,7 @@ class ErrSysNormal(IErrCalculator):
452
445
  """
453
446
  return self._err_dep
454
447
 
455
- def set_error_dep(self, dependence: EErrDependence) -> None:
448
+ def set_error_dep(self, dependence: EErrDep) -> None:
456
449
  """Sets the error dependence state for this error calculator. An
457
450
  independent error is calculated based on the input truth values as the
458
451
  error basis. A dependent error is calculated based on the accumulated
@@ -528,13 +521,24 @@ class ErrSysNormPercent(IErrCalculator):
528
521
 
529
522
  def __init__(self,
530
523
  std_percent: float,
531
- err_dep: EErrDependence = EErrDependence.INDEPENDENT,
524
+ err_dep: EErrDep = EErrDep.INDEPENDENT,
532
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
+ """
533
537
  self._std = std_percent/100
534
538
  self._rng = np.random.default_rng(seed)
535
539
  self._err_dep = err_dep
536
540
 
537
- def get_error_dep(self) -> EErrDependence:
541
+ def get_error_dep(self) -> EErrDep:
538
542
  """Gets the error dependence state for this error calculator. An
539
543
  independent error is calculated based on the input truth values as the
540
544
  error basis. A dependent error is calculated based on the accumulated
@@ -547,7 +551,7 @@ class ErrSysNormPercent(IErrCalculator):
547
551
  """
548
552
  return self._err_dep
549
553
 
550
- def set_error_dep(self, dependence: EErrDependence) -> None:
554
+ def set_error_dep(self, dependence: EErrDep) -> None:
551
555
  """Sets the error dependence state for this error calculator. An
552
556
  independent error is calculated based on the input truth values as the
553
557
  error basis. A dependent error is calculated based on the accumulated
@@ -574,12 +578,6 @@ class ErrSysNormPercent(IErrCalculator):
574
578
  err_basis: np.ndarray,
575
579
  sens_data: SensorData,
576
580
  ) -> tuple[np.ndarray, SensorData]:
577
-
578
- err_shape = np.array(err_basis.shape)
579
- err_shape[-1] = 1
580
- sys_errs = self._rng.normal(loc=0.0,
581
- scale=self._std,
582
- size=err_shape)
583
581
  """Calculates the error array based on the size of the input.
584
582
 
585
583
  Parameters
@@ -597,6 +595,13 @@ class ErrSysNormPercent(IErrCalculator):
597
595
  sensor data object as it is not modified by this class. The returned
598
596
  error array has the same shape as the input error basis.
599
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
+
600
605
  tile_shape = np.array(err_basis.shape)
601
606
  tile_shape[0:-1] = 1
602
607
  sys_errs = np.tile(sys_errs,tuple(tile_shape))
@@ -604,7 +609,7 @@ class ErrSysNormPercent(IErrCalculator):
604
609
  return (err_basis*sys_errs,sens_data)
605
610
 
606
611
 
607
- class ErrSysGenerator(IErrCalculator):
612
+ class ErrSysGen(IErrCalculator):
608
613
  """Systematic error calculator for applying a unique offset to each sensor
609
614
  by sample from a user specified probability distribution (an implementation
610
615
  of the `IGeneratorRandom` interface).
@@ -614,13 +619,21 @@ class ErrSysGenerator(IErrCalculator):
614
619
  __slots__ = ("_generator","_err_dep")
615
620
 
616
621
  def __init__(self,
617
- generator: IGeneratorRandom,
618
- err_dep: EErrDependence = EErrDependence.INDEPENDENT) -> None:
619
-
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
+ """
620
633
  self._generator = generator
621
634
  self._err_dep = err_dep
622
635
 
623
- def get_error_dep(self) -> EErrDependence:
636
+ def get_error_dep(self) -> EErrDep:
624
637
  """Gets the error dependence state for this error calculator. An
625
638
  independent error is calculated based on the input truth values as the
626
639
  error basis. A dependent error is calculated based on the accumulated
@@ -633,7 +646,7 @@ class ErrSysGenerator(IErrCalculator):
633
646
  """
634
647
  return self._err_dep
635
648
 
636
- def set_error_dep(self, dependence: EErrDependence) -> None:
649
+ def set_error_dep(self, dependence: EErrDep) -> None:
637
650
  """Sets the error dependence state for this error calculator. An
638
651
  independent error is calculated based on the input truth values as the
639
652
  error basis. A dependent error is calculated based on the accumulated
@@ -705,13 +718,21 @@ class ErrSysGenPercent(IErrCalculator):
705
718
  __slots__ = ("_generator","_err_dep")
706
719
 
707
720
  def __init__(self,
708
- generator: IGeneratorRandom,
709
- err_dep: EErrDependence = EErrDependence.INDEPENDENT) -> None:
710
-
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
+ """
711
732
  self._generator = generator
712
733
  self._err_dep = err_dep
713
734
 
714
- def get_error_dep(self) -> EErrDependence:
735
+ def get_error_dep(self) -> EErrDep:
715
736
  """Gets the error dependence state for this error calculator. An
716
737
  independent error is calculated based on the input truth values as the
717
738
  error basis. A dependent error is calculated based on the accumulated
@@ -724,7 +745,7 @@ class ErrSysGenPercent(IErrCalculator):
724
745
  """
725
746
  return self._err_dep
726
747
 
727
- def set_error_dep(self, dependence: EErrDependence) -> None:
748
+ def set_error_dep(self, dependence: EErrDep) -> None:
728
749
  """Sets the error dependence state for this error calculator. An
729
750
  independent error is calculated based on the input truth values as the
730
751
  error basis. A dependent error is calculated based on the accumulated
@@ -771,7 +792,9 @@ class ErrSysGenPercent(IErrCalculator):
771
792
  err_shape = np.array(err_basis.shape)
772
793
  err_shape[-1] = 1
773
794
 
774
- sys_errs = self._generator.generate(size=err_shape)
795
+ sys_errs = self._generator.generate(shape=err_shape)
796
+ # Convert percent to decimal
797
+ sys_errs = sys_errs/100.0
775
798
 
776
799
  tile_shape = np.array(err_basis.shape)
777
800
  tile_shape[0:-1] = 1
@@ -781,125 +804,5 @@ class ErrSysGenPercent(IErrCalculator):
781
804
  return (sys_errs,sens_data)
782
805
 
783
806
 
784
- class ErrSysCalibration(IErrCalculator):
785
- """Systematic error calculator for calibration errors. The user specifies an
786
- assumed calibration and a ground truth calibration function. The ground
787
- truth calibration function is inverted and linearly interpolated numerically
788
- based on the number of divisions specified by the user.
789
-
790
- Implements the `IErrCalculator` interface.
791
- """
792
- __slots__ = ("_assumed_cali","_truth_calib","_cal_range","_n_cal_divs",
793
- "_err_dep","_truth_calc_table")
794
-
795
- def __init__(self,
796
- assumed_calib: Callable[[np.ndarray],np.ndarray],
797
- truth_calib: Callable[[np.ndarray],np.ndarray],
798
- cal_range: tuple[float,float],
799
- n_cal_divs: int = 10000,
800
- err_dep: EErrDependence = EErrDependence.INDEPENDENT) -> None:
801
- """_summary_
802
-
803
- Parameters
804
- ----------
805
- assumed_calib : Callable[[np.ndarray],np.ndarray]
806
- Assumed calibration function taking the input unitless 'signal' and
807
- converting it to the same units as the physical field being sampled
808
- by the sensor array.
809
- truth_calib : Callable[[np.ndarray],np.ndarray]
810
- Assumed calibration function taking the input unitless 'signal' and
811
- converting it to the same units as the physical field being sampled
812
- by the sensor array.
813
- cal_range : tuple[float,float]
814
- Range over which the calibration functions are valid. This is
815
- normally based on a voltage range such as (0,10) volts.
816
- n_cal_divs : int, optional
817
- Number of divisions to discretise the the truth calibration function
818
- for numerical inversion, by default 10000.
819
- err_dep : EErrDependence, optional
820
- Error calculation dependence, by default EErrDependence.INDEPENDENT.
821
- """
822
- self._assumed_calib = assumed_calib
823
- self._truth_calib = truth_calib
824
- self._cal_range = cal_range
825
- self._n_cal_divs = n_cal_divs
826
- self._err_dep = err_dep
827
-
828
- self._truth_cal_table = np.zeros((n_cal_divs,2))
829
- self._truth_cal_table[:,0] = np.linspace(cal_range[0],
830
- cal_range[1],
831
- n_cal_divs)
832
- self._truth_cal_table[:,1] = self._truth_calib(
833
- self._truth_cal_table[:,0])
834
-
835
- def get_error_dep(self) -> EErrDependence:
836
- """Gets the error dependence state for this error calculator. An
837
- independent error is calculated based on the input truth values as the
838
- error basis. A dependent error is calculated based on the accumulated
839
- sensor reading from all preceeding errors in the chain.
840
-
841
- Returns
842
- -------
843
- EErrDependence
844
- Enumeration defining INDEPENDENT or DEPENDENT behaviour.
845
- """
846
- return self._err_dep
847
-
848
- def set_error_dep(self, dependence: EErrDependence) -> None:
849
- """Sets the error dependence state for this error calculator. An
850
- independent error is calculated based on the input truth values as the
851
- error basis. A dependent error is calculated based on the accumulated
852
- sensor reading from all preceeding errors in the chain.
853
-
854
- Parameters
855
- ----------
856
- dependence : EErrDependence
857
- Enumeration defining INDEPENDENT or DEPENDENT behaviour.
858
- """
859
- self._err_dep = dependence
860
-
861
- def get_error_type(self) -> EErrType:
862
- """Gets the error type.
863
-
864
- Returns
865
- -------
866
- EErrType
867
- Enumeration definining RANDOM or SYSTEMATIC error types.
868
- """
869
- return EErrType.SYSTEMATIC
870
-
871
- def calc_errs(self,
872
- err_basis: np.ndarray,
873
- sens_data: SensorData,
874
- ) -> tuple[np.ndarray, SensorData]:
875
- """Calculates the error array based on the size of the input.
876
-
877
- Parameters
878
- ----------
879
- err_basis : np.ndarray
880
- Array of values with the same dimensions as the sensor measurement
881
- matrix.
882
- sens_data : SensorData
883
- The accumulated sensor state data for all errors prior to this one.
884
-
885
- Returns
886
- -------
887
- tuple[np.ndarray, SensorData]
888
- Tuple containing the calculated error array and pass through of the
889
- sensor data object as it is not modified by this class. The returned
890
- error array has the same shape as the input error basis.
891
- """
892
- # shape=(n_sens,n_comps,n_time_steps)
893
- signal_from_field = np.interp(err_basis,
894
- self._truth_cal_table[:,1],
895
- self._truth_cal_table[:,0])
896
- # shape=(n_sens,n_comps,n_time_steps)
897
- field_from_assumed_calib = self._assumed_calib(signal_from_field)
898
-
899
- sys_errs = field_from_assumed_calib - err_basis
900
-
901
- return (sys_errs,sens_data)
902
-
903
-
904
807
 
905
808
 
@@ -1,7 +1,5 @@
1
- """
2
- ================================================================================
3
- pyvale: the python validation engine
4
- License: MIT
5
- Copyright (C) 2025 The Computer Aided Validation Team
6
- ================================================================================
7
- """
1
+ #===============================================================================
2
+ # pyvale: the python validation engine
3
+ # License: MIT
4
+ # Copyright (C) 2025 The Computer Aided Validation Team
5
+ #===============================================================================
@@ -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()