pyvale 2025.4.0__py3-none-any.whl → 2025.5.1__py3-none-any.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 (153) hide show
  1. pyvale/__init__.py +78 -64
  2. pyvale/analyticmeshgen.py +102 -0
  3. pyvale/{core/analyticsimdatafactory.py → analyticsimdatafactory.py} +44 -16
  4. pyvale/analyticsimdatagenerator.py +323 -0
  5. pyvale/blendercalibrationdata.py +15 -0
  6. pyvale/blenderlightdata.py +26 -0
  7. pyvale/blendermaterialdata.py +15 -0
  8. pyvale/blenderrenderdata.py +30 -0
  9. pyvale/blenderscene.py +488 -0
  10. pyvale/blendertools.py +420 -0
  11. pyvale/{core/camera.py → camera.py} +15 -15
  12. pyvale/{core/cameradata.py → cameradata.py} +27 -22
  13. pyvale/{core/cameradata2d.py → cameradata2d.py} +8 -6
  14. pyvale/camerastereo.py +217 -0
  15. pyvale/{core/cameratools.py → cameratools.py} +220 -26
  16. pyvale/{core/cython → cython}/rastercyth.py +11 -7
  17. pyvale/data/__init__.py +5 -7
  18. pyvale/data/cal_target.tiff +0 -0
  19. pyvale/data/case00_HEX20_out.e +0 -0
  20. pyvale/data/case00_HEX27_out.e +0 -0
  21. pyvale/data/case00_HEX8_out.e +0 -0
  22. pyvale/data/case00_TET10_out.e +0 -0
  23. pyvale/data/case00_TET14_out.e +0 -0
  24. pyvale/data/case00_TET4_out.e +0 -0
  25. pyvale/{core/dataset.py → dataset.py} +91 -16
  26. pyvale/{core/errorcalculator.py → errorcalculator.py} +13 -16
  27. pyvale/{core/errordriftcalc.py → errordriftcalc.py} +14 -14
  28. pyvale/{core/errorintegrator.py → errorintegrator.py} +25 -28
  29. pyvale/{core/errorrand.py → errorrand.py} +39 -46
  30. pyvale/errorsyscalib.py +134 -0
  31. pyvale/{core/errorsysdep.py → errorsysdep.py} +25 -29
  32. pyvale/{core/errorsysfield.py → errorsysfield.py} +59 -52
  33. pyvale/{core/errorsysindep.py → errorsysindep.py} +85 -182
  34. pyvale/examples/__init__.py +5 -7
  35. pyvale/examples/basics/ex1_1_basicscalars_therm2d.py +131 -0
  36. pyvale/examples/basics/ex1_2_sensormodel_therm2d.py +158 -0
  37. pyvale/examples/basics/ex1_3_customsens_therm3d.py +216 -0
  38. pyvale/examples/basics/ex1_4_basicerrors_therm3d.py +153 -0
  39. pyvale/examples/basics/ex1_5_fielderrs_therm3d.py +168 -0
  40. pyvale/examples/basics/ex1_6_caliberrs_therm2d.py +133 -0
  41. pyvale/examples/basics/ex1_7_spatavg_therm2d.py +123 -0
  42. pyvale/examples/basics/ex2_1_basicvectors_disp2d.py +112 -0
  43. pyvale/examples/basics/ex2_2_vectorsens_disp2d.py +111 -0
  44. pyvale/examples/basics/ex2_3_sensangle_disp2d.py +139 -0
  45. pyvale/examples/basics/ex2_4_chainfielderrs_disp2d.py +196 -0
  46. pyvale/examples/basics/ex2_5_vectorfields3d_disp3d.py +109 -0
  47. pyvale/examples/basics/ex3_1_basictensors_strain2d.py +114 -0
  48. pyvale/examples/basics/ex3_2_tensorsens2d_strain2d.py +111 -0
  49. pyvale/examples/basics/ex3_3_tensorsens3d_strain3d.py +182 -0
  50. pyvale/examples/basics/ex4_1_expsim2d_thermmech2d.py +171 -0
  51. pyvale/examples/basics/ex4_2_expsim3d_thermmech3d.py +252 -0
  52. pyvale/examples/{analyticdatagen → genanalyticdata}/ex1_1_scalarvisualisation.py +6 -9
  53. pyvale/examples/{analyticdatagen → genanalyticdata}/ex1_2_scalarcasebuild.py +8 -11
  54. pyvale/examples/{analyticdatagen → genanalyticdata}/ex2_1_analyticsensors.py +9 -12
  55. pyvale/examples/imagedef2d/ex_imagedef2d_todisk.py +8 -15
  56. pyvale/examples/renderblender/ex1_1_blenderscene.py +121 -0
  57. pyvale/examples/renderblender/ex1_2_blenderdeformed.py +119 -0
  58. pyvale/examples/renderblender/ex2_1_stereoscene.py +128 -0
  59. pyvale/examples/renderblender/ex2_2_stereodeformed.py +131 -0
  60. pyvale/examples/renderblender/ex3_1_blendercalibration.py +120 -0
  61. pyvale/examples/{rasterisation → renderrasterisation}/ex_rastenp.py +6 -7
  62. pyvale/examples/{rasterisation → renderrasterisation}/ex_rastercyth_oneframe.py +5 -7
  63. pyvale/examples/{rasterisation → renderrasterisation}/ex_rastercyth_static_cypara.py +6 -13
  64. pyvale/examples/{rasterisation → renderrasterisation}/ex_rastercyth_static_pypara.py +9 -12
  65. pyvale/examples/{ex1_4_thermal2d.py → visualisation/ex1_1_plot_traces.py} +33 -20
  66. pyvale/examples/{features/ex_animation_tools_3dmonoblock.py → visualisation/ex2_1_animate_sim.py} +37 -31
  67. pyvale/experimentsimulator.py +175 -0
  68. pyvale/{core/field.py → field.py} +6 -14
  69. pyvale/fieldconverter.py +351 -0
  70. pyvale/{core/fieldsampler.py → fieldsampler.py} +9 -10
  71. pyvale/{core/fieldscalar.py → fieldscalar.py} +17 -18
  72. pyvale/{core/fieldtensor.py → fieldtensor.py} +23 -26
  73. pyvale/{core/fieldtransform.py → fieldtransform.py} +9 -5
  74. pyvale/{core/fieldvector.py → fieldvector.py} +14 -16
  75. pyvale/{core/generatorsrandom.py → generatorsrandom.py} +29 -52
  76. pyvale/{core/imagedef2d.py → imagedef2d.py} +11 -8
  77. pyvale/{core/integratorfactory.py → integratorfactory.py} +12 -13
  78. pyvale/{core/integratorquadrature.py → integratorquadrature.py} +57 -32
  79. pyvale/integratorrectangle.py +165 -0
  80. pyvale/{core/integratorspatial.py → integratorspatial.py} +9 -10
  81. pyvale/{core/integratortype.py → integratortype.py} +7 -8
  82. pyvale/output.py +17 -0
  83. pyvale/pyvaleexceptions.py +11 -0
  84. pyvale/{core/raster.py → raster.py} +8 -8
  85. pyvale/{core/rastercy.py → rastercy.py} +11 -10
  86. pyvale/{core/rasternp.py → rasternp.py} +12 -13
  87. pyvale/{core/rendermesh.py → rendermesh.py} +10 -19
  88. pyvale/{core/sensorarray.py → sensorarray.py} +7 -8
  89. pyvale/{core/sensorarrayfactory.py → sensorarrayfactory.py} +64 -78
  90. pyvale/{core/sensorarraypoint.py → sensorarraypoint.py} +39 -41
  91. pyvale/{core/sensordata.py → sensordata.py} +7 -8
  92. pyvale/sensordescriptor.py +213 -0
  93. pyvale/{core/sensortools.py → sensortools.py} +8 -9
  94. pyvale/simcases/case00_HEX20.i +5 -5
  95. pyvale/simcases/case00_HEX27.i +5 -5
  96. pyvale/simcases/case00_HEX8.i +242 -0
  97. pyvale/simcases/case00_TET10.i +2 -2
  98. pyvale/simcases/case00_TET14.i +2 -2
  99. pyvale/simcases/case00_TET4.i +242 -0
  100. pyvale/simcases/run_1case.py +1 -1
  101. pyvale/simtools.py +67 -0
  102. pyvale/visualexpplotter.py +191 -0
  103. pyvale/{core/visualimagedef.py → visualimagedef.py} +13 -10
  104. pyvale/{core/visualimages.py → visualimages.py} +10 -9
  105. pyvale/visualopts.py +493 -0
  106. pyvale/{core/visualsimanimator.py → visualsimanimator.py} +47 -19
  107. pyvale/visualsimsensors.py +318 -0
  108. pyvale/visualtools.py +136 -0
  109. pyvale/visualtraceplotter.py +142 -0
  110. {pyvale-2025.4.0.dist-info → pyvale-2025.5.1.dist-info}/METADATA +17 -14
  111. pyvale-2025.5.1.dist-info/RECORD +172 -0
  112. {pyvale-2025.4.0.dist-info → pyvale-2025.5.1.dist-info}/WHEEL +1 -1
  113. pyvale/core/__init__.py +0 -7
  114. pyvale/core/analyticmeshgen.py +0 -59
  115. pyvale/core/analyticsimdatagenerator.py +0 -160
  116. pyvale/core/cython/rastercyth.c +0 -32267
  117. pyvale/core/experimentsimulator.py +0 -99
  118. pyvale/core/fieldconverter.py +0 -154
  119. pyvale/core/integratorrectangle.py +0 -88
  120. pyvale/core/optimcheckfuncs.py +0 -153
  121. pyvale/core/sensordescriptor.py +0 -101
  122. pyvale/core/visualexpplotter.py +0 -151
  123. pyvale/core/visualopts.py +0 -180
  124. pyvale/core/visualsimplotter.py +0 -182
  125. pyvale/core/visualtools.py +0 -81
  126. pyvale/core/visualtraceplotter.py +0 -256
  127. pyvale/examples/analyticdatagen/__init__.py +0 -7
  128. pyvale/examples/ex1_1_thermal2d.py +0 -89
  129. pyvale/examples/ex1_2_thermal2d.py +0 -111
  130. pyvale/examples/ex1_3_thermal2d.py +0 -113
  131. pyvale/examples/ex1_5_thermal2d.py +0 -105
  132. pyvale/examples/ex2_1_thermal3d .py +0 -87
  133. pyvale/examples/ex2_2_thermal3d.py +0 -51
  134. pyvale/examples/ex2_3_thermal3d.py +0 -109
  135. pyvale/examples/ex3_1_displacement2d.py +0 -47
  136. pyvale/examples/ex3_2_displacement2d.py +0 -79
  137. pyvale/examples/ex3_3_displacement2d.py +0 -104
  138. pyvale/examples/ex3_4_displacement2d.py +0 -105
  139. pyvale/examples/ex4_1_strain2d.py +0 -57
  140. pyvale/examples/ex4_2_strain2d.py +0 -79
  141. pyvale/examples/ex4_3_strain2d.py +0 -100
  142. pyvale/examples/ex5_1_multiphysics2d.py +0 -78
  143. pyvale/examples/ex6_1_multiphysics2d_expsim.py +0 -118
  144. pyvale/examples/ex6_2_multiphysics3d_expsim.py +0 -158
  145. pyvale/examples/features/__init__.py +0 -7
  146. pyvale/examples/features/ex_area_avg.py +0 -89
  147. pyvale/examples/features/ex_calibration_error.py +0 -108
  148. pyvale/examples/features/ex_chain_field_errs.py +0 -141
  149. pyvale/examples/features/ex_field_errs.py +0 -78
  150. pyvale/examples/features/ex_sensor_single_angle_batch.py +0 -110
  151. pyvale-2025.4.0.dist-info/RECORD +0 -157
  152. {pyvale-2025.4.0.dist-info → pyvale-2025.5.1.dist-info}/licenses/LICENSE +0 -0
  153. {pyvale-2025.4.0.dist-info → pyvale-2025.5.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,351 @@
1
+ # ==============================================================================
2
+ # pyvale: the python validation engine
3
+ # License: MIT
4
+ # Copyright (C) 2025 The Computer Aided Validation Team
5
+ # ==============================================================================
6
+
7
+ """
8
+ This module provides functions for manipulating simulation data objects to be
9
+ compatible with the underlying machinery of pyvale.
10
+ """
11
+
12
+ import numpy as np
13
+ import pyvista as pv
14
+ from pyvista import CellType
15
+ import mooseherder as mh
16
+
17
+ def simdata_to_pyvista(sim_data: mh.SimData,
18
+ components: tuple[str,...] | None,
19
+ elem_dims: int
20
+ ) -> tuple[pv.UnstructuredGrid,pv.UnstructuredGrid]:
21
+ """Converts the mesh and field data in a `SimData` object into a pyvista
22
+ UnstructuredGrid for sampling (interpolating) the data and visualisation.
23
+
24
+ Parameters
25
+ ----------
26
+ sim_data : mh.SimData
27
+ Object containing a mesh and associated field data from a simulation.
28
+ components : tuple[str,...] | None
29
+ String keys for the components of the field to extract from the
30
+ simulation data.
31
+ elem_dim : int
32
+ Number of spatial dimensions (2 or 3) used to determine the element
33
+ types in the mesh from the number of nodes per element.
34
+
35
+ Returns
36
+ -------
37
+ tuple[pv.UnstructuredGrid,pv.UnstructuredGrid]
38
+ The first UnstructuredGrid has the field components attached as dataset
39
+ arrays. The second has no field data attached for visualisation.
40
+ """
41
+ flat_connect = np.array([],dtype=np.int64)
42
+ cell_types = np.array([],dtype=np.int64)
43
+
44
+ for cc in sim_data.connect:
45
+ # NOTE: need the -1 here to make element numbers 0 indexed!
46
+ this_connect = np.copy(sim_data.connect[cc])-1
47
+ (nodes_per_elem,n_elems) = this_connect.shape
48
+
49
+ this_cell_type = _get_pyvista_cell_type(nodes_per_elem,elem_dims)
50
+ assert this_cell_type is not None, ("Cell type with dimension " +
51
+ f"{elem_dims} and {nodes_per_elem} nodes per element not recognised.")
52
+
53
+ # VTK and exodus have different winding for 3D higher order quads
54
+ this_connect = _exodus_to_pyvista_connect(this_cell_type,this_connect)
55
+
56
+ this_connect = this_connect.T.flatten()
57
+ idxs = np.arange(0,n_elems*nodes_per_elem,nodes_per_elem,dtype=np.int64)
58
+
59
+ this_connect = np.insert(this_connect,idxs,nodes_per_elem)
60
+
61
+ cell_types = np.hstack((cell_types,np.full(n_elems,this_cell_type)))
62
+ flat_connect = np.hstack((flat_connect,this_connect),dtype=np.int64)
63
+
64
+ cells = flat_connect
65
+
66
+ points = sim_data.coords
67
+ pv_grid = pv.UnstructuredGrid(cells, cell_types, points)
68
+ pv_grid_vis = pv.UnstructuredGrid(cells, cell_types, points)
69
+
70
+ if components is not None and sim_data.node_vars is not None:
71
+ for cc in components:
72
+ pv_grid[cc] = sim_data.node_vars[cc]
73
+
74
+ return (pv_grid,pv_grid_vis)
75
+
76
+
77
+ def scale_length_units(scale: float,
78
+ sim_data: mh.SimData,
79
+ disp_comps: tuple[str,...] | None = None,
80
+ ) -> mh.SimData:
81
+ """Used to scale the length units of a simulation. Commonly used to convert
82
+ SI units to mm for use with visualisation tools and rendering algorithms.
83
+
84
+ Parameters
85
+ ----------
86
+ scale : float
87
+ Scale multiplier used to scale the coordinates and displacement fields
88
+ if specified.
89
+ sim_data : mh.SimData
90
+ Simulation dataclass that will be scaled.
91
+ disp_comps : tuple[str,...] | None, optional
92
+ Tuple of string keys for the displacement keys to be scaled, by default
93
+ None. If None then the displacements are not scaled.
94
+
95
+ Returns
96
+ -------
97
+ mh.SimData
98
+ Simulation dataclass with scaled length units.
99
+ """
100
+ sim_data.coords = sim_data.coords*scale
101
+
102
+ if disp_comps is not None:
103
+ for cc in disp_comps:
104
+ sim_data.node_vars[cc] = sim_data.node_vars[cc]*scale
105
+
106
+ return sim_data
107
+
108
+
109
+ # TODO: make this work for sim_data with multiple connectivity
110
+ def extract_surf_mesh(sim_data: mh.SimData) -> mh.SimData:
111
+ """Extracts a surface mesh from a 3D simulation dataclass. Useful for
112
+ limiting the memory required for analysing sensors that only measure surface
113
+ fields. This function currently supports:
114
+ - A single connectivity table
115
+ - Higher order retrahedral and hexahedral elements (but not wedges or
116
+ pyramids)
117
+
118
+ NOTE: this function returns the surface mesh with element nodal winding
119
+ consistent with th exodus output format.
120
+
121
+ Parameters
122
+ ----------
123
+ sim_data : mh.SimData
124
+ Simulation dataclass containing the 3D mesh from which the surface mesh
125
+ is to be extracted.
126
+
127
+ Returns
128
+ -------
129
+ mh.SimData
130
+ Simulation data class containing the data for the surface mesh.
131
+ """
132
+
133
+ # NOTE: need to fix exodus 1 indexing for now and put it back at the end
134
+ # shape=(nodes_per_elem,num_elems)
135
+ connect = np.copy(sim_data.connect["connect1"])-1
136
+ num_elems = connect.shape[1]
137
+
138
+ assert "connect2" not in sim_data.connect, \
139
+ "Multiple connectivity tables not supported yet."
140
+
141
+ # Mapping of node numbers to faces for each element face
142
+ face_map = _get_surf_map(nodes_per_elem=connect.shape[0])
143
+ faces_per_elem = face_map.shape[0]
144
+ nodes_per_face = face_map.shape[1]
145
+
146
+ # shape=(faces_per_elem,nodes_per_face,num_elems)
147
+ faces_wound = connect[face_map,:]
148
+ # shape=(num_elems,faces_per_elem,nodes_per_face)
149
+ faces_wound = faces_wound.transpose((2,0,1))
150
+
151
+ # Create an array of all faces with shape=(total_faces,nodes_per_face)
152
+ faces_total = faces_per_elem*num_elems
153
+ faces_flat_wound = faces_wound.reshape((faces_total,nodes_per_face))
154
+ # Sort the rows so nodes are in the same order when comparing them
155
+ faces_flat_sorted = np.copy(np.sort(faces_flat_wound,axis=1))
156
+
157
+ # Count each unique face in the list of faces, faces that appear only once
158
+ # must be external faces
159
+ (_,
160
+ faces_unique_inds,
161
+ faces_unique_counts) = np.unique(faces_flat_sorted,
162
+ axis=0,
163
+ return_counts=True,
164
+ return_index=True)
165
+
166
+ # Indices of the external faces in faces_flat
167
+ faces_ext_inds_in_unique = np.where(faces_unique_counts==1)[0]
168
+
169
+ # shape=(num_ext_faces,nodes_per_face)
170
+ faces_ext_inds = faces_unique_inds[faces_ext_inds_in_unique]
171
+
172
+ faces_ext_wound = faces_flat_wound[faces_ext_inds]
173
+
174
+ faces_coord_inds = np.unique(faces_ext_wound.flatten())
175
+ faces_coords = np.copy(sim_data.coords[faces_coord_inds])
176
+
177
+ faces_shape = faces_ext_wound.shape
178
+ faces_ext_wound_flat = faces_ext_wound.flatten()
179
+ faces_ext_remap_flat = np.copy(faces_ext_wound_flat)
180
+
181
+ # Remap coordinates in the connectivity to match the trimmed list of coords
182
+ # that belong to the external faces
183
+ for mm,cc in enumerate(faces_coord_inds):
184
+ if mm == cc:
185
+ continue
186
+
187
+ ind_to_map = np.where(faces_ext_wound_flat == cc)[0]
188
+ faces_ext_remap_flat[ind_to_map] = mm
189
+
190
+ faces_ext_remap = faces_ext_remap_flat.reshape(faces_shape)
191
+ faces_ext_remap = faces_ext_remap + 1 # back to exodus 1 index
192
+
193
+ # Now we build the SimData object and slice out the node and element
194
+ # variables using the coordinate indexing.
195
+ face_data = mh.SimData(coords=faces_coords,
196
+ connect={"connect1":faces_ext_remap.T},
197
+ time=sim_data.time)
198
+
199
+ if sim_data.node_vars is not None:
200
+ face_data.node_vars = {}
201
+ for nn in sim_data.node_vars:
202
+ face_data.node_vars[nn] = sim_data.node_vars[nn][faces_coord_inds,:]
203
+
204
+ if sim_data.elem_vars is not None:
205
+ face_data.elem_vars = {}
206
+ for ee in sim_data.node_vars:
207
+ face_data.elem_vars[ee] = sim_data.elem_vars[ee][faces_coord_inds,:]
208
+
209
+ return face_data
210
+
211
+
212
+ def _get_pyvista_cell_type(nodes_per_elem: int, spat_dim: int) -> CellType | None:
213
+ """Helper function to identify the pyvista element type in the mesh.
214
+
215
+ Parameters
216
+ ----------
217
+ nodes_per_elem : int
218
+ Number of nodes per element.
219
+ spat_dim : int
220
+ Number of spatial dimensions in the mesh (2 or 3).
221
+
222
+ Returns
223
+ -------
224
+ CellType | None
225
+ Enumeration describing the element type in pyvista.
226
+ """
227
+ cell_type = None
228
+
229
+ if spat_dim == 2:
230
+ if nodes_per_elem == 4:
231
+ cell_type = CellType.QUAD
232
+ elif nodes_per_elem == 3:
233
+ cell_type = CellType.TRIANGLE
234
+ elif nodes_per_elem == 6:
235
+ cell_type = CellType.QUADRATIC_TRIANGLE
236
+ elif nodes_per_elem == 7:
237
+ cell_type = CellType.BIQUADRATIC_TRIANGLE
238
+ elif nodes_per_elem == 8:
239
+ cell_type = CellType.QUADRATIC_QUAD
240
+ elif nodes_per_elem == 9:
241
+ cell_type = CellType.BIQUADRATIC_QUAD
242
+ else:
243
+ if nodes_per_elem == 8:
244
+ cell_type = CellType.HEXAHEDRON
245
+ elif nodes_per_elem == 4:
246
+ cell_type = CellType.TETRA
247
+ elif nodes_per_elem == 10:
248
+ cell_type = CellType.QUADRATIC_TETRA
249
+ elif nodes_per_elem == 20:
250
+ cell_type = CellType.QUADRATIC_HEXAHEDRON
251
+ elif nodes_per_elem == 27:
252
+ cell_type = CellType.TRIQUADRATIC_HEXAHEDRON
253
+
254
+ return cell_type
255
+
256
+
257
+ def _exodus_to_pyvista_connect(cell_type: CellType,
258
+ connect: np.ndarray) -> np.ndarray:
259
+ """Helper function that specifies the nodal winding map for higher order
260
+ tet and hex elements between the exodus output format and pyvista (VTK).
261
+
262
+ Parameters
263
+ ----------
264
+ cell_type : CellType
265
+ pyvista (VTK) cell type enumeration.
266
+ connect : np.ndarray
267
+ Input connectivity table in exodus winding format.
268
+ shape=(nodes_per_elem,num_elems)
269
+
270
+ Returns
271
+ -------
272
+ np.ndarray
273
+ Output connectivity table in pyvista (VTK) format.
274
+ shape=(nodes_per_elem,num_elems)
275
+ """
276
+ copy_connect = np.copy(connect)
277
+
278
+ # NOTE: it looks like VTK does not support TET14
279
+ # VTK and exodus have different winding for 3D higher order quads
280
+ if cell_type == CellType.QUADRATIC_HEXAHEDRON:
281
+ connect[12:16,:] = copy_connect[16:20,:]
282
+ connect[16:20,:] = copy_connect[12:16,:]
283
+ elif cell_type == CellType.TRIQUADRATIC_HEXAHEDRON:
284
+ connect[12:16,:] = copy_connect[16:20,:]
285
+ connect[16:20,:] = copy_connect[12:16,:]
286
+ connect[20:24,:] = copy_connect[23:27,:]
287
+ connect[24,:] = copy_connect[21,:]
288
+ connect[25,:] = copy_connect[22,:]
289
+ connect[26,:] = copy_connect[20,:]
290
+
291
+ return connect
292
+
293
+
294
+ def _get_surf_map(nodes_per_elem: int) -> np.ndarray:
295
+ """Helper function specifying the mapping from 3D tet and hex elements to
296
+ the individual faces consistent with the exodus output format.
297
+
298
+ Parameters
299
+ ----------
300
+ nodes_per_elem : int
301
+ Number of nodes per element.
302
+
303
+ Returns
304
+ -------
305
+ np.ndarray
306
+ Array of indices mapping the nodes to faces with shape=(num_faces,n
307
+ odes_per_face)
308
+
309
+ Raises
310
+ ------
311
+ ValueError
312
+ Element type is not supported.
313
+ """
314
+ if nodes_per_elem == 4: # TET4
315
+ return np.array(((0,1,2),
316
+ (0,3,1),
317
+ (0,2,3),
318
+ (1,3,2)))
319
+
320
+ if nodes_per_elem == 8: # HEX8
321
+ return np.array(((0,1,2,3),
322
+ (0,3,7,4),
323
+ (4,7,6,5),
324
+ (1,5,6,2),
325
+ (0,4,5,1),
326
+ (2,6,7,3)))
327
+
328
+ if nodes_per_elem == 10: # TET10
329
+ return np.array(((0,1,2,4,5,6),
330
+ (0,3,1,4,8,7),
331
+ (0,2,3,6,9,7),
332
+ (1,3,2,8,9,5)))
333
+
334
+ if nodes_per_elem == 20: # HEX20
335
+ return np.array(((0,1,2,3,8,9,10,11),
336
+ (0,3,7,4,11,15,19,12),
337
+ (4,7,6,5,19,18,17,16),
338
+ (1,5,6,2,13,17,14,9),
339
+ (0,4,5,1,12,16,13,8),
340
+ (2,6,7,3,14,18,15,10)))
341
+
342
+ if nodes_per_elem == 27: # HEX27
343
+ return np.array(((0,1,2,3,8,9,10,11,21),
344
+ (0,3,7,4,11,15,19,12,23),
345
+ (4,7,6,5,19,18,17,16,22),
346
+ (1,5,6,2,13,17,14,9,24),
347
+ (0,4,5,1,12,16,13,8,25),
348
+ (2,6,7,3,14,18,15,10,26)))
349
+
350
+ raise ValueError("Number of nodes does not match a 3D element type for " \
351
+ "surface extraction.")
@@ -1,15 +1,14 @@
1
- """
2
- ================================================================================
3
- pyvale: the python validation engine
4
- License: MIT
5
- Copyright (C) 2025 The Computer Aided Validation Team
6
- ================================================================================
7
- """
1
+ # ==============================================================================
2
+ # pyvale: the python validation engine
3
+ # License: MIT
4
+ # Copyright (C) 2025 The Computer Aided Validation Team
5
+ # ==============================================================================
6
+
8
7
  import numpy as np
9
8
  import pyvista as pv
10
- from pyvale.core.field import IField
11
- from pyvale.core.sensordata import SensorData
12
- from pyvale.core.integratorfactory import build_spatial_averager
9
+ from pyvale.field import IField
10
+ from pyvale.sensordata import SensorData
11
+ from pyvale.integratorfactory import build_spatial_averager
13
12
 
14
13
 
15
14
  def sample_field_with_sensor_data(field: IField, sensor_data: SensorData
@@ -1,18 +1,18 @@
1
- """
2
- ================================================================================
3
- pyvale: the python validation engine
4
- License: MIT
5
- Copyright (C) 2025 The Computer Aided Validation Team
6
- ================================================================================
7
- """
1
+ # ==============================================================================
2
+ # pyvale: the python validation engine
3
+ # License: MIT
4
+ # Copyright (C) 2025 The Computer Aided Validation Team
5
+ # ==============================================================================
6
+
8
7
  import numpy as np
9
8
  import pyvista as pv
10
9
  from scipy.spatial.transform import Rotation
11
10
  import mooseherder as mh
12
11
 
13
- from pyvale.core.field import IField
14
- from pyvale.core.fieldconverter import simdata_to_pyvista
15
- from pyvale.core.fieldsampler import sample_pyvista_grid
12
+ from pyvale.field import IField
13
+ from pyvale.fieldconverter import simdata_to_pyvista
14
+ from pyvale.fieldsampler import sample_pyvista_grid
15
+
16
16
 
17
17
  class FieldScalar(IField):
18
18
  """Class for sampling (interpolating) scalar fields from simulations to
@@ -20,34 +20,33 @@ class FieldScalar(IField):
20
20
 
21
21
  Implements the `IField` interface.
22
22
  """
23
- __slots__ = ("_field_key","_spat_dims","_sim_data","_pyvista_grid",
23
+ __slots__ = ("_field_key","_elem_dims","_sim_data","_pyvista_grid",
24
24
  "_pyvista_vis")
25
25
 
26
26
  def __init__(self,
27
27
  sim_data: mh.SimData,
28
28
  field_key: str,
29
- spat_dims: int) -> None:
30
- """Initialiser for the `FieldScalar` class.
31
-
29
+ elem_dims: int) -> None:
30
+ """
32
31
  Parameters
33
32
  ----------
34
33
  sim_data : mh.SimData
35
34
  Simulation data object containing the mesh and field to interpolate.
36
35
  field_key : str
37
36
  String key for the scalar field component in the `SimData` object.
38
- spat_dims : int
37
+ elem_dims : int
39
38
  Number of spatial dimensions (2 or 3) used for identifying element
40
39
  types.
41
40
  """
42
41
 
43
42
  self._field_key = field_key
44
- self._spat_dims = spat_dims
43
+ self._elem_dims = elem_dims
45
44
 
46
45
  self._sim_data = sim_data
47
46
  (self._pyvista_grid,self._pyvista_vis) = simdata_to_pyvista(
48
47
  self._sim_data,
49
48
  (self._field_key,),
50
- self._spat_dims
49
+ self._elem_dims
51
50
  )
52
51
 
53
52
  def set_sim_data(self, sim_data: mh.SimData) -> None:
@@ -65,7 +64,7 @@ class FieldScalar(IField):
65
64
  (self._pyvista_grid,self._pyvista_vis) = simdata_to_pyvista(
66
65
  sim_data,
67
66
  (self._field_key,),
68
- self._spat_dims
67
+ self._elem_dims
69
68
  )
70
69
 
71
70
  def get_sim_data(self) -> mh.SimData:
@@ -1,23 +1,24 @@
1
- """
2
- ================================================================================
3
- pyvale: the python validation engine
4
- License: MIT
5
- Copyright (C) 2025 The Computer Aided Validation Team
6
- ================================================================================
7
- """
1
+ # ==============================================================================
2
+ # pyvale: the python validation engine
3
+ # License: MIT
4
+ # Copyright (C) 2025 The Computer Aided Validation Team
5
+ # ==============================================================================
6
+
8
7
  import numpy as np
9
8
  import pyvista as pv
10
9
  from scipy.spatial.transform import Rotation
11
10
  import mooseherder as mh
12
11
 
13
- from pyvale.core.field import IField
14
- from pyvale.core.fieldconverter import simdata_to_pyvista
15
- from pyvale.core.fieldsampler import sample_pyvista_grid
16
- from pyvale.core.fieldtransform import (transform_tensor_2d,
12
+ from pyvale.field import IField
13
+ from pyvale.fieldconverter import simdata_to_pyvista
14
+ from pyvale.fieldsampler import sample_pyvista_grid
15
+ from pyvale.fieldtransform import (transform_tensor_2d,
17
16
  transform_tensor_2d_batch,
18
17
  transform_tensor_3d,
19
18
  transform_tensor_3d_batch)
20
19
 
20
+ # TODO:
21
+ # - Checking to ensure normal and dev components are consistent
21
22
 
22
23
  class FieldTensor(IField):
23
24
  """Class for sampling (interpolating) tensor fields from simulations to
@@ -30,32 +31,28 @@ class FieldTensor(IField):
30
31
 
31
32
  def __init__(self,
32
33
  sim_data: mh.SimData,
33
- field_key: str,
34
- norm_components: tuple[str,...],
35
- dev_components: tuple[str,...],
36
- spat_dims: int) -> None:
37
- """Initialiser for the `FieldVector` class.
38
-
34
+ field_name: str,
35
+ norm_comps: tuple[str,...],
36
+ dev_comps: tuple[str,...],
37
+ elem_dims: int) -> None:
38
+ """
39
39
  Parameters
40
40
  ----------
41
41
  sim_data : mh.SimData
42
42
  Simulation data object containing the mesh and field to interpolate.
43
- field_key : str
43
+ field_name : str
44
44
  String describing the tensor field. For example: 'strain'.
45
45
  components : tuple[str,...]
46
46
  String keys to the field components in the `SimData` object. For
47
47
  example ('stain_xx','strain_yy','strain_xy').
48
- spat_dims : int
48
+ elem_dims : int
49
49
  Number of spatial dimensions (2 or 3) used for identifying element
50
50
  types.
51
51
  """
52
- self._field_key = field_key
53
- self._norm_components = norm_components
54
- self._dev_components = dev_components
55
- self._spat_dims = spat_dims
56
-
57
- #TODO: do some checking to make sure norm/dev components are consistent
58
- # based on the spatial dimensions
52
+ self._field_key = field_name
53
+ self._norm_components = norm_comps
54
+ self._dev_components = dev_comps
55
+ self._spat_dims = elem_dims
59
56
 
60
57
  self._sim_data = sim_data
61
58
  (self._pyvista_grid,self._pyvista_vis) = simdata_to_pyvista(
@@ -1,10 +1,14 @@
1
+ # ==============================================================================
2
+ # pyvale: the python validation engine
3
+ # License: MIT
4
+ # Copyright (C) 2025 The Computer Aided Validation Team
5
+ # ==============================================================================
6
+
1
7
  """
2
- ================================================================================
3
- pyvale: the python validation engine
4
- License: MIT
5
- Copyright (C) 2025 The Computer Aided Validation Team
6
- ================================================================================
8
+ This module contains a set of functions for transforming vector and tensor
9
+ fields based on an input transformation matrix.
7
10
  """
11
+
8
12
  import numpy as np
9
13
 
10
14
  def transform_vector_2d(trans_mat: np.ndarray, vector: np.ndarray
@@ -1,19 +1,18 @@
1
- """
2
- ================================================================================
3
- pyvale: the python validation engine
4
- License: MIT
5
- Copyright (C) 2025 The Computer Aided Validation Team
6
- ================================================================================
7
- """
1
+ # ==============================================================================
2
+ # pyvale: the python validation engine
3
+ # License: MIT
4
+ # Copyright (C) 2025 The Computer Aided Validation Team
5
+ # ==============================================================================
6
+
8
7
  import numpy as np
9
8
  import pyvista as pv
10
9
  from scipy.spatial.transform import Rotation
11
10
  import mooseherder as mh
12
11
 
13
- from pyvale.core.field import IField
14
- from pyvale.core.fieldconverter import simdata_to_pyvista
15
- from pyvale.core.fieldsampler import sample_pyvista_grid
16
- from pyvale.core.fieldtransform import (transform_vector_2d,
12
+ from pyvale.field import IField
13
+ from pyvale.fieldconverter import simdata_to_pyvista
14
+ from pyvale.fieldsampler import sample_pyvista_grid
15
+ from pyvale.fieldtransform import (transform_vector_2d,
17
16
  transform_vector_2d_batch,
18
17
  transform_vector_3d,
19
18
  transform_vector_3d_batch)
@@ -31,9 +30,8 @@ class FieldVector(IField):
31
30
  sim_data: mh.SimData,
32
31
  field_key: str,
33
32
  components: tuple[str,...],
34
- spat_dims: int) -> None:
35
- """Initialiser for the `FieldVector` class.
36
-
33
+ elem_dims: int) -> None:
34
+ """
37
35
  Parameters
38
36
  ----------
39
37
  sim_data : mh.SimData
@@ -43,13 +41,13 @@ class FieldVector(IField):
43
41
  components : tuple[str,...]
44
42
  String keys to the field components in the `SimData` object. For
45
43
  example ('disp_x','disp_y').
46
- spat_dims : int
44
+ elem_dims : int
47
45
  Number of spatial dimensions (2 or 3) used for identifying element
48
46
  types.
49
47
  """
50
48
  self._field_key = field_key
51
49
  self._components = components
52
- self._spat_dims = spat_dims
50
+ self._spat_dims = elem_dims
53
51
 
54
52
  self._sim_data = sim_data
55
53
  (self._pyvista_grid,self._pyvista_vis) = simdata_to_pyvista(