pyvale 2025.4.1__py3-none-any.whl → 2025.5.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of pyvale might be problematic. Click here for more details.
- pyvale/__init__.py +18 -3
- pyvale/analyticmeshgen.py +1 -0
- pyvale/analyticsimdatafactory.py +18 -13
- pyvale/analyticsimdatagenerator.py +105 -72
- 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 +6 -5
- pyvale/cameradata.py +25 -7
- pyvale/cameradata2d.py +6 -4
- pyvale/camerastereo.py +217 -0
- pyvale/cameratools.py +206 -11
- pyvale/cython/rastercyth.py +6 -2
- pyvale/data/cal_target.tiff +0 -0
- pyvale/dataset.py +73 -14
- pyvale/errorcalculator.py +8 -10
- pyvale/errordriftcalc.py +10 -9
- pyvale/errorintegrator.py +19 -21
- pyvale/errorrand.py +33 -39
- pyvale/errorsyscalib.py +134 -0
- pyvale/errorsysdep.py +19 -22
- pyvale/errorsysfield.py +49 -41
- pyvale/errorsysindep.py +79 -175
- 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/{analyticdatagen → genanalyticdata}/ex1_1_scalarvisualisation.py +6 -9
- pyvale/examples/{analyticdatagen → genanalyticdata}/ex1_2_scalarcasebuild.py +8 -11
- pyvale/examples/{analyticdatagen → genanalyticdata}/ex2_1_analyticsensors.py +9 -12
- pyvale/examples/imagedef2d/ex_imagedef2d_todisk.py +8 -15
- 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/{rasterisation → renderrasterisation}/ex_rastenp.py +3 -2
- pyvale/examples/{rasterisation → renderrasterisation}/ex_rastercyth_oneframe.py +2 -2
- pyvale/examples/{rasterisation → renderrasterisation}/ex_rastercyth_static_cypara.py +3 -8
- pyvale/examples/{rasterisation → renderrasterisation}/ex_rastercyth_static_pypara.py +6 -7
- pyvale/examples/{ex1_4_thermal2d.py → visualisation/ex1_1_plot_traces.py} +32 -16
- pyvale/examples/{features/ex_animation_tools_3dmonoblock.py → visualisation/ex2_1_animate_sim.py} +37 -31
- pyvale/experimentsimulator.py +107 -30
- pyvale/field.py +2 -9
- pyvale/fieldconverter.py +98 -22
- pyvale/fieldsampler.py +2 -2
- pyvale/fieldscalar.py +10 -10
- pyvale/fieldtensor.py +15 -17
- pyvale/fieldtransform.py +7 -2
- pyvale/fieldvector.py +6 -7
- pyvale/generatorsrandom.py +25 -47
- pyvale/imagedef2d.py +6 -2
- pyvale/integratorfactory.py +2 -2
- pyvale/integratorquadrature.py +50 -24
- pyvale/integratorrectangle.py +85 -7
- pyvale/integratorspatial.py +4 -4
- pyvale/integratortype.py +3 -3
- pyvale/output.py +17 -0
- pyvale/pyvaleexceptions.py +11 -0
- pyvale/raster.py +6 -5
- pyvale/rastercy.py +6 -4
- pyvale/rasternp.py +6 -4
- pyvale/rendermesh.py +6 -2
- pyvale/sensorarray.py +2 -2
- pyvale/sensorarrayfactory.py +52 -65
- pyvale/sensorarraypoint.py +29 -30
- pyvale/sensordata.py +2 -2
- pyvale/sensordescriptor.py +138 -25
- pyvale/sensortools.py +3 -3
- pyvale/simtools.py +67 -0
- pyvale/visualexpplotter.py +99 -57
- pyvale/visualimagedef.py +11 -7
- pyvale/visualimages.py +6 -4
- pyvale/visualopts.py +372 -58
- pyvale/visualsimanimator.py +42 -13
- pyvale/visualsimsensors.py +318 -0
- pyvale/visualtools.py +69 -13
- pyvale/visualtraceplotter.py +52 -165
- {pyvale-2025.4.1.dist-info → pyvale-2025.5.1.dist-info}/METADATA +17 -14
- pyvale-2025.5.1.dist-info/RECORD +172 -0
- {pyvale-2025.4.1.dist-info → pyvale-2025.5.1.dist-info}/WHEEL +1 -1
- pyvale/examples/analyticdatagen/__init__.py +0 -5
- pyvale/examples/ex1_1_thermal2d.py +0 -86
- pyvale/examples/ex1_2_thermal2d.py +0 -108
- pyvale/examples/ex1_3_thermal2d.py +0 -110
- pyvale/examples/ex1_5_thermal2d.py +0 -102
- pyvale/examples/ex2_1_thermal3d .py +0 -84
- pyvale/examples/ex2_2_thermal3d.py +0 -51
- pyvale/examples/ex2_3_thermal3d.py +0 -106
- pyvale/examples/ex3_1_displacement2d.py +0 -44
- pyvale/examples/ex3_2_displacement2d.py +0 -76
- pyvale/examples/ex3_3_displacement2d.py +0 -101
- pyvale/examples/ex3_4_displacement2d.py +0 -102
- pyvale/examples/ex4_1_strain2d.py +0 -54
- pyvale/examples/ex4_2_strain2d.py +0 -76
- pyvale/examples/ex4_3_strain2d.py +0 -97
- pyvale/examples/ex5_1_multiphysics2d.py +0 -75
- pyvale/examples/ex6_1_multiphysics2d_expsim.py +0 -115
- pyvale/examples/ex6_2_multiphysics3d_expsim.py +0 -160
- pyvale/examples/features/__init__.py +0 -5
- pyvale/examples/features/ex_area_avg.py +0 -89
- pyvale/examples/features/ex_calibration_error.py +0 -108
- pyvale/examples/features/ex_chain_field_errs.py +0 -141
- pyvale/examples/features/ex_field_errs.py +0 -78
- pyvale/examples/features/ex_sensor_single_angle_batch.py +0 -110
- pyvale/optimcheckfuncs.py +0 -153
- pyvale/visualsimplotter.py +0 -182
- pyvale-2025.4.1.dist-info/RECORD +0 -163
- {pyvale-2025.4.1.dist-info → pyvale-2025.5.1.dist-info}/licenses/LICENSE +0 -0
- {pyvale-2025.4.1.dist-info → pyvale-2025.5.1.dist-info}/top_level.txt +0 -0
pyvale/sensorarrayfactory.py
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
#
|
|
1
|
+
# ==============================================================================
|
|
2
2
|
# pyvale: the python validation engine
|
|
3
3
|
# License: MIT
|
|
4
4
|
# Copyright (C) 2025 The Computer Aided Validation Team
|
|
5
|
-
#
|
|
5
|
+
# ==============================================================================
|
|
6
6
|
|
|
7
7
|
import numpy as np
|
|
8
8
|
|
|
@@ -14,12 +14,14 @@ from pyvale.fieldtensor import FieldTensor
|
|
|
14
14
|
from pyvale.sensordescriptor import SensorDescriptorFactory
|
|
15
15
|
from pyvale.sensorarraypoint import SensorArrayPoint, SensorData
|
|
16
16
|
from pyvale.errorintegrator import ErrIntegrator
|
|
17
|
-
from pyvale.errorsysindep import
|
|
17
|
+
from pyvale.errorsysindep import ErrSysUnifPercent
|
|
18
18
|
from pyvale.errorrand import ErrRandNormPercent
|
|
19
19
|
from pyvale.errorsysdep import (ErrSysDigitisation,
|
|
20
20
|
ErrSysSaturation)
|
|
21
21
|
|
|
22
|
-
#TODO:
|
|
22
|
+
# TODO:
|
|
23
|
+
# - docstrings
|
|
24
|
+
# - more sensor models
|
|
23
25
|
|
|
24
26
|
class SensorArrayFactory:
|
|
25
27
|
"""Namespace for static methods used to build common types of sensor arrays
|
|
@@ -29,35 +31,13 @@ class SensorArrayFactory:
|
|
|
29
31
|
@staticmethod
|
|
30
32
|
def thermocouples_no_errs(sim_data: mh.SimData,
|
|
31
33
|
sensor_data: SensorData,
|
|
34
|
+
elem_dims: int,
|
|
32
35
|
field_name: str = "temperature",
|
|
33
|
-
spat_dims: int = 3,
|
|
34
36
|
) -> SensorArrayPoint:
|
|
35
|
-
|
|
36
|
-
for thermocouples applied to a temperature field without any simulated
|
|
37
|
-
measurement errors. Allows the user to build and attach their own error
|
|
38
|
-
chain or use this for fast interpolation to sensor locations without
|
|
39
|
-
errors.
|
|
40
|
-
|
|
41
|
-
Parameters
|
|
42
|
-
----------
|
|
43
|
-
sim_data : mh.SimData
|
|
44
|
-
Simulation data containing a mesh and a temperature field for the
|
|
45
|
-
thermocouple array to sample.
|
|
46
|
-
sensor_data : SensorData
|
|
47
|
-
_description_
|
|
48
|
-
field_name : str, optional
|
|
49
|
-
_description_, by default "temperature"
|
|
50
|
-
spat_dims : int, optional
|
|
51
|
-
, by default 3
|
|
52
|
-
|
|
53
|
-
Returns
|
|
54
|
-
-------
|
|
55
|
-
SensorArrayPoint
|
|
56
|
-
_description_
|
|
57
|
-
"""
|
|
37
|
+
|
|
58
38
|
descriptor = SensorDescriptorFactory.temperature_descriptor()
|
|
59
39
|
|
|
60
|
-
t_field = FieldScalar(sim_data,field_name,
|
|
40
|
+
t_field = FieldScalar(sim_data,field_name,elem_dims)
|
|
61
41
|
|
|
62
42
|
sens_array = SensorArrayPoint(sensor_data,
|
|
63
43
|
t_field,
|
|
@@ -68,19 +48,20 @@ class SensorArrayFactory:
|
|
|
68
48
|
@staticmethod
|
|
69
49
|
def thermocouples_basic_errs(sim_data: mh.SimData,
|
|
70
50
|
sensor_data: SensorData,
|
|
51
|
+
elem_dims: int,
|
|
71
52
|
field_name: str = "temperature",
|
|
72
|
-
spat_dims: int = 3,
|
|
73
53
|
errs_pc: float = 1.0
|
|
74
54
|
) -> SensorArrayPoint:
|
|
75
55
|
|
|
76
56
|
sens_array = SensorArrayFactory.thermocouples_no_errs(sim_data,
|
|
77
57
|
sensor_data,
|
|
78
|
-
|
|
79
|
-
|
|
58
|
+
elem_dims,
|
|
59
|
+
field_name)
|
|
80
60
|
|
|
81
61
|
err_int = basic_err_integrator(sens_array.get_measurement_shape(),
|
|
82
62
|
sensor_data,
|
|
83
|
-
errs_pc
|
|
63
|
+
sys_err_pc=errs_pc,
|
|
64
|
+
rand_err_pc=errs_pc)
|
|
84
65
|
|
|
85
66
|
# Normal thermcouple amp = 5mV / K
|
|
86
67
|
err_int._err_chain.append(ErrSysDigitisation(bits_per_unit=2**16/1000))
|
|
@@ -89,19 +70,22 @@ class SensorArrayFactory:
|
|
|
89
70
|
sens_array.set_error_integrator(err_int)
|
|
90
71
|
return sens_array
|
|
91
72
|
|
|
73
|
+
|
|
74
|
+
|
|
92
75
|
@staticmethod
|
|
93
76
|
def disp_sensors_no_errs(sim_data: mh.SimData,
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
77
|
+
sensor_data: SensorData,
|
|
78
|
+
elem_dims: int,
|
|
79
|
+
field_name: str,
|
|
80
|
+
field_comps: tuple[str,...],
|
|
81
|
+
) -> SensorArrayPoint:
|
|
98
82
|
|
|
99
83
|
descriptor = SensorDescriptorFactory.displacement_descriptor()
|
|
100
84
|
|
|
101
85
|
disp_field = FieldVector(sim_data,
|
|
102
86
|
field_name,
|
|
103
|
-
|
|
104
|
-
|
|
87
|
+
field_comps,
|
|
88
|
+
elem_dims)
|
|
105
89
|
|
|
106
90
|
sens_array = SensorArrayPoint(sensor_data,
|
|
107
91
|
disp_field,
|
|
@@ -112,18 +96,21 @@ class SensorArrayFactory:
|
|
|
112
96
|
@staticmethod
|
|
113
97
|
def disp_sensors_basic_errs(sim_data: mh.SimData,
|
|
114
98
|
sensor_data: SensorData,
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
99
|
+
elem_dims: int,
|
|
100
|
+
field_name: str,
|
|
101
|
+
field_comps: tuple[str,...],
|
|
102
|
+
errs_pc: float = 1.0,
|
|
118
103
|
) -> SensorArrayPoint:
|
|
119
104
|
|
|
120
105
|
sens_array = SensorArrayFactory.disp_sensors_no_errs(sim_data,
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
106
|
+
sensor_data,
|
|
107
|
+
elem_dims,
|
|
108
|
+
field_name,
|
|
109
|
+
field_comps)
|
|
124
110
|
err_int = basic_err_integrator(sens_array.get_measurement_shape(),
|
|
125
111
|
sensor_data,
|
|
126
|
-
errs_pc
|
|
112
|
+
sys_err_pc=errs_pc,
|
|
113
|
+
rand_err_pc=errs_pc)
|
|
127
114
|
sens_array.set_error_integrator(err_int)
|
|
128
115
|
|
|
129
116
|
return sens_array
|
|
@@ -131,23 +118,18 @@ class SensorArrayFactory:
|
|
|
131
118
|
@staticmethod
|
|
132
119
|
def strain_gauges_no_errs(sim_data: mh.SimData,
|
|
133
120
|
sensor_data: SensorData,
|
|
134
|
-
|
|
135
|
-
|
|
121
|
+
elem_dims: int,
|
|
122
|
+
field_name: str,
|
|
123
|
+
norm_comps: tuple[str,...],
|
|
124
|
+
dev_comps: tuple[str,...]
|
|
136
125
|
) -> SensorArrayPoint:
|
|
137
|
-
descriptor = SensorDescriptorFactory.strain_descriptor(
|
|
138
|
-
|
|
139
|
-
if spat_dims == 2:
|
|
140
|
-
norm_components = ('strain_xx','strain_yy')
|
|
141
|
-
dev_components = ('strain_xy',)
|
|
142
|
-
else:
|
|
143
|
-
norm_components = ('strain_xx','strain_yy','strain_zz')
|
|
144
|
-
dev_components = ('strain_xy','strain_yz','strain_xz')
|
|
126
|
+
descriptor = SensorDescriptorFactory.strain_descriptor(elem_dims)
|
|
145
127
|
|
|
146
128
|
strain_field = FieldTensor(sim_data,
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
129
|
+
field_name,
|
|
130
|
+
norm_comps,
|
|
131
|
+
dev_comps,
|
|
132
|
+
elem_dims)
|
|
151
133
|
|
|
152
134
|
sens_array = SensorArrayPoint(sensor_data,
|
|
153
135
|
strain_field,
|
|
@@ -159,19 +141,24 @@ class SensorArrayFactory:
|
|
|
159
141
|
@staticmethod
|
|
160
142
|
def strain_gauges_basic_errs(sim_data: mh.SimData,
|
|
161
143
|
sensor_data: SensorData,
|
|
162
|
-
|
|
163
|
-
|
|
144
|
+
elem_dims: int,
|
|
145
|
+
field_name: str,
|
|
146
|
+
norm_comps: tuple[str,...],
|
|
147
|
+
dev_comps: tuple[str,...],
|
|
164
148
|
errs_pc: float = 1.0
|
|
165
149
|
) -> SensorArrayPoint:
|
|
166
150
|
|
|
167
151
|
sens_array = SensorArrayFactory.strain_gauges_no_errs(sim_data,
|
|
168
152
|
sensor_data,
|
|
153
|
+
elem_dims,
|
|
169
154
|
field_name,
|
|
170
|
-
|
|
155
|
+
norm_comps,
|
|
156
|
+
dev_comps)
|
|
171
157
|
|
|
172
158
|
err_int = basic_err_integrator(sens_array.get_measurement_shape(),
|
|
173
159
|
sensor_data,
|
|
174
|
-
errs_pc
|
|
160
|
+
sys_err_pc=errs_pc,
|
|
161
|
+
rand_err_pc=errs_pc)
|
|
175
162
|
sens_array.set_error_integrator(err_int)
|
|
176
163
|
|
|
177
164
|
return sens_array
|
|
@@ -203,7 +190,7 @@ def basic_err_integrator(meas_shape: np.ndarray,
|
|
|
203
190
|
a normal percentage random error.
|
|
204
191
|
"""
|
|
205
192
|
err_chain = []
|
|
206
|
-
err_chain.append(
|
|
193
|
+
err_chain.append(ErrSysUnifPercent(-sys_err_pc,sys_err_pc))
|
|
207
194
|
err_chain.append(ErrRandNormPercent(rand_err_pc))
|
|
208
195
|
err_int = ErrIntegrator(err_chain,sensor_data,meas_shape)
|
|
209
196
|
return err_int
|
pyvale/sensorarraypoint.py
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
#
|
|
1
|
+
# ==============================================================================
|
|
2
2
|
# pyvale: the python validation engine
|
|
3
3
|
# License: MIT
|
|
4
4
|
# Copyright (C) 2025 The Computer Aided Validation Team
|
|
5
|
-
#
|
|
5
|
+
# ==============================================================================
|
|
6
6
|
|
|
7
7
|
import numpy as np
|
|
8
8
|
from pyvale.field import IField
|
|
@@ -52,16 +52,15 @@ class SensorArrayPoint(ISensorArray):
|
|
|
52
52
|
simulated physical fields quickly using finite element shape functions.
|
|
53
53
|
"""
|
|
54
54
|
|
|
55
|
-
__slots__ = ("
|
|
56
|
-
"
|
|
55
|
+
__slots__ = ("_field","_descriptor","_sensor_data","_truth","_measurements",
|
|
56
|
+
"_error_integrator")
|
|
57
57
|
|
|
58
58
|
def __init__(self,
|
|
59
59
|
sensor_data: SensorData,
|
|
60
60
|
field: IField,
|
|
61
61
|
descriptor: SensorDescriptor | None = None,
|
|
62
62
|
) -> None:
|
|
63
|
-
"""
|
|
64
|
-
|
|
63
|
+
"""
|
|
65
64
|
Parameters
|
|
66
65
|
----------
|
|
67
66
|
sensor_data : SensorData
|
|
@@ -75,13 +74,13 @@ class SensorArrayPoint(ISensorArray):
|
|
|
75
74
|
Contains descriptive information about the sensor array for display
|
|
76
75
|
and visualisations, by default None.
|
|
77
76
|
"""
|
|
78
|
-
self.
|
|
79
|
-
self.
|
|
80
|
-
self.
|
|
77
|
+
self._sensor_data = sensor_data
|
|
78
|
+
self._field = field
|
|
79
|
+
self._error_integrator = None
|
|
81
80
|
|
|
82
|
-
self.
|
|
81
|
+
self._descriptor = SensorDescriptor()
|
|
83
82
|
if descriptor is not None:
|
|
84
|
-
self.
|
|
83
|
+
self._descriptor = descriptor
|
|
85
84
|
|
|
86
85
|
self._truth = None
|
|
87
86
|
self._measurements = None
|
|
@@ -96,10 +95,10 @@ class SensorArrayPoint(ISensorArray):
|
|
|
96
95
|
np.ndarray
|
|
97
96
|
Sample times with shape: (num_time_steps,)
|
|
98
97
|
"""
|
|
99
|
-
if self.
|
|
100
|
-
return self.
|
|
98
|
+
if self._sensor_data.sample_times is None:
|
|
99
|
+
return self._field.get_time_steps()
|
|
101
100
|
|
|
102
|
-
return self.
|
|
101
|
+
return self._sensor_data.sample_times
|
|
103
102
|
|
|
104
103
|
def get_measurement_shape(self) -> tuple[int,int,int]:
|
|
105
104
|
"""Gets the shape of the sensor measurement array. shape=(num_sensors,
|
|
@@ -112,8 +111,8 @@ class SensorArrayPoint(ISensorArray):
|
|
|
112
111
|
num_field_components,num_time_steps)
|
|
113
112
|
"""
|
|
114
113
|
|
|
115
|
-
return (self.
|
|
116
|
-
len(self.
|
|
114
|
+
return (self._sensor_data.positions.shape[0],
|
|
115
|
+
len(self._field.get_all_components()),
|
|
117
116
|
self.get_sample_times().shape[0])
|
|
118
117
|
|
|
119
118
|
def get_field(self) -> IField:
|
|
@@ -125,7 +124,7 @@ class SensorArrayPoint(ISensorArray):
|
|
|
125
124
|
IField
|
|
126
125
|
Reference to an `IField` interface.
|
|
127
126
|
"""
|
|
128
|
-
return self.
|
|
127
|
+
return self._field
|
|
129
128
|
|
|
130
129
|
|
|
131
130
|
def calc_truth_values(self) -> np.ndarray:
|
|
@@ -139,8 +138,8 @@ class SensorArrayPoint(ISensorArray):
|
|
|
139
138
|
Array of ground truth sensor values. shape=(num_sensors,
|
|
140
139
|
num_field_components,num_time_steps).
|
|
141
140
|
"""
|
|
142
|
-
self._truth = sample_field_with_sensor_data(self.
|
|
143
|
-
self.
|
|
141
|
+
self._truth = sample_field_with_sensor_data(self._field,
|
|
142
|
+
self._sensor_data)
|
|
144
143
|
|
|
145
144
|
return self._truth
|
|
146
145
|
|
|
@@ -170,7 +169,7 @@ class SensorArrayPoint(ISensorArray):
|
|
|
170
169
|
err_int : ErrIntegrator
|
|
171
170
|
Error integration object with a chain of user defined sensor errors.
|
|
172
171
|
"""
|
|
173
|
-
self.
|
|
172
|
+
self._error_integrator = err_int
|
|
174
173
|
|
|
175
174
|
def get_sensor_data_perturbed(self) -> SensorData | None:
|
|
176
175
|
"""Gets the final sensor array parameters after all errors in the error
|
|
@@ -183,10 +182,10 @@ class SensorArrayPoint(ISensorArray):
|
|
|
183
182
|
The accumulated sensor array parameters as a SensorData object.
|
|
184
183
|
Returns None if no error integrator has been specified.
|
|
185
184
|
"""
|
|
186
|
-
if self.
|
|
185
|
+
if self._error_integrator is None:
|
|
187
186
|
return None
|
|
188
187
|
|
|
189
|
-
return self.
|
|
188
|
+
return self._error_integrator.get_sens_data_accumulated()
|
|
190
189
|
|
|
191
190
|
def get_errors_systematic(self) -> np.ndarray | None:
|
|
192
191
|
"""Gets the systematic error array from the previously calculated sensor
|
|
@@ -199,10 +198,10 @@ class SensorArrayPoint(ISensorArray):
|
|
|
199
198
|
,num_field_components,num_time_steps). Returns None if no error
|
|
200
199
|
integrator has been set.
|
|
201
200
|
"""
|
|
202
|
-
if self.
|
|
201
|
+
if self._error_integrator is None:
|
|
203
202
|
return None
|
|
204
203
|
|
|
205
|
-
return self.
|
|
204
|
+
return self._error_integrator.get_errs_systematic()
|
|
206
205
|
|
|
207
206
|
def get_errors_random(self) -> np.ndarray | None:
|
|
208
207
|
"""Gets the random error array from the previously calculated sensor
|
|
@@ -215,10 +214,10 @@ class SensorArrayPoint(ISensorArray):
|
|
|
215
214
|
,num_field_components,num_time_steps). Returns None if no error
|
|
216
215
|
integrator has been set.
|
|
217
216
|
"""
|
|
218
|
-
if self.
|
|
217
|
+
if self._error_integrator is None:
|
|
219
218
|
return None
|
|
220
219
|
|
|
221
|
-
return self.
|
|
220
|
+
return self._error_integrator.get_errs_random()
|
|
222
221
|
|
|
223
222
|
def get_errors_total(self) -> np.ndarray | None:
|
|
224
223
|
"""Gets the total error array from the previously calculated sensor
|
|
@@ -231,10 +230,10 @@ class SensorArrayPoint(ISensorArray):
|
|
|
231
230
|
,num_field_components,num_time_steps). Returns None if no error
|
|
232
231
|
integrator has been set.
|
|
233
232
|
"""
|
|
234
|
-
if self.
|
|
233
|
+
if self._error_integrator is None:
|
|
235
234
|
return None
|
|
236
235
|
|
|
237
|
-
return self.
|
|
236
|
+
return self._error_integrator.get_errs_total()
|
|
238
237
|
|
|
239
238
|
def calc_measurements(self) -> np.ndarray:
|
|
240
239
|
"""Calculates a set of sensor measurements using the specified sensor
|
|
@@ -253,11 +252,11 @@ class SensorArrayPoint(ISensorArray):
|
|
|
253
252
|
systematic errors if an error integrator is specified. shape=(
|
|
254
253
|
num_sensors,num_field_components,num_time_steps).
|
|
255
254
|
"""
|
|
256
|
-
if self.
|
|
255
|
+
if self._error_integrator is None:
|
|
257
256
|
self._measurements = self.get_truth()
|
|
258
257
|
else:
|
|
259
258
|
self._measurements = self.get_truth() + \
|
|
260
|
-
self.
|
|
259
|
+
self._error_integrator.calc_errors_from_chain(self.get_truth())
|
|
261
260
|
|
|
262
261
|
return self._measurements
|
|
263
262
|
|
pyvale/sensordata.py
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
#
|
|
1
|
+
# ==============================================================================
|
|
2
2
|
# pyvale: the python validation engine
|
|
3
3
|
# License: MIT
|
|
4
4
|
# Copyright (C) 2025 The Computer Aided Validation Team
|
|
5
|
-
#
|
|
5
|
+
# ==============================================================================
|
|
6
6
|
|
|
7
7
|
from dataclasses import dataclass
|
|
8
8
|
import numpy as np
|
pyvale/sensordescriptor.py
CHANGED
|
@@ -1,25 +1,75 @@
|
|
|
1
1
|
|
|
2
|
-
#
|
|
2
|
+
# ==============================================================================
|
|
3
3
|
# pyvale: the python validation engine
|
|
4
4
|
# License: MIT
|
|
5
5
|
# Copyright (C) 2025 The Computer Aided Validation Team
|
|
6
|
-
#
|
|
6
|
+
# ==============================================================================
|
|
7
|
+
|
|
8
|
+
"""
|
|
9
|
+
This module is used to create sensor descriptors which are strings used to label
|
|
10
|
+
plots and visualisations for virtual sensor simulations.
|
|
11
|
+
"""
|
|
7
12
|
|
|
8
13
|
from dataclasses import dataclass
|
|
9
14
|
import numpy as np
|
|
10
15
|
|
|
11
|
-
#TODO: Docstrings
|
|
12
16
|
|
|
13
17
|
@dataclass(slots=True)
|
|
14
18
|
class SensorDescriptor:
|
|
15
|
-
|
|
19
|
+
"""Dataclass for storing string descriptors for sensor array vis2ualisation.
|
|
20
|
+
Used for labelling matplotlib and pyvista plots with the sensor name,
|
|
21
|
+
physical units and other descriptors.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
name: str = "Measured Value"
|
|
25
|
+
"""String describing the field that the sensor measures e.g. temperature
|
|
26
|
+
, strain etc. Defaults to 'Measured Value'.
|
|
27
|
+
"""
|
|
28
|
+
|
|
16
29
|
units: str = r"-"
|
|
30
|
+
"""String describing the sensor measurement units. Defaults to '-'. Latex
|
|
31
|
+
symbols can be used with a raw string.
|
|
32
|
+
"""
|
|
33
|
+
|
|
17
34
|
time_units: str = r"s"
|
|
35
|
+
"""String describing time units. Defaults to 's'.
|
|
36
|
+
"""
|
|
37
|
+
|
|
18
38
|
symbol: str = r"m"
|
|
19
|
-
|
|
39
|
+
"""Symbol for describing the field the sensor measures. For example 'T' for
|
|
40
|
+
temperature of r'\epsilon' for strain. Latex symbols can be used with a raw
|
|
41
|
+
string.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
tag: str = "S"
|
|
45
|
+
"""String shorthand tag used to label sensors on pyvista plots. Defaults to
|
|
46
|
+
'S'.
|
|
47
|
+
"""
|
|
48
|
+
|
|
20
49
|
components: tuple[str,...] | None = None
|
|
50
|
+
"""Tuple of strings describing the field components. Defaults to None which
|
|
51
|
+
is used for scalar fields. For vector fields use ('x','y','z') for 3D and
|
|
52
|
+
for tensor fields use ('xx','yy','zz','xy','yz','xz').
|
|
53
|
+
"""
|
|
54
|
+
|
|
21
55
|
|
|
22
56
|
def create_label(self, comp_ind: int | None = None) -> str:
|
|
57
|
+
"""Creates an axis label for a matplotlib plot based on the sensor
|
|
58
|
+
descriptor string. The axis label takes the form: 'name, symbol [units]'
|
|
59
|
+
This version creates a label with line breaks which is useful for
|
|
60
|
+
vertical colourbars.
|
|
61
|
+
|
|
62
|
+
Parameters
|
|
63
|
+
----------
|
|
64
|
+
comp_ind : int | None, optional
|
|
65
|
+
Index of the field component to create a label for, by default None.
|
|
66
|
+
If None the first field component is used.
|
|
67
|
+
|
|
68
|
+
Returns
|
|
69
|
+
-------
|
|
70
|
+
str
|
|
71
|
+
Axis label for field component in the form: 'name, symbol [units]'.
|
|
72
|
+
"""
|
|
23
73
|
label = ""
|
|
24
74
|
if self.name != "":
|
|
25
75
|
label = label + rf"{self.name} "
|
|
@@ -37,6 +87,22 @@ class SensorDescriptor:
|
|
|
37
87
|
return label
|
|
38
88
|
|
|
39
89
|
def create_label_flat(self, comp_ind: int | None = None) -> str:
|
|
90
|
+
"""Creates an axis label for a matplotlib plot based on the sensor
|
|
91
|
+
descriptor string. The axis label takes the form: 'name, symbol [units]'
|
|
92
|
+
This version creates a label with no line breaks which is useful for
|
|
93
|
+
axis labels on plots.
|
|
94
|
+
|
|
95
|
+
Parameters
|
|
96
|
+
----------
|
|
97
|
+
comp_ind : int | None, optional
|
|
98
|
+
Index of the field component to create a label for, by default None.
|
|
99
|
+
If None the first field component is used.
|
|
100
|
+
|
|
101
|
+
Returns
|
|
102
|
+
-------
|
|
103
|
+
str
|
|
104
|
+
Axis label for field component in the form: 'name, symbol [units]'.
|
|
105
|
+
"""
|
|
40
106
|
label = ""
|
|
41
107
|
if self.name != "":
|
|
42
108
|
label = label + rf"{self.name} "
|
|
@@ -54,47 +120,94 @@ class SensorDescriptor:
|
|
|
54
120
|
return label
|
|
55
121
|
|
|
56
122
|
def create_sensor_tags(self,n_sensors: int) -> list[str]:
|
|
123
|
+
"""Creates a list of numbered sensor tags for labelling sensor locations
|
|
124
|
+
or for graph legends. Tags are shorthand names for sensors such as TC
|
|
125
|
+
for thermocouples or SG for strain gauges.
|
|
126
|
+
|
|
127
|
+
Parameters
|
|
128
|
+
----------
|
|
129
|
+
n_sensors : int
|
|
130
|
+
The number of sensors to create tags for.
|
|
131
|
+
|
|
132
|
+
Returns
|
|
133
|
+
-------
|
|
134
|
+
list[str]
|
|
135
|
+
A list of sensor tags
|
|
136
|
+
"""
|
|
57
137
|
z_width = int(np.log10(n_sensors))+1
|
|
58
138
|
|
|
59
139
|
sensor_names = list()
|
|
60
140
|
for ss in range(n_sensors):
|
|
61
|
-
num_str = f
|
|
62
|
-
sensor_names.append(f
|
|
141
|
+
num_str = f"{ss+1}".zfill(z_width)
|
|
142
|
+
sensor_names.append(f"{self.tag}{num_str}")
|
|
63
143
|
|
|
64
144
|
return sensor_names
|
|
65
145
|
|
|
66
146
|
|
|
67
147
|
class SensorDescriptorFactory:
|
|
148
|
+
"""A factory for building common sensor descriptors for scalar, vector and
|
|
149
|
+
tensor fields. Builds descriptors for thermcouples, displacement sensors
|
|
150
|
+
and strain sensors.
|
|
151
|
+
"""
|
|
152
|
+
|
|
68
153
|
@staticmethod
|
|
69
154
|
def temperature_descriptor() -> SensorDescriptor:
|
|
70
|
-
descriptor
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
155
|
+
"""Creates a generic temperature sensor descriptor. Assumes the sensor
|
|
156
|
+
is measuring a temperature in degrees C.
|
|
157
|
+
|
|
158
|
+
Returns
|
|
159
|
+
-------
|
|
160
|
+
SensorDescriptor
|
|
161
|
+
The default temperature sensor descriptor.
|
|
162
|
+
"""
|
|
163
|
+
descriptor = SensorDescriptor(name="Temp.",
|
|
164
|
+
symbol="T",
|
|
165
|
+
units=r"^{\circ}C",
|
|
166
|
+
tag="TC")
|
|
75
167
|
return descriptor
|
|
76
168
|
|
|
77
169
|
@staticmethod
|
|
78
170
|
def displacement_descriptor() -> SensorDescriptor:
|
|
79
|
-
descriptor
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
171
|
+
"""Creates a generic displacement sensor descriptor. Assumes units of mm
|
|
172
|
+
and vector components of x,y,z.
|
|
173
|
+
|
|
174
|
+
Returns
|
|
175
|
+
-------
|
|
176
|
+
SensorDescriptor
|
|
177
|
+
The default displacement sensor descriptor.
|
|
178
|
+
"""
|
|
179
|
+
descriptor = SensorDescriptor(name="Disp.",
|
|
180
|
+
symbol="u",
|
|
181
|
+
units=r"mm",
|
|
182
|
+
tag="DS",
|
|
183
|
+
components=("x","y","z"))
|
|
85
184
|
return descriptor
|
|
86
185
|
|
|
87
186
|
@staticmethod
|
|
88
187
|
def strain_descriptor(spat_dims: int = 3) -> SensorDescriptor:
|
|
89
|
-
descriptor
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
188
|
+
"""Creates a generic strain sensor descriptor. Assumes strain is
|
|
189
|
+
unitless and that the components are xx,yy,xy for 2D and xx,yy,zz,xy,yz,
|
|
190
|
+
xz for 3D.
|
|
191
|
+
|
|
192
|
+
Parameters
|
|
193
|
+
----------
|
|
194
|
+
spat_dims : int, optional
|
|
195
|
+
Number of spatial dimensions used for setting the components of the
|
|
196
|
+
tensor strain field, by default 3.
|
|
197
|
+
|
|
198
|
+
Returns
|
|
199
|
+
-------
|
|
200
|
+
SensorDescriptor
|
|
201
|
+
The default strain sensor descriptor.
|
|
202
|
+
"""
|
|
203
|
+
descriptor = SensorDescriptor(name="Strain",
|
|
204
|
+
symbol=r"\varepsilon",
|
|
205
|
+
units=r"-",
|
|
206
|
+
tag="SG")
|
|
94
207
|
|
|
95
208
|
if spat_dims == 2:
|
|
96
|
-
descriptor.components = (
|
|
209
|
+
descriptor.components = ("xx","yy","xy")
|
|
97
210
|
else:
|
|
98
|
-
descriptor.components = (
|
|
211
|
+
descriptor.components = ("xx","yy","zz","xy","yz","xz")
|
|
99
212
|
|
|
100
213
|
return descriptor
|
pyvale/sensortools.py
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
#
|
|
1
|
+
# ==============================================================================
|
|
2
2
|
# pyvale: the python validation engine
|
|
3
3
|
# License: MIT
|
|
4
4
|
# Copyright (C) 2025 The Computer Aided Validation Team
|
|
5
|
-
#
|
|
5
|
+
# ==============================================================================
|
|
6
6
|
|
|
7
7
|
import numpy as np
|
|
8
8
|
import mooseherder as mh
|
|
@@ -115,7 +115,7 @@ def print_measurements(sens_array: ISensorArray,
|
|
|
115
115
|
print_toterrs = tot_errs[sensors[0]:sensors[1],
|
|
116
116
|
components[0]:components[1],
|
|
117
117
|
time_steps[0]:time_steps[1]]
|
|
118
|
-
print(f"total errors = \n {
|
|
118
|
+
print(f"total errors = \n {print_toterrs}")
|
|
119
119
|
|
|
120
120
|
print()
|
|
121
121
|
|