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