Skip to content

Commit 2e25894

Browse files
henryleberreJRChreim
authored andcommitted
Case Optimization Refresh (MFlowCode#248)
1 parent 96cd591 commit 2e25894

File tree

10 files changed

+233
-192
lines changed

10 files changed

+233
-192
lines changed

CMakeLists.txt

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,8 @@ macro(HANDLE_SOURCES target useCommon)
240240
set(${target}_DIR "${CMAKE_SOURCE_DIR}/src/${target}")
241241
set(common_DIR "${CMAKE_SOURCE_DIR}/src/common")
242242

243+
string(TOUPPER ${target} ${target}_UPPER)
244+
243245
# Gather src/[<target>,common]/*.f90
244246
file(GLOB ${target}_F90s CONFIGURE_DEPENDS "${${target}_DIR}/*.f90")
245247
set(${target}_SRCs ${${target}_F90s})
@@ -256,9 +258,9 @@ macro(HANDLE_SOURCES target useCommon)
256258
endif()
257259

258260
# Locate src/[<target>,common]/include/*.fpp
259-
if (EXISTS "${${target}_DIR}/include")
260-
file(GLOB ${target}_incs CONFIGURE_DEPENDS "${${target}_DIR}/include/*.fpp")
261-
endif()
261+
file(GLOB ${target}_incs CONFIGURE_DEPENDS "${${target}_DIR}/include/*.fpp"
262+
"${CMAKE_CURRENT_BINARY_DIR}/include/*.fpp")
263+
262264
if (${useCommon})
263265
file(GLOB common_incs CONFIGURE_DEPENDS "${common_DIR}/include/*.fpp")
264266
list(APPEND ${target}_incs ${common_incs})
@@ -272,11 +274,14 @@ macro(HANDLE_SOURCES target useCommon)
272274
add_custom_command(
273275
OUTPUT ${f90}
274276
COMMAND ${FYPP_EXE} -m re
275-
-I "${common_DIR}"
276-
-I "${common_DIR}/include"
277+
-I "${CMAKE_CURRENT_BINARY_DIR}/include"
277278
-I "${${target}_DIR}/include"
279+
-I "${common_DIR}/include"
280+
-I "${common_DIR}"
278281
-D MFC_${CMAKE_Fortran_COMPILER_ID}
282+
-D MFC_${${target}_UPPER}
279283
-D MFC_COMPILER="${CMAKE_Fortran_COMPILER_ID}"
284+
-D MFC_CASE_OPTIMIZATION=False
280285
--line-numbering
281286
--no-folding
282287
"${fpp}" "${f90}"
@@ -328,10 +333,10 @@ function(MFC_SETUP_TARGET)
328333
"${CMAKE_SOURCE_DIR}/src/${ARGS_TARGET}/include")
329334
endif()
330335

331-
string(TOUPPER "${ARGS_TARGET}" ARGS_TARGET_UPPER)
336+
string(TOUPPER "${ARGS_TARGET}" ${ARGS_TARGET}_UPPER)
332337
target_compile_definitions(
333338
${ARGS_TARGET} PRIVATE MFC_${CMAKE_Fortran_COMPILER_ID}
334-
MFC_${ARGS_TARGET_UPPER}
339+
MFC_${${ARGS_TARGET}_UPPER}
335340
)
336341

337342
if (MFC_MPI AND ARGS_MPI)

src/pre_process/include/case.fpp

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
1-
! This file was generated by MFC. It is only used when analytical patches are
2-
! present in the input file. It is used to define the analytical patches with
3-
! expressions that are evaluated at runtime from the input file.
1+
! By default, no analytical patches are defined.
42

53
#:def analytical()
6-
74
#:enddef

src/simulation/include/case.fpp

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,2 @@
1-
! This file was generated by MFC. It is only used if the --case-optimization
2-
! option is passed to ./mfc.sh run or test, enabling a GPU-oriented optimization
3-
! that hard-codes certain case parameters from the input file.
4-
5-
#:set MFC_CASE_OPTIMIZATION = False
1+
! This file is purposefully empty. It is only important for builds that make use
2+
! of --case-optimization.

toolchain/mfc/args.py

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import re, os.path, argparse, dataclasses
22

33
from .build import TARGETS, DEFAULT_TARGETS, DEPENDENCY_TARGETS
4-
from .common import format_list_to_string
4+
from .common import MFCException, format_list_to_string
55
from .test.test import CASES as TEST_CASES
66
from .packer import packer
77

@@ -45,7 +45,7 @@ def add_common_arguments(p, mask = None):
4545

4646
if "t" not in mask:
4747
p.add_argument("-t", "--targets", metavar="TARGET", nargs="+", type=str.lower, choices=[ _.name for _ in TARGETS ],
48-
default=[ _.name for _ in DEFAULT_TARGETS ],
48+
default=[ _.name for _ in sorted(DEFAULT_TARGETS, key=lambda t: t.runOrder) ],
4949
help=f"Space separated list of targets to act upon. Allowed values are: {format_list_to_string([ _.name for _ in TARGETS ])}.")
5050

5151
if "m" not in mask:
@@ -70,6 +70,8 @@ def add_common_arguments(p, mask = None):
7070

7171
# === BUILD ===
7272
add_common_arguments(build, "g")
73+
build.add_argument("-i", "--input", type=str, default=None, help="(GPU Optimization) Build a version of MFC optimized for a case.")
74+
build.add_argument("--case-optimization", action="store_true", default=False, help="(GPU Optimization) Compile MFC targets with some case parameters hard-coded (requires --input).")
7375

7476
# === CLEAN ===
7577
add_common_arguments(clean, "jg")
@@ -128,16 +130,17 @@ def add_common_arguments(p, mask = None):
128130
args: dict = vars(parser.parse_args())
129131

130132
# Add default arguments of other subparsers
131-
def append_defaults_to_data(name: str, parser):
132-
if args["command"] != name:
133-
vals, errs = parser.parse_known_args(["-i None"])
134-
for key,val in vars(vals).items():
135-
if not key in args:
136-
args[key] = val
133+
for name, parser in [("run", run), ("test", test), ("build", build),
134+
("clean", clean), ("bench", bench), ("count", count)]:
135+
if args["command"] == name:
136+
continue
137137

138-
for a, b in [("run", run ), ("test", test ), ("build", build),
139-
("clean", clean), ("bench", bench), ("count", count)]:
140-
append_defaults_to_data(a, b)
138+
vals, errs = parser.parse_known_args(["-i", "None"])
139+
for key, val in vars(vals).items():
140+
if key == "input":
141+
args[key] = args.get(key)
142+
elif not key in args:
143+
args[key] = args.get(key, val)
141144

142145
if args["command"] is None:
143146
parser.print_help()
@@ -146,11 +149,17 @@ def append_defaults_to_data(name: str, parser):
146149
# "Slugify" the name of the job
147150
args["name"] = re.sub(r'[\W_]+', '-', args["name"])
148151

152+
# build's --case-optimization and --input depend on each other
153+
if args["command"] == "build":
154+
if (args["input"] is not None) ^ args["case_optimization"] :
155+
raise MFCException(f"./mfc.sh build's --case-optimization requires --input")
156+
157+
# Input files to absolute paths
149158
for e in ["input", "input1", "input2"]:
150159
if e not in args:
151160
continue
152-
153-
if args[e] is not None:
161+
162+
if args.get(e) is not None:
154163
args[e] = os.path.abspath(args[e])
155164

156165
return args

toolchain/mfc/build.py

Lines changed: 86 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
import re, os, typing, dataclasses
1+
import re, os, typing, hashlib, dataclasses
22

33
from .common import MFCException, system, delete_directory, create_directory
44
from .state import ARG, CFG
55
from .printer import cons
6-
from .run.input import MFCInputFile
7-
6+
from .run import input
87

98
@dataclasses.dataclass
109
class MFCTarget:
@@ -26,19 +25,27 @@ def compute(self) -> typing.Set:
2625
isDefault: bool # Should it be built by default? (unspecified -t | --targets)
2726
isRequired: bool # Should it always be built? (no matter what -t | --targets is)
2827
requires: Dependencies # Build dependencies of the target
28+
runOrder: int # For MFC Targets: Order in which targets should logically run
2929

3030
def __hash__(self) -> int:
3131
return hash(self.name)
3232

3333
# Get path to directory that will store the build files
3434
def get_build_dirpath(self) -> str:
35+
subdir = 'dependencies' if self.isDependency else CFG().make_slug()
36+
37+
if not self.isDependency and ARG("case_optimization"):
38+
m = hashlib.sha256()
39+
m.update(input.load().get_fpp(self).encode())
40+
subdir = f"{subdir}-{m.hexdigest()[:6]}"
41+
3542
return os.sep.join([
3643
os.getcwd(),
3744
"build",
38-
[CFG().make_slug(), 'dependencies'][int(self.isDependency)],
45+
subdir,
3946
self.name
4047
])
41-
48+
4249
# Get the directory that contains the target's CMakeLists.txt
4350
def get_cmake_dirpath(self) -> str:
4451
# The CMakeLists.txt file is located:
@@ -84,31 +91,16 @@ def get_configuration_txt(self) -> typing.Optional[dict]:
8491

8592
return None
8693

87-
88-
def build(self, history: typing.Set[str] = None):
89-
if history is None:
90-
history = set()
91-
92-
if self.name in history:
93-
return
94-
95-
history.add(self.name)
96-
97-
build_targets(REQUIRED_TARGETS, history)
98-
99-
cons.print(f"[bold]Building [magenta]{self.name}[/magenta]:[/bold]")
100-
cons.indent()
101-
94+
def is_buildable(self) -> bool:
10295
if ARG("no_build"):
103-
cons.print("--no-build specified, skipping...")
104-
cons.unindent()
105-
return
96+
return False
10697

10798
if self.isDependency and ARG(f"no_{self.name}"):
108-
cons.print(f"--no-{self.name} given, skipping...")
109-
cons.unindent()
110-
return
99+
return False
111100

101+
return True
102+
103+
def configure(self):
112104
build_dirpath = self.get_build_dirpath()
113105
cmake_dirpath = self.get_cmake_dirpath()
114106
install_dirpath = self.get_install_dirpath()
@@ -144,42 +136,35 @@ def build(self, history: typing.Set[str] = None):
144136
flags.append(f"-DMFC_OpenACC={'ON' if ARG('gpu') else 'OFF'}")
145137

146138
configure = ["cmake"] + flags + ["-S", cmake_dirpath, "-B", build_dirpath]
147-
build = ["cmake", "--build", build_dirpath,
148-
"--target", self.name,
149-
"-j", ARG("jobs"),
150-
"--config", 'Debug' if ARG('debug') else 'Release']
151-
if ARG('verbose'):
152-
build.append("--verbose")
153139

154-
install = ["cmake", "--install", build_dirpath]
140+
delete_directory(build_dirpath)
141+
create_directory(build_dirpath)
155142

156-
if not self.is_configured():
157-
build_targets(self.requires.compute(), history)
143+
if system(configure, no_exception=True) != 0:
144+
raise MFCException(f"Failed to configure the [bold magenta]{self.name}[/bold magenta] target.")
145+
146+
147+
def build(self):
148+
input.load({}).generate_fpp(self)
158149

159-
delete_directory(build_dirpath)
160-
create_directory(build_dirpath)
150+
build = ["cmake", "--build", self.get_build_dirpath(),
151+
"--target", self.name,
152+
"-j", ARG("jobs"),
153+
"--config", 'Debug' if ARG('debug') else 'Release']
154+
if ARG('verbose'):
155+
build.append("--verbose")
161156

162-
if system(configure, no_exception=True) != 0:
163-
raise MFCException(f"Failed to configure the [bold magenta]{self.name}[/bold magenta] target.")
157+
system(build, exception_text=f"Failed to build the [bold magenta]{self.name}[/bold magenta] target.")
164158

165-
if not self.isDependency and ARG("command") == "build":
166-
MFCInputFile("", "", {}).generate(self, bOnlyFPPs = True)
159+
def install(self):
160+
install = ["cmake", "--install", self.get_build_dirpath()]
167161

168-
system(build, exception_text=f"Failed to build the [bold magenta]{self.name}[/bold magenta] target.")
169162
system(install, exception_text=f"Failed to install the [bold magenta]{self.name}[/bold magenta] target.")
170163

171-
cons.print(no_indent=True)
172-
cons.unindent()
173-
174164
def clean(self):
175-
cons.print(f"[bold]Cleaning [magenta]{self.name}[/magenta]:[/bold]")
176-
cons.indent()
177-
178165
build_dirpath = self.get_build_dirpath()
179166

180167
if not os.path.isdir(build_dirpath):
181-
cons.print("Target not configured. Nothing to clean.")
182-
cons.unindent()
183168
return
184169

185170
clean = ["cmake", "--build", build_dirpath, "--target", "clean",
@@ -190,17 +175,15 @@ def clean(self):
190175

191176
system(clean, exception_text=f"Failed to clean the [bold magenta]{self.name}[/bold magenta] target.")
192177

193-
cons.unindent()
194-
195178

196-
FFTW = MFCTarget('fftw', ['-DMFC_FFTW=ON'], True, False, False, MFCTarget.Dependencies([], [], []))
197-
HDF5 = MFCTarget('hdf5', ['-DMFC_HDF5=ON'], True, False, False, MFCTarget.Dependencies([], [], []))
198-
SILO = MFCTarget('silo', ['-DMFC_SILO=ON'], True, False, False, MFCTarget.Dependencies([HDF5], [], []))
199-
PRE_PROCESS = MFCTarget('pre_process', ['-DMFC_PRE_PROCESS=ON'], False, True, False, MFCTarget.Dependencies([], [], []))
200-
SIMULATION = MFCTarget('simulation', ['-DMFC_SIMULATION=ON'], False, True, False, MFCTarget.Dependencies([], [FFTW], []))
201-
POST_PROCESS = MFCTarget('post_process', ['-DMFC_POST_PROCESS=ON'], False, True, False, MFCTarget.Dependencies([FFTW, SILO], [], []))
202-
SYSCHECK = MFCTarget('syscheck', ['-DMFC_SYSCHECK=ON'], False, False, True, MFCTarget.Dependencies([], [], []))
203-
DOCUMENTATION = MFCTarget('documentation', ['-DMFC_DOCUMENTATION=ON'], False, False, False, MFCTarget.Dependencies([], [], []))
179+
FFTW = MFCTarget('fftw', ['-DMFC_FFTW=ON'], True, False, False, MFCTarget.Dependencies([], [], []), -1)
180+
HDF5 = MFCTarget('hdf5', ['-DMFC_HDF5=ON'], True, False, False, MFCTarget.Dependencies([], [], []), -1)
181+
SILO = MFCTarget('silo', ['-DMFC_SILO=ON'], True, False, False, MFCTarget.Dependencies([HDF5], [], []), -1)
182+
PRE_PROCESS = MFCTarget('pre_process', ['-DMFC_PRE_PROCESS=ON'], False, True, False, MFCTarget.Dependencies([], [], []), 0)
183+
SIMULATION = MFCTarget('simulation', ['-DMFC_SIMULATION=ON'], False, True, False, MFCTarget.Dependencies([], [FFTW], []), 1)
184+
POST_PROCESS = MFCTarget('post_process', ['-DMFC_POST_PROCESS=ON'], False, True, False, MFCTarget.Dependencies([FFTW, SILO], [], []), 2)
185+
SYSCHECK = MFCTarget('syscheck', ['-DMFC_SYSCHECK=ON'], False, False, True, MFCTarget.Dependencies([], [], []), -1)
186+
DOCUMENTATION = MFCTarget('documentation', ['-DMFC_DOCUMENTATION=ON'], False, False, False, MFCTarget.Dependencies([], [], []), -1)
204187

205188
TARGETS = { FFTW, HDF5, SILO, PRE_PROCESS, SIMULATION, POST_PROCESS, SYSCHECK, DOCUMENTATION }
206189

@@ -230,17 +213,55 @@ def get_dependency_install_dirpath() -> str:
230213
raise MFCException("No dependency target found.")
231214

232215

216+
def build_target(target: typing.Union[MFCTarget, str], history: typing.Set[str] = None):
217+
if history is None:
218+
history = set()
219+
220+
t = get_target(target)
221+
222+
if t.name in history or not t.is_buildable():
223+
return
224+
225+
history.add(t.name)
226+
227+
build_targets(t.requires.compute(), history)
228+
229+
if not t.is_configured():
230+
t.configure()
231+
232+
t.build()
233+
t.install()
234+
233235
def build_targets(targets: typing.Iterable[typing.Union[MFCTarget, str]], history: typing.Set[str] = None):
234236
if history is None:
235237
history = set()
238+
239+
for target in list(REQUIRED_TARGETS) + targets:
240+
build_target(target, history)
241+
242+
243+
def clean_target(target: typing.Union[MFCTarget, str], history: typing.Set[str] = None):
244+
if history is None:
245+
history = set()
246+
247+
t = get_target(target)
236248

237-
for target in targets:
238-
get_target(target).build(history)
249+
if t.name in history or not t.is_buildable():
250+
return
239251

252+
history.add(t.name)
253+
254+
t.clean()
255+
256+
257+
def clean_targets(targets: typing.Iterable[typing.Union[MFCTarget, str]], history: typing.Set[str] = None):
258+
if history is None:
259+
history = set()
240260

241-
def clean_targets(targets: typing.Iterable[typing.Union[MFCTarget, str]]):
242-
for target in targets:
243-
get_target(target).clean()
261+
for target in list(REQUIRED_TARGETS) + targets:
262+
t = get_target(target)
263+
if t.is_configured():
264+
t.clean()
244265

245266

246267
def get_configured_targets() -> typing.List[MFCTarget]:

toolchain/mfc/run/engines.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ def run(self, targets: typing.List[MFCTarget]) -> None:
161161

162162
cons.print(f"[bold green]Done[/bold green] (in {datetime.timedelta(seconds=end_time - start_time)})")
163163

164+
cons.print()
164165
cons.unindent()
165166

166167

0 commit comments

Comments
 (0)