pyvale 2025.7.2__cp311-cp311-macosx_14_0_arm64.whl → 2025.8.1__cp311-cp311-macosx_14_0_arm64.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-darwin.so → dic/dic2dcpp.cpython-311-darwin.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-darwin.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 +262 -0
  153. pyvale/dataset.py +0 -415
  154. pyvale/simtools.py +0 -67
  155. pyvale-2025.7.2.dist-info/RECORD +0 -214
  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
@@ -0,0 +1,163 @@
1
+ # ==============================================================================
2
+ # pyvale: the python validation engine
3
+ # License: MIT
4
+ # Copyright (C) 2025 The Computer Aided Validation Team
5
+ # ==============================================================================
6
+
7
+ import subprocess
8
+ from pathlib import Path
9
+ from pyvale.mooseherder.simrunner import SimRunner
10
+
11
+
12
+ class GmshRunner(SimRunner):
13
+ """Used to call gmsh to create a mesh file to be used to run a finite
14
+ element simulation. Implements the SimRunner abstract interface so that it
15
+ can be used by the herd workflow manager.
16
+ """
17
+
18
+ def __init__(self, gmsh_path: Path | None = None):
19
+ """Initialiser for the GmshRunner.
20
+
21
+ Parameters
22
+ ----------
23
+ gmsh_path : Path | None, optional
24
+ Path to the gmsh executable, by default None
25
+ """
26
+
27
+ if gmsh_path is None:
28
+ self._gmsh_app = None
29
+ else:
30
+ self.set_gmsh_app(gmsh_path)
31
+
32
+ self._input_path = None
33
+ self._arg_list = []
34
+
35
+ def set_gmsh_app(self, gmsh_app: Path) -> None: # type: ignore
36
+ """Sets path to the gmsh app.
37
+
38
+ Parameters
39
+ ----------
40
+ gmsh_app : Path
41
+ full path to the gmsh app.
42
+
43
+ Returns
44
+ -------
45
+
46
+ Raises
47
+ ------
48
+ FileNotFoundError
49
+ gmsh app does not exist at the specified path.
50
+
51
+ """
52
+ if not gmsh_app.exists():
53
+ raise FileNotFoundError('Gmsh app not found at given path.')
54
+
55
+ self._gmsh_app = gmsh_app
56
+
57
+
58
+ def get_input_file(self) -> Path | None:
59
+ """get_input_path: the path to the input file to run gmsh with.
60
+
61
+ Parameters
62
+ ----------
63
+
64
+ Returns
65
+ -------
66
+ Path | None
67
+ path to the gmsh *.geo file.
68
+
69
+ """
70
+ return self._input_path
71
+
72
+
73
+ def set_input_file(self, input_path: Path) -> None:
74
+ """Sets the input geo file for gmsh.
75
+
76
+ Parameters
77
+ ----------
78
+ input_file : Path
79
+ Full path to the gmsh *.geo input file.
80
+
81
+ Returns
82
+ -------
83
+
84
+ Raises
85
+ ------
86
+ FileNotFoundError
87
+ Not a .geo file
88
+ FileNotFoundError
89
+ Geo file does not exist
90
+
91
+ """
92
+ if input_path.suffix != '.geo':
93
+ raise FileNotFoundError('Incorrect file type. Must be *.geo.')
94
+
95
+ if not input_path.exists():
96
+ raise FileNotFoundError('Specified gmsh geo file does not exist.')
97
+
98
+ self._input_path = input_path
99
+
100
+
101
+ def run(self, input_file: Path | None = None, parse_only: bool = True) -> None:
102
+ """Run the geo file to create the mesh.
103
+
104
+ Parameters
105
+ ----------
106
+ input_file : Path | None
107
+ Path to the .geo file containing the input.
108
+ Can also be preset using set_input_file. Defaults to "" and ises
109
+ the input file specified using set_input_file.
110
+ parse_only: bool :
111
+ (Default value = True)
112
+
113
+ Returns
114
+ -------
115
+
116
+ Raises
117
+ ------
118
+ RuntimeError
119
+ the path to the gmsh app is empty and must be
120
+ specified first.
121
+ RuntimeError
122
+ the input file string is empty and must be specified
123
+ first.
124
+
125
+ """
126
+ if input_file is not None:
127
+ self.set_input_file(input_file)
128
+
129
+ if self._gmsh_app is None:
130
+ raise RuntimeError("Specify the full path to the gmsh app before calling run.")
131
+
132
+ if self._input_path is None:
133
+ raise RuntimeError("Specify input *.geo file before running gmsh.")
134
+
135
+ arg_list = [str(self._gmsh_app)]
136
+ if parse_only is True:
137
+ arg_list = arg_list+["-parse_and_exit"]
138
+
139
+ self._arg_list = arg_list + [str(self._input_path)]
140
+
141
+ print(f'arg_list={self._arg_list}')
142
+
143
+ subprocess.run(self._arg_list,
144
+ shell=False,
145
+ check=False)
146
+
147
+
148
+ def get_output_path(self) -> Path | None:
149
+ """get_output_path: default return None for gmsh as there is no output
150
+ to be read after the simulation has run. This information is stored in
151
+ the exodus.
152
+
153
+ Parameters
154
+ ----------
155
+
156
+ Returns
157
+ -------
158
+ Path | None
159
+ Default returns None.
160
+
161
+ """
162
+ return None
163
+
@@ -0,0 +1,236 @@
1
+ # ==============================================================================
2
+ # pyvale: the python validation engine
3
+ # License: MIT
4
+ # Copyright (C) 2025 The Computer Aided Validation Team
5
+ # ==============================================================================
6
+ from pathlib import Path
7
+
8
+
9
+ class InputModifier:
10
+ """Class to modify variables in generic text-based input files. Once
11
+ variables have been modified by the user by passing in a dictionary of
12
+ new variables the input can be written to file.
13
+ """
14
+
15
+ def __init__(
16
+ self,
17
+ input_file: Path,
18
+ comment_chars="#",
19
+ end_chars="",
20
+ var_start="_*",
21
+ var_end="**",
22
+ ) -> None:
23
+ """Initialise the class by reading in the input file. Find and read
24
+ any variables that are at the top of the file. Default comment_char
25
+ and end_char are set based on reading MOOSE *.i files.
26
+
27
+
28
+ Parameters
29
+ ----------
30
+ input_file : Path
31
+ Path to the input text file.
32
+ comment_chars : str, optional
33
+ Character or characters for a comment in the input file, by default
34
+ "#".
35
+ end_chars : str, optional
36
+ Character or characters for a line ending the file. If there is no
37
+ line ending character this should be an empty string, by default "".
38
+ var_start : str, optional
39
+ Character sequence to look for in comments for starting the block of
40
+ text to look for variables in, by default "_*".
41
+ var_end : str, optional
42
+ Character sequence to look for in comment for ending the block of
43
+ text to look for variables in, by default "**"
44
+ """
45
+ self._vars = dict({})
46
+ self._input_file = input_file
47
+
48
+ with open(self._input_file, "r", encoding="utf-8") as in_file:
49
+ self._input_lines = in_file.readlines()
50
+
51
+ self._comment_char = comment_chars
52
+ self._end_char = end_chars
53
+
54
+ self._var_start_str = var_start
55
+ self._var_end_str = var_end
56
+
57
+ self._var_start_ind = 0
58
+ self._var_end_ind = -1
59
+
60
+ self.find_vars()
61
+ self.read_vars()
62
+
63
+ def _extract_var_str(self, var_line: str) -> tuple[str, str | float, str]:
64
+ """Helper function to split a string from the input file variable block
65
+ into the variable key, the variable value and any remaining comment.
66
+
67
+ Parameters
68
+ ----------
69
+ var_line : str
70
+ line from the input file to process
71
+
72
+ Returns
73
+ -------
74
+ [str,str | float,str]
75
+ returns a three element list. The first element
76
+ is the variable key, the second is the variable value as a float
77
+ or string, the third is any comment string remaining.
78
+
79
+ """
80
+
81
+ extract_var = var_line.strip()
82
+ extract_var = extract_var.replace(self._end_char, "")
83
+ extract_var = extract_var.split(self._comment_char)[0] # Remove trailing comments should they exist
84
+
85
+ var_key = ""
86
+ var_val = ""
87
+ if extract_var and extract_var.find("=") >= 0:
88
+ var_str = extract_var.split("=", 1)[1]
89
+ var_str = var_str.strip()
90
+ try:
91
+ var_val = float(var_str)
92
+ if var_val.is_integer():
93
+ var_val = int(var_val)
94
+ except ValueError:
95
+ var_val = var_str
96
+
97
+ var_key = extract_var.split("=", 1)[0].strip()
98
+
99
+ if len(var_line.split(self._comment_char)) > 1:
100
+ com_loc = var_line.find(self._comment_char)
101
+ comment_str = var_line[com_loc + len(self._comment_char) :]
102
+ else:
103
+ comment_str = ""
104
+
105
+ return (var_key, var_val, comment_str)
106
+
107
+
108
+ def read_vars(self) -> None:
109
+ """Reads the variables in the file"""
110
+ for ss in self._input_lines[self._var_start_ind + 1 : self._var_end_ind]:
111
+ [var_key, var_val, _] = self._extract_var_str(ss)
112
+ if len(var_key) != 0:
113
+ self._vars[var_key] = var_val
114
+
115
+
116
+ def find_vars(self) -> None:
117
+ """Find what lines the variables are defined on."""
118
+
119
+ # TODO: this needs to be made more robust to multiple occurences of variable block characters.
120
+ # Also need to handle cases when the start and end block characters are the same
121
+
122
+ start_string = self._comment_char + self._var_start_str
123
+ end_string = self._comment_char + self._var_end_str
124
+
125
+ start_found = False
126
+ for ii, ll in enumerate(self._input_lines):
127
+ if start_string in ll and (not start_found):
128
+ self._var_start_ind = ii
129
+ start_found = True
130
+ if end_string in ll:
131
+ self._var_end_ind = ii
132
+ break
133
+
134
+
135
+ def update_vars(self, new_vars: dict) -> None:
136
+ """Updates the variable dictionary that will be written to the input
137
+ file.
138
+
139
+ Parameters
140
+ ----------
141
+ new_vars : dict
142
+ new variables to be written to the input file.
143
+ The keys must exist within the dictionary of variables
144
+ extracted from the input file. Only the variables to be edited
145
+ need to be present.
146
+ new_vars: dict :
147
+
148
+
149
+ Returns
150
+ -------
151
+
152
+ """
153
+ for kk in new_vars:
154
+ if kk in self._vars:
155
+ self._vars[kk] = new_vars[kk]
156
+ else:
157
+ raise KeyError(
158
+ f"Key {kk} does not exist in the variables found in the input file. "
159
+ + "Check input file to make sure the variable exists."
160
+ )
161
+
162
+
163
+ def write_file(self, input_write_file: Path) -> None:
164
+ """Write the input file using the current variable dictionary.
165
+
166
+ Parameters
167
+ ----------
168
+ input_write_file : str
169
+ Path to where the file should be written.
170
+ input_write_file: Path :
171
+
172
+
173
+ Returns
174
+ -------
175
+
176
+ """
177
+ var_block = self._input_lines[self._var_start_ind + 1 : self._var_end_ind]
178
+
179
+ for ii, ll in enumerate(var_block):
180
+ [var_key, _, com_str] = self._extract_var_str(ll)
181
+ if (len(var_key) != 0) and (var_key in self._vars):
182
+ if len(com_str) == 0:
183
+ var_line = f"{var_key} = {self._vars[var_key]}{self._end_char}\n"
184
+ else:
185
+ # NOTE: comment string includes the new line character already
186
+ var_line = f"{var_key} = {self._vars[var_key]}{self._end_char} {self._comment_char}{com_str}"
187
+ line_ind = ii + self._var_start_ind + 1
188
+ self._input_lines[line_ind] = var_line
189
+
190
+ with open(input_write_file, "w", encoding="utf-8") as out_file:
191
+ out_file.writelines(self._input_lines)
192
+
193
+
194
+ def get_vars(self) -> dict:
195
+ """Gets the variables found in the file.
196
+
197
+ Parameters
198
+ ----------
199
+
200
+ Returns
201
+ -------
202
+ dict
203
+ keys are variable strings and values are variable values.
204
+
205
+ """
206
+ return self._vars
207
+
208
+
209
+ def get_var_keys(self) -> list[str]:
210
+ """Gets a list of variable names found in the input file.
211
+
212
+ Parameters
213
+ ----------
214
+
215
+ Returns
216
+ -------
217
+ list[str]
218
+ list of variables name as strings
219
+
220
+ """
221
+ return list(self._vars.keys())
222
+
223
+
224
+ def get_input_file(self) -> Path:
225
+ """Gets the path and input file name.
226
+
227
+ Parameters
228
+ ----------
229
+
230
+ Returns
231
+ -------
232
+ Path
233
+ path and input file name.
234
+
235
+ """
236
+ return self._input_file
@@ -0,0 +1,226 @@
1
+
2
+ # ==============================================================================
3
+ # pyvale: the python validation engine
4
+ # License: MIT
5
+ # Copyright (C) 2025 The Computer Aided Validation Team
6
+ # ==============================================================================
7
+ from typing import Self
8
+ import json
9
+ from pathlib import Path
10
+
11
+
12
+ class MooseConfig:
13
+ """Moose configuration class that handles 1) the path to the main moose
14
+ build, 2) the path to the moose app, and 3) the name of the app to be used
15
+ to construct the command string. These are stored as a dictionary keyed
16
+ with 'main_path', 'app_path' and 'app_name'. This class can also write and
17
+ read json files containing the moose config.
18
+
19
+ Parameters
20
+ ----------
21
+
22
+ Returns
23
+ -------
24
+
25
+ """
26
+ def __init__(self, config: dict[str,Path | str] | None = None) -> None:
27
+
28
+ self._required_keys = ['main_path','app_path','app_name']
29
+
30
+ if config is not None:
31
+ self._check_config_valid(config)
32
+
33
+ self._config = config
34
+
35
+
36
+ def get_config(self) -> dict[str,Path | str]:
37
+ """get_config: returns the config dictionary after checking it is
38
+ valid.
39
+
40
+ Parameters
41
+ ----------
42
+
43
+ Returns
44
+ -------
45
+ dict[str, Path | str]
46
+ dictionary containing the moose config.
47
+
48
+ """
49
+ self._check_config_valid(self._config)
50
+ return self._config # type: ignore
51
+
52
+
53
+ def _check_config_valid(self,
54
+ config: dict[str,Path | str] | None = None
55
+ ) -> None:
56
+ """_check_config_valid: helper function to check if the moose config
57
+ is valid.
58
+
59
+ Parameters
60
+ ----------
61
+ config : dict[str, Path | str] | None = None
62
+ Dictionary containing the moose config. Defaults to None.
63
+
64
+ Returns
65
+ -------
66
+
67
+ Raises
68
+ ------
69
+ MooseConfigError
70
+ Dicitionary not initialised.
71
+ MooseConfigError
72
+ Dicitionary does not contain the required keys.
73
+ MooseConfigError
74
+ Path to MOOSE does not exist.
75
+ MooseConfigError
76
+ Path to MOOSE app does not exist.
77
+
78
+ """
79
+ if config is None:
80
+ raise MooseConfigError(
81
+ 'Config dictionary must be initialised, load config file first.')
82
+
83
+ for kk in self._required_keys:
84
+ if kk not in config:
85
+ raise MooseConfigError(
86
+ f'Config dictionary must contain all keys: {self._required_keys}')
87
+
88
+ if not config['main_path'].is_dir(): # type: ignore
89
+ raise MooseConfigError(
90
+ "Main path to MOOSE does not exist. Check path at key 'main_path'.")
91
+
92
+ if not config['app_path'].is_dir(): # type: ignore
93
+ raise MooseConfigError(
94
+ "MOOSE app path does not exist. Check path at key: 'app_path'.")
95
+
96
+
97
+ def convert_path_to_str(self, in_config: dict[str,Path | str] | None
98
+ ) -> dict[str,str] | None:
99
+ """convert_path_to_str: converts all paths in the config dictionary to
100
+ strings so that it can be saved to json.
101
+
102
+ Parameters
103
+ ----------
104
+ in_config : dict[str, Path | str] | None
105
+ Dictionary containing the moose config. Defaults to None.
106
+
107
+ Returns
108
+ -------
109
+ dict[str,str] | None
110
+ as input with Paths converted to strings.
111
+
112
+ """
113
+ if in_config is None:
114
+ return None
115
+
116
+ conv_config = dict({})
117
+ for kk in in_config:
118
+ conv_config[kk] = str(in_config[kk])
119
+
120
+ return conv_config
121
+
122
+
123
+ def convert_str_to_path(self, in_config: dict[str,str] | None = None
124
+ ) -> dict[str, Path | str] | None:
125
+ """convert_str_to_path: helper function to convert string to Path for
126
+ readin in json dictionary. Does not check if the paths exist.
127
+
128
+ Parameters
129
+ ----------
130
+ in_config : dict[str, Path | str] | None
131
+ Dictionary containing the moose config. Defaults to None.
132
+
133
+ Returns
134
+ -------
135
+ dict[str, Path | str] | None
136
+ as in_config but strings to main_path
137
+ and app_path are converted to Path.
138
+
139
+ """
140
+ if in_config is None:
141
+ return None
142
+
143
+ conv_config = dict({})
144
+ for kk in in_config:
145
+ if 'path' in kk:
146
+ conv_config[kk] = Path(in_config[kk])
147
+ else:
148
+ conv_config[kk] = in_config[kk]
149
+
150
+ return conv_config
151
+
152
+
153
+ def save_config(self, config_path: Path) -> None:
154
+ """save_config: saves the moose config dictionary as a json file in the
155
+ specified path.
156
+
157
+ Parameters
158
+ ----------
159
+ config_path : Path
160
+ path and file name with extension .json to save the moose config
161
+ dictionary.
162
+
163
+ Returns
164
+ -------
165
+
166
+ Raises
167
+ ------
168
+ MooseConfigError
169
+ Parent path to save config file does not exist.
170
+
171
+ """
172
+ if not config_path.parent.is_dir():
173
+ raise MooseConfigError('Parent path to save config file does not exist.')
174
+
175
+ with open(config_path, 'w', encoding='utf-8') as cf:
176
+ config_strs = self.convert_path_to_str(self._config)
177
+ json.dump(config_strs, cf, indent=4)
178
+
179
+
180
+ def read_config(self, config_path: Path) -> Self:
181
+ """read_config: reads the moose json configuration file at the
182
+ specified path. Checks if the configuration is valid and raises a
183
+ MooseConfigError if it is not.
184
+
185
+ Parameters
186
+ ----------
187
+ config_path : Path
188
+ path to the json config file containing the
189
+
190
+ Returns
191
+ -------
192
+ Self
193
+ returns a MooseConfig object allowing the config to be
194
+ initialised by directly loading a json config file.
195
+
196
+ Raises
197
+ ------
198
+ MooseConfigError
199
+ MOOSE config file does not exist.
200
+
201
+ """
202
+ if not config_path.is_file():
203
+ raise MooseConfigError(
204
+ f'MOOSE config file does not exist at: {str(config_path)}.')
205
+
206
+ with open(config_path, 'r', encoding='utf-8') as cf:
207
+ config_strs = json.load(cf)
208
+ config_paths = self.convert_str_to_path(config_strs)
209
+
210
+ self._check_config_valid(config_paths)
211
+
212
+ self._config = config_paths
213
+ return self
214
+
215
+
216
+ class MooseConfigError(Exception):
217
+ """MooseConfigError: custom error class for flagging errors with the moose
218
+ configuration.
219
+
220
+ Parameters
221
+ ----------
222
+
223
+ Returns
224
+ -------
225
+
226
+ """