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.
- pyvale/__init__.py +78 -64
- pyvale/analyticmeshgen.py +102 -0
- pyvale/{core/analyticsimdatafactory.py → analyticsimdatafactory.py} +44 -16
- pyvale/analyticsimdatagenerator.py +323 -0
- pyvale/blendercalibrationdata.py +15 -0
- pyvale/blenderlightdata.py +26 -0
- pyvale/blendermaterialdata.py +15 -0
- pyvale/blenderrenderdata.py +30 -0
- pyvale/blenderscene.py +488 -0
- pyvale/blendertools.py +420 -0
- pyvale/{core/camera.py → camera.py} +15 -15
- pyvale/{core/cameradata.py → cameradata.py} +27 -22
- pyvale/{core/cameradata2d.py → cameradata2d.py} +8 -6
- pyvale/camerastereo.py +217 -0
- pyvale/{core/cameratools.py → cameratools.py} +220 -26
- pyvale/{core/cython → cython}/rastercyth.py +11 -7
- pyvale/data/__init__.py +5 -7
- pyvale/data/cal_target.tiff +0 -0
- pyvale/data/case00_HEX20_out.e +0 -0
- pyvale/data/case00_HEX27_out.e +0 -0
- pyvale/data/case00_HEX8_out.e +0 -0
- pyvale/data/case00_TET10_out.e +0 -0
- pyvale/data/case00_TET14_out.e +0 -0
- pyvale/data/case00_TET4_out.e +0 -0
- pyvale/{core/dataset.py → dataset.py} +91 -16
- pyvale/{core/errorcalculator.py → errorcalculator.py} +13 -16
- pyvale/{core/errordriftcalc.py → errordriftcalc.py} +14 -14
- pyvale/{core/errorintegrator.py → errorintegrator.py} +25 -28
- pyvale/{core/errorrand.py → errorrand.py} +39 -46
- pyvale/errorsyscalib.py +134 -0
- pyvale/{core/errorsysdep.py → errorsysdep.py} +25 -29
- pyvale/{core/errorsysfield.py → errorsysfield.py} +59 -52
- pyvale/{core/errorsysindep.py → errorsysindep.py} +85 -182
- pyvale/examples/__init__.py +5 -7
- pyvale/examples/basics/ex1_1_basicscalars_therm2d.py +131 -0
- pyvale/examples/basics/ex1_2_sensormodel_therm2d.py +158 -0
- pyvale/examples/basics/ex1_3_customsens_therm3d.py +216 -0
- pyvale/examples/basics/ex1_4_basicerrors_therm3d.py +153 -0
- pyvale/examples/basics/ex1_5_fielderrs_therm3d.py +168 -0
- pyvale/examples/basics/ex1_6_caliberrs_therm2d.py +133 -0
- pyvale/examples/basics/ex1_7_spatavg_therm2d.py +123 -0
- pyvale/examples/basics/ex2_1_basicvectors_disp2d.py +112 -0
- pyvale/examples/basics/ex2_2_vectorsens_disp2d.py +111 -0
- pyvale/examples/basics/ex2_3_sensangle_disp2d.py +139 -0
- pyvale/examples/basics/ex2_4_chainfielderrs_disp2d.py +196 -0
- pyvale/examples/basics/ex2_5_vectorfields3d_disp3d.py +109 -0
- pyvale/examples/basics/ex3_1_basictensors_strain2d.py +114 -0
- pyvale/examples/basics/ex3_2_tensorsens2d_strain2d.py +111 -0
- pyvale/examples/basics/ex3_3_tensorsens3d_strain3d.py +182 -0
- pyvale/examples/basics/ex4_1_expsim2d_thermmech2d.py +171 -0
- pyvale/examples/basics/ex4_2_expsim3d_thermmech3d.py +252 -0
- pyvale/examples/{analyticdatagen → genanalyticdata}/ex1_1_scalarvisualisation.py +6 -9
- pyvale/examples/{analyticdatagen → genanalyticdata}/ex1_2_scalarcasebuild.py +8 -11
- pyvale/examples/{analyticdatagen → genanalyticdata}/ex2_1_analyticsensors.py +9 -12
- pyvale/examples/imagedef2d/ex_imagedef2d_todisk.py +8 -15
- pyvale/examples/renderblender/ex1_1_blenderscene.py +121 -0
- pyvale/examples/renderblender/ex1_2_blenderdeformed.py +119 -0
- pyvale/examples/renderblender/ex2_1_stereoscene.py +128 -0
- pyvale/examples/renderblender/ex2_2_stereodeformed.py +131 -0
- pyvale/examples/renderblender/ex3_1_blendercalibration.py +120 -0
- pyvale/examples/{rasterisation → renderrasterisation}/ex_rastenp.py +6 -7
- pyvale/examples/{rasterisation → renderrasterisation}/ex_rastercyth_oneframe.py +5 -7
- pyvale/examples/{rasterisation → renderrasterisation}/ex_rastercyth_static_cypara.py +6 -13
- pyvale/examples/{rasterisation → renderrasterisation}/ex_rastercyth_static_pypara.py +9 -12
- pyvale/examples/{ex1_4_thermal2d.py → visualisation/ex1_1_plot_traces.py} +33 -20
- pyvale/examples/{features/ex_animation_tools_3dmonoblock.py → visualisation/ex2_1_animate_sim.py} +37 -31
- pyvale/experimentsimulator.py +175 -0
- pyvale/{core/field.py → field.py} +6 -14
- pyvale/fieldconverter.py +351 -0
- pyvale/{core/fieldsampler.py → fieldsampler.py} +9 -10
- pyvale/{core/fieldscalar.py → fieldscalar.py} +17 -18
- pyvale/{core/fieldtensor.py → fieldtensor.py} +23 -26
- pyvale/{core/fieldtransform.py → fieldtransform.py} +9 -5
- pyvale/{core/fieldvector.py → fieldvector.py} +14 -16
- pyvale/{core/generatorsrandom.py → generatorsrandom.py} +29 -52
- pyvale/{core/imagedef2d.py → imagedef2d.py} +11 -8
- pyvale/{core/integratorfactory.py → integratorfactory.py} +12 -13
- pyvale/{core/integratorquadrature.py → integratorquadrature.py} +57 -32
- pyvale/integratorrectangle.py +165 -0
- pyvale/{core/integratorspatial.py → integratorspatial.py} +9 -10
- pyvale/{core/integratortype.py → integratortype.py} +7 -8
- pyvale/output.py +17 -0
- pyvale/pyvaleexceptions.py +11 -0
- pyvale/{core/raster.py → raster.py} +8 -8
- pyvale/{core/rastercy.py → rastercy.py} +11 -10
- pyvale/{core/rasternp.py → rasternp.py} +12 -13
- pyvale/{core/rendermesh.py → rendermesh.py} +10 -19
- pyvale/{core/sensorarray.py → sensorarray.py} +7 -8
- pyvale/{core/sensorarrayfactory.py → sensorarrayfactory.py} +64 -78
- pyvale/{core/sensorarraypoint.py → sensorarraypoint.py} +39 -41
- pyvale/{core/sensordata.py → sensordata.py} +7 -8
- pyvale/sensordescriptor.py +213 -0
- pyvale/{core/sensortools.py → sensortools.py} +8 -9
- pyvale/simcases/case00_HEX20.i +5 -5
- pyvale/simcases/case00_HEX27.i +5 -5
- pyvale/simcases/case00_HEX8.i +242 -0
- pyvale/simcases/case00_TET10.i +2 -2
- pyvale/simcases/case00_TET14.i +2 -2
- pyvale/simcases/case00_TET4.i +242 -0
- pyvale/simcases/run_1case.py +1 -1
- pyvale/simtools.py +67 -0
- pyvale/visualexpplotter.py +191 -0
- pyvale/{core/visualimagedef.py → visualimagedef.py} +13 -10
- pyvale/{core/visualimages.py → visualimages.py} +10 -9
- pyvale/visualopts.py +493 -0
- pyvale/{core/visualsimanimator.py → visualsimanimator.py} +47 -19
- pyvale/visualsimsensors.py +318 -0
- pyvale/visualtools.py +136 -0
- pyvale/visualtraceplotter.py +142 -0
- {pyvale-2025.4.0.dist-info → pyvale-2025.5.1.dist-info}/METADATA +17 -14
- pyvale-2025.5.1.dist-info/RECORD +172 -0
- {pyvale-2025.4.0.dist-info → pyvale-2025.5.1.dist-info}/WHEEL +1 -1
- pyvale/core/__init__.py +0 -7
- pyvale/core/analyticmeshgen.py +0 -59
- pyvale/core/analyticsimdatagenerator.py +0 -160
- pyvale/core/cython/rastercyth.c +0 -32267
- pyvale/core/experimentsimulator.py +0 -99
- pyvale/core/fieldconverter.py +0 -154
- pyvale/core/integratorrectangle.py +0 -88
- pyvale/core/optimcheckfuncs.py +0 -153
- pyvale/core/sensordescriptor.py +0 -101
- pyvale/core/visualexpplotter.py +0 -151
- pyvale/core/visualopts.py +0 -180
- pyvale/core/visualsimplotter.py +0 -182
- pyvale/core/visualtools.py +0 -81
- pyvale/core/visualtraceplotter.py +0 -256
- pyvale/examples/analyticdatagen/__init__.py +0 -7
- pyvale/examples/ex1_1_thermal2d.py +0 -89
- pyvale/examples/ex1_2_thermal2d.py +0 -111
- pyvale/examples/ex1_3_thermal2d.py +0 -113
- pyvale/examples/ex1_5_thermal2d.py +0 -105
- pyvale/examples/ex2_1_thermal3d .py +0 -87
- pyvale/examples/ex2_2_thermal3d.py +0 -51
- pyvale/examples/ex2_3_thermal3d.py +0 -109
- pyvale/examples/ex3_1_displacement2d.py +0 -47
- pyvale/examples/ex3_2_displacement2d.py +0 -79
- pyvale/examples/ex3_3_displacement2d.py +0 -104
- pyvale/examples/ex3_4_displacement2d.py +0 -105
- pyvale/examples/ex4_1_strain2d.py +0 -57
- pyvale/examples/ex4_2_strain2d.py +0 -79
- pyvale/examples/ex4_3_strain2d.py +0 -100
- pyvale/examples/ex5_1_multiphysics2d.py +0 -78
- pyvale/examples/ex6_1_multiphysics2d_expsim.py +0 -118
- pyvale/examples/ex6_2_multiphysics3d_expsim.py +0 -158
- pyvale/examples/features/__init__.py +0 -7
- pyvale/examples/features/ex_area_avg.py +0 -89
- pyvale/examples/features/ex_calibration_error.py +0 -108
- pyvale/examples/features/ex_chain_field_errs.py +0 -141
- pyvale/examples/features/ex_field_errs.py +0 -78
- pyvale/examples/features/ex_sensor_single_angle_batch.py +0 -110
- pyvale-2025.4.0.dist-info/RECORD +0 -157
- {pyvale-2025.4.0.dist-info → pyvale-2025.5.1.dist-info}/licenses/LICENSE +0 -0
- {pyvale-2025.4.0.dist-info → pyvale-2025.5.1.dist-info}/top_level.txt +0 -0
pyvale/fieldconverter.py
ADDED
|
@@ -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
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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.
|
|
11
|
-
from pyvale.
|
|
12
|
-
from pyvale.
|
|
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
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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.
|
|
14
|
-
from pyvale.
|
|
15
|
-
from pyvale.
|
|
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","
|
|
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
|
-
|
|
30
|
-
"""
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
67
|
+
self._elem_dims
|
|
69
68
|
)
|
|
70
69
|
|
|
71
70
|
def get_sim_data(self) -> mh.SimData:
|
|
@@ -1,23 +1,24 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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.
|
|
14
|
-
from pyvale.
|
|
15
|
-
from pyvale.
|
|
16
|
-
from pyvale.
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
"""
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
53
|
-
self._norm_components =
|
|
54
|
-
self._dev_components =
|
|
55
|
-
self._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
|
-
|
|
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
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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.
|
|
14
|
-
from pyvale.
|
|
15
|
-
from pyvale.
|
|
16
|
-
from pyvale.
|
|
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
|
-
|
|
35
|
-
"""
|
|
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
|
-
|
|
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 =
|
|
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(
|