Coverage for src/meshpy/four_c/run_four_c.py: 57%
58 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-28 04:21 +0000
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-28 04:21 +0000
1# The MIT License (MIT)
2#
3# Copyright (c) 2018-2025 MeshPy Authors
4#
5# Permission is hereby granted, free of charge, to any person obtaining a copy
6# of this software and associated documentation files (the "Software"), to deal
7# in the Software without restriction, including without limitation the rights
8# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9# copies of the Software, and to permit persons to whom the Software is
10# furnished to do so, subject to the following conditions:
11#
12# The above copyright notice and this permission notice shall be included in
13# all copies or substantial portions of the Software.
14#
15# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21# THE SOFTWARE.
22"""Provide a function that allows to run 4C."""
24import os as _os
25import shutil as _shutil
26import subprocess as _subprocess # nosec B404
27import sys as _sys
28from pathlib import Path as _Path
30from meshpy.utils.environment import get_env_variable as _get_env_variable
33def run_four_c(
34 input_file,
35 output_dir,
36 *,
37 four_c_exe=None,
38 mpi_command=None,
39 n_proc=None,
40 output_name="xxx",
41 restart_step=None,
42 restart_from=None,
43 log_to_console=False,
44):
45 """Run a 4C simulation and return the exit code of the run.
47 This function looks into the environment variables for some parameters:
48 "MESHPY_FOUR_C_EXE"
49 "MESHPY_MPI_COMMAND"
50 "MESHPY_MPI_NUM_PROC"
51 If the corresponding keyword arguments are set, they overwrite the environment
52 variable.
54 Args
55 ----
56 input_file: str
57 Path to the input file on the filesystem
58 output_dir: str
59 Directory where the simulation should be performed (will be created if
60 it does not exist)
61 four_c_exe: str
62 Optionally explicitly specify path to the 4C executable
63 mpi_command: str
64 Command to launch MPI, defaults to "mpirun"
65 n_proc: int
66 Number of process used with MPI, defaults to 1
67 output_name: str
68 Base name of the output files
69 restart_step: int
70 Time step to restart from
71 restart_from: str
72 Path to initial simulation (relative to output_dir)
73 log_to_console: bool
74 If the 4C simulation output should be shown in the console.
76 Return
77 ----
78 return_code: int
79 Return code of 4C run
80 """
82 # Fist get all needed parameters
83 if four_c_exe is None:
84 four_c_exe = _get_env_variable("MESHPY_FOUR_C_EXE")
85 if mpi_command is None:
86 mpi_command = _get_env_variable("MESHPY_MPI_COMMAND", default="mpirun")
87 if n_proc is None:
88 n_proc = _get_env_variable("MESHPY_MPI_NUM_PROC", default="1")
90 # Setup paths and actual command to run
91 _os.makedirs(output_dir, exist_ok=True)
92 log_file = _os.path.join(output_dir, output_name + ".log")
93 error_file = _os.path.join(output_dir, output_name + ".err")
94 command = mpi_command.split(" ") + [
95 "-np",
96 str(n_proc),
97 four_c_exe,
98 input_file,
99 output_name,
100 ]
101 if restart_step is None and restart_from is None:
102 pass
103 elif restart_step is not None and restart_from is not None:
104 command.extend([f"restart={restart_step}", f"restartfrom={restart_from}"])
105 else:
106 raise ValueError(
107 "Provide either both or no argument of [restart_step, restart_from]"
108 )
110 # Actually run the command
111 with open(log_file, "w") as stdout_file, open(error_file, "w") as stderr_file:
112 process = _subprocess.Popen(
113 command, # nosec B603
114 stdout=_subprocess.PIPE,
115 stderr=_subprocess.PIPE,
116 cwd=output_dir,
117 text=True,
118 )
120 for stdout_line in process.stdout:
121 if log_to_console:
122 _sys.stdout.write(stdout_line)
123 stdout_file.write(stdout_line)
125 for stderr_line in process.stderr:
126 if log_to_console:
127 _sys.stderr.write(stderr_line)
128 stderr_file.write(stderr_line)
130 process.stdout.close()
131 process.stderr.close()
132 return_code = process.wait()
133 return return_code
136def clean_simulation_directory(sim_dir, *, ask_before_clean=False):
137 """Clear the simulation directory. If it does not exist, it is created.
138 Optionally the user can be asked before a deletion of files.
140 Args
141 ----
142 sim_dir:
143 Path to a directory
144 ask_before_clean: bool
145 Flag which indicates whether the user must confirm removal of files and directories
146 """
148 # Check if simulation directory exists.
149 if _os.path.exists(sim_dir):
150 if ask_before_clean:
151 print(f'Path "{sim_dir}" already exists')
152 while True:
153 if ask_before_clean:
154 answer = input("DELETE all contents? (y/n): ")
155 else:
156 answer = "y"
157 if answer.lower() == "y":
158 for filename in _os.listdir(sim_dir):
159 file_path = _os.path.join(sim_dir, filename)
160 try:
161 if _os.path.isfile(file_path) or _os.path.islink(file_path):
162 _os.unlink(file_path)
163 elif _os.path.isdir(file_path):
164 _shutil.rmtree(file_path)
165 except Exception as e:
166 raise ValueError(f"Failed to delete {file_path}. Reason: {e}")
167 return
168 elif answer.lower() == "n":
169 raise ValueError("Directory is not deleted!")
170 else:
171 _Path(sim_dir).mkdir(parents=True, exist_ok=True)