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,120 @@
1
+ # ==============================================================================
2
+ # pyvale: the python validation engine
3
+ # License: MIT
4
+ # Copyright (C) 2025 The Computer Aided Validation Team
5
+ # ==============================================================================
6
+
7
+ import numpy as np
8
+ from scipy.spatial.transform import Rotation
9
+ from pathlib import Path
10
+ import pyvale
11
+
12
+ def main() -> None:
13
+ #NOTE: All lengths are to be specified in mm
14
+
15
+ # Set the save path
16
+ # --------------------------------------------------------------------------
17
+ # All the files saved will be saved to a subfolder within this specified
18
+ # base directory.
19
+ # This base directory can be specified by:
20
+ base_dir = Path("./")
21
+ # If no base directory is specified, it will be set as your home directory
22
+
23
+ # Creating the scene
24
+ # --------------------------------------------------------------------------
25
+ scene = pyvale.BlenderScene()
26
+
27
+ # Add the calibration target
28
+ # A rectangular calibration target of the specified size is added to the scene
29
+ target = scene.add_cal_target(target_size=np.array([150, 100, 10]))
30
+
31
+ # Add the camera
32
+ cam_data_0 = pyvale.CameraData(pixels_num=np.array([1540, 1040]),
33
+ pixels_size=np.array([0.00345, 0.00345]),
34
+ pos_world=np.array([0, 0, 400]),
35
+ rot_world=Rotation.from_euler("xyz", [0, 0, 0]),
36
+ roi_cent_world=(0, 0, 0),
37
+ focal_length=15.0)
38
+ # Set this to "symmetric" to get a symmetric stereo system or set this to
39
+ # "faceon" to get a face-on stereo system
40
+ stereo_setup = "faceon"
41
+ if stereo_setup == "symmetric":
42
+ stereo_system = pyvale.CameraTools.symmetric_stereo_cameras(
43
+ cam_data_0=cam_data_0,
44
+ stereo_angle=15.0)
45
+ if stereo_setup == "faceon":
46
+ stereo_system = pyvale.CameraTools.faceon_stereo_cameras(
47
+ cam_data_0=cam_data_0,
48
+ stereo_angle=15.0)
49
+
50
+ scene.add_stereo_system(stereo_system)
51
+
52
+ # Generate calibration file
53
+ stereo_system.save_calibration(base_dir)
54
+
55
+ # Add the light
56
+ light_data = pyvale.BlenderLightData(type=pyvale.BlenderLightType.POINT,
57
+ pos_world=(0, 0, 200),
58
+ rot_world=Rotation.from_euler("xyz",
59
+ [0, 0, 0]),
60
+ energy=1)
61
+ light = scene.add_light(light_data)
62
+ # The light can be moved and rotated:
63
+ light.location = (0, 0, 210)
64
+ light.rotation_euler = (0, 0, 0) # NOTE: The default is an XYZ Euler angle
65
+
66
+ # Apply the calibration target pattern
67
+ material_data = pyvale.BlenderMaterialData()
68
+ speckle_path = Path.cwd() / "src/pyvale/data/cal_target.tiff"
69
+ mm_px_resolution = pyvale.CameraTools.calculate_mm_px_resolution(cam_data_0)
70
+ scene.add_speckle(part=target,
71
+ speckle_path=speckle_path,
72
+ mat_data=material_data,
73
+ mm_px_resolution=mm_px_resolution,
74
+ cal=True)
75
+ # NOTE: The `cal` flag has to be set to True in order to scale the
76
+ # calibration target pattern correctly
77
+
78
+ # Rendering calibration images
79
+ # --------------------------------------------------------------------------
80
+ save_dir = Path.cwd() / "blenderimages"
81
+ save_name = "cal"
82
+ render_data = pyvale.RenderData(cam_data=(stereo_system.cam_data_0,
83
+ stereo_system.cam_data_1),
84
+ base_dir=base_dir)
85
+ # NOTE: The number of threads used to render the images is set within
86
+ # RenderData, it is defaulted to 4 threads
87
+
88
+ # The desired limits for the calibration target movement are to be set within
89
+ # the CalibrationData dataclass
90
+ calibration_data = pyvale.CalibrationData(angle_lims=(-10, 10),
91
+ angle_step=5,
92
+ plunge_lims=(-5, 5),
93
+ plunge_step=5)
94
+
95
+ # The number of calibration images that will be rendered can be calculated
96
+ number_calibration_images = pyvale.BlenderTools.number_calibration_images(calibration_data)
97
+ print()
98
+ print(80*"-")
99
+ print("Number of calibration images to be rendered:", number_calibration_images)
100
+ print(80*"-")
101
+
102
+ # The calibration images can then be rendered
103
+ pyvale.BlenderTools.render_calibration_images(render_data,
104
+ calibration_data,
105
+ target)
106
+
107
+ print()
108
+ print(80*"-")
109
+ print("Save directory of the images:", (render_data.base_dir / "calimages"))
110
+ print(80*"-")
111
+ print()
112
+
113
+ # Save Blender file
114
+ # --------------------------------------------------------------------------
115
+ # The file that will be saved is a Blender project file. This can be opened
116
+ # with the Blender GUI to view the scene.
117
+ pyvale.BlenderTools.save_blender_file(base_dir)
118
+
119
+ if __name__ == "__main__":
120
+ main()
@@ -1,10 +1,9 @@
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
  from pathlib import Path
9
8
  import time
10
9
  import numpy as np
@@ -1,10 +1,8 @@
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
+ # ==============================================================================
8
6
 
9
7
  import time
10
8
  import numpy as np
@@ -1,10 +1,9 @@
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
  from pathlib import Path
9
8
  import time
10
9
  import numpy as np
@@ -14,10 +13,6 @@ import mooseherder as mh
14
13
  import pyvale as pyv
15
14
 
16
15
  def main() -> None:
17
- """pyvale example: rasterisation field renderer
18
- ----------------------------------------------------------------------------
19
- - TODO
20
- """
21
16
  print()
22
17
  print(80*"=")
23
18
  print("RASTER CYTHON FILE (should be *.so on Linux):")
@@ -25,8 +20,6 @@ def main() -> None:
25
20
  print(80*"=")
26
21
  print()
27
22
 
28
- # This a path to an exodus *.e output file from MOOSE, this can be
29
- # replaced with a path to your own simulation file
30
23
  sim_path = pyv.DataSet.render_mechanical_3d_path()
31
24
  #sim_path = pyv.DataSet.render_simple_block_path()
32
25
  #sim_path = Path.home()/"pyvale"/"src"/"pyvale"/"simcases"/"case26_out.e"
@@ -1,11 +1,11 @@
1
- """
2
- ================================================================================
3
- pyvale: the python validation engine
4
- License: MIT
5
- Copyright (C) 2025 The Computer Aided Validation Team
6
- ================================================================================
7
- """
8
- from pathlib import Path
1
+ # ==============================================================================
2
+ # pyvale: the python validation engine
3
+ # License: MIT
4
+ # Copyright (C) 2025 The Computer Aided Validation Team
5
+ # ==============================================================================
6
+
7
+
8
+
9
9
  import time
10
10
  import numpy as np
11
11
  from scipy.spatial.transform import Rotation
@@ -13,11 +13,8 @@ import matplotlib.pyplot as plt
13
13
  import mooseherder as mh
14
14
  import pyvale as pyv
15
15
 
16
+
16
17
  def main() -> None:
17
- """pyvale example: rasterisation field renderer
18
- ----------------------------------------------------------------------------
19
- - TODO
20
- """
21
18
  print()
22
19
  print(80*"=")
23
20
  print("RASTER CYTHON FILE (should be *.so on Linux):")
@@ -1,29 +1,31 @@
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
- Example: thermocouples on a 2d plate
8
+ Pyvale example: TODO
9
+ --------------------------------------------------------------------------------
10
+ TODO
4
11
 
5
- pyvale: the python validation engine
6
- License: MIT
7
- Copyright (C) 2025 The Computer Aided Validation Team
8
- ================================================================================
12
+ Test case: TODO
9
13
  """
14
+
10
15
  import numpy as np
11
16
  import matplotlib.pyplot as plt
12
17
  import mooseherder as mh
13
18
  import pyvale as pyv
14
19
 
20
+ # TODO: comments and full description for this example like the basics examples
15
21
 
16
22
  def main() -> None:
17
- """pyvale example: point sensors on a 2D thermal simulation
18
- ----------------------------------------------------------------------------
19
- - Demonstrates options for controlling plots of points sensor traces using
20
- matplotlib
21
- """
23
+
22
24
  data_path = pyv.DataSet.thermal_2d_path()
23
25
  sim_data = mh.ExodusReader(data_path).read_all_sim_data()
24
- field_key = list(sim_data.node_vars.keys())[0] # type: ignore
25
- # Scale to mm to make 3D visualisation scaling easier
26
- sim_data.coords = sim_data.coords*1000.0 # type: ignore
26
+ sim_data = pyv.scale_length_units(scale=1000.0,
27
+ sim_data=sim_data,
28
+ disp_comps=None)
27
29
 
28
30
  n_sens = (4,1,1)
29
31
  x_lims = (0.0,100.0)
@@ -36,11 +38,12 @@ def main() -> None:
36
38
  sens_data = pyv.SensorData(positions=sens_pos,
37
39
  sample_times=sample_times)
38
40
 
41
+ field_key = "temperature"
39
42
  tc_array = pyv.SensorArrayFactory \
40
43
  .thermocouples_basic_errs(sim_data,
41
44
  sens_data,
42
45
  field_key,
43
- spat_dims=2)
46
+ elem_dims=2)
44
47
 
45
48
  err_int = pyv.ErrIntegrator([pyv.ErrSysOffset(offset=-5.0)],
46
49
  sens_data,
@@ -49,14 +52,24 @@ def main() -> None:
49
52
 
50
53
  measurements = tc_array.get_measurements()
51
54
 
55
+
52
56
  print(80*"-")
53
- print("Looking at the last 5 time steps (measurements) of sensor 0:")
54
- pyv.print_measurements(tc_array,
55
- (0,1),
56
- (0,1),
57
- (measurements.shape[2]-5,measurements.shape[2]))
57
+
58
+ sens_print: int = 0
59
+ time_print: int = 5
60
+ comp_print: int = 0
61
+
62
+ print(f"These are the last {time_print} virtual measurements of sensor "
63
+ + f"{sens_print}:")
64
+
65
+ pyv.print_measurements(sens_array=tc_array,
66
+ sensors=(sens_print,sens_print+1),
67
+ components=(comp_print,comp_print+1),
68
+ time_steps=(measurements.shape[2]-time_print,
69
+ measurements.shape[2]))
58
70
  print(80*"-")
59
71
 
72
+
60
73
  trace_props = pyv.TraceOptsSensor()
61
74
 
62
75
  trace_props.truth_line = None
@@ -1,48 +1,54 @@
1
- '''
2
- ================================================================================
3
- Example: 3d thermocouples on a monoblock
4
-
5
- pyvale: the python validation engine
6
- License: MIT
7
- Copyright (C) 2025 The Computer Aided Validation Team
8
- ================================================================================
9
- '''
1
+ # ==============================================================================
2
+ # pyvale: the python validation engine
3
+ # License: MIT
4
+ # Copyright (C) 2025 The Computer Aided Validation Team
5
+ # ==============================================================================
6
+
7
+ """
8
+ Pyvale example: TODO
9
+ --------------------------------------------------------------------------------
10
+ TODO
11
+
12
+ Test case: TODO
13
+ """
14
+
10
15
  from pathlib import Path
11
16
  import numpy as np
12
17
  import mooseherder as mh
13
- import pyvale
18
+ import pyvale as pyv
14
19
 
15
20
 
16
21
  def main() -> None:
17
- """pyvale example: visualisation tools 3D
18
- """
19
- # Use mooseherder to read the exodus and get a SimData object
20
- data_path = pyvale.DataSet.thermal_3d_path()
22
+
23
+ data_path = pyv.DataSet.thermal_3d_path()
21
24
  sim_data = mh.ExodusReader(data_path).read_all_sim_data()
22
- field_name = 'temperature'
23
- # Scale to mm to make 3D visualisation scaling easier
25
+
26
+ sim_data = pyv.scale_length_units(scale=1000.0,
27
+ sim_data=sim_data,
28
+ disp_comps=None)
24
29
  sim_data.coords = sim_data.coords*1000.0 # type: ignore
25
30
 
26
- pyvale.print_dimensions(sim_data)
31
+ pyv.print_dimensions(sim_data)
27
32
 
28
33
  n_sens = (1,4,1)
29
34
  x_lims = (12.5,12.5)
30
35
  y_lims = (0,33.0)
31
36
  z_lims = (0.0,12.0)
32
- sens_pos = pyvale.create_sensor_pos_array(n_sens,x_lims,y_lims,z_lims)
37
+ sens_pos = pyv.create_sensor_pos_array(n_sens,x_lims,y_lims,z_lims)
33
38
 
34
- sens_data = pyvale.SensorData(positions=sens_pos)
39
+ sens_data = pyv.SensorData(positions=sens_pos)
35
40
 
36
- tc_array = pyvale.SensorArrayFactory() \
41
+ field_key = 'temperature'
42
+ tc_array = pyv.SensorArrayFactory() \
37
43
  .thermocouples_basic_errs(sim_data,
38
44
  sens_data,
39
- field_name,
40
- spat_dims=3)
45
+ field_key,
46
+ elem_dims=3)
41
47
 
42
48
  measurements = tc_array.get_measurements()
43
49
  print(f'\nMeasurements for sensor at top of block:\n{measurements[-1,0,:]}\n')
44
50
 
45
- vis_opts = pyvale.VisOptsSimSensors()
51
+ vis_opts = pyv.VisOptsSimSensors()
46
52
  vis_opts.window_size_px = (1200,800)
47
53
  vis_opts.camera_position = np.array([(59.354, 43.428, 69.946),
48
54
  (-2.858, 13.189, 4.523),
@@ -54,25 +60,25 @@ def main() -> None:
54
60
  save_dir.mkdir()
55
61
 
56
62
  if vis_mode == "animate":
57
- anim_opts = pyvale.VisOptsAnimation()
63
+ anim_opts = pyv.VisOptsAnimation()
58
64
 
59
65
  anim_opts.save_path = save_dir / "test_animation"
60
- anim_opts.save_animation = pyvale.EAnimationType.MP4
66
+ anim_opts.save_animation = pyv.EAnimationType.MP4
61
67
 
62
- pv_anim = pyvale.animate_sim_with_sensors(tc_array,
63
- field_name,
68
+ pv_anim = pyv.animate_sim_with_sensors(tc_array,
69
+ field_key,
64
70
  time_steps=None,
65
71
  vis_opts=vis_opts,
66
72
  anim_opts=anim_opts)
67
73
 
68
74
  else:
69
- image_save_opts = pyvale.VisOptsImageSave()
75
+ image_save_opts = pyv.VisOptsImageSave()
70
76
 
71
77
  image_save_opts.path = save_dir / "test_vector_graphics"
72
- image_save_opts.image_type = pyvale.EImageType.SVG
78
+ image_save_opts.image_type = pyv.EImageType.SVG
73
79
 
74
- pv_plot = pyvale.plot_point_sensors_on_sim(tc_array,
75
- field_name,
80
+ pv_plot = pyv.plot_point_sensors_on_sim(tc_array,
81
+ field_key,
76
82
  time_step=-1,
77
83
  vis_opts=vis_opts,
78
84
  image_save_opts=image_save_opts)
@@ -0,0 +1,175 @@
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 is used for performing Monte-Carlo virtual experiments over a series
9
+ of input simulation cases and sensor arrays.
10
+ """
11
+
12
+ from dataclasses import dataclass
13
+ import numpy as np
14
+ import mooseherder as mh
15
+ from pyvale.sensorarray import ISensorArray
16
+
17
+
18
+ @dataclass(slots=True)
19
+ class ExperimentStats:
20
+ """Dataclass holding summary statistics for a series of simulated
21
+ experiments produced using the experiment simulator. All summary statistics
22
+ are calculated over the 'experiments' dimension of the measurements array so
23
+ the arrays of statistics have the shape=(n_sims,n_sensors,n_field_comps,
24
+ n_time_steps). Note that the n_sims dimension refers to the number of input
25
+ multi-physics simulations (i.e. SimData objects) that the virtual
26
+ experiments were performed over.
27
+ """
28
+
29
+ mean: np.ndarray | None = None
30
+ """Mean of each sensors measurement for the given field component and time
31
+ step as an array with shape=(n_sims,n_sensors,n_field_comps,n_time_steps).
32
+ """
33
+
34
+ std: np.ndarray | None = None
35
+ """Standard deviation of the sensor measurements for the given field
36
+ component and time step as an array with shape=(n_sims,n_sensors,
37
+ n_field_comps, n_time_steps)
38
+ """
39
+
40
+ max: np.ndarray | None = None
41
+ """Maximum of the sensor measurements for the given field component and time
42
+ step as an array with shape=(n_sims,n_sensors,n_field_comps,n_time_steps)
43
+ """
44
+
45
+ min: np.ndarray | None = None
46
+ """Minmum of the sensor measurements for the given field component and time
47
+ step as an array with shape=(n_sims,n_sensors,n_field_comps,n_time_steps)
48
+ """
49
+
50
+ med: np.ndarray | None = None
51
+ """Median of the sensor measurements for the given field component and time
52
+ step as an array with shape=(n_sims,n_sensors,n_field_comps,n_time_steps)
53
+ """
54
+
55
+ q25: np.ndarray | None = None
56
+ """Lower 25% quantile of the sensor measurements for the given field
57
+ component and time step as an array with shape=(n_sims,n_sensors,
58
+ n_field_comps, n_time_steps)
59
+ """
60
+
61
+ q75: np.ndarray | None = None
62
+ """Upper 75% quantile of the sensor measurements for the given field
63
+ component and time step as an array with shape=(n_sims,n_sensors,
64
+ _field_comps, n_time_steps)
65
+ """
66
+
67
+ mad: np.ndarray | None = None
68
+ """Median absolute deviation of the sensor measurements for the given field
69
+ component and time step as an array with shape=(n_sims,n_sensors,
70
+ n_field_comps, n_time_steps)
71
+ """
72
+
73
+
74
+ class ExperimentSimulator:
75
+ """An experiment simulator for running monte-carlo analysis by applying a
76
+ list of sensor arrays to a list of simulations over a given number of user
77
+ defined experiments. Calculates summary statistics for each sensor array
78
+ applied to each simulation.
79
+ """
80
+ __slots__ = ("_sim_list","_sensor_arrays","_num_exp_per_sim","_exp_data",
81
+ "_exp_stats")
82
+
83
+ def __init__(self,
84
+ sim_list: list[mh.SimData],
85
+ sensor_arrays: list[ISensorArray],
86
+ num_exp_per_sim: int
87
+ ) -> None:
88
+ """
89
+ Parameters
90
+ ----------
91
+ sim_list : list[mh.SimData]
92
+ List of simulation data objects over which the virtual experiments
93
+ will be performed.
94
+ sensor_arrays : list[ISensorArray]
95
+ The sensor arrays that will be applied to each simulation to
96
+ generate the virtual experiment data.
97
+ num_exp_per_sim : int
98
+ Number of virtual experiments to perform for each simulation and
99
+ sensor array.
100
+ """
101
+ self._sim_list = sim_list
102
+ self._sensor_arrays = sensor_arrays
103
+ self._num_exp_per_sim = num_exp_per_sim
104
+ self._exp_data = None
105
+ self._exp_stats = None
106
+
107
+ def run_experiments(self) -> list[np.ndarray]:
108
+ """Runs the specified number of virtual experiments over the number of
109
+ input simulation cases and virtual sensor arrays.
110
+
111
+ Returns
112
+ -------
113
+ list[np.ndarray]
114
+ List of virtual experimental data arrays where the list index
115
+ corresponds to the virtual sensor array and the data is an array
116
+ with shape=(n_sims,n_exps,n_sens,n_comps,n_time_steps).
117
+ """
118
+
119
+ n_sims = len(self._sim_list)
120
+ # shape=list[n_sens_arrays](n_sims,n_exps,n_sens,n_comps,n_time_steps)
121
+ self._exp_data = [None]*len(self._sensor_arrays)
122
+
123
+ for ii,aa in enumerate(self._sensor_arrays):
124
+ meas_array = np.zeros((n_sims,self._num_exp_per_sim)+
125
+ aa.get_measurement_shape())
126
+
127
+ for jj,ss in enumerate(self._sim_list):
128
+ aa.get_field().set_sim_data(ss)
129
+
130
+ for ee in range(self._num_exp_per_sim):
131
+ meas_array[jj,ee,:,:,:] = aa.calc_measurements()
132
+
133
+ self._exp_data[ii] = meas_array
134
+
135
+ # shape=list[n_sens_arrays](n_sims,n_exps,n_sens,n_comps,n_time_steps)
136
+ return self._exp_data
137
+
138
+
139
+ def calc_stats(self) -> list[ExperimentStats]:
140
+ """Calculates summary statistics over the number of virtual experiments
141
+ specified. If `run_experiments()` has not been called then it is called
142
+ to generate the virtual experimental data to perform the statistical
143
+ calculations.
144
+
145
+ Returns
146
+ -------
147
+ list[ExperimentStats]
148
+ List of summary statistics data classes for the virtual experiments.
149
+ The list index correponds to the virtual sensor array.
150
+ """
151
+ if self._exp_data is None:
152
+ self._exp_data = self.run_experiments()
153
+
154
+ # shape=list[n_sens_arrays](n_sims,n_sens,n_comps,n_time_steps)
155
+ self._exp_stats = [None]*len(self._sensor_arrays)
156
+ for ii,_ in enumerate(self._sensor_arrays):
157
+ array_stats = ExperimentStats()
158
+ array_stats.max = np.max(self._exp_data[ii],axis=1)
159
+ array_stats.min = np.min(self._exp_data[ii],axis=1)
160
+ array_stats.mean = np.mean(self._exp_data[ii],axis=1)
161
+ array_stats.std = np.std(self._exp_data[ii],axis=1)
162
+ array_stats.med = np.median(self._exp_data[ii],axis=1)
163
+ array_stats.q25 = np.quantile(self._exp_data[ii],0.25,axis=1)
164
+ array_stats.q75 = np.quantile(self._exp_data[ii],0.75,axis=1)
165
+ array_stats.mad = np.median(np.abs(self._exp_data[ii] -
166
+ np.median(self._exp_data[ii],axis=1,keepdims=True)),axis=1)
167
+ self._exp_stats[ii] = array_stats
168
+
169
+ # shape=list[n_sens_arrays](n_sims,n_sens,n_comps,n_time_steps)
170
+ return self._exp_stats
171
+
172
+
173
+
174
+
175
+
@@ -1,10 +1,9 @@
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
  from abc import ABC, abstractmethod
9
8
  import numpy as np
10
9
  from scipy.spatial.transform import Rotation
@@ -30,7 +29,6 @@ class IField(ABC):
30
29
  Mooseherder SimData object. Contains a mesh and a simulated
31
30
  physical field.
32
31
  """
33
- pass
34
32
 
35
33
  @abstractmethod
36
34
  def get_sim_data(self) -> mh.SimData:
@@ -44,7 +42,6 @@ class IField(ABC):
44
42
  Mooseherder SimData object. Contains a mesh and a simulated
45
43
  physical field.
46
44
  """
47
- pass
48
45
 
49
46
  @abstractmethod
50
47
  def get_time_steps(self) -> np.ndarray:
@@ -56,7 +53,6 @@ class IField(ABC):
56
53
  np.ndarray
57
54
  1D array of simulation time steps. shape=(num_time_steps,)
58
55
  """
59
- pass
60
56
 
61
57
  @abstractmethod
62
58
  def get_visualiser(self) -> pv.UnstructuredGrid:
@@ -69,7 +65,6 @@ class IField(ABC):
69
65
  Pyvista unstructured grid object containing only a mesh without any
70
66
  physical field data attached.
71
67
  """
72
- pass
73
68
 
74
69
  @abstractmethod
75
70
  def get_all_components(self) -> tuple[str,...]:
@@ -83,7 +78,6 @@ class IField(ABC):
83
78
  Tuple containing the string keys for all components of the physical
84
79
  field.
85
80
  """
86
- pass
87
81
 
88
82
  @abstractmethod
89
83
  def get_component_index(self,component: str) -> int:
@@ -100,7 +94,6 @@ class IField(ABC):
100
94
  int
101
95
  Index for the selected field component
102
96
  """
103
- pass
104
97
 
105
98
  @abstractmethod
106
99
  def sample_field(self,
@@ -133,4 +126,3 @@ class IField(ABC):
133
126
  An array of sampled (interpolated) values with the following
134
127
  dimensions: shape=(num_points,num_components,num_time_steps).
135
128
  """
136
- pass