pyvale 2025.5.3__cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.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.
- pyvale/__init__.py +89 -0
- pyvale/analyticmeshgen.py +102 -0
- pyvale/analyticsimdatafactory.py +91 -0
- pyvale/analyticsimdatagenerator.py +323 -0
- pyvale/blendercalibrationdata.py +15 -0
- pyvale/blenderlightdata.py +26 -0
- pyvale/blendermaterialdata.py +15 -0
- pyvale/blenderrenderdata.py +30 -0
- pyvale/blenderscene.py +488 -0
- pyvale/blendertools.py +420 -0
- pyvale/camera.py +146 -0
- pyvale/cameradata.py +69 -0
- pyvale/cameradata2d.py +84 -0
- pyvale/camerastereo.py +217 -0
- pyvale/cameratools.py +522 -0
- pyvale/cython/rastercyth.c +32211 -0
- pyvale/cython/rastercyth.cpython-311-aarch64-linux-gnu.so +0 -0
- pyvale/cython/rastercyth.py +640 -0
- pyvale/data/__init__.py +5 -0
- pyvale/data/cal_target.tiff +0 -0
- pyvale/data/case00_HEX20_out.e +0 -0
- pyvale/data/case00_HEX27_out.e +0 -0
- pyvale/data/case00_HEX8_out.e +0 -0
- pyvale/data/case00_TET10_out.e +0 -0
- pyvale/data/case00_TET14_out.e +0 -0
- pyvale/data/case00_TET4_out.e +0 -0
- pyvale/data/case13_out.e +0 -0
- pyvale/data/case16_out.e +0 -0
- pyvale/data/case17_out.e +0 -0
- pyvale/data/case18_1_out.e +0 -0
- pyvale/data/case18_2_out.e +0 -0
- pyvale/data/case18_3_out.e +0 -0
- pyvale/data/case25_out.e +0 -0
- pyvale/data/case26_out.e +0 -0
- pyvale/data/optspeckle_2464x2056px_spec5px_8bit_gblur1px.tiff +0 -0
- pyvale/dataset.py +325 -0
- pyvale/errorcalculator.py +109 -0
- pyvale/errordriftcalc.py +146 -0
- pyvale/errorintegrator.py +336 -0
- pyvale/errorrand.py +607 -0
- pyvale/errorsyscalib.py +134 -0
- pyvale/errorsysdep.py +327 -0
- pyvale/errorsysfield.py +414 -0
- pyvale/errorsysindep.py +808 -0
- pyvale/examples/__init__.py +5 -0
- pyvale/examples/basics/ex1_1_basicscalars_therm2d.py +131 -0
- pyvale/examples/basics/ex1_2_sensormodel_therm2d.py +158 -0
- pyvale/examples/basics/ex1_3_customsens_therm3d.py +216 -0
- pyvale/examples/basics/ex1_4_basicerrors_therm3d.py +153 -0
- pyvale/examples/basics/ex1_5_fielderrs_therm3d.py +168 -0
- pyvale/examples/basics/ex1_6_caliberrs_therm2d.py +133 -0
- pyvale/examples/basics/ex1_7_spatavg_therm2d.py +123 -0
- pyvale/examples/basics/ex2_1_basicvectors_disp2d.py +112 -0
- pyvale/examples/basics/ex2_2_vectorsens_disp2d.py +111 -0
- pyvale/examples/basics/ex2_3_sensangle_disp2d.py +139 -0
- pyvale/examples/basics/ex2_4_chainfielderrs_disp2d.py +196 -0
- pyvale/examples/basics/ex2_5_vectorfields3d_disp3d.py +109 -0
- pyvale/examples/basics/ex3_1_basictensors_strain2d.py +114 -0
- pyvale/examples/basics/ex3_2_tensorsens2d_strain2d.py +111 -0
- pyvale/examples/basics/ex3_3_tensorsens3d_strain3d.py +182 -0
- pyvale/examples/basics/ex4_1_expsim2d_thermmech2d.py +171 -0
- pyvale/examples/basics/ex4_2_expsim3d_thermmech3d.py +252 -0
- pyvale/examples/genanalyticdata/ex1_1_scalarvisualisation.py +35 -0
- pyvale/examples/genanalyticdata/ex1_2_scalarcasebuild.py +43 -0
- pyvale/examples/genanalyticdata/ex2_1_analyticsensors.py +80 -0
- pyvale/examples/imagedef2d/ex_imagedef2d_todisk.py +79 -0
- pyvale/examples/renderblender/ex1_1_blenderscene.py +121 -0
- pyvale/examples/renderblender/ex1_2_blenderdeformed.py +119 -0
- pyvale/examples/renderblender/ex2_1_stereoscene.py +128 -0
- pyvale/examples/renderblender/ex2_2_stereodeformed.py +131 -0
- pyvale/examples/renderblender/ex3_1_blendercalibration.py +120 -0
- pyvale/examples/renderrasterisation/ex_rastenp.py +153 -0
- pyvale/examples/renderrasterisation/ex_rastercyth_oneframe.py +218 -0
- pyvale/examples/renderrasterisation/ex_rastercyth_static_cypara.py +187 -0
- pyvale/examples/renderrasterisation/ex_rastercyth_static_pypara.py +190 -0
- pyvale/examples/visualisation/ex1_1_plot_traces.py +102 -0
- pyvale/examples/visualisation/ex2_1_animate_sim.py +89 -0
- pyvale/experimentsimulator.py +175 -0
- pyvale/field.py +128 -0
- pyvale/fieldconverter.py +351 -0
- pyvale/fieldsampler.py +111 -0
- pyvale/fieldscalar.py +166 -0
- pyvale/fieldtensor.py +218 -0
- pyvale/fieldtransform.py +388 -0
- pyvale/fieldvector.py +213 -0
- pyvale/generatorsrandom.py +505 -0
- pyvale/imagedef2d.py +569 -0
- pyvale/integratorfactory.py +240 -0
- pyvale/integratorquadrature.py +217 -0
- pyvale/integratorrectangle.py +165 -0
- pyvale/integratorspatial.py +89 -0
- pyvale/integratortype.py +43 -0
- pyvale/output.py +17 -0
- pyvale/pyvaleexceptions.py +11 -0
- pyvale/raster.py +31 -0
- pyvale/rastercy.py +77 -0
- pyvale/rasternp.py +603 -0
- pyvale/rendermesh.py +147 -0
- pyvale/sensorarray.py +178 -0
- pyvale/sensorarrayfactory.py +196 -0
- pyvale/sensorarraypoint.py +278 -0
- pyvale/sensordata.py +71 -0
- pyvale/sensordescriptor.py +213 -0
- pyvale/sensortools.py +142 -0
- pyvale/simcases/case00_HEX20.i +242 -0
- pyvale/simcases/case00_HEX27.i +242 -0
- pyvale/simcases/case00_HEX8.i +242 -0
- pyvale/simcases/case00_TET10.i +242 -0
- pyvale/simcases/case00_TET14.i +242 -0
- pyvale/simcases/case00_TET4.i +242 -0
- pyvale/simcases/case01.i +101 -0
- pyvale/simcases/case02.i +156 -0
- pyvale/simcases/case03.i +136 -0
- pyvale/simcases/case04.i +181 -0
- pyvale/simcases/case05.i +234 -0
- pyvale/simcases/case06.i +305 -0
- pyvale/simcases/case07.geo +135 -0
- pyvale/simcases/case07.i +87 -0
- pyvale/simcases/case08.geo +144 -0
- pyvale/simcases/case08.i +153 -0
- pyvale/simcases/case09.geo +204 -0
- pyvale/simcases/case09.i +87 -0
- pyvale/simcases/case10.geo +204 -0
- pyvale/simcases/case10.i +257 -0
- pyvale/simcases/case11.geo +337 -0
- pyvale/simcases/case11.i +147 -0
- pyvale/simcases/case12.geo +388 -0
- pyvale/simcases/case12.i +329 -0
- pyvale/simcases/case13.i +140 -0
- pyvale/simcases/case14.i +159 -0
- pyvale/simcases/case15.geo +337 -0
- pyvale/simcases/case15.i +150 -0
- pyvale/simcases/case16.geo +391 -0
- pyvale/simcases/case16.i +357 -0
- pyvale/simcases/case17.geo +135 -0
- pyvale/simcases/case17.i +144 -0
- pyvale/simcases/case18.i +254 -0
- pyvale/simcases/case18_1.i +254 -0
- pyvale/simcases/case18_2.i +254 -0
- pyvale/simcases/case18_3.i +254 -0
- pyvale/simcases/case19.geo +252 -0
- pyvale/simcases/case19.i +99 -0
- pyvale/simcases/case20.geo +252 -0
- pyvale/simcases/case20.i +250 -0
- pyvale/simcases/case21.geo +74 -0
- pyvale/simcases/case21.i +155 -0
- pyvale/simcases/case22.geo +82 -0
- pyvale/simcases/case22.i +140 -0
- pyvale/simcases/case23.geo +164 -0
- pyvale/simcases/case23.i +140 -0
- pyvale/simcases/case24.geo +79 -0
- pyvale/simcases/case24.i +123 -0
- pyvale/simcases/case25.geo +82 -0
- pyvale/simcases/case25.i +140 -0
- pyvale/simcases/case26.geo +166 -0
- pyvale/simcases/case26.i +140 -0
- pyvale/simcases/run_1case.py +61 -0
- pyvale/simcases/run_all_cases.py +69 -0
- pyvale/simcases/run_build_case.py +64 -0
- pyvale/simcases/run_example_cases.py +69 -0
- pyvale/simtools.py +67 -0
- pyvale/visualexpplotter.py +191 -0
- pyvale/visualimagedef.py +74 -0
- pyvale/visualimages.py +76 -0
- pyvale/visualopts.py +493 -0
- pyvale/visualsimanimator.py +111 -0
- pyvale/visualsimsensors.py +318 -0
- pyvale/visualtools.py +136 -0
- pyvale/visualtraceplotter.py +142 -0
- pyvale-2025.5.3.dist-info/METADATA +144 -0
- pyvale-2025.5.3.dist-info/RECORD +175 -0
- pyvale-2025.5.3.dist-info/WHEEL +6 -0
- pyvale-2025.5.3.dist-info/licenses/LICENSE +21 -0
- pyvale-2025.5.3.dist-info/top_level.txt +1 -0
- pyvale.libs/libgomp-d22c30c5.so.1.0.0 +0 -0
|
@@ -0,0 +1,240 @@
|
|
|
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.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,
|
|
14
|
+
create_gauss_weights_2d_4pts,
|
|
15
|
+
create_gauss_weights_2d_9pts)
|
|
16
|
+
|
|
17
|
+
class IntegratorSpatialFactory:
|
|
18
|
+
"""Namespace for static methods used to build 2D spatial integrators. These
|
|
19
|
+
integrators are used to simulate spatial averaging for sensors.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
@staticmethod
|
|
23
|
+
def rect_2d_1pt(field: IField,
|
|
24
|
+
sensor_data: SensorData,
|
|
25
|
+
)-> Rectangle2D:
|
|
26
|
+
"""Builds and returns a 2D rectangular spatial integrator with a single
|
|
27
|
+
integration point.
|
|
28
|
+
|
|
29
|
+
Parameters
|
|
30
|
+
----------
|
|
31
|
+
field : IField
|
|
32
|
+
Interface specifying the physical field that the integrator will
|
|
33
|
+
sample from.
|
|
34
|
+
sensor_data : SensorData
|
|
35
|
+
Sensor data specifying the location of the sensors that will be
|
|
36
|
+
used for the spatial integration
|
|
37
|
+
|
|
38
|
+
Returns
|
|
39
|
+
-------
|
|
40
|
+
Rectangle2D
|
|
41
|
+
Rectangular spatial integrator that can sample the specified
|
|
42
|
+
physical field at specified locations
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
int_pt_offsets = np.array([[0,0,0],])
|
|
46
|
+
|
|
47
|
+
return Rectangle2D(field,
|
|
48
|
+
sensor_data,
|
|
49
|
+
int_pt_offsets)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@staticmethod
|
|
53
|
+
def rect_2d_4pt(field: IField,
|
|
54
|
+
sensor_data: SensorData,
|
|
55
|
+
)-> Rectangle2D:
|
|
56
|
+
"""Builds and returns a 2D rectangular spatial integrator with four
|
|
57
|
+
equally spaced integration points in a grid pattern.
|
|
58
|
+
|
|
59
|
+
Parameters
|
|
60
|
+
----------
|
|
61
|
+
field : IField
|
|
62
|
+
Interface specifying the physical field that the integrator will
|
|
63
|
+
sample from.
|
|
64
|
+
sensor_data : SensorData
|
|
65
|
+
Sensor data specifying the location of the sensors that will be
|
|
66
|
+
used for the spatial integration
|
|
67
|
+
|
|
68
|
+
Returns
|
|
69
|
+
-------
|
|
70
|
+
Rectangle2D
|
|
71
|
+
Rectangular spatial integrator that can sample the specified
|
|
72
|
+
physical field at specified locations
|
|
73
|
+
"""
|
|
74
|
+
int_pt_offsets = sensor_data.spatial_dims * np.array([[-0.5,-0.5,0],
|
|
75
|
+
[-0.5,0.5,0],
|
|
76
|
+
[0.5,-0.5,0],
|
|
77
|
+
[0.5,0.5,0],])
|
|
78
|
+
|
|
79
|
+
return Rectangle2D(field,
|
|
80
|
+
sensor_data,
|
|
81
|
+
int_pt_offsets)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@staticmethod
|
|
86
|
+
def rect_2d_9pt(field: IField,
|
|
87
|
+
sensor_data: SensorData,
|
|
88
|
+
)-> Rectangle2D:
|
|
89
|
+
"""Builds and returns a 2D rectangular spatial integrator with nine
|
|
90
|
+
equally spaced integration points in a grid pattern.
|
|
91
|
+
|
|
92
|
+
Parameters
|
|
93
|
+
----------
|
|
94
|
+
field : IField
|
|
95
|
+
Interface specifying the physical field that the integrator will
|
|
96
|
+
sample from.
|
|
97
|
+
sensor_data : SensorData
|
|
98
|
+
Sensor data specifying the location of the sensors that will be
|
|
99
|
+
used for the spatial integration
|
|
100
|
+
|
|
101
|
+
Returns
|
|
102
|
+
-------
|
|
103
|
+
Rectangle2D
|
|
104
|
+
Rectangular spatial integrator that can sample the specified
|
|
105
|
+
physical field at specified locations
|
|
106
|
+
"""
|
|
107
|
+
int_pt_offsets = sensor_data.spatial_dims * np.array([[-1/3,-1/3,0],
|
|
108
|
+
[-1/3,0,0],
|
|
109
|
+
[-1/3,1/3,0],
|
|
110
|
+
[0,-1/3,0],
|
|
111
|
+
[0,0,0],
|
|
112
|
+
[0,1/3,0],
|
|
113
|
+
[1/3,-1/3,0],
|
|
114
|
+
[1/3,0,0],
|
|
115
|
+
[1/3,1/3,0]])
|
|
116
|
+
|
|
117
|
+
return Rectangle2D(field,
|
|
118
|
+
sensor_data,
|
|
119
|
+
int_pt_offsets)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@staticmethod
|
|
123
|
+
def quad_2d_4pt(field: IField,
|
|
124
|
+
sensor_data: SensorData,
|
|
125
|
+
)-> Quadrature2D:
|
|
126
|
+
"""Builds and returns a Gaussian quadrature spatal integrator based on
|
|
127
|
+
a rectangular area with four integration points.
|
|
128
|
+
|
|
129
|
+
Parameters
|
|
130
|
+
----------
|
|
131
|
+
field : IField
|
|
132
|
+
Interface specifying the physical field that the integrator will
|
|
133
|
+
sample from.
|
|
134
|
+
sensor_data : SensorData
|
|
135
|
+
Sensor data specifying the location of the sensors that will be
|
|
136
|
+
used for the spatial integration
|
|
137
|
+
|
|
138
|
+
Returns
|
|
139
|
+
-------
|
|
140
|
+
Quadrature2D
|
|
141
|
+
Quadrature integrator that can be used to sample the physical field
|
|
142
|
+
at specified locations.
|
|
143
|
+
"""
|
|
144
|
+
gauss_pt_offsets = (sensor_data.spatial_dims * 1/np.sqrt(3)
|
|
145
|
+
* np.array([[-1,-1,0],
|
|
146
|
+
[-1,1,0],
|
|
147
|
+
[1,-1,0],
|
|
148
|
+
[1,1,0]]))
|
|
149
|
+
|
|
150
|
+
gauss_weight_func = create_gauss_weights_2d_4pts
|
|
151
|
+
|
|
152
|
+
return Quadrature2D(field,
|
|
153
|
+
sensor_data,
|
|
154
|
+
gauss_pt_offsets,
|
|
155
|
+
gauss_weight_func)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
@staticmethod
|
|
159
|
+
def quad_2d_9pt(field: IField,
|
|
160
|
+
sensor_data: SensorData,
|
|
161
|
+
)-> Quadrature2D:
|
|
162
|
+
"""Builds and returns a Gaussian quadrature spatal integrator based on
|
|
163
|
+
a rectangular area with nine integration points.
|
|
164
|
+
|
|
165
|
+
Parameters
|
|
166
|
+
----------
|
|
167
|
+
field : IField
|
|
168
|
+
Interface specifying the physical field that the integrator will
|
|
169
|
+
sample from.
|
|
170
|
+
sensor_data : SensorData
|
|
171
|
+
Sensor data specifying the location of the sensors that will be
|
|
172
|
+
used for the spatial integration
|
|
173
|
+
|
|
174
|
+
Returns
|
|
175
|
+
-------
|
|
176
|
+
Quadrature2D
|
|
177
|
+
Quadrature integrator that can be used to sample the physical field
|
|
178
|
+
at specified locations.
|
|
179
|
+
"""
|
|
180
|
+
gauss_pt_offsets = (sensor_data.spatial_dims
|
|
181
|
+
* np.array([[-np.sqrt(0.6),-np.sqrt(0.6),0],
|
|
182
|
+
[-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),0,0],
|
|
186
|
+
[0,-np.sqrt(0.6),0],
|
|
187
|
+
[0,np.sqrt(0.6),0],
|
|
188
|
+
[np.sqrt(0.6),0,0],
|
|
189
|
+
[0,0,0]]))
|
|
190
|
+
|
|
191
|
+
gauss_weight_func = create_gauss_weights_2d_9pts
|
|
192
|
+
|
|
193
|
+
return Quadrature2D(field,
|
|
194
|
+
sensor_data,
|
|
195
|
+
gauss_pt_offsets,
|
|
196
|
+
gauss_weight_func)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def build_spatial_averager(field: IField, sensor_data: SensorData,
|
|
200
|
+
) -> IIntegratorSpatial | None:
|
|
201
|
+
"""Helper function to build a spatial integrator based on the
|
|
202
|
+
"EIntSpatialType" enumeration in the SensorData object. Separates the
|
|
203
|
+
spatial integration object from the SensorData object.
|
|
204
|
+
|
|
205
|
+
Parameters
|
|
206
|
+
----------
|
|
207
|
+
field : IField
|
|
208
|
+
Physical field that will be sampled by the spatial averager.
|
|
209
|
+
sensor_data : SensorData
|
|
210
|
+
Sensor data containing the type of spatial integrator to build in the
|
|
211
|
+
`sensor_data.spatial_averager` as an `EIntSpatialType` enumeration.
|
|
212
|
+
|
|
213
|
+
Returns
|
|
214
|
+
-------
|
|
215
|
+
IIntegratorSpatial | None
|
|
216
|
+
The spatial averager that will sample the physical field at the
|
|
217
|
+
specified sensor locations. If `sensor_data.spatial_averager` or
|
|
218
|
+
`sensor_data.spatial_dims` are None then it is not possible to build a
|
|
219
|
+
spatial integrator and None is returned.
|
|
220
|
+
"""
|
|
221
|
+
if sensor_data.spatial_averager is None or sensor_data.spatial_dims is None:
|
|
222
|
+
return None
|
|
223
|
+
|
|
224
|
+
if sensor_data.spatial_averager == EIntSpatialType.RECT1PT:
|
|
225
|
+
return IntegratorSpatialFactory.rect_2d_1pt(field,
|
|
226
|
+
sensor_data)
|
|
227
|
+
elif sensor_data.spatial_averager == EIntSpatialType.RECT4PT:
|
|
228
|
+
return IntegratorSpatialFactory.rect_2d_4pt(field,
|
|
229
|
+
sensor_data)
|
|
230
|
+
elif sensor_data.spatial_averager == EIntSpatialType.RECT9PT:
|
|
231
|
+
return IntegratorSpatialFactory.rect_2d_9pt(field,
|
|
232
|
+
sensor_data)
|
|
233
|
+
elif sensor_data.spatial_averager == EIntSpatialType.QUAD4PT:
|
|
234
|
+
return IntegratorSpatialFactory.quad_2d_4pt(field,
|
|
235
|
+
sensor_data)
|
|
236
|
+
elif sensor_data.spatial_averager == EIntSpatialType.QUAD9PT:
|
|
237
|
+
return IntegratorSpatialFactory.quad_2d_9pt(field,
|
|
238
|
+
sensor_data)
|
|
239
|
+
else:
|
|
240
|
+
return None
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
# ==============================================================================
|
|
2
|
+
# pyvale: the python validation engine
|
|
3
|
+
# License: MIT
|
|
4
|
+
# Copyright (C) 2025 The Computer Aided Validation Team
|
|
5
|
+
# ==============================================================================
|
|
6
|
+
|
|
7
|
+
from typing import Callable
|
|
8
|
+
import numpy as np
|
|
9
|
+
from pyvale.field import IField
|
|
10
|
+
from pyvale.integratorspatial import (IIntegratorSpatial,
|
|
11
|
+
create_int_pt_array)
|
|
12
|
+
from pyvale.sensordata import SensorData
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Quadrature2D(IIntegratorSpatial):
|
|
16
|
+
"""Gaussian quadrature numerical integrator for spatial averaging in 2D.
|
|
17
|
+
Used to model spatial averaging of sensors over a rectangular area which is
|
|
18
|
+
specified in the SensorData object. Handles sampling of the physical field
|
|
19
|
+
at the integration points and averages them back to a single value per
|
|
20
|
+
sensor location as specified in the SensorData object.
|
|
21
|
+
|
|
22
|
+
Implements the `IIntegratorSpatial` interface allowing for interoperability
|
|
23
|
+
of different spatial integration algorithms for modelling sensor averaging.
|
|
24
|
+
"""
|
|
25
|
+
__slots__ = ("_field","_area","_gauss_pts_num","_gauss_pt_offsets"
|
|
26
|
+
,"_gauss_weight_func","_gauss_pts","_averages","_sens_data")
|
|
27
|
+
|
|
28
|
+
def __init__(self,
|
|
29
|
+
field: IField,
|
|
30
|
+
sens_data: SensorData,
|
|
31
|
+
gauss_pt_offsets: np.ndarray,
|
|
32
|
+
gauss_weight_func: Callable) -> 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
|
+
gauss_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
|
+
gauss_weight_func : Callable
|
|
47
|
+
A function that takes the shape of the measurement array as a tuple
|
|
48
|
+
and returns a numpy array of weights for the gaussian integration
|
|
49
|
+
points. The function must return an array with shape=(n_gauss_pts,)
|
|
50
|
+
+meas_shape where meas_shape=(num_sensors,num_field_components,
|
|
51
|
+
num_time_steps)
|
|
52
|
+
"""
|
|
53
|
+
self._field = field
|
|
54
|
+
self._sens_data = sens_data
|
|
55
|
+
self._area = self._sens_data.spatial_dims[0] * \
|
|
56
|
+
self._sens_data.spatial_dims[1]
|
|
57
|
+
|
|
58
|
+
self._gauss_pts_num = gauss_pt_offsets.shape[0]
|
|
59
|
+
self._gauss_pt_offsets = gauss_pt_offsets
|
|
60
|
+
self._gauss_weight_func = gauss_weight_func
|
|
61
|
+
|
|
62
|
+
self._gauss_pts = create_int_pt_array(self._sens_data,
|
|
63
|
+
self._gauss_pt_offsets)
|
|
64
|
+
self._averages = None
|
|
65
|
+
|
|
66
|
+
def calc_integrals(self, sens_data: SensorData | None = None) -> np.ndarray:
|
|
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).
|
|
70
|
+
|
|
71
|
+
Parameters
|
|
72
|
+
----------
|
|
73
|
+
sens_data : SensorData | None, optional
|
|
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.
|
|
79
|
+
|
|
80
|
+
Returns
|
|
81
|
+
-------
|
|
82
|
+
np.ndarray
|
|
83
|
+
Array of virtual sensor integrals with shape=(n_sensors,n_comps,
|
|
84
|
+
n_timsteps). Note this is consistent with pyvales measurement array.
|
|
85
|
+
"""
|
|
86
|
+
self._averages = self.calc_averages(sens_data)
|
|
87
|
+
return self._area*self.get_averages()
|
|
88
|
+
|
|
89
|
+
def get_integrals(self) -> np.ndarray:
|
|
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.
|
|
94
|
+
|
|
95
|
+
Returns
|
|
96
|
+
-------
|
|
97
|
+
np.ndarray
|
|
98
|
+
Array of virtual sensor averages with shape=(n_sensors,n_comps,
|
|
99
|
+
n_timsteps). Note this is consistent with pyvales measurement array.
|
|
100
|
+
"""
|
|
101
|
+
return self._area*self.get_averages()
|
|
102
|
+
|
|
103
|
+
def calc_averages(self, sens_data: SensorData | None = None) -> np.ndarray:
|
|
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).
|
|
107
|
+
|
|
108
|
+
Parameters
|
|
109
|
+
----------
|
|
110
|
+
sens_data : SensorData | None, optional
|
|
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.
|
|
116
|
+
|
|
117
|
+
Returns
|
|
118
|
+
-------
|
|
119
|
+
np.ndarray
|
|
120
|
+
Array of virtual sensor averages with shape=(n_sensors,n_comps,
|
|
121
|
+
n_timsteps). Note this is consistent with pyvales measurement array.
|
|
122
|
+
"""
|
|
123
|
+
if sens_data is not None:
|
|
124
|
+
self._sens_data = sens_data
|
|
125
|
+
|
|
126
|
+
# shape=(n_sens*n_gauss_pts,n_dims)
|
|
127
|
+
self._gauss_pts = create_int_pt_array(self._sens_data,
|
|
128
|
+
self._gauss_pt_offsets)
|
|
129
|
+
|
|
130
|
+
# shape=(n_gauss_pts*n_sens,n_comps,n_timesteps)
|
|
131
|
+
gauss_vals = self._field.sample_field(self._gauss_pts,
|
|
132
|
+
self._sens_data.sample_times,
|
|
133
|
+
self._sens_data.angles)
|
|
134
|
+
|
|
135
|
+
meas_shape = (self._sens_data.positions.shape[0],
|
|
136
|
+
gauss_vals.shape[1],
|
|
137
|
+
gauss_vals.shape[2])
|
|
138
|
+
|
|
139
|
+
# shape=(n_gauss_pts,n_sens,n_comps,n_timesteps)
|
|
140
|
+
gauss_vals = gauss_vals.reshape((self._gauss_pts_num,)+meas_shape,
|
|
141
|
+
order='F')
|
|
142
|
+
|
|
143
|
+
# shape=(n_gauss_pts,n_sens,n_comps,n_timesteps)
|
|
144
|
+
gauss_weights = self._gauss_weight_func(meas_shape)
|
|
145
|
+
|
|
146
|
+
# NOTE: coeff comes from changing gauss interval from [-1,1] to [a,b] -
|
|
147
|
+
# so (a-b)/2 * (a-b)/2 = sensor_area / 4, then need to divide by the
|
|
148
|
+
# integration area to convert to an average:
|
|
149
|
+
# integrals = self._area/4 * np.sum(gauss_weights*gauss_vals,axis=0)
|
|
150
|
+
# self._averages = (1/self._area)*integrals
|
|
151
|
+
|
|
152
|
+
# shape=(n_sensors,n_comps,n_timsteps)=meas_shape
|
|
153
|
+
self._averages = 1/4 * np.sum(gauss_weights*gauss_vals,axis=0)
|
|
154
|
+
return self._averages
|
|
155
|
+
|
|
156
|
+
def get_averages(self) -> np.ndarray:
|
|
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.
|
|
161
|
+
|
|
162
|
+
Returns
|
|
163
|
+
-------
|
|
164
|
+
np.ndarray
|
|
165
|
+
Array of virtual sensor averages with shape=(n_sensors,n_comps,
|
|
166
|
+
n_timsteps). Note this is consistent with pyvales measurement array.
|
|
167
|
+
"""
|
|
168
|
+
if self._averages is None:
|
|
169
|
+
self._averages = self.calc_averages()
|
|
170
|
+
|
|
171
|
+
return self._averages
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def create_gauss_weights_2d_4pts(meas_shape: tuple[int,int,int]) -> np.ndarray:
|
|
175
|
+
"""Helper function that creates an array of weights for gaussian quadrature
|
|
176
|
+
integration. This function provides the weights for 2D integrator with 4
|
|
177
|
+
integration points.
|
|
178
|
+
|
|
179
|
+
Parameters
|
|
180
|
+
----------
|
|
181
|
+
meas_shape : tuple[int,int,int]
|
|
182
|
+
Shape of the measurement array, shape=(n_sensors,n_field_comps,
|
|
183
|
+
n_time_steps).
|
|
184
|
+
|
|
185
|
+
Returns
|
|
186
|
+
-------
|
|
187
|
+
np.ndarray
|
|
188
|
+
Array of gaussian quadrature weights with shape = (n_gauss_pts,n_sensors
|
|
189
|
+
,n_field_comps,n_time_steps).
|
|
190
|
+
"""
|
|
191
|
+
#shape=(4,)+meas_shape
|
|
192
|
+
return np.ones((4,)+meas_shape)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def create_gauss_weights_2d_9pts(meas_shape: tuple[int,int,int]) -> np.ndarray:
|
|
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.
|
|
199
|
+
|
|
200
|
+
Parameters
|
|
201
|
+
----------
|
|
202
|
+
meas_shape : tuple[int,int,int]
|
|
203
|
+
Shape of the measurement array, shape=(n_sensors,n_field_comps,
|
|
204
|
+
n_time_steps).
|
|
205
|
+
|
|
206
|
+
Returns
|
|
207
|
+
-------
|
|
208
|
+
np.ndarray
|
|
209
|
+
Array of gaussian quadrature weights with shape = (n_gauss_pts,n_sensors
|
|
210
|
+
,n_field_comps,n_time_steps).
|
|
211
|
+
"""
|
|
212
|
+
# shape=(9,)+meas_shape
|
|
213
|
+
gauss_weights = np.vstack((25/81 * np.ones((4,)+meas_shape),
|
|
214
|
+
40/81 * np.ones((4,)+meas_shape),
|
|
215
|
+
64/81 * np.ones((1,)+meas_shape)))
|
|
216
|
+
return gauss_weights
|
|
217
|
+
|
|
@@ -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
|
+
|