Skip to content

Commit

Permalink
Fix --case-optimization (MFlowCode#288)
Browse files Browse the repository at this point in the history
  • Loading branch information
henryleberre committed Jan 7, 2024
1 parent cce8402 commit ce8db57
Show file tree
Hide file tree
Showing 8 changed files with 114 additions and 88 deletions.
16 changes: 8 additions & 8 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -196,11 +196,11 @@ endif()
# * For each .fpp file found with filepath <dirpath>/<filename>.fpp, using a
# custom command, instruct CMake how to generate a file with path
#
# src/<target>/autogen/<filename>.f90
# src/<target>/fypp/<filename>.f90
#
# by running Fypp on <dirpath>/<filename>.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/<target>/autogen/<filename>.f90 path
# CMake what to do when it finds a src/<target>/fypp/<filename>.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.
Expand All @@ -223,7 +223,7 @@ endif()
# one of them is modified).
#
# .fpp files in src/common are treated as if they were in src/<target> (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 <target>_SRCs, a list of all source files (.f90)
Expand All @@ -250,7 +250,7 @@ macro(HANDLE_SOURCES target useCommon)
list(APPEND ${target}_SRCs ${common_F90s})
endif()

# src/[<target>,common]/*.fpp -> src/<target>/autogen/*.f90
# src/[<target>,common]/*.fpp -> src/<target>/fypp/*.f90
file(GLOB ${target}_FPPs CONFIGURE_DEPENDS "${${target}_DIR}/*.fpp")
if (${useCommon})
file(GLOB common_FPPs CONFIGURE_DEPENDS "${common_DIR}/*.fpp")
Expand All @@ -259,22 +259,22 @@ macro(HANDLE_SOURCES target useCommon)

# Locate src/[<target>,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}"
Expand Down
14 changes: 13 additions & 1 deletion src/simulation/m_start_up.fpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
112 changes: 63 additions & 49 deletions toolchain/mfc/build.py
Original file line number Diff line number Diff line change
@@ -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:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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)

Expand All @@ -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()

Expand Down Expand Up @@ -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.
Expand All @@ -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()
4 changes: 2 additions & 2 deletions toolchain/mfc/printer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
18 changes: 11 additions & 7 deletions toolchain/mfc/run/engines.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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")[:]
Expand All @@ -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.
Expand Down Expand Up @@ -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()
Expand All @@ -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()
Expand Down Expand Up @@ -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())),
Expand Down
9 changes: 7 additions & 2 deletions toolchain/mfc/run/input.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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()

Expand Down
Loading

0 comments on commit ce8db57

Please sign in to comment.