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.

Files changed (95) hide show
  1. pyvale/__init__.py +12 -0
  2. pyvale/blendercalibrationdata.py +3 -1
  3. pyvale/blenderscene.py +7 -5
  4. pyvale/blendertools.py +27 -5
  5. pyvale/camera.py +1 -0
  6. pyvale/cameradata.py +3 -0
  7. pyvale/camerasensor.py +147 -0
  8. pyvale/camerastereo.py +4 -4
  9. pyvale/cameratools.py +23 -61
  10. pyvale/cython/rastercyth.c +1657 -1352
  11. pyvale/cython/rastercyth.cp311-win32.pyd +0 -0
  12. pyvale/cython/rastercyth.py +71 -26
  13. pyvale/data/DIC_Challenge_Star_Noise_Def.tiff +0 -0
  14. pyvale/data/DIC_Challenge_Star_Noise_Ref.tiff +0 -0
  15. pyvale/data/plate_hole_def0000.tiff +0 -0
  16. pyvale/data/plate_hole_def0001.tiff +0 -0
  17. pyvale/data/plate_hole_ref0000.tiff +0 -0
  18. pyvale/data/plate_rigid_def0000.tiff +0 -0
  19. pyvale/data/plate_rigid_def0001.tiff +0 -0
  20. pyvale/data/plate_rigid_ref0000.tiff +0 -0
  21. pyvale/dataset.py +96 -6
  22. pyvale/dic/cpp/dicbruteforce.cpp +370 -0
  23. pyvale/dic/cpp/dicfourier.cpp +648 -0
  24. pyvale/dic/cpp/dicinterpolator.cpp +559 -0
  25. pyvale/dic/cpp/dicmain.cpp +215 -0
  26. pyvale/dic/cpp/dicoptimizer.cpp +675 -0
  27. pyvale/dic/cpp/dicrg.cpp +137 -0
  28. pyvale/dic/cpp/dicscanmethod.cpp +677 -0
  29. pyvale/dic/cpp/dicsmooth.cpp +138 -0
  30. pyvale/dic/cpp/dicstrain.cpp +383 -0
  31. pyvale/dic/cpp/dicutil.cpp +563 -0
  32. pyvale/dic2d.py +164 -0
  33. pyvale/dic2dcpp.cp311-win32.pyd +0 -0
  34. pyvale/dicchecks.py +476 -0
  35. pyvale/dicdataimport.py +247 -0
  36. pyvale/dicregionofinterest.py +887 -0
  37. pyvale/dicresults.py +55 -0
  38. pyvale/dicspecklegenerator.py +238 -0
  39. pyvale/dicspecklequality.py +305 -0
  40. pyvale/dicstrain.py +387 -0
  41. pyvale/dicstrainresults.py +37 -0
  42. pyvale/errorintegrator.py +10 -8
  43. pyvale/examples/basics/ex1_1_basicscalars_therm2d.py +124 -113
  44. pyvale/examples/basics/ex1_2_sensormodel_therm2d.py +124 -132
  45. pyvale/examples/basics/ex1_3_customsens_therm3d.py +199 -195
  46. pyvale/examples/basics/ex1_4_basicerrors_therm3d.py +125 -121
  47. pyvale/examples/basics/ex1_5_fielderrs_therm3d.py +145 -141
  48. pyvale/examples/basics/ex1_6_caliberrs_therm2d.py +96 -101
  49. pyvale/examples/basics/ex1_7_spatavg_therm2d.py +109 -105
  50. pyvale/examples/basics/ex2_1_basicvectors_disp2d.py +92 -91
  51. pyvale/examples/basics/ex2_2_vectorsens_disp2d.py +96 -90
  52. pyvale/examples/basics/ex2_3_sensangle_disp2d.py +88 -89
  53. pyvale/examples/basics/ex2_4_chainfielderrs_disp2d.py +172 -171
  54. pyvale/examples/basics/ex2_5_vectorfields3d_disp3d.py +88 -86
  55. pyvale/examples/basics/ex3_1_basictensors_strain2d.py +90 -90
  56. pyvale/examples/basics/ex3_2_tensorsens2d_strain2d.py +93 -91
  57. pyvale/examples/basics/ex3_3_tensorsens3d_strain3d.py +172 -160
  58. pyvale/examples/basics/ex4_1_expsim2d_thermmech2d.py +154 -148
  59. pyvale/examples/basics/ex4_2_expsim3d_thermmech3d.py +249 -231
  60. pyvale/examples/dic/ex1_region_of_interest.py +98 -0
  61. pyvale/examples/dic/ex2_plate_with_hole.py +149 -0
  62. pyvale/examples/dic/ex3_plate_with_hole_strain.py +93 -0
  63. pyvale/examples/dic/ex4_dic_blender.py +95 -0
  64. pyvale/examples/dic/ex5_dic_challenge.py +102 -0
  65. pyvale/examples/imagedef2d/ex_imagedef2d_todisk.py +4 -2
  66. pyvale/examples/renderblender/ex1_1_blenderscene.py +152 -105
  67. pyvale/examples/renderblender/ex1_2_blenderdeformed.py +151 -100
  68. pyvale/examples/renderblender/ex2_1_stereoscene.py +183 -116
  69. pyvale/examples/renderblender/ex2_2_stereodeformed.py +185 -112
  70. pyvale/examples/renderblender/ex3_1_blendercalibration.py +164 -109
  71. pyvale/examples/renderrasterisation/ex_rastenp.py +74 -35
  72. pyvale/examples/renderrasterisation/ex_rastercyth_oneframe.py +6 -13
  73. pyvale/examples/renderrasterisation/ex_rastercyth_static_cypara.py +2 -2
  74. pyvale/examples/renderrasterisation/ex_rastercyth_static_pypara.py +2 -4
  75. pyvale/imagedef2d.py +3 -2
  76. pyvale/imagetools.py +137 -0
  77. pyvale/rastercy.py +34 -4
  78. pyvale/rasternp.py +300 -276
  79. pyvale/rasteropts.py +58 -0
  80. pyvale/renderer.py +47 -0
  81. pyvale/rendermesh.py +52 -62
  82. pyvale/renderscene.py +51 -0
  83. pyvale/sensorarrayfactory.py +2 -2
  84. pyvale/sensortools.py +19 -35
  85. pyvale/simcases/case21.i +1 -1
  86. pyvale/simcases/run_1case.py +8 -0
  87. pyvale/simtools.py +2 -2
  88. pyvale/visualsimplotter.py +180 -0
  89. {pyvale-2025.5.3.dist-info → pyvale-2025.7.1.dist-info}/METADATA +11 -57
  90. {pyvale-2025.5.3.dist-info → pyvale-2025.7.1.dist-info}/RECORD +93 -56
  91. {pyvale-2025.5.3.dist-info → pyvale-2025.7.1.dist-info}/WHEEL +1 -1
  92. pyvale/examples/visualisation/ex1_1_plot_traces.py +0 -102
  93. pyvale/examples/visualisation/ex2_1_animate_sim.py +0 -89
  94. {pyvale-2025.5.3.dist-info → pyvale-2025.7.1.dist-info}/licenses/LICENSE +0 -0
  95. {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
- Pyvale example: Field-based systematic errors
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
- Pyvale example: Sensor calibration systematic errors
9
- --------------------------------------------------------------------------------
10
- In this example we show how pyvale can simulate sensor calibration errors with
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
- def main() -> None:
37
-
38
- # We are first going to do a quick analytical calculation for the minimum
39
- # and maximum systematic error we expect between our assumed and true
40
- # calibration. For our true calibration we know this holds between 0 and 6mV
41
- # so we perform the calculation over this range and print the min/max
42
- # expected error over this range.
43
- n_cal_divs = 10000
44
- signal_calib_range = np.array((0.0,6.0),dtype=np.float64)
45
- milli_volts = np.linspace(signal_calib_range[0],
46
- signal_calib_range[1],
47
- n_cal_divs)
48
- temp_truth = truth_calib(milli_volts)
49
- temp_assumed = assumed_calib(milli_volts)
50
- calib_error = temp_assumed - temp_truth
51
-
52
- print()
53
- print(80*"-")
54
- print(f"Max calibrated temperature: {np.min(temp_truth)} degC")
55
- print(f"Min calibrated temperature: {np.max(temp_truth)} degC")
56
- print()
57
- print(f"Calibration error over signal:"
58
- + f" {signal_calib_range[0]} to {signal_calib_range[1]} mV")
59
- print(f"Max calib error: {np.max(calib_error)}")
60
- print(f"Min calib error: {np.min(calib_error)}")
61
- print(80*"-")
62
- print()
63
-
64
-
65
- # Now let's go back and build the 2D thermal plate with simulated
66
- # thermocouples that we analysed in the first two examples. We use this
67
- # simulation as the temperatures are within our calibrated range.
68
- data_path = pyv.DataSet.thermal_2d_path()
69
- sim_data = mh.ExodusReader(data_path).read_all_sim_data()
70
- sim_data = pyv.scale_length_units(scale=1000.0,
71
- sim_data=sim_data,
72
- disp_comps=None)
73
-
74
- n_sens = (4,1,1)
75
- x_lims = (0.0,100.0)
76
- y_lims = (0.0,50.0)
77
- z_lims = (0.0,0.0)
78
- sens_pos = pyv.create_sensor_pos_array(n_sens,x_lims,y_lims,z_lims)
79
-
80
- sample_times = np.linspace(0.0,np.max(sim_data.time),50) # | None
81
-
82
- sensor_data = pyv.SensorData(positions=sens_pos,
83
- sample_times=sample_times)
84
-
85
- field_key: str = "temperature"
86
- tc_array = pyv.SensorArrayFactory \
87
- .thermocouples_no_errs(sim_data,
88
- sensor_data,
89
- elem_dims=2,
90
- field_name=field_key)
91
-
92
-
93
- # With our assumed and true calibration functions we can build our
94
- # calibration error object and add it to our error chain as normal. Note
95
- # that the truth calibration function must be inverted numerically so to
96
- # increase accuracy the number of divisions can be increased. However, 1e4
97
- # divisions should be suitable for most applications.
98
- cal_err = pyv.ErrSysCalibration(assumed_calib,
99
- truth_calib,
100
- signal_calib_range,
101
- n_cal_divs=10000)
102
- sys_err_int = pyv.ErrIntegrator([cal_err],
103
- sensor_data,
104
- tc_array.get_measurement_shape())
105
- tc_array.set_error_integrator(sys_err_int)
106
-
107
- # Now we run our sensor simulation to see what our calibration does.
108
- measurements = tc_array.calc_measurements()
109
-
110
- print(80*"-")
111
-
112
- sens_print: int = 0
113
- time_print: int = 5
114
- comp_print: int = 0
115
-
116
- print(f"These are the last {time_print} virtual measurements of sensor "
117
- + f"{sens_print}:")
118
-
119
- pyv.print_measurements(sens_array=tc_array,
120
- sensors=(sens_print,sens_print+1),
121
- components=(comp_print,comp_print+1),
122
- time_steps=(measurements.shape[2]-time_print,
123
- measurements.shape[2]))
124
- print(80*"-")
125
-
126
- pyv.plot_time_traces(tc_array,field_key)
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
+