pyvale 2025.7.2__cp311-cp311-win_amd64.whl → 2025.8.1__cp311-cp311-win_amd64.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.
- pyvale/__init__.py +12 -92
- pyvale/blender/__init__.py +23 -0
- pyvale/{pyvaleexceptions.py → blender/blenderexceptions.py} +0 -3
- pyvale/{blenderlightdata.py → blender/blenderlightdata.py} +3 -3
- pyvale/{blendermaterialdata.py → blender/blendermaterialdata.py} +1 -1
- pyvale/{blenderrenderdata.py → blender/blenderrenderdata.py} +5 -3
- pyvale/{blenderscene.py → blender/blenderscene.py} +33 -30
- pyvale/{blendertools.py → blender/blendertools.py} +14 -10
- pyvale/dataset/__init__.py +7 -0
- pyvale/dataset/dataset.py +443 -0
- pyvale/dic/__init__.py +20 -0
- pyvale/{dic2d.py → dic/dic2d.py} +31 -36
- pyvale/dic/dic2dconv.py +6 -0
- pyvale/{dic2dcpp.cp311-win_amd64.pyd → dic/dic2dcpp.cp311-win_amd64.pyd} +0 -0
- pyvale/{dicdataimport.py → dic/dicdataimport.py} +8 -8
- pyvale/{dicregionofinterest.py → dic/dicregionofinterest.py} +1 -1
- pyvale/{dicresults.py → dic/dicresults.py} +1 -1
- pyvale/{dicstrain.py → dic/dicstrain.py} +9 -9
- pyvale/examples/basics/{ex1_1_basicscalars_therm2d.py → ex1a_basicscalars_therm2d.py} +12 -9
- pyvale/examples/basics/{ex1_2_sensormodel_therm2d.py → ex1b_sensormodel_therm2d.py} +17 -14
- pyvale/examples/basics/{ex1_3_customsens_therm3d.py → ex1c_customsens_therm3d.py} +27 -24
- pyvale/examples/basics/{ex1_4_basicerrors_therm3d.py → ex1d_basicerrors_therm3d.py} +32 -29
- pyvale/examples/basics/{ex1_5_fielderrs_therm3d.py → ex1e_fielderrs_therm3d.py} +19 -15
- pyvale/examples/basics/{ex1_6_caliberrs_therm2d.py → ex1f_caliberrs_therm2d.py} +20 -16
- pyvale/examples/basics/{ex1_7_spatavg_therm2d.py → ex1g_spatavg_therm2d.py} +19 -16
- pyvale/examples/basics/{ex2_1_basicvectors_disp2d.py → ex2a_basicvectors_disp2d.py} +13 -10
- pyvale/examples/basics/{ex2_2_vectorsens_disp2d.py → ex2b_vectorsens_disp2d.py} +19 -15
- pyvale/examples/basics/{ex2_3_sensangle_disp2d.py → ex2c_sensangle_disp2d.py} +21 -18
- pyvale/examples/basics/{ex2_4_chainfielderrs_disp2d.py → ex2d_chainfielderrs_disp2d.py} +31 -29
- pyvale/examples/basics/{ex2_5_vectorfields3d_disp3d.py → ex2e_vectorfields3d_disp3d.py} +21 -18
- pyvale/examples/basics/{ex3_1_basictensors_strain2d.py → ex3a_basictensors_strain2d.py} +16 -14
- pyvale/examples/basics/{ex3_2_tensorsens2d_strain2d.py → ex3b_tensorsens2d_strain2d.py} +17 -14
- pyvale/examples/basics/{ex3_3_tensorsens3d_strain3d.py → ex3c_tensorsens3d_strain3d.py} +25 -22
- pyvale/examples/basics/{ex4_1_expsim2d_thermmech2d.py → ex4a_expsim2d_thermmech2d.py} +17 -14
- pyvale/examples/basics/{ex4_2_expsim3d_thermmech3d.py → ex4b_expsim3d_thermmech3d.py} +37 -34
- pyvale/examples/basics/ex5_nomesh.py +24 -0
- pyvale/examples/dic/ex1_2_blenderdeformed.py +174 -0
- pyvale/examples/dic/ex1_region_of_interest.py +6 -3
- pyvale/examples/dic/ex2_plate_with_hole.py +21 -18
- pyvale/examples/dic/ex3_plate_with_hole_strain.py +8 -6
- pyvale/examples/dic/ex4_dic_blender.py +17 -15
- pyvale/examples/dic/ex5_dic_challenge.py +19 -14
- pyvale/examples/genanalyticdata/ex1_1_scalarvisualisation.py +16 -10
- pyvale/examples/genanalyticdata/ex1_2_scalarcasebuild.py +3 -3
- pyvale/examples/genanalyticdata/ex2_1_analyticsensors.py +29 -23
- pyvale/examples/genanalyticdata/ex2_2_analyticsensors_nomesh.py +67 -0
- pyvale/examples/imagedef2d/ex_imagedef2d_todisk.py +12 -9
- pyvale/examples/mooseherder/ex0_create_moose_config.py +65 -0
- pyvale/examples/mooseherder/ex1a_modify_moose_input.py +71 -0
- pyvale/examples/mooseherder/ex1b_modify_gmsh_input.py +69 -0
- pyvale/examples/mooseherder/ex2a_run_moose_once.py +80 -0
- pyvale/examples/mooseherder/ex2b_run_gmsh_once.py +64 -0
- pyvale/examples/mooseherder/ex2c_run_both_once.py +114 -0
- pyvale/examples/mooseherder/ex3_run_moose_seq_para.py +157 -0
- pyvale/examples/mooseherder/ex4_run_gmsh-moose_seq_para.py +176 -0
- pyvale/examples/mooseherder/ex5_run_moose_paramulti.py +136 -0
- pyvale/examples/mooseherder/ex6_read_moose_exodus.py +163 -0
- pyvale/examples/mooseherder/ex7a_read_moose_herd_results.py +153 -0
- pyvale/examples/mooseherder/ex7b_read_multi_herd_results.py +116 -0
- pyvale/examples/mooseherder/ex7c_read_multi_gmshmoose_results.py +127 -0
- pyvale/examples/mooseherder/ex7d_readconfig_multi_gmshmoose_results.py +143 -0
- pyvale/examples/mooseherder/ex8_read_existing_sweep_output.py +72 -0
- pyvale/examples/renderblender/ex1_1_blenderscene.py +24 -20
- pyvale/examples/renderblender/ex1_2_blenderdeformed.py +22 -18
- pyvale/examples/renderblender/ex2_1_stereoscene.py +36 -29
- pyvale/examples/renderblender/ex2_2_stereodeformed.py +26 -20
- pyvale/examples/renderblender/ex3_1_blendercalibration.py +24 -17
- pyvale/examples/renderrasterisation/ex_rastenp.py +14 -12
- pyvale/examples/renderrasterisation/ex_rastercyth_oneframe.py +14 -15
- pyvale/examples/renderrasterisation/ex_rastercyth_static_cypara.py +13 -11
- pyvale/examples/renderrasterisation/ex_rastercyth_static_pypara.py +13 -11
- pyvale/mooseherder/__init__.py +32 -0
- pyvale/mooseherder/directorymanager.py +416 -0
- pyvale/mooseherder/exodusreader.py +763 -0
- pyvale/mooseherder/gmshrunner.py +163 -0
- pyvale/mooseherder/inputmodifier.py +236 -0
- pyvale/mooseherder/mooseconfig.py +226 -0
- pyvale/mooseherder/mooseherd.py +527 -0
- pyvale/mooseherder/mooserunner.py +303 -0
- pyvale/mooseherder/outputreader.py +22 -0
- pyvale/mooseherder/simdata.py +92 -0
- pyvale/mooseherder/simrunner.py +31 -0
- pyvale/mooseherder/sweepreader.py +356 -0
- pyvale/mooseherder/sweeptools.py +76 -0
- pyvale/sensorsim/__init__.py +82 -0
- pyvale/{camera.py → sensorsim/camera.py} +7 -7
- pyvale/{camerasensor.py → sensorsim/camerasensor.py} +7 -7
- pyvale/{camerastereo.py → sensorsim/camerastereo.py} +2 -2
- pyvale/{cameratools.py → sensorsim/cameratools.py} +4 -4
- pyvale/{cython → sensorsim/cython}/rastercyth.c +596 -596
- pyvale/{cython → sensorsim/cython}/rastercyth.cp311-win_amd64.pyd +0 -0
- pyvale/{cython → sensorsim/cython}/rastercyth.py +16 -17
- pyvale/{errorcalculator.py → sensorsim/errorcalculator.py} +1 -1
- pyvale/{errorintegrator.py → sensorsim/errorintegrator.py} +2 -2
- pyvale/{errorrand.py → sensorsim/errorrand.py} +4 -4
- pyvale/{errorsyscalib.py → sensorsim/errorsyscalib.py} +2 -2
- pyvale/{errorsysdep.py → sensorsim/errorsysdep.py} +2 -2
- pyvale/{errorsysfield.py → sensorsim/errorsysfield.py} +8 -8
- pyvale/{errorsysindep.py → sensorsim/errorsysindep.py} +3 -3
- pyvale/sensorsim/exceptions.py +8 -0
- pyvale/{experimentsimulator.py → sensorsim/experimentsimulator.py} +23 -3
- pyvale/{field.py → sensorsim/field.py} +1 -1
- pyvale/{fieldconverter.py → sensorsim/fieldconverter.py} +72 -19
- pyvale/sensorsim/fieldinterp.py +37 -0
- pyvale/sensorsim/fieldinterpmesh.py +124 -0
- pyvale/sensorsim/fieldinterppoints.py +55 -0
- pyvale/{fieldsampler.py → sensorsim/fieldsampler.py} +4 -4
- pyvale/{fieldscalar.py → sensorsim/fieldscalar.py} +28 -24
- pyvale/{fieldtensor.py → sensorsim/fieldtensor.py} +33 -31
- pyvale/{fieldvector.py → sensorsim/fieldvector.py} +33 -31
- pyvale/{imagedef2d.py → sensorsim/imagedef2d.py} +9 -5
- pyvale/{integratorfactory.py → sensorsim/integratorfactory.py} +6 -6
- pyvale/{integratorquadrature.py → sensorsim/integratorquadrature.py} +3 -3
- pyvale/{integratorrectangle.py → sensorsim/integratorrectangle.py} +3 -3
- pyvale/{integratorspatial.py → sensorsim/integratorspatial.py} +1 -1
- pyvale/{rastercy.py → sensorsim/rastercy.py} +5 -5
- pyvale/{rasternp.py → sensorsim/rasternp.py} +9 -9
- pyvale/{rasteropts.py → sensorsim/rasteropts.py} +1 -1
- pyvale/{renderer.py → sensorsim/renderer.py} +1 -1
- pyvale/{rendermesh.py → sensorsim/rendermesh.py} +5 -5
- pyvale/{renderscene.py → sensorsim/renderscene.py} +2 -2
- pyvale/{sensorarray.py → sensorsim/sensorarray.py} +1 -1
- pyvale/{sensorarrayfactory.py → sensorsim/sensorarrayfactory.py} +12 -12
- pyvale/{sensorarraypoint.py → sensorsim/sensorarraypoint.py} +10 -8
- pyvale/{sensordata.py → sensorsim/sensordata.py} +1 -1
- pyvale/{sensortools.py → sensorsim/sensortools.py} +2 -20
- pyvale/sensorsim/simtools.py +174 -0
- pyvale/{visualexpplotter.py → sensorsim/visualexpplotter.py} +3 -3
- pyvale/{visualimages.py → sensorsim/visualimages.py} +2 -2
- pyvale/{visualsimanimator.py → sensorsim/visualsimanimator.py} +4 -4
- pyvale/{visualsimplotter.py → sensorsim/visualsimplotter.py} +5 -5
- pyvale/{visualsimsensors.py → sensorsim/visualsimsensors.py} +12 -12
- pyvale/{visualtools.py → sensorsim/visualtools.py} +1 -1
- pyvale/{visualtraceplotter.py → sensorsim/visualtraceplotter.py} +2 -2
- pyvale/simcases/case17.geo +3 -0
- pyvale/simcases/case17.i +4 -4
- pyvale/simcases/run_1case.py +1 -9
- pyvale/simcases/run_all_cases.py +1 -1
- pyvale/simcases/run_build_case.py +1 -1
- pyvale/simcases/run_example_cases.py +1 -1
- pyvale/verif/__init__.py +12 -0
- pyvale/{analyticsimdatafactory.py → verif/analyticsimdatafactory.py} +2 -2
- pyvale/{analyticsimdatagenerator.py → verif/analyticsimdatagenerator.py} +2 -2
- pyvale/verif/psens.py +125 -0
- pyvale/verif/psensconst.py +18 -0
- pyvale/verif/psensmech.py +227 -0
- pyvale/verif/psensmultiphys.py +187 -0
- pyvale/verif/psensscalar.py +347 -0
- pyvale/verif/psenstensor.py +123 -0
- pyvale/verif/psensvector.py +116 -0
- {pyvale-2025.7.2.dist-info → pyvale-2025.8.1.dist-info}/METADATA +6 -7
- pyvale-2025.8.1.dist-info/RECORD +260 -0
- pyvale/dataset.py +0 -415
- pyvale/simtools.py +0 -67
- pyvale-2025.7.2.dist-info/RECORD +0 -212
- /pyvale/{blendercalibrationdata.py → blender/blendercalibrationdata.py} +0 -0
- /pyvale/{dicchecks.py → dic/dicchecks.py} +0 -0
- /pyvale/{dicspecklegenerator.py → dic/dicspecklegenerator.py} +0 -0
- /pyvale/{dicspecklequality.py → dic/dicspecklequality.py} +0 -0
- /pyvale/{dicstrainresults.py → dic/dicstrainresults.py} +0 -0
- /pyvale/{cameradata.py → sensorsim/cameradata.py} +0 -0
- /pyvale/{cameradata2d.py → sensorsim/cameradata2d.py} +0 -0
- /pyvale/{errordriftcalc.py → sensorsim/errordriftcalc.py} +0 -0
- /pyvale/{fieldtransform.py → sensorsim/fieldtransform.py} +0 -0
- /pyvale/{generatorsrandom.py → sensorsim/generatorsrandom.py} +0 -0
- /pyvale/{imagetools.py → sensorsim/imagetools.py} +0 -0
- /pyvale/{integratortype.py → sensorsim/integratortype.py} +0 -0
- /pyvale/{output.py → sensorsim/output.py} +0 -0
- /pyvale/{raster.py → sensorsim/raster.py} +0 -0
- /pyvale/{sensordescriptor.py → sensorsim/sensordescriptor.py} +0 -0
- /pyvale/{visualimagedef.py → sensorsim/visualimagedef.py} +0 -0
- /pyvale/{visualopts.py → sensorsim/visualopts.py} +0 -0
- /pyvale/{analyticmeshgen.py → verif/analyticmeshgen.py} +0 -0
- {pyvale-2025.7.2.dist-info → pyvale-2025.8.1.dist-info}/WHEEL +0 -0
- {pyvale-2025.7.2.dist-info → pyvale-2025.8.1.dist-info}/licenses/LICENSE +0 -0
- {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
|
+
"""
|