pyvale 2025.5.3__cp311-cp311-macosx_13_0_x86_64.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 (175) hide show
  1. pyvale/.dylibs/libomp.dylib +0 -0
  2. pyvale/__init__.py +89 -0
  3. pyvale/analyticmeshgen.py +102 -0
  4. pyvale/analyticsimdatafactory.py +91 -0
  5. pyvale/analyticsimdatagenerator.py +323 -0
  6. pyvale/blendercalibrationdata.py +15 -0
  7. pyvale/blenderlightdata.py +26 -0
  8. pyvale/blendermaterialdata.py +15 -0
  9. pyvale/blenderrenderdata.py +30 -0
  10. pyvale/blenderscene.py +488 -0
  11. pyvale/blendertools.py +420 -0
  12. pyvale/camera.py +146 -0
  13. pyvale/cameradata.py +69 -0
  14. pyvale/cameradata2d.py +84 -0
  15. pyvale/camerastereo.py +217 -0
  16. pyvale/cameratools.py +522 -0
  17. pyvale/cython/rastercyth.c +32211 -0
  18. pyvale/cython/rastercyth.cpython-311-darwin.so +0 -0
  19. pyvale/cython/rastercyth.py +640 -0
  20. pyvale/data/__init__.py +5 -0
  21. pyvale/data/cal_target.tiff +0 -0
  22. pyvale/data/case00_HEX20_out.e +0 -0
  23. pyvale/data/case00_HEX27_out.e +0 -0
  24. pyvale/data/case00_HEX8_out.e +0 -0
  25. pyvale/data/case00_TET10_out.e +0 -0
  26. pyvale/data/case00_TET14_out.e +0 -0
  27. pyvale/data/case00_TET4_out.e +0 -0
  28. pyvale/data/case13_out.e +0 -0
  29. pyvale/data/case16_out.e +0 -0
  30. pyvale/data/case17_out.e +0 -0
  31. pyvale/data/case18_1_out.e +0 -0
  32. pyvale/data/case18_2_out.e +0 -0
  33. pyvale/data/case18_3_out.e +0 -0
  34. pyvale/data/case25_out.e +0 -0
  35. pyvale/data/case26_out.e +0 -0
  36. pyvale/data/optspeckle_2464x2056px_spec5px_8bit_gblur1px.tiff +0 -0
  37. pyvale/dataset.py +325 -0
  38. pyvale/errorcalculator.py +109 -0
  39. pyvale/errordriftcalc.py +146 -0
  40. pyvale/errorintegrator.py +336 -0
  41. pyvale/errorrand.py +607 -0
  42. pyvale/errorsyscalib.py +134 -0
  43. pyvale/errorsysdep.py +327 -0
  44. pyvale/errorsysfield.py +414 -0
  45. pyvale/errorsysindep.py +808 -0
  46. pyvale/examples/__init__.py +5 -0
  47. pyvale/examples/basics/ex1_1_basicscalars_therm2d.py +131 -0
  48. pyvale/examples/basics/ex1_2_sensormodel_therm2d.py +158 -0
  49. pyvale/examples/basics/ex1_3_customsens_therm3d.py +216 -0
  50. pyvale/examples/basics/ex1_4_basicerrors_therm3d.py +153 -0
  51. pyvale/examples/basics/ex1_5_fielderrs_therm3d.py +168 -0
  52. pyvale/examples/basics/ex1_6_caliberrs_therm2d.py +133 -0
  53. pyvale/examples/basics/ex1_7_spatavg_therm2d.py +123 -0
  54. pyvale/examples/basics/ex2_1_basicvectors_disp2d.py +112 -0
  55. pyvale/examples/basics/ex2_2_vectorsens_disp2d.py +111 -0
  56. pyvale/examples/basics/ex2_3_sensangle_disp2d.py +139 -0
  57. pyvale/examples/basics/ex2_4_chainfielderrs_disp2d.py +196 -0
  58. pyvale/examples/basics/ex2_5_vectorfields3d_disp3d.py +109 -0
  59. pyvale/examples/basics/ex3_1_basictensors_strain2d.py +114 -0
  60. pyvale/examples/basics/ex3_2_tensorsens2d_strain2d.py +111 -0
  61. pyvale/examples/basics/ex3_3_tensorsens3d_strain3d.py +182 -0
  62. pyvale/examples/basics/ex4_1_expsim2d_thermmech2d.py +171 -0
  63. pyvale/examples/basics/ex4_2_expsim3d_thermmech3d.py +252 -0
  64. pyvale/examples/genanalyticdata/ex1_1_scalarvisualisation.py +35 -0
  65. pyvale/examples/genanalyticdata/ex1_2_scalarcasebuild.py +43 -0
  66. pyvale/examples/genanalyticdata/ex2_1_analyticsensors.py +80 -0
  67. pyvale/examples/imagedef2d/ex_imagedef2d_todisk.py +79 -0
  68. pyvale/examples/renderblender/ex1_1_blenderscene.py +121 -0
  69. pyvale/examples/renderblender/ex1_2_blenderdeformed.py +119 -0
  70. pyvale/examples/renderblender/ex2_1_stereoscene.py +128 -0
  71. pyvale/examples/renderblender/ex2_2_stereodeformed.py +131 -0
  72. pyvale/examples/renderblender/ex3_1_blendercalibration.py +120 -0
  73. pyvale/examples/renderrasterisation/ex_rastenp.py +153 -0
  74. pyvale/examples/renderrasterisation/ex_rastercyth_oneframe.py +218 -0
  75. pyvale/examples/renderrasterisation/ex_rastercyth_static_cypara.py +187 -0
  76. pyvale/examples/renderrasterisation/ex_rastercyth_static_pypara.py +190 -0
  77. pyvale/examples/visualisation/ex1_1_plot_traces.py +102 -0
  78. pyvale/examples/visualisation/ex2_1_animate_sim.py +89 -0
  79. pyvale/experimentsimulator.py +175 -0
  80. pyvale/field.py +128 -0
  81. pyvale/fieldconverter.py +351 -0
  82. pyvale/fieldsampler.py +111 -0
  83. pyvale/fieldscalar.py +166 -0
  84. pyvale/fieldtensor.py +218 -0
  85. pyvale/fieldtransform.py +388 -0
  86. pyvale/fieldvector.py +213 -0
  87. pyvale/generatorsrandom.py +505 -0
  88. pyvale/imagedef2d.py +569 -0
  89. pyvale/integratorfactory.py +240 -0
  90. pyvale/integratorquadrature.py +217 -0
  91. pyvale/integratorrectangle.py +165 -0
  92. pyvale/integratorspatial.py +89 -0
  93. pyvale/integratortype.py +43 -0
  94. pyvale/output.py +17 -0
  95. pyvale/pyvaleexceptions.py +11 -0
  96. pyvale/raster.py +31 -0
  97. pyvale/rastercy.py +77 -0
  98. pyvale/rasternp.py +603 -0
  99. pyvale/rendermesh.py +147 -0
  100. pyvale/sensorarray.py +178 -0
  101. pyvale/sensorarrayfactory.py +196 -0
  102. pyvale/sensorarraypoint.py +278 -0
  103. pyvale/sensordata.py +71 -0
  104. pyvale/sensordescriptor.py +213 -0
  105. pyvale/sensortools.py +142 -0
  106. pyvale/simcases/case00_HEX20.i +242 -0
  107. pyvale/simcases/case00_HEX27.i +242 -0
  108. pyvale/simcases/case00_HEX8.i +242 -0
  109. pyvale/simcases/case00_TET10.i +242 -0
  110. pyvale/simcases/case00_TET14.i +242 -0
  111. pyvale/simcases/case00_TET4.i +242 -0
  112. pyvale/simcases/case01.i +101 -0
  113. pyvale/simcases/case02.i +156 -0
  114. pyvale/simcases/case03.i +136 -0
  115. pyvale/simcases/case04.i +181 -0
  116. pyvale/simcases/case05.i +234 -0
  117. pyvale/simcases/case06.i +305 -0
  118. pyvale/simcases/case07.geo +135 -0
  119. pyvale/simcases/case07.i +87 -0
  120. pyvale/simcases/case08.geo +144 -0
  121. pyvale/simcases/case08.i +153 -0
  122. pyvale/simcases/case09.geo +204 -0
  123. pyvale/simcases/case09.i +87 -0
  124. pyvale/simcases/case10.geo +204 -0
  125. pyvale/simcases/case10.i +257 -0
  126. pyvale/simcases/case11.geo +337 -0
  127. pyvale/simcases/case11.i +147 -0
  128. pyvale/simcases/case12.geo +388 -0
  129. pyvale/simcases/case12.i +329 -0
  130. pyvale/simcases/case13.i +140 -0
  131. pyvale/simcases/case14.i +159 -0
  132. pyvale/simcases/case15.geo +337 -0
  133. pyvale/simcases/case15.i +150 -0
  134. pyvale/simcases/case16.geo +391 -0
  135. pyvale/simcases/case16.i +357 -0
  136. pyvale/simcases/case17.geo +135 -0
  137. pyvale/simcases/case17.i +144 -0
  138. pyvale/simcases/case18.i +254 -0
  139. pyvale/simcases/case18_1.i +254 -0
  140. pyvale/simcases/case18_2.i +254 -0
  141. pyvale/simcases/case18_3.i +254 -0
  142. pyvale/simcases/case19.geo +252 -0
  143. pyvale/simcases/case19.i +99 -0
  144. pyvale/simcases/case20.geo +252 -0
  145. pyvale/simcases/case20.i +250 -0
  146. pyvale/simcases/case21.geo +74 -0
  147. pyvale/simcases/case21.i +155 -0
  148. pyvale/simcases/case22.geo +82 -0
  149. pyvale/simcases/case22.i +140 -0
  150. pyvale/simcases/case23.geo +164 -0
  151. pyvale/simcases/case23.i +140 -0
  152. pyvale/simcases/case24.geo +79 -0
  153. pyvale/simcases/case24.i +123 -0
  154. pyvale/simcases/case25.geo +82 -0
  155. pyvale/simcases/case25.i +140 -0
  156. pyvale/simcases/case26.geo +166 -0
  157. pyvale/simcases/case26.i +140 -0
  158. pyvale/simcases/run_1case.py +61 -0
  159. pyvale/simcases/run_all_cases.py +69 -0
  160. pyvale/simcases/run_build_case.py +64 -0
  161. pyvale/simcases/run_example_cases.py +69 -0
  162. pyvale/simtools.py +67 -0
  163. pyvale/visualexpplotter.py +191 -0
  164. pyvale/visualimagedef.py +74 -0
  165. pyvale/visualimages.py +76 -0
  166. pyvale/visualopts.py +493 -0
  167. pyvale/visualsimanimator.py +111 -0
  168. pyvale/visualsimsensors.py +318 -0
  169. pyvale/visualtools.py +136 -0
  170. pyvale/visualtraceplotter.py +142 -0
  171. pyvale-2025.5.3.dist-info/METADATA +144 -0
  172. pyvale-2025.5.3.dist-info/RECORD +175 -0
  173. pyvale-2025.5.3.dist-info/WHEEL +6 -0
  174. pyvale-2025.5.3.dist-info/licenses/LICENSE +21 -0
  175. pyvale-2025.5.3.dist-info/top_level.txt +1 -0
pyvale/rendermesh.py ADDED
@@ -0,0 +1,147 @@
1
+ # ==============================================================================
2
+ # pyvale: the python validation engine
3
+ # License: MIT
4
+ # Copyright (C) 2025 The Computer Aided Validation Team
5
+ # ==============================================================================
6
+
7
+ """
8
+ NOTE: this module is a feature under developement.
9
+ """
10
+
11
+ from enum import Enum
12
+ from dataclasses import dataclass, field
13
+ import numpy as np
14
+ import mooseherder as mh
15
+ from pyvale.fieldconverter import simdata_to_pyvista
16
+
17
+
18
+ @dataclass(slots=True)
19
+ class RenderMeshData:
20
+ coords: np.ndarray
21
+ connectivity: np.ndarray
22
+ fields_render: np.ndarray
23
+
24
+ # If this is None then the mesh is not deformable
25
+ fields_disp: np.ndarray | None = None
26
+
27
+ node_count: int = field(init=False)
28
+ elem_count: int = field(init=False)
29
+ nodes_per_elem: int = field(init=False)
30
+
31
+ coord_cent: np.ndarray = field(init=False)
32
+ coord_bound_min: np.ndarray = field(init=False)
33
+ coord_bound_max: np.ndarray = field(init=False)
34
+
35
+ def __post_init__(self) -> None:
36
+ # C format: num_nodes/num_elems first as it is the largest dimension
37
+ self.node_count = self.coords.shape[0]
38
+ self.elem_count = self.connectivity.shape[0]
39
+ self.nodes_per_elem = self.connectivity.shape[1]
40
+
41
+ self.coord_bound_min = np.min(self.coords,axis=0)
42
+ self.coord_bound_max = np.max(self.coords,axis=0)
43
+ self.coord_cent = (self.coord_bound_max + self.coord_bound_min)/2.0
44
+
45
+
46
+ def create_render_mesh(sim_data: mh.SimData,
47
+ field_render_keys: tuple[str,...],
48
+ sim_spat_dim: int,
49
+ field_disp_keys: tuple[str,...] | None = None,
50
+ ) -> RenderMeshData:
51
+
52
+ extract_keys = field_render_keys
53
+ if field_disp_keys is not None:
54
+ extract_keys = field_render_keys+field_disp_keys
55
+
56
+ (pv_grid,_) = simdata_to_pyvista(sim_data,
57
+ extract_keys,
58
+ elem_dims=sim_spat_dim)
59
+
60
+ pv_surf = pv_grid.extract_surface()
61
+ faces = np.array(pv_surf.faces)
62
+
63
+ first_elem_nodes_per_face = faces[0]
64
+ nodes_per_face_vec = faces[0::(first_elem_nodes_per_face+1)]
65
+
66
+ # TODO: CHECKS
67
+ # - Number of displacement keys match the spat_dim parameter
68
+ assert np.all(nodes_per_face_vec == first_elem_nodes_per_face), \
69
+ "Not all elements in the simdata object have the same number of nodes per element"
70
+
71
+ nodes_per_face = first_elem_nodes_per_face
72
+ num_faces = int(faces.shape[0] / (nodes_per_face+1))
73
+
74
+ # Reshape the faces table and slice off the first column which is just the
75
+ # number of nodes per element and should be the same for all elements
76
+ connectivity = np.reshape(faces,(num_faces,nodes_per_face+1))
77
+ # shape=(num_elems,nodes_per_elem), C format
78
+ connectivity = np.ascontiguousarray(connectivity[:,1:],dtype=np.uintp)
79
+
80
+ # shape=(num_nodes,3), C format
81
+ coords_world = np.array(pv_surf.points)
82
+
83
+ # Add w coord=1, shape=(num_nodes,3+1)
84
+ coords_world= np.hstack((coords_world,np.ones([coords_world.shape[0],1])))
85
+
86
+ # shape=(num_nodes,num_time_steps,num_components)
87
+ field_render_shape = np.array(pv_surf[field_render_keys[0]]).shape
88
+ fields_render_by_node = np.zeros(field_render_shape+(len(field_render_keys),),
89
+ dtype=np.float64)
90
+ for ii,cc in enumerate(field_render_keys):
91
+ fields_render_by_node[:,:,ii] = np.ascontiguousarray(
92
+ np.array(pv_surf[cc]))
93
+
94
+
95
+ field_disp_by_node = None
96
+ if field_disp_keys is not None:
97
+ field_disp_shape = np.array(pv_surf[field_disp_keys[0]]).shape
98
+ # shape=(num_nodes,num_time_steps,num_components)
99
+ field_disp_by_node = np.zeros(field_disp_shape+(len(field_disp_keys),),
100
+ dtype=np.float64)
101
+ for ii,cc in enumerate(field_disp_keys):
102
+ field_disp_by_node[:,:,ii] = np.ascontiguousarray(
103
+ np.array(pv_surf[cc]))
104
+
105
+
106
+
107
+ return RenderMeshData(coords=coords_world,
108
+ connectivity=connectivity,
109
+ fields_render=fields_render_by_node,
110
+ fields_disp=field_disp_by_node)
111
+
112
+
113
+ def slice_mesh_data_by_elem(coords_world: np.ndarray,
114
+ connectivity: np.ndarray,
115
+ field_by_node: np.ndarray,
116
+ ) -> tuple[np.ndarray,np.ndarray]:
117
+ """_summary_
118
+
119
+ Parameters
120
+ ----------
121
+ coords_world : np.ndarray
122
+ _description_
123
+ connectivity : np.ndarray
124
+ _description_
125
+ field_by_node : np.ndarray
126
+ _description_
127
+
128
+ Returns
129
+ -------
130
+ tuple[np.ndarray,np.ndarray]
131
+ _description_
132
+ """
133
+ # shape=(coord[X,Y,Z,W],node_per_elem,elem_num)
134
+ elem_world_coords = np.copy(coords_world[connectivity,:])
135
+
136
+ # shape=(elem_num,nodes_per_elem,coord[X,Y,Z,W]), C memory format
137
+ # elem_world_coords = np.ascontiguousarray(np.swapaxes(elem_world_coords,0,2))
138
+ elem_world_coords = np.ascontiguousarray(elem_world_coords)
139
+
140
+ # shape=(nodes_per_elem,elem_num,time_steps)
141
+ field_by_elem = np.copy(field_by_node[connectivity,:])
142
+
143
+ # shape=(elem_num,nodes_per_elem,time_steps), C memory format
144
+ # field_by_elem = np.ascontiguousarray(np.swapaxes(field_by_elem,0,1))
145
+ field_by_elem = np.ascontiguousarray(field_by_elem)
146
+
147
+ return (elem_world_coords,field_by_elem)
pyvale/sensorarray.py ADDED
@@ -0,0 +1,178 @@
1
+ # ==============================================================================
2
+ # pyvale: the python validation engine
3
+ # License: MIT
4
+ # Copyright (C) 2025 The Computer Aided Validation Team
5
+ # ==============================================================================
6
+
7
+ from abc import ABC, abstractmethod
8
+ import numpy as np
9
+ from pyvale.field import IField
10
+
11
+
12
+ class ISensorArray(ABC):
13
+ """Interface (abstract base class) for an array of sensors of the same
14
+ type sampling a given physical field.
15
+
16
+ This class implements the `pyvale` sensor measurement simulation model. Here
17
+ a measurement is taken as: measurement = truth + random errors + systematic
18
+ errors. The truth value for each sensor is interpolated from the physical
19
+ field (an implementation of the `IField` interface, nominally a
20
+ `FieldScalar`, `FieldVector` or `FieldTensor` object).
21
+
22
+ The random and systematic errors are calculated by a user specified error
23
+ integrator (`ErrIntegrator` class). This class contains a chain of different
24
+ types of user selected errors (implementations of the `IErrCalculator`
25
+ interface). Further information can be found in the `ErrIntegrator` class
26
+ and in implementations of the `IErrCalculator` interface.
27
+
28
+ In `pyvale`, function and methods with `calc` in their name will cause
29
+ probability distributions to be resampled and any additional calculations,
30
+ such as interpolation, to be performed. Functions and methods with `get` in
31
+ the name will directly return the previously calculated values without
32
+ resampling probability distributions.
33
+
34
+ Calling the class method `calc_measurements()` will create and return an
35
+ array of simulated sensor measurements with the following shape=(num_sensors
36
+ ,num_field_component,num_time_steps). When calling `calc_measurements()` all
37
+ sensor errors that are based on probability distributions are resampled and
38
+ any required interpolations are performed (e.g. a random perturbation of the
39
+ sensor positions requiring interpolation at the perturbed sensor location).
40
+
41
+ Calling the class method `get_measurements()` just returns the previously
42
+ calculated set of sensor measurements without resampling of probability.
43
+ Distributions.
44
+
45
+ Without an error integrator this class can be used for interpolating
46
+ simulated physical fields quickly using finite element shape functions.
47
+ """
48
+
49
+ @abstractmethod
50
+ def get_measurement_shape(self) -> tuple[int,int,int]:
51
+ """Abstract method. Gets the shape of the measurement array:
52
+ shape=(num_sensors,num_field_components,num_time_steps).
53
+
54
+ The number of sensors is specified by the user with a SensorData object.
55
+ The number of field components is dependent on the field being sampled
56
+ (i.e. 1 for a scalar field and 3 for a vector field in 3D). The number
57
+ of time steps is specified by the user in the SensorData object or
58
+ defaults to the time steps taken from the simulation.
59
+
60
+ Returns
61
+ -------
62
+ tuple[int,int,int]
63
+ Shape of the measurement array as (num_sensors,
64
+ num_field_components,num_time_steps)
65
+ """
66
+ pass
67
+
68
+ @abstractmethod
69
+ def get_field(self) -> IField:
70
+ """Abstract method. Gets the field object that this array of sensors is
71
+ sampling to simulate measurements.
72
+
73
+ Returns
74
+ -------
75
+ IField
76
+ A field object interface.
77
+ """
78
+ pass
79
+
80
+ @abstractmethod
81
+ def get_truth(self) -> np.ndarray:
82
+ """Abstract method. Gets the ground truth sensor values that were
83
+ calculated previously. If the ground truth values have not been
84
+ calculated then `calc_truth_values()` is called first.
85
+
86
+ Returns
87
+ -------
88
+ np.ndarray
89
+ Array of ground truth sensor values. shape=(num_sensors,
90
+ num_field_components,num_time_steps).
91
+ """
92
+ pass
93
+
94
+ @abstractmethod
95
+ def get_errors_systematic(self) -> np.ndarray | None:
96
+ """Abstract method. Gets the systematic error array from the previously
97
+ calculated sensor measurements. Returns None is no error integrator has
98
+ been specified.
99
+
100
+ Returns
101
+ -------
102
+ np.ndarray | None
103
+ Array of systematic errors for this sensor array. shape=(num_sensors
104
+ ,num_field_components,num_time_steps). Returns None if no error
105
+ integrator has been set.
106
+ """
107
+ pass
108
+
109
+ @abstractmethod
110
+ def get_errors_random(self) -> np.ndarray | None:
111
+ """Abstract method. Gets the random error array from the previously
112
+ calculated sensor measurements. Returns None is no error integrator has
113
+ been specified.
114
+
115
+ Returns
116
+ -------
117
+ np.ndarray | None
118
+ Array of random errors for this sensor array. shape=(num_sensors
119
+ ,num_field_components,num_time_steps). Returns None if no error
120
+ integrator has been set.
121
+ """
122
+ pass
123
+
124
+ @abstractmethod
125
+ def get_errors_total(self) -> np.ndarray | None:
126
+ """Abstract method. Gets the total error array from the previously
127
+ calculated sensor measurements. Returns None is no error integrator has
128
+ been specified.
129
+
130
+ Returns
131
+ -------
132
+ np.ndarray | None
133
+ Array of total errors for this sensor array. shape=(num_sensors
134
+ ,num_field_components,num_time_steps). Returns None if no error
135
+ integrator has been set.
136
+ """
137
+ pass
138
+
139
+ @abstractmethod
140
+ def calc_measurements(self) -> np.ndarray:
141
+ """Abstract method. Calculates measurements as: measurement = truth +
142
+ systematic errors + random errors. The truth is calculated once and is
143
+ interpolated from the input simulation field. The errors are calculated
144
+ based on the user specified error chain.
145
+
146
+ NOTE: this is a 'calc' method and will sample all probability
147
+ distributions in the error chain returning a new simulated experiment
148
+ for this sensor array.
149
+
150
+ Returns
151
+ -------
152
+ np.ndarray
153
+ The calculated measurements for this sensor array with shape:
154
+ (num_sensors,num_field_components,num_time_steps)
155
+ """
156
+ pass
157
+
158
+ @abstractmethod
159
+ def get_measurements(self) -> np.ndarray:
160
+ """Abstract method. Returns the current set of simulated measurements if
161
+ theses have been calculated. If these have not been calculated then
162
+ 'calc_measurements()' is called and a set of measurements in then
163
+ returned.
164
+
165
+ NOTE: this is a 'get' method and does not sample from probability
166
+ distributions in the error chain and directly returns the current set of
167
+ measurements if they exist.
168
+
169
+ Returns
170
+ -------
171
+ np.ndarray
172
+ The calculated measurements for this sensor array with shape:
173
+ (num_sensors,num_field_components,num_time_steps)
174
+ """
175
+ pass
176
+
177
+
178
+
@@ -0,0 +1,196 @@
1
+ # ==============================================================================
2
+ # pyvale: the python validation engine
3
+ # License: MIT
4
+ # Copyright (C) 2025 The Computer Aided Validation Team
5
+ # ==============================================================================
6
+
7
+ import numpy as np
8
+
9
+ import mooseherder as mh
10
+
11
+ from pyvale.fieldscalar import FieldScalar
12
+ from pyvale.fieldvector import FieldVector
13
+ from pyvale.fieldtensor import FieldTensor
14
+ from pyvale.sensordescriptor import SensorDescriptorFactory
15
+ from pyvale.sensorarraypoint import SensorArrayPoint, SensorData
16
+ from pyvale.errorintegrator import ErrIntegrator
17
+ from pyvale.errorsysindep import ErrSysUnifPercent
18
+ from pyvale.errorrand import ErrRandNormPercent
19
+ from pyvale.errorsysdep import (ErrSysDigitisation,
20
+ ErrSysSaturation)
21
+
22
+ # TODO:
23
+ # - docstrings
24
+ # - more sensor models
25
+
26
+ class SensorArrayFactory:
27
+ """Namespace for static methods used to build common types of sensor arrays
28
+ simplifying sensor array creation for users.
29
+ """
30
+
31
+ @staticmethod
32
+ def thermocouples_no_errs(sim_data: mh.SimData,
33
+ sensor_data: SensorData,
34
+ elem_dims: int,
35
+ field_name: str = "temperature",
36
+ ) -> SensorArrayPoint:
37
+
38
+ descriptor = SensorDescriptorFactory.temperature_descriptor()
39
+
40
+ t_field = FieldScalar(sim_data,field_name,elem_dims)
41
+
42
+ sens_array = SensorArrayPoint(sensor_data,
43
+ t_field,
44
+ descriptor)
45
+
46
+ return sens_array
47
+
48
+ @staticmethod
49
+ def thermocouples_basic_errs(sim_data: mh.SimData,
50
+ sensor_data: SensorData,
51
+ elem_dims: int,
52
+ field_name: str = "temperature",
53
+ errs_pc: float = 1.0
54
+ ) -> SensorArrayPoint:
55
+
56
+ sens_array = SensorArrayFactory.thermocouples_no_errs(sim_data,
57
+ sensor_data,
58
+ elem_dims,
59
+ field_name)
60
+
61
+ err_int = basic_err_integrator(sens_array.get_measurement_shape(),
62
+ sensor_data,
63
+ sys_err_pc=errs_pc,
64
+ rand_err_pc=errs_pc)
65
+
66
+ # Normal thermcouple amp = 5mV / K
67
+ err_int._err_chain.append(ErrSysDigitisation(bits_per_unit=2**16/1000))
68
+ err_int._err_chain.append(ErrSysSaturation(meas_min=0.0,meas_max=1000.0))
69
+
70
+ sens_array.set_error_integrator(err_int)
71
+ return sens_array
72
+
73
+
74
+
75
+ @staticmethod
76
+ def disp_sensors_no_errs(sim_data: mh.SimData,
77
+ sensor_data: SensorData,
78
+ elem_dims: int,
79
+ field_name: str,
80
+ field_comps: tuple[str,...],
81
+ ) -> SensorArrayPoint:
82
+
83
+ descriptor = SensorDescriptorFactory.displacement_descriptor()
84
+
85
+ disp_field = FieldVector(sim_data,
86
+ field_name,
87
+ field_comps,
88
+ elem_dims)
89
+
90
+ sens_array = SensorArrayPoint(sensor_data,
91
+ disp_field,
92
+ descriptor)
93
+ return sens_array
94
+
95
+
96
+ @staticmethod
97
+ def disp_sensors_basic_errs(sim_data: mh.SimData,
98
+ sensor_data: SensorData,
99
+ elem_dims: int,
100
+ field_name: str,
101
+ field_comps: tuple[str,...],
102
+ errs_pc: float = 1.0,
103
+ ) -> SensorArrayPoint:
104
+
105
+ sens_array = SensorArrayFactory.disp_sensors_no_errs(sim_data,
106
+ sensor_data,
107
+ elem_dims,
108
+ field_name,
109
+ field_comps)
110
+ err_int = basic_err_integrator(sens_array.get_measurement_shape(),
111
+ sensor_data,
112
+ sys_err_pc=errs_pc,
113
+ rand_err_pc=errs_pc)
114
+ sens_array.set_error_integrator(err_int)
115
+
116
+ return sens_array
117
+
118
+ @staticmethod
119
+ def strain_gauges_no_errs(sim_data: mh.SimData,
120
+ sensor_data: SensorData,
121
+ elem_dims: int,
122
+ field_name: str,
123
+ norm_comps: tuple[str,...],
124
+ dev_comps: tuple[str,...]
125
+ ) -> SensorArrayPoint:
126
+ descriptor = SensorDescriptorFactory.strain_descriptor(elem_dims)
127
+
128
+ strain_field = FieldTensor(sim_data,
129
+ field_name,
130
+ norm_comps,
131
+ dev_comps,
132
+ elem_dims)
133
+
134
+ sens_array = SensorArrayPoint(sensor_data,
135
+ strain_field,
136
+ descriptor)
137
+
138
+ return sens_array
139
+
140
+
141
+ @staticmethod
142
+ def strain_gauges_basic_errs(sim_data: mh.SimData,
143
+ sensor_data: SensorData,
144
+ elem_dims: int,
145
+ field_name: str,
146
+ norm_comps: tuple[str,...],
147
+ dev_comps: tuple[str,...],
148
+ errs_pc: float = 1.0
149
+ ) -> SensorArrayPoint:
150
+
151
+ sens_array = SensorArrayFactory.strain_gauges_no_errs(sim_data,
152
+ sensor_data,
153
+ elem_dims,
154
+ field_name,
155
+ norm_comps,
156
+ dev_comps)
157
+
158
+ err_int = basic_err_integrator(sens_array.get_measurement_shape(),
159
+ sensor_data,
160
+ sys_err_pc=errs_pc,
161
+ rand_err_pc=errs_pc)
162
+ sens_array.set_error_integrator(err_int)
163
+
164
+ return sens_array
165
+
166
+
167
+ def basic_err_integrator(meas_shape: np.ndarray,
168
+ sensor_data: SensorData,
169
+ sys_err_pc: float = 1.0,
170
+ rand_err_pc: float = 1.0) -> ErrIntegrator:
171
+ """Builds a basic error integrator with uniform percentage systematic error
172
+ calculator and a percentage normal random error calculator.
173
+
174
+ Parameters
175
+ ----------
176
+ meas_shape : np.ndarray
177
+ Shape of the measurement array which is (num_sensors,
178
+ num_field_components,num_time_steps)
179
+ sensor_data : SensorData
180
+ Sensor array parameters for feeding through the error chain.
181
+ sys_err_pc : float, optional
182
+ Percentage systematic error, by default 1.0.
183
+ rand_err_pc : float, optional
184
+ Percentage random error, by default 1.0.
185
+
186
+ Returns
187
+ -------
188
+ ErrIntegrator
189
+ A basic error integrator with a uniform percentage systematic error and
190
+ a normal percentage random error.
191
+ """
192
+ err_chain = []
193
+ err_chain.append(ErrSysUnifPercent(-sys_err_pc,sys_err_pc))
194
+ err_chain.append(ErrRandNormPercent(rand_err_pc))
195
+ err_int = ErrIntegrator(err_chain,sensor_data,meas_shape)
196
+ return err_int