From ce8db57d963398340cc9f4b7325535400b47a22b Mon Sep 17 00:00:00 2001 From: Henry LE BERRE Date: Fri, 5 Jan 2024 16:28:30 -0500 Subject: [PATCH] Fix --case-optimization (#288) --- CMakeLists.txt | 16 ++--- src/simulation/m_start_up.fpp | 14 ++++- toolchain/mfc/build.py | 112 +++++++++++++++++++--------------- toolchain/mfc/printer.py | 4 +- toolchain/mfc/run/engines.py | 18 +++--- toolchain/mfc/run/input.py | 9 ++- toolchain/mfc/run/run.py | 25 +++----- toolchain/mfc/test/test.py | 4 +- 8 files changed, 114 insertions(+), 88 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3d656d1244..21d7b9d68f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -196,11 +196,11 @@ endif() # * For each .fpp file found with filepath /.fpp, using a # custom command, instruct CMake how to generate a file with path # -# src//autogen/.f90 +# src//fypp/.f90 # # by running Fypp on /.fpp. It is important to understand # that this does not actually run the pre-processor. Rather, it instructs -# CMake what to do when it finds a src//autogen/.f90 path +# CMake what to do when it finds a src//fypp/.f90 path # in the source list for a target. Thus, an association is made from an .f90 # file to its corresponding .fpp file (if applicable) even though the # generation is of the form .fpp -> .f90. @@ -223,7 +223,7 @@ endif() # one of them is modified). # # .fpp files in src/common are treated as if they were in src/ (not -# pre-processed to src/common/autogen/) so as not to clash with other targets' +# pre-processed to src/common/fypp/) so as not to clash with other targets' # .fpp files (this has caused problems in the past). # # * Export, in the variable _SRCs, a list of all source files (.f90) @@ -250,7 +250,7 @@ macro(HANDLE_SOURCES target useCommon) list(APPEND ${target}_SRCs ${common_F90s}) endif() - # src/[,common]/*.fpp -> src//autogen/*.f90 + # src/[,common]/*.fpp -> src//fypp/*.f90 file(GLOB ${target}_FPPs CONFIGURE_DEPENDS "${${target}_DIR}/*.fpp") if (${useCommon}) file(GLOB common_FPPs CONFIGURE_DEPENDS "${common_DIR}/*.fpp") @@ -259,22 +259,22 @@ macro(HANDLE_SOURCES target useCommon) # Locate src/[,common]/include/*.fpp file(GLOB ${target}_incs CONFIGURE_DEPENDS "${${target}_DIR}/include/*.fpp" - "${CMAKE_CURRENT_BINARY_DIR}/include/*.fpp") + "${CMAKE_BINARY_DIR}/include/${target}/*.fpp") if (${useCommon}) file(GLOB common_incs CONFIGURE_DEPENDS "${common_DIR}/include/*.fpp") list(APPEND ${target}_incs ${common_incs}) endif() - file(MAKE_DIRECTORY "${${target}_DIR}/autogen") + file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/fypp/${target}") foreach(fpp ${${target}_FPPs}) cmake_path(GET fpp FILENAME fpp_filename) - set(f90 "${${target}_DIR}/autogen/${fpp_filename}.f90") + set(f90 "${CMAKE_BINARY_DIR}/fypp/${target}/${fpp_filename}.f90") add_custom_command( OUTPUT ${f90} COMMAND ${FYPP_EXE} -m re - -I "${CMAKE_CURRENT_BINARY_DIR}/include" + -I "${CMAKE_BINARY_DIR}/include/${target}" -I "${${target}_DIR}/include" -I "${common_DIR}/include" -I "${common_DIR}" diff --git a/src/simulation/m_start_up.fpp b/src/simulation/m_start_up.fpp index eac47bcd35..4df7af79ea 100644 --- a/src/simulation/m_start_up.fpp +++ b/src/simulation/m_start_up.fpp @@ -1147,7 +1147,19 @@ contains call s_assign_default_values_to_user_inputs() call s_read_input_file() call s_check_input_file() - print '(" Simulating a "I0"x"I0"x"I0" case on "I0" rank(s)")', m, n, p, num_procs + + print '(" Simulating a ", A, " ", I0, "x", I0, "x", I0, " case on ", I0, " rank(s) ", A, ".")', & +#:if not MFC_CASE_OPTIMIZATION + "regular", & +#:else + "case-optimized", & +#:endif + m, n, p, num_procs, & +#ifdef MFC_OpenACC + "with OpenACC offloading" +#else + "on CPUs" +#endif end if ! Broadcasting the user inputs to all of the processors and performing the diff --git a/toolchain/mfc/build.py b/toolchain/mfc/build.py index 2458a9a358..5a47fe94ac 100644 --- a/toolchain/mfc/build.py +++ b/toolchain/mfc/build.py @@ -1,8 +1,10 @@ import os, typing, hashlib, dataclasses -from .common import MFCException, system, delete_directory, create_directory -from .state import ARG, CFG -from .run import input +from .printer import cons +from .common import MFCException, system, delete_directory, create_directory, \ + format_list_to_string +from .state import ARG, CFG +from .run import input @dataclasses.dataclass class MFCTarget: @@ -31,19 +33,18 @@ def __hash__(self) -> int: # Get path to directory that will store the build files def get_build_dirpath(self) -> str: - subdir = 'dependencies' if self.isDependency else CFG().make_slug() + if self.isDependency: + slug = self.name + else: + slug = f"{CFG().make_slug()}-{self.name}" - if not self.isDependency and ARG("case_optimization"): - m = hashlib.sha256() - m.update(input.load().get_fpp(self).encode()) - subdir = f"{subdir}-{m.hexdigest()[:6]}" + if ARG("case_optimization"): + m = hashlib.sha256() + m.update(input.load().get_fpp(self).encode()) - return os.sep.join([ - os.getcwd(), - "build", - subdir, - self.name - ]) + slug = f"{slug}-{m.hexdigest()[:6]}" + + return os.sep.join([os.getcwd(), "build", slug ]) # Get the directory that contains the target's CMakeLists.txt def get_cmake_dirpath(self) -> str: @@ -146,6 +147,8 @@ def configure(self): if system(command, no_exception=True) != 0: raise MFCException(f"Failed to configure the [bold magenta]{self.name}[/bold magenta] target.") + cons.print(no_indent=True) + def build(self): input.load({}).generate_fpp(self) @@ -158,11 +161,15 @@ def build(self): system(command, exception_text=f"Failed to build the [bold magenta]{self.name}[/bold magenta] target.") + cons.print(no_indent=True) + def install(self): command = ["cmake", "--install", self.get_build_dirpath()] system(command, exception_text=f"Failed to install the [bold magenta]{self.name}[/bold magenta] target.") + cons.print(no_indent=True) + def clean(self): build_dirpath = self.get_build_dirpath() @@ -205,6 +212,10 @@ def get_target(target: typing.Union[str, MFCTarget]) -> MFCTarget: raise MFCException(f"Target '{target}' does not exist.") +def get_targets(targets: typing.List[typing.Union[str, MFCTarget]]) -> typing.List[MFCTarget]: + return [ get_target(t) for t in targets ] + + 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. @@ -215,64 +226,67 @@ def get_dependency_install_dirpath() -> str: raise MFCException("No dependency target found.") -def build_target(target: typing.Union[MFCTarget, str], history: typing.Set[str] = None): +def __build_target(target: typing.Union[MFCTarget, str], history: typing.Set[str] = None): if history is None: history = set() - t = get_target(target) + target = get_target(target) - if t.name in history or not t.is_buildable(): + if target.name in history or not target.is_buildable(): return - history.add(t.name) + history.add(target.name) - build_targets(t.requires.compute(), history) + build(target.requires.compute(), history) - if not t.is_configured(): - t.configure() + if not target.is_configured(): + target.configure() - t.build() - t.install() + target.build() + target.install() -def build_targets(targets: typing.Iterable[typing.Union[MFCTarget, str]], history: typing.Set[str] = None): - if history is None: - history = set() - - for target in list(REQUIRED_TARGETS) + targets: - build_target(target, history) +def get_configured_targets() -> typing.List[MFCTarget]: + return [ target for target in TARGETS if target.is_configured() ] -def clean_target(target: typing.Union[MFCTarget, str], history: typing.Set[str] = None): - if history is None: - history = set() - t = get_target(target) +def __generate_header(step_name: str, targets: typing.List): + caseopt_info = "Generic Build" + if ARG("case_optimization"): + caseopt_info = f"Case Optimized for [magenta]{ARG('input')}[/magenta]" - if t.name in history or not t.is_buildable(): - return + targets = get_targets(targets) + target_list = format_list_to_string([ t.name for t in targets ], 'magenta') - history.add(t.name) + return f"[bold]{step_name} | {target_list} | {caseopt_info}[/bold]" - t.clean() - -def clean_targets(targets: typing.Iterable[typing.Union[MFCTarget, str]], history: typing.Set[str] = None): +def build(targets_ = None, history: typing.Set[str] = None): if history is None: history = set() - for target in list(REQUIRED_TARGETS) + targets: - t = get_target(target) - if t.is_configured(): - t.clean() + targets = get_targets(list(REQUIRED_TARGETS) + (targets_ or ARG("targets"))) + if len(history) == 0: + cons.print(__generate_header("Build", targets)) + cons.print(no_indent=True) + + for target in targets: + __build_target(target, history) + + if len(history) == 0: + cons.print(no_indent=True) -def get_configured_targets() -> typing.List[MFCTarget]: - return [ target for target in TARGETS if target.is_configured() ] +def clean(targets = None): + targets = get_targets(list(REQUIRED_TARGETS) + (targets or ARG("targets"))) -def build(): - build_targets(ARG("targets")) + cons.print(__generate_header("Clean", targets)) + cons.print(no_indent=True) + for target in targets: + if target.is_configured(): + target.clean() -def clean(): - clean_targets(ARG("targets")) + cons.print(no_indent=True) + cons.unindent() diff --git a/toolchain/mfc/printer.py b/toolchain/mfc/printer.py index dbbd0267bd..e74d8ec01c 100644 --- a/toolchain/mfc/printer.py +++ b/toolchain/mfc/printer.py @@ -28,14 +28,14 @@ def print(self, *args, msg: typing.Any = None, no_indent: bool = False, **kwargs msg = "" if no_indent: - self.raw.print(str(msg), *args, **kwargs) + self.raw.print(str(msg), soft_wrap=True, *args, **kwargs) else: print_s, lines = "", str(msg).split('\n', maxsplit=-1) for i, s in enumerate(lines): newline = '\n' if (i != len(lines)-1) else '' print_s += f"{''.join(self.stack)}{s}{newline}" - self.raw.print(print_s, *args, **kwargs) + self.raw.print(print_s, soft_wrap=True, *args, **kwargs) def print_exception(self): self.raw.print_exception() diff --git a/toolchain/mfc/run/engines.py b/toolchain/mfc/run/engines.py index 7ca4c67e5c..df979140ee 100644 --- a/toolchain/mfc/run/engines.py +++ b/toolchain/mfc/run/engines.py @@ -2,7 +2,7 @@ from ..state import ARG, ARGS from ..printer import cons -from ..build import MFCTarget, SYSCHECK +from ..build import MFCTarget, SYSCHECK, get_targets from ..common import MFCException, does_command_exist, isspace, system from ..common import format_list_to_string, does_system_use_modules from ..common import get_loaded_modules, file_write @@ -78,7 +78,8 @@ def get_args(self) -> str: MPI Binary (-b) {self.mpibin.bin}\ """ - def get_exec_cmd(self, target: MFCTarget) -> typing.List[str]: + + def __get_exec_cmd(self, target: MFCTarget) -> typing.List[str]: cmd = [] if ARG("mpi"): cmd += [self.mpibin.bin] + self.mpibin.gen_params() + ARG("flags")[:] @@ -89,8 +90,9 @@ def get_exec_cmd(self, target: MFCTarget) -> typing.List[str]: return cmd + def run(self, targets) -> None: + targets = get_targets(targets) - def run(self, targets: typing.List[MFCTarget]) -> None: if not self.bKnowWorks: # Fix MFlowCode/MFC#21: Check whether attempting to run a job will hang # forever. This can happen when using the wrong queue system. @@ -158,7 +160,7 @@ def run(self, targets: typing.List[MFCTarget]) -> None: env['CUDA_VISIBLE_DEVICES'] = ','.join([str(_) for _ in ARG('gpus')]) system( - self.get_exec_cmd(target), cwd=self.input.case_dirpath, + self.__get_exec_cmd(target), cwd=self.input.case_dirpath, env=env ) end_time = time.monotonic() @@ -184,11 +186,11 @@ def get_args(self) -> str: Email (-@) {ARG("email")} """ - def run(self, targets: typing.List[MFCTarget]) -> None: + def run(self, targets) -> None: qsystem = queues.get_system() cons.print(f"Detected the [bold magenta]{qsystem.name}[/bold magenta] queue system.") - targets = [SYSCHECK] + targets + targets = get_targets([SYSCHECK] + targets) cons.print(f"Running {format_list_to_string([_.name for _ in targets], 'bold magenta')}:") cons.indent() @@ -278,7 +280,9 @@ def __evaluate_expression(self, expr: str) -> str: except Exception as exc: raise MFCException(f"BatchEngine: '{expr}' is not a valid expression in the template file. Please check your spelling.") from exc - def __batch_evaluate(self, s: str, qsystem: queues.QueueSystem, targets: typing.List[MFCTarget]): + def __batch_evaluate(self, s: str, qsystem: queues.QueueSystem, targets): + targets = get_targets(targets) + replace_list = [ ("{MFC::PROLOGUE}", self.__generate_prologue(qsystem)), ("{MFC::PROFILER}", ' '.join(profiler_prepend())), diff --git a/toolchain/mfc/run/input.py b/toolchain/mfc/run/input.py index 2a2cf8fa06..08f2409999 100644 --- a/toolchain/mfc/run/input.py +++ b/toolchain/mfc/run/input.py @@ -30,6 +30,8 @@ def __is_ic_analytical(self, key: str, val: str) -> bool: return False def generate_inp(self, target) -> None: + target = build.get_target(target) + cons.print(f"Generating [magenta]{target.name}.inp[/magenta]:") cons.indent() @@ -72,10 +74,10 @@ def __contents_equal(str_a: str, str_b: str) -> bool: return lhs == rhs - inc_dir = os.path.join(target.get_build_dirpath(), "include") + inc_dir = os.path.join(target.get_build_dirpath(), "include", target.name) common.create_directory(inc_dir) - fpp_path = os.path.join(inc_dir, f"case.fpp") + fpp_path = os.path.join(inc_dir, "case.fpp") opt_fpp = common.file_read(fpp_path) if os.path.exists(fpp_path) else "" if __contents_equal(contents, opt_fpp): @@ -210,6 +212,9 @@ def _default() -> str: return result def generate_fpp(self, target) -> None: + if target.isDependency: + return + cons.print(f"Generating [magenta]{build.get_target(target).name}/include/case.fpp[/magenta].") cons.indent() diff --git a/toolchain/mfc/run/run.py b/toolchain/mfc/run/run.py index 10a86b6751..2fd7ec8b48 100644 --- a/toolchain/mfc/run/run.py +++ b/toolchain/mfc/run/run.py @@ -1,11 +1,11 @@ -import re, typing +import re -from ..build import MFCTarget, get_target, build_targets +from ..build import get_targets, build from ..printer import cons from ..state import ARG from ..common import MFCException, isspace -from . import engines, input +from . import engines, input def validate_job_options() -> None: @@ -24,14 +24,14 @@ def validate_job_options() -> None: raise MFCException(f'RUN: {ARG("email")} is not a valid e-mail address.') -def run_targets(targets: typing.List[MFCTarget]): +def run(targets = None): + targets = get_targets(targets or ARG("targets")) + + build(targets) + cons.print("[bold]Run[/bold]") cons.indent() - if len(targets) == 0: - cons.print(f"> No target selected.") - return - input_file = input.load() engine = engines.get_engine(ARG("engine")) @@ -57,13 +57,4 @@ def run_targets(targets: typing.List[MFCTarget]): cons.print() cons.unindent() - build_targets(targets) engine.run(targets) - - -def run_target(target: MFCTarget): - run_targets([target]) - - -def run() -> None: - run_targets([ get_target(_) for _ in ARG("targets")]) diff --git a/toolchain/mfc/test/test.py b/toolchain/mfc/test/test.py index 73986da835..adca675dad 100644 --- a/toolchain/mfc/test/test.py +++ b/toolchain/mfc/test/test.py @@ -10,7 +10,7 @@ from .cases import generate_cases from .. import sched from ..common import MFCException, does_command_exist, format_list_to_string, get_program_output -from ..build import build_targets, HDF5, PRE_PROCESS, SIMULATION, POST_PROCESS +from ..build import build, HDF5, PRE_PROCESS, SIMULATION, POST_PROCESS from ..packer import tol as packtol from ..packer import packer @@ -94,7 +94,7 @@ def test(): codes = [PRE_PROCESS, SIMULATION] + ([POST_PROCESS] if ARG('test_all') else []) if not ARG("case_optimization"): - build_targets(codes) + build(codes) cons.print()