pyvale 2025.5.3__cp311-cp311-musllinux_1_2_i686.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 (174) hide show
  1. pyvale/__init__.py +89 -0
  2. pyvale/analyticmeshgen.py +102 -0
  3. pyvale/analyticsimdatafactory.py +91 -0
  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/camera.py +146 -0
  12. pyvale/cameradata.py +69 -0
  13. pyvale/cameradata2d.py +84 -0
  14. pyvale/camerastereo.py +217 -0
  15. pyvale/cameratools.py +522 -0
  16. pyvale/cython/rastercyth.c +32211 -0
  17. pyvale/cython/rastercyth.cpython-311-i386-linux-musl.so +0 -0
  18. pyvale/cython/rastercyth.py +640 -0
  19. pyvale/data/__init__.py +5 -0
  20. pyvale/data/cal_target.tiff +0 -0
  21. pyvale/data/case00_HEX20_out.e +0 -0
  22. pyvale/data/case00_HEX27_out.e +0 -0
  23. pyvale/data/case00_HEX8_out.e +0 -0
  24. pyvale/data/case00_TET10_out.e +0 -0
  25. pyvale/data/case00_TET14_out.e +0 -0
  26. pyvale/data/case00_TET4_out.e +0 -0
  27. pyvale/data/case13_out.e +0 -0
  28. pyvale/data/case16_out.e +0 -0
  29. pyvale/data/case17_out.e +0 -0
  30. pyvale/data/case18_1_out.e +0 -0
  31. pyvale/data/case18_2_out.e +0 -0
  32. pyvale/data/case18_3_out.e +0 -0
  33. pyvale/data/case25_out.e +0 -0
  34. pyvale/data/case26_out.e +0 -0
  35. pyvale/data/optspeckle_2464x2056px_spec5px_8bit_gblur1px.tiff +0 -0
  36. pyvale/dataset.py +325 -0
  37. pyvale/errorcalculator.py +109 -0
  38. pyvale/errordriftcalc.py +146 -0
  39. pyvale/errorintegrator.py +336 -0
  40. pyvale/errorrand.py +607 -0
  41. pyvale/errorsyscalib.py +134 -0
  42. pyvale/errorsysdep.py +327 -0
  43. pyvale/errorsysfield.py +414 -0
  44. pyvale/errorsysindep.py +808 -0
  45. pyvale/examples/__init__.py +5 -0
  46. pyvale/examples/basics/ex1_1_basicscalars_therm2d.py +131 -0
  47. pyvale/examples/basics/ex1_2_sensormodel_therm2d.py +158 -0
  48. pyvale/examples/basics/ex1_3_customsens_therm3d.py +216 -0
  49. pyvale/examples/basics/ex1_4_basicerrors_therm3d.py +153 -0
  50. pyvale/examples/basics/ex1_5_fielderrs_therm3d.py +168 -0
  51. pyvale/examples/basics/ex1_6_caliberrs_therm2d.py +133 -0
  52. pyvale/examples/basics/ex1_7_spatavg_therm2d.py +123 -0
  53. pyvale/examples/basics/ex2_1_basicvectors_disp2d.py +112 -0
  54. pyvale/examples/basics/ex2_2_vectorsens_disp2d.py +111 -0
  55. pyvale/examples/basics/ex2_3_sensangle_disp2d.py +139 -0
  56. pyvale/examples/basics/ex2_4_chainfielderrs_disp2d.py +196 -0
  57. pyvale/examples/basics/ex2_5_vectorfields3d_disp3d.py +109 -0
  58. pyvale/examples/basics/ex3_1_basictensors_strain2d.py +114 -0
  59. pyvale/examples/basics/ex3_2_tensorsens2d_strain2d.py +111 -0
  60. pyvale/examples/basics/ex3_3_tensorsens3d_strain3d.py +182 -0
  61. pyvale/examples/basics/ex4_1_expsim2d_thermmech2d.py +171 -0
  62. pyvale/examples/basics/ex4_2_expsim3d_thermmech3d.py +252 -0
  63. pyvale/examples/genanalyticdata/ex1_1_scalarvisualisation.py +35 -0
  64. pyvale/examples/genanalyticdata/ex1_2_scalarcasebuild.py +43 -0
  65. pyvale/examples/genanalyticdata/ex2_1_analyticsensors.py +80 -0
  66. pyvale/examples/imagedef2d/ex_imagedef2d_todisk.py +79 -0
  67. pyvale/examples/renderblender/ex1_1_blenderscene.py +121 -0
  68. pyvale/examples/renderblender/ex1_2_blenderdeformed.py +119 -0
  69. pyvale/examples/renderblender/ex2_1_stereoscene.py +128 -0
  70. pyvale/examples/renderblender/ex2_2_stereodeformed.py +131 -0
  71. pyvale/examples/renderblender/ex3_1_blendercalibration.py +120 -0
  72. pyvale/examples/renderrasterisation/ex_rastenp.py +153 -0
  73. pyvale/examples/renderrasterisation/ex_rastercyth_oneframe.py +218 -0
  74. pyvale/examples/renderrasterisation/ex_rastercyth_static_cypara.py +187 -0
  75. pyvale/examples/renderrasterisation/ex_rastercyth_static_pypara.py +190 -0
  76. pyvale/examples/visualisation/ex1_1_plot_traces.py +102 -0
  77. pyvale/examples/visualisation/ex2_1_animate_sim.py +89 -0
  78. pyvale/experimentsimulator.py +175 -0
  79. pyvale/field.py +128 -0
  80. pyvale/fieldconverter.py +351 -0
  81. pyvale/fieldsampler.py +111 -0
  82. pyvale/fieldscalar.py +166 -0
  83. pyvale/fieldtensor.py +218 -0
  84. pyvale/fieldtransform.py +388 -0
  85. pyvale/fieldvector.py +213 -0
  86. pyvale/generatorsrandom.py +505 -0
  87. pyvale/imagedef2d.py +569 -0
  88. pyvale/integratorfactory.py +240 -0
  89. pyvale/integratorquadrature.py +217 -0
  90. pyvale/integratorrectangle.py +165 -0
  91. pyvale/integratorspatial.py +89 -0
  92. pyvale/integratortype.py +43 -0
  93. pyvale/output.py +17 -0
  94. pyvale/pyvaleexceptions.py +11 -0
  95. pyvale/raster.py +31 -0
  96. pyvale/rastercy.py +77 -0
  97. pyvale/rasternp.py +603 -0
  98. pyvale/rendermesh.py +147 -0
  99. pyvale/sensorarray.py +178 -0
  100. pyvale/sensorarrayfactory.py +196 -0
  101. pyvale/sensorarraypoint.py +278 -0
  102. pyvale/sensordata.py +71 -0
  103. pyvale/sensordescriptor.py +213 -0
  104. pyvale/sensortools.py +142 -0
  105. pyvale/simcases/case00_HEX20.i +242 -0
  106. pyvale/simcases/case00_HEX27.i +242 -0
  107. pyvale/simcases/case00_HEX8.i +242 -0
  108. pyvale/simcases/case00_TET10.i +242 -0
  109. pyvale/simcases/case00_TET14.i +242 -0
  110. pyvale/simcases/case00_TET4.i +242 -0
  111. pyvale/simcases/case01.i +101 -0
  112. pyvale/simcases/case02.i +156 -0
  113. pyvale/simcases/case03.i +136 -0
  114. pyvale/simcases/case04.i +181 -0
  115. pyvale/simcases/case05.i +234 -0
  116. pyvale/simcases/case06.i +305 -0
  117. pyvale/simcases/case07.geo +135 -0
  118. pyvale/simcases/case07.i +87 -0
  119. pyvale/simcases/case08.geo +144 -0
  120. pyvale/simcases/case08.i +153 -0
  121. pyvale/simcases/case09.geo +204 -0
  122. pyvale/simcases/case09.i +87 -0
  123. pyvale/simcases/case10.geo +204 -0
  124. pyvale/simcases/case10.i +257 -0
  125. pyvale/simcases/case11.geo +337 -0
  126. pyvale/simcases/case11.i +147 -0
  127. pyvale/simcases/case12.geo +388 -0
  128. pyvale/simcases/case12.i +329 -0
  129. pyvale/simcases/case13.i +140 -0
  130. pyvale/simcases/case14.i +159 -0
  131. pyvale/simcases/case15.geo +337 -0
  132. pyvale/simcases/case15.i +150 -0
  133. pyvale/simcases/case16.geo +391 -0
  134. pyvale/simcases/case16.i +357 -0
  135. pyvale/simcases/case17.geo +135 -0
  136. pyvale/simcases/case17.i +144 -0
  137. pyvale/simcases/case18.i +254 -0
  138. pyvale/simcases/case18_1.i +254 -0
  139. pyvale/simcases/case18_2.i +254 -0
  140. pyvale/simcases/case18_3.i +254 -0
  141. pyvale/simcases/case19.geo +252 -0
  142. pyvale/simcases/case19.i +99 -0
  143. pyvale/simcases/case20.geo +252 -0
  144. pyvale/simcases/case20.i +250 -0
  145. pyvale/simcases/case21.geo +74 -0
  146. pyvale/simcases/case21.i +155 -0
  147. pyvale/simcases/case22.geo +82 -0
  148. pyvale/simcases/case22.i +140 -0
  149. pyvale/simcases/case23.geo +164 -0
  150. pyvale/simcases/case23.i +140 -0
  151. pyvale/simcases/case24.geo +79 -0
  152. pyvale/simcases/case24.i +123 -0
  153. pyvale/simcases/case25.geo +82 -0
  154. pyvale/simcases/case25.i +140 -0
  155. pyvale/simcases/case26.geo +166 -0
  156. pyvale/simcases/case26.i +140 -0
  157. pyvale/simcases/run_1case.py +61 -0
  158. pyvale/simcases/run_all_cases.py +69 -0
  159. pyvale/simcases/run_build_case.py +64 -0
  160. pyvale/simcases/run_example_cases.py +69 -0
  161. pyvale/simtools.py +67 -0
  162. pyvale/visualexpplotter.py +191 -0
  163. pyvale/visualimagedef.py +74 -0
  164. pyvale/visualimages.py +76 -0
  165. pyvale/visualopts.py +493 -0
  166. pyvale/visualsimanimator.py +111 -0
  167. pyvale/visualsimsensors.py +318 -0
  168. pyvale/visualtools.py +136 -0
  169. pyvale/visualtraceplotter.py +142 -0
  170. pyvale-2025.5.3.dist-info/METADATA +144 -0
  171. pyvale-2025.5.3.dist-info/RECORD +174 -0
  172. pyvale-2025.5.3.dist-info/WHEEL +5 -0
  173. pyvale-2025.5.3.dist-info/licenses/LICENSE +21 -0
  174. pyvale-2025.5.3.dist-info/top_level.txt +1 -0
pyvale/__init__.py ADDED
@@ -0,0 +1,89 @@
1
+ # ==============================================================================
2
+ # pyvale: the python validation engine
3
+ # License: MIT
4
+ # Copyright (C) 2025 The Computer Aided Validation Team
5
+ # ==============================================================================
6
+
7
+ """
8
+ `pyvale`: the python validation engine. Used to simulate experimental data from
9
+ an input multi-physics simulation by explicitly modelling sensors with realistic
10
+ uncertainties. Useful for experimental design, sensor placement optimisation,
11
+ testing simulation validation metrics and testing digital shadows/twins.
12
+ """
13
+
14
+ # NOTE: this simplifies and decouples how the user calls pyvale from the
15
+ # underlying project structure: the user should be able to use 'pyvale.'
16
+ # and access everything in one layer without multiple import dots
17
+
18
+
19
+ from pyvale.dataset import *
20
+
21
+ from pyvale.field import *
22
+ from pyvale.fieldscalar import *
23
+ from pyvale.fieldvector import *
24
+ from pyvale.fieldtensor import *
25
+ from pyvale.fieldconverter import *
26
+ from pyvale.fieldtransform import *
27
+
28
+ from pyvale.integratorspatial import *
29
+ from pyvale.integratorquadrature import *
30
+ from pyvale.integratorrectangle import *
31
+ from pyvale.integratorfactory import *
32
+
33
+ from pyvale.sensordescriptor import *
34
+ from pyvale.sensortools import *
35
+ from pyvale.sensorarray import *
36
+ from pyvale.sensorarrayfactory import *
37
+ from pyvale.sensorarraypoint import *
38
+ from pyvale.sensordata import *
39
+
40
+ from pyvale.camera import *
41
+ from pyvale.cameradata import *
42
+ from pyvale.cameradata2d import *
43
+ from pyvale.cameratools import *
44
+ from pyvale.camerastereo import *
45
+
46
+ import pyvale.cython.rastercyth as rastercyth
47
+ from pyvale.rastercy import *
48
+
49
+ from pyvale.rendermesh import *
50
+ from pyvale.rasternp import *
51
+
52
+ from pyvale.imagedef2d import *
53
+
54
+ from pyvale.errorintegrator import *
55
+ from pyvale.errorrand import *
56
+ from pyvale.errorsysindep import *
57
+ from pyvale.errorsysdep import *
58
+ from pyvale.errorsysfield import *
59
+ from pyvale.errorsyscalib import *
60
+ from pyvale.errordriftcalc import *
61
+
62
+ from pyvale.generatorsrandom import *
63
+
64
+ from pyvale.visualopts import *
65
+ from pyvale.visualtools import *
66
+ from pyvale.visualsimsensors import *
67
+ from pyvale.visualsimanimator import *
68
+ from pyvale.visualexpplotter import *
69
+ from pyvale.visualtraceplotter import *
70
+ from pyvale.visualimages import *
71
+ from pyvale.visualimagedef import *
72
+
73
+ from pyvale.analyticmeshgen import *
74
+ from pyvale.analyticsimdatagenerator import *
75
+ from pyvale.analyticsimdatafactory import *
76
+
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
+
@@ -0,0 +1,102 @@
1
+ #===============================================================================
2
+ # pyvale: the python validation engine
3
+ # License: MIT
4
+ # Copyright (C) 2025 The Computer Aided Validation Team
5
+ #===============================================================================
6
+
7
+ """
8
+ Analytic mesh creation tools for testing pyvale sensor simulation and
9
+ uncertainty quantification functionality with a known analytic function for the
10
+ scalar/vector/tensor field of interest.
11
+ """
12
+ import numpy as np
13
+
14
+
15
+ def rectangle_mesh_2d(leng_x: float,
16
+ leng_y: float,
17
+ n_elem_x: int,
18
+ n_elem_y: int) -> tuple[np.ndarray,np.ndarray]:
19
+ """Creates the nodal coordinates and element connectivity table for a simple
20
+ 2D quad mesh for a rectangular plate.
21
+
22
+ Parameters
23
+ ----------
24
+ leng_x : float
25
+ Length of the plate in the x direction.
26
+ leng_y : float
27
+ Length of the plate in the y direction.
28
+ n_elem_x : int
29
+ Number of elements along the x axis
30
+ n_elem_y : int
31
+ Number of elements along the y axis
32
+
33
+ Returns
34
+ -------
35
+ tuple[np.ndarray,np.ndarray]
36
+ The coordinates and connectivity table as numpy arrays. The coordinates
37
+ have shape=(n_nodes,coord[x,y,z]). The connectivity tables has shape=
38
+ (nodes_per_elem,num_elems).
39
+ """
40
+ n_elems = n_elem_x*n_elem_y
41
+ n_node_x = n_elem_x+1
42
+ n_node_y = n_elem_y+1
43
+ nodes_per_elem = 4
44
+
45
+ coord_x = np.linspace(0,leng_x,n_node_x)
46
+ coord_y = np.linspace(0,leng_y,n_node_y)
47
+ (coord_grid_x,coord_grid_y) = np.meshgrid(coord_x,coord_y)
48
+
49
+ coord_x = np.atleast_2d(coord_grid_x.flatten()).T
50
+ coord_y = np.atleast_2d(coord_grid_y.flatten()).T
51
+ coord_z = np.zeros_like(coord_x)
52
+ coords = np.hstack((coord_x,coord_y,coord_z))
53
+
54
+ connect = np.zeros((n_elems,nodes_per_elem)).astype(np.int64)
55
+ row = 1
56
+ nn = 0
57
+ for ee in range(n_elems):
58
+ nn += 1
59
+ if nn >= row*n_node_x:
60
+ row += 1
61
+ nn += 1
62
+
63
+ connect[ee,:] = np.array([nn,nn+1,nn+n_node_x+1,nn+n_node_x])
64
+ connect = connect.T
65
+
66
+ return (coords,connect)
67
+
68
+
69
+ def fill_dims_2d(coord_x: np.ndarray,
70
+ coord_y: np.ndarray,
71
+ time: np.ndarray) -> tuple[np.ndarray,np.ndarray,np.ndarray]:
72
+ """Helper function to generate 2D filled arrays tih consistent dimensions
73
+ for array based maths operations. Takes 1D input vectors for the x, y and
74
+ time dimensions and returns 2D arrays with shape=(num_coords,num_timesteps).
75
+ Useful for evaluating analytical functions in space and time.
76
+
77
+ Parameters
78
+ ----------
79
+ coord_x : np.ndarray
80
+ 1D flattened coordinate list for the x axis.
81
+ coord_y : np.ndarray
82
+ 1D flattened coordinate list for the y axis.
83
+ time : np.ndarray
84
+ 1D array of time steps.
85
+
86
+
87
+ Returns
88
+ -------
89
+ tuple[np.ndarray,np.ndarray,np.ndarray]
90
+ Filled 2D arrays with shape=(num_coords,num_timesteps) for the x, y and
91
+ time parameters respectively.
92
+ """
93
+ full_x = np.repeat(np.atleast_2d(coord_x).T,
94
+ time.shape[0],
95
+ axis=1)
96
+ full_y = np.repeat(np.atleast_2d(coord_y).T,
97
+ time.shape[0],
98
+ axis=1)
99
+ full_time = np.repeat(np.atleast_2d(time),
100
+ coord_x.shape[0],
101
+ axis=0)
102
+ return (full_x,full_y,full_time)
@@ -0,0 +1,91 @@
1
+ # ==============================================================================
2
+ # pyvale: the python validation engine
3
+ # License: MIT
4
+ # Copyright (C) 2025 The Computer Aided Validation Team
5
+ # ==============================================================================
6
+
7
+ """
8
+ Helper functions and mini factory for building standard test meshes with
9
+ analytic functions for the physical fields.
10
+ """
11
+
12
+ import numpy as np
13
+ import sympy
14
+ import mooseherder as mh
15
+ from pyvale.analyticsimdatagenerator import (AnalyticData2D,
16
+ AnalyticSimDataGen)
17
+
18
+
19
+ def standard_case_2d() -> AnalyticData2D:
20
+ """Created the standard 2D analytic test case which is a plate with
21
+ dimensions 10x7.5 (x,y), number of elements 40x30 (x,y), and time steps of
22
+ 0 to 10 in increments of 1.
23
+
24
+ Returns
25
+ -------
26
+ AnalyticCaseData2D
27
+ _description_
28
+ """
29
+ case_data = AnalyticData2D()
30
+ case_data.length_x = 10.0
31
+ case_data.length_y = 7.5
32
+ n_elem_mult = 10
33
+ case_data.num_elem_x = 4*n_elem_mult
34
+ case_data.num_elem_y = 3*n_elem_mult
35
+ case_data.time_steps = np.linspace(0.0,1.0,11)
36
+ return case_data
37
+
38
+
39
+ class AnalyticCaseFactory:
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.
43
+ """
44
+
45
+ @staticmethod
46
+ def scalar_linear_2d() -> tuple[mh.SimData,AnalyticSimDataGen]:
47
+ """_summary_
48
+
49
+ Returns
50
+ -------
51
+ tuple[mh.SimData,AnalyticSimDataGenerator]
52
+ _description_
53
+ """
54
+ case_data = standard_case_2d()
55
+ (sym_y,sym_x,sym_t) = sympy.symbols("y,x,t")
56
+ case_data.funcs_x = (20.0/case_data.length_x * sym_x,)
57
+ case_data.funcs_y = (10.0/case_data.length_y * sym_y,)
58
+ case_data.funcs_t = (sym_t,)
59
+ case_data.offsets_space = (20.0,)
60
+ case_data.offsets_time = (0.0,)
61
+
62
+ data_gen = AnalyticSimDataGen(case_data)
63
+
64
+ sim_data = data_gen.generate_sim_data()
65
+
66
+ return (sim_data,data_gen)
67
+
68
+ @staticmethod
69
+ def scalar_quadratic_2d() -> tuple[mh.SimData,AnalyticSimDataGen]:
70
+ """_summary_
71
+
72
+ Returns
73
+ -------
74
+ tuple[mh.SimData,AnalyticSimDataGenerator]
75
+ _description_
76
+ """
77
+ case_data = standard_case_2d()
78
+ (sym_y,sym_x,sym_t) = sympy.symbols("y,x,t")
79
+ case_data.funcs_x = (sym_x*(sym_x - case_data.length_x),)
80
+ case_data.funcs_y = (sym_y*(sym_y - case_data.length_y),)
81
+ case_data.funcs_t = (sym_t,)
82
+
83
+ data_gen = AnalyticSimDataGen(case_data)
84
+
85
+ sim_data = data_gen.generate_sim_data()
86
+
87
+ return (sim_data,data_gen)
88
+
89
+
90
+
91
+
@@ -0,0 +1,323 @@
1
+ #===============================================================================
2
+ # pyvale: the python validation engine
3
+ # License: MIT
4
+ # Copyright (C) 2025 The Computer Aided Validation Team
5
+ #===============================================================================
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
+
12
+ from dataclasses import dataclass
13
+ import numpy as np
14
+ import sympy
15
+ import mooseherder as mh
16
+ from pyvale.analyticmeshgen import rectangle_mesh_2d, fill_dims_2d
17
+
18
+
19
+ @dataclass(slots=True)
20
+ class AnalyticData2D:
21
+ """Dataclass for describing a 2D analytic test case for pyvale sensor
22
+ simulation. Includes information about the geometry, the mesh and the
23
+ analytic functions used to generate the field data.
24
+ """
25
+
26
+ length_x: float = 10.0
27
+ """Length of the test case geometry in the X direction in length units.
28
+ Defaults to 10.0.
29
+ """
30
+
31
+ length_y: float = 7.5
32
+ """Length of the test case geometry in the Y direction in length units.
33
+ Defaults to 7.5.
34
+ """
35
+
36
+ num_elem_x: int = 4
37
+ """Number of elements in the mesh in the X direction. Defaults to 4.
38
+ """
39
+
40
+ num_elem_y: int = 3
41
+ """Number of elements in the mesh in the Y direction. Defaults to 3.
42
+ """
43
+
44
+ time_steps: np.ndarray | None = None
45
+ """1D array of time steps for the analytic test case. Defaults to None which
46
+ is for a test case that only has spatially varying functions.
47
+ """
48
+
49
+ field_keys: tuple[str,...] = ('scalar',)
50
+ """Keys used to describe the field of interest. For a scalar field there is
51
+ only a single key. For a vector field 2 keys are required in 2D (xx,yy). For
52
+ a tensor field 3 keys are required for 2D (xx,yy,xy). Defaults to a single
53
+ key for a scalar field: ("scalar",).
54
+ """
55
+
56
+ funcs_x: tuple[sympy.Expr,...] | None = None
57
+ """Analytic functions describing the field variation as a function of the x
58
+ coordinate. This tuple should have the same number of functions as the
59
+ number of field keys. Analytic functions in x, y and t are multiplied
60
+ together so setting a function to a constant of 1 will have no effect.
61
+ """
62
+ funcs_y: tuple[sympy.Expr,...] | None = None
63
+ """Analytic functions describing the field variation as a function of the y
64
+ coordinate. This tuple should have the same number of functions as the
65
+ number of field keys. Analytic functions in x, y and t are multiplied
66
+ together so setting a function to a constant of 1 will have no effect.
67
+ """
68
+
69
+ funcs_t: tuple[sympy.Expr,...] | None = None
70
+ """Analytic functions describing the field variation as a function of time
71
+ This tuple should have the same number of functions as the number of field
72
+ keys. Analytic functions in x, y and t are multiplied together so setting a
73
+ function to a constant of 1 will have no effect.
74
+ """
75
+
76
+ symbols: tuple[sympy.Symbol,...] = (sympy.Symbol("y"),
77
+ sympy.Symbol("x"),
78
+ sympy.Symbol("t"))
79
+ """Sympy symbols describing the relevant dimensions of the problem. For 2D
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.
82
+ """
83
+
84
+ offsets_space: tuple[float,...] = (0.0,)
85
+ """Constants which are added to the physical field functions in each spatial
86
+ dimensions.
87
+ """
88
+
89
+ offsets_time: tuple[float,...] = (0.0,)
90
+ """Constant which is added to the physical field function in time.
91
+ """
92
+
93
+ nodes_per_elem: int = 4
94
+ """Number of nodes per element. Currently only rectangular meshes and with
95
+ 4 nodes per element are supported. Defaults to 4.
96
+ """
97
+
98
+
99
+ class AnalyticSimDataGen:
100
+ """Class for generating analytic field data as a `SimData` object to test
101
+ the sensor simulation functionality of pyvale. Provides tools to evaluate
102
+ the analytic field functions at a given spatial coordinate/time to check
103
+ against pyvale interpolation functions. Currently only support 2D cases.
104
+ """
105
+
106
+ __slots__ = ("case_data","coords","connect","field_sym_funcs",
107
+ "field_lam_funcs","field_eval")
108
+
109
+ def __init__(self, case_data: AnalyticData2D
110
+ ) -> None:
111
+ """
112
+ Parameters
113
+ ----------
114
+ case_data : AnalyticCaseData2D
115
+ Data class containing the parameters required to create the analytic
116
+ mesh and the underlying physical field functions.
117
+ """
118
+ self.case_data = case_data
119
+ (self.coords,self.connect) = rectangle_mesh_2d(case_data.length_x,
120
+ case_data.length_y,
121
+ case_data.num_elem_x,
122
+ case_data.num_elem_y)
123
+
124
+ self.field_sym_funcs = dict()
125
+ self.field_lam_funcs = dict()
126
+ for ii,kk in enumerate(case_data.field_keys):
127
+ self.field_sym_funcs[kk] = ((case_data.funcs_x[ii] *
128
+ case_data.funcs_y[ii] +
129
+ case_data.offsets_space[ii]) *
130
+ (case_data.funcs_t[ii] +
131
+ case_data.offsets_time[ii]))
132
+
133
+ self.field_lam_funcs[kk] = sympy.lambdify(case_data.symbols,
134
+ self.field_sym_funcs[kk],
135
+ 'numpy')
136
+ self.field_eval = dict()
137
+
138
+
139
+ def evaluate_field_truth(self,
140
+ field_key: str,
141
+ coords: np.ndarray,
142
+ time_steps: np.ndarray | None = None) -> np.ndarray:
143
+ """Calculates the 'truth' from the analytical functions describing the
144
+ physical fields at the specified coordinates and time steps.
145
+
146
+ Parameters
147
+ ----------
148
+ field_key : str
149
+ Key for the underlying physical field.
150
+ coords : np.ndarray
151
+ Coordinates at which to evaluate the analytic physical field. shape
152
+ =(n_coords,coord[x,y,z])
153
+ time_steps : np.ndarray | None, optional
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.
157
+
158
+ Returns
159
+ -------
160
+ np.ndarray
161
+ Array of analytic field evaluations with shape = (n_coords,
162
+ n_time_steps)
163
+ """
164
+ if time_steps is None:
165
+ time_steps = self.case_data.time_steps
166
+
167
+ (x_eval,y_eval,t_eval) = fill_dims_2d(coords[:,0],
168
+ coords[:,1],
169
+ time_steps)
170
+
171
+ field_vals = self.field_lam_funcs[field_key](y_eval,
172
+ x_eval,
173
+ t_eval)
174
+ return field_vals
175
+
176
+
177
+ def evaluate_all_fields_truth(self,
178
+ coords: np.ndarray,
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.
183
+
184
+ Parameters
185
+ ----------
186
+ coords : np.ndarray
187
+ Coordinates at which to evaluate the analytic physical field. shape
188
+ =(n_coords,coord[x,y,z])
189
+ time_steps : np.ndarray | None, optional
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.
193
+
194
+ Returns
195
+ -------
196
+ dict[str,np.ndarray]
197
+ Dictionary keyed by the field name giving a numpy array with shape =
198
+ (n_coords,n_timesteps)
199
+ """
200
+ if time_steps is None:
201
+ time_steps = self.case_data.time_steps
202
+
203
+ (x_eval,y_eval,t_eval) = fill_dims_2d(coords[:,0],
204
+ coords[:,1],
205
+ time_steps)
206
+
207
+ eval_comps = dict()
208
+ for kk in self.case_data.field_keys:
209
+ eval_comps[kk] = self.field_lam_funcs[kk](y_eval,
210
+ x_eval,
211
+ t_eval)
212
+ return eval_comps
213
+
214
+
215
+ def evaluate_field_at_nodes(self, field_key: str) -> np.ndarray:
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.
223
+
224
+ Returns
225
+ -------
226
+ np.ndarray
227
+ Array of field evaluations with shape=(n_nodes,n_timesteps)
228
+ """
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,
234
+ x_eval,
235
+ t_eval)
236
+ return self.field_eval[field_key]
237
+
238
+ def evaluate_all_fields_at_nodes(self) -> dict[str,np.ndarray]:
239
+ """Evaluates all physical fields at the node locations and nominal time
240
+ steps.
241
+
242
+ Returns
243
+ -------
244
+ dict[str,np.ndarray]
245
+ Dictionary keyed by the field name giving a numpy array with shape =
246
+ (n_coords,n_timesteps)
247
+ """
248
+ (x_eval,y_eval,t_eval) = fill_dims_2d(self.coords[:,0],
249
+ self.coords[:,1],
250
+ self.case_data.time_steps)
251
+ eval_comps = dict()
252
+ for kk in self.case_data.field_keys:
253
+ eval_comps[kk] = self.field_lam_funcs[kk](y_eval,
254
+ x_eval,
255
+ t_eval)
256
+ self.field_eval = eval_comps
257
+ return self.field_eval
258
+
259
+
260
+ def generate_sim_data(self) -> mh.SimData:
261
+ """Creates a SimData object using the analytic case geometry, mesh
262
+ parameters and the underlying physical fields.
263
+
264
+ Returns
265
+ -------
266
+ mh.SimData
267
+ SimData object built from the analytic case data.
268
+ """
269
+ sim_data = mh.SimData()
270
+ sim_data.num_spat_dims = 2
271
+ sim_data.time = self.case_data.time_steps
272
+ sim_data.coords = self.coords
273
+ sim_data.connect = {'connect1': self.connect}
274
+
275
+ if not self.field_eval:
276
+ self.evaluate_all_fields_at_nodes()
277
+ sim_data.node_vars = self.field_eval
278
+
279
+ return sim_data
280
+
281
+
282
+ def get_visualisation_grid(self,
283
+ field_key: str | None = None,
284
+ time_step: int = -1
285
+ ) -> tuple[np.ndarray,np.ndarray,np.ndarray]:
286
+ """Creates a visualisation grid for plotting heatmaps of the specified
287
+ analytic field using matplotlib.
288
+
289
+ Parameters
290
+ ----------
291
+ field_key : str | None, optional
292
+ String key for the field to be visualised, by default None. If None
293
+ then the first field key is used.
294
+ time_step : int, optional
295
+ Time step at which to extract the field to be plotted, by default -1
296
+
297
+ Returns
298
+ -------
299
+ tuple[np.ndarray,np.ndarray,np.ndarray]
300
+ Tuple containing the 2D grid of x coordinates, grid of y coordinates
301
+ and a grid of field evaluations.
302
+ """
303
+ if field_key is None:
304
+ field_key = self.case_data.field_keys[0]
305
+
306
+ grid_shape = (self.case_data.num_elem_y+1,
307
+ self.case_data.num_elem_x+1)
308
+
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)
311
+
312
+ if not self.field_eval:
313
+ self.evaluate_all_fields_at_nodes()
314
+
315
+ scalar_grid = np.reshape(self.field_eval[field_key][:,time_step],grid_shape)
316
+
317
+ return (grid_x,grid_y,scalar_grid)
318
+
319
+
320
+
321
+
322
+
323
+
@@ -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