Skip to content

Commit

Permalink
Merge pull request #204 from henryleberre/master
Browse files Browse the repository at this point in the history
Save build artifacts for all configurations (among other things)
  • Loading branch information
henryleberre authored Aug 27, 2023
2 parents d2062cf + ec68896 commit ddbf707
Show file tree
Hide file tree
Showing 8 changed files with 224 additions and 157 deletions.
4 changes: 3 additions & 1 deletion src/simulation/m_global_parameters.fpp
Original file line number Diff line number Diff line change
Expand Up @@ -718,6 +718,8 @@ contains
if (fluid_pp(i)%Re(1) > 0) Re_size(1) = Re_size(1) + 1
if (fluid_pp(i)%Re(2) > 0) Re_size(2) = Re_size(2) + 1
end do

!$acc update device(Re_size)

! Bookkeeping the indexes of any viscous fluids and any pairs of
! fluids whose interface will support effects of surface tension
Expand Down Expand Up @@ -770,8 +772,8 @@ contains
! cell-boundary values or otherwise, the unaltered left and right,
! WENO-reconstructed, cell-boundary values
wa_flg = 0d0; IF(weno_avg) wa_flg = 1d0
!$acc update device(wa_flg)

!$acc update device(Re_size)
! Determining the number of cells that are needed in order to store
! sufficient boundary conditions data as to iterate the solution in
! the physical computational domain from one time-step iteration to
Expand Down
19 changes: 10 additions & 9 deletions toolchain/mfc/args.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,15 +74,16 @@ def add_common_arguments(p, mask = None):

# === TEST ===
add_common_arguments(test, "t")
test.add_argument("-l", "--list", action="store_true", help="List all available tests.")
test.add_argument("-f", "--from", default=TEST_CASES[0].get_uuid(), type=str, help="First test UUID to run.")
test.add_argument("-t", "--to", default=TEST_CASES[-1].get_uuid(), type=str, help="Last test UUID to run.")
test.add_argument("-o", "--only", nargs="+", type=str, default=[], metavar="L", help="Only run tests with UUIDs or hashes L.")
test.add_argument("-b", "--binary", choices=binaries, type=str, default=None, help="(Serial) Override MPI execution binary")
test.add_argument("-r", "--relentless", action="store_true", default=False, help="Run all tests, even if multiple fail.")
test.add_argument("-a", "--test-all", action="store_true", default=False, help="Run the Post Process Tests too.")
test.add_argument("-g", "--gpus", type=str, default="0", help="(GPU) Comma separated list of GPU #s to use.")
test.add_argument("-%", "--percent", type=int, default=100, help="Percentage of tests to run.")
test.add_argument("-l", "--list", action="store_true", help="List all available tests.")
test.add_argument("-f", "--from", default=TEST_CASES[0].get_uuid(), type=str, help="First test UUID to run.")
test.add_argument("-t", "--to", default=TEST_CASES[-1].get_uuid(), type=str, help="Last test UUID to run.")
test.add_argument("-o", "--only", nargs="+", type=str, default=[], metavar="L", help="Only run tests with UUIDs or hashes L.")
test.add_argument("-b", "--binary", choices=binaries, type=str, default=None, help="(Serial) Override MPI execution binary")
test.add_argument("-r", "--relentless", action="store_true", default=False, help="Run all tests, even if multiple fail.")
test.add_argument("-a", "--test-all", action="store_true", default=False, help="Run the Post Process Tests too.")
test.add_argument("-g", "--gpus", type=str, default="0", help="(GPU) Comma separated list of GPU #s to use.")
test.add_argument("-%", "--percent", type=int, default=100, help="Percentage of tests to run.")
test.add_argument("-m", "--max-attempts", type=int, default=3, help="Maximum number of attempts to run a test.")

test.add_argument("--case-optimization", action="store_true", default=False, help="(GPU Optimization) Compile MFC targets with some case parameters hard-coded.")

Expand Down
89 changes: 60 additions & 29 deletions toolchain/mfc/build.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import os, typing, dataclasses

from .state import ARG
from .state import ARG, CFG
from .printer import cons
from . import common
from .run.input import MFCInputFile
Expand All @@ -20,25 +20,24 @@ def compute(self) -> typing.List[str]:

return r

name: str
flags: typing.List[str]
isDependency: bool
isDefault: bool
isRequired: bool
requires: Dependencies
name: str # Name of the target
flags: typing.List[str] # Extra flags to pass to CMake
isDependency: bool # Is it a dependency of an MFC target?
isDefault: bool # Should it be built by default? (unspecified -t | --targets)
isRequired: bool # Should it always be built? (no matter what -t | --targets is)
requires: Dependencies # Build dependencies of the target


TARGETS: typing.List[MFCTarget] = [
MFCTarget('fftw', ['-DMFC_FFTW=ON'], True, False, False, MFCTarget.Dependencies([], [], [])),
MFCTarget('hdf5', ['-DMFC_HDF5=ON'], True, False, False, MFCTarget.Dependencies([], [], [])),
MFCTarget('silo', ['-DMFC_SILO=ON'], True, False, False, MFCTarget.Dependencies(["hdf5"], [], [])),
MFCTarget('pre_process', ['-DMFC_PRE_PROCESS=ON'], False, True, False, MFCTarget.Dependencies([], [], [])),
MFCTarget('simulation', ['-DMFC_SIMULATION=ON'], False, True, False, MFCTarget.Dependencies([], ["fftw"], [])),
MFCTarget('post_process', ['-DMFC_POST_PROCESS=ON'], False, True, False, MFCTarget.Dependencies(['fftw', 'silo'], [], [])),
MFCTarget('syscheck', ['-DMFC_SYSCHECK=ON'], False, False, True, MFCTarget.Dependencies([], [], [])),
MFCTarget('documentation', ['-DMFC_DOCUMENTATION=ON'], False, False, False, MFCTarget.Dependencies([], [], []))
]
FFTW = MFCTarget('fftw', ['-DMFC_FFTW=ON'], True, False, False, MFCTarget.Dependencies([], [], []))
HDF5 = MFCTarget('hdf5', ['-DMFC_HDF5=ON'], True, False, False, MFCTarget.Dependencies([], [], []))
SILO = MFCTarget('silo', ['-DMFC_SILO=ON'], True, False, False, MFCTarget.Dependencies(["hdf5"], [], []))
PRE_PROCESS = MFCTarget('pre_process', ['-DMFC_PRE_PROCESS=ON'], False, True, False, MFCTarget.Dependencies([], [], []))
SIMULATION = MFCTarget('simulation', ['-DMFC_SIMULATION=ON'], False, True, False, MFCTarget.Dependencies([], ["fftw"], []))
POST_PROCESS = MFCTarget('post_process', ['-DMFC_POST_PROCESS=ON'], False, True, False, MFCTarget.Dependencies(['fftw', 'silo'], [], []))
SYSCHECK = MFCTarget('syscheck', ['-DMFC_SYSCHECK=ON'], False, False, True, MFCTarget.Dependencies([], [], []))
DOCUMENTATION = MFCTarget('documentation', ['-DMFC_DOCUMENTATION=ON'], False, False, False, MFCTarget.Dependencies([], [], []))

TARGETS: typing.List[MFCTarget] = [ FFTW, HDF5, SILO, PRE_PROCESS, SIMULATION, POST_PROCESS, SYSCHECK, DOCUMENTATION ]

def get_mfc_target_names() -> typing.List[str]:
return [ target.name for target in TARGETS if target.isDefault ]
Expand Down Expand Up @@ -66,23 +65,53 @@ def get_target(name: str) -> MFCTarget:

# Get path to directory that will store the build files
def get_build_dirpath(target: MFCTarget) -> str:
return os.sep.join([os.getcwd(), "build", target.name])
return os.sep.join([
os.getcwd(),
"build",
[CFG().make_slug(), 'dependencies'][int(target.isDependency)],
target.name
])


# Get the directory that contains the target's CMakeLists.txt
def get_cmake_dirpath(target: MFCTarget) -> str:
subdir = ["", os.sep.join(["toolchain", "dependencies"])][int(target.isDependency)]

return os.sep.join([os.getcwd(), subdir])

# The CMakeLists.txt file is located:
# * Regular: <root>/CMakelists.txt
# * Dependency: <root>/toolchain/dependencies/CMakelists.txt
return os.sep.join([
os.getcwd(),
os.sep.join(["toolchain", "dependencies"]) if target.isDependency else "",
])


def get_install_dirpath(target: MFCTarget) -> str:
# The install directory is located:
# Regular: <root>/build/install/<configuration_slug>
# Dependency: <root>/build/install/dependencies (shared)
return os.sep.join([
os.getcwd(),
"build",
"install",
'dependencies' if target.isDependency else CFG().make_slug()
])


def get_dependency_install_dirpath() -> str:
# Since dependencies share the same install directory, we can just return
# the install directory of the first dependency we find.
for target in TARGETS:
if target.isDependency:
return get_install_dirpath(target)

def get_install_dirpath() -> str:
return os.sep.join([os.getcwd(), "build", "install"])
raise common.MFCException("No dependency target found.")


def is_target_configured(target: MFCTarget) -> bool:
cmake_cachepath = os.sep.join([get_build_dirpath(target), "CMakeCache.txt"])
return os.path.isfile(cmake_cachepath)
# We assume that if the CMakeCache.txt file exists, then the target is
# configured. (this isn't perfect, but it's good enough for now)
return os.path.isfile(
os.sep.join([get_build_dirpath(target), "CMakeCache.txt"])
)


def clean_target(name: str):
Expand Down Expand Up @@ -146,7 +175,9 @@ def build_target(name: str, history: typing.List[str] = None):

build_dirpath = get_build_dirpath(target)
cmake_dirpath = get_cmake_dirpath(target)
install_dirpath = get_install_dirpath()
install_dirpath = get_install_dirpath(target)

install_prefixes = ';'.join([install_dirpath, get_dependency_install_dirpath()])

flags: list = target.flags.copy() + [
# Disable CMake warnings intended for developers (us).
Expand All @@ -163,10 +194,10 @@ def build_target(name: str, history: typing.List[str] = None):
# second heighest level of priority, still letting users manually
# specify <PackageName>_ROOT, which has precedence over CMAKE_PREFIX_PATH.
# See: https://cmake.org/cmake/help/latest/command/find_package.html.
f"-DCMAKE_PREFIX_PATH={install_dirpath}",
f"-DCMAKE_PREFIX_PATH={install_prefixes}",
# First directory that FIND_LIBRARY searches.
# See: https://cmake.org/cmake/help/latest/command/find_library.html.
f"-DCMAKE_FIND_ROOT_PATH={install_dirpath}",
f"-DCMAKE_FIND_ROOT_PATH={install_prefixes}",
# Location prefix to install bin/, lib/, include/, etc.
# See: https://cmake.org/cmake/help/latest/command/install.html.
f"-DCMAKE_INSTALL_PREFIX={install_dirpath}",
Expand Down
7 changes: 1 addition & 6 deletions toolchain/mfc/lock.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import os, dataclasses

from . import build, state, common
from . import state, common
from .state import MFCConfig
from .printer import cons

Expand Down Expand Up @@ -68,8 +68,3 @@ def switch(to: MFCConfig):
data.config = to
state.gCFG = to
write()

for target_name in build.get_mfc_target_names() + build.get_required_target_names():
dirpath = build.get_build_dirpath(build.get_target(target_name))
cons.print(f"[bold red]Removing {os.path.relpath(dirpath)}[/bold red]")
common.delete_directory(dirpath)
6 changes: 3 additions & 3 deletions toolchain/mfc/packer/tol.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,13 @@ def raise_err(msg: str):
"""

if math.isnan(gVal):
raise_err("is NaN in the golden file")
return raise_err("is NaN in the golden file")

if math.isnan(cVal):
raise_err("is NaN in the pack file")
return raise_err("is NaN in the pack file")

if not is_close(error, tol):
raise_err("is not within tolerance")
return raise_err("is not within tolerance")

# Return the average relative error
return avg_err.get(), None
42 changes: 25 additions & 17 deletions toolchain/mfc/run/engines.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,13 @@ def run(self, names: typing.List[str]) -> None:
raise common.MFCException(f"MFCEngine::run: not implemented for {self.name}.")

def get_binpath(self, target: str) -> str:
return os.sep.join([build.get_install_dirpath(), "bin", target])
# <root>/install/<slug>/bin/<target>
prefix = build.get_install_dirpath(build.get_target(target))
return os.sep.join([prefix, "bin", target])


def _interactive_working_worker(cmd, q):
def _interactive_working_worker(cmd: typing.List[str], q: multiprocessing.Queue):
""" Runs a command and puts the result in a queue. """
cmd = [ str(_) for _ in cmd ]
cons.print(f"$ {' '.join(cmd)}")
result = subprocess.run(
Expand Down Expand Up @@ -99,39 +102,44 @@ def run(self, names: typing.List[str]) -> None:
p = multiprocessing.Process(
target=_interactive_working_worker,
args=(
[self.mpibin.bin] + self.mpibin.gen_params() + [os.sep.join([build.get_install_dirpath(), "bin", "syscheck"])],
[self.mpibin.bin] + self.mpibin.gen_params() + [os.sep.join([build.get_install_dirpath(build.SYSCHECK), "bin", "syscheck"])],
q,
))

p.start()
p.join(work_timeout)

try:
result = q.get(block=False)
self.bKnowWorks = result.returncode == 0
except queue.Empty as e:
self.bKnowWorks = False

if p.is_alive() or not self.bKnowWorks:
if p.is_alive():
raise common.MFCException("""\
if p.is_alive():
raise common.MFCException("""\
The [bold magenta]Interactive Engine[/bold magenta] appears to hang.
This may indicate that the wrong MPI binary is being used to launch parallel jobs. You can specify the correct one for your system
using the <-b,--binary> option. For example:
* ./mfc.sh run <myfile.py> -b mpirun
* ./mfc.sh run <myfile.py> -b srun
* ./mfc.sh run <myfile.py> -b mpirun
* ./mfc.sh run <myfile.py> -b srun
""")
else:
raise common.MFCException(f"""\

result = q.get(block=False)
self.bKnowWorks = result.returncode == 0

if not self.bKnowWorks:
error_txt = f"""\
MFC's [bold magenta]syscheck[/bold magenta] (system check) failed to run successfully.
Please review the output bellow and ensure that your system is configured correctly:
"""

if result is not None:
error_txt += f"""\
STDOUT:
{result.stdout}
STDERR:
{result.stderr}
""")
"""
else:
error_txt += f"Evaluation timed out after {work_timeout}s."

raise common.MFCException(error_txt)

cons.print()
cons.unindent()
Expand Down
27 changes: 21 additions & 6 deletions toolchain/mfc/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,40 @@ class MFCConfig:
debug: bool = False

def from_dict(d: dict):
""" Create a MFCConfig object from a dictionary with the same keys
as the fields of MFCConfig """
r = MFCConfig()

for key in d:
setattr(r, key, d[key])
for field in dataclasses.fields(MFCConfig):
setattr(r, field.name, d[field.name])

return r

def items(self) -> typing.List[typing.Tuple[str, bool]]:
return dataclasses.asdict(self).items()

def make_options(self) -> typing.List[str]:
""" Returns a list of options that could be passed to mfc.sh again.
Example: --no-debug --mpi --no-gpu """
return [ f"--{'no-' if not v else ''}{k}" for k, v in self.items() ]

def make_slug(self) -> str:
""" Sort the items by key, then join them with underscores. This uniquely
identifies the configuration. Example: no-debug_no-gpu_mpi """
return '_'.join([ f"{'no-' if not v else ''}{k}" for k, v in sorted(self.items(), key=lambda x: x[0]) ])

def __eq__(self, other) -> bool:
""" Check if two MFCConfig objects are equal, field by field. """
for field in dataclasses.fields(self):
if getattr(self, field.name) != getattr(other, field.name):
return False

return True

def __str__(self) -> str:
m = { False: "No", True: "Yes" }
r = ' & '.join([ f"{field.name}={m[getattr(self, field.name)]}" for field in dataclasses.fields(self) ])

return r
""" Returns a string like "mpi=No & gpu=No & debug=No" """

return ' & '.join([ f"{k}={'Yes' if v else 'No'}" for k, v in self.items() ])


gCFG: MFCConfig = MFCConfig()
Expand Down
Loading

0 comments on commit ddbf707

Please sign in to comment.