Skip to content

Commit

Permalink
fluids: Add smartsim regression testing
Browse files Browse the repository at this point in the history
Requires that the fluids code be built with SMARTREDIS_DIR set, and in
an environment with SmartSim (Python library) installed as well.

Co-authored by: Zach Atkins <[email protected]>
  • Loading branch information
jrwrigh committed Oct 11, 2023
1 parent 0b9902b commit 3b15795
Show file tree
Hide file tree
Showing 10 changed files with 403 additions and 38 deletions.
3 changes: 3 additions & 0 deletions .gitlab-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ noether-cpu:
# Libraries for examples
# -- PETSc with HIP (minimal)
- export PETSC_DIR=/projects/petsc PETSC_ARCH=mpich-hip && git -C $PETSC_DIR -c safe.directory=$PETSC_DIR describe
- source /home/jawr8143/SmartSimTestingSoftware/bin/activate && export SMARTREDIS_DIR=/home/jawr8143/SmartSimTestingSoftware/smartredis/install
- echo "-------------- PETSc ---------------" && make -C $PETSC_DIR info
- make -k -j$NPROC_CPU BACKENDS="$BACKENDS_CPU" JUNIT_BATCH="cpu" junit search="petsc fluids solids"
# -- MFEM v4.2
Expand Down Expand Up @@ -111,6 +112,7 @@ noether-rocm:
# Libraries for examples
# -- PETSc with HIP (minimal)
- export PETSC_DIR=/projects/petsc PETSC_ARCH=mpich-hip && git -C $PETSC_DIR -c safe.directory=$PETSC_DIR describe
- source /home/jawr8143/SmartSimTestingSoftware/bin/activate && export SMARTREDIS_DIR=/home/jawr8143/SmartSimTestingSoftware/smartredis/install
- echo "-------------- PETSc ---------------" && make -C $PETSC_DIR info
- make -k -j$NPROC_GPU BACKENDS="$BACKENDS_GPU" JUNIT_BATCH="hip" junit search="petsc fluids solids"
# -- MFEM v4.2
Expand Down Expand Up @@ -234,6 +236,7 @@ noether-cuda:
# Libraries for examples
# -- PETSc with CUDA (minimal)
- export PETSC_DIR=/projects/petsc PETSC_ARCH=mpich-cuda-O PETSC_OPTIONS='-use_gpu_aware_mpi 0' && git -C $PETSC_DIR -c safe.directory=$PETSC_DIR describe
- source /home/jawr8143/SmartSimTestingSoftware/bin/activate && export SMARTREDIS_DIR=/home/jawr8143/SmartSimTestingSoftware/smartredis/install
- echo "-------------- PETSc ---------------" && make -C $PETSC_DIR info
- make -k -j$NPROC_GPU JUNIT_BATCH="cuda" junit BACKENDS="$BACKENDS_GPU" search="petsc fluids solids"
# Report status
Expand Down
9 changes: 7 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -249,8 +249,10 @@ nekexamples := $(OBJDIR)/nek-bps
petscexamples.c := $(wildcard examples/petsc/*.c)
petscexamples := $(petscexamples.c:examples/petsc/%.c=$(OBJDIR)/petsc-%)
# Fluid Dynamics Examples
fluidsexamples.c := $(sort $(wildcard examples/fluids/*.c))
fluidsexamples := $(fluidsexamples.c:examples/fluids/%.c=$(OBJDIR)/fluids-%)
fluidsexamples.c := $(sort $(wildcard examples/fluids/*.c))
fluidsexamples.py := examples/fluids/smartsim_regression_framework.py
fluidsexamples := $(fluidsexamples.c:examples/fluids/%.c=$(OBJDIR)/fluids-%)
fluidsexamples += $(fluidsexamples.py:examples/fluids/%.py=$(OBJDIR)/fluids-py-%)
# Solid Mechanics Examples
solidsexamples.c := $(sort $(wildcard examples/solids/*.c))
solidsexamples := $(solidsexamples.c:examples/solids/%.c=$(OBJDIR)/solids-%)
Expand Down Expand Up @@ -614,6 +616,9 @@ $(OBJDIR)/fluids-% : examples/fluids/%.c examples/fluids/src/*.c examples/fluids
PETSC_DIR="$(abspath $(PETSC_DIR))" OPT="$(OPT)" $*
cp examples/fluids/$* $@

$(OBJDIR)/fluids-py-% : examples/fluids/%.py
cp $< $@

$(OBJDIR)/solids-% : examples/solids/%.c examples/solids/%.h \
examples/solids/problems/*.c examples/solids/src/*.c \
examples/solids/include/*.h examples/solids/problems/*.h examples/solids/qfunctions/*.h \
Expand Down
1 change: 1 addition & 0 deletions examples/fluids/navierstokes.c
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ int main(int argc, char **argv) {
MPI_Comm comm = PETSC_COMM_WORLD;
user->comm = comm;
PetscCall(ProcessCommandLineOptions(comm, app_ctx, bc));
if (app_ctx->test_type == TESTTYPE_EXTERNAL) RunExternalTest();

// ---------------------------------------------------------------------------
// Initialize libCEED
Expand Down
5 changes: 4 additions & 1 deletion examples/fluids/navierstokes.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,11 @@ typedef enum {
TESTTYPE_SOLVER = 1,
TESTTYPE_TURB_SPANSTATS = 2,
TESTTYPE_DIFF_FILTER = 3,
TESTTYPE_EXTERNAL = 4,
} TestType;
static const char *const TestTypes[] = {"none", "solver", "turb_spanstats", "diff_filter", "TestType", "TESTTYPE_", NULL};
static const char *const TestTypes[] = {"none", "solver", "turb_spanstats", "diff_filter", "external", "TestType", "TESTTYPE_", NULL};

PetscErrorCode RunExternalTest(void);

// Subgrid-Stress mode type
typedef enum {
Expand Down
195 changes: 195 additions & 0 deletions examples/fluids/smartsim_regression_framework.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
#!/usr/bin/env python3
from junit_xml import TestCase
from smartsim import Experiment
from smartsim.settings import RunSettings
from smartredis import Client
import numpy as np
from pathlib import Path
import argparse
import traceback
import sys
import time
from typing import Tuple
import os
import shutil
import logging

# autopep8 off
sys.path.insert(0, (Path(__file__).parents[3] / "tests/junit-xml").as_posix())
# autopep8 on

logging.disable(logging.WARNING)

fluids_example_dir = Path(__file__).parent.absolute()


class NoError(Exception):
pass


class SmartSimTest(object):

def __init__(self, directory_path: Path):
self.exp: Experiment
self.database = None
self.directory_path: Path = directory_path
self.original_path: Path

def setup(self):
"""To create the test directory and start SmartRedis database"""
self.original_path = Path(os.getcwd())

if self.directory_path.exists() and self.directory_path.is_dir():
shutil.rmtree(self.directory_path)
self.directory_path.mkdir()
os.chdir(self.directory_path)

PORT = 6780
self.exp = Experiment("test", launcher="local")
self.database = self.exp.create_database(port=PORT, batch=False, interface="lo")
self.exp.generate(self.database)
self.exp.start(self.database)

# SmartRedis will complain if these aren't set
os.environ['SR_LOG_FILE'] = 'R'
os.environ['SR_LOG_LEVEL'] = 'INFO'

def test(self, ceed_resource) -> Tuple[bool, Exception, str]:
client = None
arguments = []
try:
exe_path = "../../build/fluids-navierstokes"
arguments = [
'-ceed', ceed_resource,
'-options_file', (fluids_example_dir / 'blasius.yaml').as_posix(),
'-ts_max_steps', '2',
'-diff_filter_grid_based_width',
'-diff_filter_width_scaling', '1,0.7,1',
'-ts_monitor', '-snes_monitor',
'-diff_filter_ksp_max_it', '50', '-diff_filter_ksp_monitor',
'-degree', '1',
'-sgs_train_enable',
'-sgs_train_put_tensor_interval', '2',
]

run_settings = RunSettings(exe_path, exe_args=arguments)

client_exp = self.exp.create_model(f"client_{ceed_resource.replace('/', '_')}", run_settings)

# Start the client model
self.exp.start(client_exp, summary=False, block=True)

client = Client(cluster=False)

assert client.poll_tensor("sizeInfo", 250, 5)
assert np.all(client.get_tensor("sizeInfo") == np.array([5002, 12, 6, 1, 1, 0]))

assert client.poll_tensor("check-run", 250, 5)
assert client.get_tensor("check-run")[0] == 1

assert client.poll_tensor("tensor-ow", 250, 5)
assert client.get_tensor("tensor-ow")[0] == 1

assert client.poll_tensor("step", 250, 5)
assert client.get_tensor("step")[0] == 2

assert client.poll_tensor("y.0", 250, 5)
test_data_path = fluids_example_dir / "tests-output/y0_output.npy"
assert test_data_path.is_file()

y0_correct_value = np.load(test_data_path)
y0_database_value = client.get_tensor("y.0")
rtol = 1e-8
atol = 1e-8
if not np.allclose(y0_database_value, y0_correct_value, atol=atol, rtol=rtol):
# Check whether the S-frame-oriented vorticity vector's second component is just flipped.
# This can happen due to the eigenvector ordering changing based on whichever one is closest to the vorticity vector.
# If two eigenvectors are very close to the vorticity vector, this can cause the ordering to flip.
# This flipping of the vorticity vector is not incorrect, just a known sensitivity of the model.

total_tolerances = atol + rtol * np.abs(y0_correct_value) # mimic np.allclose tolerance calculation
idx_notclose = np.where(np.abs(y0_database_value - y0_correct_value) > total_tolerances)
if not np.all(idx_notclose[1] == 4):
# values other than vorticity are not close
test_fail = True
else:
database_vorticity = y0_database_value[idx_notclose]
correct_vorticity = y0_correct_value[idx_notclose]
test_fail = False if np.allclose(-database_vorticity, correct_vorticity,
atol=atol, rtol=rtol) else True

if test_fail:
database_output_path = Path(
f"./y0_database_values_{ceed_resource.replace('/', '_')}.npy").absolute()
np.save(database_output_path, y0_database_value)
raise AssertionError(f"Array values in database max difference: {np.max(np.abs(y0_correct_value - y0_database_value))}\n"
f"Array saved to {database_output_path.as_posix()}")

client.flush_db([os.environ["SSDB"]])
output = (True, NoError(), ' '.join(arguments))
except Exception as e:
output = (False, e, ' '.join(arguments))

finally:
if client:
client.flush_db([os.environ["SSDB"]])

return output

def test_junit(self, ceed_resource):
start: float = time.time()

passTest, exception, args = self.test(ceed_resource)

output = "" if isinstance(exception, NoError) else ''.join(
traceback.TracebackException.from_exception(exception).format())

test_case = TestCase(f'SmartSim Test {ceed_resource}',
elapsed_sec=time.time() - start,
timestamp=time.strftime(
'%Y-%m-%d %H:%M:%S %Z', time.localtime(start)),
stdout=output,
stderr=output,
allow_multiple_subelements=True)
test_case.args = args
if not passTest and 'occa' in ceed_resource:
test_case.add_skipped_info("OCCA mode not supported")
elif not passTest:
test_case.add_failure_info("exception", output)

return test_case

def teardown(self):
self.exp.stop(self.database)
os.chdir(self.original_path)


if __name__ == "__main__":
parser = argparse.ArgumentParser('Testing script for SmartSim integration')
parser.add_argument(
'-c',
'--ceed-backends',
type=str,
nargs='*',
default=['/cpu/self'],
help='libCEED backend to use with convergence tests')
args = parser.parse_args()

test_dir = fluids_example_dir / "test_dir"
print("Setting up database...", end='')
test_framework = SmartSimTest(test_dir)
test_framework.setup()
print(" Done!")
for ceed_resource in args.ceed_backends:
print("working on " + ceed_resource + ' ...', end='')
passTest, exception, _ = test_framework.test(ceed_resource)

if passTest:
print("Passed!")
else:
print("Failed!", file=sys.stderr)
print('\t' + ''.join(traceback.TracebackException.from_exception(exception).format()), file=sys.stderr)

print("Cleaning up database...", end='')
test_framework.teardown()
print(" Done!")
16 changes: 16 additions & 0 deletions examples/fluids/src/misc.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include <petscdm.h>
#include <petscsf.h>
#include <petscts.h>
#include <unistd.h>

#include "../navierstokes.h"
#include "../qfunctions/mass.h"
Expand Down Expand Up @@ -576,3 +577,18 @@ PetscErrorCode PrintRunInfo(User user, Physics phys_ctx, ProblemData *problem, M
}
PetscFunctionReturn(PETSC_SUCCESS);
}

// @brief For running tests that execute a script external to the code
PetscErrorCode RunExternalTest(void) {
char test_path[PETSC_MAX_PATH_LEN];
PetscBool is_set = PETSC_FALSE;

PetscFunctionBeginUser;
PetscCall(PetscOptionsGetString(NULL, NULL, "-external_test_path", test_path, PETSC_MAX_PATH_LEN, &is_set));
PetscCheck(is_set, PETSC_COMM_WORLD, PETSC_ERR_ARG_INCOMP, "'-external_test_path' flag must be set if '-test_type %s'",
TestTypes[TESTTYPE_EXTERNAL]);
char *test[] = {NULL};
execv(test_path, test);

PetscFunctionReturn(0);
}
Loading

0 comments on commit 3b15795

Please sign in to comment.