pyvale 2025.4.0__py3-none-any.whl

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

Potentially problematic release.


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

Files changed (157) hide show
  1. pyvale/__init__.py +75 -0
  2. pyvale/core/__init__.py +7 -0
  3. pyvale/core/analyticmeshgen.py +59 -0
  4. pyvale/core/analyticsimdatafactory.py +63 -0
  5. pyvale/core/analyticsimdatagenerator.py +160 -0
  6. pyvale/core/camera.py +146 -0
  7. pyvale/core/cameradata.py +64 -0
  8. pyvale/core/cameradata2d.py +82 -0
  9. pyvale/core/cameratools.py +328 -0
  10. pyvale/core/cython/rastercyth.c +32267 -0
  11. pyvale/core/cython/rastercyth.py +636 -0
  12. pyvale/core/dataset.py +250 -0
  13. pyvale/core/errorcalculator.py +112 -0
  14. pyvale/core/errordriftcalc.py +146 -0
  15. pyvale/core/errorintegrator.py +339 -0
  16. pyvale/core/errorrand.py +614 -0
  17. pyvale/core/errorsysdep.py +331 -0
  18. pyvale/core/errorsysfield.py +407 -0
  19. pyvale/core/errorsysindep.py +905 -0
  20. pyvale/core/experimentsimulator.py +99 -0
  21. pyvale/core/field.py +136 -0
  22. pyvale/core/fieldconverter.py +154 -0
  23. pyvale/core/fieldsampler.py +112 -0
  24. pyvale/core/fieldscalar.py +167 -0
  25. pyvale/core/fieldtensor.py +221 -0
  26. pyvale/core/fieldtransform.py +384 -0
  27. pyvale/core/fieldvector.py +215 -0
  28. pyvale/core/generatorsrandom.py +528 -0
  29. pyvale/core/imagedef2d.py +566 -0
  30. pyvale/core/integratorfactory.py +241 -0
  31. pyvale/core/integratorquadrature.py +192 -0
  32. pyvale/core/integratorrectangle.py +88 -0
  33. pyvale/core/integratorspatial.py +90 -0
  34. pyvale/core/integratortype.py +44 -0
  35. pyvale/core/optimcheckfuncs.py +153 -0
  36. pyvale/core/raster.py +31 -0
  37. pyvale/core/rastercy.py +76 -0
  38. pyvale/core/rasternp.py +604 -0
  39. pyvale/core/rendermesh.py +156 -0
  40. pyvale/core/sensorarray.py +179 -0
  41. pyvale/core/sensorarrayfactory.py +210 -0
  42. pyvale/core/sensorarraypoint.py +280 -0
  43. pyvale/core/sensordata.py +72 -0
  44. pyvale/core/sensordescriptor.py +101 -0
  45. pyvale/core/sensortools.py +143 -0
  46. pyvale/core/visualexpplotter.py +151 -0
  47. pyvale/core/visualimagedef.py +71 -0
  48. pyvale/core/visualimages.py +75 -0
  49. pyvale/core/visualopts.py +180 -0
  50. pyvale/core/visualsimanimator.py +83 -0
  51. pyvale/core/visualsimplotter.py +182 -0
  52. pyvale/core/visualtools.py +81 -0
  53. pyvale/core/visualtraceplotter.py +256 -0
  54. pyvale/data/__init__.py +7 -0
  55. pyvale/data/case13_out.e +0 -0
  56. pyvale/data/case16_out.e +0 -0
  57. pyvale/data/case17_out.e +0 -0
  58. pyvale/data/case18_1_out.e +0 -0
  59. pyvale/data/case18_2_out.e +0 -0
  60. pyvale/data/case18_3_out.e +0 -0
  61. pyvale/data/case25_out.e +0 -0
  62. pyvale/data/case26_out.e +0 -0
  63. pyvale/data/optspeckle_2464x2056px_spec5px_8bit_gblur1px.tiff +0 -0
  64. pyvale/examples/__init__.py +7 -0
  65. pyvale/examples/analyticdatagen/__init__.py +7 -0
  66. pyvale/examples/analyticdatagen/ex1_1_scalarvisualisation.py +38 -0
  67. pyvale/examples/analyticdatagen/ex1_2_scalarcasebuild.py +46 -0
  68. pyvale/examples/analyticdatagen/ex2_1_analyticsensors.py +83 -0
  69. pyvale/examples/ex1_1_thermal2d.py +89 -0
  70. pyvale/examples/ex1_2_thermal2d.py +111 -0
  71. pyvale/examples/ex1_3_thermal2d.py +113 -0
  72. pyvale/examples/ex1_4_thermal2d.py +89 -0
  73. pyvale/examples/ex1_5_thermal2d.py +105 -0
  74. pyvale/examples/ex2_1_thermal3d .py +87 -0
  75. pyvale/examples/ex2_2_thermal3d.py +51 -0
  76. pyvale/examples/ex2_3_thermal3d.py +109 -0
  77. pyvale/examples/ex3_1_displacement2d.py +47 -0
  78. pyvale/examples/ex3_2_displacement2d.py +79 -0
  79. pyvale/examples/ex3_3_displacement2d.py +104 -0
  80. pyvale/examples/ex3_4_displacement2d.py +105 -0
  81. pyvale/examples/ex4_1_strain2d.py +57 -0
  82. pyvale/examples/ex4_2_strain2d.py +79 -0
  83. pyvale/examples/ex4_3_strain2d.py +100 -0
  84. pyvale/examples/ex5_1_multiphysics2d.py +78 -0
  85. pyvale/examples/ex6_1_multiphysics2d_expsim.py +118 -0
  86. pyvale/examples/ex6_2_multiphysics3d_expsim.py +158 -0
  87. pyvale/examples/features/__init__.py +7 -0
  88. pyvale/examples/features/ex_animation_tools_3dmonoblock.py +83 -0
  89. pyvale/examples/features/ex_area_avg.py +89 -0
  90. pyvale/examples/features/ex_calibration_error.py +108 -0
  91. pyvale/examples/features/ex_chain_field_errs.py +141 -0
  92. pyvale/examples/features/ex_field_errs.py +78 -0
  93. pyvale/examples/features/ex_sensor_single_angle_batch.py +110 -0
  94. pyvale/examples/imagedef2d/ex_imagedef2d_todisk.py +86 -0
  95. pyvale/examples/rasterisation/ex_rastenp.py +154 -0
  96. pyvale/examples/rasterisation/ex_rastercyth_oneframe.py +220 -0
  97. pyvale/examples/rasterisation/ex_rastercyth_static_cypara.py +194 -0
  98. pyvale/examples/rasterisation/ex_rastercyth_static_pypara.py +193 -0
  99. pyvale/simcases/case00_HEX20.i +242 -0
  100. pyvale/simcases/case00_HEX27.i +242 -0
  101. pyvale/simcases/case00_TET10.i +242 -0
  102. pyvale/simcases/case00_TET14.i +242 -0
  103. pyvale/simcases/case01.i +101 -0
  104. pyvale/simcases/case02.i +156 -0
  105. pyvale/simcases/case03.i +136 -0
  106. pyvale/simcases/case04.i +181 -0
  107. pyvale/simcases/case05.i +234 -0
  108. pyvale/simcases/case06.i +305 -0
  109. pyvale/simcases/case07.geo +135 -0
  110. pyvale/simcases/case07.i +87 -0
  111. pyvale/simcases/case08.geo +144 -0
  112. pyvale/simcases/case08.i +153 -0
  113. pyvale/simcases/case09.geo +204 -0
  114. pyvale/simcases/case09.i +87 -0
  115. pyvale/simcases/case10.geo +204 -0
  116. pyvale/simcases/case10.i +257 -0
  117. pyvale/simcases/case11.geo +337 -0
  118. pyvale/simcases/case11.i +147 -0
  119. pyvale/simcases/case12.geo +388 -0
  120. pyvale/simcases/case12.i +329 -0
  121. pyvale/simcases/case13.i +140 -0
  122. pyvale/simcases/case14.i +159 -0
  123. pyvale/simcases/case15.geo +337 -0
  124. pyvale/simcases/case15.i +150 -0
  125. pyvale/simcases/case16.geo +391 -0
  126. pyvale/simcases/case16.i +357 -0
  127. pyvale/simcases/case17.geo +135 -0
  128. pyvale/simcases/case17.i +144 -0
  129. pyvale/simcases/case18.i +254 -0
  130. pyvale/simcases/case18_1.i +254 -0
  131. pyvale/simcases/case18_2.i +254 -0
  132. pyvale/simcases/case18_3.i +254 -0
  133. pyvale/simcases/case19.geo +252 -0
  134. pyvale/simcases/case19.i +99 -0
  135. pyvale/simcases/case20.geo +252 -0
  136. pyvale/simcases/case20.i +250 -0
  137. pyvale/simcases/case21.geo +74 -0
  138. pyvale/simcases/case21.i +155 -0
  139. pyvale/simcases/case22.geo +82 -0
  140. pyvale/simcases/case22.i +140 -0
  141. pyvale/simcases/case23.geo +164 -0
  142. pyvale/simcases/case23.i +140 -0
  143. pyvale/simcases/case24.geo +79 -0
  144. pyvale/simcases/case24.i +123 -0
  145. pyvale/simcases/case25.geo +82 -0
  146. pyvale/simcases/case25.i +140 -0
  147. pyvale/simcases/case26.geo +166 -0
  148. pyvale/simcases/case26.i +140 -0
  149. pyvale/simcases/run_1case.py +61 -0
  150. pyvale/simcases/run_all_cases.py +69 -0
  151. pyvale/simcases/run_build_case.py +64 -0
  152. pyvale/simcases/run_example_cases.py +69 -0
  153. pyvale-2025.4.0.dist-info/METADATA +140 -0
  154. pyvale-2025.4.0.dist-info/RECORD +157 -0
  155. pyvale-2025.4.0.dist-info/WHEEL +5 -0
  156. pyvale-2025.4.0.dist-info/licenses/LICENSE +21 -0
  157. pyvale-2025.4.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,241 @@
1
+ """
2
+ ================================================================================
3
+ pyvale: the python validation engine
4
+ License: MIT
5
+ Copyright (C) 2025 The Computer Aided Validation Team
6
+ ================================================================================
7
+ """
8
+ 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,
15
+ create_gauss_weights_2d_4pts,
16
+ create_gauss_weights_2d_9pts)
17
+
18
+ class IntegratorSpatialFactory:
19
+ """Namespace for static methods used to build 2D spatial integrators. These
20
+ integrators are used to simulate spatial averaging for sensors.
21
+ """
22
+
23
+ @staticmethod
24
+ def rect_2d_1pt(field: IField,
25
+ sensor_data: SensorData,
26
+ )-> Rectangle2D:
27
+ """Builds and returns a 2D rectangular spatial integrator with a single
28
+ integration point.
29
+
30
+ Parameters
31
+ ----------
32
+ field : IField
33
+ Interface specifying the physical field that the integrator will
34
+ sample from.
35
+ sensor_data : SensorData
36
+ Sensor data specifying the location of the sensors that will be
37
+ used for the spatial integration
38
+
39
+ Returns
40
+ -------
41
+ Rectangle2D
42
+ Rectangular spatial integrator that can sample the specified
43
+ physical field at specified locations
44
+ """
45
+
46
+ int_pt_offsets = np.array([[0,0,0],])
47
+
48
+ return Rectangle2D(field,
49
+ sensor_data,
50
+ int_pt_offsets)
51
+
52
+
53
+ @staticmethod
54
+ def rect_2d_4pt(field: IField,
55
+ sensor_data: SensorData,
56
+ )-> Rectangle2D:
57
+ """Builds and returns a 2D rectangular spatial integrator with four
58
+ equally spaced integration points in a grid pattern.
59
+
60
+ Parameters
61
+ ----------
62
+ field : IField
63
+ Interface specifying the physical field that the integrator will
64
+ sample from.
65
+ sensor_data : SensorData
66
+ Sensor data specifying the location of the sensors that will be
67
+ used for the spatial integration
68
+
69
+ Returns
70
+ -------
71
+ Rectangle2D
72
+ Rectangular spatial integrator that can sample the specified
73
+ physical field at specified locations
74
+ """
75
+ int_pt_offsets = sensor_data.spatial_dims * np.array([[-0.5,-0.5,0],
76
+ [-0.5,0.5,0],
77
+ [0.5,-0.5,0],
78
+ [0.5,0.5,0],])
79
+
80
+ return Rectangle2D(field,
81
+ sensor_data,
82
+ int_pt_offsets)
83
+
84
+
85
+
86
+ @staticmethod
87
+ def rect_2d_9pt(field: IField,
88
+ sensor_data: SensorData,
89
+ )-> Rectangle2D:
90
+ """Builds and returns a 2D rectangular spatial integrator with nine
91
+ equally spaced integration points in a grid pattern.
92
+
93
+ Parameters
94
+ ----------
95
+ field : IField
96
+ Interface specifying the physical field that the integrator will
97
+ sample from.
98
+ sensor_data : SensorData
99
+ Sensor data specifying the location of the sensors that will be
100
+ used for the spatial integration
101
+
102
+ Returns
103
+ -------
104
+ Rectangle2D
105
+ Rectangular spatial integrator that can sample the specified
106
+ physical field at specified locations
107
+ """
108
+ int_pt_offsets = sensor_data.spatial_dims * np.array([[-1/3,-1/3,0],
109
+ [-1/3,0,0],
110
+ [-1/3,1/3,0],
111
+ [0,-1/3,0],
112
+ [0,0,0],
113
+ [0,1/3,0],
114
+ [1/3,-1/3,0],
115
+ [1/3,0,0],
116
+ [1/3,1/3,0]])
117
+
118
+ return Rectangle2D(field,
119
+ sensor_data,
120
+ int_pt_offsets)
121
+
122
+
123
+ @staticmethod
124
+ def quad_2d_4pt(field: IField,
125
+ sensor_data: SensorData,
126
+ )-> Quadrature2D:
127
+ """Builds and returns a Gaussian quadrature spatal integrator based on
128
+ a rectangular area with four integration points.
129
+
130
+ Parameters
131
+ ----------
132
+ field : IField
133
+ Interface specifying the physical field that the integrator will
134
+ sample from.
135
+ sensor_data : SensorData
136
+ Sensor data specifying the location of the sensors that will be
137
+ used for the spatial integration
138
+
139
+ Returns
140
+ -------
141
+ Quadrature2D
142
+ Quadrature integrator that can be used to sample the physical field
143
+ at specified locations.
144
+ """
145
+ gauss_pt_offsets = (sensor_data.spatial_dims * 1/np.sqrt(3)
146
+ * np.array([[-1,-1,0],
147
+ [-1,1,0],
148
+ [1,-1,0],
149
+ [1,1,0]]))
150
+
151
+ gauss_weight_func = create_gauss_weights_2d_4pts
152
+
153
+ return Quadrature2D(field,
154
+ sensor_data,
155
+ gauss_pt_offsets,
156
+ gauss_weight_func)
157
+
158
+
159
+ @staticmethod
160
+ def quad_2d_9pt(field: IField,
161
+ sensor_data: SensorData,
162
+ )-> Quadrature2D:
163
+ """Builds and returns a Gaussian quadrature spatal integrator based on
164
+ a rectangular area with nine integration points.
165
+
166
+ Parameters
167
+ ----------
168
+ field : IField
169
+ Interface specifying the physical field that the integrator will
170
+ sample from.
171
+ sensor_data : SensorData
172
+ Sensor data specifying the location of the sensors that will be
173
+ used for the spatial integration
174
+
175
+ Returns
176
+ -------
177
+ Quadrature2D
178
+ Quadrature integrator that can be used to sample the physical field
179
+ at specified locations.
180
+ """
181
+ gauss_pt_offsets = (sensor_data.spatial_dims
182
+ * np.array([[-np.sqrt(0.6),-np.sqrt(0.6),0],
183
+ [-np.sqrt(0.6),np.sqrt(0.6),0],
184
+ [np.sqrt(0.6),-np.sqrt(0.6),0],
185
+ [np.sqrt(0.6),np.sqrt(0.6),0],
186
+ [-np.sqrt(0.6),0,0],
187
+ [0,-np.sqrt(0.6),0],
188
+ [0,np.sqrt(0.6),0],
189
+ [np.sqrt(0.6),0,0],
190
+ [0,0,0]]))
191
+
192
+ gauss_weight_func = create_gauss_weights_2d_9pts
193
+
194
+ return Quadrature2D(field,
195
+ sensor_data,
196
+ gauss_pt_offsets,
197
+ gauss_weight_func)
198
+
199
+
200
+ def build_spatial_averager(field: IField, sensor_data: SensorData,
201
+ ) -> IIntegratorSpatial | None:
202
+ """Helper function to build a spatial integrator based on the
203
+ "EIntSpatialType" enumeration in the SensorData object. Separates the
204
+ spatial integration object from the SensorData object.
205
+
206
+ Parameters
207
+ ----------
208
+ field : IField
209
+ Physical field that will be sampled by the spatial averager.
210
+ sensor_data : SensorData
211
+ Sensor data containing the type of spatial integrator to build in the
212
+ `sensor_data.spatial_averager` as an `EIntSpatialType` enumeration.
213
+
214
+ Returns
215
+ -------
216
+ IIntegratorSpatial | None
217
+ The spatial averager that will sample the physical field at the
218
+ specified sensor locations. If `sensor_data.spatial_averager` or
219
+ `sensor_data.spatial_dims` are None then it is not possible to build a
220
+ spatial integrator and None is returned.
221
+ """
222
+ if sensor_data.spatial_averager is None or sensor_data.spatial_dims is None:
223
+ return None
224
+
225
+ if sensor_data.spatial_averager == EIntSpatialType.RECT1PT:
226
+ return IntegratorSpatialFactory.rect_2d_1pt(field,
227
+ sensor_data)
228
+ elif sensor_data.spatial_averager == EIntSpatialType.RECT4PT:
229
+ return IntegratorSpatialFactory.rect_2d_4pt(field,
230
+ sensor_data)
231
+ elif sensor_data.spatial_averager == EIntSpatialType.RECT9PT:
232
+ return IntegratorSpatialFactory.rect_2d_9pt(field,
233
+ sensor_data)
234
+ elif sensor_data.spatial_averager == EIntSpatialType.QUAD4PT:
235
+ return IntegratorSpatialFactory.quad_2d_4pt(field,
236
+ sensor_data)
237
+ elif sensor_data.spatial_averager == EIntSpatialType.QUAD9PT:
238
+ return IntegratorSpatialFactory.quad_2d_9pt(field,
239
+ sensor_data)
240
+ else:
241
+ return None
@@ -0,0 +1,192 @@
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
9
+ 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
14
+
15
+ #TODO: Docstrings
16
+
17
+ class Quadrature2D(IIntegratorSpatial):
18
+ """Gaussian quadrature numerical integrator for spatial averaging in 2D.
19
+ Used to model spatial averaging of sensors over a rectangular area which is
20
+ specified in the SensorData object. Handles sampling of the physical field
21
+ at the integration points and averages them back to a single value per
22
+ sensor location as specified in the SensorData object.
23
+
24
+ Implements the `IIntegratorSpatial` interface allowing for interoperability
25
+ of different spatial integration algorithms for modelling sensor averaging.
26
+ """
27
+ __slots__ = ("_field","_area","_n_gauss_pts","_gauss_pt_offsets"
28
+ ,"_gauss_weight_func","_gauss_pts","_averages","_sens_data")
29
+
30
+ def __init__(self,
31
+ field: IField,
32
+ sens_data: SensorData,
33
+ gauss_pt_offsets: np.ndarray,
34
+ gauss_weight_func: Callable) -> None:
35
+ """Initiliaser for the 2D Gaussian quadrature numerical integrator.
36
+
37
+ Parameters
38
+ ----------
39
+ field : IField
40
+ A physical field interface that will be sampled at the integration
41
+ points and averaged back to single value per sensor.
42
+ sens_data : SensorData
43
+ Parameters of the sensor array including the sensor locations,
44
+ sampling times, type of spatial integrator and its dimensions. See
45
+ the `SensorData` dataclass for more details.
46
+ gauss_pt_offsets : np.ndarray
47
+ Offsets from the central location of the integration area with
48
+ shape=(n_gauss_pts,coord[X,Y,Z])
49
+ gauss_weight_func : Callable
50
+ A function that takes the shape of the measurement array as a tuple
51
+ and returns a numpy array of weights for the gaussian integration
52
+ points. The function must return an array with shape=(n_gauss_pts,)
53
+ +meas_shape where meas_shape=(num_sensors,num_field_components,
54
+ num_time_steps)
55
+ """
56
+ self._field = field
57
+ self._sens_data = sens_data
58
+ self._area = self._sens_data.spatial_dims[0] * \
59
+ self._sens_data.spatial_dims[1]
60
+
61
+ self._n_gauss_pts = gauss_pt_offsets.shape[0]
62
+ self._gauss_pt_offsets = gauss_pt_offsets
63
+ self._gauss_weight_func = gauss_weight_func
64
+
65
+ self._gauss_pts = create_int_pt_array(self._sens_data,
66
+ self._gauss_pt_offsets)
67
+ self._averages = None
68
+
69
+ def calc_integrals(self, sens_data: SensorData | None = None) -> np.ndarray:
70
+ """_summary_
71
+
72
+ Parameters
73
+ ----------
74
+ sens_data : SensorData | None, optional
75
+ _description_, by default None
76
+
77
+ Returns
78
+ -------
79
+ np.ndarray
80
+ _description_
81
+ """
82
+ self._averages = self.calc_averages(sens_data)
83
+ return self._area*self.get_averages()
84
+
85
+ def get_integrals(self) -> np.ndarray:
86
+ """_summary_
87
+
88
+ Returns
89
+ -------
90
+ np.ndarray
91
+ _description_
92
+ """
93
+ return self._area*self.get_averages()
94
+
95
+ def calc_averages(self, sens_data: SensorData | None = None) -> np.ndarray:
96
+ """_summary_
97
+
98
+ Parameters
99
+ ----------
100
+ sens_data : SensorData | None, optional
101
+ _description_, by default None
102
+
103
+ Returns
104
+ -------
105
+ np.ndarray
106
+ _description_
107
+ """
108
+ if sens_data is not None:
109
+ self._sens_data = sens_data
110
+
111
+ # shape=(n_sens*n_gauss_pts,n_dims)
112
+ self._gauss_pts = create_int_pt_array(self._sens_data,
113
+ self._gauss_pt_offsets)
114
+
115
+ # shape=(n_gauss_pts*n_sens,n_comps,n_timesteps)
116
+ gauss_vals = self._field.sample_field(self._gauss_pts,
117
+ self._sens_data.sample_times,
118
+ self._sens_data.angles)
119
+
120
+ meas_shape = (self._sens_data.positions.shape[0],
121
+ gauss_vals.shape[1],
122
+ gauss_vals.shape[2])
123
+
124
+ # shape=(n_gauss_pts,n_sens,n_comps,n_timesteps)
125
+ gauss_vals = gauss_vals.reshape((self._n_gauss_pts,)+meas_shape,
126
+ order='F')
127
+
128
+ # shape=(n_gauss_pts,n_sens,n_comps,n_timesteps)
129
+ gauss_weights = self._gauss_weight_func(meas_shape)
130
+
131
+ # NOTE: coeff comes from changing gauss interval from [-1,1] to [a,b] -
132
+ # so (a-b)/2 * (a-b)/2 = sensor_area / 4, then need to divide by the
133
+ # integration area to convert to an average:
134
+ # integrals = self._area/4 * np.sum(gauss_weights*gauss_vals,axis=0)
135
+ # self._averages = (1/self._area)*integrals
136
+
137
+ # shape=(n_sensors,n_comps,n_timsteps)=meas_shape
138
+ self._averages = 1/4 * np.sum(gauss_weights*gauss_vals,axis=0)
139
+ return self._averages
140
+
141
+ def get_averages(self) -> np.ndarray:
142
+ """_summary_
143
+
144
+ Returns
145
+ -------
146
+ np.ndarray
147
+ _description_
148
+ """
149
+ if self._averages is None:
150
+ self._averages = self.calc_averages()
151
+
152
+ return self._averages
153
+
154
+
155
+ def create_gauss_weights_2d_4pts(meas_shape: tuple[int,int,int]) -> np.ndarray:
156
+ """Helper function that creates an array of weights for gaussian quadrature
157
+ integration. This function provides the weights for 2D integrator with 4
158
+ integration points.
159
+
160
+ Parameters
161
+ ----------
162
+ meas_shape : tuple[int,int,int]
163
+ _description_
164
+
165
+ Returns
166
+ -------
167
+ np.ndarray
168
+ _description_
169
+ """
170
+ #shape=(4,)+meas_shape
171
+ return np.ones((4,)+meas_shape)
172
+
173
+
174
+ def create_gauss_weights_2d_9pts(meas_shape: tuple[int,int,int]) -> np.ndarray:
175
+ """_summary_
176
+
177
+ Parameters
178
+ ----------
179
+ meas_shape : tuple[int,int,int]
180
+ _description_
181
+
182
+ Returns
183
+ -------
184
+ np.ndarray
185
+ _description_
186
+ """
187
+ # shape=(9,)+meas_shape
188
+ gauss_weights = np.vstack((25/81 * np.ones((4,)+meas_shape),
189
+ 40/81 * np.ones((4,)+meas_shape),
190
+ 64/81 * np.ones((1,)+meas_shape)))
191
+ return gauss_weights
192
+
@@ -0,0 +1,88 @@
1
+ """
2
+ ================================================================================
3
+ pyvale: the python validation engine
4
+ License: MIT
5
+ Copyright (C) 2025 The Computer Aided Validation Team
6
+ ================================================================================
7
+ """
8
+ import numpy as np
9
+ from pyvale.core.field import IField
10
+ from pyvale.core.integratorspatial import (IIntegratorSpatial,
11
+ create_int_pt_array)
12
+ from pyvale.core.sensordata import SensorData
13
+
14
+
15
+ #TODO: Docstrings
16
+
17
+ #NOTE: code below is very similar to quadrature integrator should be able to
18
+ # refactor into injected classes/functions
19
+
20
+ class Rectangle2D(IIntegratorSpatial):
21
+ __slots__ = ("_field","sens_data","_area","_area_int","_n_int_pts",
22
+ "_int_pt_offsets","_int_pts","_averages")
23
+
24
+ def __init__(self,
25
+ field: IField,
26
+ sens_data: SensorData,
27
+ int_pt_offsets: np.ndarray) -> None:
28
+
29
+ self._field = field
30
+ self._sens_data = sens_data
31
+
32
+ self._area = (self._sens_data.spatial_dims[0]
33
+ * self._sens_data.spatial_dims[1])
34
+ self._area_int = self._area/int_pt_offsets.shape[0]
35
+
36
+ self._n_int_pts = int_pt_offsets.shape[0]
37
+ self._int_pt_offsets = int_pt_offsets
38
+ self._int_pts = create_int_pt_array(self._sens_data,
39
+ self._int_pt_offsets)
40
+
41
+ self._averages = None
42
+
43
+
44
+ def calc_integrals(self, sens_data: SensorData | None = None) -> np.ndarray:
45
+ self._averages = self.calc_averages(sens_data)
46
+ return self._area*self.get_averages()
47
+
48
+
49
+ def get_integrals(self) -> np.ndarray:
50
+ return self._area*self.get_averages()
51
+
52
+ def calc_averages(self, sens_data: SensorData | None = None) -> np.ndarray:
53
+
54
+ if sens_data is not None:
55
+ self._sens_data = sens_data
56
+
57
+ # shape=(n_sens*n_int_pts,n_dims)
58
+ self._int_pts = create_int_pt_array(self._sens_data,
59
+ self._int_pt_offsets)
60
+
61
+
62
+ # shape=(n_int_pts*n_sens,n_comps,n_timesteps)
63
+ int_vals = self._field.sample_field(self._int_pts,
64
+ self._sens_data.sample_times,
65
+ self._sens_data.angles)
66
+
67
+ meas_shape = (self._sens_data.positions.shape[0],
68
+ int_vals.shape[1],
69
+ int_vals.shape[2])
70
+
71
+ # shape=(n_gauss_pts,n_sens,n_comps,n_timesteps)
72
+ int_vals = int_vals.reshape((self._n_int_pts,)+meas_shape,
73
+ order='F')
74
+
75
+ # shape=(n_sensors,n_comps,n_timsteps)
76
+ self._averages = 1/self._area * np.sum(self._area_int*int_vals,axis=0)
77
+
78
+ return self._averages
79
+
80
+
81
+ def get_averages(self) -> np.ndarray:
82
+ if self._averages is None:
83
+ self._averages = self.calc_averages()
84
+
85
+ return self._averages
86
+
87
+
88
+
@@ -0,0 +1,90 @@
1
+ """
2
+ ================================================================================
3
+ pyvale: the python validation engine
4
+ License: MIT
5
+ Copyright (C) 2025 The Computer Aided Validation Team
6
+ ================================================================================
7
+ """
8
+ from abc import ABC, abstractmethod
9
+ import numpy as np
10
+ from pyvale.core.sensordata import SensorData
11
+
12
+
13
+ def create_int_pt_array(sens_data: SensorData,
14
+ int_pt_offsets: np.ndarray,
15
+ ) -> np.ndarray:
16
+ """Creates the integration point locations in local coordinates based on the
17
+ specified offsets from the local origin.
18
+
19
+ Parameters
20
+ ----------
21
+ sens_data : SensorData
22
+ Contains the parameters of the sensor array including: positions, sample
23
+ times and orientations. If specified the sensor orientations are used
24
+ to rotate the positions of the integration points.
25
+ int_pt_offsets : np.ndarray
26
+ Offsets of the intergation points in non-rotated local coordinates.
27
+
28
+ Returns
29
+ -------
30
+ np.ndarray
31
+ The integration point locations in world (simulation) coordinates. The
32
+ rows of the array are all the integration points for all sensors and the
33
+ columns are the X,Y,Z coordinates. shape=(num_sensors*num_int_points,3).
34
+ """
35
+ n_sens = sens_data.positions.shape[0]
36
+ n_int_pts = int_pt_offsets.shape[0]
37
+
38
+ # shape=(n_sens*n_int_pts,n_dims)
39
+ offset_array = np.tile(int_pt_offsets,(n_sens,1))
40
+
41
+ if sens_data.angles is not None:
42
+ for ii,rr in enumerate(sens_data.angles):
43
+ offset_array[ii*n_int_pts:(ii+1)*n_int_pts,:] = \
44
+ np.matmul(rr.as_matrix(),int_pt_offsets.T).T
45
+
46
+ # shape=(n_sens*n_int_pts,n_dims)
47
+ int_pt_array = np.repeat(sens_data.positions,int_pt_offsets.shape[0],axis=0)
48
+
49
+ return int_pt_array + offset_array
50
+
51
+
52
+ class IIntegratorSpatial(ABC):
53
+ """Interface (abstract base class) for spatial integrators. Used for
54
+ averaging sensor values over a given space.
55
+ """
56
+
57
+ @abstractmethod
58
+ def calc_averages(self, sens_data: SensorData) -> np.ndarray:
59
+ """Abstract method. Calculates the spatial average for each sensor using
60
+ the specified sensor dimensions and integration method. This is done by
61
+ interpolating the sensor values at each sensors integration points.
62
+
63
+ Parameters
64
+ ----------
65
+ sens_data : SensorData
66
+ Contains the parameters of the sensor array including: positions,
67
+ sample times, spatial averaging and orientations.
68
+
69
+ Returns
70
+ -------
71
+ np.ndarray
72
+ Array of simulated sensor measurements. shape=(num_sensors,
73
+ num_field_components,num_time_steps).
74
+ """
75
+ pass
76
+
77
+ @abstractmethod
78
+ def get_averages(self) -> np.ndarray:
79
+ """Abstract method. Returns the previously calculated spatial averages
80
+ for each sensor. If these have not been calculated then `calc_averages`
81
+ is called and the result is returned.
82
+
83
+ Returns
84
+ -------
85
+ np.ndarray
86
+ Array of simulated sensor measurements. shape=(num_sensors,
87
+ num_field_components,num_time_steps).
88
+ """
89
+ pass
90
+
@@ -0,0 +1,44 @@
1
+ """
2
+ ================================================================================
3
+ pyvale: the python validation engine
4
+ License: MIT
5
+ Copyright (C) 2025 The Computer Aided Validation Team
6
+ ================================================================================
7
+ """
8
+ import enum
9
+
10
+
11
+ class EIntSpatialType(enum.Enum):
12
+ """Enumeration specifying the type of spatial integrator to build. Used for
13
+ spatial averaging for sensors.
14
+
15
+ RECT1PT
16
+ Rectangular 2D integrator splitting the area into 1 part.
17
+
18
+ RECT4PT
19
+ Rectangular 2D integrator splitting the area into 4 equal parts.
20
+
21
+ RECT9PT
22
+ Rectangular 2D integrator splitting the area into 9 equal parts.
23
+
24
+ QUAD4PT
25
+ Gaussian quadrature 2D integrator over 4 points.
26
+
27
+ QUAD9PT
28
+ Gaussia quadrature 2D integrator over 9 points.
29
+ """
30
+
31
+ RECT1PT = enum.auto()
32
+ """Rectangular 2D integrator splitting the area into 1 part."""
33
+
34
+ RECT4PT = enum.auto()
35
+ """Rectangular 2D integrator splitting the area into 4 equal parts."""
36
+
37
+ RECT9PT = enum.auto()
38
+ """Rectangular 2D integrator splitting the area into 9 equal parts."""
39
+
40
+ QUAD4PT = enum.auto()
41
+ """Gaussian quadrature 2D integrator over 4 points."""
42
+
43
+ QUAD9PT = enum.auto()
44
+ """Gaussia quadrature 2D integrator over 9 points."""