pyvale 2025.5.3__cp311-cp311-win_amd64.whl → 2025.7.0__cp311-cp311-win_amd64.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 +12 -0
- pyvale/blendercalibrationdata.py +3 -1
- pyvale/blenderscene.py +7 -5
- pyvale/blendertools.py +27 -5
- pyvale/camera.py +1 -0
- pyvale/cameradata.py +3 -0
- pyvale/camerasensor.py +147 -0
- pyvale/camerastereo.py +4 -4
- pyvale/cameratools.py +23 -61
- pyvale/cython/rastercyth.c +1657 -1352
- pyvale/cython/rastercyth.cp311-win_amd64.pyd +0 -0
- pyvale/cython/rastercyth.py +71 -26
- pyvale/data/plate_hole_def0000.tiff +0 -0
- pyvale/data/plate_hole_def0001.tiff +0 -0
- pyvale/data/plate_hole_ref0000.tiff +0 -0
- pyvale/data/plate_rigid_def0000.tiff +0 -0
- pyvale/data/plate_rigid_def0001.tiff +0 -0
- pyvale/data/plate_rigid_ref0000.tiff +0 -0
- pyvale/dataset.py +96 -6
- pyvale/dic/cpp/dicbruteforce.cpp +370 -0
- pyvale/dic/cpp/dicfourier.cpp +648 -0
- pyvale/dic/cpp/dicinterpolator.cpp +559 -0
- pyvale/dic/cpp/dicmain.cpp +215 -0
- pyvale/dic/cpp/dicoptimizer.cpp +675 -0
- pyvale/dic/cpp/dicrg.cpp +137 -0
- pyvale/dic/cpp/dicscanmethod.cpp +677 -0
- pyvale/dic/cpp/dicsmooth.cpp +138 -0
- pyvale/dic/cpp/dicstrain.cpp +383 -0
- pyvale/dic/cpp/dicutil.cpp +563 -0
- pyvale/dic2d.py +164 -0
- pyvale/dic2dcpp.cp311-win_amd64.pyd +0 -0
- pyvale/dicchecks.py +476 -0
- pyvale/dicdataimport.py +247 -0
- pyvale/dicregionofinterest.py +887 -0
- pyvale/dicresults.py +55 -0
- pyvale/dicspecklegenerator.py +238 -0
- pyvale/dicspecklequality.py +305 -0
- pyvale/dicstrain.py +387 -0
- pyvale/dicstrainresults.py +37 -0
- pyvale/errorintegrator.py +10 -8
- pyvale/examples/basics/ex1_1_basicscalars_therm2d.py +124 -113
- pyvale/examples/basics/ex1_2_sensormodel_therm2d.py +124 -132
- pyvale/examples/basics/ex1_3_customsens_therm3d.py +199 -195
- pyvale/examples/basics/ex1_4_basicerrors_therm3d.py +125 -121
- pyvale/examples/basics/ex1_5_fielderrs_therm3d.py +145 -141
- pyvale/examples/basics/ex1_6_caliberrs_therm2d.py +96 -101
- pyvale/examples/basics/ex1_7_spatavg_therm2d.py +109 -105
- pyvale/examples/basics/ex2_1_basicvectors_disp2d.py +92 -91
- pyvale/examples/basics/ex2_2_vectorsens_disp2d.py +96 -90
- pyvale/examples/basics/ex2_3_sensangle_disp2d.py +88 -89
- pyvale/examples/basics/ex2_4_chainfielderrs_disp2d.py +172 -171
- pyvale/examples/basics/ex2_5_vectorfields3d_disp3d.py +88 -86
- pyvale/examples/basics/ex3_1_basictensors_strain2d.py +90 -90
- pyvale/examples/basics/ex3_2_tensorsens2d_strain2d.py +93 -91
- pyvale/examples/basics/ex3_3_tensorsens3d_strain3d.py +172 -160
- pyvale/examples/basics/ex4_1_expsim2d_thermmech2d.py +154 -148
- pyvale/examples/basics/ex4_2_expsim3d_thermmech3d.py +249 -231
- pyvale/examples/dic/ex1_region_of_interest.py +98 -0
- pyvale/examples/dic/ex2_plate_with_hole.py +149 -0
- pyvale/examples/dic/ex3_plate_with_hole_strain.py +93 -0
- pyvale/examples/dic/ex4_dic_blender.py +95 -0
- pyvale/examples/dic/ex5_dic_challenge.py +102 -0
- pyvale/examples/imagedef2d/ex_imagedef2d_todisk.py +4 -2
- pyvale/examples/renderblender/ex1_1_blenderscene.py +152 -105
- pyvale/examples/renderblender/ex1_2_blenderdeformed.py +151 -100
- pyvale/examples/renderblender/ex2_1_stereoscene.py +183 -116
- pyvale/examples/renderblender/ex2_2_stereodeformed.py +185 -112
- pyvale/examples/renderblender/ex3_1_blendercalibration.py +164 -109
- pyvale/examples/renderrasterisation/ex_rastenp.py +74 -35
- pyvale/examples/renderrasterisation/ex_rastercyth_oneframe.py +6 -13
- pyvale/examples/renderrasterisation/ex_rastercyth_static_cypara.py +2 -2
- pyvale/examples/renderrasterisation/ex_rastercyth_static_pypara.py +2 -4
- pyvale/imagedef2d.py +3 -2
- pyvale/imagetools.py +137 -0
- pyvale/rastercy.py +34 -4
- pyvale/rasternp.py +300 -276
- pyvale/rasteropts.py +58 -0
- pyvale/renderer.py +47 -0
- pyvale/rendermesh.py +52 -62
- pyvale/renderscene.py +51 -0
- pyvale/sensorarrayfactory.py +2 -2
- pyvale/sensortools.py +19 -35
- pyvale/simcases/case21.i +1 -1
- pyvale/simcases/run_1case.py +8 -0
- pyvale/simtools.py +2 -2
- pyvale/visualsimplotter.py +180 -0
- {pyvale-2025.5.3.dist-info → pyvale-2025.7.0.dist-info}/METADATA +11 -57
- {pyvale-2025.5.3.dist-info → pyvale-2025.7.0.dist-info}/RECORD +91 -56
- {pyvale-2025.5.3.dist-info → pyvale-2025.7.0.dist-info}/WHEEL +1 -1
- pyvale/examples/visualisation/ex1_1_plot_traces.py +0 -102
- pyvale/examples/visualisation/ex2_1_animate_sim.py +0 -89
- {pyvale-2025.5.3.dist-info → pyvale-2025.7.0.dist-info}/licenses/LICENSE +0 -0
- {pyvale-2025.5.3.dist-info → pyvale-2025.7.0.dist-info}/top_level.txt +0 -0
pyvale/rasteropts.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
#===============================================================================
|
|
2
|
+
# pyvale: the python validation engine
|
|
3
|
+
# License: MIT
|
|
4
|
+
# Copyright (C) 2025 The Computer Aided Validation Team
|
|
5
|
+
#===============================================================================
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
import numpy as np
|
|
9
|
+
from pyvale.imagetools import EImageType, ImageTools
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass(slots=True)
|
|
13
|
+
class RasterOpts:
|
|
14
|
+
|
|
15
|
+
image_min_frac: float = 0.0
|
|
16
|
+
image_max_frac: float = 1.0
|
|
17
|
+
image_background_frac: float = 0.5
|
|
18
|
+
image_bits: int = 16
|
|
19
|
+
image_type: EImageType = EImageType.TIFF
|
|
20
|
+
|
|
21
|
+
subsample: int = 2
|
|
22
|
+
parallel: int = 4
|
|
23
|
+
|
|
24
|
+
force_static: bool = False
|
|
25
|
+
save_depth_array: bool = False
|
|
26
|
+
save_depth_image: EImageType | None = None
|
|
27
|
+
save_image_array: bool = False
|
|
28
|
+
|
|
29
|
+
def __post_init__(self) -> None:
|
|
30
|
+
assert self.image_bits > 0, "Image bits must be greater than 0."
|
|
31
|
+
assert self.subsample > 0, "Subsampling must be larger than 0."
|
|
32
|
+
assert self.parallel > 0, "Number of parallel renders must be larger than 0"
|
|
33
|
+
|
|
34
|
+
def save_raster(save_file: Path,
|
|
35
|
+
image_buff: np.ndarray,
|
|
36
|
+
depth_buff: np.ndarray,
|
|
37
|
+
opts: RasterOpts) -> None:
|
|
38
|
+
|
|
39
|
+
ImageTools.scale_digitise_save(save_file,
|
|
40
|
+
image_buff,
|
|
41
|
+
opts.image_type,
|
|
42
|
+
opts.image_bits,
|
|
43
|
+
opts.image_min_frac,
|
|
44
|
+
opts.image_max_frac,
|
|
45
|
+
opts.image_background_frac)
|
|
46
|
+
|
|
47
|
+
if opts.save_image_array:
|
|
48
|
+
np.save(save_file.with_suffix(".npy"),image_buff)
|
|
49
|
+
|
|
50
|
+
if opts.save_depth_image is not None:
|
|
51
|
+
depth_path = save_file.with_stem(f"{save_file.stem}_depth")
|
|
52
|
+
ImageTools.scale_digitise_save(depth_path,
|
|
53
|
+
depth_buff,
|
|
54
|
+
opts.save_depth_image)
|
|
55
|
+
|
|
56
|
+
if opts.save_depth_array:
|
|
57
|
+
depth_path = save_file.with_stem(f"{save_file.stem}_depth")
|
|
58
|
+
np.save(depth_path.with_suffix(".npy"),depth_buff)
|
pyvale/renderer.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#===============================================================================
|
|
2
|
+
# pyvale: the python validation engine
|
|
3
|
+
# License: MIT
|
|
4
|
+
# Copyright (C) 2025 The Computer Aided Validation Team
|
|
5
|
+
#===============================================================================
|
|
6
|
+
from abc import ABC, abstractmethod
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
import numpy as np
|
|
9
|
+
from pyvale.renderscene import RenderScene
|
|
10
|
+
|
|
11
|
+
# NOTE: This module is a feature under developement.
|
|
12
|
+
|
|
13
|
+
class IRenderer(ABC):
|
|
14
|
+
@abstractmethod
|
|
15
|
+
def render(self,
|
|
16
|
+
scene: RenderScene,
|
|
17
|
+
cam_ind: int = 0,
|
|
18
|
+
frame_ind: int = 0,
|
|
19
|
+
field_ind: int = 0) -> np.ndarray:
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
@abstractmethod
|
|
23
|
+
def render_to_disk(self,
|
|
24
|
+
scene: RenderScene,
|
|
25
|
+
cam_ind: int = 0,
|
|
26
|
+
frame_ind: int = 0,
|
|
27
|
+
field_ind: int = 0,
|
|
28
|
+
save_path: Path | None = None,
|
|
29
|
+
) -> None:
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
@abstractmethod
|
|
33
|
+
def render_all(self, scene: RenderScene) -> list[np.ndarray]:
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
@abstractmethod
|
|
37
|
+
def render_all_to_disk(self,
|
|
38
|
+
scene: RenderScene,
|
|
39
|
+
save_path: Path | None = None) -> None:
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
|
pyvale/rendermesh.py
CHANGED
|
@@ -8,46 +8,72 @@
|
|
|
8
8
|
NOTE: this module is a feature under developement.
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
|
-
from enum import Enum
|
|
12
|
-
from dataclasses import dataclass, field
|
|
13
11
|
import numpy as np
|
|
12
|
+
from scipy.spatial.transform import Rotation
|
|
14
13
|
import mooseherder as mh
|
|
15
14
|
from pyvale.fieldconverter import simdata_to_pyvista
|
|
16
15
|
|
|
17
16
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
coords: np.ndarray
|
|
21
|
-
connectivity: np.ndarray
|
|
22
|
-
fields_render: np.ndarray
|
|
17
|
+
# TODO:
|
|
18
|
+
# - Store the render field keys and match them between meshes?
|
|
23
19
|
|
|
24
|
-
# If this is None then the mesh is not deformable
|
|
25
|
-
fields_disp: np.ndarray | None = None
|
|
26
20
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
21
|
+
class RenderMesh:
|
|
22
|
+
__slots__ = ("coords","connectivity","fields_render","fields_disp",
|
|
23
|
+
"pos_world","rot_world","node_count","elem_count",
|
|
24
|
+
"nodes_per_elem","mesh_to_world_mat","world_to_mesh_mat")
|
|
30
25
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
26
|
+
def __init__(self,
|
|
27
|
+
coords: np.ndarray,
|
|
28
|
+
connectivity: np.ndarray,
|
|
29
|
+
fields_render: np.ndarray,
|
|
30
|
+
fields_disp: np.ndarray | None = None,
|
|
31
|
+
pos_world: np.ndarray | None = None,
|
|
32
|
+
rot_world: Rotation | None = None) -> None:
|
|
33
|
+
|
|
34
|
+
self.coords = coords
|
|
35
|
+
self.connectivity = connectivity
|
|
36
|
+
self.fields_render = fields_render
|
|
37
|
+
self.fields_disp = fields_disp
|
|
34
38
|
|
|
35
|
-
def __post_init__(self) -> None:
|
|
36
|
-
# C format: num_nodes/num_elems first as it is the largest dimension
|
|
37
39
|
self.node_count = self.coords.shape[0]
|
|
38
40
|
self.elem_count = self.connectivity.shape[0]
|
|
39
41
|
self.nodes_per_elem = self.connectivity.shape[1]
|
|
40
42
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
43
|
+
if pos_world is None:
|
|
44
|
+
self.pos_world = np.array((0.0,0.0,0.0),dtype=np.float64)
|
|
45
|
+
|
|
46
|
+
if rot_world is None:
|
|
47
|
+
self.rot_world = Rotation.from_euler("zyx",(0.0,0.0,0.0),degrees=True)
|
|
48
|
+
|
|
49
|
+
self.mesh_to_world_mat = np.zeros((4,4),dtype=np.float64)
|
|
50
|
+
self.world_to_mesh_mat = np.zeros((4,4),dtype=np.float64)
|
|
51
|
+
self._build_transform_mats()
|
|
52
|
+
|
|
53
|
+
def _build_transform_mats(self) -> None:
|
|
54
|
+
self.mesh_to_world_mat = np.zeros((4,4))
|
|
55
|
+
self.mesh_to_world_mat[0:3,0:3] = self.rot_world.as_matrix()
|
|
56
|
+
self.mesh_to_world_mat[-1,-1] = 1.0
|
|
57
|
+
self.mesh_to_world_mat[0:3,-1] = self.pos_world
|
|
58
|
+
self.world_to_mesh_mat = np.linalg.inv(self.mesh_to_world_mat)
|
|
59
|
+
|
|
60
|
+
def set_pos(self, pos_world: np.ndarray) -> None:
|
|
61
|
+
self.pos_world = pos_world
|
|
62
|
+
self._build_transform_mats()
|
|
63
|
+
|
|
64
|
+
def set_rot(self, rot_world: Rotation) -> None:
|
|
65
|
+
self.rot_world = rot_world
|
|
66
|
+
self._build_transform_mats()
|
|
67
|
+
|
|
44
68
|
|
|
45
69
|
|
|
46
70
|
def create_render_mesh(sim_data: mh.SimData,
|
|
47
71
|
field_render_keys: tuple[str,...],
|
|
48
72
|
sim_spat_dim: int,
|
|
49
73
|
field_disp_keys: tuple[str,...] | None = None,
|
|
50
|
-
|
|
74
|
+
pos_world: np.ndarray | None = None,
|
|
75
|
+
rot_world: Rotation | None = None
|
|
76
|
+
) -> RenderMesh:
|
|
51
77
|
|
|
52
78
|
extract_keys = field_render_keys
|
|
53
79
|
if field_disp_keys is not None:
|
|
@@ -102,46 +128,10 @@ def create_render_mesh(sim_data: mh.SimData,
|
|
|
102
128
|
field_disp_by_node[:,:,ii] = np.ascontiguousarray(
|
|
103
129
|
np.array(pv_surf[cc]))
|
|
104
130
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
return RenderMeshData(coords=coords_world,
|
|
131
|
+
return RenderMesh(coords=coords_world,
|
|
108
132
|
connectivity=connectivity,
|
|
109
133
|
fields_render=fields_render_by_node,
|
|
110
|
-
fields_disp=field_disp_by_node
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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)
|
|
134
|
+
fields_disp=field_disp_by_node,
|
|
135
|
+
pos_world=pos_world,
|
|
136
|
+
rot_world=rot_world)
|
|
137
|
+
|
pyvale/renderscene.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
|
|
2
|
+
#===============================================================================
|
|
3
|
+
# pyvale: the python validation engine
|
|
4
|
+
# License: MIT
|
|
5
|
+
# Copyright (C) 2025 The Computer Aided Validation Team
|
|
6
|
+
#===============================================================================
|
|
7
|
+
import numpy as np
|
|
8
|
+
from pyvale.cameradata import CameraData
|
|
9
|
+
from pyvale.rendermesh import RenderMesh
|
|
10
|
+
|
|
11
|
+
#===============================================================================
|
|
12
|
+
# TODO
|
|
13
|
+
# - How do we match render fields between meshes?
|
|
14
|
+
# - How do we check displacement fields are the same between meshes?
|
|
15
|
+
# - Eventually this will need to take render times and do the field interpolations
|
|
16
|
+
# - Check all render meshes to see if any are deformable
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class RenderScene:
|
|
20
|
+
__slots__ = ("cameras","meshes")
|
|
21
|
+
|
|
22
|
+
def __init__(self,
|
|
23
|
+
cameras: list[CameraData] | None = None,
|
|
24
|
+
meshes: list[RenderMesh] | None = None,
|
|
25
|
+
) -> None:
|
|
26
|
+
if cameras is None:
|
|
27
|
+
self.cameras = []
|
|
28
|
+
else:
|
|
29
|
+
self.cameras = cameras
|
|
30
|
+
|
|
31
|
+
if meshes is None:
|
|
32
|
+
self.meshes = []
|
|
33
|
+
else:
|
|
34
|
+
self.meshes = meshes
|
|
35
|
+
|
|
36
|
+
def is_deformable(self) -> bool:
|
|
37
|
+
for mm in self.meshes:
|
|
38
|
+
if mm.fields_disp is not None:
|
|
39
|
+
return True
|
|
40
|
+
|
|
41
|
+
return False
|
|
42
|
+
|
|
43
|
+
def get_all_coords_world(meshes: list[RenderMesh]) -> np.ndarray:
|
|
44
|
+
coords_all = []
|
|
45
|
+
for mm in meshes:
|
|
46
|
+
coords_all.append(np.matmul(mm.coords,mm.mesh_to_world_mat.T))
|
|
47
|
+
|
|
48
|
+
return np.vstack(coords_all)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
|
pyvale/sensorarrayfactory.py
CHANGED
|
@@ -64,8 +64,8 @@ class SensorArrayFactory:
|
|
|
64
64
|
rand_err_pc=errs_pc)
|
|
65
65
|
|
|
66
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))
|
|
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
69
|
|
|
70
70
|
sens_array.set_error_integrator(err_int)
|
|
71
71
|
return sens_array
|
pyvale/sensortools.py
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
# License: MIT
|
|
4
4
|
# Copyright (C) 2025 The Computer Aided Validation Team
|
|
5
5
|
# ==============================================================================
|
|
6
|
-
|
|
7
6
|
import numpy as np
|
|
8
7
|
import mooseherder as mh
|
|
9
8
|
from pyvale.sensorarray import ISensorArray
|
|
@@ -60,9 +59,9 @@ def create_sensor_pos_array(num_sensors: tuple[int,int,int],
|
|
|
60
59
|
|
|
61
60
|
|
|
62
61
|
def print_measurements(sens_array: ISensorArray,
|
|
63
|
-
sensors:
|
|
64
|
-
components:
|
|
65
|
-
time_steps:
|
|
62
|
+
sensors: int | slice,
|
|
63
|
+
components: int | slice,
|
|
64
|
+
time_steps: int | slice) -> None:
|
|
66
65
|
"""Diagnostic function to print sensor measurements to the console. Also
|
|
67
66
|
prints the ground truth, the random and the systematic errors for the
|
|
68
67
|
specified sensor array. The sensors, components and time steps are specified
|
|
@@ -71,17 +70,18 @@ def print_measurements(sens_array: ISensorArray,
|
|
|
71
70
|
Parameters
|
|
72
71
|
----------
|
|
73
72
|
sens_array : ISensorArray
|
|
74
|
-
Sensor array to print
|
|
75
|
-
sensors :
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
components :
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
time_steps :
|
|
82
|
-
|
|
83
|
-
|
|
73
|
+
Sensor array to print measurements for.
|
|
74
|
+
sensors : int | slice
|
|
75
|
+
Index for the sensor or slice of range of sensors to be printed to the
|
|
76
|
+
console.
|
|
77
|
+
components : int | slice
|
|
78
|
+
Index for the field component or slice of range of field components to
|
|
79
|
+
be printed to the console
|
|
80
|
+
time_steps : int | slice
|
|
81
|
+
Index for the time step or slice of time steps to be printed to the
|
|
82
|
+
console.
|
|
84
83
|
"""
|
|
84
|
+
|
|
85
85
|
measurement = sens_array.get_measurements()
|
|
86
86
|
truth = sens_array.get_truth()
|
|
87
87
|
rand_errs = sens_array.get_errors_random()
|
|
@@ -89,33 +89,17 @@ def print_measurements(sens_array: ISensorArray,
|
|
|
89
89
|
tot_errs = sens_array.get_errors_total()
|
|
90
90
|
|
|
91
91
|
print(f"\nmeasurement.shape = \n {measurement.shape}")
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
time_steps[0]:time_steps[1]]
|
|
95
|
-
print(f"measurement = \n {print_meas}")
|
|
96
|
-
|
|
97
|
-
print_truth = truth[sensors[0]:sensors[1],
|
|
98
|
-
components[0]:components[1],
|
|
99
|
-
time_steps[0]:time_steps[1]]
|
|
100
|
-
print(f"truth = \n {print_truth}")
|
|
92
|
+
print(f"measurement = \n {measurement[sensors,components,time_steps]}")
|
|
93
|
+
print(f"truth = \n {truth[sensors,components,time_steps]}")
|
|
101
94
|
|
|
102
95
|
if rand_errs is not None:
|
|
103
|
-
|
|
104
|
-
components[0]:components[1],
|
|
105
|
-
time_steps[0]:time_steps[1]]
|
|
106
|
-
print(f"random errors = \n {print_randerrs}")
|
|
96
|
+
print(f"random errors = \n {rand_errs[sensors,components,time_steps]}")
|
|
107
97
|
|
|
108
98
|
if sys_errs is not None:
|
|
109
|
-
|
|
110
|
-
components[0]:components[1],
|
|
111
|
-
time_steps[0]:time_steps[1]]
|
|
112
|
-
print(f"systematic errors = \n {print_syserrs}")
|
|
99
|
+
print(f"systematic errors = \n {sys_errs[sensors,components,time_steps]}")
|
|
113
100
|
|
|
114
101
|
if tot_errs is not None:
|
|
115
|
-
|
|
116
|
-
components[0]:components[1],
|
|
117
|
-
time_steps[0]:time_steps[1]]
|
|
118
|
-
print(f"total errors = \n {print_toterrs}")
|
|
102
|
+
print(f"total errors = \n {tot_errs[sensors,components,time_steps]}")
|
|
119
103
|
|
|
120
104
|
print()
|
|
121
105
|
|
pyvale/simcases/case21.i
CHANGED
pyvale/simcases/run_1case.py
CHANGED
|
@@ -15,7 +15,15 @@ from mooseherder import (MooseConfig,
|
|
|
15
15
|
|
|
16
16
|
#===============================================================================
|
|
17
17
|
# Change this to run a different case
|
|
18
|
+
<<<<<<< HEAD
|
|
19
|
+
<<<<<<< HEAD
|
|
20
|
+
CASE_STR = 'case21'
|
|
21
|
+
=======
|
|
22
|
+
CASE_STR = 'case16'
|
|
23
|
+
>>>>>>> deploy
|
|
24
|
+
=======
|
|
18
25
|
CASE_STR = 'case00_HEX27'
|
|
26
|
+
>>>>>>> main
|
|
19
27
|
#===============================================================================
|
|
20
28
|
|
|
21
29
|
CASE_FILES = (CASE_STR+'.geo',CASE_STR+'.i')
|
pyvale/simtools.py
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# Copyright (C) 2025 The Computer Aided Validation Team
|
|
5
5
|
# ==============================================================================
|
|
6
6
|
import numpy as np
|
|
7
|
-
from pyvale.rendermesh import
|
|
7
|
+
from pyvale.rendermesh import RenderMesh
|
|
8
8
|
|
|
9
9
|
class SimTools:
|
|
10
10
|
"""Namespace for tools required for analysing simulation results.
|
|
@@ -37,7 +37,7 @@ class SimTools:
|
|
|
37
37
|
|
|
38
38
|
@staticmethod
|
|
39
39
|
def get_deformed_nodes(timestep: int,
|
|
40
|
-
render_mesh:
|
|
40
|
+
render_mesh: RenderMesh) -> np.ndarray | None:
|
|
41
41
|
"""A method to obtain the deformed locations of all the nodes at a given
|
|
42
42
|
timestep.
|
|
43
43
|
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
#===============================================================================
|
|
2
|
+
# pyvale: the python validation engine
|
|
3
|
+
# License: MIT
|
|
4
|
+
# Copyright (C) 2025 The Computer Aided Validation Team
|
|
5
|
+
#===============================================================================
|
|
6
|
+
# import vtk #NOTE: has to be here to fix latex bug in pyvista/vtk
|
|
7
|
+
# See: https://github.com/pyvista/pyvista/discussions/2928
|
|
8
|
+
#NOTE: causes output to console to be suppressed unfortunately
|
|
9
|
+
import pyvista as pv
|
|
10
|
+
|
|
11
|
+
import mooseherder as mh
|
|
12
|
+
|
|
13
|
+
from pyvale.sensorarraypoint import SensorArrayPoint
|
|
14
|
+
from pyvale.fieldconverter import simdata_to_pyvista
|
|
15
|
+
from pyvale.visualopts import (VisOptsSimSensors,VisOptsImageSave)
|
|
16
|
+
from pyvale.visualtools import (create_pv_plotter,
|
|
17
|
+
get_colour_lims,
|
|
18
|
+
save_image)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
#TODO: Docstrings
|
|
22
|
+
|
|
23
|
+
def add_sim_field(pv_plot: pv.Plotter,
|
|
24
|
+
sensor_array: SensorArrayPoint,
|
|
25
|
+
component: str,
|
|
26
|
+
time_step: int,
|
|
27
|
+
vis_opts: VisOptsSimSensors,
|
|
28
|
+
) -> tuple[pv.Plotter,pv.UnstructuredGrid]:
|
|
29
|
+
|
|
30
|
+
sim_vis = sensor_array.field.get_visualiser()
|
|
31
|
+
sim_data = sensor_array.field.get_sim_data()
|
|
32
|
+
sim_vis[component] = sim_data.node_vars[component][:,time_step]
|
|
33
|
+
comp_ind = sensor_array.field.get_component_index(component)
|
|
34
|
+
|
|
35
|
+
scalar_bar_args = {"title":sensor_array.descriptor.create_label(comp_ind),
|
|
36
|
+
"vertical":vis_opts.colour_bar_vertical,
|
|
37
|
+
"title_font_size":vis_opts.colour_bar_font_size,
|
|
38
|
+
"label_font_size":vis_opts.colour_bar_font_size}
|
|
39
|
+
|
|
40
|
+
pv_plot.add_mesh(sim_vis,
|
|
41
|
+
scalars=component,
|
|
42
|
+
label="sim-data",
|
|
43
|
+
show_edges=vis_opts.show_edges,
|
|
44
|
+
show_scalar_bar=vis_opts.colour_bar_show,
|
|
45
|
+
scalar_bar_args=scalar_bar_args,
|
|
46
|
+
lighting=False,
|
|
47
|
+
clim=vis_opts.colour_bar_lims)
|
|
48
|
+
|
|
49
|
+
if vis_opts.time_label_show:
|
|
50
|
+
pv_plot.add_text(f"Time: {sim_data.time[time_step]} " + \
|
|
51
|
+
f"{sensor_array.descriptor.time_units}",
|
|
52
|
+
position=vis_opts.time_label_position,
|
|
53
|
+
font_size=vis_opts.time_label_font_size,
|
|
54
|
+
name='time-label')
|
|
55
|
+
|
|
56
|
+
return (pv_plot,sim_vis)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def add_sensor_points_nom(pv_plot: pv.Plotter,
|
|
60
|
+
sensor_array: SensorArrayPoint,
|
|
61
|
+
vis_opts: VisOptsSimSensors,
|
|
62
|
+
) -> pv.Plotter:
|
|
63
|
+
|
|
64
|
+
vis_sens_nominal = pv.PolyData(sensor_array.sensor_data.positions)
|
|
65
|
+
vis_sens_nominal["labels"] = sensor_array.descriptor.create_sensor_tags(
|
|
66
|
+
sensor_array.get_measurement_shape()[0])
|
|
67
|
+
|
|
68
|
+
# Add points to show sensor locations
|
|
69
|
+
pv_plot.add_point_labels(vis_sens_nominal,"labels",
|
|
70
|
+
font_size=vis_opts.sens_label_font_size,
|
|
71
|
+
shape_color=vis_opts.sens_label_colour,
|
|
72
|
+
point_color=vis_opts.sens_colour_nom,
|
|
73
|
+
render_points_as_spheres=True,
|
|
74
|
+
point_size=vis_opts.sens_point_size,
|
|
75
|
+
always_visible=True)
|
|
76
|
+
|
|
77
|
+
return pv_plot
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def add_sensor_points_pert(pv_plot: pv.Plotter,
|
|
81
|
+
sensor_array: SensorArrayPoint,
|
|
82
|
+
vis_opts: VisOptsSimSensors,
|
|
83
|
+
) -> pv.Plotter:
|
|
84
|
+
|
|
85
|
+
sens_data_perturbed = sensor_array.get_sensor_data_perturbed()
|
|
86
|
+
|
|
87
|
+
if sens_data_perturbed is not None and vis_opts.show_perturbed_pos:
|
|
88
|
+
vis_sens_perturbed = pv.PolyData(sens_data_perturbed.positions)
|
|
89
|
+
vis_sens_perturbed["labels"] = ["",]*sensor_array.get_measurement_shape()[0]
|
|
90
|
+
|
|
91
|
+
pv_plot.add_point_labels(vis_sens_perturbed,"labels",
|
|
92
|
+
font_size=vis_opts.sens_label_font_size,
|
|
93
|
+
shape_color=vis_opts.sens_label_colour,
|
|
94
|
+
point_color=vis_opts.sens_colour_pert,
|
|
95
|
+
render_points_as_spheres=True,
|
|
96
|
+
point_size=vis_opts.sens_point_size,
|
|
97
|
+
always_visible=True)
|
|
98
|
+
|
|
99
|
+
return pv_plot
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def plot_sim_mesh(sim_data: mh.SimData,
|
|
103
|
+
vis_opts: VisOptsSimSensors | None = None,
|
|
104
|
+
) -> pv.Plotter:
|
|
105
|
+
|
|
106
|
+
if vis_opts is None:
|
|
107
|
+
vis_opts = VisOptsSimSensors()
|
|
108
|
+
|
|
109
|
+
pv_simdata = simdata_to_pyvista(sim_data,
|
|
110
|
+
None,
|
|
111
|
+
sim_data.num_spat_dims)
|
|
112
|
+
|
|
113
|
+
pv_plot = create_pv_plotter(vis_opts)
|
|
114
|
+
|
|
115
|
+
pv_plot.add_mesh(pv_simdata,
|
|
116
|
+
label='sim-data',
|
|
117
|
+
show_edges=True,
|
|
118
|
+
show_scalar_bar=False)
|
|
119
|
+
|
|
120
|
+
return pv_plot
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def plot_sim_data(sim_data: mh.SimData,
|
|
124
|
+
component: str,
|
|
125
|
+
time_step: int = -1,
|
|
126
|
+
vis_opts: VisOptsSimSensors | None = None
|
|
127
|
+
) -> pv.Plotter:
|
|
128
|
+
|
|
129
|
+
if vis_opts is None:
|
|
130
|
+
vis_opts = VisOptsSimSensors()
|
|
131
|
+
|
|
132
|
+
pv_simdata = simdata_to_pyvista(sim_data,
|
|
133
|
+
(component,),
|
|
134
|
+
sim_data.num_spat_dims)
|
|
135
|
+
|
|
136
|
+
pv_plot = create_pv_plotter(vis_opts)
|
|
137
|
+
|
|
138
|
+
pv_plot.add_mesh(pv_simdata,
|
|
139
|
+
scalars=pv_simdata[component][:,time_step],
|
|
140
|
+
label="sim-data",
|
|
141
|
+
show_edges=True,
|
|
142
|
+
show_scalar_bar=True,
|
|
143
|
+
scalar_bar_args={"title":component},)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
return pv_plot
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def plot_point_sensors_on_sim(sensor_array: SensorArrayPoint,
|
|
150
|
+
component: str,
|
|
151
|
+
time_step: int = -1,
|
|
152
|
+
vis_opts: VisOptsSimSensors | None = None,
|
|
153
|
+
image_save_opts: VisOptsImageSave | None = None,
|
|
154
|
+
) -> pv.Plotter:
|
|
155
|
+
|
|
156
|
+
if vis_opts is None:
|
|
157
|
+
vis_opts = VisOptsSimSensors()
|
|
158
|
+
|
|
159
|
+
sim_data = sensor_array.field.get_sim_data()
|
|
160
|
+
vis_opts.colour_bar_lims = get_colour_lims(
|
|
161
|
+
sim_data.node_vars[component][:,time_step],
|
|
162
|
+
vis_opts.colour_bar_lims)
|
|
163
|
+
|
|
164
|
+
pv_plot = create_pv_plotter(vis_opts)
|
|
165
|
+
|
|
166
|
+
pv_plot = add_sensor_points_pert(pv_plot,sensor_array,vis_opts)
|
|
167
|
+
pv_plot = add_sensor_points_nom(pv_plot,sensor_array,vis_opts)
|
|
168
|
+
(pv_plot,_) = add_sim_field(pv_plot,
|
|
169
|
+
sensor_array,
|
|
170
|
+
component,
|
|
171
|
+
time_step,
|
|
172
|
+
vis_opts)
|
|
173
|
+
|
|
174
|
+
pv_plot.camera_position = vis_opts.camera_position
|
|
175
|
+
|
|
176
|
+
if image_save_opts is not None:
|
|
177
|
+
save_image(pv_plot,image_save_opts)
|
|
178
|
+
|
|
179
|
+
return pv_plot
|
|
180
|
+
|