pyvale 2025.5.3__cp311-cp311-macosx_13_0_x86_64.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/.dylibs/libomp.dylib +0 -0
- 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-darwin.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/rendermesh.py
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# ==============================================================================
|
|
2
|
+
# pyvale: the python validation engine
|
|
3
|
+
# License: MIT
|
|
4
|
+
# Copyright (C) 2025 The Computer Aided Validation Team
|
|
5
|
+
# ==============================================================================
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
NOTE: this module is a feature under developement.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from enum import Enum
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
import numpy as np
|
|
14
|
+
import mooseherder as mh
|
|
15
|
+
from pyvale.fieldconverter import simdata_to_pyvista
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass(slots=True)
|
|
19
|
+
class RenderMeshData:
|
|
20
|
+
coords: np.ndarray
|
|
21
|
+
connectivity: np.ndarray
|
|
22
|
+
fields_render: np.ndarray
|
|
23
|
+
|
|
24
|
+
# If this is None then the mesh is not deformable
|
|
25
|
+
fields_disp: np.ndarray | None = None
|
|
26
|
+
|
|
27
|
+
node_count: int = field(init=False)
|
|
28
|
+
elem_count: int = field(init=False)
|
|
29
|
+
nodes_per_elem: int = field(init=False)
|
|
30
|
+
|
|
31
|
+
coord_cent: np.ndarray = field(init=False)
|
|
32
|
+
coord_bound_min: np.ndarray = field(init=False)
|
|
33
|
+
coord_bound_max: np.ndarray = field(init=False)
|
|
34
|
+
|
|
35
|
+
def __post_init__(self) -> None:
|
|
36
|
+
# C format: num_nodes/num_elems first as it is the largest dimension
|
|
37
|
+
self.node_count = self.coords.shape[0]
|
|
38
|
+
self.elem_count = self.connectivity.shape[0]
|
|
39
|
+
self.nodes_per_elem = self.connectivity.shape[1]
|
|
40
|
+
|
|
41
|
+
self.coord_bound_min = np.min(self.coords,axis=0)
|
|
42
|
+
self.coord_bound_max = np.max(self.coords,axis=0)
|
|
43
|
+
self.coord_cent = (self.coord_bound_max + self.coord_bound_min)/2.0
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def create_render_mesh(sim_data: mh.SimData,
|
|
47
|
+
field_render_keys: tuple[str,...],
|
|
48
|
+
sim_spat_dim: int,
|
|
49
|
+
field_disp_keys: tuple[str,...] | None = None,
|
|
50
|
+
) -> RenderMeshData:
|
|
51
|
+
|
|
52
|
+
extract_keys = field_render_keys
|
|
53
|
+
if field_disp_keys is not None:
|
|
54
|
+
extract_keys = field_render_keys+field_disp_keys
|
|
55
|
+
|
|
56
|
+
(pv_grid,_) = simdata_to_pyvista(sim_data,
|
|
57
|
+
extract_keys,
|
|
58
|
+
elem_dims=sim_spat_dim)
|
|
59
|
+
|
|
60
|
+
pv_surf = pv_grid.extract_surface()
|
|
61
|
+
faces = np.array(pv_surf.faces)
|
|
62
|
+
|
|
63
|
+
first_elem_nodes_per_face = faces[0]
|
|
64
|
+
nodes_per_face_vec = faces[0::(first_elem_nodes_per_face+1)]
|
|
65
|
+
|
|
66
|
+
# TODO: CHECKS
|
|
67
|
+
# - Number of displacement keys match the spat_dim parameter
|
|
68
|
+
assert np.all(nodes_per_face_vec == first_elem_nodes_per_face), \
|
|
69
|
+
"Not all elements in the simdata object have the same number of nodes per element"
|
|
70
|
+
|
|
71
|
+
nodes_per_face = first_elem_nodes_per_face
|
|
72
|
+
num_faces = int(faces.shape[0] / (nodes_per_face+1))
|
|
73
|
+
|
|
74
|
+
# Reshape the faces table and slice off the first column which is just the
|
|
75
|
+
# number of nodes per element and should be the same for all elements
|
|
76
|
+
connectivity = np.reshape(faces,(num_faces,nodes_per_face+1))
|
|
77
|
+
# shape=(num_elems,nodes_per_elem), C format
|
|
78
|
+
connectivity = np.ascontiguousarray(connectivity[:,1:],dtype=np.uintp)
|
|
79
|
+
|
|
80
|
+
# shape=(num_nodes,3), C format
|
|
81
|
+
coords_world = np.array(pv_surf.points)
|
|
82
|
+
|
|
83
|
+
# Add w coord=1, shape=(num_nodes,3+1)
|
|
84
|
+
coords_world= np.hstack((coords_world,np.ones([coords_world.shape[0],1])))
|
|
85
|
+
|
|
86
|
+
# shape=(num_nodes,num_time_steps,num_components)
|
|
87
|
+
field_render_shape = np.array(pv_surf[field_render_keys[0]]).shape
|
|
88
|
+
fields_render_by_node = np.zeros(field_render_shape+(len(field_render_keys),),
|
|
89
|
+
dtype=np.float64)
|
|
90
|
+
for ii,cc in enumerate(field_render_keys):
|
|
91
|
+
fields_render_by_node[:,:,ii] = np.ascontiguousarray(
|
|
92
|
+
np.array(pv_surf[cc]))
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
field_disp_by_node = None
|
|
96
|
+
if field_disp_keys is not None:
|
|
97
|
+
field_disp_shape = np.array(pv_surf[field_disp_keys[0]]).shape
|
|
98
|
+
# shape=(num_nodes,num_time_steps,num_components)
|
|
99
|
+
field_disp_by_node = np.zeros(field_disp_shape+(len(field_disp_keys),),
|
|
100
|
+
dtype=np.float64)
|
|
101
|
+
for ii,cc in enumerate(field_disp_keys):
|
|
102
|
+
field_disp_by_node[:,:,ii] = np.ascontiguousarray(
|
|
103
|
+
np.array(pv_surf[cc]))
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
return RenderMeshData(coords=coords_world,
|
|
108
|
+
connectivity=connectivity,
|
|
109
|
+
fields_render=fields_render_by_node,
|
|
110
|
+
fields_disp=field_disp_by_node)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def slice_mesh_data_by_elem(coords_world: np.ndarray,
|
|
114
|
+
connectivity: np.ndarray,
|
|
115
|
+
field_by_node: np.ndarray,
|
|
116
|
+
) -> tuple[np.ndarray,np.ndarray]:
|
|
117
|
+
"""_summary_
|
|
118
|
+
|
|
119
|
+
Parameters
|
|
120
|
+
----------
|
|
121
|
+
coords_world : np.ndarray
|
|
122
|
+
_description_
|
|
123
|
+
connectivity : np.ndarray
|
|
124
|
+
_description_
|
|
125
|
+
field_by_node : np.ndarray
|
|
126
|
+
_description_
|
|
127
|
+
|
|
128
|
+
Returns
|
|
129
|
+
-------
|
|
130
|
+
tuple[np.ndarray,np.ndarray]
|
|
131
|
+
_description_
|
|
132
|
+
"""
|
|
133
|
+
# shape=(coord[X,Y,Z,W],node_per_elem,elem_num)
|
|
134
|
+
elem_world_coords = np.copy(coords_world[connectivity,:])
|
|
135
|
+
|
|
136
|
+
# shape=(elem_num,nodes_per_elem,coord[X,Y,Z,W]), C memory format
|
|
137
|
+
# elem_world_coords = np.ascontiguousarray(np.swapaxes(elem_world_coords,0,2))
|
|
138
|
+
elem_world_coords = np.ascontiguousarray(elem_world_coords)
|
|
139
|
+
|
|
140
|
+
# shape=(nodes_per_elem,elem_num,time_steps)
|
|
141
|
+
field_by_elem = np.copy(field_by_node[connectivity,:])
|
|
142
|
+
|
|
143
|
+
# shape=(elem_num,nodes_per_elem,time_steps), C memory format
|
|
144
|
+
# field_by_elem = np.ascontiguousarray(np.swapaxes(field_by_elem,0,1))
|
|
145
|
+
field_by_elem = np.ascontiguousarray(field_by_elem)
|
|
146
|
+
|
|
147
|
+
return (elem_world_coords,field_by_elem)
|
pyvale/sensorarray.py
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# ==============================================================================
|
|
2
|
+
# pyvale: the python validation engine
|
|
3
|
+
# License: MIT
|
|
4
|
+
# Copyright (C) 2025 The Computer Aided Validation Team
|
|
5
|
+
# ==============================================================================
|
|
6
|
+
|
|
7
|
+
from abc import ABC, abstractmethod
|
|
8
|
+
import numpy as np
|
|
9
|
+
from pyvale.field import IField
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ISensorArray(ABC):
|
|
13
|
+
"""Interface (abstract base class) for an array of sensors of the same
|
|
14
|
+
type sampling a given physical field.
|
|
15
|
+
|
|
16
|
+
This class implements the `pyvale` sensor measurement simulation model. Here
|
|
17
|
+
a measurement is taken as: measurement = truth + random errors + systematic
|
|
18
|
+
errors. The truth value for each sensor is interpolated from the physical
|
|
19
|
+
field (an implementation of the `IField` interface, nominally a
|
|
20
|
+
`FieldScalar`, `FieldVector` or `FieldTensor` object).
|
|
21
|
+
|
|
22
|
+
The random and systematic errors are calculated by a user specified error
|
|
23
|
+
integrator (`ErrIntegrator` class). This class contains a chain of different
|
|
24
|
+
types of user selected errors (implementations of the `IErrCalculator`
|
|
25
|
+
interface). Further information can be found in the `ErrIntegrator` class
|
|
26
|
+
and in implementations of the `IErrCalculator` interface.
|
|
27
|
+
|
|
28
|
+
In `pyvale`, function and methods with `calc` in their name will cause
|
|
29
|
+
probability distributions to be resampled and any additional calculations,
|
|
30
|
+
such as interpolation, to be performed. Functions and methods with `get` in
|
|
31
|
+
the name will directly return the previously calculated values without
|
|
32
|
+
resampling probability distributions.
|
|
33
|
+
|
|
34
|
+
Calling the class method `calc_measurements()` will create and return an
|
|
35
|
+
array of simulated sensor measurements with the following shape=(num_sensors
|
|
36
|
+
,num_field_component,num_time_steps). When calling `calc_measurements()` all
|
|
37
|
+
sensor errors that are based on probability distributions are resampled and
|
|
38
|
+
any required interpolations are performed (e.g. a random perturbation of the
|
|
39
|
+
sensor positions requiring interpolation at the perturbed sensor location).
|
|
40
|
+
|
|
41
|
+
Calling the class method `get_measurements()` just returns the previously
|
|
42
|
+
calculated set of sensor measurements without resampling of probability.
|
|
43
|
+
Distributions.
|
|
44
|
+
|
|
45
|
+
Without an error integrator this class can be used for interpolating
|
|
46
|
+
simulated physical fields quickly using finite element shape functions.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
@abstractmethod
|
|
50
|
+
def get_measurement_shape(self) -> tuple[int,int,int]:
|
|
51
|
+
"""Abstract method. Gets the shape of the measurement array:
|
|
52
|
+
shape=(num_sensors,num_field_components,num_time_steps).
|
|
53
|
+
|
|
54
|
+
The number of sensors is specified by the user with a SensorData object.
|
|
55
|
+
The number of field components is dependent on the field being sampled
|
|
56
|
+
(i.e. 1 for a scalar field and 3 for a vector field in 3D). The number
|
|
57
|
+
of time steps is specified by the user in the SensorData object or
|
|
58
|
+
defaults to the time steps taken from the simulation.
|
|
59
|
+
|
|
60
|
+
Returns
|
|
61
|
+
-------
|
|
62
|
+
tuple[int,int,int]
|
|
63
|
+
Shape of the measurement array as (num_sensors,
|
|
64
|
+
num_field_components,num_time_steps)
|
|
65
|
+
"""
|
|
66
|
+
pass
|
|
67
|
+
|
|
68
|
+
@abstractmethod
|
|
69
|
+
def get_field(self) -> IField:
|
|
70
|
+
"""Abstract method. Gets the field object that this array of sensors is
|
|
71
|
+
sampling to simulate measurements.
|
|
72
|
+
|
|
73
|
+
Returns
|
|
74
|
+
-------
|
|
75
|
+
IField
|
|
76
|
+
A field object interface.
|
|
77
|
+
"""
|
|
78
|
+
pass
|
|
79
|
+
|
|
80
|
+
@abstractmethod
|
|
81
|
+
def get_truth(self) -> np.ndarray:
|
|
82
|
+
"""Abstract method. Gets the ground truth sensor values that were
|
|
83
|
+
calculated previously. If the ground truth values have not been
|
|
84
|
+
calculated then `calc_truth_values()` is called first.
|
|
85
|
+
|
|
86
|
+
Returns
|
|
87
|
+
-------
|
|
88
|
+
np.ndarray
|
|
89
|
+
Array of ground truth sensor values. shape=(num_sensors,
|
|
90
|
+
num_field_components,num_time_steps).
|
|
91
|
+
"""
|
|
92
|
+
pass
|
|
93
|
+
|
|
94
|
+
@abstractmethod
|
|
95
|
+
def get_errors_systematic(self) -> np.ndarray | None:
|
|
96
|
+
"""Abstract method. Gets the systematic error array from the previously
|
|
97
|
+
calculated sensor measurements. Returns None is no error integrator has
|
|
98
|
+
been specified.
|
|
99
|
+
|
|
100
|
+
Returns
|
|
101
|
+
-------
|
|
102
|
+
np.ndarray | None
|
|
103
|
+
Array of systematic errors for this sensor array. shape=(num_sensors
|
|
104
|
+
,num_field_components,num_time_steps). Returns None if no error
|
|
105
|
+
integrator has been set.
|
|
106
|
+
"""
|
|
107
|
+
pass
|
|
108
|
+
|
|
109
|
+
@abstractmethod
|
|
110
|
+
def get_errors_random(self) -> np.ndarray | None:
|
|
111
|
+
"""Abstract method. Gets the random error array from the previously
|
|
112
|
+
calculated sensor measurements. Returns None is no error integrator has
|
|
113
|
+
been specified.
|
|
114
|
+
|
|
115
|
+
Returns
|
|
116
|
+
-------
|
|
117
|
+
np.ndarray | None
|
|
118
|
+
Array of random errors for this sensor array. shape=(num_sensors
|
|
119
|
+
,num_field_components,num_time_steps). Returns None if no error
|
|
120
|
+
integrator has been set.
|
|
121
|
+
"""
|
|
122
|
+
pass
|
|
123
|
+
|
|
124
|
+
@abstractmethod
|
|
125
|
+
def get_errors_total(self) -> np.ndarray | None:
|
|
126
|
+
"""Abstract method. Gets the total error array from the previously
|
|
127
|
+
calculated sensor measurements. Returns None is no error integrator has
|
|
128
|
+
been specified.
|
|
129
|
+
|
|
130
|
+
Returns
|
|
131
|
+
-------
|
|
132
|
+
np.ndarray | None
|
|
133
|
+
Array of total errors for this sensor array. shape=(num_sensors
|
|
134
|
+
,num_field_components,num_time_steps). Returns None if no error
|
|
135
|
+
integrator has been set.
|
|
136
|
+
"""
|
|
137
|
+
pass
|
|
138
|
+
|
|
139
|
+
@abstractmethod
|
|
140
|
+
def calc_measurements(self) -> np.ndarray:
|
|
141
|
+
"""Abstract method. Calculates measurements as: measurement = truth +
|
|
142
|
+
systematic errors + random errors. The truth is calculated once and is
|
|
143
|
+
interpolated from the input simulation field. The errors are calculated
|
|
144
|
+
based on the user specified error chain.
|
|
145
|
+
|
|
146
|
+
NOTE: this is a 'calc' method and will sample all probability
|
|
147
|
+
distributions in the error chain returning a new simulated experiment
|
|
148
|
+
for this sensor array.
|
|
149
|
+
|
|
150
|
+
Returns
|
|
151
|
+
-------
|
|
152
|
+
np.ndarray
|
|
153
|
+
The calculated measurements for this sensor array with shape:
|
|
154
|
+
(num_sensors,num_field_components,num_time_steps)
|
|
155
|
+
"""
|
|
156
|
+
pass
|
|
157
|
+
|
|
158
|
+
@abstractmethod
|
|
159
|
+
def get_measurements(self) -> np.ndarray:
|
|
160
|
+
"""Abstract method. Returns the current set of simulated measurements if
|
|
161
|
+
theses have been calculated. If these have not been calculated then
|
|
162
|
+
'calc_measurements()' is called and a set of measurements in then
|
|
163
|
+
returned.
|
|
164
|
+
|
|
165
|
+
NOTE: this is a 'get' method and does not sample from probability
|
|
166
|
+
distributions in the error chain and directly returns the current set of
|
|
167
|
+
measurements if they exist.
|
|
168
|
+
|
|
169
|
+
Returns
|
|
170
|
+
-------
|
|
171
|
+
np.ndarray
|
|
172
|
+
The calculated measurements for this sensor array with shape:
|
|
173
|
+
(num_sensors,num_field_components,num_time_steps)
|
|
174
|
+
"""
|
|
175
|
+
pass
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
|
|
@@ -0,0 +1,196 @@
|
|
|
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
|
+
|
|
9
|
+
import mooseherder as mh
|
|
10
|
+
|
|
11
|
+
from pyvale.fieldscalar import FieldScalar
|
|
12
|
+
from pyvale.fieldvector import FieldVector
|
|
13
|
+
from pyvale.fieldtensor import FieldTensor
|
|
14
|
+
from pyvale.sensordescriptor import SensorDescriptorFactory
|
|
15
|
+
from pyvale.sensorarraypoint import SensorArrayPoint, SensorData
|
|
16
|
+
from pyvale.errorintegrator import ErrIntegrator
|
|
17
|
+
from pyvale.errorsysindep import ErrSysUnifPercent
|
|
18
|
+
from pyvale.errorrand import ErrRandNormPercent
|
|
19
|
+
from pyvale.errorsysdep import (ErrSysDigitisation,
|
|
20
|
+
ErrSysSaturation)
|
|
21
|
+
|
|
22
|
+
# TODO:
|
|
23
|
+
# - docstrings
|
|
24
|
+
# - more sensor models
|
|
25
|
+
|
|
26
|
+
class SensorArrayFactory:
|
|
27
|
+
"""Namespace for static methods used to build common types of sensor arrays
|
|
28
|
+
simplifying sensor array creation for users.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
@staticmethod
|
|
32
|
+
def thermocouples_no_errs(sim_data: mh.SimData,
|
|
33
|
+
sensor_data: SensorData,
|
|
34
|
+
elem_dims: int,
|
|
35
|
+
field_name: str = "temperature",
|
|
36
|
+
) -> SensorArrayPoint:
|
|
37
|
+
|
|
38
|
+
descriptor = SensorDescriptorFactory.temperature_descriptor()
|
|
39
|
+
|
|
40
|
+
t_field = FieldScalar(sim_data,field_name,elem_dims)
|
|
41
|
+
|
|
42
|
+
sens_array = SensorArrayPoint(sensor_data,
|
|
43
|
+
t_field,
|
|
44
|
+
descriptor)
|
|
45
|
+
|
|
46
|
+
return sens_array
|
|
47
|
+
|
|
48
|
+
@staticmethod
|
|
49
|
+
def thermocouples_basic_errs(sim_data: mh.SimData,
|
|
50
|
+
sensor_data: SensorData,
|
|
51
|
+
elem_dims: int,
|
|
52
|
+
field_name: str = "temperature",
|
|
53
|
+
errs_pc: float = 1.0
|
|
54
|
+
) -> SensorArrayPoint:
|
|
55
|
+
|
|
56
|
+
sens_array = SensorArrayFactory.thermocouples_no_errs(sim_data,
|
|
57
|
+
sensor_data,
|
|
58
|
+
elem_dims,
|
|
59
|
+
field_name)
|
|
60
|
+
|
|
61
|
+
err_int = basic_err_integrator(sens_array.get_measurement_shape(),
|
|
62
|
+
sensor_data,
|
|
63
|
+
sys_err_pc=errs_pc,
|
|
64
|
+
rand_err_pc=errs_pc)
|
|
65
|
+
|
|
66
|
+
# Normal thermcouple amp = 5mV / K
|
|
67
|
+
err_int._err_chain.append(ErrSysDigitisation(bits_per_unit=2**16/1000))
|
|
68
|
+
err_int._err_chain.append(ErrSysSaturation(meas_min=0.0,meas_max=1000.0))
|
|
69
|
+
|
|
70
|
+
sens_array.set_error_integrator(err_int)
|
|
71
|
+
return sens_array
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@staticmethod
|
|
76
|
+
def disp_sensors_no_errs(sim_data: mh.SimData,
|
|
77
|
+
sensor_data: SensorData,
|
|
78
|
+
elem_dims: int,
|
|
79
|
+
field_name: str,
|
|
80
|
+
field_comps: tuple[str,...],
|
|
81
|
+
) -> SensorArrayPoint:
|
|
82
|
+
|
|
83
|
+
descriptor = SensorDescriptorFactory.displacement_descriptor()
|
|
84
|
+
|
|
85
|
+
disp_field = FieldVector(sim_data,
|
|
86
|
+
field_name,
|
|
87
|
+
field_comps,
|
|
88
|
+
elem_dims)
|
|
89
|
+
|
|
90
|
+
sens_array = SensorArrayPoint(sensor_data,
|
|
91
|
+
disp_field,
|
|
92
|
+
descriptor)
|
|
93
|
+
return sens_array
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@staticmethod
|
|
97
|
+
def disp_sensors_basic_errs(sim_data: mh.SimData,
|
|
98
|
+
sensor_data: SensorData,
|
|
99
|
+
elem_dims: int,
|
|
100
|
+
field_name: str,
|
|
101
|
+
field_comps: tuple[str,...],
|
|
102
|
+
errs_pc: float = 1.0,
|
|
103
|
+
) -> SensorArrayPoint:
|
|
104
|
+
|
|
105
|
+
sens_array = SensorArrayFactory.disp_sensors_no_errs(sim_data,
|
|
106
|
+
sensor_data,
|
|
107
|
+
elem_dims,
|
|
108
|
+
field_name,
|
|
109
|
+
field_comps)
|
|
110
|
+
err_int = basic_err_integrator(sens_array.get_measurement_shape(),
|
|
111
|
+
sensor_data,
|
|
112
|
+
sys_err_pc=errs_pc,
|
|
113
|
+
rand_err_pc=errs_pc)
|
|
114
|
+
sens_array.set_error_integrator(err_int)
|
|
115
|
+
|
|
116
|
+
return sens_array
|
|
117
|
+
|
|
118
|
+
@staticmethod
|
|
119
|
+
def strain_gauges_no_errs(sim_data: mh.SimData,
|
|
120
|
+
sensor_data: SensorData,
|
|
121
|
+
elem_dims: int,
|
|
122
|
+
field_name: str,
|
|
123
|
+
norm_comps: tuple[str,...],
|
|
124
|
+
dev_comps: tuple[str,...]
|
|
125
|
+
) -> SensorArrayPoint:
|
|
126
|
+
descriptor = SensorDescriptorFactory.strain_descriptor(elem_dims)
|
|
127
|
+
|
|
128
|
+
strain_field = FieldTensor(sim_data,
|
|
129
|
+
field_name,
|
|
130
|
+
norm_comps,
|
|
131
|
+
dev_comps,
|
|
132
|
+
elem_dims)
|
|
133
|
+
|
|
134
|
+
sens_array = SensorArrayPoint(sensor_data,
|
|
135
|
+
strain_field,
|
|
136
|
+
descriptor)
|
|
137
|
+
|
|
138
|
+
return sens_array
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
@staticmethod
|
|
142
|
+
def strain_gauges_basic_errs(sim_data: mh.SimData,
|
|
143
|
+
sensor_data: SensorData,
|
|
144
|
+
elem_dims: int,
|
|
145
|
+
field_name: str,
|
|
146
|
+
norm_comps: tuple[str,...],
|
|
147
|
+
dev_comps: tuple[str,...],
|
|
148
|
+
errs_pc: float = 1.0
|
|
149
|
+
) -> SensorArrayPoint:
|
|
150
|
+
|
|
151
|
+
sens_array = SensorArrayFactory.strain_gauges_no_errs(sim_data,
|
|
152
|
+
sensor_data,
|
|
153
|
+
elem_dims,
|
|
154
|
+
field_name,
|
|
155
|
+
norm_comps,
|
|
156
|
+
dev_comps)
|
|
157
|
+
|
|
158
|
+
err_int = basic_err_integrator(sens_array.get_measurement_shape(),
|
|
159
|
+
sensor_data,
|
|
160
|
+
sys_err_pc=errs_pc,
|
|
161
|
+
rand_err_pc=errs_pc)
|
|
162
|
+
sens_array.set_error_integrator(err_int)
|
|
163
|
+
|
|
164
|
+
return sens_array
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def basic_err_integrator(meas_shape: np.ndarray,
|
|
168
|
+
sensor_data: SensorData,
|
|
169
|
+
sys_err_pc: float = 1.0,
|
|
170
|
+
rand_err_pc: float = 1.0) -> ErrIntegrator:
|
|
171
|
+
"""Builds a basic error integrator with uniform percentage systematic error
|
|
172
|
+
calculator and a percentage normal random error calculator.
|
|
173
|
+
|
|
174
|
+
Parameters
|
|
175
|
+
----------
|
|
176
|
+
meas_shape : np.ndarray
|
|
177
|
+
Shape of the measurement array which is (num_sensors,
|
|
178
|
+
num_field_components,num_time_steps)
|
|
179
|
+
sensor_data : SensorData
|
|
180
|
+
Sensor array parameters for feeding through the error chain.
|
|
181
|
+
sys_err_pc : float, optional
|
|
182
|
+
Percentage systematic error, by default 1.0.
|
|
183
|
+
rand_err_pc : float, optional
|
|
184
|
+
Percentage random error, by default 1.0.
|
|
185
|
+
|
|
186
|
+
Returns
|
|
187
|
+
-------
|
|
188
|
+
ErrIntegrator
|
|
189
|
+
A basic error integrator with a uniform percentage systematic error and
|
|
190
|
+
a normal percentage random error.
|
|
191
|
+
"""
|
|
192
|
+
err_chain = []
|
|
193
|
+
err_chain.append(ErrSysUnifPercent(-sys_err_pc,sys_err_pc))
|
|
194
|
+
err_chain.append(ErrRandNormPercent(rand_err_pc))
|
|
195
|
+
err_int = ErrIntegrator(err_chain,sensor_data,meas_shape)
|
|
196
|
+
return err_int
|