pyvale 2025.5.3__cp311-cp311-win32.whl → 2025.7.0__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 (93) 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/plate_hole_def0000.tiff +0 -0
  14. pyvale/data/plate_hole_def0001.tiff +0 -0
  15. pyvale/data/plate_hole_ref0000.tiff +0 -0
  16. pyvale/data/plate_rigid_def0000.tiff +0 -0
  17. pyvale/data/plate_rigid_def0001.tiff +0 -0
  18. pyvale/data/plate_rigid_ref0000.tiff +0 -0
  19. pyvale/dataset.py +96 -6
  20. pyvale/dic/cpp/dicbruteforce.cpp +370 -0
  21. pyvale/dic/cpp/dicfourier.cpp +648 -0
  22. pyvale/dic/cpp/dicinterpolator.cpp +559 -0
  23. pyvale/dic/cpp/dicmain.cpp +215 -0
  24. pyvale/dic/cpp/dicoptimizer.cpp +675 -0
  25. pyvale/dic/cpp/dicrg.cpp +137 -0
  26. pyvale/dic/cpp/dicscanmethod.cpp +677 -0
  27. pyvale/dic/cpp/dicsmooth.cpp +138 -0
  28. pyvale/dic/cpp/dicstrain.cpp +383 -0
  29. pyvale/dic/cpp/dicutil.cpp +563 -0
  30. pyvale/dic2d.py +164 -0
  31. pyvale/dic2dcpp.cp311-win32.pyd +0 -0
  32. pyvale/dicchecks.py +476 -0
  33. pyvale/dicdataimport.py +247 -0
  34. pyvale/dicregionofinterest.py +887 -0
  35. pyvale/dicresults.py +55 -0
  36. pyvale/dicspecklegenerator.py +238 -0
  37. pyvale/dicspecklequality.py +305 -0
  38. pyvale/dicstrain.py +387 -0
  39. pyvale/dicstrainresults.py +37 -0
  40. pyvale/errorintegrator.py +10 -8
  41. pyvale/examples/basics/ex1_1_basicscalars_therm2d.py +124 -113
  42. pyvale/examples/basics/ex1_2_sensormodel_therm2d.py +124 -132
  43. pyvale/examples/basics/ex1_3_customsens_therm3d.py +199 -195
  44. pyvale/examples/basics/ex1_4_basicerrors_therm3d.py +125 -121
  45. pyvale/examples/basics/ex1_5_fielderrs_therm3d.py +145 -141
  46. pyvale/examples/basics/ex1_6_caliberrs_therm2d.py +96 -101
  47. pyvale/examples/basics/ex1_7_spatavg_therm2d.py +109 -105
  48. pyvale/examples/basics/ex2_1_basicvectors_disp2d.py +92 -91
  49. pyvale/examples/basics/ex2_2_vectorsens_disp2d.py +96 -90
  50. pyvale/examples/basics/ex2_3_sensangle_disp2d.py +88 -89
  51. pyvale/examples/basics/ex2_4_chainfielderrs_disp2d.py +172 -171
  52. pyvale/examples/basics/ex2_5_vectorfields3d_disp3d.py +88 -86
  53. pyvale/examples/basics/ex3_1_basictensors_strain2d.py +90 -90
  54. pyvale/examples/basics/ex3_2_tensorsens2d_strain2d.py +93 -91
  55. pyvale/examples/basics/ex3_3_tensorsens3d_strain3d.py +172 -160
  56. pyvale/examples/basics/ex4_1_expsim2d_thermmech2d.py +154 -148
  57. pyvale/examples/basics/ex4_2_expsim3d_thermmech3d.py +249 -231
  58. pyvale/examples/dic/ex1_region_of_interest.py +98 -0
  59. pyvale/examples/dic/ex2_plate_with_hole.py +149 -0
  60. pyvale/examples/dic/ex3_plate_with_hole_strain.py +93 -0
  61. pyvale/examples/dic/ex4_dic_blender.py +95 -0
  62. pyvale/examples/dic/ex5_dic_challenge.py +102 -0
  63. pyvale/examples/imagedef2d/ex_imagedef2d_todisk.py +4 -2
  64. pyvale/examples/renderblender/ex1_1_blenderscene.py +152 -105
  65. pyvale/examples/renderblender/ex1_2_blenderdeformed.py +151 -100
  66. pyvale/examples/renderblender/ex2_1_stereoscene.py +183 -116
  67. pyvale/examples/renderblender/ex2_2_stereodeformed.py +185 -112
  68. pyvale/examples/renderblender/ex3_1_blendercalibration.py +164 -109
  69. pyvale/examples/renderrasterisation/ex_rastenp.py +74 -35
  70. pyvale/examples/renderrasterisation/ex_rastercyth_oneframe.py +6 -13
  71. pyvale/examples/renderrasterisation/ex_rastercyth_static_cypara.py +2 -2
  72. pyvale/examples/renderrasterisation/ex_rastercyth_static_pypara.py +2 -4
  73. pyvale/imagedef2d.py +3 -2
  74. pyvale/imagetools.py +137 -0
  75. pyvale/rastercy.py +34 -4
  76. pyvale/rasternp.py +300 -276
  77. pyvale/rasteropts.py +58 -0
  78. pyvale/renderer.py +47 -0
  79. pyvale/rendermesh.py +52 -62
  80. pyvale/renderscene.py +51 -0
  81. pyvale/sensorarrayfactory.py +2 -2
  82. pyvale/sensortools.py +19 -35
  83. pyvale/simcases/case21.i +1 -1
  84. pyvale/simcases/run_1case.py +8 -0
  85. pyvale/simtools.py +2 -2
  86. pyvale/visualsimplotter.py +180 -0
  87. {pyvale-2025.5.3.dist-info → pyvale-2025.7.0.dist-info}/METADATA +11 -57
  88. {pyvale-2025.5.3.dist-info → pyvale-2025.7.0.dist-info}/RECORD +91 -56
  89. {pyvale-2025.5.3.dist-info → pyvale-2025.7.0.dist-info}/WHEEL +1 -1
  90. pyvale/examples/visualisation/ex1_1_plot_traces.py +0 -102
  91. pyvale/examples/visualisation/ex2_1_animate_sim.py +0 -89
  92. {pyvale-2025.5.3.dist-info → pyvale-2025.7.0.dist-info}/licenses/LICENSE +0 -0
  93. {pyvale-2025.5.3.dist-info → pyvale-2025.7.0.dist-info}/top_level.txt +0 -0
@@ -5,15 +5,16 @@
5
5
  # ==============================================================================
6
6
 
7
7
  """
8
- Pyvale example: Sensor angles for vector fields
9
- --------------------------------------------------------------------------------
8
+ Basics: Sensor angles for vector fields
9
+ ================================================================================
10
+
10
11
  In this example we demonstrate how to setup vector field sensors at custom
11
12
  orientations with respect to the simulation coordinate system. We first build a
12
13
  sensor array aligned with the simulation coords in the same way as the previous
13
14
  example. We then build a sensor array with the sensors rotated and compare this
14
15
  to the case with no rotation.
15
16
 
16
- Note that this tutorial assumes you are familiar with the use of pyvale for
17
+ Note that this tutorial assumes you are familiar with the use of `pyvale` for
17
18
  scalar fields as described in the first set of examples.
18
19
 
19
20
  Test case: point displacement sensors on a 2D plate with hole loaded in tension
@@ -25,115 +26,113 @@ from scipy.spatial.transform import Rotation
25
26
  import mooseherder as mh
26
27
  import pyvale as pyv
27
28
 
28
- def main() -> None:
29
- # First we are going to setup the same displacement sensor array on the 2D
30
- # solid mechanics test case we have used previously. This will serve as a
31
- # baseline with no sensor rotation.
32
-
33
- data_path = pyv.DataSet.mechanical_2d_path()
34
- sim_data = mh.ExodusReader(data_path).read_all_sim_data()
35
-
36
- field_name = "disp"
37
- field_comps = ("disp_x","disp_y")
38
- sim_data = pyv.scale_length_units(scale=1000.0,
39
- sim_data=sim_data,
40
- disp_comps=field_comps)
29
+ #%%
30
+ # First we are going to setup the same displacement sensor array on the 2D
31
+ # solid mechanics test case we have used previously. This will serve as a
32
+ # baseline with no sensor rotation.
33
+ data_path = pyv.DataSet.mechanical_2d_path()
34
+ sim_data = mh.ExodusReader(data_path).read_all_sim_data()
35
+
36
+ field_name = "disp"
37
+ field_comps = ("disp_x","disp_y")
38
+ sim_data = pyv.scale_length_units(scale=1000.0,
39
+ sim_data=sim_data,
40
+ disp_comps=field_comps)
41
41
 
42
- descriptor = pyv.SensorDescriptorFactory.displacement_descriptor()
42
+ descriptor = pyv.SensorDescriptorFactory.displacement_descriptor()
43
43
 
44
- disp_field = pyv.FieldVector(sim_data,field_name,field_comps,elem_dims=2)
44
+ disp_field = pyv.FieldVector(sim_data,field_name,field_comps,elem_dims=2)
45
45
 
46
- n_sens = (2,3,1)
47
- x_lims = (0.0,100.0)
48
- y_lims = (0.0,150.0)
49
- z_lims = (0.0,0.0)
50
- sens_pos = pyv.create_sensor_pos_array(n_sens,x_lims,y_lims,z_lims)
46
+ n_sens = (2,3,1)
47
+ x_lims = (0.0,100.0)
48
+ y_lims = (0.0,150.0)
49
+ z_lims = (0.0,0.0)
50
+ sens_pos = pyv.create_sensor_pos_array(n_sens,x_lims,y_lims,z_lims)
51
51
 
52
52
 
53
- sample_times = np.linspace(0.0,np.max(sim_data.time),50)
53
+ sample_times = np.linspace(0.0,np.max(sim_data.time),50)
54
54
 
55
- sens_data_norot = pyv.SensorData(positions=sens_pos,
56
- sample_times=sample_times)
55
+ sens_data_norot = pyv.SensorData(positions=sens_pos,
56
+ sample_times=sample_times)
57
57
 
58
- disp_sens_norot = pyv.SensorArrayPoint(sens_data_norot,
59
- disp_field,
60
- descriptor)
58
+ disp_sens_norot = pyv.SensorArrayPoint(sens_data_norot,
59
+ disp_field,
60
+ descriptor)
61
61
 
62
- disp_sens_norot.calc_measurements()
62
+ disp_sens_norot.calc_measurements()
63
63
 
64
- # To create our sensor array with rotated sensors we need to add a tuple of
65
- # scipy rotation objects to our sensor data class. This tuple must be the
66
- # same length as the number of sensors in the sensor array. Note that it is
67
- # also possible to specify a single rotation in the tuple in this case all
68
- # sensors are assumed to have the same rotation and they are batch processed
69
- # to increase speed. Here we will define our rotations to all be the same
70
- # rotation in degrees about the z axis which is the out of plane axis for
71
- # our current test case.
72
- sens_angles = sens_pos.shape[0] * \
73
- (Rotation.from_euler("zyx", [45, 0, 0], degrees=True),)
64
+ #%%
65
+ # To create our sensor array with rotated sensors we need to add a tuple of
66
+ # scipy rotation objects to our sensor data class. This tuple must be the
67
+ # same length as the number of sensors in the sensor array. Note that it is
68
+ # also possible to specify a single rotation in the tuple in this case all
69
+ # sensors are assumed to have the same rotation and they are batch processed
70
+ # to increase speed. Here we will define our rotations to all be the same
71
+ # rotation in degrees about the z axis which is the out of plane axis for
72
+ # our current test case.
73
+ sens_angles = sens_pos.shape[0] * \
74
+ (Rotation.from_euler("zyx", [45, 0, 0], degrees=True),)
74
75
 
75
- # We could have also use a single element tuple to have all sensors have the
76
- # angle and batch process them:
77
- sens_angles = (Rotation.from_euler("zyx", [45, 0, 0], degrees=True),)
76
+ # We could have also use a single element tuple to have all sensors have the
77
+ # angle and batch process them:
78
+ sens_angles = (Rotation.from_euler("zyx", [45, 0, 0], degrees=True),)
78
79
 
79
80
 
80
- sens_data_rot = pyv.SensorData(positions=sens_pos,
81
- sample_times=sample_times,
82
- angles=sens_angles)
81
+ sens_data_rot = pyv.SensorData(positions=sens_pos,
82
+ sample_times=sample_times,
83
+ angles=sens_angles)
83
84
 
84
- disp_sens_rot = pyv.SensorArrayPoint(sens_data_rot,
85
- disp_field,
86
- descriptor)
85
+ disp_sens_rot = pyv.SensorArrayPoint(sens_data_rot,
86
+ disp_field,
87
+ descriptor)
87
88
 
88
- # We can also use a field error to add uncertainty to the sensors angle.
89
- # We can apply a specific offset to each sensor or provide a random
90
- # generator to perturb the sensors orientation. Note that the offset and
91
- # the random generator should provide the perturbation in degrees.
92
- angle_offset = np.zeros_like(sens_pos)
93
- angle_offset[:,0] = 2.0 # only rotate about z in 2D
94
- angle_rand = (pyv.GenNormal(std=2.0),None,None)
95
- angle_error_data = pyv.ErrFieldData(ang_offset_zyx=angle_offset,
96
- ang_rand_zyx=angle_rand)
89
+ #%%
90
+ # We can also use a field error to add uncertainty to the sensors angle.
91
+ # We can apply a specific offset to each sensor or provide a random
92
+ # generator to perturb the sensors orientation. Note that the offset and
93
+ # the random generator should provide the perturbation in degrees.
94
+ angle_offset = np.zeros_like(sens_pos)
95
+ angle_offset[:,0] = 2.0 # only rotate about z in 2D
96
+ angle_rand = (pyv.GenNormal(std=2.0),None,None)
97
+ angle_error_data = pyv.ErrFieldData(ang_offset_zyx=angle_offset,
98
+ ang_rand_zyx=angle_rand)
97
99
 
98
100
 
99
- sys_err_rot = pyv.ErrSysField(disp_field,angle_error_data)
100
- sys_err_int = pyv.ErrIntegrator([sys_err_rot],
101
- sens_data_rot,
102
- disp_sens_rot.get_measurement_shape())
103
- disp_sens_rot.set_error_integrator(sys_err_int)
101
+ sys_err_rot = pyv.ErrSysField(disp_field,angle_error_data)
102
+ sys_err_int = pyv.ErrIntegrator([sys_err_rot],
103
+ sens_data_rot,
104
+ disp_sens_rot.get_measurement_shape())
105
+ disp_sens_rot.set_error_integrator(sys_err_int)
104
106
 
105
- measurements = disp_sens_rot.calc_measurements()
107
+ measurements = disp_sens_rot.calc_measurements()
106
108
 
107
109
 
108
- # We print some of the results for one of the sensors so we can see the
109
- # effect of the field errors.
110
- print(80*"-")
110
+ #%%
111
+ # We print some of the results for one of the sensors so we can see the
112
+ # effect of the field errors.
113
+ print(80*"-")
111
114
 
112
- sens_print: int = 0
113
- time_print: int = 5
114
- comp_print: int = 0
115
+ sens_print = 0
116
+ comp_print = 0
117
+ time_last = 5
118
+ time_print = slice(measurements.shape[2]-time_last,measurements.shape[2])
115
119
 
116
- print("ROTATED SENSORS WITH ANGLE ERRORS:")
117
- print(f"These are the last {time_print} virtual measurements of sensor "
118
- + f"{sens_print} for {field_comps[comp_print]}:")
120
+ print("ROTATED SENSORS WITH ANGLE ERRORS:")
121
+ print(f"These are the last {time_last} virtual measurements of sensor "
122
+ + f"{sens_print} for {field_comps[comp_print]}:")
119
123
 
120
- pyv.print_measurements(sens_array=disp_sens_rot,
121
- sensors=(sens_print,sens_print+1),
122
- components=(comp_print,comp_print+1),
123
- time_steps=(measurements.shape[2]-time_print,
124
- measurements.shape[2]))
125
- print(80*"-")
124
+ pyv.print_measurements(disp_sens_rot,sens_print,comp_print,time_print)
126
125
 
127
- # We can now plot the traces for the non-rotated and rotated sensors to
128
- # compare them:
129
- for ff in field_comps:
130
- (_,ax) = pyv.plot_time_traces(disp_sens_norot,ff)
131
- ax.set_title("No Rotation")
132
- (_,ax) = pyv.plot_time_traces(disp_sens_rot,ff)
133
- ax.set_title("Rotated with Errors")
126
+ print(80*"-")
134
127
 
135
- plt.show()
128
+ #%%
129
+ # We can now plot the traces for the non-rotated and rotated sensors to
130
+ # compare them:
131
+ for ff in field_comps:
132
+ (_,ax) = pyv.plot_time_traces(disp_sens_norot,ff)
133
+ ax.set_title("No Rotation")
134
+ (_,ax) = pyv.plot_time_traces(disp_sens_rot,ff)
135
+ ax.set_title("Rotated with Errors")
136
136
 
137
+ plt.show()
137
138
 
138
- if __name__ == "__main__":
139
- main()
@@ -5,8 +5,9 @@
5
5
  # ==============================================================================
6
6
 
7
7
  """
8
- Pyvale example: Chaining field errors
9
- ----------------------------------------------------------------------------
8
+ Basics: Chaining field errors
9
+ ================================================================================
10
+
10
11
  In this example we show how field errors can be chained together and accumulated
11
12
  allowing for successive perturbations in postion, sampling time and orientation.
12
13
  It is more computationally efficient to provide a single field error object as
@@ -15,7 +16,7 @@ interpolation of the underlying physical field. However, in some cases it can
15
16
  be useful to separate the sensor parameter perturbations to determine which is
16
17
  contributing most to the total error.
17
18
 
18
- Note that this tutorial assumes you are familiar with the use of pyvale for
19
+ Note that this tutorial assumes you are familiar with the use of `pyvale` for
19
20
  scalar fields as described in the first set of examples.
20
21
 
21
22
  Test case: point displacement sensors on a 2D plate with hole loaded in tension
@@ -26,171 +27,171 @@ import matplotlib.pyplot as plt
26
27
  import mooseherder as mh
27
28
  import pyvale as pyv
28
29
 
29
- def main() -> None:
30
- # We start by building the same displacement sensor array applied to a 2D
31
- # solid mechanics simulation that we have analysed previously.
32
- data_path = pyv.DataSet.mechanical_2d_path()
33
- sim_data = mh.ExodusReader(data_path).read_all_sim_data()
34
- field_name = "disp"
35
- field_comps = ("disp_x","disp_y")
36
- sim_data = pyv.scale_length_units(scale=1000.0,
37
- sim_data=sim_data,
38
- disp_comps=field_comps)
39
-
40
- descriptor = pyv.SensorDescriptorFactory.displacement_descriptor()
41
-
42
- disp_field = pyv.FieldVector(sim_data,field_name,field_comps,elem_dims=2)
43
-
44
- n_sens = (2,3,1)
45
- x_lims = (0.0,100.0)
46
- y_lims = (0.0,150.0)
47
- z_lims = (0.0,0.0)
48
- sensor_positions = 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)
51
-
52
- sensor_data = pyv.SensorData(positions=sensor_positions,
53
- sample_times=sample_times)
54
-
55
- disp_sens_array = pyv.SensorArrayPoint(sensor_data,
56
- disp_field,
57
- descriptor)
58
-
59
-
60
- # Now we will build a series of field errors that cause succesive offsets in
61
- # sensor sampling time, sensor position and sensor orientation. That way
62
- # we should be able to analyse the sensor data object at each point in the
63
- # error chain to see how the sensor parameters have accumulated.
64
-
65
- # We will apply a position offset of -1.0mm in the x and y axes.
66
- pos_offset = -1.0*np.ones_like(sensor_positions)
67
- pos_offset[:,2] = 0.0 # in 2d we only have offset in x and y so zero z
68
- pos_error_data = pyv.ErrFieldData(pos_offset_xyz=pos_offset)
69
-
70
- # We will apply a rotation offset about the z axis of 1 degree
71
- angle_offset = np.zeros_like(sensor_positions)
72
- angle_offset[:,0] = 1.0 # only rotate about z in 2D
73
- angle_error_data = pyv.ErrFieldData(ang_offset_zyx=angle_offset)
74
-
75
- time_offset = 2.0*np.ones_like(disp_sens_array.get_sample_times())
76
- time_error_data = pyv.ErrFieldData(time_offset=time_offset)
77
-
78
- # Now we add all our field errors to our error chain. We add each error
79
- # twice to see how they accumulate with each other. We also need to set the
80
- # error dependence to `DEPENDENT` so that the sensor state is accumulated
81
- # over the error chain as field errors are `INDEPENDENT` by default.
82
- err_chain = []
83
- err_chain.append(pyv.ErrSysField(disp_field,
84
- time_error_data,
85
- pyv.EErrDep.DEPENDENT))
86
- err_chain.append(pyv.ErrSysField(disp_field,
87
- time_error_data,
88
- pyv.EErrDep.DEPENDENT))
89
-
90
- err_chain.append(pyv.ErrSysField(disp_field,
91
- pos_error_data,
92
- pyv.EErrDep.DEPENDENT))
93
- err_chain.append(pyv.ErrSysField(disp_field,
94
- pos_error_data,
95
- pyv.EErrDep.DEPENDENT))
96
-
97
- err_chain.append(pyv.ErrSysField(disp_field,
98
- angle_error_data,
99
- pyv.EErrDep.DEPENDENT))
100
- err_chain.append(pyv.ErrSysField(disp_field,
101
- angle_error_data,
102
- pyv.EErrDep.DEPENDENT))
103
-
104
- # Instead of setting the dependence for each individual error above we could
105
- # also just use our error integration options to force all errors to be
106
- # `DEPENDENT`. We also set the error integration options to store the errors
107
- # for each step in the error chain so we can analyse the sensor data at each
108
- # step of chain. This option also allows us to separate the contribution of
109
- # each error in the chain to the total error rather than just being able to
110
- # analyse the total systematic and total random error which is the default.
111
- # Note that this option will use more memory.
112
- err_int_opts = pyv.ErrIntOpts(force_dependence=pyv.EErrDep.DEPENDENT,
113
- store_all_errs=True)
114
-
115
- # Now we build our error integrator, add it to our sensor array and then run
116
- # our sensor simulation to obtain some virtual measurements.
117
- error_int = pyv.ErrIntegrator(err_chain,
118
- sensor_data,
119
- disp_sens_array.get_measurement_shape(),
120
- err_int_opts)
121
- disp_sens_array.set_error_integrator(error_int)
122
-
123
- measurements = disp_sens_array.calc_measurements()
124
-
125
-
126
- # Here we will print to the console the time, position and angle of from the
127
- # sensor data objects at each point in the error chain. We should see each
128
- # sensor parameter perturbed and accumulated throughout the chain:
129
- sens_data_by_chain = error_int.get_sens_data_by_chain()
130
- if sens_data_by_chain is not None:
131
- for ii,ss in enumerate(sens_data_by_chain):
132
- print(80*"-")
133
- if ss is not None:
134
- print(f"SensorData @ [{ii}]")
135
- print("TIME")
136
- print(ss.sample_times)
137
- print()
138
- print("POSITIONS")
139
- print(ss.positions)
140
- print()
141
- print("ANGLES")
142
- for aa in ss.angles:
143
- print(aa.as_euler("zyx",degrees=True))
144
- print()
145
- print(80*"-")
146
-
147
- # Try setting all the field errors to be `INDEPENDENT` using the error
148
- # integration options above. You should see that the sensor parameters are
149
- # not accumulated throughout the error chain.
150
-
151
- # Here we print the final sampling time, sensor positions and sensor angles
152
- # at the end of error chain.
153
- print()
154
- print(80*"=")
155
- sens_data_accumulated = error_int.get_sens_data_accumulated()
156
- print("TIME")
157
- print(sens_data_accumulated.sample_times)
158
- print()
159
- print("POSITIONS")
160
- print(sens_data_accumulated.positions)
161
- print()
162
- print("ANGLES")
163
- for aa in sens_data_accumulated.angles:
164
- print(aa.as_euler("zyx",degrees=True))
165
- print()
166
- print(80*"=")
167
-
168
- # We print the results for one of the sensors so we can see what the errors
169
- # are for the last few sampling times.
170
- print(80*"-")
171
-
172
- sens_print: int = 0
173
- time_print: int = 5
174
- comp_print: int = 0
175
-
176
- print("ROTATED SENSORS WITH ANGLE ERRORS:")
177
- print(f"These are the last {time_print} virtual measurements of sensor "
178
- + f"{sens_print} for {field_comps[comp_print]}:")
179
-
180
- pyv.print_measurements(sens_array=disp_sens_array,
181
- sensors=(sens_print,sens_print+1),
182
- components=(comp_print,comp_print+1),
183
- time_steps=(measurements.shape[2]-time_print,
184
- measurements.shape[2]))
185
- print(80*"-")
186
-
187
-
188
- # Finally, we plot the time traces for all field components.
189
- for ff in field_comps:
190
- pyv.plot_time_traces(disp_sens_array,ff)
191
-
192
- plt.show()
193
-
194
-
195
- if __name__ == "__main__":
196
- main()
30
+ #%%
31
+ # We start by building the same displacement sensor array applied to a 2D
32
+ # solid mechanics simulation that we have analysed previously.
33
+ data_path = pyv.DataSet.mechanical_2d_path()
34
+ sim_data = mh.ExodusReader(data_path).read_all_sim_data()
35
+ field_name = "disp"
36
+ field_comps = ("disp_x","disp_y")
37
+ sim_data = pyv.scale_length_units(scale=1000.0,
38
+ sim_data=sim_data,
39
+ disp_comps=field_comps)
40
+
41
+ descriptor = pyv.SensorDescriptorFactory.displacement_descriptor()
42
+
43
+ disp_field = pyv.FieldVector(sim_data,field_name,field_comps,elem_dims=2)
44
+
45
+ n_sens = (2,3,1)
46
+ x_lims = (0.0,100.0)
47
+ y_lims = (0.0,150.0)
48
+ z_lims = (0.0,0.0)
49
+ sensor_positions = pyv.create_sensor_pos_array(n_sens,x_lims,y_lims,z_lims)
50
+
51
+ sample_times = np.linspace(0.0,np.max(sim_data.time),50)
52
+
53
+ sensor_data = pyv.SensorData(positions=sensor_positions,
54
+ sample_times=sample_times)
55
+
56
+ disp_sens_array = pyv.SensorArrayPoint(sensor_data,
57
+ disp_field,
58
+ descriptor)
59
+
60
+ #%%
61
+ # Now we will build a series of field errors that cause succesive offsets in
62
+ # sensor sampling time, sensor position and sensor orientation. That way
63
+ # we should be able to analyse the sensor data object at each point in the
64
+ # error chain to see how the sensor parameters have accumulated.
65
+ #
66
+ # We will apply a position offset of -1.0mm in the x and y axes.
67
+ pos_offset = -1.0*np.ones_like(sensor_positions)
68
+ pos_offset[:,2] = 0.0 # in 2d we only have offset in x and y so zero z
69
+ pos_error_data = pyv.ErrFieldData(pos_offset_xyz=pos_offset)
70
+
71
+ #%%
72
+ # We will apply a rotation offset about the z axis of 1 degree
73
+ angle_offset = np.zeros_like(sensor_positions)
74
+ angle_offset[:,0] = 1.0 # only rotate about z in 2D
75
+ angle_error_data = pyv.ErrFieldData(ang_offset_zyx=angle_offset)
76
+
77
+ time_offset = 2.0*np.ones_like(disp_sens_array.get_sample_times())
78
+ time_error_data = pyv.ErrFieldData(time_offset=time_offset)
79
+
80
+ #%%
81
+ # Now we add all our field errors to our error chain. We add each error
82
+ # twice to see how they accumulate with each other. We also need to set the
83
+ # error dependence to `DEPENDENT` so that the sensor state is accumulated
84
+ # over the error chain as field errors are `INDEPENDENT` by default.
85
+ err_chain = []
86
+ err_chain.append(pyv.ErrSysField(disp_field,
87
+ time_error_data,
88
+ pyv.EErrDep.DEPENDENT))
89
+ err_chain.append(pyv.ErrSysField(disp_field,
90
+ time_error_data,
91
+ pyv.EErrDep.DEPENDENT))
92
+
93
+ err_chain.append(pyv.ErrSysField(disp_field,
94
+ pos_error_data,
95
+ pyv.EErrDep.DEPENDENT))
96
+ err_chain.append(pyv.ErrSysField(disp_field,
97
+ pos_error_data,
98
+ pyv.EErrDep.DEPENDENT))
99
+
100
+ err_chain.append(pyv.ErrSysField(disp_field,
101
+ angle_error_data,
102
+ pyv.EErrDep.DEPENDENT))
103
+ err_chain.append(pyv.ErrSysField(disp_field,
104
+ angle_error_data,
105
+ pyv.EErrDep.DEPENDENT))
106
+
107
+ #%%
108
+ # Instead of setting the dependence for each individual error above we could
109
+ # also just use our error integration options to force all errors to be
110
+ # `DEPENDENT`. We also set the error integration options to store the errors
111
+ # for each step in the error chain so we can analyse the sensor data at each
112
+ # step of chain. This option also allows us to separate the contribution of
113
+ # each error in the chain to the total error rather than just being able to
114
+ # analyse the total systematic and total random error which is the default.
115
+ # Note that this option will use more memory.
116
+ err_int_opts = pyv.ErrIntOpts(force_dependence=pyv.EErrDep.DEPENDENT,
117
+ store_all_errs=True)
118
+
119
+ #%%
120
+ # Now we build our error integrator, add it to our sensor array and then run
121
+ # our sensor simulation to obtain some virtual measurements.
122
+ error_int = pyv.ErrIntegrator(err_chain,
123
+ sensor_data,
124
+ disp_sens_array.get_measurement_shape(),
125
+ err_int_opts)
126
+ disp_sens_array.set_error_integrator(error_int)
127
+
128
+ measurements = disp_sens_array.calc_measurements()
129
+
130
+ #%%
131
+ # Here we will print to the console the time, position and angle of from the
132
+ # sensor data objects at each point in the error chain. We should see each
133
+ # sensor parameter perturbed and accumulated throughout the chain:
134
+ sens_data_by_chain = error_int.get_sens_data_by_chain()
135
+ if sens_data_by_chain is not None:
136
+ for ii,ss in enumerate(sens_data_by_chain):
137
+ print(80*"-")
138
+ if ss is not None:
139
+ print(f"SensorData @ [{ii}]")
140
+ print("TIME")
141
+ print(ss.sample_times)
142
+ print()
143
+ print("POSITIONS")
144
+ print(ss.positions)
145
+ print()
146
+ print("ANGLES")
147
+ for aa in ss.angles:
148
+ print(aa.as_euler("zyx",degrees=True))
149
+ print()
150
+ print(80*"-")
151
+
152
+ #%%
153
+ # Try setting all the field errors to be `INDEPENDENT` using the error
154
+ # integration options above. You should see that the sensor parameters are
155
+ # not accumulated throughout the error chain.
156
+ #
157
+ # Here we print the final sampling time, sensor positions and sensor angles
158
+ # at the end of error chain.
159
+ print()
160
+ print(80*"=")
161
+ sens_data_accumulated = error_int.get_sens_data_accumulated()
162
+ print("TIME")
163
+ print(sens_data_accumulated.sample_times)
164
+ print()
165
+ print("POSITIONS")
166
+ print(sens_data_accumulated.positions)
167
+ print()
168
+ print("ANGLES")
169
+ for aa in sens_data_accumulated.angles:
170
+ print(aa.as_euler("zyx",degrees=True))
171
+ print()
172
+ print(80*"=")
173
+
174
+ #%%
175
+ # We print the results for one of the sensors so we can see what the errors
176
+ # are for the last few sampling times.
177
+ print(80*"-")
178
+
179
+ sens_print = 0
180
+ comp_print = 0
181
+ time_last = 5
182
+ time_print = slice(measurements.shape[2]-time_last,measurements.shape[2])
183
+
184
+ print("ROTATED SENSORS WITH ANGLE ERRORS:")
185
+ print(f"These are the last {time_last} virtual measurements of sensor "
186
+ + f"{sens_print} for {field_comps[comp_print]}:")
187
+
188
+ pyv.print_measurements(disp_sens_array,sens_print,comp_print,time_print)
189
+
190
+ print(80*"-")
191
+
192
+ #%%
193
+ # Finally, we plot the time traces for all field components.
194
+ for ff in field_comps:
195
+ pyv.plot_time_traces(disp_sens_array,ff)
196
+
197
+ plt.show()