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.
- pyvale/__init__.py +75 -0
- pyvale/core/__init__.py +7 -0
- pyvale/core/analyticmeshgen.py +59 -0
- pyvale/core/analyticsimdatafactory.py +63 -0
- pyvale/core/analyticsimdatagenerator.py +160 -0
- pyvale/core/camera.py +146 -0
- pyvale/core/cameradata.py +64 -0
- pyvale/core/cameradata2d.py +82 -0
- pyvale/core/cameratools.py +328 -0
- pyvale/core/cython/rastercyth.c +32267 -0
- pyvale/core/cython/rastercyth.py +636 -0
- pyvale/core/dataset.py +250 -0
- pyvale/core/errorcalculator.py +112 -0
- pyvale/core/errordriftcalc.py +146 -0
- pyvale/core/errorintegrator.py +339 -0
- pyvale/core/errorrand.py +614 -0
- pyvale/core/errorsysdep.py +331 -0
- pyvale/core/errorsysfield.py +407 -0
- pyvale/core/errorsysindep.py +905 -0
- pyvale/core/experimentsimulator.py +99 -0
- pyvale/core/field.py +136 -0
- pyvale/core/fieldconverter.py +154 -0
- pyvale/core/fieldsampler.py +112 -0
- pyvale/core/fieldscalar.py +167 -0
- pyvale/core/fieldtensor.py +221 -0
- pyvale/core/fieldtransform.py +384 -0
- pyvale/core/fieldvector.py +215 -0
- pyvale/core/generatorsrandom.py +528 -0
- pyvale/core/imagedef2d.py +566 -0
- pyvale/core/integratorfactory.py +241 -0
- pyvale/core/integratorquadrature.py +192 -0
- pyvale/core/integratorrectangle.py +88 -0
- pyvale/core/integratorspatial.py +90 -0
- pyvale/core/integratortype.py +44 -0
- pyvale/core/optimcheckfuncs.py +153 -0
- pyvale/core/raster.py +31 -0
- pyvale/core/rastercy.py +76 -0
- pyvale/core/rasternp.py +604 -0
- pyvale/core/rendermesh.py +156 -0
- pyvale/core/sensorarray.py +179 -0
- pyvale/core/sensorarrayfactory.py +210 -0
- pyvale/core/sensorarraypoint.py +280 -0
- pyvale/core/sensordata.py +72 -0
- pyvale/core/sensordescriptor.py +101 -0
- pyvale/core/sensortools.py +143 -0
- pyvale/core/visualexpplotter.py +151 -0
- pyvale/core/visualimagedef.py +71 -0
- pyvale/core/visualimages.py +75 -0
- pyvale/core/visualopts.py +180 -0
- pyvale/core/visualsimanimator.py +83 -0
- pyvale/core/visualsimplotter.py +182 -0
- pyvale/core/visualtools.py +81 -0
- pyvale/core/visualtraceplotter.py +256 -0
- pyvale/data/__init__.py +7 -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/examples/__init__.py +7 -0
- pyvale/examples/analyticdatagen/__init__.py +7 -0
- pyvale/examples/analyticdatagen/ex1_1_scalarvisualisation.py +38 -0
- pyvale/examples/analyticdatagen/ex1_2_scalarcasebuild.py +46 -0
- pyvale/examples/analyticdatagen/ex2_1_analyticsensors.py +83 -0
- pyvale/examples/ex1_1_thermal2d.py +89 -0
- pyvale/examples/ex1_2_thermal2d.py +111 -0
- pyvale/examples/ex1_3_thermal2d.py +113 -0
- pyvale/examples/ex1_4_thermal2d.py +89 -0
- pyvale/examples/ex1_5_thermal2d.py +105 -0
- pyvale/examples/ex2_1_thermal3d .py +87 -0
- pyvale/examples/ex2_2_thermal3d.py +51 -0
- pyvale/examples/ex2_3_thermal3d.py +109 -0
- pyvale/examples/ex3_1_displacement2d.py +47 -0
- pyvale/examples/ex3_2_displacement2d.py +79 -0
- pyvale/examples/ex3_3_displacement2d.py +104 -0
- pyvale/examples/ex3_4_displacement2d.py +105 -0
- pyvale/examples/ex4_1_strain2d.py +57 -0
- pyvale/examples/ex4_2_strain2d.py +79 -0
- pyvale/examples/ex4_3_strain2d.py +100 -0
- pyvale/examples/ex5_1_multiphysics2d.py +78 -0
- pyvale/examples/ex6_1_multiphysics2d_expsim.py +118 -0
- pyvale/examples/ex6_2_multiphysics3d_expsim.py +158 -0
- pyvale/examples/features/__init__.py +7 -0
- pyvale/examples/features/ex_animation_tools_3dmonoblock.py +83 -0
- pyvale/examples/features/ex_area_avg.py +89 -0
- pyvale/examples/features/ex_calibration_error.py +108 -0
- pyvale/examples/features/ex_chain_field_errs.py +141 -0
- pyvale/examples/features/ex_field_errs.py +78 -0
- pyvale/examples/features/ex_sensor_single_angle_batch.py +110 -0
- pyvale/examples/imagedef2d/ex_imagedef2d_todisk.py +86 -0
- pyvale/examples/rasterisation/ex_rastenp.py +154 -0
- pyvale/examples/rasterisation/ex_rastercyth_oneframe.py +220 -0
- pyvale/examples/rasterisation/ex_rastercyth_static_cypara.py +194 -0
- pyvale/examples/rasterisation/ex_rastercyth_static_pypara.py +193 -0
- pyvale/simcases/case00_HEX20.i +242 -0
- pyvale/simcases/case00_HEX27.i +242 -0
- pyvale/simcases/case00_TET10.i +242 -0
- pyvale/simcases/case00_TET14.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-2025.4.0.dist-info/METADATA +140 -0
- pyvale-2025.4.0.dist-info/RECORD +157 -0
- pyvale-2025.4.0.dist-info/WHEEL +5 -0
- pyvale-2025.4.0.dist-info/licenses/LICENSE +21 -0
- 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."""
|