From eee9bc4a7d3a9cfdae0eb59c173df09f7e5c7707 Mon Sep 17 00:00:00 2001 From: Henry LE BERRE Date: Tue, 14 Nov 2023 19:59:36 -0800 Subject: [PATCH] --case-optimization refresh --- CMakeLists.txt | 19 ++-- src/pre_process/include/case.fpp | 5 +- src/simulation/include/case.fpp | 7 +- toolchain/mfc/args.py | 12 ++- toolchain/mfc/build.py | 151 ++++++++++++++------------ toolchain/mfc/run/engines.py | 1 + toolchain/mfc/run/input.py | 177 ++++++++++++++++--------------- toolchain/mfc/run/run.py | 2 +- toolchain/mfc/test/case.py | 8 +- toolchain/mfc/test/test.py | 10 +- 10 files changed, 212 insertions(+), 180 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7eb2546b16..b4f11678ae 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -229,6 +229,8 @@ macro(HANDLE_SOURCES target useCommon) set(${target}_DIR "${CMAKE_SOURCE_DIR}/src/${target}") set(common_DIR "${CMAKE_SOURCE_DIR}/src/common") + string(TOUPPER ${target} ${target}_UPPER) + # Gather src/[,common]/*.f90 file(GLOB ${target}_F90s CONFIGURE_DEPENDS "${${target}_DIR}/*.f90") set(${target}_SRCs ${${target}_F90s}) @@ -245,9 +247,9 @@ macro(HANDLE_SOURCES target useCommon) endif() # Locate src/[,common]/include/*.fpp - if (EXISTS "${${target}_DIR}/include") - file(GLOB ${target}_incs CONFIGURE_DEPENDS "${${target}_DIR}/include/*.fpp") - endif() + file(GLOB ${target}_incs CONFIGURE_DEPENDS "${${target}_DIR}/include/*.fpp" + "${CMAKE_CURRENT_BINARY_DIR}/include/*.fpp") + if (${useCommon}) file(GLOB common_incs CONFIGURE_DEPENDS "${common_DIR}/include/*.fpp") list(APPEND ${target}_incs ${common_incs}) @@ -261,11 +263,14 @@ macro(HANDLE_SOURCES target useCommon) add_custom_command( OUTPUT ${f90} COMMAND ${FYPP_EXE} -m re - -I "${common_DIR}" - -I "${common_DIR}/include" + -I "${CMAKE_CURRENT_BINARY_DIR}/include" -I "${${target}_DIR}/include" + -I "${common_DIR}/include" + -I "${common_DIR}" -D MFC_${CMAKE_Fortran_COMPILER_ID} + -D MFC_${${target}_UPPER} -D MFC_COMPILER="${CMAKE_Fortran_COMPILER_ID}" + -D MFC_CASE_OPTIMIZATION=False --line-numbering --no-folding "${fpp}" "${f90}" @@ -317,10 +322,10 @@ function(MFC_SETUP_TARGET) "${CMAKE_SOURCE_DIR}/src/${ARGS_TARGET}/include") endif() - string(TOUPPER "${ARGS_TARGET}" ARGS_TARGET_UPPER) + string(TOUPPER "${ARGS_TARGET}" ${ARGS_TARGET}_UPPER) target_compile_definitions( ${ARGS_TARGET} PRIVATE MFC_${CMAKE_Fortran_COMPILER_ID} - MFC_${ARGS_TARGET_UPPER} + MFC_${${ARGS_TARGET}_UPPER} ) if (MFC_MPI AND ARGS_MPI) diff --git a/src/pre_process/include/case.fpp b/src/pre_process/include/case.fpp index 9ec4553143..1f3f32d6f4 100644 --- a/src/pre_process/include/case.fpp +++ b/src/pre_process/include/case.fpp @@ -1,7 +1,4 @@ -! This file was generated by MFC. It is only used when analytical patches are -! present in the input file. It is used to define the analytical patches with -! expressions that are evaluated at runtime from the input file. +! By default, no analytical patches are defined. #:def analytical() - #:enddef diff --git a/src/simulation/include/case.fpp b/src/simulation/include/case.fpp index 3fd2045031..2fb832db37 100644 --- a/src/simulation/include/case.fpp +++ b/src/simulation/include/case.fpp @@ -1,5 +1,2 @@ -! This file was generated by MFC. It is only used if the --case-optimization -! option is passed to ./mfc.sh run or test, enabling a GPU-oriented optimization -! that hard-codes certain case parameters from the input file. - -#:set MFC_CASE_OPTIMIZATION = False +! This file is purposefully empty. It is only important for builds that make use +! of --case-optimization. diff --git a/toolchain/mfc/args.py b/toolchain/mfc/args.py index 482959bc93..c4a7c39a18 100644 --- a/toolchain/mfc/args.py +++ b/toolchain/mfc/args.py @@ -1,7 +1,7 @@ import re, os.path, argparse, dataclasses from .build import TARGETS, DEFAULT_TARGETS, DEPENDENCY_TARGETS -from .common import format_list_to_string +from .common import MFCException, format_list_to_string from .test.test import CASES as TEST_CASES from .packer import packer @@ -45,7 +45,7 @@ def add_common_arguments(p, mask = None): if "t" not in mask: p.add_argument("-t", "--targets", metavar="TARGET", nargs="+", type=str.lower, choices=[ _.name for _ in TARGETS ], - default=[ _.name for _ in DEFAULT_TARGETS ], + default=[ _.name for _ in sorted(DEFAULT_TARGETS, key=lambda t: t.runOrder) ], help=f"Space separated list of targets to act upon. Allowed values are: {format_list_to_string([ _.name for _ in TARGETS ])}.") if "m" not in mask: @@ -70,6 +70,8 @@ def add_common_arguments(p, mask = None): # === BUILD === add_common_arguments(build, "g") + build.add_argument("-i", "--input", type=str, default=None, help="(GPU Optimization) Build a version of MFC optimized for a case.") + build.add_argument("--case-optimization", action="store_true", default=False, help="(GPU Optimization) Compile MFC targets with some case parameters hard-coded (requires --input).") # === CLEAN === add_common_arguments(clean, "jg") @@ -146,6 +148,12 @@ def append_defaults_to_data(name: str, parser): # "Slugify" the name of the job args["name"] = re.sub(r'[\W_]+', '-', args["name"]) + # build's --case-optimization and --input depend on each other + if args["command"] == "build": + if (args["input"] is not None) ^ args["case_optimization"] : + raise MFCException(f"./mfc.sh build's --case-optimization requires --input") + + # Input files to absolute paths for e in ["input", "input1", "input2"]: if e not in args: continue diff --git a/toolchain/mfc/build.py b/toolchain/mfc/build.py index a3db1ff8c5..614ed3b912 100644 --- a/toolchain/mfc/build.py +++ b/toolchain/mfc/build.py @@ -1,10 +1,9 @@ -import re, os, typing, dataclasses +import re, os, typing, hashlib, dataclasses from .common import MFCException, system, delete_directory, create_directory from .state import ARG, CFG from .printer import cons -from .run.input import MFCInputFile - +from .run import input @dataclasses.dataclass class MFCTarget: @@ -26,19 +25,27 @@ def compute(self) -> typing.Set: 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 + runOrder: int # For MFC Targets: Order in which targets should logically run def __hash__(self) -> int: return hash(self.name) # 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 not self.isDependency and ARG("case_optimization"): + m = hashlib.sha256() + m.update(input.load().get_fpp(self).encode()) + subdir = f"{subdir}-{m.hexdigest()[:6]}" + return os.sep.join([ os.getcwd(), "build", - [CFG().make_slug(), 'dependencies'][int(self.isDependency)], + subdir, self.name ]) - + # Get the directory that contains the target's CMakeLists.txt def get_cmake_dirpath(self) -> str: # The CMakeLists.txt file is located: @@ -84,31 +91,16 @@ def get_configuration_txt(self) -> typing.Optional[dict]: return None - - def build(self, history: typing.Set[str] = None): - if history is None: - history = set() - - if self.name in history: - return - - history.add(self.name) - - build_targets(REQUIRED_TARGETS, history) - - cons.print(f"[bold]Building [magenta]{self.name}[/magenta]:[/bold]") - cons.indent() - + def is_buildable(self) -> bool: if ARG("no_build"): - cons.print("--no-build specified, skipping...") - cons.unindent() - return + return False if self.isDependency and ARG(f"no_{self.name}"): - cons.print(f"--no-{self.name} given, skipping...") - cons.unindent() - return + return False + return True + + def configure(self): build_dirpath = self.get_build_dirpath() cmake_dirpath = self.get_cmake_dirpath() install_dirpath = self.get_install_dirpath() @@ -144,42 +136,35 @@ def build(self, history: typing.Set[str] = None): flags.append(f"-DMFC_OpenACC={'ON' if ARG('gpu') else 'OFF'}") configure = ["cmake"] + flags + ["-S", cmake_dirpath, "-B", build_dirpath] - build = ["cmake", "--build", build_dirpath, - "--target", self.name, - "-j", ARG("jobs"), - "--config", 'Debug' if ARG('debug') else 'Release'] - if ARG('verbose'): - build.append("--verbose") - install = ["cmake", "--install", build_dirpath] + delete_directory(build_dirpath) + create_directory(build_dirpath) - if not self.is_configured(): - build_targets(self.requires.compute(), history) + if system(configure, no_exception=True) != 0: + raise MFCException(f"Failed to configure the [bold magenta]{self.name}[/bold magenta] target.") + + + def build(self): + input.load().generate_fpp(self) - delete_directory(build_dirpath) - create_directory(build_dirpath) + build = ["cmake", "--build", self.get_build_dirpath(), + "--target", self.name, + "-j", ARG("jobs"), + "--config", 'Debug' if ARG('debug') else 'Release'] + if ARG('verbose'): + build.append("--verbose") - if system(configure, no_exception=True) != 0: - raise MFCException(f"Failed to configure the [bold magenta]{self.name}[/bold magenta] target.") + system(build, exception_text=f"Failed to build the [bold magenta]{self.name}[/bold magenta] target.") - if not self.isDependency and ARG("command") == "build": - MFCInputFile("", "", {}).generate(self, bOnlyFPPs = True) + def install(self): + install = ["cmake", "--install", self.get_build_dirpath()] - system(build, exception_text=f"Failed to build the [bold magenta]{self.name}[/bold magenta] target.") system(install, exception_text=f"Failed to install the [bold magenta]{self.name}[/bold magenta] target.") - cons.print(no_indent=True) - cons.unindent() - def clean(self): - cons.print(f"[bold]Cleaning [magenta]{self.name}[/magenta]:[/bold]") - cons.indent() - build_dirpath = self.get_build_dirpath() if not os.path.isdir(build_dirpath): - cons.print("Target not configured. Nothing to clean.") - cons.unindent() return clean = ["cmake", "--build", build_dirpath, "--target", "clean", @@ -190,17 +175,15 @@ def clean(self): system(clean, exception_text=f"Failed to clean the [bold magenta]{self.name}[/bold magenta] target.") - cons.unindent() - -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([], [], [])) +FFTW = MFCTarget('fftw', ['-DMFC_FFTW=ON'], True, False, False, MFCTarget.Dependencies([], [], []), -1) +HDF5 = MFCTarget('hdf5', ['-DMFC_HDF5=ON'], True, False, False, MFCTarget.Dependencies([], [], []), -1) +SILO = MFCTarget('silo', ['-DMFC_SILO=ON'], True, False, False, MFCTarget.Dependencies([HDF5], [], []), -1) +PRE_PROCESS = MFCTarget('pre_process', ['-DMFC_PRE_PROCESS=ON'], False, True, False, MFCTarget.Dependencies([], [], []), 0) +SIMULATION = MFCTarget('simulation', ['-DMFC_SIMULATION=ON'], False, True, False, MFCTarget.Dependencies([], [FFTW], []), 1) +POST_PROCESS = MFCTarget('post_process', ['-DMFC_POST_PROCESS=ON'], False, True, False, MFCTarget.Dependencies([FFTW, SILO], [], []), 2) +SYSCHECK = MFCTarget('syscheck', ['-DMFC_SYSCHECK=ON'], False, False, True, MFCTarget.Dependencies([], [], []), -1) +DOCUMENTATION = MFCTarget('documentation', ['-DMFC_DOCUMENTATION=ON'], False, False, False, MFCTarget.Dependencies([], [], []), -1) TARGETS = { FFTW, HDF5, SILO, PRE_PROCESS, SIMULATION, POST_PROCESS, SYSCHECK, DOCUMENTATION } @@ -230,17 +213,55 @@ 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): + if history is None: + history = set() + + t = get_target(target) + + if t.name in history or not t.is_buildable(): + return + + history.add(t.name) + + build_targets(t.requires.compute(), history) + + if not t.is_configured(): + t.configure() + + t.build() + t.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 clean_target(target: typing.Union[MFCTarget, str], history: typing.Set[str] = None): + if history is None: + history = set() + + t = get_target(target) - for target in targets: - get_target(target).build(history) + if t.name in history or not t.is_buildable(): + return + history.add(t.name) + + t.clean() + + +def clean_targets(targets: typing.Iterable[typing.Union[MFCTarget, str]], history: typing.Set[str] = None): + if history is None: + history = set() -def clean_targets(targets: typing.Iterable[typing.Union[MFCTarget, str]]): - for target in targets: - get_target(target).clean() + for target in list(REQUIRED_TARGETS) + targets: + t = get_target(target) + if t.is_configured(): + t.clean() def get_configured_targets() -> typing.List[MFCTarget]: diff --git a/toolchain/mfc/run/engines.py b/toolchain/mfc/run/engines.py index b6d2f28ece..d7a6dbe53b 100644 --- a/toolchain/mfc/run/engines.py +++ b/toolchain/mfc/run/engines.py @@ -161,6 +161,7 @@ def run(self, targets: typing.List[MFCTarget]) -> None: cons.print(f"[bold green]Done[/bold green] (in {datetime.timedelta(seconds=end_time - start_time)})") + cons.print() cons.unindent() diff --git a/toolchain/mfc/run/input.py b/toolchain/mfc/run/input.py index 2ed66b5445..839f58a5db 100644 --- a/toolchain/mfc/run/input.py +++ b/toolchain/mfc/run/input.py @@ -29,7 +29,7 @@ def __is_ic_analytical(self, key: str, val: str) -> bool: return False - def __generate_inp(self, target) -> None: + def generate_inp(self, target) -> None: cons.print(f"Generating [magenta]{target.name}.inp[/magenta].") cons.indent() @@ -65,29 +65,28 @@ def __generate_inp(self, target) -> None: common.file_write(f"{self.case_dirpath}/{target.name}.inp", contents) cons.unindent() - + def __save_fpp(self, target, contents: str) -> None: - filepath = os.path.join(os.getcwd(), "src", target.name, "include", "case.fpp") + def __contents_equal(str_a: str, str_b: str) -> bool: + lhs = [ l.strip() for l in str_a.splitlines() if not common.isspace(l) ] + rhs = [ l.strip() for l in str_b.splitlines() if not common.isspace(l) ] + + return lhs == rhs - # Check if this case already has a case.fpp file. - # If so, we don't need to generate a new one, which - # would cause a partial and unnecessary rebuild. - if os.path.exists(filepath): - with open(filepath, "r") as f: - lhs = [ l.strip() for l in f.read().splitlines() if not common.isspace(l) ] - rhs = [ l.strip() for l in contents.splitlines() if not common.isspace(l) ] + inc_dir = os.path.join(target.get_build_dirpath(), "include") + common.create_directory(inc_dir) + + fpp_path = os.path.join(inc_dir, f"case.fpp") + opt_fpp = open(fpp_path).read() if os.path.exists(fpp_path) else "" - if lhs == rhs: - cons.print("[yellow]INFO:[/yellow] Existing case.fpp file is up to date.") - return + if __contents_equal(contents, opt_fpp): + cons.print("[yellow]INFO:[/yellow] Custom case.fpp file is up to date.") + return - cons.print("[yellow]INFO:[/yellow] Overwriting existing case.fpp file. This will cause a partial rebuild.") - common.file_write(filepath, contents) + cons.print("[yellow]INFO:[/yellow] Writing a custom case.fpp file: --case-optimization configuration has changed.") + common.file_write(fpp_path, contents) - def __generate_pre_fpp(self) -> None: - cons.print(f"Generating [magenta]pre_process/include/case.fpp[/magenta].") - cons.indent() - + def __get_pre_fpp(self) -> str: DATA = { 1: {'ptypes': [1, 15, 16], 'sf_idx': 'i, 0, 0'}, 2: {'ptypes': [2, 3, 4, 5, 6, 7, 17, 18, 21], 'sf_idx': 'i, j, 0'}, @@ -106,7 +105,7 @@ def __generate_pre_fpp(self) -> None: patches[patch_id] = [] patches[patch_id].append((key, val)) - + srcs = [] for pid, items in patches.items(): @@ -114,7 +113,7 @@ def __generate_pre_fpp(self) -> None: if ptype not in DATA['ptypes']: raise common.MFCException(f"Patch #{pid} of type {ptype} cannot be analytically defined.") - + def rhs_replace(match): return { 'x': 'x_cc(i)', 'y': 'y_cc(j)', 'z': 'z_cc(k)', @@ -127,7 +126,7 @@ def rhs_replace(match): 'e' : f'{math.e}', 'pi': f'{math.pi}', }.get(match.group(), match.group()) - + lines = [] for attribute, expr in items: varname = re.findall(r"[a-zA-Z][a-zA-Z0-9_]*", attribute)[1] @@ -140,14 +139,14 @@ def rhs_replace(match): qpvf_idx_offset = f" + {idx}" sf_idx = DATA['sf_idx'] - + cons.print(f"[yellow]INFO:[/yellow] {self.__get_ndims()}D Analytical Patch #{pid}: Code generation for [magenta]{varname}[/magenta]...") - + lhs = f"q_prim_vf({qpvf_idx_var}{qpvf_idx_offset})%sf({sf_idx})" rhs = re.sub(r"[a-zA-Z]+", rhs_replace, expr) - + lines.append(f" {lhs} = {rhs}") - + srcs.append(f"""\ if (patch_id == {pid}) then {f'{chr(10)}'.join(lines)} @@ -163,23 +162,10 @@ def rhs_replace(match): {f'{chr(10)}{chr(10)}'.join(srcs)} #:enddef """ - - self.__save_fpp(build.PRE_PROCESS, content) - - cons.unindent() - - def __generate_sim_fpp(self) -> None: - cons.print(f"Generating [magenta]simulation/include/case.fpp[/magenta].") - cons.indent() - - content = f"""\ -! This file was generated by MFC. It is only used if the --case-optimization -! option is passed to ./mfc.sh run or test, enabling a GPU-oriented optimization -! that hard-codes certain case parameters from the input file. -#:set MFC_CASE_OPTIMIZATION = {ARG("case_optimization")} -""" + return content + def __get_sim_fpp(self) -> str: if ARG("case_optimization"): cons.print("[yellow]INFO:[/yellow] Case optimization is enabled.") @@ -192,68 +178,83 @@ def __generate_sim_fpp(self) -> None: elif bubble_model == 3: nterms = 7 - content = content + f""" -#:set weno_order = {int(self.case_dict["weno_order"])} -#:set weno_polyn = {int((self.case_dict["weno_order"] - 1) / 2)} -#:set nb = {int(self.case_dict.get("nb", 1))} -#:set num_dims = {1 + min(int(self.case_dict.get("n", 0)), 1) + min(int(self.case_dict.get("p", 0)), 1)} -#:set nterms = {nterms} + return f"""\ +#:set MFC_CASE_OPTIMIZATION = {ARG("case_optimization")} +#:set weno_order = {int(self.case_dict["weno_order"])} +#:set weno_polyn = {int((self.case_dict["weno_order"] - 1) / 2)} +#:set nb = {int(self.case_dict.get("nb", 1))} +#:set num_dims = {1 + min(int(self.case_dict.get("n", 0)), 1) + min(int(self.case_dict.get("p", 0)), 1)} +#:set nterms = {nterms} +""" + + return """\ +! This file is purposefully empty. It is only important for builds that make use +! of --case-optimization. """ - else: - cons.print("[yellow]INFO:[/yellow] Case optimization is disabled. Use --case-optimization to enable it.") - self.__save_fpp(build.SIMULATION, content) - cons.unindent() + def __get_post_fpp(self) -> str: + return """\ +! This file is purposefully empty for all post-process builds. +""" + + def get_fpp(self, target) -> str: + def _default() -> str: + return "" - def __generate_post_fpp(self) -> None: - cons.print("Generating [magenta]post_process/include/case.fpp[/magenta].") + result = { + "pre_process" : self.__get_pre_fpp, + "simulation" : self.__get_sim_fpp, + "post_process" : self.__get_post_fpp, + }.get(build.get_target(target).name, _default)() + + return result + + def generate_fpp(self, target) -> None: + cons.print(f"Generating [magenta]{build.get_target(target).name}/include/case.fpp[/magenta].") cons.indent() - cons.print("[yellow]INFO:[/yellow] No case.fpp file is generated for post_process.") + + self.__save_fpp(target, self.get_fpp(target)) + cons.unindent() - pass # Generate case.fpp & [target.name].inp - def generate(self, target, bOnlyFPPs = False) -> None: - if not bOnlyFPPs: - self.__generate_inp(target) - + def generate(self, target) -> None: + self.generate_inp(target) cons.print() - - def _default(): - cons.print(f"No additional input file generation needed for [bold magenta]{target.name}[/bold magenta].") - - { - "pre_process" : self.__generate_pre_fpp, - "simulation" : self.__generate_sim_fpp, - "post_process" : self.__generate_post_fpp, - }.get(target.name, _default)() - + self.generate_fpp(target) + # Load the input file def load() -> MFCInputFile: - filename: str = ARG("input").strip() + if load.CACHED_MFCInputFile is None: + filename: str = ARG("input").strip() + + cons.print(f"Acquiring [bold magenta]{filename}[/bold magenta]...") - cons.print(f"Acquiring [bold magenta]{filename}[/bold magenta]...") + dirpath: str = os.path.abspath(os.path.dirname(filename)) + dictionary: dict = {} - dirpath: str = os.path.abspath(os.path.dirname(filename)) - dictionary: dict = {} + if not os.path.exists(filename): + raise common.MFCException(f"Input file '{filename}' does not exist. Please check the path is valid.") - if not os.path.exists(filename): - raise common.MFCException(f"Input file '{filename}' does not exist. Please check the path is valid.") + if filename.endswith(".py"): + (json_str, err) = common.get_py_program_output(filename, [json.dumps(ARGS())] + ARG("arguments")) - if filename.endswith(".py"): - (json_str, err) = common.get_py_program_output(filename, [json.dumps(ARGS())] + ARG("arguments")) + if err != 0: + raise common.MFCException(f"Input file {filename} terminated with a non-zero exit code. Please make sure running the file doesn't produce any errors.") + elif filename.endswith(".json"): + json_str = common.file_read(filename) + else: + raise common.MFCException("Unrecognized input file format. Only .py and .json files are supported. Please check the README and sample cases in the examples directory.") - if err != 0: - raise common.MFCException(f"Input file {filename} terminated with a non-zero exit code. Please make sure running the file doesn't produce any errors.") - elif filename.endswith(".json"): - json_str = common.file_read(filename) - else: - raise common.MFCException("Unrecognized input file format. Only .py and .json files are supported. Please check the README and sample cases in the examples directory.") + try: + dictionary = json.loads(json_str) + except Exception as exc: + raise common.MFCException(f"Input file {filename} did not produce valid JSON. It should only print the case dictionary.\n\n{exc}\n") + + load.CACHED_MFCInputFile = MFCInputFile(filename, dirpath, dictionary) + + return load.CACHED_MFCInputFile - try: - dictionary = json.loads(json_str) - except Exception as exc: - raise common.MFCException(f"Input file {filename} did not produce valid JSON. It should only print the case dictionary.\n\n{exc}\n") - return MFCInputFile(filename, dirpath, dictionary) +load.CACHED_MFCInputFile = None diff --git a/toolchain/mfc/run/run.py b/toolchain/mfc/run/run.py index 47fcd2fe22..ac0d86803d 100644 --- a/toolchain/mfc/run/run.py +++ b/toolchain/mfc/run/run.py @@ -53,7 +53,7 @@ def run_targets(targets: typing.List[MFCTarget]): cons.print(f"Generating input files for [magenta]{target.name}[/magenta]...") cons.indent() cons.print() - input_file.generate(target) + input_file.generate_inp(target) cons.print() cons.unindent() diff --git a/toolchain/mfc/test/case.py b/toolchain/mfc/test/case.py index 6cb7bb5665..19a63bedcc 100644 --- a/toolchain/mfc/test/case.py +++ b/toolchain/mfc/test/case.py @@ -94,10 +94,12 @@ class TestCase(case.Case): ppn: int trace: str + opt: bool - def __init__(self, trace: str, mods: dict, ppn: int = None) -> None: + def __init__(self, trace: str, mods: dict, ppn: int = None, opt: bool = None) -> None: self.trace = trace - self.ppn = ppn if ppn is not None else 1 + self.ppn = ppn or 1 + self.opt = opt or False super().__init__({**BASE_CFG.copy(), **mods}) def run(self, targets: typing.List[typing.Union[str, MFCTarget]], gpus: typing.Set[int]) -> subprocess.CompletedProcess: @@ -110,7 +112,7 @@ def run(self, targets: typing.List[typing.Union[str, MFCTarget]], gpus: typing.S tasks = f"-n {self.ppn}" jobs = f"-j {ARG('jobs')}" if ARG("case_optimization") else "" binary_option = f"-b {ARG('binary')}" if ARG("binary") is not None else "" - case_optimization = "--case-optimization" if ARG("case_optimization") else "--no-build" + case_optimization = "--case-optimization" if ARG("case_optimization") or self.opt else "--no-build" mfc_script = ".\mfc.bat" if os.name == 'nt' else "./mfc.sh" diff --git a/toolchain/mfc/test/test.py b/toolchain/mfc/test/test.py index a68eefa3c0..7dc19d73d9 100644 --- a/toolchain/mfc/test/test.py +++ b/toolchain/mfc/test/test.py @@ -95,6 +95,8 @@ def test(): if not ARG("case_optimization"): build_targets(codes) + cons.print() + range_str = f"from [bold magenta]{ARG('from')}[/bold magenta] to [bold magenta]{ARG('to')}[/bold magenta]" if len(ARG("only")) > 0: @@ -113,11 +115,9 @@ def test(): # because running a test case may cause it to rebuild, and thus # interfere with the other test CASES. It is a niche feature so we won't # engineer around this issue (for now). - nThreads = ARG("jobs") if not ARG("case_optimization") else 1 - tasks = [ - sched.Task(ppn=case.ppn, func=handle_case, args=[case], load=case.get_cell_count()) for case in CASES - ] - sched.sched(tasks, nThreads, ARG("gpus")) + sched.sched( + [ sched.Task(ppn=case.ppn, func=handle_case, args=[case], load=case.get_cell_count()) for case in CASES ], + ARG("jobs"), ARG("gpus")) cons.print() if nFAIL == 0: