pyvale 2025.7.1__cp311-cp311-musllinux_1_2_aarch64.whl → 2025.8.1__cp311-cp311-musllinux_1_2_aarch64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of pyvale might be problematic. Click here for more details.

Files changed (186) 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/dic/cpp/dicfourier.cpp +36 -4
  13. pyvale/dic/cpp/dicinterpolator.cpp +56 -1
  14. pyvale/dic/cpp/dicmain.cpp +24 -19
  15. pyvale/dic/cpp/dicoptimizer.cpp +6 -1
  16. pyvale/dic/cpp/dicscanmethod.cpp +32 -32
  17. pyvale/dic/cpp/dicsignalhandler.cpp +16 -0
  18. pyvale/dic/cpp/dicstrain.cpp +7 -3
  19. pyvale/dic/cpp/dicutil.cpp +79 -23
  20. pyvale/{dic2d.py → dic/dic2d.py} +51 -29
  21. pyvale/dic/dic2dconv.py +6 -0
  22. pyvale/{dic2dcpp.cpython-311-aarch64-linux-musl.so → dic/dic2dcpp.cpython-311-aarch64-linux-musl.so} +0 -0
  23. pyvale/{dicchecks.py → dic/dicchecks.py} +28 -16
  24. pyvale/dic/dicdataimport.py +370 -0
  25. pyvale/{dicregionofinterest.py → dic/dicregionofinterest.py} +169 -12
  26. pyvale/{dicresults.py → dic/dicresults.py} +4 -1
  27. pyvale/{dicstrain.py → dic/dicstrain.py} +9 -9
  28. pyvale/examples/basics/{ex1_1_basicscalars_therm2d.py → ex1a_basicscalars_therm2d.py} +12 -9
  29. pyvale/examples/basics/{ex1_2_sensormodel_therm2d.py → ex1b_sensormodel_therm2d.py} +17 -14
  30. pyvale/examples/basics/{ex1_3_customsens_therm3d.py → ex1c_customsens_therm3d.py} +27 -24
  31. pyvale/examples/basics/{ex1_4_basicerrors_therm3d.py → ex1d_basicerrors_therm3d.py} +32 -29
  32. pyvale/examples/basics/{ex1_5_fielderrs_therm3d.py → ex1e_fielderrs_therm3d.py} +19 -15
  33. pyvale/examples/basics/{ex1_6_caliberrs_therm2d.py → ex1f_caliberrs_therm2d.py} +20 -16
  34. pyvale/examples/basics/{ex1_7_spatavg_therm2d.py → ex1g_spatavg_therm2d.py} +19 -16
  35. pyvale/examples/basics/{ex2_1_basicvectors_disp2d.py → ex2a_basicvectors_disp2d.py} +13 -10
  36. pyvale/examples/basics/{ex2_2_vectorsens_disp2d.py → ex2b_vectorsens_disp2d.py} +19 -15
  37. pyvale/examples/basics/{ex2_3_sensangle_disp2d.py → ex2c_sensangle_disp2d.py} +21 -18
  38. pyvale/examples/basics/{ex2_4_chainfielderrs_disp2d.py → ex2d_chainfielderrs_disp2d.py} +31 -29
  39. pyvale/examples/basics/{ex2_5_vectorfields3d_disp3d.py → ex2e_vectorfields3d_disp3d.py} +21 -18
  40. pyvale/examples/basics/{ex3_1_basictensors_strain2d.py → ex3a_basictensors_strain2d.py} +16 -14
  41. pyvale/examples/basics/{ex3_2_tensorsens2d_strain2d.py → ex3b_tensorsens2d_strain2d.py} +17 -14
  42. pyvale/examples/basics/{ex3_3_tensorsens3d_strain3d.py → ex3c_tensorsens3d_strain3d.py} +25 -22
  43. pyvale/examples/basics/{ex4_1_expsim2d_thermmech2d.py → ex4a_expsim2d_thermmech2d.py} +17 -14
  44. pyvale/examples/basics/{ex4_2_expsim3d_thermmech3d.py → ex4b_expsim3d_thermmech3d.py} +37 -34
  45. pyvale/examples/basics/ex5_nomesh.py +24 -0
  46. pyvale/examples/dic/ex1_2_blenderdeformed.py +174 -0
  47. pyvale/examples/dic/ex1_region_of_interest.py +6 -3
  48. pyvale/examples/dic/ex2_plate_with_hole.py +21 -18
  49. pyvale/examples/dic/ex3_plate_with_hole_strain.py +8 -6
  50. pyvale/examples/dic/ex4_dic_blender.py +17 -15
  51. pyvale/examples/dic/ex5_dic_challenge.py +19 -14
  52. pyvale/examples/genanalyticdata/ex1_1_scalarvisualisation.py +16 -10
  53. pyvale/examples/genanalyticdata/ex1_2_scalarcasebuild.py +3 -3
  54. pyvale/examples/genanalyticdata/ex2_1_analyticsensors.py +29 -23
  55. pyvale/examples/genanalyticdata/ex2_2_analyticsensors_nomesh.py +67 -0
  56. pyvale/examples/imagedef2d/ex_imagedef2d_todisk.py +12 -9
  57. pyvale/examples/mooseherder/ex0_create_moose_config.py +65 -0
  58. pyvale/examples/mooseherder/ex1a_modify_moose_input.py +71 -0
  59. pyvale/examples/mooseherder/ex1b_modify_gmsh_input.py +69 -0
  60. pyvale/examples/mooseherder/ex2a_run_moose_once.py +80 -0
  61. pyvale/examples/mooseherder/ex2b_run_gmsh_once.py +64 -0
  62. pyvale/examples/mooseherder/ex2c_run_both_once.py +114 -0
  63. pyvale/examples/mooseherder/ex3_run_moose_seq_para.py +157 -0
  64. pyvale/examples/mooseherder/ex4_run_gmsh-moose_seq_para.py +176 -0
  65. pyvale/examples/mooseherder/ex5_run_moose_paramulti.py +136 -0
  66. pyvale/examples/mooseherder/ex6_read_moose_exodus.py +163 -0
  67. pyvale/examples/mooseherder/ex7a_read_moose_herd_results.py +153 -0
  68. pyvale/examples/mooseherder/ex7b_read_multi_herd_results.py +116 -0
  69. pyvale/examples/mooseherder/ex7c_read_multi_gmshmoose_results.py +127 -0
  70. pyvale/examples/mooseherder/ex7d_readconfig_multi_gmshmoose_results.py +143 -0
  71. pyvale/examples/mooseherder/ex8_read_existing_sweep_output.py +72 -0
  72. pyvale/examples/renderblender/ex1_1_blenderscene.py +24 -20
  73. pyvale/examples/renderblender/ex1_2_blenderdeformed.py +22 -18
  74. pyvale/examples/renderblender/ex2_1_stereoscene.py +36 -29
  75. pyvale/examples/renderblender/ex2_2_stereodeformed.py +26 -20
  76. pyvale/examples/renderblender/ex3_1_blendercalibration.py +24 -17
  77. pyvale/examples/renderrasterisation/ex_rastenp.py +14 -12
  78. pyvale/examples/renderrasterisation/ex_rastercyth_oneframe.py +14 -15
  79. pyvale/examples/renderrasterisation/ex_rastercyth_static_cypara.py +13 -11
  80. pyvale/examples/renderrasterisation/ex_rastercyth_static_pypara.py +13 -11
  81. pyvale/mooseherder/__init__.py +32 -0
  82. pyvale/mooseherder/directorymanager.py +416 -0
  83. pyvale/mooseherder/exodusreader.py +763 -0
  84. pyvale/mooseherder/gmshrunner.py +163 -0
  85. pyvale/mooseherder/inputmodifier.py +236 -0
  86. pyvale/mooseherder/mooseconfig.py +226 -0
  87. pyvale/mooseherder/mooseherd.py +527 -0
  88. pyvale/mooseherder/mooserunner.py +303 -0
  89. pyvale/mooseherder/outputreader.py +22 -0
  90. pyvale/mooseherder/simdata.py +92 -0
  91. pyvale/mooseherder/simrunner.py +31 -0
  92. pyvale/mooseherder/sweepreader.py +356 -0
  93. pyvale/mooseherder/sweeptools.py +76 -0
  94. pyvale/sensorsim/__init__.py +82 -0
  95. pyvale/{camera.py → sensorsim/camera.py} +7 -7
  96. pyvale/{camerasensor.py → sensorsim/camerasensor.py} +7 -7
  97. pyvale/{camerastereo.py → sensorsim/camerastereo.py} +2 -2
  98. pyvale/{cameratools.py → sensorsim/cameratools.py} +4 -4
  99. pyvale/{cython → sensorsim/cython}/rastercyth.c +596 -596
  100. pyvale/sensorsim/cython/rastercyth.cpython-311-aarch64-linux-musl.so +0 -0
  101. pyvale/{cython → sensorsim/cython}/rastercyth.py +16 -17
  102. pyvale/{errorcalculator.py → sensorsim/errorcalculator.py} +1 -1
  103. pyvale/{errorintegrator.py → sensorsim/errorintegrator.py} +2 -2
  104. pyvale/{errorrand.py → sensorsim/errorrand.py} +4 -4
  105. pyvale/{errorsyscalib.py → sensorsim/errorsyscalib.py} +2 -2
  106. pyvale/{errorsysdep.py → sensorsim/errorsysdep.py} +2 -2
  107. pyvale/{errorsysfield.py → sensorsim/errorsysfield.py} +8 -8
  108. pyvale/{errorsysindep.py → sensorsim/errorsysindep.py} +3 -3
  109. pyvale/sensorsim/exceptions.py +8 -0
  110. pyvale/{experimentsimulator.py → sensorsim/experimentsimulator.py} +23 -3
  111. pyvale/{field.py → sensorsim/field.py} +1 -1
  112. pyvale/{fieldconverter.py → sensorsim/fieldconverter.py} +72 -19
  113. pyvale/sensorsim/fieldinterp.py +37 -0
  114. pyvale/sensorsim/fieldinterpmesh.py +124 -0
  115. pyvale/sensorsim/fieldinterppoints.py +55 -0
  116. pyvale/{fieldsampler.py → sensorsim/fieldsampler.py} +4 -4
  117. pyvale/{fieldscalar.py → sensorsim/fieldscalar.py} +28 -24
  118. pyvale/{fieldtensor.py → sensorsim/fieldtensor.py} +33 -31
  119. pyvale/{fieldvector.py → sensorsim/fieldvector.py} +33 -31
  120. pyvale/{imagedef2d.py → sensorsim/imagedef2d.py} +9 -5
  121. pyvale/{integratorfactory.py → sensorsim/integratorfactory.py} +6 -6
  122. pyvale/{integratorquadrature.py → sensorsim/integratorquadrature.py} +3 -3
  123. pyvale/{integratorrectangle.py → sensorsim/integratorrectangle.py} +3 -3
  124. pyvale/{integratorspatial.py → sensorsim/integratorspatial.py} +1 -1
  125. pyvale/{rastercy.py → sensorsim/rastercy.py} +5 -5
  126. pyvale/{rasternp.py → sensorsim/rasternp.py} +9 -9
  127. pyvale/{rasteropts.py → sensorsim/rasteropts.py} +1 -1
  128. pyvale/{renderer.py → sensorsim/renderer.py} +1 -1
  129. pyvale/{rendermesh.py → sensorsim/rendermesh.py} +5 -5
  130. pyvale/{renderscene.py → sensorsim/renderscene.py} +2 -2
  131. pyvale/{sensorarray.py → sensorsim/sensorarray.py} +1 -1
  132. pyvale/{sensorarrayfactory.py → sensorsim/sensorarrayfactory.py} +12 -12
  133. pyvale/{sensorarraypoint.py → sensorsim/sensorarraypoint.py} +10 -8
  134. pyvale/{sensordata.py → sensorsim/sensordata.py} +1 -1
  135. pyvale/{sensortools.py → sensorsim/sensortools.py} +2 -20
  136. pyvale/sensorsim/simtools.py +174 -0
  137. pyvale/{visualexpplotter.py → sensorsim/visualexpplotter.py} +3 -3
  138. pyvale/{visualimages.py → sensorsim/visualimages.py} +2 -2
  139. pyvale/{visualsimanimator.py → sensorsim/visualsimanimator.py} +4 -4
  140. pyvale/{visualsimplotter.py → sensorsim/visualsimplotter.py} +5 -5
  141. pyvale/{visualsimsensors.py → sensorsim/visualsimsensors.py} +12 -12
  142. pyvale/{visualtools.py → sensorsim/visualtools.py} +1 -1
  143. pyvale/{visualtraceplotter.py → sensorsim/visualtraceplotter.py} +2 -2
  144. pyvale/simcases/case17.geo +3 -0
  145. pyvale/simcases/case17.i +4 -4
  146. pyvale/simcases/run_1case.py +1 -9
  147. pyvale/simcases/run_all_cases.py +1 -1
  148. pyvale/simcases/run_build_case.py +1 -1
  149. pyvale/simcases/run_example_cases.py +1 -1
  150. pyvale/verif/__init__.py +12 -0
  151. pyvale/{analyticsimdatafactory.py → verif/analyticsimdatafactory.py} +2 -2
  152. pyvale/{analyticsimdatagenerator.py → verif/analyticsimdatagenerator.py} +2 -2
  153. pyvale/verif/psens.py +125 -0
  154. pyvale/verif/psensconst.py +18 -0
  155. pyvale/verif/psensmech.py +227 -0
  156. pyvale/verif/psensmultiphys.py +187 -0
  157. pyvale/verif/psensscalar.py +347 -0
  158. pyvale/verif/psenstensor.py +123 -0
  159. pyvale/verif/psensvector.py +116 -0
  160. {pyvale-2025.7.1.dist-info → pyvale-2025.8.1.dist-info}/METADATA +6 -7
  161. pyvale-2025.8.1.dist-info/RECORD +263 -0
  162. pyvale/cython/rastercyth.cpython-311-aarch64-linux-musl.so +0 -0
  163. pyvale/dataset.py +0 -415
  164. pyvale/dicdataimport.py +0 -247
  165. pyvale/simtools.py +0 -67
  166. pyvale-2025.7.1.dist-info/RECORD +0 -214
  167. /pyvale/{blendercalibrationdata.py → blender/blendercalibrationdata.py} +0 -0
  168. /pyvale/{dicspecklegenerator.py → dic/dicspecklegenerator.py} +0 -0
  169. /pyvale/{dicspecklequality.py → dic/dicspecklequality.py} +0 -0
  170. /pyvale/{dicstrainresults.py → dic/dicstrainresults.py} +0 -0
  171. /pyvale/{cameradata.py → sensorsim/cameradata.py} +0 -0
  172. /pyvale/{cameradata2d.py → sensorsim/cameradata2d.py} +0 -0
  173. /pyvale/{errordriftcalc.py → sensorsim/errordriftcalc.py} +0 -0
  174. /pyvale/{fieldtransform.py → sensorsim/fieldtransform.py} +0 -0
  175. /pyvale/{generatorsrandom.py → sensorsim/generatorsrandom.py} +0 -0
  176. /pyvale/{imagetools.py → sensorsim/imagetools.py} +0 -0
  177. /pyvale/{integratortype.py → sensorsim/integratortype.py} +0 -0
  178. /pyvale/{output.py → sensorsim/output.py} +0 -0
  179. /pyvale/{raster.py → sensorsim/raster.py} +0 -0
  180. /pyvale/{sensordescriptor.py → sensorsim/sensordescriptor.py} +0 -0
  181. /pyvale/{visualimagedef.py → sensorsim/visualimagedef.py} +0 -0
  182. /pyvale/{visualopts.py → sensorsim/visualopts.py} +0 -0
  183. /pyvale/{analyticmeshgen.py → verif/analyticmeshgen.py} +0 -0
  184. {pyvale-2025.7.1.dist-info → pyvale-2025.8.1.dist-info}/WHEEL +0 -0
  185. {pyvale-2025.7.1.dist-info → pyvale-2025.8.1.dist-info}/licenses/LICENSE +0 -0
  186. {pyvale-2025.7.1.dist-info → pyvale-2025.8.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,527 @@
1
+ # ==============================================================================
2
+ # pyvale: the python validation engine
3
+ # License: MIT
4
+ # Copyright (C) 2025 The Computer Aided Validation Team
5
+ # ==============================================================================
6
+ import os
7
+ import time
8
+ import multiprocessing as mp
9
+ from pathlib import Path
10
+ from multiprocessing.pool import Pool
11
+
12
+ from pyvale.mooseherder.directorymanager import DirectoryManager
13
+ from pyvale.mooseherder.simrunner import SimRunner
14
+ from pyvale.mooseherder.inputmodifier import InputModifier
15
+
16
+
17
+ class MooseHerdError(Exception):
18
+ """MooseHerdError: custom error class for flagging errors with the moose
19
+ herd.
20
+ """
21
+
22
+ class MooseHerd:
23
+ """MooseHerd class that can run parametric sweeps of simulation chains in
24
+ parallel with configurable parallelisation options. Takes a list of
25
+ SimRunner objects and a corresponding list of InputModifiers to insert the
26
+ variables into the input scripts for the SimRunners. When calling run_* the
27
+ herd will first call all InputModifiers in the specified order and then
28
+ call run on all the SimRunners in order. Uses the DirectoryManager class to
29
+ create/clear and log the directories in which each parallel worker is
30
+ creating input files and running simulations.
31
+ """
32
+ def __init__(self, sim_runners: list[SimRunner],
33
+ input_mods: list[InputModifier],
34
+ dir_manager: DirectoryManager,
35
+ num_para_sims: int = 1) -> None:
36
+ """__init__
37
+
38
+ Args:
39
+ sim_runners (list[SimRunner]): list of objects that inherit from
40
+ the SimRunner ABC in the order they need to be run. The mesh
41
+ needs to be created before runnning moose so a common chain
42
+ would be [GmshRunner,MooseRunner].
43
+ input_mods (list[InputModifier]): list of InputModifiers to create
44
+ the required input scripts for the SimRunners.
45
+ dir_manager (DirectoryManager): used to control how many and
46
+ which directories are used to run the simulations.
47
+ """
48
+ self._runners = sim_runners
49
+ self._modifiers = input_mods
50
+ self._dir_manager = dir_manager
51
+
52
+ self._n_para_sims = num_para_sims
53
+
54
+ self._input_names = [f'sim-{ii+1}' for ii,_ in enumerate(sim_runners)]
55
+
56
+ self._keep_all = True
57
+
58
+ self._var_sweep = list([])
59
+
60
+ self._sweep_iter = 0
61
+ self._sweep_run_time = -1.0
62
+
63
+ self._sim_iter = 0
64
+ self._iter_run_time = -1.0
65
+
66
+
67
+ def set_input_copy_names(self, input_names: list[str] | None = None) -> None:
68
+ """set_input_copy_name: sets the name that will be used when copying
69
+ input files to the working directories for the sweep. The defualt name
70
+ is 'sim-i' so the first combination of variables in the simulation chain
71
+ will be called 'sim-1-1'.
72
+
73
+ Parameters
74
+ ----------
75
+ input_names : list[str] | None
76
+ List of name prefixes to be used for the simulation files. Defaults
77
+ to None.
78
+
79
+ Returns
80
+ -------
81
+
82
+ Raises
83
+ ------
84
+ MooseHerdError
85
+ The lengths of the sim runner list and the input
86
+ modifier lists are not the same.
87
+
88
+ """
89
+ if input_names is None:
90
+ self._input_names = [f'sim-{ii+1}' for ii,_ in enumerate(self._runners)]
91
+ return
92
+
93
+ if len(input_names) != len(self._runners):
94
+ raise MooseHerdError(f'The length of the input names ({len(input_names)})'
95
+ 'must match the length of the sim runners ' +
96
+ f'and input modifiers ({len(self._runners)})')
97
+
98
+ self._input_names = input_names
99
+
100
+
101
+ def set_keep_flag(self, keep_all: bool = True) -> None:
102
+ """set_keep_flag: flag used for allowing multiple calls to run_para or
103
+ run_seq to keep everything or to overwrite with every call to run_*.
104
+
105
+ Parameters
106
+ ----------
107
+ keep_all : bool
108
+ True = keep all inputs and outputs with
109
+ multiple calls to run_*. False = overwrite inputs and outputs
110
+ with multiple calls to run_*. Defaults to True.
111
+
112
+ Returns
113
+ -------
114
+
115
+ """
116
+ self._keep_all = keep_all
117
+
118
+
119
+ def set_num_para_sims(self, n_para: int = 1) -> None:
120
+ """set_num_para_sims: sets the number of simulation chains to run in
121
+ parallel. Limits the number
122
+
123
+ Parameters
124
+ ----------
125
+ n_para : int
126
+ Number of parallel simulation to run. Defaults to 1.
127
+
128
+ Returns
129
+ -------
130
+
131
+ """
132
+ n_para = int(n_para)
133
+ n_cpus = os.cpu_count()
134
+
135
+ if n_cpus is None:
136
+ pass
137
+ elif n_para <= 0:
138
+ n_para = 1
139
+ elif n_para > n_cpus:
140
+ n_para = n_cpus
141
+
142
+ if self._n_para_sims != n_para:
143
+ self._n_para_sims = n_para
144
+
145
+
146
+ def get_sim_iter(self) -> int:
147
+ """get_sim_iter: returns the current simulation iteration corresponding
148
+ to the combination of variables being analysed. This number will
149
+ accumulate with multiple calls to run_* is keep_all=true.
150
+
151
+ Parameters
152
+ ----------
153
+
154
+ Returns
155
+ -------
156
+ int
157
+ current simulation iteration number.
158
+
159
+ """
160
+ return self._sim_iter
161
+
162
+
163
+ def get_sweep_iter(self) -> int:
164
+ """get_sweep_iter: gets the current sweep iteration. The sweep
165
+ iteration is incremented with every call to run_* if keep_all = true.
166
+ If keep_all = false then sweep_iter is held at 1.
167
+
168
+ Parameters
169
+ ----------
170
+
171
+ Returns
172
+ -------
173
+ int
174
+ current sweep iteration number.
175
+
176
+ """
177
+ return self._sweep_iter
178
+
179
+
180
+ def reset_iter_counts(self) -> None:
181
+ """reset_iter_counts: resets the simulation iteration and the sweep
182
+ iteration counters to zero.
183
+
184
+ Parameters
185
+ ----------
186
+
187
+ Returns
188
+ -------
189
+
190
+ """
191
+ self._sim_iter = 0
192
+ self._sweep_iter = 0
193
+
194
+
195
+ def _get_process_name(self) -> str:
196
+ """_get_process_name: only here for monkey patching with pytest.
197
+
198
+ Parameters
199
+ ----------
200
+
201
+ Returns
202
+ -------
203
+ str
204
+ the process name string.
205
+
206
+ """
207
+ return mp.current_process().name
208
+
209
+
210
+ def _get_worker_num(self) -> str:
211
+ """_get_worker_num: helper function to get the worker number for the
212
+ current sub-process.
213
+
214
+ Parameters
215
+ ----------
216
+
217
+ Returns
218
+ -------
219
+ str
220
+ number string taken from the process name. If this is the main
221
+ process returns '1'.
222
+
223
+ """
224
+ name = self._get_process_name()
225
+
226
+ if name == 'MainProcess':
227
+ worker_num = '1'
228
+ else:
229
+ worker_num = name.split('-',1)[1]
230
+
231
+ if int(worker_num) > self._n_para_sims:
232
+ worker_num = str((int(worker_num) % self._n_para_sims)+1)
233
+
234
+ return worker_num
235
+
236
+
237
+ def _get_run_num(self, sim_iter: int, worker_num: str) -> str:
238
+ """_get_run_num: helper function to get the run directory number string
239
+
240
+ Parameters
241
+ ----------
242
+ sim_iter : int
243
+ the current simulation iteration.
244
+ worker_num : str
245
+ the worker number extracted from the current
246
+ process number as a string.
247
+
248
+ Returns
249
+ -------
250
+ str
251
+ the string specifying the run directory number for this
252
+ simulation iteration.
253
+
254
+ """
255
+ if self._keep_all:
256
+ run_num = str(sim_iter+1)
257
+ else:
258
+ run_num = worker_num
259
+
260
+ return run_num
261
+
262
+
263
+ def _mod_input(self,
264
+ modifier: InputModifier,
265
+ mod_vars: dict | None,
266
+ save_file: Path) -> None:
267
+ """_mod_input: helper function that uses the input modifier to write
268
+ new variables to the input file and save it to the specified path.
269
+
270
+ Parameters
271
+ ----------
272
+ modifier : InputModifier
273
+ input modifier for the specified type of
274
+ input file.
275
+ mod_vars : dict | None
276
+ dictionary of variables to write to the
277
+ input file, if None just copy the input file.
278
+ save_file : Path
279
+ path with file name and extension to output the
280
+ modified input file.
281
+
282
+ Returns
283
+ -------
284
+
285
+ """
286
+ if mod_vars is not None:
287
+ modifier.update_vars(mod_vars)
288
+ modifier.write_file(save_file)
289
+
290
+
291
+ def _run(self, runner: SimRunner, run_file: Path) -> Path | None:
292
+ """_run: helper function to call the SimRunner and get the path to the
293
+ output file.
294
+
295
+ Parameters
296
+ ----------
297
+ runner : SimRunner
298
+ for running the simulation, must be a class
299
+ that implements the SimRunner ABC.
300
+ run_file : Path
301
+ path to the input file to run with SimRunner.
302
+
303
+
304
+ Returns
305
+ -------
306
+ Path | None
307
+ Path to the output or None
308
+
309
+ """
310
+ runner.run(run_file)
311
+ return runner.get_output_path()
312
+
313
+
314
+ def run_once(self, sim_iter: int, var_list: list[dict | None]
315
+ ) -> list[Path | None]:
316
+ """run_once: runs a specific simulation chain with the given variable
317
+ list once and returns a list of paths to the output files. Used by
318
+ run_seq and run_para for parallelisation.
319
+
320
+ Parameters
321
+ ----------
322
+ sim_iter : int
323
+ current simulation iteration which is the index of
324
+ the var_list from the var_sweep.
325
+ var_list : list[dict | None]
326
+ list of dictionaries that contain
327
+ the variables that will be run for this iteration.
328
+
329
+ Returns
330
+ -------
331
+ list[Path | None]
332
+ list of paths to the simulation output. If there
333
+ is no useful output from the runner in the simulation chain it
334
+ returns None in the list.
335
+
336
+ """
337
+ iter_start_time = time.perf_counter()
338
+
339
+ worker_num = self._get_worker_num()
340
+ run_dir = self._dir_manager.get_run_dir(int(worker_num)-1)
341
+ run_num = self._get_run_num(sim_iter,worker_num)
342
+
343
+ run_files = list([])
344
+ for ii,mm in enumerate(self._modifiers):
345
+ ext = mm.get_input_file().suffix
346
+ run_files.append(run_dir / (self._input_names[ii] +'-'+run_num+ext))
347
+ self._mod_input(mm,var_list[ii],run_files[ii])
348
+
349
+ output_list = list([])
350
+ for ii,rr in enumerate(self._runners):
351
+ output_list.append(self._run(rr,run_files[ii]))
352
+
353
+ self._iter_run_time = time.perf_counter() - iter_start_time
354
+
355
+ return output_list
356
+
357
+
358
+ def _start_sweep(self, var_sweep: list[list[dict | None]]) -> float:
359
+ """_start_sweep: helper function used at the start of a variable sweep
360
+ in either run_seq or run_para. Sets the var_sweep attribute, deals with
361
+ the management of directories and starts the performance counter.
362
+
363
+ Parameters
364
+ ----------
365
+ var_sweep : list[list[dict | None]]
366
+ as passed to run_seq/para.
367
+
368
+ Returns
369
+ -------
370
+ float
371
+ performance timer start value.
372
+
373
+ """
374
+ self._var_sweep = var_sweep
375
+
376
+ if not self._keep_all:
377
+ self.reset_iter_counts()
378
+ self._dir_manager.clear_dirs()
379
+ self._dir_manager.create_dirs()
380
+
381
+ return time.perf_counter()
382
+
383
+
384
+ def _end_sweep(self, start_sweep_time: float,
385
+ output_files: list[list[Path | None]]) -> None:
386
+ """_end_sweep: helper function called at the end of runseq/para.
387
+ Reacords the sweep run time. Increments the iteration counters. and
388
+ writes the output key and sweep variables to the first workers
389
+ directory.
390
+
391
+ Parameters
392
+ ----------
393
+ start_sweep_time : float
394
+ the sweep start time taken from the
395
+ _start_sweep() function.
396
+ output_files : list[list[Path]]
397
+ list of list of paths to the
398
+ simulation chain output files.
399
+
400
+
401
+ Returns
402
+ -------
403
+
404
+ """
405
+ self._sweep_run_time = time.perf_counter() - start_sweep_time
406
+
407
+ self._sweep_iter += 1
408
+ self._sim_iter += len(self._var_sweep)
409
+
410
+ self._dir_manager.set_output_paths(output_files)
411
+ self._dir_manager.write_output_key(self._sweep_iter)
412
+ self._dir_manager.write_sweep_vars(self._var_sweep,self._sweep_iter)
413
+
414
+
415
+ def run_sequential(self, var_sweep: list[list[dict | None]]
416
+ ) -> list[list[Path | None]]:
417
+ """run_sequential: runs the variable sweep given in var_sweep
418
+ sequentially and returns the paths to the simulation outputs.
419
+
420
+ Parameters
421
+ ----------
422
+ var_sweep : list[list[dict | None]]
423
+ outer list is the simulation
424
+ iteration, inner list is the position in the simulation chain
425
+ that the variable dictionary corresponds to. The dictionary
426
+ contains the variables that will be inserted into the input
427
+ file before calling run on the SimRunner. If None instead of
428
+ a dictionary then the input file is copied with no modification
429
+
430
+
431
+ Returns
432
+ -------
433
+ list[list[Path | None]]
434
+ outer list is the simulation iteration and
435
+ the inner list corresponds to the position of the SimRunner in
436
+ the cimulation chain. Gives the path to the simulation output
437
+ or None if no useful output is produced.
438
+
439
+ """
440
+ start_sweep_time = self._start_sweep(var_sweep)
441
+
442
+ output_files = list([])
443
+
444
+ ii = self._sim_iter
445
+ for vv in var_sweep:
446
+ output_files.append(self.run_once(ii,vv))
447
+ ii += 1
448
+
449
+
450
+ self._end_sweep(start_sweep_time,output_files)
451
+
452
+ return output_files
453
+
454
+
455
+ def run_para(self, var_sweep: list[list[dict | None]]
456
+ ) -> list[list[Path | None]]:
457
+ """run_para: runs the variable sweep with the simulation chain in
458
+ parallel.
459
+
460
+ Parameters
461
+ ----------
462
+ var_sweep : list[list[dict | None]]
463
+ outer list is the simulation
464
+ iteration, inner list is the position in the simulation chain
465
+ that the variable dictionary corresponds to. The dictionary
466
+ contains the variables that will be inserted into the input
467
+ file before calling run on the SimRunner. If None instead of
468
+ a dictionary then the input file is copied with no modification
469
+
470
+
471
+ Returns
472
+ -------
473
+ list[list[Path | None]]
474
+ outer list is the simulation iteration and
475
+ the inner list corresponds to the position of the SimRunner in
476
+ the cimulation chain. Gives the path to the simulation output
477
+ or None if no useful output is produced.
478
+
479
+ """
480
+ sweep_start_time = self._start_sweep(var_sweep)
481
+
482
+ with Pool(self._n_para_sims) as pool:
483
+ processes = list([])
484
+
485
+ ii = self._sim_iter
486
+ for vv in var_sweep:
487
+ processes.append(pool.apply_async(self.run_once, args=(ii,vv)))
488
+ ii += 1
489
+
490
+ output_files = [pp.get() for pp in processes]
491
+
492
+ self._end_sweep(sweep_start_time, output_files)
493
+
494
+ return output_files
495
+
496
+
497
+ def get_sweep_time(self) -> float:
498
+ """get_sweep_time
499
+
500
+ Parameters
501
+ ----------
502
+
503
+ Returns
504
+ -------
505
+ float
506
+ the time taken for the variable sweep to run based on the
507
+ performance counter.
508
+
509
+ """
510
+ return self._sweep_run_time
511
+
512
+
513
+ def get_iter_time(self) -> float:
514
+ """get_iter_time
515
+
516
+ Returns
517
+ float: the time taken for the current simulation iteration to run.
518
+
519
+ Parameters
520
+ ----------
521
+
522
+ Returns
523
+ -------
524
+
525
+ """
526
+ return self._iter_run_time
527
+