pyvale 2025.5.3__cp311-cp311-win32.whl → 2025.7.1__cp311-cp311-win32.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-win32.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-win32.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: Field-based systematic errors
|
|
9
|
+
================================================================================
|
|
10
|
+
|
|
10
11
|
In this example we give an overview of field-based systematic errors. Field
|
|
11
12
|
errors require additional interpolation of the underlying physical field such as
|
|
12
13
|
uncertainty in a sensors position or sampling time. For this example we will
|
|
@@ -26,143 +27,146 @@ import matplotlib.pyplot as plt
|
|
|
26
27
|
import mooseherder as mh
|
|
27
28
|
import pyvale as pyv
|
|
28
29
|
|
|
30
|
+
#%%
|
|
31
|
+
# First we use everything we learned from the first three examples to build
|
|
32
|
+
# a thermocouple sensor array for the same 3D thermal simulation we have
|
|
33
|
+
# analysed in the previous examples. Then we will look at a new type of
|
|
34
|
+
# systematic error called a field error which requires additional
|
|
35
|
+
# interpolation of the underlying physical field to be measured.
|
|
36
|
+
data_path = pyv.DataSet.thermal_3d_path()
|
|
37
|
+
sim_data = mh.ExodusReader(data_path).read_all_sim_data()
|
|
38
|
+
sim_data = pyv.scale_length_units(scale=1000.0,
|
|
39
|
+
sim_data=sim_data,
|
|
40
|
+
disp_comps=None)
|
|
41
|
+
|
|
42
|
+
n_sens = (1,4,1)
|
|
43
|
+
x_lims = (12.5,12.5)
|
|
44
|
+
y_lims = (0.0,33.0)
|
|
45
|
+
z_lims = (0.0,12.0)
|
|
46
|
+
sens_pos = pyv.create_sensor_pos_array(n_sens,x_lims,y_lims,z_lims)
|
|
47
|
+
|
|
48
|
+
sample_times = np.linspace(0.0,np.max(sim_data.time),50) # | None
|
|
49
|
+
|
|
50
|
+
sensor_data = pyv.SensorData(positions=sens_pos,
|
|
51
|
+
sample_times=sample_times)
|
|
52
|
+
|
|
53
|
+
field_key: str = "temperature"
|
|
54
|
+
tc_array = pyv.SensorArrayFactory \
|
|
55
|
+
.thermocouples_no_errs(sim_data,
|
|
56
|
+
sensor_data,
|
|
57
|
+
elem_dims=3,
|
|
58
|
+
field_name=field_key)
|
|
59
|
+
#%%
|
|
60
|
+
# Now we will create a field error data class which we will use to build our
|
|
61
|
+
# field error. This controls which sensor parameters will be perturbed such
|
|
62
|
+
# as: position, time and orientation. Here we will perturb the sensor
|
|
63
|
+
# positions on the face of the block using a normal distribution and we will
|
|
64
|
+
# also perturb the measurement times.
|
|
65
|
+
#
|
|
66
|
+
# We can apply a constant offset to each sensors position in x,y,z by
|
|
67
|
+
# providing a shape=(num_sensors,coord[x,y,z]) array. Here we apply a
|
|
68
|
+
# constant offset in the y and z direction for all sensors.
|
|
69
|
+
pos_offset_xyz = np.array((0.0,1.0,1.0),dtype=np.float64)
|
|
70
|
+
pos_offset_xyz = np.tile(pos_offset_xyz,(sens_pos.shape[0],1))
|
|
71
|
+
|
|
72
|
+
#%%
|
|
73
|
+
# We can also apply a constant offset to the sampling times for all sensors
|
|
74
|
+
time_offset = np.full((sample_times.shape[0],),0.1)
|
|
75
|
+
|
|
76
|
+
#%%
|
|
77
|
+
# Using the `Gen*` random generators in pyvale we can randomly perturb the
|
|
78
|
+
# position or sampling times of our virtual sensors.
|
|
79
|
+
pos_rand = pyv.GenNormal(std=1.0) # units = mm
|
|
80
|
+
time_rand = pyv.GenNormal(std=0.1) # units = s
|
|
81
|
+
|
|
82
|
+
#%%
|
|
83
|
+
# Now we put everything into our field error data class ready to build our
|
|
84
|
+
# field error object. Have a look at the other parameters in this data class
|
|
85
|
+
# to geta feel for the other types of supported field errors. We will look
|
|
86
|
+
# at the orientation and area averaging errors when we look at vector and
|
|
87
|
+
# tensor fields in later examples.
|
|
88
|
+
field_err_data = pyv.ErrFieldData(
|
|
89
|
+
pos_offset_xyz=pos_offset_xyz,
|
|
90
|
+
time_offset=time_offset,
|
|
91
|
+
pos_rand_xyz=(None,pos_rand,pos_rand),
|
|
92
|
+
time_rand=time_rand
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
#%%
|
|
96
|
+
# Adding our field error to our error chain is exactly the same as the basic
|
|
97
|
+
# errors we have seen previously. We can also combine field errors with
|
|
98
|
+
# basic errors and place them anywhere in our error chain. We can even chain
|
|
99
|
+
# field errors together which we will look at in the next example. For now
|
|
100
|
+
# we will just have a single field error so we can easily visualise what
|
|
101
|
+
# this type of error does.
|
|
102
|
+
err_chain = []
|
|
103
|
+
|
|
104
|
+
#%%
|
|
105
|
+
# A field error needs to know which field it should interpolate for error
|
|
106
|
+
# calculations so we provide the field from the sensor array as well as the
|
|
107
|
+
# field error error data class.
|
|
108
|
+
err_chain.append(pyv.ErrSysField(tc_array.get_field(),
|
|
109
|
+
field_err_data))
|
|
110
|
+
err_int = pyv.ErrIntegrator(err_chain,
|
|
111
|
+
sensor_data,
|
|
112
|
+
tc_array.get_measurement_shape())
|
|
113
|
+
tc_array.set_error_integrator(err_int)
|
|
114
|
+
|
|
115
|
+
#%%
|
|
116
|
+
# Now we can run the sensor simulation and display the results to see what
|
|
117
|
+
# our field error has done.
|
|
118
|
+
measurements = tc_array.calc_measurements()
|
|
119
|
+
|
|
120
|
+
print(80*"-")
|
|
121
|
+
|
|
122
|
+
sens_print = 0
|
|
123
|
+
comp_print = 0
|
|
124
|
+
time_last = 5
|
|
125
|
+
time_print = slice(measurements.shape[2]-time_last,measurements.shape[2])
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
print(f"These are the last {time_last} virtual measurements of sensor "
|
|
129
|
+
+ f"{sens_print}:")
|
|
130
|
+
|
|
131
|
+
pyv.print_measurements(tc_array,sens_print,comp_print,time_print)
|
|
132
|
+
|
|
133
|
+
print(80*"-")
|
|
134
|
+
|
|
135
|
+
#%%
|
|
136
|
+
# We are going to save some figures to disk as well as displaying them
|
|
137
|
+
# interactively so we create a directory for this:
|
|
138
|
+
output_path = Path.cwd() / "pyvale-output"
|
|
139
|
+
if not output_path.is_dir():
|
|
140
|
+
output_path.mkdir(parents=True, exist_ok=True)
|
|
141
|
+
|
|
142
|
+
#%%
|
|
143
|
+
# If we analyse the time traces we can see offsets in the sensor value and
|
|
144
|
+
# the sampling times which we expect from our field error setup.
|
|
145
|
+
(fig,ax) = pyv.plot_time_traces(tc_array,field_key)
|
|
146
|
+
|
|
147
|
+
save_traces = output_path/"field_ex1_5_sensortraces.png"
|
|
148
|
+
fig.savefig(save_traces, dpi=300, bbox_inches="tight")
|
|
149
|
+
fig.savefig(save_traces.with_suffix(".svg"), dpi=300, bbox_inches="tight")
|
|
150
|
+
|
|
151
|
+
plt.show()
|
|
152
|
+
|
|
153
|
+
#%%
|
|
154
|
+
# It is also possible to view the perturbed sensor locations on the
|
|
155
|
+
# simulation mesh if we create a plot after running the sensor simulation.
|
|
156
|
+
pv_plot = pyv.plot_point_sensors_on_sim(tc_array,field_key)
|
|
157
|
+
pv_plot.camera_position = [(59.354, 43.428, 69.946),
|
|
158
|
+
(-2.858, 13.189, 4.523),
|
|
159
|
+
(-0.215, 0.948, -0.233)]
|
|
160
|
+
|
|
161
|
+
save_render = output_path / "fielderrs_ex1_5_sensorlocs.svg"
|
|
162
|
+
pv_plot.save_graphic(save_render) # only for .svg .eps .ps .pdf .tex
|
|
163
|
+
pv_plot.screenshot(save_render.with_suffix(".png"))
|
|
164
|
+
|
|
165
|
+
pv_plot.show()
|
|
166
|
+
|
|
167
|
+
#%%
|
|
168
|
+
# We have saved an image of the sensor traces and the perturbed locations of
|
|
169
|
+
# the sensors to the `pyvale-output` directory in your current working
|
|
170
|
+
# directory. Analyse these figures side by side to show that the location of
|
|
171
|
+
# the perturbed sensor locations matches the expected sensor traces.
|
|
29
172
|
|
|
30
|
-
def main() -> None:
|
|
31
|
-
# First we use everything we learned from the first three examples to build
|
|
32
|
-
# a thermocouple sensor array for the same 3D thermal simulation we have
|
|
33
|
-
# analysed in the previous examples. Then we will look at a new type of
|
|
34
|
-
# systematic error called a field error which requires additional
|
|
35
|
-
# interpolation of the underlying physical field to be measured.
|
|
36
|
-
data_path = pyv.DataSet.thermal_3d_path()
|
|
37
|
-
sim_data = mh.ExodusReader(data_path).read_all_sim_data()
|
|
38
|
-
sim_data = pyv.scale_length_units(scale=1000.0,
|
|
39
|
-
sim_data=sim_data,
|
|
40
|
-
disp_comps=None)
|
|
41
|
-
|
|
42
|
-
n_sens = (1,4,1)
|
|
43
|
-
x_lims = (12.5,12.5)
|
|
44
|
-
y_lims = (0.0,33.0)
|
|
45
|
-
z_lims = (0.0,12.0)
|
|
46
|
-
sens_pos = pyv.create_sensor_pos_array(n_sens,x_lims,y_lims,z_lims)
|
|
47
|
-
|
|
48
|
-
sample_times = np.linspace(0.0,np.max(sim_data.time),50) # | None
|
|
49
|
-
|
|
50
|
-
sensor_data = pyv.SensorData(positions=sens_pos,
|
|
51
|
-
sample_times=sample_times)
|
|
52
|
-
|
|
53
|
-
field_key: str = "temperature"
|
|
54
|
-
tc_array = pyv.SensorArrayFactory \
|
|
55
|
-
.thermocouples_no_errs(sim_data,
|
|
56
|
-
sensor_data,
|
|
57
|
-
elem_dims=3,
|
|
58
|
-
field_name=field_key)
|
|
59
|
-
|
|
60
|
-
# Now we will create a field error data class which we will use to build our
|
|
61
|
-
# field error. This controls which sensor parameters will be perturbed such
|
|
62
|
-
# as: position, time and orientation. Here we will perturb the sensor
|
|
63
|
-
# positions on the face of the block using a normal distribution and we will
|
|
64
|
-
# also perturb the measurement times.
|
|
65
|
-
|
|
66
|
-
# We can apply a constant offset to each sensors position in x,y,z by
|
|
67
|
-
# providing a shape=(num_sensors,coord[x,y,z]) array. Here we apply a
|
|
68
|
-
# constant offset in the y and z direction for all sensors.
|
|
69
|
-
pos_offset_xyz = np.array((0.0,1.0,1.0),dtype=np.float64)
|
|
70
|
-
pos_offset_xyz = np.tile(pos_offset_xyz,(sens_pos.shape[0],1))
|
|
71
|
-
|
|
72
|
-
# We can also apply a constant offset to the sampling times for all sensors
|
|
73
|
-
time_offset = np.full((sample_times.shape[0],),0.1)
|
|
74
|
-
|
|
75
|
-
# Using the `Gen*` random generators in pyvale we can randomly perturb the
|
|
76
|
-
# position or sampling times of our virtual sensors.
|
|
77
|
-
pos_rand = pyv.GenNormal(std=1.0) # units = mm
|
|
78
|
-
time_rand = pyv.GenNormal(std=0.1) # units = s
|
|
79
|
-
|
|
80
|
-
# Now we put everything into our field error data class ready to build our
|
|
81
|
-
# field error object. Have a look at the other parameters in this data class
|
|
82
|
-
# to geta feel for the other types of supported field errors. We will look
|
|
83
|
-
# at the orientation and area averaging errors when we look at vector and
|
|
84
|
-
# tensor fields in later examples.
|
|
85
|
-
field_err_data = pyv.ErrFieldData(
|
|
86
|
-
pos_offset_xyz=pos_offset_xyz,
|
|
87
|
-
time_offset=time_offset,
|
|
88
|
-
pos_rand_xyz=(None,pos_rand,pos_rand),
|
|
89
|
-
time_rand=time_rand
|
|
90
|
-
)
|
|
91
|
-
|
|
92
|
-
# Adding our field error to our error chain is exactly the same as the basic
|
|
93
|
-
# errors we have seen previously. We can also combine field errors with
|
|
94
|
-
# basic errors and place them anywhere in our error chain. We can even chain
|
|
95
|
-
# field errors together which we will look at in the next example. For now
|
|
96
|
-
# we will just have a single field error so we can easily visualise what
|
|
97
|
-
# this type of error does.
|
|
98
|
-
err_chain = []
|
|
99
|
-
|
|
100
|
-
# A field error needs to know which field it should interpolate for error
|
|
101
|
-
# calculations so we provide the field from the sensor array as well as the
|
|
102
|
-
# field error error data class.
|
|
103
|
-
err_chain.append(pyv.ErrSysField(tc_array.get_field(),
|
|
104
|
-
field_err_data))
|
|
105
|
-
err_int = pyv.ErrIntegrator(err_chain,
|
|
106
|
-
sensor_data,
|
|
107
|
-
tc_array.get_measurement_shape())
|
|
108
|
-
tc_array.set_error_integrator(err_int)
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
# Now we can run the sensor simulation and display the results to see what
|
|
112
|
-
# our field error has done.
|
|
113
|
-
measurements = tc_array.calc_measurements()
|
|
114
|
-
|
|
115
|
-
print(80*"-")
|
|
116
|
-
|
|
117
|
-
sens_print: int = 0
|
|
118
|
-
time_print: int = 5
|
|
119
|
-
comp_print: int = 0
|
|
120
|
-
|
|
121
|
-
print(f"These are the last {time_print} virtual measurements of sensor "
|
|
122
|
-
+ f"{sens_print}:")
|
|
123
|
-
|
|
124
|
-
pyv.print_measurements(sens_array=tc_array,
|
|
125
|
-
sensors=(sens_print,sens_print+1),
|
|
126
|
-
components=(comp_print,comp_print+1),
|
|
127
|
-
time_steps=(measurements.shape[2]-time_print,
|
|
128
|
-
measurements.shape[2]))
|
|
129
|
-
print(80*"-")
|
|
130
|
-
|
|
131
|
-
# We are going to save some figures to disk as well as displaying them
|
|
132
|
-
# interactively so we create a directory for this:
|
|
133
|
-
output_path = Path.cwd() / "pyvale-output"
|
|
134
|
-
if not output_path.is_dir():
|
|
135
|
-
output_path.mkdir(parents=True, exist_ok=True)
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
# If we analyse the time traces we can see offsets in the sensor value and
|
|
139
|
-
# the sampling times which we expect from our field error setup.
|
|
140
|
-
(fig,ax) = pyv.plot_time_traces(tc_array,field_key)
|
|
141
|
-
|
|
142
|
-
save_traces = output_path/"field_ex1_5_sensortraces.png"
|
|
143
|
-
fig.savefig(save_traces, dpi=300, bbox_inches="tight")
|
|
144
|
-
fig.savefig(save_traces.with_suffix(".svg"), dpi=300, bbox_inches="tight")
|
|
145
|
-
|
|
146
|
-
plt.show()
|
|
147
|
-
|
|
148
|
-
# It is also possible to view the perturbed sensor locations on the
|
|
149
|
-
# simulation mesh if we create a plot after running the sensor simulation.
|
|
150
|
-
pv_plot = pyv.plot_point_sensors_on_sim(tc_array,field_key)
|
|
151
|
-
pv_plot.camera_position = [(59.354, 43.428, 69.946),
|
|
152
|
-
(-2.858, 13.189, 4.523),
|
|
153
|
-
(-0.215, 0.948, -0.233)]
|
|
154
|
-
|
|
155
|
-
save_render = output_path / "fielderrs_ex1_5_sensorlocs.svg"
|
|
156
|
-
pv_plot.save_graphic(save_render) # only for .svg .eps .ps .pdf .tex
|
|
157
|
-
pv_plot.screenshot(save_render.with_suffix(".png"))
|
|
158
|
-
|
|
159
|
-
pv_plot.show()
|
|
160
|
-
|
|
161
|
-
# We have saved an image of the sensor traces and the perturbed locations of
|
|
162
|
-
# the sensors to the `pyvale-output` directory in your current working
|
|
163
|
-
# directory. Analyse these figures side by side to show that the location of
|
|
164
|
-
# the perturbed sensor locations matches the expected sensor traces.
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
if __name__ == '__main__':
|
|
168
|
-
main()
|
|
@@ -5,9 +5,10 @@
|
|
|
5
5
|
# ==============================================================================
|
|
6
6
|
|
|
7
7
|
"""
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
Basics: Sensor calibration systematic errors
|
|
9
|
+
================================================================================
|
|
10
|
+
|
|
11
|
+
In this example we show how `pyvale` can simulate sensor calibration errors with
|
|
11
12
|
user defined calibration functions.
|
|
12
13
|
|
|
13
14
|
Test case: Scalar field point sensors (thermocouples) on a 2D thermal simulation
|
|
@@ -18,6 +19,7 @@ import matplotlib.pyplot as plt
|
|
|
18
19
|
import mooseherder as mh
|
|
19
20
|
import pyvale as pyv
|
|
20
21
|
|
|
22
|
+
#%%
|
|
21
23
|
# First we need to define some calibration functions. These functions must take
|
|
22
24
|
# a numpy array and return a numpy array of the same shape. We start by
|
|
23
25
|
# defining what we think our calibration is called `assumed_calib()` and then
|
|
@@ -33,101 +35,94 @@ def truth_calib(signal: np.ndarray) -> np.ndarray:
|
|
|
33
35
|
return -0.01897 + 25.41881*signal - 0.42456*signal**2 + 0.04365*signal**3
|
|
34
36
|
|
|
35
37
|
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
plt.show()
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
if __name__ == "__main__":
|
|
133
|
-
main()
|
|
38
|
+
#%%
|
|
39
|
+
# We are first going to do a quick analytical calculation for the minimum
|
|
40
|
+
# and maximum systematic error we expect between our assumed and true
|
|
41
|
+
# calibration. For our true calibration we know this holds between 0 and 6mV
|
|
42
|
+
# so we perform the calculation over this range and print the min/max
|
|
43
|
+
# expected error over this range.
|
|
44
|
+
n_cal_divs = 10000
|
|
45
|
+
signal_calib_range = np.array((0.0,6.0),dtype=np.float64)
|
|
46
|
+
milli_volts = np.linspace(signal_calib_range[0],
|
|
47
|
+
signal_calib_range[1],
|
|
48
|
+
n_cal_divs)
|
|
49
|
+
temp_truth = truth_calib(milli_volts)
|
|
50
|
+
temp_assumed = assumed_calib(milli_volts)
|
|
51
|
+
calib_error = temp_assumed - temp_truth
|
|
52
|
+
|
|
53
|
+
print()
|
|
54
|
+
print(80*"-")
|
|
55
|
+
print(f"Max calibrated temperature: {np.min(temp_truth)} degC")
|
|
56
|
+
print(f"Min calibrated temperature: {np.max(temp_truth)} degC")
|
|
57
|
+
print()
|
|
58
|
+
print(f"Calibration error over signal:"
|
|
59
|
+
+ f" {signal_calib_range[0]} to {signal_calib_range[1]} mV")
|
|
60
|
+
print(f"Max calib error: {np.max(calib_error)}")
|
|
61
|
+
print(f"Min calib error: {np.min(calib_error)}")
|
|
62
|
+
print(80*"-")
|
|
63
|
+
print()
|
|
64
|
+
|
|
65
|
+
#%%
|
|
66
|
+
# Now let's go back and build the 2D thermal plate with simulated
|
|
67
|
+
# thermocouples that we analysed in the first two examples. We use this
|
|
68
|
+
# simulation as the temperatures are within our calibrated range.
|
|
69
|
+
data_path = pyv.DataSet.thermal_2d_path()
|
|
70
|
+
sim_data = mh.ExodusReader(data_path).read_all_sim_data()
|
|
71
|
+
sim_data = pyv.scale_length_units(scale=1000.0,
|
|
72
|
+
sim_data=sim_data,
|
|
73
|
+
disp_comps=None)
|
|
74
|
+
|
|
75
|
+
n_sens = (4,1,1)
|
|
76
|
+
x_lims = (0.0,100.0)
|
|
77
|
+
y_lims = (0.0,50.0)
|
|
78
|
+
z_lims = (0.0,0.0)
|
|
79
|
+
sens_pos = pyv.create_sensor_pos_array(n_sens,x_lims,y_lims,z_lims)
|
|
80
|
+
|
|
81
|
+
sample_times = np.linspace(0.0,np.max(sim_data.time),50) # | None
|
|
82
|
+
|
|
83
|
+
sensor_data = pyv.SensorData(positions=sens_pos,
|
|
84
|
+
sample_times=sample_times)
|
|
85
|
+
|
|
86
|
+
field_key: str = "temperature"
|
|
87
|
+
tc_array = pyv.SensorArrayFactory \
|
|
88
|
+
.thermocouples_no_errs(sim_data,
|
|
89
|
+
sensor_data,
|
|
90
|
+
elem_dims=2,
|
|
91
|
+
field_name=field_key)
|
|
92
|
+
|
|
93
|
+
#%%
|
|
94
|
+
# With our assumed and true calibration functions we can build our
|
|
95
|
+
# calibration error object and add it to our error chain as normal. Note
|
|
96
|
+
# that the truth calibration function must be inverted numerically so to
|
|
97
|
+
# increase accuracy the number of divisions can be increased. However, 1e4
|
|
98
|
+
# divisions should be suitable for most applications.
|
|
99
|
+
cal_err = pyv.ErrSysCalibration(assumed_calib,
|
|
100
|
+
truth_calib,
|
|
101
|
+
signal_calib_range,
|
|
102
|
+
n_cal_divs=10000)
|
|
103
|
+
sys_err_int = pyv.ErrIntegrator([cal_err],
|
|
104
|
+
sensor_data,
|
|
105
|
+
tc_array.get_measurement_shape())
|
|
106
|
+
tc_array.set_error_integrator(sys_err_int)
|
|
107
|
+
|
|
108
|
+
#%%
|
|
109
|
+
# Now we run our sensor simulation to see what our calibration does.
|
|
110
|
+
measurements = tc_array.calc_measurements()
|
|
111
|
+
|
|
112
|
+
print(80*"-")
|
|
113
|
+
|
|
114
|
+
sens_print = 0
|
|
115
|
+
comp_print = 0
|
|
116
|
+
time_last = 5
|
|
117
|
+
time_print = slice(measurements.shape[2]-time_last,measurements.shape[2])
|
|
118
|
+
|
|
119
|
+
print(f"These are the last {time_last} virtual measurements of sensor "
|
|
120
|
+
+ f"{sens_print}:")
|
|
121
|
+
|
|
122
|
+
pyv.print_measurements(tc_array,sens_print,comp_print,time_print)
|
|
123
|
+
|
|
124
|
+
print(80*"-")
|
|
125
|
+
|
|
126
|
+
pyv.plot_time_traces(tc_array,field_key)
|
|
127
|
+
plt.show()
|
|
128
|
+
|