pyvale 2025.4.1__py3-none-any.whl → 2025.5.2__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 (126) hide show
  1. pyvale/__init__.py +18 -3
  2. pyvale/analyticmeshgen.py +1 -0
  3. pyvale/analyticsimdatafactory.py +18 -13
  4. pyvale/analyticsimdatagenerator.py +105 -72
  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/camera.py +6 -5
  12. pyvale/cameradata.py +25 -7
  13. pyvale/cameradata2d.py +6 -4
  14. pyvale/camerastereo.py +217 -0
  15. pyvale/cameratools.py +206 -11
  16. pyvale/cython/rastercyth.py +6 -2
  17. pyvale/data/cal_target.tiff +0 -0
  18. pyvale/dataset.py +73 -14
  19. pyvale/errorcalculator.py +8 -10
  20. pyvale/errordriftcalc.py +10 -9
  21. pyvale/errorintegrator.py +19 -21
  22. pyvale/errorrand.py +33 -39
  23. pyvale/errorsyscalib.py +134 -0
  24. pyvale/errorsysdep.py +19 -22
  25. pyvale/errorsysfield.py +49 -41
  26. pyvale/errorsysindep.py +79 -175
  27. pyvale/examples/basics/ex1_1_basicscalars_therm2d.py +131 -0
  28. pyvale/examples/basics/ex1_2_sensormodel_therm2d.py +158 -0
  29. pyvale/examples/basics/ex1_3_customsens_therm3d.py +216 -0
  30. pyvale/examples/basics/ex1_4_basicerrors_therm3d.py +153 -0
  31. pyvale/examples/basics/ex1_5_fielderrs_therm3d.py +168 -0
  32. pyvale/examples/basics/ex1_6_caliberrs_therm2d.py +133 -0
  33. pyvale/examples/basics/ex1_7_spatavg_therm2d.py +123 -0
  34. pyvale/examples/basics/ex2_1_basicvectors_disp2d.py +112 -0
  35. pyvale/examples/basics/ex2_2_vectorsens_disp2d.py +111 -0
  36. pyvale/examples/basics/ex2_3_sensangle_disp2d.py +139 -0
  37. pyvale/examples/basics/ex2_4_chainfielderrs_disp2d.py +196 -0
  38. pyvale/examples/basics/ex2_5_vectorfields3d_disp3d.py +109 -0
  39. pyvale/examples/basics/ex3_1_basictensors_strain2d.py +114 -0
  40. pyvale/examples/basics/ex3_2_tensorsens2d_strain2d.py +111 -0
  41. pyvale/examples/basics/ex3_3_tensorsens3d_strain3d.py +182 -0
  42. pyvale/examples/basics/ex4_1_expsim2d_thermmech2d.py +171 -0
  43. pyvale/examples/basics/ex4_2_expsim3d_thermmech3d.py +252 -0
  44. pyvale/examples/{analyticdatagen → genanalyticdata}/ex1_1_scalarvisualisation.py +6 -9
  45. pyvale/examples/{analyticdatagen → genanalyticdata}/ex1_2_scalarcasebuild.py +8 -11
  46. pyvale/examples/{analyticdatagen → genanalyticdata}/ex2_1_analyticsensors.py +9 -12
  47. pyvale/examples/imagedef2d/ex_imagedef2d_todisk.py +8 -15
  48. pyvale/examples/renderblender/ex1_1_blenderscene.py +121 -0
  49. pyvale/examples/renderblender/ex1_2_blenderdeformed.py +119 -0
  50. pyvale/examples/renderblender/ex2_1_stereoscene.py +128 -0
  51. pyvale/examples/renderblender/ex2_2_stereodeformed.py +131 -0
  52. pyvale/examples/renderblender/ex3_1_blendercalibration.py +120 -0
  53. pyvale/examples/{rasterisation → renderrasterisation}/ex_rastenp.py +3 -2
  54. pyvale/examples/{rasterisation → renderrasterisation}/ex_rastercyth_oneframe.py +2 -2
  55. pyvale/examples/{rasterisation → renderrasterisation}/ex_rastercyth_static_cypara.py +3 -8
  56. pyvale/examples/{rasterisation → renderrasterisation}/ex_rastercyth_static_pypara.py +6 -7
  57. pyvale/examples/{ex1_4_thermal2d.py → visualisation/ex1_1_plot_traces.py} +32 -16
  58. pyvale/examples/{features/ex_animation_tools_3dmonoblock.py → visualisation/ex2_1_animate_sim.py} +37 -31
  59. pyvale/experimentsimulator.py +107 -30
  60. pyvale/field.py +2 -9
  61. pyvale/fieldconverter.py +98 -22
  62. pyvale/fieldsampler.py +2 -2
  63. pyvale/fieldscalar.py +10 -10
  64. pyvale/fieldtensor.py +15 -17
  65. pyvale/fieldtransform.py +7 -2
  66. pyvale/fieldvector.py +6 -7
  67. pyvale/generatorsrandom.py +25 -47
  68. pyvale/imagedef2d.py +6 -2
  69. pyvale/integratorfactory.py +2 -2
  70. pyvale/integratorquadrature.py +50 -24
  71. pyvale/integratorrectangle.py +85 -7
  72. pyvale/integratorspatial.py +4 -4
  73. pyvale/integratortype.py +3 -3
  74. pyvale/output.py +17 -0
  75. pyvale/pyvaleexceptions.py +11 -0
  76. pyvale/raster.py +6 -5
  77. pyvale/rastercy.py +6 -4
  78. pyvale/rasternp.py +6 -4
  79. pyvale/rendermesh.py +6 -2
  80. pyvale/sensorarray.py +2 -2
  81. pyvale/sensorarrayfactory.py +52 -65
  82. pyvale/sensorarraypoint.py +29 -30
  83. pyvale/sensordata.py +2 -2
  84. pyvale/sensordescriptor.py +138 -25
  85. pyvale/sensortools.py +3 -3
  86. pyvale/simtools.py +67 -0
  87. pyvale/visualexpplotter.py +99 -57
  88. pyvale/visualimagedef.py +11 -7
  89. pyvale/visualimages.py +6 -4
  90. pyvale/visualopts.py +372 -58
  91. pyvale/visualsimanimator.py +42 -13
  92. pyvale/visualsimsensors.py +318 -0
  93. pyvale/visualtools.py +69 -13
  94. pyvale/visualtraceplotter.py +52 -165
  95. {pyvale-2025.4.1.dist-info → pyvale-2025.5.2.dist-info}/METADATA +17 -14
  96. pyvale-2025.5.2.dist-info/RECORD +172 -0
  97. {pyvale-2025.4.1.dist-info → pyvale-2025.5.2.dist-info}/WHEEL +1 -1
  98. pyvale/examples/analyticdatagen/__init__.py +0 -5
  99. pyvale/examples/ex1_1_thermal2d.py +0 -86
  100. pyvale/examples/ex1_2_thermal2d.py +0 -108
  101. pyvale/examples/ex1_3_thermal2d.py +0 -110
  102. pyvale/examples/ex1_5_thermal2d.py +0 -102
  103. pyvale/examples/ex2_1_thermal3d .py +0 -84
  104. pyvale/examples/ex2_2_thermal3d.py +0 -51
  105. pyvale/examples/ex2_3_thermal3d.py +0 -106
  106. pyvale/examples/ex3_1_displacement2d.py +0 -44
  107. pyvale/examples/ex3_2_displacement2d.py +0 -76
  108. pyvale/examples/ex3_3_displacement2d.py +0 -101
  109. pyvale/examples/ex3_4_displacement2d.py +0 -102
  110. pyvale/examples/ex4_1_strain2d.py +0 -54
  111. pyvale/examples/ex4_2_strain2d.py +0 -76
  112. pyvale/examples/ex4_3_strain2d.py +0 -97
  113. pyvale/examples/ex5_1_multiphysics2d.py +0 -75
  114. pyvale/examples/ex6_1_multiphysics2d_expsim.py +0 -115
  115. pyvale/examples/ex6_2_multiphysics3d_expsim.py +0 -160
  116. pyvale/examples/features/__init__.py +0 -5
  117. pyvale/examples/features/ex_area_avg.py +0 -89
  118. pyvale/examples/features/ex_calibration_error.py +0 -108
  119. pyvale/examples/features/ex_chain_field_errs.py +0 -141
  120. pyvale/examples/features/ex_field_errs.py +0 -78
  121. pyvale/examples/features/ex_sensor_single_angle_batch.py +0 -110
  122. pyvale/optimcheckfuncs.py +0 -153
  123. pyvale/visualsimplotter.py +0 -182
  124. pyvale-2025.4.1.dist-info/RECORD +0 -163
  125. {pyvale-2025.4.1.dist-info → pyvale-2025.5.2.dist-info}/licenses/LICENSE +0 -0
  126. {pyvale-2025.4.1.dist-info → pyvale-2025.5.2.dist-info}/top_level.txt +0 -0
pyvale/__init__.py CHANGED
@@ -1,8 +1,8 @@
1
- # ================================================================================
1
+ # ==============================================================================
2
2
  # pyvale: the python validation engine
3
3
  # License: MIT
4
4
  # Copyright (C) 2025 The Computer Aided Validation Team
5
- # ================================================================================
5
+ # ==============================================================================
6
6
 
7
7
  """
8
8
  `pyvale`: the python validation engine. Used to simulate experimental data from
@@ -15,6 +15,7 @@ testing simulation validation metrics and testing digital shadows/twins.
15
15
  # underlying project structure: the user should be able to use 'pyvale.'
16
16
  # and access everything in one layer without multiple import dots
17
17
 
18
+
18
19
  from pyvale.dataset import *
19
20
 
20
21
  from pyvale.field import *
@@ -40,6 +41,7 @@ from pyvale.camera import *
40
41
  from pyvale.cameradata import *
41
42
  from pyvale.cameradata2d import *
42
43
  from pyvale.cameratools import *
44
+ from pyvale.camerastereo import *
43
45
 
44
46
  import pyvale.cython.rastercyth as rastercyth
45
47
  from pyvale.rastercy import *
@@ -54,13 +56,14 @@ from pyvale.errorrand import *
54
56
  from pyvale.errorsysindep import *
55
57
  from pyvale.errorsysdep import *
56
58
  from pyvale.errorsysfield import *
59
+ from pyvale.errorsyscalib import *
57
60
  from pyvale.errordriftcalc import *
58
61
 
59
62
  from pyvale.generatorsrandom import *
60
63
 
61
64
  from pyvale.visualopts import *
62
65
  from pyvale.visualtools import *
63
- from pyvale.visualsimplotter import *
66
+ from pyvale.visualsimsensors import *
64
67
  from pyvale.visualsimanimator import *
65
68
  from pyvale.visualexpplotter import *
66
69
  from pyvale.visualtraceplotter import *
@@ -72,3 +75,15 @@ from pyvale.analyticsimdatagenerator import *
72
75
  from pyvale.analyticsimdatafactory import *
73
76
 
74
77
  from pyvale.experimentsimulator import *
78
+
79
+ from pyvale.blendercalibrationdata import *
80
+ from pyvale.blenderlightdata import *
81
+ from pyvale.blendermaterialdata import *
82
+ from pyvale.blenderrenderdata import *
83
+ from pyvale.blenderscene import *
84
+ from pyvale.blendertools import *
85
+ from pyvale.simtools import *
86
+
87
+ from pyvale.output import *
88
+ from pyvale.pyvaleexceptions import *
89
+
pyvale/analyticmeshgen.py CHANGED
@@ -3,6 +3,7 @@
3
3
  # License: MIT
4
4
  # Copyright (C) 2025 The Computer Aided Validation Team
5
5
  #===============================================================================
6
+
6
7
  """
7
8
  Analytic mesh creation tools for testing pyvale sensor simulation and
8
9
  uncertainty quantification functionality with a known analytic function for the
@@ -1,18 +1,22 @@
1
- # ================================================================================
1
+ # ==============================================================================
2
2
  # pyvale: the python validation engine
3
3
  # License: MIT
4
4
  # Copyright (C) 2025 The Computer Aided Validation Team
5
- # ================================================================================
5
+ # ==============================================================================
6
+
7
+ """
8
+ Helper functions and mini factory for building standard test meshes with
9
+ analytic functions for the physical fields.
10
+ """
6
11
 
7
12
  import numpy as np
8
13
  import sympy
9
14
  import mooseherder as mh
10
- from pyvale.analyticsimdatagenerator import (AnalyticCaseData2D,
11
- AnalyticSimDataGenerator)
12
-
15
+ from pyvale.analyticsimdatagenerator import (AnalyticData2D,
16
+ AnalyticSimDataGen)
13
17
 
14
18
 
15
- def standard_case_2d() -> AnalyticCaseData2D:
19
+ def standard_case_2d() -> AnalyticData2D:
16
20
  """Created the standard 2D analytic test case which is a plate with
17
21
  dimensions 10x7.5 (x,y), number of elements 40x30 (x,y), and time steps of
18
22
  0 to 10 in increments of 1.
@@ -22,7 +26,7 @@ def standard_case_2d() -> AnalyticCaseData2D:
22
26
  AnalyticCaseData2D
23
27
  _description_
24
28
  """
25
- case_data = AnalyticCaseData2D()
29
+ case_data = AnalyticData2D()
26
30
  case_data.length_x = 10.0
27
31
  case_data.length_y = 7.5
28
32
  n_elem_mult = 10
@@ -33,12 +37,13 @@ def standard_case_2d() -> AnalyticCaseData2D:
33
37
 
34
38
 
35
39
  class AnalyticCaseFactory:
36
- """Builds pre-defined 2D meshes and fields based on analytic functions for
37
- testing the sensor simulation functionality of pyvale.
40
+ """Namespace for function used to build pre-defined 2D meshes and fields
41
+ based on analytic functions for testing the sensor simulation functionality
42
+ of pyvale.
38
43
  """
39
44
 
40
45
  @staticmethod
41
- def scalar_linear_2d() -> tuple[mh.SimData,AnalyticSimDataGenerator]:
46
+ def scalar_linear_2d() -> tuple[mh.SimData,AnalyticSimDataGen]:
42
47
  """_summary_
43
48
 
44
49
  Returns
@@ -54,14 +59,14 @@ class AnalyticCaseFactory:
54
59
  case_data.offsets_space = (20.0,)
55
60
  case_data.offsets_time = (0.0,)
56
61
 
57
- data_gen = AnalyticSimDataGenerator(case_data)
62
+ data_gen = AnalyticSimDataGen(case_data)
58
63
 
59
64
  sim_data = data_gen.generate_sim_data()
60
65
 
61
66
  return (sim_data,data_gen)
62
67
 
63
68
  @staticmethod
64
- def scalar_quadratic_2d() -> tuple[mh.SimData,AnalyticSimDataGenerator]:
69
+ def scalar_quadratic_2d() -> tuple[mh.SimData,AnalyticSimDataGen]:
65
70
  """_summary_
66
71
 
67
72
  Returns
@@ -75,7 +80,7 @@ class AnalyticCaseFactory:
75
80
  case_data.funcs_y = (sym_y*(sym_y - case_data.length_y),)
76
81
  case_data.funcs_t = (sym_t,)
77
82
 
78
- data_gen = AnalyticSimDataGenerator(case_data)
83
+ data_gen = AnalyticSimDataGen(case_data)
79
84
 
80
85
  sim_data = data_gen.generate_sim_data()
81
86
 
@@ -4,6 +4,11 @@
4
4
  # Copyright (C) 2025 The Computer Aided Validation Team
5
5
  #===============================================================================
6
6
 
7
+ """
8
+ Generic tools for creating SimData objects based on analytic functions for the
9
+ underlying physical fields. Useful for testing pyvale.
10
+ """
11
+
7
12
  from dataclasses import dataclass
8
13
  import numpy as np
9
14
  import sympy
@@ -12,7 +17,7 @@ from pyvale.analyticmeshgen import rectangle_mesh_2d, fill_dims_2d
12
17
 
13
18
 
14
19
  @dataclass(slots=True)
15
- class AnalyticCaseData2D:
20
+ class AnalyticData2D:
16
21
  """Dataclass for describing a 2D analytic test case for pyvale sensor
17
22
  simulation. Includes information about the geometry, the mesh and the
18
23
  analytic functions used to generate the field data.
@@ -72,89 +77,98 @@ class AnalyticCaseData2D:
72
77
  sympy.Symbol("x"),
73
78
  sympy.Symbol("t"))
74
79
  """Sympy symbols describing the relevant dimensions of the problem. For 2D
75
- spatial dimensions default to x and y and time is denoted t.
80
+ spatial dimensions default to x and y and time is denoted t. Note that these
81
+ are the symbols used to describe the analytic field functions.
76
82
  """
77
83
 
78
84
  offsets_space: tuple[float,...] = (0.0,)
79
- """_summary_
85
+ """Constants which are added to the physical field functions in each spatial
86
+ dimensions.
80
87
  """
81
88
 
82
89
  offsets_time: tuple[float,...] = (0.0,)
83
- """_summary_
90
+ """Constant which is added to the physical field function in time.
84
91
  """
85
92
 
86
93
  nodes_per_elem: int = 4
87
- """_summary_
94
+ """Number of nodes per element. Currently only rectangular meshes and with
95
+ 4 nodes per element are supported. Defaults to 4.
88
96
  """
89
97
 
90
98
 
91
- class AnalyticSimDataGenerator:
99
+ class AnalyticSimDataGen:
92
100
  """Class for generating analytic field data as a `SimData` object to test
93
101
  the sensor simulation functionality of pyvale. Provides tools to evaluate
94
- the analytic field functions at a given spatial coordinate and time to check
95
- against pyvale interpolation functions.
102
+ the analytic field functions at a given spatial coordinate/time to check
103
+ against pyvale interpolation functions. Currently only support 2D cases.
96
104
  """
97
105
 
98
- __slots__ = ("_case_data","_coords","_connect")
106
+ __slots__ = ("case_data","coords","connect","field_sym_funcs",
107
+ "field_lam_funcs","field_eval")
99
108
 
100
- def __init__(self, case_data: AnalyticCaseData2D
109
+ def __init__(self, case_data: AnalyticData2D
101
110
  ) -> None:
102
- """_summary_
103
-
111
+ """
104
112
  Parameters
105
113
  ----------
106
114
  case_data : AnalyticCaseData2D
107
- _description_
115
+ Data class containing the parameters required to create the analytic
116
+ mesh and the underlying physical field functions.
108
117
  """
109
- self._case_data = case_data
110
- (self._coords,self._connect) = rectangle_mesh_2d(case_data.length_x,
118
+ self.case_data = case_data
119
+ (self.coords,self.connect) = rectangle_mesh_2d(case_data.length_x,
111
120
  case_data.length_y,
112
121
  case_data.num_elem_x,
113
122
  case_data.num_elem_y)
114
123
 
115
- self._field_sym_funcs = dict()
116
- self._field_lam_funcs = dict()
124
+ self.field_sym_funcs = dict()
125
+ self.field_lam_funcs = dict()
117
126
  for ii,kk in enumerate(case_data.field_keys):
118
- self._field_sym_funcs[kk] = ((case_data.funcs_x[ii] *
127
+ self.field_sym_funcs[kk] = ((case_data.funcs_x[ii] *
119
128
  case_data.funcs_y[ii] +
120
129
  case_data.offsets_space[ii]) *
121
130
  (case_data.funcs_t[ii] +
122
131
  case_data.offsets_time[ii]))
123
132
 
124
- self._field_lam_funcs[kk] = sympy.lambdify(case_data.symbols,
125
- self._field_sym_funcs[kk],
133
+ self.field_lam_funcs[kk] = sympy.lambdify(case_data.symbols,
134
+ self.field_sym_funcs[kk],
126
135
  'numpy')
127
- self._field_eval = dict()
136
+ self.field_eval = dict()
128
137
 
129
138
 
130
139
  def evaluate_field_truth(self,
131
140
  field_key: str,
132
141
  coords: np.ndarray,
133
142
  time_steps: np.ndarray | None = None) -> np.ndarray:
134
- """_summary_
143
+ """Calculates the 'truth' from the analytical functions describing the
144
+ physical fields at the specified coordinates and time steps.
135
145
 
136
146
  Parameters
137
147
  ----------
138
148
  field_key : str
139
- _description_
149
+ Key for the underlying physical field.
140
150
  coords : np.ndarray
141
- _description_
151
+ Coordinates at which to evaluate the analytic physical field. shape
152
+ =(n_coords,coord[x,y,z])
142
153
  time_steps : np.ndarray | None, optional
143
- _description_, by default None
154
+ Time steps at which to evaluate the physical field, by default None.
155
+ If this is none the evaluation time steps are assumed to match the
156
+ nominal time steps.
144
157
 
145
158
  Returns
146
159
  -------
147
160
  np.ndarray
148
- _description_
161
+ Array of analytic field evaluations with shape = (n_coords,
162
+ n_time_steps)
149
163
  """
150
164
  if time_steps is None:
151
- time_steps = self._case_data.time_steps
165
+ time_steps = self.case_data.time_steps
152
166
 
153
167
  (x_eval,y_eval,t_eval) = fill_dims_2d(coords[:,0],
154
168
  coords[:,1],
155
169
  time_steps)
156
170
 
157
- field_vals = self._field_lam_funcs[field_key](y_eval,
171
+ field_vals = self.field_lam_funcs[field_key](y_eval,
158
172
  x_eval,
159
173
  t_eval)
160
174
  return field_vals
@@ -162,89 +176,105 @@ class AnalyticSimDataGenerator:
162
176
 
163
177
  def evaluate_all_fields_truth(self,
164
178
  coords: np.ndarray,
165
- time_steps: np.ndarray | None = None) -> np.ndarray:
166
- """_summary_
179
+ time_steps: np.ndarray | None = None
180
+ ) -> dict[str,np.ndarray]:
181
+ """Evaluates all analytic physical fields at the specified coordinates
182
+ and time steps.
167
183
 
168
184
  Parameters
169
185
  ----------
170
186
  coords : np.ndarray
171
- _description_
187
+ Coordinates at which to evaluate the analytic physical field. shape
188
+ =(n_coords,coord[x,y,z])
172
189
  time_steps : np.ndarray | None, optional
173
- _description_, by default None
190
+ Time steps at which to evaluate the physical field, by default None.
191
+ If this is none the evaluation time steps are assumed to match the
192
+ nominal time steps.
174
193
 
175
194
  Returns
176
195
  -------
177
- np.ndarray
178
- _description_
196
+ dict[str,np.ndarray]
197
+ Dictionary keyed by the field name giving a numpy array with shape =
198
+ (n_coords,n_timesteps)
179
199
  """
180
200
  if time_steps is None:
181
- time_steps = self._case_data.time_steps
201
+ time_steps = self.case_data.time_steps
182
202
 
183
203
  (x_eval,y_eval,t_eval) = fill_dims_2d(coords[:,0],
184
204
  coords[:,1],
185
205
  time_steps)
186
206
 
187
207
  eval_comps = dict()
188
- for kk in self._case_data.field_keys:
189
- eval_comps[kk] = self._field_lam_funcs[kk](y_eval,
208
+ for kk in self.case_data.field_keys:
209
+ eval_comps[kk] = self.field_lam_funcs[kk](y_eval,
190
210
  x_eval,
191
211
  t_eval)
192
212
  return eval_comps
193
213
 
194
214
 
195
215
  def evaluate_field_at_nodes(self, field_key: str) -> np.ndarray:
196
- (x_eval,y_eval,t_eval) = fill_dims_2d(self._coords[:,0],
197
- self._coords[:,1],
198
- self._case_data.time_steps)
199
- """_summary_
216
+ """Evaluates the underlying physical field at the node locations and
217
+ nominal time steps.
218
+
219
+ Parameters
220
+ ----------
221
+ field_key : str
222
+ String key for the field to be evaluated.
200
223
 
201
224
  Returns
202
225
  -------
203
- _type_
204
- _description_
226
+ np.ndarray
227
+ Array of field evaluations with shape=(n_nodes,n_timesteps)
205
228
  """
206
- self._field_eval[field_key] = self._field_lam_funcs[field_key](y_eval,
229
+ (x_eval,y_eval,t_eval) = fill_dims_2d(self.coords[:,0],
230
+ self.coords[:,1],
231
+ self.case_data.time_steps)
232
+
233
+ self.field_eval[field_key] = self.field_lam_funcs[field_key](y_eval,
207
234
  x_eval,
208
235
  t_eval)
209
- return self._field_eval[field_key]
236
+ return self.field_eval[field_key]
210
237
 
211
238
  def evaluate_all_fields_at_nodes(self) -> dict[str,np.ndarray]:
212
- """_summary_
239
+ """Evaluates all physical fields at the node locations and nominal time
240
+ steps.
213
241
 
214
242
  Returns
215
243
  -------
216
244
  dict[str,np.ndarray]
217
- _description_
245
+ Dictionary keyed by the field name giving a numpy array with shape =
246
+ (n_coords,n_timesteps)
218
247
  """
219
- (x_eval,y_eval,t_eval) = fill_dims_2d(self._coords[:,0],
220
- self._coords[:,1],
221
- self._case_data.time_steps)
248
+ (x_eval,y_eval,t_eval) = fill_dims_2d(self.coords[:,0],
249
+ self.coords[:,1],
250
+ self.case_data.time_steps)
222
251
  eval_comps = dict()
223
- for kk in self._case_data.field_keys:
224
- eval_comps[kk] = self._field_lam_funcs[kk](y_eval,
252
+ for kk in self.case_data.field_keys:
253
+ eval_comps[kk] = self.field_lam_funcs[kk](y_eval,
225
254
  x_eval,
226
255
  t_eval)
227
- self._field_eval = eval_comps
228
- return self._field_eval
256
+ self.field_eval = eval_comps
257
+ return self.field_eval
229
258
 
230
259
 
231
260
  def generate_sim_data(self) -> mh.SimData:
232
- """_summary_
261
+ """Creates a SimData object using the analytic case geometry, mesh
262
+ parameters and the underlying physical fields.
233
263
 
234
264
  Returns
235
265
  -------
236
266
  mh.SimData
237
- _description_
267
+ SimData object built from the analytic case data.
238
268
  """
239
269
  sim_data = mh.SimData()
240
270
  sim_data.num_spat_dims = 2
241
- sim_data.time = self._case_data.time_steps
242
- sim_data.coords = self._coords
243
- sim_data.connect = {'connect1': self._connect}
271
+ sim_data.time = self.case_data.time_steps
272
+ sim_data.coords = self.coords
273
+ sim_data.connect = {'connect1': self.connect}
244
274
 
245
- if not self._field_eval:
275
+ if not self.field_eval:
246
276
  self.evaluate_all_fields_at_nodes()
247
- sim_data.node_vars = self._field_eval
277
+ sim_data.node_vars = self.field_eval
248
278
 
249
279
  return sim_data
250
280
 
@@ -253,33 +283,36 @@ class AnalyticSimDataGenerator:
253
283
  field_key: str | None = None,
254
284
  time_step: int = -1
255
285
  ) -> tuple[np.ndarray,np.ndarray,np.ndarray]:
256
- """_summary_
286
+ """Creates a visualisation grid for plotting heatmaps of the specified
287
+ analytic field using matplotlib.
257
288
 
258
289
  Parameters
259
290
  ----------
260
291
  field_key : str | None, optional
261
- _description_, by default None
292
+ String key for the field to be visualised, by default None. If None
293
+ then the first field key is used.
262
294
  time_step : int, optional
263
- _description_, by default -1
295
+ Time step at which to extract the field to be plotted, by default -1
264
296
 
265
297
  Returns
266
298
  -------
267
299
  tuple[np.ndarray,np.ndarray,np.ndarray]
268
- _description_
300
+ Tuple containing the 2D grid of x coordinates, grid of y coordinates
301
+ and a grid of field evaluations.
269
302
  """
270
303
  if field_key is None:
271
- field_key = self._case_data.field_keys[0]
304
+ field_key = self.case_data.field_keys[0]
272
305
 
273
- grid_shape = (self._case_data.num_elem_y+1,
274
- self._case_data.num_elem_x+1)
306
+ grid_shape = (self.case_data.num_elem_y+1,
307
+ self.case_data.num_elem_x+1)
275
308
 
276
- grid_x = np.atleast_2d(self._coords[:,0]).T.reshape(grid_shape)
277
- grid_y = np.atleast_2d(self._coords[:,1]).T.reshape(grid_shape)
309
+ grid_x = np.atleast_2d(self.coords[:,0]).T.reshape(grid_shape)
310
+ grid_y = np.atleast_2d(self.coords[:,1]).T.reshape(grid_shape)
278
311
 
279
- if not self._field_eval:
312
+ if not self.field_eval:
280
313
  self.evaluate_all_fields_at_nodes()
281
314
 
282
- scalar_grid = np.reshape(self._field_eval[field_key][:,time_step],grid_shape)
315
+ scalar_grid = np.reshape(self.field_eval[field_key][:,time_step],grid_shape)
283
316
 
284
317
  return (grid_x,grid_y,scalar_grid)
285
318
 
@@ -0,0 +1,15 @@
1
+ # ==============================================================================
2
+ # pyvale: the python validation engine
3
+ # License: MIT
4
+ # Copyright (C) 2025 The Computer Aided Validation Team
5
+ # ==============================================================================
6
+ from dataclasses import dataclass
7
+
8
+ #TODO: doctsrings
9
+
10
+ @dataclass(slots=True)
11
+ class CalibrationData:
12
+ angle_lims: tuple = (-10, 10)
13
+ angle_step: int = 5
14
+ plunge_lims: tuple = (-5, 5)
15
+ plunge_step: int = 5
@@ -0,0 +1,26 @@
1
+ # ==============================================================================
2
+ # pyvale: the python validation engine
3
+ # License: MIT
4
+ # Copyright (C) 2025 The Computer Aided Validation Team
5
+ # ==============================================================================
6
+ from dataclasses import dataclass
7
+ from enum import Enum
8
+ import numpy as np
9
+ from scipy.spatial.transform import Rotation
10
+
11
+ #TODO: docstrings
12
+
13
+ class BlenderLightType(Enum):
14
+ POINT = 'POINT'
15
+ SUN = 'SUN'
16
+ SPOT = 'SPOT'
17
+ AREA = 'AREA'
18
+
19
+ @dataclass(slots=True)
20
+ class BlenderLightData():
21
+ pos_world: np.ndarray
22
+ rot_world: Rotation
23
+ energy: int # NOTE: In Watts
24
+ type: BlenderLightType = BlenderLightType.POINT
25
+ shadow_soft_size: float = 1.5
26
+
@@ -0,0 +1,15 @@
1
+ # ==============================================================================
2
+ # pyvale: the python validation engine
3
+ # License: MIT
4
+ # Copyright (C) 2025 The Computer Aided Validation Team
5
+ # ==============================================================================
6
+ from dataclasses import dataclass
7
+
8
+ #TODO: docstrings
9
+
10
+ @dataclass(slots=True)
11
+ class BlenderMaterialData():
12
+ # TODO: Add other material properties here
13
+ roughness: float = 1.0
14
+ metallic: float = 0.0
15
+ interpolant: int = 'Cubic'
@@ -0,0 +1,30 @@
1
+ # ==============================================================================
2
+ # pyvale: the python validation engine
3
+ # License: MIT
4
+ # Copyright (C) 2025 The Computer Aided Validation Team
5
+ # ==============================================================================
6
+
7
+ from enum import Enum
8
+ from dataclasses import dataclass
9
+ from pathlib import Path
10
+ from pyvale.cameradata import CameraData
11
+ from pyvale.output import Outputs
12
+
13
+ #TODO: docstrings
14
+
15
+ class RenderEngine(Enum):
16
+ """Different render engines on Blender
17
+ """
18
+ CYCLES = "CYCLES"
19
+ EEVEE = "BLENDER_EEVEE_NEXT"
20
+ WORKBENCH = "BLENDER_WORKBENCH"
21
+
22
+ @dataclass(slots=True)
23
+ class RenderData:
24
+ cam_data: CameraData | tuple[CameraData, CameraData]
25
+ base_dir: Path = Outputs.base_dir
26
+ samples: int = 2
27
+ engine: RenderEngine = RenderEngine.CYCLES
28
+ max_bounces: int = 12
29
+ bit_size: int = 8
30
+ threads:int = 4