pyvale 2025.5.3__cp311-cp311-musllinux_1_2_aarch64.whl → 2025.7.1__cp311-cp311-musllinux_1_2_aarch64.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 (98) hide show
  1. pyvale/__init__.py +12 -0
  2. pyvale/blendercalibrationdata.py +3 -1
  3. pyvale/blenderscene.py +7 -5
  4. pyvale/blendertools.py +27 -5
  5. pyvale/camera.py +1 -0
  6. pyvale/cameradata.py +3 -0
  7. pyvale/camerasensor.py +147 -0
  8. pyvale/camerastereo.py +4 -4
  9. pyvale/cameratools.py +23 -61
  10. pyvale/cython/rastercyth.c +1657 -1352
  11. pyvale/cython/rastercyth.cpython-311-aarch64-linux-musl.so +0 -0
  12. pyvale/cython/rastercyth.py +71 -26
  13. pyvale/data/DIC_Challenge_Star_Noise_Def.tiff +0 -0
  14. pyvale/data/DIC_Challenge_Star_Noise_Ref.tiff +0 -0
  15. pyvale/data/plate_hole_def0000.tiff +0 -0
  16. pyvale/data/plate_hole_def0001.tiff +0 -0
  17. pyvale/data/plate_hole_ref0000.tiff +0 -0
  18. pyvale/data/plate_rigid_def0000.tiff +0 -0
  19. pyvale/data/plate_rigid_def0001.tiff +0 -0
  20. pyvale/data/plate_rigid_ref0000.tiff +0 -0
  21. pyvale/dataset.py +96 -6
  22. pyvale/dic/cpp/dicbruteforce.cpp +370 -0
  23. pyvale/dic/cpp/dicfourier.cpp +648 -0
  24. pyvale/dic/cpp/dicinterpolator.cpp +559 -0
  25. pyvale/dic/cpp/dicmain.cpp +215 -0
  26. pyvale/dic/cpp/dicoptimizer.cpp +675 -0
  27. pyvale/dic/cpp/dicrg.cpp +137 -0
  28. pyvale/dic/cpp/dicscanmethod.cpp +677 -0
  29. pyvale/dic/cpp/dicsmooth.cpp +138 -0
  30. pyvale/dic/cpp/dicstrain.cpp +383 -0
  31. pyvale/dic/cpp/dicutil.cpp +563 -0
  32. pyvale/dic2d.py +164 -0
  33. pyvale/dic2dcpp.cpython-311-aarch64-linux-musl.so +0 -0
  34. pyvale/dicchecks.py +476 -0
  35. pyvale/dicdataimport.py +247 -0
  36. pyvale/dicregionofinterest.py +887 -0
  37. pyvale/dicresults.py +55 -0
  38. pyvale/dicspecklegenerator.py +238 -0
  39. pyvale/dicspecklequality.py +305 -0
  40. pyvale/dicstrain.py +387 -0
  41. pyvale/dicstrainresults.py +37 -0
  42. pyvale/errorintegrator.py +10 -8
  43. pyvale/examples/basics/ex1_1_basicscalars_therm2d.py +124 -113
  44. pyvale/examples/basics/ex1_2_sensormodel_therm2d.py +124 -132
  45. pyvale/examples/basics/ex1_3_customsens_therm3d.py +199 -195
  46. pyvale/examples/basics/ex1_4_basicerrors_therm3d.py +125 -121
  47. pyvale/examples/basics/ex1_5_fielderrs_therm3d.py +145 -141
  48. pyvale/examples/basics/ex1_6_caliberrs_therm2d.py +96 -101
  49. pyvale/examples/basics/ex1_7_spatavg_therm2d.py +109 -105
  50. pyvale/examples/basics/ex2_1_basicvectors_disp2d.py +92 -91
  51. pyvale/examples/basics/ex2_2_vectorsens_disp2d.py +96 -90
  52. pyvale/examples/basics/ex2_3_sensangle_disp2d.py +88 -89
  53. pyvale/examples/basics/ex2_4_chainfielderrs_disp2d.py +172 -171
  54. pyvale/examples/basics/ex2_5_vectorfields3d_disp3d.py +88 -86
  55. pyvale/examples/basics/ex3_1_basictensors_strain2d.py +90 -90
  56. pyvale/examples/basics/ex3_2_tensorsens2d_strain2d.py +93 -91
  57. pyvale/examples/basics/ex3_3_tensorsens3d_strain3d.py +172 -160
  58. pyvale/examples/basics/ex4_1_expsim2d_thermmech2d.py +154 -148
  59. pyvale/examples/basics/ex4_2_expsim3d_thermmech3d.py +249 -231
  60. pyvale/examples/dic/ex1_region_of_interest.py +98 -0
  61. pyvale/examples/dic/ex2_plate_with_hole.py +149 -0
  62. pyvale/examples/dic/ex3_plate_with_hole_strain.py +93 -0
  63. pyvale/examples/dic/ex4_dic_blender.py +95 -0
  64. pyvale/examples/dic/ex5_dic_challenge.py +102 -0
  65. pyvale/examples/imagedef2d/ex_imagedef2d_todisk.py +4 -2
  66. pyvale/examples/renderblender/ex1_1_blenderscene.py +152 -105
  67. pyvale/examples/renderblender/ex1_2_blenderdeformed.py +151 -100
  68. pyvale/examples/renderblender/ex2_1_stereoscene.py +183 -116
  69. pyvale/examples/renderblender/ex2_2_stereodeformed.py +185 -112
  70. pyvale/examples/renderblender/ex3_1_blendercalibration.py +164 -109
  71. pyvale/examples/renderrasterisation/ex_rastenp.py +74 -35
  72. pyvale/examples/renderrasterisation/ex_rastercyth_oneframe.py +6 -13
  73. pyvale/examples/renderrasterisation/ex_rastercyth_static_cypara.py +2 -2
  74. pyvale/examples/renderrasterisation/ex_rastercyth_static_pypara.py +2 -4
  75. pyvale/imagedef2d.py +3 -2
  76. pyvale/imagetools.py +137 -0
  77. pyvale/rastercy.py +34 -4
  78. pyvale/rasternp.py +300 -276
  79. pyvale/rasteropts.py +58 -0
  80. pyvale/renderer.py +47 -0
  81. pyvale/rendermesh.py +52 -62
  82. pyvale/renderscene.py +51 -0
  83. pyvale/sensorarrayfactory.py +2 -2
  84. pyvale/sensortools.py +19 -35
  85. pyvale/simcases/case21.i +1 -1
  86. pyvale/simcases/run_1case.py +8 -0
  87. pyvale/simtools.py +2 -2
  88. pyvale/visualsimplotter.py +180 -0
  89. {pyvale-2025.5.3.dist-info → pyvale-2025.7.1.dist-info}/METADATA +11 -57
  90. {pyvale-2025.5.3.dist-info → pyvale-2025.7.1.dist-info}/RECORD +96 -56
  91. {pyvale-2025.5.3.dist-info → pyvale-2025.7.1.dist-info}/WHEEL +1 -1
  92. pyvale.libs/libgcc_s-69c45f16.so.1 +0 -0
  93. pyvale.libs/libgomp-b626072d.so.1.0.0 +0 -0
  94. pyvale.libs/libstdc++-1f1a71be.so.6.0.33 +0 -0
  95. pyvale/examples/visualisation/ex1_1_plot_traces.py +0 -102
  96. pyvale/examples/visualisation/ex2_1_animate_sim.py +0 -89
  97. {pyvale-2025.5.3.dist-info → pyvale-2025.7.1.dist-info}/licenses/LICENSE +0 -0
  98. {pyvale-2025.5.3.dist-info → pyvale-2025.7.1.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
- @dataclass(slots=True)
19
- class RenderMeshData:
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
- node_count: int = field(init=False)
28
- elem_count: int = field(init=False)
29
- nodes_per_elem: int = field(init=False)
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
- 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)
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
- 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
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
- ) -> RenderMeshData:
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
- 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)
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
+
@@ -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: tuple[int,int],
64
- components: tuple[int,int],
65
- time_steps: tuple[int,int]) -> None:
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 measurement for.
75
- sensors : tuple[int,int]
76
- Range of sensors to print from the measurement array using the slice
77
- specified by this tuple.
78
- components : tuple[int,int]
79
- Range of field components to print based on slicing the measurement
80
- array with this tuple.
81
- time_steps : tuple[int,int]
82
- Range of time steps to print based on slicing the measurement array with
83
- this tuple.
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
- print_meas = measurement[sensors[0]:sensors[1],
93
- components[0]:components[1],
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
- print_randerrs = rand_errs[sensors[0]:sensors[1],
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
- print_syserrs = sys_errs[sensors[0]:sensors[1],
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
- print_toterrs = tot_errs[sensors[0]:sensors[1],
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
@@ -9,7 +9,7 @@ endTime = 10
9
9
  timeStep = 1
10
10
 
11
11
  # Mechanical Loads/BCs
12
- topDispRate = ${fparse -1.5e-3 / endTime} # m/s
12
+ topDispRate = ${fparse -5e-3 / endTime} # m/s
13
13
 
14
14
  # Mechanical Props: SS316L @ 20degC
15
15
  ss316LEMod = 200e9 # Pa
@@ -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 RenderMeshData
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: RenderMeshData) -> np.ndarray | None:
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
+