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,15 +1,14 @@
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
+ # ==============================================================================
6
+
8
7
  from abc import ABC, abstractmethod
9
8
  import numpy as np
10
9
 
11
10
 
12
- class IGeneratorRandom(ABC):
11
+ class IGenRandom(ABC):
13
12
  """Interface (abstract base class) for wrapping numpy random number
14
13
  generation to allow probability distribution parameters to be specified in
15
14
  the initialiser whereas the generation of random numbers has a common
@@ -35,7 +34,7 @@ class IGeneratorRandom(ABC):
35
34
  pass
36
35
 
37
36
 
38
- class GeneratorNormal(IGeneratorRandom):
37
+ class GenNormal(IGenRandom):
39
38
  """Class wrapping the numpy normal random number generator. Implements the
40
39
  IGeneratorRandom interface to allow for interchangeability with other random
41
40
  number generators.
@@ -46,9 +45,7 @@ class GeneratorNormal(IGeneratorRandom):
46
45
  std: float = 1.0,
47
46
  mean: float = 0.0,
48
47
  seed: int | None = None) -> None:
49
- """Initialiser taking the parameters of the probability distribution and
50
- an optional seed for the random generator to allow for reproducibility.
51
-
48
+ """
52
49
  Parameters
53
50
  ----------
54
51
  std : float, optional
@@ -83,7 +80,7 @@ class GeneratorNormal(IGeneratorRandom):
83
80
  size = shape)
84
81
 
85
82
 
86
- class GeneratorLogNormal(IGeneratorRandom):
83
+ class GenLogNormal(IGenRandom):
87
84
  """Class wrapping the numpy lognormal random generator. Implements the
88
85
  IGeneratorRandom interface to allow for interchangeability with other random
89
86
  number generators.
@@ -94,9 +91,7 @@ class GeneratorLogNormal(IGeneratorRandom):
94
91
  std: float = 1.0,
95
92
  mean: float = 0.0,
96
93
  seed: int | None = None) -> None:
97
- """Initialiser taking the parameters of the probability distribution and
98
- an optional seed for the random generator to allow for reproducibility.
99
-
94
+ """
100
95
  Parameters
101
96
  ----------
102
97
  std : float, optional
@@ -130,7 +125,7 @@ class GeneratorLogNormal(IGeneratorRandom):
130
125
  size = shape)
131
126
 
132
127
 
133
- class GeneratorUniform(IGeneratorRandom):
128
+ class GenUniform(IGenRandom):
134
129
  """Class wrapping the numpy uniform random number generator. Implements the
135
130
  IGeneratorRandom interface to allow for interchangeability with other random
136
131
  number generators.
@@ -141,9 +136,7 @@ class GeneratorUniform(IGeneratorRandom):
141
136
  low: float = -1.0,
142
137
  high: float = 1.0,
143
138
  seed: int | None = None) -> None:
144
- """Initialiser taking the parameters of the probability distribution and
145
- an optional seed for the random generator to allow for reproducibility.
146
-
139
+ """
147
140
  Parameters
148
141
  ----------
149
142
  low : float, optional
@@ -177,7 +170,7 @@ class GeneratorUniform(IGeneratorRandom):
177
170
  size = shape)
178
171
 
179
172
 
180
- class GeneratorExponential(IGeneratorRandom):
173
+ class GenExponential(IGenRandom):
181
174
  """Class wrapping the numpy exponential random generator. Implements the
182
175
  IGeneratorRandom interface to allow for interchangeability with other random
183
176
  number generators.
@@ -187,9 +180,7 @@ class GeneratorExponential(IGeneratorRandom):
187
180
  def __init__(self,
188
181
  scale: float = 1.0,
189
182
  seed: int | None = None) -> None:
190
- """Initialiser taking the parameters of the probability distribution and
191
- an optional seed for the random generator to allow for reproducibility.
192
-
183
+ """
193
184
  Parameters
194
185
  ----------
195
186
  scale : float, optional
@@ -219,7 +210,7 @@ class GeneratorExponential(IGeneratorRandom):
219
210
  size = shape)
220
211
 
221
212
 
222
- class GeneratorChiSquare(IGeneratorRandom):
213
+ class GenChiSquare(IGenRandom):
223
214
  """Class wrapping the numpy chi square random generator. Implements the
224
215
  IGeneratorRandom interface to allow for interchangeability with other random
225
216
  number generators.
@@ -229,9 +220,7 @@ class GeneratorChiSquare(IGeneratorRandom):
229
220
  def __init__(self,
230
221
  dofs: float,
231
222
  seed: int | None = None) -> None:
232
- """Initialiser taking the parameters of the probability distribution and
233
- an optional seed for the random generator to allow for reproducibility.
234
-
223
+ """
235
224
  Parameters
236
225
  ----------
237
226
  dofs : float
@@ -261,7 +250,7 @@ class GeneratorChiSquare(IGeneratorRandom):
261
250
  size = shape)
262
251
 
263
252
 
264
- class GeneratorDirichlet(IGeneratorRandom):
253
+ class GenDirichlet(IGenRandom):
265
254
  """Class wrapping the numpy dirichlet random generator. Implements the
266
255
  IGeneratorRandom interface to allow for interchangeability with other random
267
256
  number generators.
@@ -271,9 +260,7 @@ class GeneratorDirichlet(IGeneratorRandom):
271
260
  def __init__(self,
272
261
  alpha: float,
273
262
  seed: int | None = None) -> None:
274
- """Initialiser taking the parameters of the probability distribution and
275
- an optional seed for the random generator to allow for reproducibility.
276
-
263
+ """
277
264
  Parameters
278
265
  ----------
279
266
  alpha : float
@@ -301,7 +288,7 @@ class GeneratorDirichlet(IGeneratorRandom):
301
288
  return self._rng.dirichlet(alpha = self._alpha, size = shape)
302
289
 
303
290
 
304
- class GeneratorF(IGeneratorRandom):
291
+ class GenF(IGenRandom):
305
292
  """Class wrapping the numpy F distribution random generator. Implements the
306
293
  IGeneratorRandom interface to allow for interchangeability with other random
307
294
  number generators.
@@ -311,9 +298,7 @@ class GeneratorF(IGeneratorRandom):
311
298
  def __init__(self,
312
299
  dofs: float,
313
300
  seed: int | None = None) -> None:
314
- """Initialiser taking the parameters of the probability distribution and
315
- an optional seed for the random generator to allow for reproducibility.
316
-
301
+ """
317
302
  Parameters
318
303
  ----------
319
304
  dofs : float
@@ -342,7 +327,7 @@ class GeneratorF(IGeneratorRandom):
342
327
  return self._rng.f(dfnum = self._dofs, size = shape)
343
328
 
344
329
 
345
- class GeneratorGamma(IGeneratorRandom):
330
+ class GenGamma(IGenRandom):
346
331
  """Class wrapping the numpy gamma random generator. Implements the
347
332
  IGeneratorRandom interface to allow for interchangeability with other random
348
333
  number generators.
@@ -353,9 +338,7 @@ class GeneratorGamma(IGeneratorRandom):
353
338
  shape: float,
354
339
  scale: float = 1.0,
355
340
  seed: int | None = None) -> None:
356
- """Initialiser taking the parameters of the probability distribution and
357
- an optional seed for the random generator to allow for reproducibility.
358
-
341
+ """
359
342
  Parameters
360
343
  ----------
361
344
  shape : float
@@ -388,7 +371,7 @@ class GeneratorGamma(IGeneratorRandom):
388
371
  size = shape)
389
372
 
390
373
 
391
- class GeneratorStandardT(IGeneratorRandom):
374
+ class GenStandardT(IGenRandom):
392
375
  """Class wrapping the numpy t distribution random generator. Implements the
393
376
  IGeneratorRandom interface to allow for interchangeability with other random
394
377
  number generators.
@@ -398,9 +381,7 @@ class GeneratorStandardT(IGeneratorRandom):
398
381
  def __init__(self,
399
382
  dofs: float,
400
383
  seed: int | None = None) -> None:
401
- """Initialiser taking the parameters of the probability distribution and
402
- an optional seed for the random generator to allow for reproducibility.
403
-
384
+ """
404
385
  Parameters
405
386
  ----------
406
387
  dofs : float
@@ -430,7 +411,7 @@ class GeneratorStandardT(IGeneratorRandom):
430
411
  size = shape)
431
412
 
432
413
 
433
- class GeneratorBeta(IGeneratorRandom):
414
+ class GenBeta(IGenRandom):
434
415
  """Class wrapping the numpy beta distribution random generator. Implements
435
416
  the IGeneratorRandom interface to allow for interchangeability with other
436
417
  random number generators.
@@ -441,9 +422,7 @@ class GeneratorBeta(IGeneratorRandom):
441
422
  a: float,
442
423
  b: float,
443
424
  seed: int | None = None) -> None:
444
- """Initialiser taking the parameters of the probability distribution and
445
- an optional seed for the random generator to allow for reproducibility.
446
-
425
+ """
447
426
  Parameters
448
427
  ----------
449
428
  a : float
@@ -476,7 +455,7 @@ class GeneratorBeta(IGeneratorRandom):
476
455
  size = shape)
477
456
 
478
457
 
479
- class GeneratorTriangular(IGeneratorRandom):
458
+ class GenTriangular(IGenRandom):
480
459
  """Class wrapping the numpy triangular random generator. Implements the
481
460
  IGeneratorRandom interface to allow for interchangeability with other random
482
461
  number generators.
@@ -488,9 +467,7 @@ class GeneratorTriangular(IGeneratorRandom):
488
467
  mode: float = 0.0,
489
468
  right: float = 1.0,
490
469
  seed: int | None = None) -> None:
491
- """Initialiser taking the parameters of the probability distribution and
492
- an optional seed for the random generator to allow for reproducibility.
493
-
470
+ """
494
471
  Parameters
495
472
  ----------
496
473
  left : float, optional
@@ -1,10 +1,13 @@
1
+ # ==============================================================================
2
+ # pyvale: the python validation engine
3
+ # License: MIT
4
+ # Copyright (C) 2025 The Computer Aided Validation Team
5
+ # ==============================================================================
6
+
1
7
  """
2
- ================================================================================
3
- pyvale: the python validation engine
4
- License: MIT
5
- Copyright (C) 2025 The Computer Aided Validation Team
6
- ================================================================================
8
+ NOTE: This module is a feature under developement.
7
9
  """
10
+
8
11
  import time
9
12
  import warnings
10
13
  from dataclasses import dataclass
@@ -14,9 +17,9 @@ from scipy.interpolate import griddata
14
17
  from scipy.interpolate import RectBivariateSpline
15
18
  from scipy import ndimage
16
19
 
17
- from pyvale.core.rasternp import edge_function, RasterNP
18
- from pyvale.core.cameradata2d import CameraData2D
19
- from pyvale.core.cameratools import CameraTools
20
+ from pyvale.rasternp import edge_function, RasterNP
21
+ from pyvale.cameradata2d import CameraData2D
22
+ from pyvale.cameratools import CameraTools
20
23
 
21
24
 
22
25
  @dataclass(slots=True)
@@ -1,17 +1,16 @@
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
+ # ==============================================================================
6
+
8
7
  import numpy as np
9
- from pyvale.core.field import IField
10
- from pyvale.core.sensordata import SensorData
11
- from pyvale.core.integratorspatial import IIntegratorSpatial
12
- from pyvale.core.integratortype import EIntSpatialType
13
- from pyvale.core.integratorrectangle import Rectangle2D
14
- from pyvale.core.integratorquadrature import (Quadrature2D,
8
+ from pyvale.field import IField
9
+ from pyvale.sensordata import SensorData
10
+ from pyvale.integratorspatial import IIntegratorSpatial
11
+ from pyvale.integratortype import EIntSpatialType
12
+ from pyvale.integratorrectangle import Rectangle2D
13
+ from pyvale.integratorquadrature import (Quadrature2D,
15
14
  create_gauss_weights_2d_4pts,
16
15
  create_gauss_weights_2d_9pts)
17
16
 
@@ -1,18 +1,16 @@
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
+ # ==============================================================================
6
+
8
7
  from typing import Callable
9
8
  import numpy as np
10
- from pyvale.core.field import IField
11
- from pyvale.core.integratorspatial import (IIntegratorSpatial,
12
- create_int_pt_array)
13
- from pyvale.core.sensordata import SensorData
9
+ from pyvale.field import IField
10
+ from pyvale.integratorspatial import (IIntegratorSpatial,
11
+ create_int_pt_array)
12
+ from pyvale.sensordata import SensorData
14
13
 
15
- #TODO: Docstrings
16
14
 
17
15
  class Quadrature2D(IIntegratorSpatial):
18
16
  """Gaussian quadrature numerical integrator for spatial averaging in 2D.
@@ -24,7 +22,7 @@ class Quadrature2D(IIntegratorSpatial):
24
22
  Implements the `IIntegratorSpatial` interface allowing for interoperability
25
23
  of different spatial integration algorithms for modelling sensor averaging.
26
24
  """
27
- __slots__ = ("_field","_area","_n_gauss_pts","_gauss_pt_offsets"
25
+ __slots__ = ("_field","_area","_gauss_pts_num","_gauss_pt_offsets"
28
26
  ,"_gauss_weight_func","_gauss_pts","_averages","_sens_data")
29
27
 
30
28
  def __init__(self,
@@ -32,8 +30,7 @@ class Quadrature2D(IIntegratorSpatial):
32
30
  sens_data: SensorData,
33
31
  gauss_pt_offsets: np.ndarray,
34
32
  gauss_weight_func: Callable) -> None:
35
- """Initiliaser for the 2D Gaussian quadrature numerical integrator.
36
-
33
+ """
37
34
  Parameters
38
35
  ----------
39
36
  field : IField
@@ -58,7 +55,7 @@ class Quadrature2D(IIntegratorSpatial):
58
55
  self._area = self._sens_data.spatial_dims[0] * \
59
56
  self._sens_data.spatial_dims[1]
60
57
 
61
- self._n_gauss_pts = gauss_pt_offsets.shape[0]
58
+ self._gauss_pts_num = gauss_pt_offsets.shape[0]
62
59
  self._gauss_pt_offsets = gauss_pt_offsets
63
60
  self._gauss_weight_func = gauss_weight_func
64
61
 
@@ -67,43 +64,61 @@ class Quadrature2D(IIntegratorSpatial):
67
64
  self._averages = None
68
65
 
69
66
  def calc_integrals(self, sens_data: SensorData | None = None) -> np.ndarray:
70
- """_summary_
67
+ """Calculates the numerical integrals for each sensor based on the
68
+ specified sensor data and numerical integration options (i.e. geometry
69
+ and integration points).
71
70
 
72
71
  Parameters
73
72
  ----------
74
73
  sens_data : SensorData | None, optional
75
- _description_, by default None
74
+ Specifies the sensor parameters used to calculate the averages, by
75
+ default None. Is a sensor data object is passed a reference to that
76
+ object is stored by this class and used in later calculations. If
77
+ None then it uses the SensorData object stored by this class.
78
+ Defaults to None.
76
79
 
77
80
  Returns
78
81
  -------
79
82
  np.ndarray
80
- _description_
83
+ Array of virtual sensor integrals with shape=(n_sensors,n_comps,
84
+ n_timsteps). Note this is consistent with pyvales measurement array.
81
85
  """
82
86
  self._averages = self.calc_averages(sens_data)
83
87
  return self._area*self.get_averages()
84
88
 
85
89
  def get_integrals(self) -> np.ndarray:
86
- """_summary_
90
+ """Gets the most recent calculation of the spatial averages for all
91
+ sensors in the sensor array without performing any new interpolation. If
92
+ the averages have not been calculated they are first calculated and then
93
+ returned.
87
94
 
88
95
  Returns
89
96
  -------
90
97
  np.ndarray
91
- _description_
98
+ Array of virtual sensor averages with shape=(n_sensors,n_comps,
99
+ n_timsteps). Note this is consistent with pyvales measurement array.
92
100
  """
93
101
  return self._area*self.get_averages()
94
102
 
95
103
  def calc_averages(self, sens_data: SensorData | None = None) -> np.ndarray:
96
- """_summary_
104
+ """Calculates the spatial averages for each sensor based on the
105
+ specified sensor data and numerical integration options (i.e. geometry
106
+ and integration points).
97
107
 
98
108
  Parameters
99
109
  ----------
100
110
  sens_data : SensorData | None, optional
101
- _description_, by default None
111
+ Specifies the sensor parameters used to calculate the averages, by
112
+ default None. Is a sensor data object is passed a reference to that
113
+ object is stored by this class and used in later calculations. If
114
+ None then it uses the SensorData object stored by this class.
115
+ Defaults to None.
102
116
 
103
117
  Returns
104
118
  -------
105
119
  np.ndarray
106
- _description_
120
+ Array of virtual sensor averages with shape=(n_sensors,n_comps,
121
+ n_timsteps). Note this is consistent with pyvales measurement array.
107
122
  """
108
123
  if sens_data is not None:
109
124
  self._sens_data = sens_data
@@ -122,7 +137,7 @@ class Quadrature2D(IIntegratorSpatial):
122
137
  gauss_vals.shape[2])
123
138
 
124
139
  # shape=(n_gauss_pts,n_sens,n_comps,n_timesteps)
125
- gauss_vals = gauss_vals.reshape((self._n_gauss_pts,)+meas_shape,
140
+ gauss_vals = gauss_vals.reshape((self._gauss_pts_num,)+meas_shape,
126
141
  order='F')
127
142
 
128
143
  # shape=(n_gauss_pts,n_sens,n_comps,n_timesteps)
@@ -139,12 +154,16 @@ class Quadrature2D(IIntegratorSpatial):
139
154
  return self._averages
140
155
 
141
156
  def get_averages(self) -> np.ndarray:
142
- """_summary_
157
+ """Gets the most recent calculation of the spatial averages for all
158
+ sensors in the sensor array without performing any new interpolation. If
159
+ the averages have not been calculated they are first calculated and then
160
+ returned.
143
161
 
144
162
  Returns
145
163
  -------
146
164
  np.ndarray
147
- _description_
165
+ Array of virtual sensor averages with shape=(n_sensors,n_comps,
166
+ n_timsteps). Note this is consistent with pyvales measurement array.
148
167
  """
149
168
  if self._averages is None:
150
169
  self._averages = self.calc_averages()
@@ -160,29 +179,35 @@ def create_gauss_weights_2d_4pts(meas_shape: tuple[int,int,int]) -> np.ndarray:
160
179
  Parameters
161
180
  ----------
162
181
  meas_shape : tuple[int,int,int]
163
- _description_
182
+ Shape of the measurement array, shape=(n_sensors,n_field_comps,
183
+ n_time_steps).
164
184
 
165
185
  Returns
166
186
  -------
167
187
  np.ndarray
168
- _description_
188
+ Array of gaussian quadrature weights with shape = (n_gauss_pts,n_sensors
189
+ ,n_field_comps,n_time_steps).
169
190
  """
170
191
  #shape=(4,)+meas_shape
171
192
  return np.ones((4,)+meas_shape)
172
193
 
173
194
 
174
195
  def create_gauss_weights_2d_9pts(meas_shape: tuple[int,int,int]) -> np.ndarray:
175
- """_summary_
196
+ """Helper function that creates an array of weights for gaussian quadrature
197
+ integration. This function provides the weights for 2D integrator with 9
198
+ integration points.
176
199
 
177
200
  Parameters
178
201
  ----------
179
202
  meas_shape : tuple[int,int,int]
180
- _description_
203
+ Shape of the measurement array, shape=(n_sensors,n_field_comps,
204
+ n_time_steps).
181
205
 
182
206
  Returns
183
207
  -------
184
208
  np.ndarray
185
- _description_
209
+ Array of gaussian quadrature weights with shape = (n_gauss_pts,n_sensors
210
+ ,n_field_comps,n_time_steps).
186
211
  """
187
212
  # shape=(9,)+meas_shape
188
213
  gauss_weights = np.vstack((25/81 * np.ones((4,)+meas_shape),
@@ -0,0 +1,165 @@
1
+ # ==============================================================================
2
+ # pyvale: the python validation engine
3
+ # License: MIT
4
+ # Copyright (C) 2025 The Computer Aided Validation Team
5
+ # ==============================================================================
6
+
7
+ import numpy as np
8
+ from pyvale.field import IField
9
+ from pyvale.integratorspatial import (IIntegratorSpatial,
10
+ create_int_pt_array)
11
+ from pyvale.sensordata import SensorData
12
+
13
+ #NOTE: code below is very similar to quadrature integrator should be able to
14
+ # refactor into injected classes/functions
15
+
16
+ class Rectangle2D(IIntegratorSpatial):
17
+ """Rectangular numerical integrator for spatial averaging in 2D. Used to
18
+ model spatial averaging of sensors over a rectangular area which is
19
+ specified in the SensorData object. Handles sampling of the physical field
20
+ at the integration points and averages them back to a single value per
21
+ sensor location as specified in the SensorData object.
22
+
23
+ Implements the `IIntegratorSpatial` interface allowing for interoperability
24
+ of different spatial integration algorithms for modelling sensor averaging.
25
+ """
26
+ __slots__ = ("_field","sens_data","_area","_area_int","_n_int_pts",
27
+ "_int_pt_offsets","_int_pts","_averages")
28
+
29
+ def __init__(self,
30
+ field: IField,
31
+ sens_data: SensorData,
32
+ int_pt_offsets: np.ndarray) -> None:
33
+ """
34
+ Parameters
35
+ ----------
36
+ field : IField
37
+ A physical field interface that will be sampled at the integration
38
+ points and averaged back to single value per sensor.
39
+ sens_data : SensorData
40
+ Parameters of the sensor array including the sensor locations,
41
+ sampling times, type of spatial integrator and its dimensions. See
42
+ the `SensorData` dataclass for more details.
43
+ int_pt_offsets : np.ndarray
44
+ Offsets from the central location of the integration area with
45
+ shape=(n_gauss_pts,coord[X,Y,Z])
46
+ """
47
+ self._field = field
48
+ self._sens_data = sens_data
49
+
50
+ self._area = (self._sens_data.spatial_dims[0]
51
+ * self._sens_data.spatial_dims[1])
52
+ self._area_int = self._area/int_pt_offsets.shape[0]
53
+
54
+ self._n_int_pts = int_pt_offsets.shape[0]
55
+ self._int_pt_offsets = int_pt_offsets
56
+ self._int_pts = create_int_pt_array(self._sens_data,
57
+ self._int_pt_offsets)
58
+
59
+ self._averages = None
60
+
61
+
62
+ def calc_integrals(self, sens_data: SensorData | None = None) -> np.ndarray:
63
+ """Calculates the numerical integrals for each sensor based on the
64
+ specified sensor data and numerical integration options (i.e. geometry
65
+ and integration points).
66
+
67
+ Parameters
68
+ ----------
69
+ sens_data : SensorData | None, optional
70
+ Specifies the sensor parameters used to calculate the averages, by
71
+ default None. Is a sensor data object is passed a reference to that
72
+ object is stored by this class and used in later calculations. If
73
+ None then it uses the SensorData object stored by this class.
74
+ Defaults to None.
75
+
76
+ Returns
77
+ -------
78
+ np.ndarray
79
+ Array of virtual sensor integrals with shape=(n_sensors,n_comps,
80
+ n_timsteps). Note this is consistent with pyvales measurement array.
81
+ """
82
+ self._averages = self.calc_averages(sens_data)
83
+ return self._area*self.get_averages()
84
+
85
+
86
+ def get_integrals(self) -> np.ndarray:
87
+ """Gets the most recent calculation of the spatial averages for all
88
+ sensors in the sensor array without performing any new interpolation. If
89
+ the averages have not been calculated they are first calculated and then
90
+ returned.
91
+
92
+ Returns
93
+ -------
94
+ np.ndarray
95
+ Array of virtual sensor averages with shape=(n_sensors,n_comps,
96
+ n_timsteps). Note this is consistent with pyvales measurement array.
97
+ """
98
+ return self._area*self.get_averages()
99
+
100
+ def calc_averages(self, sens_data: SensorData | None = None) -> np.ndarray:
101
+ """Calculates the spatial averages for each sensor based on the
102
+ specified sensor data and numerical integration options (i.e. geometry
103
+ and integration points).
104
+
105
+ Parameters
106
+ ----------
107
+ sens_data : SensorData | None, optional
108
+ Specifies the sensor parameters used to calculate the averages, by
109
+ default None. Is a sensor data object is passed a reference to that
110
+ object is stored by this class and used in later calculations. If
111
+ None then it uses the SensorData object stored by this class.
112
+ Defaults to None.
113
+
114
+ Returns
115
+ -------
116
+ np.ndarray
117
+ Array of virtual sensor averages with shape=(n_sensors,n_comps,
118
+ n_timsteps). Note this is consistent with pyvales measurement array.
119
+ """
120
+ if sens_data is not None:
121
+ self._sens_data = sens_data
122
+
123
+ # shape=(n_sens*n_int_pts,n_dims)
124
+ self._int_pts = create_int_pt_array(self._sens_data,
125
+ self._int_pt_offsets)
126
+
127
+
128
+ # shape=(n_int_pts*n_sens,n_comps,n_timesteps)
129
+ int_vals = self._field.sample_field(self._int_pts,
130
+ self._sens_data.sample_times,
131
+ self._sens_data.angles)
132
+
133
+ meas_shape = (self._sens_data.positions.shape[0],
134
+ int_vals.shape[1],
135
+ int_vals.shape[2])
136
+
137
+ # shape=(n_gauss_pts,n_sens,n_comps,n_timesteps)
138
+ int_vals = int_vals.reshape((self._n_int_pts,)+meas_shape,
139
+ order='F')
140
+
141
+ # shape=(n_sensors,n_comps,n_timsteps)
142
+ self._averages = 1/self._area * np.sum(self._area_int*int_vals,axis=0)
143
+
144
+ return self._averages
145
+
146
+
147
+ def get_averages(self) -> np.ndarray:
148
+ """Gets the most recent calculation of the spatial averages for all
149
+ sensors in the sensor array without performing any new interpolation. If
150
+ the averages have not been calculated they are first calculated and then
151
+ returned.
152
+
153
+ Returns
154
+ -------
155
+ np.ndarray
156
+ Array of virtual sensor averages with shape=(n_sensors,n_comps,
157
+ n_timsteps). Note this is consistent with pyvales measurement array.
158
+ """
159
+ if self._averages is None:
160
+ self._averages = self.calc_averages()
161
+
162
+ return self._averages
163
+
164
+
165
+