pyvale 2025.7.2__cp311-cp311-musllinux_1_2_i686.whl → 2025.8.1__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 (176) hide show
  1. pyvale/__init__.py +12 -92
  2. pyvale/blender/__init__.py +23 -0
  3. pyvale/{pyvaleexceptions.py → blender/blenderexceptions.py} +0 -3
  4. pyvale/{blenderlightdata.py → blender/blenderlightdata.py} +3 -3
  5. pyvale/{blendermaterialdata.py → blender/blendermaterialdata.py} +1 -1
  6. pyvale/{blenderrenderdata.py → blender/blenderrenderdata.py} +5 -3
  7. pyvale/{blenderscene.py → blender/blenderscene.py} +33 -30
  8. pyvale/{blendertools.py → blender/blendertools.py} +14 -10
  9. pyvale/dataset/__init__.py +7 -0
  10. pyvale/dataset/dataset.py +443 -0
  11. pyvale/dic/__init__.py +20 -0
  12. pyvale/{dic2d.py → dic/dic2d.py} +31 -36
  13. pyvale/dic/dic2dconv.py +6 -0
  14. pyvale/{dic2dcpp.cpython-311-i386-linux-musl.so → dic/dic2dcpp.cpython-311-i386-linux-musl.so} +0 -0
  15. pyvale/{dicdataimport.py → dic/dicdataimport.py} +8 -8
  16. pyvale/{dicregionofinterest.py → dic/dicregionofinterest.py} +1 -1
  17. pyvale/{dicresults.py → dic/dicresults.py} +1 -1
  18. pyvale/{dicstrain.py → dic/dicstrain.py} +9 -9
  19. pyvale/examples/basics/{ex1_1_basicscalars_therm2d.py → ex1a_basicscalars_therm2d.py} +12 -9
  20. pyvale/examples/basics/{ex1_2_sensormodel_therm2d.py → ex1b_sensormodel_therm2d.py} +17 -14
  21. pyvale/examples/basics/{ex1_3_customsens_therm3d.py → ex1c_customsens_therm3d.py} +27 -24
  22. pyvale/examples/basics/{ex1_4_basicerrors_therm3d.py → ex1d_basicerrors_therm3d.py} +32 -29
  23. pyvale/examples/basics/{ex1_5_fielderrs_therm3d.py → ex1e_fielderrs_therm3d.py} +19 -15
  24. pyvale/examples/basics/{ex1_6_caliberrs_therm2d.py → ex1f_caliberrs_therm2d.py} +20 -16
  25. pyvale/examples/basics/{ex1_7_spatavg_therm2d.py → ex1g_spatavg_therm2d.py} +19 -16
  26. pyvale/examples/basics/{ex2_1_basicvectors_disp2d.py → ex2a_basicvectors_disp2d.py} +13 -10
  27. pyvale/examples/basics/{ex2_2_vectorsens_disp2d.py → ex2b_vectorsens_disp2d.py} +19 -15
  28. pyvale/examples/basics/{ex2_3_sensangle_disp2d.py → ex2c_sensangle_disp2d.py} +21 -18
  29. pyvale/examples/basics/{ex2_4_chainfielderrs_disp2d.py → ex2d_chainfielderrs_disp2d.py} +31 -29
  30. pyvale/examples/basics/{ex2_5_vectorfields3d_disp3d.py → ex2e_vectorfields3d_disp3d.py} +21 -18
  31. pyvale/examples/basics/{ex3_1_basictensors_strain2d.py → ex3a_basictensors_strain2d.py} +16 -14
  32. pyvale/examples/basics/{ex3_2_tensorsens2d_strain2d.py → ex3b_tensorsens2d_strain2d.py} +17 -14
  33. pyvale/examples/basics/{ex3_3_tensorsens3d_strain3d.py → ex3c_tensorsens3d_strain3d.py} +25 -22
  34. pyvale/examples/basics/{ex4_1_expsim2d_thermmech2d.py → ex4a_expsim2d_thermmech2d.py} +17 -14
  35. pyvale/examples/basics/{ex4_2_expsim3d_thermmech3d.py → ex4b_expsim3d_thermmech3d.py} +37 -34
  36. pyvale/examples/basics/ex5_nomesh.py +24 -0
  37. pyvale/examples/dic/ex1_2_blenderdeformed.py +174 -0
  38. pyvale/examples/dic/ex1_region_of_interest.py +6 -3
  39. pyvale/examples/dic/ex2_plate_with_hole.py +21 -18
  40. pyvale/examples/dic/ex3_plate_with_hole_strain.py +8 -6
  41. pyvale/examples/dic/ex4_dic_blender.py +17 -15
  42. pyvale/examples/dic/ex5_dic_challenge.py +19 -14
  43. pyvale/examples/genanalyticdata/ex1_1_scalarvisualisation.py +16 -10
  44. pyvale/examples/genanalyticdata/ex1_2_scalarcasebuild.py +3 -3
  45. pyvale/examples/genanalyticdata/ex2_1_analyticsensors.py +29 -23
  46. pyvale/examples/genanalyticdata/ex2_2_analyticsensors_nomesh.py +67 -0
  47. pyvale/examples/imagedef2d/ex_imagedef2d_todisk.py +12 -9
  48. pyvale/examples/mooseherder/ex0_create_moose_config.py +65 -0
  49. pyvale/examples/mooseherder/ex1a_modify_moose_input.py +71 -0
  50. pyvale/examples/mooseherder/ex1b_modify_gmsh_input.py +69 -0
  51. pyvale/examples/mooseherder/ex2a_run_moose_once.py +80 -0
  52. pyvale/examples/mooseherder/ex2b_run_gmsh_once.py +64 -0
  53. pyvale/examples/mooseherder/ex2c_run_both_once.py +114 -0
  54. pyvale/examples/mooseherder/ex3_run_moose_seq_para.py +157 -0
  55. pyvale/examples/mooseherder/ex4_run_gmsh-moose_seq_para.py +176 -0
  56. pyvale/examples/mooseherder/ex5_run_moose_paramulti.py +136 -0
  57. pyvale/examples/mooseherder/ex6_read_moose_exodus.py +163 -0
  58. pyvale/examples/mooseherder/ex7a_read_moose_herd_results.py +153 -0
  59. pyvale/examples/mooseherder/ex7b_read_multi_herd_results.py +116 -0
  60. pyvale/examples/mooseherder/ex7c_read_multi_gmshmoose_results.py +127 -0
  61. pyvale/examples/mooseherder/ex7d_readconfig_multi_gmshmoose_results.py +143 -0
  62. pyvale/examples/mooseherder/ex8_read_existing_sweep_output.py +72 -0
  63. pyvale/examples/renderblender/ex1_1_blenderscene.py +24 -20
  64. pyvale/examples/renderblender/ex1_2_blenderdeformed.py +22 -18
  65. pyvale/examples/renderblender/ex2_1_stereoscene.py +36 -29
  66. pyvale/examples/renderblender/ex2_2_stereodeformed.py +26 -20
  67. pyvale/examples/renderblender/ex3_1_blendercalibration.py +24 -17
  68. pyvale/examples/renderrasterisation/ex_rastenp.py +14 -12
  69. pyvale/examples/renderrasterisation/ex_rastercyth_oneframe.py +14 -15
  70. pyvale/examples/renderrasterisation/ex_rastercyth_static_cypara.py +13 -11
  71. pyvale/examples/renderrasterisation/ex_rastercyth_static_pypara.py +13 -11
  72. pyvale/mooseherder/__init__.py +32 -0
  73. pyvale/mooseherder/directorymanager.py +416 -0
  74. pyvale/mooseherder/exodusreader.py +763 -0
  75. pyvale/mooseherder/gmshrunner.py +163 -0
  76. pyvale/mooseherder/inputmodifier.py +236 -0
  77. pyvale/mooseherder/mooseconfig.py +226 -0
  78. pyvale/mooseherder/mooseherd.py +527 -0
  79. pyvale/mooseherder/mooserunner.py +303 -0
  80. pyvale/mooseherder/outputreader.py +22 -0
  81. pyvale/mooseherder/simdata.py +92 -0
  82. pyvale/mooseherder/simrunner.py +31 -0
  83. pyvale/mooseherder/sweepreader.py +356 -0
  84. pyvale/mooseherder/sweeptools.py +76 -0
  85. pyvale/sensorsim/__init__.py +82 -0
  86. pyvale/{camera.py → sensorsim/camera.py} +7 -7
  87. pyvale/{camerasensor.py → sensorsim/camerasensor.py} +7 -7
  88. pyvale/{camerastereo.py → sensorsim/camerastereo.py} +2 -2
  89. pyvale/{cameratools.py → sensorsim/cameratools.py} +4 -4
  90. pyvale/{cython → sensorsim/cython}/rastercyth.c +596 -596
  91. pyvale/{cython → sensorsim/cython}/rastercyth.cpython-311-i386-linux-musl.so +0 -0
  92. pyvale/{cython → sensorsim/cython}/rastercyth.py +16 -17
  93. pyvale/{errorcalculator.py → sensorsim/errorcalculator.py} +1 -1
  94. pyvale/{errorintegrator.py → sensorsim/errorintegrator.py} +2 -2
  95. pyvale/{errorrand.py → sensorsim/errorrand.py} +4 -4
  96. pyvale/{errorsyscalib.py → sensorsim/errorsyscalib.py} +2 -2
  97. pyvale/{errorsysdep.py → sensorsim/errorsysdep.py} +2 -2
  98. pyvale/{errorsysfield.py → sensorsim/errorsysfield.py} +8 -8
  99. pyvale/{errorsysindep.py → sensorsim/errorsysindep.py} +3 -3
  100. pyvale/sensorsim/exceptions.py +8 -0
  101. pyvale/{experimentsimulator.py → sensorsim/experimentsimulator.py} +23 -3
  102. pyvale/{field.py → sensorsim/field.py} +1 -1
  103. pyvale/{fieldconverter.py → sensorsim/fieldconverter.py} +72 -19
  104. pyvale/sensorsim/fieldinterp.py +37 -0
  105. pyvale/sensorsim/fieldinterpmesh.py +124 -0
  106. pyvale/sensorsim/fieldinterppoints.py +55 -0
  107. pyvale/{fieldsampler.py → sensorsim/fieldsampler.py} +4 -4
  108. pyvale/{fieldscalar.py → sensorsim/fieldscalar.py} +28 -24
  109. pyvale/{fieldtensor.py → sensorsim/fieldtensor.py} +33 -31
  110. pyvale/{fieldvector.py → sensorsim/fieldvector.py} +33 -31
  111. pyvale/{imagedef2d.py → sensorsim/imagedef2d.py} +9 -5
  112. pyvale/{integratorfactory.py → sensorsim/integratorfactory.py} +6 -6
  113. pyvale/{integratorquadrature.py → sensorsim/integratorquadrature.py} +3 -3
  114. pyvale/{integratorrectangle.py → sensorsim/integratorrectangle.py} +3 -3
  115. pyvale/{integratorspatial.py → sensorsim/integratorspatial.py} +1 -1
  116. pyvale/{rastercy.py → sensorsim/rastercy.py} +5 -5
  117. pyvale/{rasternp.py → sensorsim/rasternp.py} +9 -9
  118. pyvale/{rasteropts.py → sensorsim/rasteropts.py} +1 -1
  119. pyvale/{renderer.py → sensorsim/renderer.py} +1 -1
  120. pyvale/{rendermesh.py → sensorsim/rendermesh.py} +5 -5
  121. pyvale/{renderscene.py → sensorsim/renderscene.py} +2 -2
  122. pyvale/{sensorarray.py → sensorsim/sensorarray.py} +1 -1
  123. pyvale/{sensorarrayfactory.py → sensorsim/sensorarrayfactory.py} +12 -12
  124. pyvale/{sensorarraypoint.py → sensorsim/sensorarraypoint.py} +10 -8
  125. pyvale/{sensordata.py → sensorsim/sensordata.py} +1 -1
  126. pyvale/{sensortools.py → sensorsim/sensortools.py} +2 -20
  127. pyvale/sensorsim/simtools.py +174 -0
  128. pyvale/{visualexpplotter.py → sensorsim/visualexpplotter.py} +3 -3
  129. pyvale/{visualimages.py → sensorsim/visualimages.py} +2 -2
  130. pyvale/{visualsimanimator.py → sensorsim/visualsimanimator.py} +4 -4
  131. pyvale/{visualsimplotter.py → sensorsim/visualsimplotter.py} +5 -5
  132. pyvale/{visualsimsensors.py → sensorsim/visualsimsensors.py} +12 -12
  133. pyvale/{visualtools.py → sensorsim/visualtools.py} +1 -1
  134. pyvale/{visualtraceplotter.py → sensorsim/visualtraceplotter.py} +2 -2
  135. pyvale/simcases/case17.geo +3 -0
  136. pyvale/simcases/case17.i +4 -4
  137. pyvale/simcases/run_1case.py +1 -9
  138. pyvale/simcases/run_all_cases.py +1 -1
  139. pyvale/simcases/run_build_case.py +1 -1
  140. pyvale/simcases/run_example_cases.py +1 -1
  141. pyvale/verif/__init__.py +12 -0
  142. pyvale/{analyticsimdatafactory.py → verif/analyticsimdatafactory.py} +2 -2
  143. pyvale/{analyticsimdatagenerator.py → verif/analyticsimdatagenerator.py} +2 -2
  144. pyvale/verif/psens.py +125 -0
  145. pyvale/verif/psensconst.py +18 -0
  146. pyvale/verif/psensmech.py +227 -0
  147. pyvale/verif/psensmultiphys.py +187 -0
  148. pyvale/verif/psensscalar.py +347 -0
  149. pyvale/verif/psenstensor.py +123 -0
  150. pyvale/verif/psensvector.py +116 -0
  151. {pyvale-2025.7.2.dist-info → pyvale-2025.8.1.dist-info}/METADATA +6 -7
  152. pyvale-2025.8.1.dist-info/RECORD +263 -0
  153. pyvale/dataset.py +0 -415
  154. pyvale/simtools.py +0 -67
  155. pyvale-2025.7.2.dist-info/RECORD +0 -215
  156. /pyvale/{blendercalibrationdata.py → blender/blendercalibrationdata.py} +0 -0
  157. /pyvale/{dicchecks.py → dic/dicchecks.py} +0 -0
  158. /pyvale/{dicspecklegenerator.py → dic/dicspecklegenerator.py} +0 -0
  159. /pyvale/{dicspecklequality.py → dic/dicspecklequality.py} +0 -0
  160. /pyvale/{dicstrainresults.py → dic/dicstrainresults.py} +0 -0
  161. /pyvale/{cameradata.py → sensorsim/cameradata.py} +0 -0
  162. /pyvale/{cameradata2d.py → sensorsim/cameradata2d.py} +0 -0
  163. /pyvale/{errordriftcalc.py → sensorsim/errordriftcalc.py} +0 -0
  164. /pyvale/{fieldtransform.py → sensorsim/fieldtransform.py} +0 -0
  165. /pyvale/{generatorsrandom.py → sensorsim/generatorsrandom.py} +0 -0
  166. /pyvale/{imagetools.py → sensorsim/imagetools.py} +0 -0
  167. /pyvale/{integratortype.py → sensorsim/integratortype.py} +0 -0
  168. /pyvale/{output.py → sensorsim/output.py} +0 -0
  169. /pyvale/{raster.py → sensorsim/raster.py} +0 -0
  170. /pyvale/{sensordescriptor.py → sensorsim/sensordescriptor.py} +0 -0
  171. /pyvale/{visualimagedef.py → sensorsim/visualimagedef.py} +0 -0
  172. /pyvale/{visualopts.py → sensorsim/visualopts.py} +0 -0
  173. /pyvale/{analyticmeshgen.py → verif/analyticmeshgen.py} +0 -0
  174. {pyvale-2025.7.2.dist-info → pyvale-2025.8.1.dist-info}/WHEEL +0 -0
  175. {pyvale-2025.7.2.dist-info → pyvale-2025.8.1.dist-info}/licenses/LICENSE +0 -0
  176. {pyvale-2025.7.2.dist-info → pyvale-2025.8.1.dist-info}/top_level.txt +0 -0
@@ -12,7 +12,7 @@ import numpy as np
12
12
  import cython
13
13
  #from cython.parallel import prange, parallel, threadid
14
14
  from cython.cimports.libc.math import floor, ceil
15
- from pyvale.cameradata import CameraData
15
+ from pyvale.sensorsim.cameradata import CameraData
16
16
 
17
17
  # NOTE: This module is a feature under developement.
18
18
 
@@ -382,23 +382,22 @@ def raster_static_frame(coords: cython.double[:,:],
382
382
 
383
383
 
384
384
  #---------------------------------------------------------------------------
385
-
386
385
  elems_in_image: cython.size_t = _raster_frame(coords[:,:],
387
- connect[:,:],
388
- fields_to_render[:,:],
389
- world_to_cam_mat[:,:],
390
- pixels_num[:],
391
- image_dims[:],
392
- image_dist,
393
- sub_samp,
394
- image_buff_avg[:,:,:],
395
- depth_buff_avg[:,:],
396
- image_buff_subpx[:,:,:],
397
- depth_buff_subpx[:,:],
398
- nodes_raster_buff[:,:],
399
- field_raster_buff[:],
400
- px_coord_buff[:],
401
- weights_buff[:])
386
+ connect[:,:],
387
+ fields_to_render[:,:],
388
+ world_to_cam_mat[:,:],
389
+ pixels_num[:],
390
+ image_dims[:],
391
+ image_dist,
392
+ sub_samp,
393
+ image_buff_avg[:,:,:],
394
+ depth_buff_avg[:,:],
395
+ image_buff_subpx[:,:,:],
396
+ depth_buff_subpx[:,:],
397
+ nodes_raster_buff[:,:],
398
+ field_raster_buff[:],
399
+ px_coord_buff[:],
400
+ weights_buff[:])
402
401
 
403
402
  return (image_buff_avg_np,depth_buff_avg_np,elems_in_image)
404
403
 
@@ -7,7 +7,7 @@
7
7
  import enum
8
8
  from abc import ABC, abstractmethod
9
9
  import numpy as np
10
- from pyvale.sensordata import SensorData
10
+ from pyvale.sensorsim.sensordata import SensorData
11
11
 
12
12
 
13
13
  class EErrType(enum.Enum):
@@ -7,10 +7,10 @@
7
7
  import copy
8
8
  from dataclasses import dataclass
9
9
  import numpy as np
10
- from pyvale.errorcalculator import (IErrCalculator,
10
+ from pyvale.sensorsim.errorcalculator import (IErrCalculator,
11
11
  EErrType,
12
12
  EErrDep)
13
- from pyvale.sensordata import SensorData
13
+ from pyvale.sensorsim.sensordata import SensorData
14
14
 
15
15
 
16
16
  @dataclass(slots=True)
@@ -5,11 +5,11 @@
5
5
  # ==============================================================================
6
6
 
7
7
  import numpy as np
8
- from pyvale.sensordata import SensorData
9
- from pyvale.errorcalculator import (IErrCalculator,
8
+ from pyvale.sensorsim.sensordata import SensorData
9
+ from pyvale.sensorsim.errorcalculator import (IErrCalculator,
10
10
  EErrType,
11
11
  EErrDep)
12
- from pyvale.generatorsrandom import IGenRandom
12
+ from pyvale.sensorsim.generatorsrandom import IGenRandom
13
13
 
14
14
 
15
15
  class ErrRandUnif(IErrCalculator):
@@ -428,7 +428,7 @@ class ErrRandNormPercent(IErrCalculator):
428
428
  return (err_basis*self._std*norm_rand,sens_data)
429
429
 
430
430
 
431
- class ErrRandGenerator(IErrCalculator):
431
+ class ErrRandGen(IErrCalculator):
432
432
  """Sensor random error calculator based on sampling a user specified random
433
433
  number generator implementing the `IGeneratorRandom` interface.
434
434
 
@@ -6,10 +6,10 @@
6
6
 
7
7
  from typing import Callable
8
8
  import numpy as np
9
- from pyvale.errorcalculator import (IErrCalculator,
9
+ from pyvale.sensorsim.errorcalculator import (IErrCalculator,
10
10
  EErrType,
11
11
  EErrDep)
12
- from pyvale.sensordata import SensorData
12
+ from pyvale.sensorsim.sensordata import SensorData
13
13
 
14
14
  # TODO: add option to use Newton's method for function inversion instead of a
15
15
  # cal table.
@@ -7,8 +7,8 @@
7
7
  import enum
8
8
  from typing import Callable
9
9
  import numpy as np
10
- from pyvale.sensordata import SensorData
11
- from pyvale.errorcalculator import (IErrCalculator,
10
+ from pyvale.sensorsim.sensordata import SensorData
11
+ from pyvale.sensorsim.errorcalculator import (IErrCalculator,
12
12
  EErrType,
13
13
  EErrDep)
14
14
 
@@ -9,15 +9,15 @@ from dataclasses import dataclass
9
9
  import numpy as np
10
10
  from scipy.spatial.transform import Rotation
11
11
 
12
- from pyvale.field import IField
13
- from pyvale.fieldsampler import sample_field_with_sensor_data
14
- from pyvale.sensordata import SensorData
15
- from pyvale.integratortype import EIntSpatialType
16
- from pyvale.errorcalculator import (IErrCalculator,
12
+ from pyvale.sensorsim.field import IField
13
+ from pyvale.sensorsim.fieldsampler import sample_field_with_sensor_data
14
+ from pyvale.sensorsim.sensordata import SensorData
15
+ from pyvale.sensorsim.integratortype import EIntSpatialType
16
+ from pyvale.sensorsim.errorcalculator import (IErrCalculator,
17
17
  EErrType,
18
18
  EErrDep)
19
- from pyvale.errordriftcalc import IDriftCalculator
20
- from pyvale.generatorsrandom import IGenRandom
19
+ from pyvale.sensorsim.errordriftcalc import IDriftCalculator
20
+ from pyvale.sensorsim.generatorsrandom import IGenRandom
21
21
 
22
22
  # TODO:
23
23
  # - Implement different perturbed sampling times for each sensor or allow all
@@ -402,7 +402,7 @@ def _perturb_sensor_angles(n_sensors: int,
402
402
  for jj,rand_ang in enumerate(rand_ang_zyx): # loop over components
403
403
  if rand_ang is not None:
404
404
  sensor_rot_angs[jj] = sensor_rot_angs[jj] + \
405
- rand_ang.generate(shape=1)
405
+ rand_ang.generate(shape=(1,))[0]
406
406
 
407
407
  if angle_loc_zyx is not None:
408
408
  # No rotation about locked axes using mask
@@ -5,11 +5,11 @@
5
5
  # ==============================================================================
6
6
 
7
7
  import numpy as np
8
- from pyvale.errorcalculator import (IErrCalculator,
8
+ from pyvale.sensorsim.errorcalculator import (IErrCalculator,
9
9
  EErrType,
10
10
  EErrDep)
11
- from pyvale.generatorsrandom import IGenRandom
12
- from pyvale.sensordata import SensorData
11
+ from pyvale.sensorsim.generatorsrandom import IGenRandom
12
+ from pyvale.sensorsim.sensordata import SensorData
13
13
 
14
14
 
15
15
  class ErrSysOffset(IErrCalculator):
@@ -0,0 +1,8 @@
1
+ # ==============================================================================
2
+ # pyvale: the python validation engine
3
+ # License: MIT
4
+ # Copyright (C) 2025 The Computer Aided Validation Team
5
+ # ==============================================================================
6
+
7
+ class VisError(Exception):
8
+ pass
@@ -11,8 +11,8 @@ of input simulation cases and sensor arrays.
11
11
 
12
12
  from dataclasses import dataclass
13
13
  import numpy as np
14
- import mooseherder as mh
15
- from pyvale.sensorarray import ISensorArray
14
+ import pyvale.mooseherder as mh
15
+ from pyvale.sensorsim.sensorarray import ISensorArray
16
16
 
17
17
 
18
18
  @dataclass(slots=True)
@@ -104,6 +104,26 @@ class ExperimentSimulator:
104
104
  self._exp_data = None
105
105
  self._exp_stats = None
106
106
 
107
+ def get_sim_list(self) -> list[mh.SimData]:
108
+ """Gets the list of simulations to run simulated experiments for.
109
+
110
+ Returns
111
+ -------
112
+ list[mh.SimData]
113
+ List of simulation data objects.
114
+ """
115
+ return self._sim_list
116
+
117
+ def get_sensor_arrays(self) -> list[ISensorArray]:
118
+ """Gets the sensor array list for this experiment.
119
+
120
+ Returns
121
+ -------
122
+ list[ISensorArray]
123
+ List of sensor arrays for the simulated experiment.
124
+ """
125
+ return self._sensor_arrays
126
+
107
127
  def run_experiments(self) -> list[np.ndarray]:
108
128
  """Runs the specified number of virtual experiments over the number of
109
129
  input simulation cases and virtual sensor arrays.
@@ -122,7 +142,7 @@ class ExperimentSimulator:
122
142
 
123
143
  for ii,aa in enumerate(self._sensor_arrays):
124
144
  meas_array = np.zeros((n_sims,self._num_exp_per_sim)+
125
- aa.get_measurement_shape())
145
+ aa.get_measurement_shape())
126
146
 
127
147
  for jj,ss in enumerate(self._sim_list):
128
148
  aa.get_field().set_sim_data(ss)
@@ -8,7 +8,7 @@ from abc import ABC, abstractmethod
8
8
  import numpy as np
9
9
  from scipy.spatial.transform import Rotation
10
10
  import pyvista as pv
11
- import mooseherder as mh
11
+ import pyvale.mooseherder as mh
12
12
 
13
13
 
14
14
  class IField(ABC):
@@ -12,14 +12,15 @@ compatible with the underlying machinery of pyvale.
12
12
  import numpy as np
13
13
  import pyvista as pv
14
14
  from pyvista import CellType
15
- import mooseherder as mh
15
+ import pyvale.mooseherder as mh
16
16
 
17
- def simdata_to_pyvista(sim_data: mh.SimData,
18
- components: tuple[str,...] | None,
19
- elem_dims: int
20
- ) -> tuple[pv.UnstructuredGrid,pv.UnstructuredGrid]:
17
+
18
+ def simdata_to_pyvista_interp(sim_data: mh.SimData,
19
+ components: tuple[str,...] | None,
20
+ elem_dims: int
21
+ ) -> pv.UnstructuredGrid:
21
22
  """Converts the mesh and field data in a `SimData` object into a pyvista
22
- UnstructuredGrid for sampling (interpolating) the data and visualisation.
23
+ UnstructuredGrid for interpolating the data.
23
24
 
24
25
  Parameters
25
26
  ----------
@@ -34,9 +35,67 @@ def simdata_to_pyvista(sim_data: mh.SimData,
34
35
 
35
36
  Returns
36
37
  -------
37
- tuple[pv.UnstructuredGrid,pv.UnstructuredGrid]
38
- The first UnstructuredGrid has the field components attached as dataset
39
- arrays. The second has no field data attached for visualisation.
38
+ pv.UnstructuredGrid
39
+ As pyvista grid with attached field data to allow for interpolation on
40
+ the mesh using the element shape functions.
41
+ """
42
+
43
+ pv_grid = _gen_pyvista_grid(sim_data,elem_dims)
44
+
45
+ if components is not None and sim_data.node_vars is not None:
46
+ for cc in components:
47
+ pv_grid[cc] = sim_data.node_vars[cc]
48
+
49
+ return pv_grid
50
+
51
+
52
+ def simdata_to_pyvista_vis(sim_data: mh.SimData,
53
+ elem_dims: int
54
+ ) -> pv.UnstructuredGrid | pv.PolyData:
55
+ """Converts the mesh and field data in a `SimData` object into a pyvista
56
+ UnstructuredGrid or PolyData object for visualisation.
57
+
58
+ Parameters
59
+ ----------
60
+ sim_data : mh.SimData
61
+ Object containing a mesh and associated field data from a simulation.
62
+ elem_dim : int
63
+ Number of spatial dimensions (2 or 3) used to determine the element
64
+ types in the mesh from the number of nodes per element. Set to the
65
+ dimensionality of the problem for point cloud data as 2D triangulation
66
+ will be much faster if possible.
67
+
68
+ Returns
69
+ -------
70
+ pv.UnstructuredGrid | pv.PolyData
71
+ A pyvista unstructured grid or poly data object that has no field data
72
+ attached for visualisation purposes.
73
+ """
74
+ if sim_data.connect is None:
75
+ return pv.PolyData(sim_data.coords)
76
+
77
+ return _gen_pyvista_grid(sim_data,elem_dims)
78
+
79
+
80
+
81
+ def _gen_pyvista_grid(sim_data: mh.SimData,
82
+ elem_dims: int) -> pv.UnstructuredGrid:
83
+ """Helper function for generating a blank pyvista unstructure grid mesh from
84
+ a SimData object.
85
+
86
+ Parameters
87
+ ----------
88
+ sim_data : mh.SimData
89
+ Object containing a mesh and associated field data from a simulation.
90
+ elem_dims : int
91
+ Number of spatial dimensions (2 or 3) used to determine the element
92
+ types in the mesh from the number of nodes per element.
93
+
94
+
95
+ Returns
96
+ -------
97
+ pv.UnstructuredGrid
98
+ A pyvista unstructured grid that has no field data attached.
40
99
  """
41
100
  flat_connect = np.array([],dtype=np.int64)
42
101
  cell_types = np.array([],dtype=np.int64)
@@ -65,13 +124,7 @@ def simdata_to_pyvista(sim_data: mh.SimData,
65
124
 
66
125
  points = sim_data.coords
67
126
  pv_grid = pv.UnstructuredGrid(cells, cell_types, points)
68
- pv_grid_vis = pv.UnstructuredGrid(cells, cell_types, points)
69
-
70
- if components is not None and sim_data.node_vars is not None:
71
- for cc in components:
72
- pv_grid[cc] = sim_data.node_vars[cc]
73
-
74
- return (pv_grid,pv_grid_vis)
127
+ return pv_grid
75
128
 
76
129
 
77
130
  def scale_length_units(scale: float,
@@ -208,7 +261,7 @@ def extract_surf_mesh(sim_data: mh.SimData) -> mh.SimData:
208
261
 
209
262
  return face_data
210
263
 
211
-
264
+ #TODO: make this support triangular prisms in 3D.
212
265
  def _get_pyvista_cell_type(nodes_per_elem: int, spat_dim: int) -> CellType | None:
213
266
  """Helper function to identify the pyvista element type in the mesh.
214
267
 
@@ -253,7 +306,7 @@ def _get_pyvista_cell_type(nodes_per_elem: int, spat_dim: int) -> CellType | Non
253
306
 
254
307
  return cell_type
255
308
 
256
-
309
+ #TODO: make this support triangular prisms in 3D.
257
310
  def _exodus_to_pyvista_connect(cell_type: CellType,
258
311
  connect: np.ndarray) -> np.ndarray:
259
312
  """Helper function that specifies the nodal winding map for higher order
@@ -290,7 +343,7 @@ def _exodus_to_pyvista_connect(cell_type: CellType,
290
343
 
291
344
  return connect
292
345
 
293
-
346
+ #TODO: make this support triangular prisms in 3D.
294
347
  def _get_surf_map(nodes_per_elem: int) -> np.ndarray:
295
348
  """Helper function specifying the mapping from 3D tet and hex elements to
296
349
  the individual faces consistent with the exodus output format.
@@ -0,0 +1,37 @@
1
+ # ==============================================================================
2
+ # pyvale: the python validation engine
3
+ # License: MIT
4
+ # Copyright (C) 2025 The Computer Aided Validation Team
5
+ # ==============================================================================
6
+
7
+ from abc import ABC, abstractmethod
8
+ import numpy as np
9
+
10
+
11
+ class FieldInterp(ABC):
12
+ @abstractmethod
13
+ def interp_field(self,
14
+ points: np.ndarray,
15
+ sample_times: np.ndarray | None = None,
16
+ ) -> np.ndarray:
17
+ pass
18
+
19
+
20
+ def interp_to_sample_time(sample_at_sim_time: np.ndarray,
21
+ sim_time_steps: np.ndarray,
22
+ sample_times: np.ndarray,
23
+ ) -> np.ndarray:
24
+
25
+ def sample_time_interp(x):
26
+ return np.interp(sample_times, sim_time_steps, x)
27
+
28
+ n_time_steps = sample_times.shape[0]
29
+ n_sensors = sample_at_sim_time.shape[0]
30
+ n_comps = sample_at_sim_time.shape[1]
31
+ sample_at_spec_time = np.empty((n_sensors,n_comps,n_time_steps))
32
+
33
+ for ii in range(n_comps):
34
+ sample_at_spec_time[:,ii,:] = np.apply_along_axis(sample_time_interp,-1,
35
+ sample_at_sim_time[:,ii,:])
36
+
37
+ return sample_at_spec_time
@@ -0,0 +1,124 @@
1
+ # ==============================================================================
2
+ # pyvale: the python validation engine
3
+ # License: MIT
4
+ # Copyright (C) 2025 The Computer Aided Validation Team
5
+ # ==============================================================================
6
+
7
+
8
+ import numpy as np
9
+ import pyvista as pv
10
+ import pyvale.mooseherder as mh
11
+ from pyvale.sensorsim.fieldconverter import simdata_to_pyvista_interp
12
+ from pyvale.sensorsim.fieldinterp import (FieldInterp,
13
+ interp_to_sample_time)
14
+
15
+ class FieldInterpMesh(FieldInterp):
16
+ """TODO
17
+ """
18
+
19
+ __slots__ = ("_sim_time_steps","_components","_pyvista_interp")
20
+
21
+ def __init__(self,
22
+ sim_data: mh.SimData,
23
+ components: tuple[str,...],
24
+ elem_dims: int,
25
+ ) -> None:
26
+ """
27
+ Parameters
28
+ ----------
29
+ sim_data : mh.SimData
30
+ _description_
31
+ components : tuple[str,...]
32
+ _description_
33
+ elem_dims : int
34
+ _description_
35
+ """
36
+ self._sim_time_steps = sim_data.time
37
+ self._components = components
38
+ self._pyvista_interp = simdata_to_pyvista_interp(sim_data,
39
+ self._components,
40
+ elem_dims=elem_dims)
41
+
42
+ def interp_field(self,
43
+ points: np.ndarray,
44
+ sample_times: np.ndarray | None = None,
45
+ ) -> np.ndarray:
46
+ """_summary_
47
+
48
+ Parameters
49
+ ----------
50
+ points : np.ndarray
51
+ _description_
52
+ times : np.ndarray | None, optional
53
+ _description_, by default None
54
+
55
+ Returns
56
+ -------
57
+ np.ndarray
58
+ _description_
59
+ """
60
+ return sample_pyvista_grid(self._components,
61
+ self._pyvista_interp,
62
+ self._sim_time_steps,
63
+ points,
64
+ sample_times)
65
+
66
+
67
+ def sample_pyvista_grid(components: tuple[str,...],
68
+ pyvista_interp: pv.UnstructuredGrid,
69
+ sim_time_steps: np.ndarray,
70
+ points: np.ndarray,
71
+ sample_times: np.ndarray | None = None
72
+ ) -> np.ndarray:
73
+ """Function for sampling (interpolating) a pyvista grid object containing
74
+ simulated field data. The pyvista sample method uses VTK to perform the
75
+ spatial interpolation using the element shape functions. If the sampling
76
+ time steps are not the same as the simulation time then a linear
77
+ interpolation over time is performed using numpy.
78
+
79
+ NOTE: sampling outside the mesh bounds of the sample returns a value of 0.
80
+
81
+ Parameters
82
+ ----------
83
+ components : tuple[str,...]
84
+ String keys for the components to be sampled in the pyvista grid object.
85
+ Useful for only interpolating the field components of interest for speed
86
+ and memory reduction.
87
+ pyvista_interp : pv.UnstructuredGrid
88
+ Pyvista grid object containing the simulation mesh and the components of
89
+ the physical field that will be sampled.
90
+ sim_time_steps : np.ndarray
91
+ Simulation time steps corresponding to the fields in the pyvista grid
92
+ object.
93
+ points : np.ndarray
94
+ Coordinates of the points at which to sample the pyvista grid object.
95
+ shape=(num_points,3) where the columns are the X, Y and Z coordinates of
96
+ the sample points in simulation world coordintes.
97
+ sample_times : np.ndarray | None, optional
98
+ Array of time steps at which to sample the pyvista grid. If None then no
99
+ temporal interpolation is performed and the sample times are assumed to
100
+ be the simulation time steps.
101
+
102
+ Returns
103
+ -------
104
+ np.ndarray
105
+ Array of sampled sensor measurements with shape=(num_sensors,
106
+ num_field_components,num_time_steps).
107
+ """
108
+ pv_points = pv.PolyData(points)
109
+ sample_data = pv_points.sample(pyvista_interp)
110
+
111
+ n_comps = len(components)
112
+ (n_sensors,n_time_steps) = np.array(sample_data[components[0]]).shape
113
+ sample_at_sim_time = np.empty((n_sensors,n_comps,n_time_steps))
114
+
115
+ for ii,cc in enumerate(components):
116
+ sample_at_sim_time[:,ii,:] = np.array(sample_data[cc])
117
+
118
+ if sample_times is None:
119
+ return sample_at_sim_time
120
+
121
+ return interp_to_sample_time(sample_at_sim_time,
122
+ sim_time_steps,
123
+ sample_times)
124
+
@@ -0,0 +1,55 @@
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 import Delaunay
9
+ from scipy.interpolate import LinearNDInterpolator
10
+ from pyvale.sensorsim.fieldinterp import (FieldInterp,
11
+ interp_to_sample_time)
12
+
13
+ class FieldInterpPoints(FieldInterp):
14
+
15
+ def __init__(self,
16
+ coords: np.ndarray,
17
+ sim_time_steps: np.ndarray,
18
+ components: tuple[str,...],
19
+ ) -> None:
20
+ pass
21
+ # self._coords = coords
22
+ # self._sim_time_steps = sim_time_steps
23
+ # self._components = components
24
+
25
+ # self._triangulation = Delaunay()
26
+ # self._interp_funcs = []
27
+
28
+ # for ii in range(self._sim_time_steps.shape[0]):
29
+ # interp = LinearNDInterpolator(coords,)
30
+ # self._interp_funcs.append(interp)
31
+
32
+
33
+ def interp_field(self,
34
+ points: np.ndarray,
35
+ sample_times: np.ndarray | None = None,
36
+ ) -> np.ndarray:
37
+ pass
38
+ # n_points = points.shape[0]
39
+ # n_comps = len(self._components)
40
+ # n_sim_time = self._sim_time_steps.shape[0]
41
+ # sample_at_sim_time = np.empty((n_points,n_comps,n_sim_time),
42
+ # dtype=np.float64)
43
+
44
+ # for ii in range(self._sim_time_steps.shape[0]):
45
+
46
+ # sample_at_sim_time[:,]
47
+
48
+
49
+
50
+ # if sample_times is None:
51
+ # return sample_at_sim_time
52
+
53
+ # return interp_to_sample_time(sample_at_sim_time,
54
+ # sim_time_steps,
55
+ # sample_times)
@@ -6,9 +6,9 @@
6
6
 
7
7
  import numpy as np
8
8
  import pyvista as pv
9
- from pyvale.field import IField
10
- from pyvale.sensordata import SensorData
11
- from pyvale.integratorfactory import build_spatial_averager
9
+ from pyvale.sensorsim.field import IField
10
+ from pyvale.sensorsim.sensordata import SensorData
11
+ from pyvale.sensorsim.integratorfactory import build_spatial_averager
12
12
 
13
13
 
14
14
  def sample_field_with_sensor_data(field: IField, sensor_data: SensorData
@@ -40,7 +40,7 @@ def sample_field_with_sensor_data(field: IField, sensor_data: SensorData
40
40
  return spatial_integrator.calc_averages()
41
41
 
42
42
 
43
- # NOTE: sampling outside the bounds of the sample returns a value of 0
43
+ # TODO: move this into the fieldinterpmesh object
44
44
  def sample_pyvista_grid(components: tuple[str,...],
45
45
  pyvista_grid: pv.UnstructuredGrid,
46
46
  sim_time_steps: np.ndarray,