pyvale 2025.5.3__cp311-cp311-win_amd64.whl → 2025.7.1__cp311-cp311-win_amd64.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 +12 -0
- pyvale/blendercalibrationdata.py +3 -1
- pyvale/blenderscene.py +7 -5
- pyvale/blendertools.py +27 -5
- pyvale/camera.py +1 -0
- pyvale/cameradata.py +3 -0
- pyvale/camerasensor.py +147 -0
- pyvale/camerastereo.py +4 -4
- pyvale/cameratools.py +23 -61
- pyvale/cython/rastercyth.c +1657 -1352
- pyvale/cython/rastercyth.cp311-win_amd64.pyd +0 -0
- pyvale/cython/rastercyth.py +71 -26
- pyvale/data/DIC_Challenge_Star_Noise_Def.tiff +0 -0
- pyvale/data/DIC_Challenge_Star_Noise_Ref.tiff +0 -0
- pyvale/data/plate_hole_def0000.tiff +0 -0
- pyvale/data/plate_hole_def0001.tiff +0 -0
- pyvale/data/plate_hole_ref0000.tiff +0 -0
- pyvale/data/plate_rigid_def0000.tiff +0 -0
- pyvale/data/plate_rigid_def0001.tiff +0 -0
- pyvale/data/plate_rigid_ref0000.tiff +0 -0
- pyvale/dataset.py +96 -6
- pyvale/dic/cpp/dicbruteforce.cpp +370 -0
- pyvale/dic/cpp/dicfourier.cpp +648 -0
- pyvale/dic/cpp/dicinterpolator.cpp +559 -0
- pyvale/dic/cpp/dicmain.cpp +215 -0
- pyvale/dic/cpp/dicoptimizer.cpp +675 -0
- pyvale/dic/cpp/dicrg.cpp +137 -0
- pyvale/dic/cpp/dicscanmethod.cpp +677 -0
- pyvale/dic/cpp/dicsmooth.cpp +138 -0
- pyvale/dic/cpp/dicstrain.cpp +383 -0
- pyvale/dic/cpp/dicutil.cpp +563 -0
- pyvale/dic2d.py +164 -0
- pyvale/dic2dcpp.cp311-win_amd64.pyd +0 -0
- pyvale/dicchecks.py +476 -0
- pyvale/dicdataimport.py +247 -0
- pyvale/dicregionofinterest.py +887 -0
- pyvale/dicresults.py +55 -0
- pyvale/dicspecklegenerator.py +238 -0
- pyvale/dicspecklequality.py +305 -0
- pyvale/dicstrain.py +387 -0
- pyvale/dicstrainresults.py +37 -0
- pyvale/errorintegrator.py +10 -8
- pyvale/examples/basics/ex1_1_basicscalars_therm2d.py +124 -113
- pyvale/examples/basics/ex1_2_sensormodel_therm2d.py +124 -132
- pyvale/examples/basics/ex1_3_customsens_therm3d.py +199 -195
- pyvale/examples/basics/ex1_4_basicerrors_therm3d.py +125 -121
- pyvale/examples/basics/ex1_5_fielderrs_therm3d.py +145 -141
- pyvale/examples/basics/ex1_6_caliberrs_therm2d.py +96 -101
- pyvale/examples/basics/ex1_7_spatavg_therm2d.py +109 -105
- pyvale/examples/basics/ex2_1_basicvectors_disp2d.py +92 -91
- pyvale/examples/basics/ex2_2_vectorsens_disp2d.py +96 -90
- pyvale/examples/basics/ex2_3_sensangle_disp2d.py +88 -89
- pyvale/examples/basics/ex2_4_chainfielderrs_disp2d.py +172 -171
- pyvale/examples/basics/ex2_5_vectorfields3d_disp3d.py +88 -86
- pyvale/examples/basics/ex3_1_basictensors_strain2d.py +90 -90
- pyvale/examples/basics/ex3_2_tensorsens2d_strain2d.py +93 -91
- pyvale/examples/basics/ex3_3_tensorsens3d_strain3d.py +172 -160
- pyvale/examples/basics/ex4_1_expsim2d_thermmech2d.py +154 -148
- pyvale/examples/basics/ex4_2_expsim3d_thermmech3d.py +249 -231
- pyvale/examples/dic/ex1_region_of_interest.py +98 -0
- pyvale/examples/dic/ex2_plate_with_hole.py +149 -0
- pyvale/examples/dic/ex3_plate_with_hole_strain.py +93 -0
- pyvale/examples/dic/ex4_dic_blender.py +95 -0
- pyvale/examples/dic/ex5_dic_challenge.py +102 -0
- pyvale/examples/imagedef2d/ex_imagedef2d_todisk.py +4 -2
- pyvale/examples/renderblender/ex1_1_blenderscene.py +152 -105
- pyvale/examples/renderblender/ex1_2_blenderdeformed.py +151 -100
- pyvale/examples/renderblender/ex2_1_stereoscene.py +183 -116
- pyvale/examples/renderblender/ex2_2_stereodeformed.py +185 -112
- pyvale/examples/renderblender/ex3_1_blendercalibration.py +164 -109
- pyvale/examples/renderrasterisation/ex_rastenp.py +74 -35
- pyvale/examples/renderrasterisation/ex_rastercyth_oneframe.py +6 -13
- pyvale/examples/renderrasterisation/ex_rastercyth_static_cypara.py +2 -2
- pyvale/examples/renderrasterisation/ex_rastercyth_static_pypara.py +2 -4
- pyvale/imagedef2d.py +3 -2
- pyvale/imagetools.py +137 -0
- pyvale/rastercy.py +34 -4
- pyvale/rasternp.py +300 -276
- pyvale/rasteropts.py +58 -0
- pyvale/renderer.py +47 -0
- pyvale/rendermesh.py +52 -62
- pyvale/renderscene.py +51 -0
- pyvale/sensorarrayfactory.py +2 -2
- pyvale/sensortools.py +19 -35
- pyvale/simcases/case21.i +1 -1
- pyvale/simcases/run_1case.py +8 -0
- pyvale/simtools.py +2 -2
- pyvale/visualsimplotter.py +180 -0
- {pyvale-2025.5.3.dist-info → pyvale-2025.7.1.dist-info}/METADATA +11 -57
- {pyvale-2025.5.3.dist-info → pyvale-2025.7.1.dist-info}/RECORD +93 -56
- {pyvale-2025.5.3.dist-info → pyvale-2025.7.1.dist-info}/WHEEL +1 -1
- pyvale/examples/visualisation/ex1_1_plot_traces.py +0 -102
- pyvale/examples/visualisation/ex2_1_animate_sim.py +0 -89
- {pyvale-2025.5.3.dist-info → pyvale-2025.7.1.dist-info}/licenses/LICENSE +0 -0
- {pyvale-2025.5.3.dist-info → pyvale-2025.7.1.dist-info}/top_level.txt +0 -0
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
# ==============================================================================
|
|
6
6
|
|
|
7
7
|
"""
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
Basics: Building a point sensor array from scratch with custom errors
|
|
9
|
+
================================================================================
|
|
10
|
+
|
|
10
11
|
Here we build a custom point sensor array from scratch that is similar to the
|
|
11
12
|
pre-built thermocouple array from example 1.1. For this example we switch to a
|
|
12
13
|
3D thermal simulation of a fusion heatsink component.
|
|
@@ -20,197 +21,200 @@ import matplotlib.pyplot as plt
|
|
|
20
21
|
import mooseherder as mh
|
|
21
22
|
import pyvale as pyv
|
|
22
23
|
|
|
24
|
+
#%%
|
|
25
|
+
# To build our custom point sensor array we need to at minimum provide a
|
|
26
|
+
# `IField` (i.e. `FieldScaler`, `FieldVector`, `FieldTensor`) and a
|
|
27
|
+
# `SensorData` object. For labelling visualisations (e.g. axis labels and
|
|
28
|
+
# unit labels) we can also provide a `SensorDescriptor` object.
|
|
29
|
+
# Once we have built our `SensorArrayPoint` object from these we can then
|
|
30
|
+
# attach custom chains of different types of random and systematic errors
|
|
31
|
+
# to be evaluated when we run our measurement simulation. This example is
|
|
32
|
+
# based on the same thermal example we have used in the last two examples so
|
|
33
|
+
# we start by loading our simulation data:
|
|
34
|
+
|
|
35
|
+
data_path = pyv.DataSet.thermal_3d_path()
|
|
36
|
+
sim_data = mh.ExodusReader(data_path).read_all_sim_data()
|
|
37
|
+
sim_data = pyv.scale_length_units(scale=1000.0,
|
|
38
|
+
sim_data=sim_data,
|
|
39
|
+
disp_comps=None)
|
|
40
|
+
|
|
41
|
+
#%%
|
|
42
|
+
# We are going to build a custom temperature sensor so we need a scalar
|
|
43
|
+
# field object to perform interpolation to the sensor locations at the
|
|
44
|
+
# desired sampling times.
|
|
45
|
+
field_key: str = "temperature"
|
|
46
|
+
t_field = pyv.FieldScalar(sim_data,
|
|
47
|
+
field_key=field_key,
|
|
48
|
+
elem_dims=3)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
#%%
|
|
52
|
+
# Next we need to create our `SensorData` object which will set the position
|
|
53
|
+
# and sampling times of our sensors. We use the same helper function we used
|
|
54
|
+
# previously to create a uniformly spaced grid of sensors in space
|
|
55
|
+
n_sens = (1,4,1)
|
|
56
|
+
x_lims = (12.5,12.5)
|
|
57
|
+
y_lims = (0.0,33.0)
|
|
58
|
+
z_lims = (0.0,12.0)
|
|
59
|
+
sens_pos = pyv.create_sensor_pos_array(n_sens,x_lims,y_lims,z_lims)
|
|
60
|
+
|
|
61
|
+
#%%
|
|
62
|
+
# We are also going to specify the times at which we would like to simulate
|
|
63
|
+
# measurements. Setting this to `None` will default the measurements times
|
|
64
|
+
# to match the simulation time steps.
|
|
65
|
+
sample_times = np.linspace(0.0,np.max(sim_data.time),50)
|
|
66
|
+
|
|
67
|
+
sensor_data = pyv.SensorData(positions=sens_pos,
|
|
68
|
+
sample_times=sample_times)
|
|
69
|
+
|
|
70
|
+
#%%
|
|
71
|
+
# Finally, we can create a `SensorDescriptor` which will be used to label
|
|
72
|
+
# the visualisation and sensor trace plots we have seen in previous
|
|
73
|
+
# examples.
|
|
74
|
+
use_auto_descriptor: str = "blank"
|
|
75
|
+
if use_auto_descriptor == "manual":
|
|
76
|
+
descriptor = pyv.SensorDescriptor(name="Temperature",
|
|
77
|
+
symbol="T",
|
|
78
|
+
units = r"^{\circ}C",
|
|
79
|
+
tag = "TC")
|
|
80
|
+
elif use_auto_descriptor == "factory":
|
|
81
|
+
descriptor = pyv.SensorDescriptorFactory.temperature_descriptor()
|
|
82
|
+
else:
|
|
83
|
+
descriptor = pyv.SensorDescriptor()
|
|
84
|
+
|
|
85
|
+
#%%
|
|
86
|
+
# We can now build our custom point sensor array. This sensor array has no
|
|
87
|
+
# errors so if we call `get_measurements()` or `calc_measurements()` we will
|
|
88
|
+
# be able to extract the simulation truth values at the sensor locations.
|
|
89
|
+
tc_array = pyv.SensorArrayPoint(sensor_data,
|
|
90
|
+
t_field,
|
|
91
|
+
descriptor)
|
|
92
|
+
|
|
93
|
+
#%%
|
|
94
|
+
# This is a new 3D simulation we are analysing so we should visualise the
|
|
95
|
+
# sensor locations before we run our measurement simulation. We use the same
|
|
96
|
+
# code as we did in example 1.1 to display the sensor locations.
|
|
97
|
+
#
|
|
98
|
+
# We are also going to save some figures to disk as well as displaying them
|
|
99
|
+
# interactively so we create a directory for this:
|
|
100
|
+
output_path = Path.cwd() / "pyvale-output"
|
|
101
|
+
if not output_path.is_dir():
|
|
102
|
+
output_path.mkdir(parents=True, exist_ok=True)
|
|
103
|
+
|
|
104
|
+
pv_plot = pyv.plot_point_sensors_on_sim(tc_array,field_key)
|
|
105
|
+
|
|
106
|
+
pv_plot.camera_position = [(59.354, 43.428, 69.946),
|
|
107
|
+
(-2.858, 13.189, 4.523),
|
|
108
|
+
(-0.215, 0.948, -0.233)]
|
|
109
|
+
|
|
110
|
+
save_render = output_path / "customsensors_ex1_3_sensorlocs.svg"
|
|
111
|
+
pv_plot.save_graphic(save_render) # only for .svg .eps .ps .pdf .tex
|
|
112
|
+
pv_plot.screenshot(save_render.with_suffix(".png"))
|
|
113
|
+
|
|
114
|
+
pv_plot.show()
|
|
115
|
+
|
|
116
|
+
#%%
|
|
117
|
+
# If we want to simulate sources of uncertainty for our sensor array we need
|
|
118
|
+
# to add an `ErrIntegrator` to our sensor array using the method
|
|
119
|
+
# `set_error_integrator()`. We provide our `ErrIntegrator` a list of error
|
|
120
|
+
# objects which will be evaluated in the order specified in the list.
|
|
121
|
+
#
|
|
122
|
+
# In pyvale errors have a type specified as: random / systematic
|
|
123
|
+
# (`EErrorType`) and a dependence `EErrDependence` as: independent /
|
|
124
|
+
# dependent. When analysing errors all random all systematic errors are
|
|
125
|
+
# grouped and summed together.
|
|
126
|
+
#
|
|
127
|
+
# The error dependence determines if an error is
|
|
128
|
+
# calculated based on the truth (independent) or the accumulated measurement
|
|
129
|
+
# based on all previous errors in the chain (dependent). Some errors are
|
|
130
|
+
# purely independent such as random noise with a normal distribution with a
|
|
131
|
+
# set standard devitation. An example of an error that is dependent would be
|
|
132
|
+
# saturation which must be place last in the error chain and will clamp the
|
|
133
|
+
# final sensor value to be within the specified bounds.
|
|
134
|
+
#
|
|
135
|
+
# pyvale provides a library of different random `ErrRand*` and systematic
|
|
136
|
+
# `ErrSys*` errors which can be found listed in the docs. In the next
|
|
137
|
+
# example we will explore the error library but for now we will specify some
|
|
138
|
+
# common error types. Try experimenting with the code below to turn the
|
|
139
|
+
# different error types off and on to see how it changes the virtual sensor
|
|
140
|
+
# measurements.
|
|
141
|
+
#
|
|
142
|
+
# This systematic error is just a constant offset of -5 to all simulated
|
|
143
|
+
# measurements. Note that error values should be specified in the same
|
|
144
|
+
# units as the simulation.
|
|
145
|
+
#
|
|
146
|
+
# This systematic error samples from a uniform probability distribution.
|
|
147
|
+
errors_on = {"sys": True,
|
|
148
|
+
"rand": True}
|
|
149
|
+
|
|
150
|
+
error_chain = []
|
|
151
|
+
if errors_on["sys"]:
|
|
152
|
+
error_chain.append(pyv.ErrSysOffset(offset=-10.0))
|
|
153
|
+
error_chain.append(pyv.ErrSysUnif(low=-10.0,
|
|
154
|
+
high=10.0))
|
|
155
|
+
#%%
|
|
156
|
+
# This random error is generated by sampling from a normal distribution
|
|
157
|
+
# with the given standard deviation in simulation units.
|
|
158
|
+
# This random error is generated as a percentage sampled from uniform
|
|
159
|
+
# probability distribution
|
|
160
|
+
|
|
161
|
+
if errors_on["rand"]:
|
|
162
|
+
error_chain.append(pyv.ErrRandNorm(std=5.0))
|
|
163
|
+
error_chain.append(pyv.ErrRandUnifPercent(low_percent=-5.0,
|
|
164
|
+
high_percent=5.0))
|
|
165
|
+
|
|
166
|
+
#%%
|
|
167
|
+
# By default pyvale does not store all individual error source
|
|
168
|
+
# calculations (i.e. only the total random and total systematic error are
|
|
169
|
+
# stored) to save memory but this can be changed using `ErrIntOpts`. This
|
|
170
|
+
# can also be used to force all errors to behave as if they are DEPENDENT or
|
|
171
|
+
# INDEPENDENT.
|
|
172
|
+
|
|
173
|
+
if len(error_chain) > 0:
|
|
174
|
+
err_int_opts = pyv.ErrIntOpts()
|
|
175
|
+
error_integrator = pyv.ErrIntegrator(error_chain,
|
|
176
|
+
sensor_data,
|
|
177
|
+
tc_array.get_measurement_shape(),
|
|
178
|
+
err_int_opts=err_int_opts)
|
|
179
|
+
tc_array.set_error_integrator(error_integrator)
|
|
180
|
+
|
|
181
|
+
#%%
|
|
182
|
+
# Now that we have added our error chain we can run a simulation to sample
|
|
183
|
+
# from all our error sources.
|
|
184
|
+
measurements = tc_array.calc_measurements()
|
|
185
|
+
|
|
186
|
+
#%%
|
|
187
|
+
# We display the simulation results by printing to the console and by
|
|
188
|
+
# plotting the sensor times traces. Try experimenting with the errors above
|
|
189
|
+
# to see how the results change.
|
|
190
|
+
print("\n"+80*"-")
|
|
191
|
+
print("For a virtual sensor: measurement = truth + sysematic error + random error")
|
|
192
|
+
print(f"measurements.shape = {measurements.shape} = "+
|
|
193
|
+
"(n_sensors,n_field_components,n_timesteps)\n")
|
|
194
|
+
print("The truth, systematic error and random error arrays have the same "+
|
|
195
|
+
"shape.")
|
|
196
|
+
|
|
197
|
+
print(80*"-")
|
|
198
|
+
|
|
199
|
+
sens_print = 0
|
|
200
|
+
comp_print = 0
|
|
201
|
+
time_last = 5
|
|
202
|
+
time_print = slice(measurements.shape[2]-time_last,measurements.shape[2])
|
|
203
|
+
|
|
204
|
+
print(f"These are the last {time_last} virtual measurements of sensor "
|
|
205
|
+
+ f"{sens_print}:")
|
|
206
|
+
|
|
207
|
+
pyv.print_measurements(tc_array,sens_print,comp_print,time_print)
|
|
208
|
+
|
|
209
|
+
print(80*"-")
|
|
210
|
+
|
|
211
|
+
(fig,ax) = pyv.plot_time_traces(tc_array,field_key)
|
|
212
|
+
|
|
213
|
+
save_traces = output_path/"customsensors_ex1_3_sensortraces.png"
|
|
214
|
+
fig.savefig(save_traces, dpi=300, bbox_inches="tight")
|
|
215
|
+
fig.savefig(save_traces.with_suffix(".svg"), dpi=300, bbox_inches="tight")
|
|
216
|
+
|
|
217
|
+
plt.show()
|
|
218
|
+
|
|
219
|
+
|
|
23
220
|
|
|
24
|
-
def main() -> None:
|
|
25
|
-
# To build our custom point sensor array we need to at minimum provide a
|
|
26
|
-
# `IField` (i.e. `FieldScaler`, `FieldVector`, `FieldTensor`) and a
|
|
27
|
-
# `SensorData` object. For labelling visualisations (e.g. axis labels and
|
|
28
|
-
# unit labels) we can also provide a `SensorDescriptor` object.
|
|
29
|
-
# Once we have built our `SensorArrayPoint` object from these we can then
|
|
30
|
-
# attach custom chains of different types of random and systematic errors
|
|
31
|
-
# to be evaluated when we run our measurement simulation. This example is
|
|
32
|
-
# based on the same thermal example we have used in the last two examples so
|
|
33
|
-
# we start by loading our simulation data:
|
|
34
|
-
|
|
35
|
-
data_path = pyv.DataSet.thermal_3d_path()
|
|
36
|
-
sim_data = mh.ExodusReader(data_path).read_all_sim_data()
|
|
37
|
-
# Scale to mm to make 3D visualisation scaling easier as pyvista scales
|
|
38
|
-
# everything to unity
|
|
39
|
-
sim_data = pyv.scale_length_units(scale=1000.0,
|
|
40
|
-
sim_data=sim_data,
|
|
41
|
-
disp_comps=None)
|
|
42
|
-
|
|
43
|
-
# We are going to build a custom temperature sensor so we need a scalar
|
|
44
|
-
# field object to perform interpolation to the sensor locations at the
|
|
45
|
-
# desired sampling times.
|
|
46
|
-
field_key: str = "temperature"
|
|
47
|
-
t_field = pyv.FieldScalar(sim_data,
|
|
48
|
-
field_key=field_key,
|
|
49
|
-
elem_dims=3)
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
# Next we need to create our `SensorData` object which will set the position
|
|
53
|
-
# and sampling times of our sensors. We use the same helper function we used
|
|
54
|
-
# previously to create a uniformly spaced grid of sensors in space
|
|
55
|
-
n_sens = (1,4,1)
|
|
56
|
-
x_lims = (12.5,12.5)
|
|
57
|
-
y_lims = (0.0,33.0)
|
|
58
|
-
z_lims = (0.0,12.0)
|
|
59
|
-
sens_pos = pyv.create_sensor_pos_array(n_sens,x_lims,y_lims,z_lims)
|
|
60
|
-
|
|
61
|
-
# We are also going to specify the times at which we would like to simulate
|
|
62
|
-
# measurements. Setting this to `None` will default the measurements times
|
|
63
|
-
# to match the simulation time steps.
|
|
64
|
-
sample_times = np.linspace(0.0,np.max(sim_data.time),50)
|
|
65
|
-
|
|
66
|
-
sensor_data = pyv.SensorData(positions=sens_pos,
|
|
67
|
-
sample_times=sample_times)
|
|
68
|
-
|
|
69
|
-
# Finally, we can create a `SensorDescriptor` which will be used to label
|
|
70
|
-
# the visualisation and sensor trace plots we have seen in previous
|
|
71
|
-
# examples.
|
|
72
|
-
use_auto_descriptor: str = "blank"
|
|
73
|
-
if use_auto_descriptor == "manual":
|
|
74
|
-
descriptor = pyv.SensorDescriptor(name="Temperature",
|
|
75
|
-
symbol="T",
|
|
76
|
-
units = r"^{\circ}C",
|
|
77
|
-
tag = "TC")
|
|
78
|
-
elif use_auto_descriptor == "factory":
|
|
79
|
-
descriptor = pyv.SensorDescriptorFactory.temperature_descriptor()
|
|
80
|
-
else:
|
|
81
|
-
descriptor = pyv.SensorDescriptor()
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
# We can now build our custom point sensor array. This sensor array has no
|
|
85
|
-
# errors so if we call `get_measurements()` or `calc_measurements()` we will
|
|
86
|
-
# be able to extract the simulation truth values at the sensor locations.
|
|
87
|
-
tc_array = pyv.SensorArrayPoint(sensor_data,
|
|
88
|
-
t_field,
|
|
89
|
-
descriptor)
|
|
90
|
-
|
|
91
|
-
# This is a new 3D simulation we are analysing so we should visualise the
|
|
92
|
-
# sensor locations before we run our measurement simulation. We use the same
|
|
93
|
-
# code as we did in example 1.1 to display the sensor locations.
|
|
94
|
-
|
|
95
|
-
# We are also going to save some figures to disk as well as displaying them
|
|
96
|
-
# interactively so we create a directory for this:
|
|
97
|
-
output_path = Path.cwd() / "pyvale-output"
|
|
98
|
-
if not output_path.is_dir():
|
|
99
|
-
output_path.mkdir(parents=True, exist_ok=True)
|
|
100
|
-
|
|
101
|
-
pv_plot = pyv.plot_point_sensors_on_sim(tc_array,field_key)
|
|
102
|
-
|
|
103
|
-
pv_plot.camera_position = [(59.354, 43.428, 69.946),
|
|
104
|
-
(-2.858, 13.189, 4.523),
|
|
105
|
-
(-0.215, 0.948, -0.233)]
|
|
106
|
-
|
|
107
|
-
save_render = output_path / "customsensors_ex1_3_sensorlocs.svg"
|
|
108
|
-
pv_plot.save_graphic(save_render) # only for .svg .eps .ps .pdf .tex
|
|
109
|
-
pv_plot.screenshot(save_render.with_suffix(".png"))
|
|
110
|
-
|
|
111
|
-
pv_plot.show()
|
|
112
|
-
|
|
113
|
-
# If we want to simulate sources of uncertainty for our sensor array we need
|
|
114
|
-
# to add an `ErrIntegrator` to our sensor array using the method
|
|
115
|
-
# `set_error_integrator()`. We provide our `ErrIntegrator` a list of error
|
|
116
|
-
# objects which will be evaluated in the order specified in the list.
|
|
117
|
-
#
|
|
118
|
-
# In pyvale errors have a type specified as: random / systematic
|
|
119
|
-
# (`EErrorType`) and a dependence `EErrDependence` as: independent /
|
|
120
|
-
# dependent. When analysing errors all random all systematic errors are
|
|
121
|
-
# grouped and summed together.
|
|
122
|
-
#
|
|
123
|
-
# The error dependence determines if an error is
|
|
124
|
-
# calculated based on the truth (independent) or the accumulated measurement
|
|
125
|
-
# based on all previous errors in the chain (dependent). Some errors are
|
|
126
|
-
# purely independent such as random noise with a normal distribution with a
|
|
127
|
-
# set standard devitation. An example of an error that is dependent would be
|
|
128
|
-
# saturation which must be place last in the error chain and will clamp the
|
|
129
|
-
# final sensor value to be within the specified bounds.
|
|
130
|
-
#
|
|
131
|
-
# pyvale provides a library of different random `ErrRand*` and systematic
|
|
132
|
-
# `ErrSys*` errors which can be found listed in the docs. In the next
|
|
133
|
-
# example we will explore the error library but for now we will specify some
|
|
134
|
-
# common error types. Try experimenting with the code below to turn the
|
|
135
|
-
# different error types off and on to see how it changes the virtual sensor
|
|
136
|
-
# measurements.
|
|
137
|
-
errors_on = {"sys": True,
|
|
138
|
-
"rand": True}
|
|
139
|
-
|
|
140
|
-
error_chain = []
|
|
141
|
-
if errors_on["sys"]:
|
|
142
|
-
# This systematic error is just a constant offset of -5 to all simulated
|
|
143
|
-
# measurements. Note that error values should be specified in the same
|
|
144
|
-
# units as the simulation.
|
|
145
|
-
error_chain.append(pyv.ErrSysOffset(offset=-10.0))
|
|
146
|
-
|
|
147
|
-
# This systematic error samples from a uniform probability distribution.
|
|
148
|
-
error_chain.append(pyv.ErrSysUnif(low=-10.0,
|
|
149
|
-
high=10.0))
|
|
150
|
-
|
|
151
|
-
if errors_on["rand"]:
|
|
152
|
-
# This random error is generated by sampling from a normal distribution
|
|
153
|
-
# with the given standard deviation in simulation units.
|
|
154
|
-
error_chain.append(pyv.ErrRandNorm(std=5.0))
|
|
155
|
-
|
|
156
|
-
# This random error is generated as a percentage sampled from uniform
|
|
157
|
-
# probability distribution
|
|
158
|
-
error_chain.append(pyv.ErrRandUnifPercent(low_percent=-5.0,
|
|
159
|
-
high_percent=5.0))
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
# By default pyvale does not store all individual error source
|
|
163
|
-
# calculations (i.e. only the total random and total systematic error are
|
|
164
|
-
# stored) to save memory but this can be changed using `ErrIntOpts`. This
|
|
165
|
-
# can also be used to force all errors to behave if they
|
|
166
|
-
|
|
167
|
-
if len(error_chain) > 0:
|
|
168
|
-
err_int_opts = pyv.ErrIntOpts()
|
|
169
|
-
error_integrator = pyv.ErrIntegrator(error_chain,
|
|
170
|
-
sensor_data,
|
|
171
|
-
tc_array.get_measurement_shape(),
|
|
172
|
-
err_int_opts=err_int_opts)
|
|
173
|
-
tc_array.set_error_integrator(error_integrator)
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
# Now that we have added our error chain we can run a simulation to sample
|
|
177
|
-
# from all our error sources.
|
|
178
|
-
measurements = tc_array.calc_measurements()
|
|
179
|
-
|
|
180
|
-
# We display the simulation results by printing to the console and by
|
|
181
|
-
# plotting the sensor times traces. Try experimenting with the errors above
|
|
182
|
-
# to see how the results change.
|
|
183
|
-
print("\n"+80*"-")
|
|
184
|
-
print("For a virtual sensor: measurement = truth + sysematic error + random error")
|
|
185
|
-
print(f"measurements.shape = {measurements.shape} = "+
|
|
186
|
-
"(n_sensors,n_field_components,n_timesteps)\n")
|
|
187
|
-
print("The truth, systematic error and random error arrays have the same "+
|
|
188
|
-
"shape.")
|
|
189
|
-
|
|
190
|
-
print(80*"-")
|
|
191
|
-
|
|
192
|
-
sens_print: int = 0
|
|
193
|
-
time_print: int = 5
|
|
194
|
-
comp_print: int = 0
|
|
195
|
-
|
|
196
|
-
print(f"These are the last {time_print} virtual measurements of sensor "
|
|
197
|
-
+ f"{sens_print}:")
|
|
198
|
-
|
|
199
|
-
pyv.print_measurements(sens_array=tc_array,
|
|
200
|
-
sensors=(sens_print,sens_print+1),
|
|
201
|
-
components=(comp_print,comp_print+1),
|
|
202
|
-
time_steps=(measurements.shape[2]-time_print,
|
|
203
|
-
measurements.shape[2]))
|
|
204
|
-
print(80*"-")
|
|
205
|
-
|
|
206
|
-
(fig,ax) = pyv.plot_time_traces(tc_array,field_key)
|
|
207
|
-
|
|
208
|
-
save_traces = output_path/"customsensors_ex1_3_sensortraces.png"
|
|
209
|
-
fig.savefig(save_traces, dpi=300, bbox_inches="tight")
|
|
210
|
-
fig.savefig(save_traces.with_suffix(".svg"), dpi=300, bbox_inches="tight")
|
|
211
|
-
|
|
212
|
-
plt.show()
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
if __name__ == "__main__":
|
|
216
|
-
main()
|
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
# ==============================================================================
|
|
6
6
|
|
|
7
7
|
"""
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
Basics: Overview of the basic error library
|
|
9
|
+
================================================================================
|
|
10
|
+
|
|
10
11
|
Building on what we learned in examples 1.1-1.3 we now have a look at the basic
|
|
11
12
|
error library for pyvale. The sensor error models in pyvale are grouped into the
|
|
12
13
|
types of random (`ErrRand*`) and systematic (`ErrSys*`). In this example we will
|
|
@@ -31,123 +32,126 @@ import mooseherder as mh
|
|
|
31
32
|
import pyvale as pyv
|
|
32
33
|
|
|
33
34
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
35
|
+
#%%
|
|
36
|
+
# First we use everything we learned from the first three examples to build
|
|
37
|
+
# a thermocouple sensor array for the same 3D thermal simulation we have
|
|
38
|
+
# analysed in the previous example.
|
|
39
|
+
data_path = pyv.DataSet.thermal_3d_path()
|
|
40
|
+
sim_data = mh.ExodusReader(data_path).read_all_sim_data()
|
|
41
|
+
sim_data = pyv.scale_length_units(scale=1000.0,
|
|
42
|
+
sim_data=sim_data,
|
|
43
|
+
disp_comps=None)
|
|
44
|
+
n_sens = (1,4,1)
|
|
45
|
+
x_lims = (12.5,12.5)
|
|
46
|
+
y_lims = (0.0,33.0)
|
|
47
|
+
z_lims = (0.0,12.0)
|
|
48
|
+
sens_pos = pyv.create_sensor_pos_array(n_sens,x_lims,y_lims,z_lims)
|
|
49
|
+
|
|
50
|
+
sample_times = np.linspace(0.0,np.max(sim_data.time),50) # | None
|
|
51
|
+
|
|
52
|
+
sensor_data = pyv.SensorData(positions=sens_pos,
|
|
53
|
+
sample_times=sample_times)
|
|
54
|
+
|
|
55
|
+
field_key: str = "temperature"
|
|
56
|
+
tc_array = pyv.SensorArrayFactory \
|
|
57
|
+
.thermocouples_no_errs(sim_data,
|
|
58
|
+
sensor_data,
|
|
59
|
+
elem_dims=3,
|
|
60
|
+
field_name=field_key)
|
|
61
|
+
|
|
62
|
+
#%%
|
|
63
|
+
# Now we have our thermocouple array applied to our simulation without any
|
|
64
|
+
# errors we can build a custom chain of basic errors. Here we will start by
|
|
65
|
+
# adding a series of systematic errors that are independent:
|
|
66
|
+
err_chain = []
|
|
67
|
+
|
|
68
|
+
#%%
|
|
69
|
+
# For probability sampling systematic errors the distribution is sampled to
|
|
70
|
+
# provide an offset which is assumed to be constant over all sensor sampling
|
|
71
|
+
# times. This is different to random errors which are sampled to provide a
|
|
72
|
+
# different error for each sensor and time step.
|
|
73
|
+
#
|
|
74
|
+
# These systematic errors provide a constant offset to all measurements in
|
|
75
|
+
# simulation units or as a percentage.
|
|
76
|
+
err_chain.append(pyv.ErrSysOffset(offset=-10.0))
|
|
77
|
+
err_chain.append(pyv.ErrSysOffsetPercent(offset_percent=-1.0))
|
|
78
|
+
|
|
79
|
+
#%%
|
|
80
|
+
# These systematic errors are sampled from a uniform or normal probability
|
|
81
|
+
# distribution either in simulation units or as a percentage.
|
|
82
|
+
err_chain.append(pyv.ErrSysUnif(low=-1.0,
|
|
83
|
+
high=1.0))
|
|
84
|
+
err_chain.append(pyv.ErrSysUnifPercent(low_percent=-1.0,
|
|
85
|
+
high_percent=1.0))
|
|
86
|
+
err_chain.append(pyv.ErrSysNorm(std=1.0))
|
|
87
|
+
err_chain.append(pyv.ErrSysNormPercent(std_percent=1.0))
|
|
88
|
+
|
|
89
|
+
#%%
|
|
90
|
+
# pyvale includes a series of random number generator objects that wrap the
|
|
91
|
+
# random number generators from numpy. These are named `Gen*` and can be
|
|
92
|
+
# used with an `ErrSysGen` or an `ErrSysGenPercent` object to create custom
|
|
93
|
+
# probability distribution sampling errors:
|
|
94
|
+
sys_gen = pyv.GenTriangular(left=-1.0,
|
|
95
|
+
mode=0.0,
|
|
96
|
+
right=1.0)
|
|
97
|
+
err_chain.append(pyv.ErrSysGen(sys_gen))
|
|
98
|
+
|
|
99
|
+
#%%
|
|
100
|
+
# We can also build the equivalent of `ErrSysUnifPercent` above using a
|
|
101
|
+
# `Gen` object inserted into an `ErrSysGenPercent` object:
|
|
102
|
+
unif_gen = pyv.GenUniform(low=-1.0,
|
|
103
|
+
high=1.0)
|
|
104
|
+
err_chain.append(pyv.ErrSysGenPercent(unif_gen))
|
|
105
|
+
|
|
106
|
+
#%%
|
|
107
|
+
# We can also add a series of random errors in a similar manner to the
|
|
108
|
+
# systematic errors above noting that these will generate a new error for
|
|
109
|
+
# each sensor and each time step whereas the systematic error sampling
|
|
110
|
+
# provides a constant shift over all sampling times for each sensor.
|
|
111
|
+
err_chain.append(pyv.ErrRandNorm(std = 2.0))
|
|
112
|
+
err_chain.append(pyv.ErrRandNormPercent(std_percent=2.0))
|
|
113
|
+
err_chain.append(pyv.ErrRandUnif(low=-2.0,high=2.0))
|
|
114
|
+
err_chain.append(pyv.ErrRandUnifPercent(low_percent=-2.0,
|
|
115
|
+
high_percent=2.0))
|
|
116
|
+
rand_gen = pyv.GenTriangular(left=-5.0,
|
|
90
117
|
mode=0.0,
|
|
91
|
-
right=
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
print(80*"-")
|
|
133
|
-
|
|
134
|
-
sens_print: int = 0
|
|
135
|
-
time_print: int = 5
|
|
136
|
-
comp_print: int = 0
|
|
137
|
-
|
|
138
|
-
print(f"These are the last {time_print} virtual measurements of sensor "
|
|
139
|
-
+ f"{sens_print}:")
|
|
140
|
-
|
|
141
|
-
pyv.print_measurements(sens_array=tc_array,
|
|
142
|
-
sensors=(sens_print,sens_print+1),
|
|
143
|
-
components=(comp_print,comp_print+1),
|
|
144
|
-
time_steps=(measurements.shape[2]-time_print,
|
|
145
|
-
measurements.shape[2]))
|
|
146
|
-
print(80*"-")
|
|
147
|
-
|
|
148
|
-
pyv.plot_time_traces(tc_array,field_key)
|
|
149
|
-
plt.show()
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
if __name__ == '__main__':
|
|
153
|
-
main()
|
|
118
|
+
right=5.0)
|
|
119
|
+
err_chain.append(pyv.ErrRandGenerator(rand_gen))
|
|
120
|
+
|
|
121
|
+
#%%
|
|
122
|
+
# Finally we add some dependent systematic errors including rounding errors,
|
|
123
|
+
# digitisation and saturation. Note that the saturation error must be placed
|
|
124
|
+
# last in the error chain. Try changing some of these values to see how the
|
|
125
|
+
# sensor traces change - particularly the saturation error.
|
|
126
|
+
err_chain.append(pyv.ErrSysRoundOff(pyv.ERoundMethod.ROUND,0.1))
|
|
127
|
+
err_chain.append(pyv.ErrSysDigitisation(bits_per_unit=2**16/100))
|
|
128
|
+
err_chain.append(pyv.ErrSysSaturation(meas_min=0.0,meas_max=400.0))
|
|
129
|
+
|
|
130
|
+
err_int = pyv.ErrIntegrator(err_chain,
|
|
131
|
+
sensor_data,
|
|
132
|
+
tc_array.get_measurement_shape())
|
|
133
|
+
tc_array.set_error_integrator(err_int)
|
|
134
|
+
|
|
135
|
+
#%%
|
|
136
|
+
# Now we can run the sensor simulation and display the results to see the
|
|
137
|
+
# different error sources as we have done in previous examples.
|
|
138
|
+
measurements = tc_array.calc_measurements()
|
|
139
|
+
|
|
140
|
+
print(80*"-")
|
|
141
|
+
|
|
142
|
+
sens_print = 0
|
|
143
|
+
comp_print = 0
|
|
144
|
+
time_last = 5
|
|
145
|
+
time_print = slice(measurements.shape[2]-time_last,measurements.shape[2])
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
print(f"These are the last {time_last} virtual measurements of sensor "
|
|
149
|
+
+ f"{sens_print}:")
|
|
150
|
+
|
|
151
|
+
pyv.print_measurements(tc_array,sens_print,comp_print,time_print)
|
|
152
|
+
|
|
153
|
+
print(80*"-")
|
|
154
|
+
|
|
155
|
+
pyv.plot_time_traces(tc_array,field_key)
|
|
156
|
+
plt.show()
|
|
157
|
+
|