Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Parsing and testing improvements #312

Merged
merged 3 commits into from
Nov 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ Notable changes include:
* Clang C++ warnings have eliminated, so the Clang CI tests have been updated to treat warnings as errors.
* Fix for installing libraries when building individual package WITH ENABLE_DEV_BUILD=On.
* Bugfix for RZ solid CRKSPH with compatible energy.
* Parsing of None string now always becomes None python type. Tests have been updated accordingly.
* IO for checkpoints and visuzalization can now be properly turned off through SpheralController input options.

Version v2024.06.1 -- Release date 2024-07-09
==============================================
Expand Down
8 changes: 4 additions & 4 deletions src/PYB11/Utilities/Utilities_PYB11.py
Original file line number Diff line number Diff line change
Expand Up @@ -819,7 +819,7 @@ def clippedVolume(poly = "const Dim<3>::FacetedVolume&",
("long", "Long"),
("double", "Scalar"),
("std::string", "String")):
exec("""
adiak_value%(label)s = PYB11TemplateFunction(adiak_value, "%(value)s")
adiak_value2%(label)s = PYB11TemplateFunction(adiak_value2, "%(value)s", pyname="adiak_value%(label)s")
""" % {"label" : label, "value" : value})
exec(f"""
adiak_value{label} = PYB11TemplateFunction(adiak_value, "{value}", pyname="adiak_value")
adiak_value2{label} = PYB11TemplateFunction(adiak_value2, "{value}", pyname="adiak_value")
""")
1 change: 1 addition & 0 deletions src/SimulationControl/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ spheral_install_python_files(
SpheralPolytopeSiloDump.py
Spheral1dVizDump.py
SpheralMatplotlib.py
SpheralTimingParser.py
findLastRestart.py
Pnorm.py
filearraycmp.py
Expand Down
26 changes: 20 additions & 6 deletions src/SimulationControl/SpheralController.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from SpheralCompiledPackages import *
from SpheralTimer import SpheralTimer
from SpheralUtilities import adiak_value
from SpheralConservation import SpheralConservation
from GzipFileIO import GzipFileIO
from SpheralTestUtilities import globalFrame
Expand Down Expand Up @@ -52,6 +53,7 @@ def __init__(self, integrator,
volumeType = RKVolumeType.RKVoronoiVolume,
facetedBoundaries = None,
printAllTimers = False):
self.restartBaseName = restartBaseName
self.restart = RestartableObject(self)
self.integrator = integrator
self.restartObjects = restartObjects
Expand Down Expand Up @@ -80,6 +82,7 @@ def __init__(self, integrator,

# Determine the dimensionality of this run, based on the integrator.
self.dim = "%id" % self.integrator.dataBase.nDim
adiak_value("dim", self.dim)

# Determine the visualization method.
if self.dim == "1d":
Expand All @@ -100,8 +103,11 @@ def __init__(self, integrator,
self.insertDistributedBoundary(integrator.physicsPackages())

# Should we look for the last restart set?
if restoreCycle == -1:
restoreCycle = findLastRestart(restartBaseName)
if restartBaseName:
if restoreCycle == -1:
restoreCycle = findLastRestart(restartBaseName)
else:
restoreCycle = None

# Generic initialization work.
self.reinitializeProblem(restartBaseName,
Expand Down Expand Up @@ -181,7 +187,8 @@ def reinitializeProblem(self, restartBaseName, vizBaseName,
self._periodicTimeWork = []

# Set the restart file base name.
self.setRestartBaseName(restartBaseName)
if restartBaseName:
self.setRestartBaseName(restartBaseName)

# Set the simulation time.
self.integrator.currentTime = initialTime
Expand Down Expand Up @@ -393,9 +400,12 @@ def advance(self, goalTime, maxSteps=None):
numActualGhostNodes = 0
for bc in bcs:
numActualGhostNodes += bc.numGhostNodes
print("Total number of (internal, ghost, active ghost) nodes : (%i, %i, %i)" % (mpi.allreduce(db.numInternalNodes, mpi.SUM),
mpi.allreduce(db.numGhostNodes, mpi.SUM),
mpi.allreduce(numActualGhostNodes, mpi.SUM)))
numInternal = db.globalNumInternalNodes
numGhost = db.globalNumGhostNodes
numActGhost = mpi.allreduce(numActualGhostNodes, mpi.SUM)
print(f"Total number of (internal, ghost, active ghost) nodes : ({numInternal}, {numGhost}, {numActGhost})")
adiak_value("total_internal_nodes", numInternal)
adiak_value("total_ghost_nodes", numGhost)

# Print how much time was spent per integration cycle.
self.stepTimer.printStatus()
Expand Down Expand Up @@ -566,6 +576,8 @@ def findname(thing):
#--------------------------------------------------------------------------
def dropRestartFile(self):

if not self.restartBaseName:
return
# First find out if the requested directory exists.
import os
dire = os.path.dirname(os.path.abspath(self.restartBaseName))
Expand Down Expand Up @@ -594,6 +606,8 @@ def dropRestartFile(self):
def loadRestartFile(self, restoreCycle,
frameDict=None):

if not self.restartBaseName:
return
# Find out if the requested file exists.
import os
fileName = self.restartBaseName + "_cycle%i" % restoreCycle
Expand Down
75 changes: 23 additions & 52 deletions src/SimulationControl/SpheralOptionParser.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,36 +7,41 @@
from SpheralCompiledPackages import *

from SpheralTestUtilities import globalFrame
from SpheralUtilities import TimerMgr
import SpheralTimingParser

def parse_value(value):
gd = globalFrame().f_globals
try:
return eval(value, gd)
except:
return value

def commandLine(**options):

# Build a command line parser with the keyword arguments passed to us.
parser = argparse.ArgumentParser()
for key in options:
parser.add_argument("--" + key,
dest = key,
default = options[key])
for key, default in options.items():
if default == "None":
raise SyntaxError(f"ERROR: {key}, None as a default value cannot be a string")
elif type(default) is str:
parser.add_argument(f"--{key}", type = str, default = default)
else:
parser.add_argument(f"--{key}", type = parse_value, default = default)

# Add the universal options supported by all Spheral++ scripts.
parser.add_argument("-v", "--verbose",
action = "store_true",
dest = "verbose",
default = False,
help = "Verbose output -- print all options that were set.")
parser.add_argument("--caliperConfig", default="", type=str)
parser.add_argument("--caliperFilename", default="", type=str)
parser.add_argument("--caliperConfigJSON", default="", type=str)

# Parse Caliper and Adiak inputs
SpheralTimingParser.add_timing_args(parser)

# Evaluate the command line.
args = parser.parse_args()
arg_dict = vars(args)

if (not TimerMgr.timers_usable()):
if (args.caliperConfig or args.caliperFilename or args.caliperConfigJSON):
print("WARNING: Caliper command line inputs provided for "+\
"non-timer install. Reconfigure the install with "+\
"-DENABLE_TIMER=ON to be able to use Caliper timers.")

# Verbose output?
if args.verbose:
print("All parameters set:")
Expand All @@ -46,46 +51,12 @@ def commandLine(**options):
print(" * ", key, " = ", val)
else:
print(" ", key, " = ", val)
if (args.caliperConfig):
print(" * caliperConfig = ", args.caliperConfig)
if (args.caliperFilename):
print(" * caliperFilename = ", args.caliperFilename)
if (args.caliperConfigJSON):
print(" * caliperConfigJSON = ", args.caliperConfigJSON)
# Set all the variables.
gd = globalFrame().f_globals
for key, val in arg_dict.items():
if key in options:
if (type(val) != type(options[key])):
val = eval(val, gd)
if val == "None":
val = None
gd[key] = val
# Initialize timers
InitTimers(args.caliperConfig, args.caliperFilename, args.caliperConfigJSON)
return

def InitTimers(caliper_config, filename, caliper_json):
if(caliper_json):
TimerMgr.load(caliper_json)
if(not caliper_config):
raise RuntimeError("SpheralOptionParser: specifying a configuration file without using one of the configurations means no timers are started")
off_tests = ["none", "off", "disable", "disabled", "0"]
if (caliper_config.lower() in off_tests):
return
elif (caliper_config):
TimerMgr.add(caliper_config)
TimerMgr.start()
else:
import os, sys
if (filename):
testname = filename
else:
from datetime import datetime
# Append the current day and time to the filename
unique_digits = datetime.now().strftime("_%Y_%m_%d_%H%M%S_%f")
# Name file based on name of python file being run
testname = os.path.splitext(os.path.basename(sys.argv[0]))[0]
testname += unique_digits + ".cali"
TimerMgr.default_start(testname)
adiak_valueInt("threads_per_rank", omp_get_num_threads())
adiak_valueInt("num_ranks", mpi.procs)
# Initialize timers and add inputs as Adiak metadata
SpheralTimingParser.init_timer(args)
return
100 changes: 100 additions & 0 deletions src/SimulationControl/SpheralTimingParser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
#-------------------------------------------------------------------------------
# Functions for adding Caliper and Adiak parsing arguments and initializing
# the timer manager
#-------------------------------------------------------------------------------

import argparse, mpi
from SpheralUtilities import TimerMgr
from SpheralUtilities import adiak_value
import SpheralOpenMP

cali_args = ["caliperConfig", "caliperFilename", "caliperConfigJSON"]

def parse_dict(string):
"""
Function to parse a dictionary provided through the command line
"""
try:
inp_dict = dict(item.split(":") for item in string.split(","))
except:
raise SyntaxError("Input to --adiakData must be in key:value format, separated by commas")
new_dict = {}
for ikey, ival in inp_dict.items():
try:
key = eval(ikey)
except:
key = ikey.strip()
try:
val = eval(ival)
except:
val = ival.strip()
new_dict.update({key: val})
return new_dict

def add_timing_args(parser):
"""
Add Caliper and Adiak arguments to the parser
"""
# Allow Adiak values to be set on the command line
# Inputs are a string that can be evaluated into a dictionary
# For example, --adiakData "testname: ShockTube1, testing:3"
parser.add_argument("--adiakData", default=None,
type=parse_dict)
# This logic checks if the user already set a Caliper
# argument and default value and prevents adding the argument
# if it already exists
arg_list = [action.dest for action in parser._actions]
for ca in cali_args:
if (ca not in arg_list):
parser.add_argument(f"--{ca}", default="", type=str)

def init_timer(args):
"""
Initializes the timing manager and adds input values to Adiak from parsed arguments
"""
if args.verbose:
if (args.caliperConfig):
print(" * caliperConfig = ", args.caliperConfig)
if (args.caliperFilename):
print(" * caliperFilename = ", ars.caliperFilename)
if (args.caliperConfigJSON):
print(" * caliperConfigJSON = ", args.caliperConfigJSON)
if (not TimerMgr.timers_usable()):
if (args.caliperConfig or args.caliperFilename or args.caliperConfigJSON):
print("WARNING: Caliper command line inputs provided for "+\
"non-timer install. Reconfigure the install with "+\
"-DENABLE_TIMER=ON to be able to use Caliper timers.")
if(args.caliperConfigJSON):
TimerMgr.load(args.caliperConfigJSON)
if(not args.caliperConfig):
raise RuntimeError("SpheralOptionParser: specifying a configuration file without "+\
"using one of the configurations means no timers are started")
off_tests = ["none", "off", "disable", "disabled", "0"]
# Check if Caliper is turned off
if (args.caliperConfig):
if (args.caliperConfig.lower() in off_tests):
return
TimerMgr.add(args.caliperConfig)
TimerMgr.start()
else:
import os, sys
# If output name for Caliper is given, use it
if (args.caliperFilename):
testname = args.caliperFilename
else:
from datetime import datetime
# Append the current day and time to the filename
unique_digits = datetime.now().strftime("_%Y_%m_%d_%H%M%S_%f")
# Name file based on name of python file being run
testname = os.path.splitext(os.path.basename(sys.argv[0]))[0]
testname += unique_digits + ".cali"
TimerMgr.default_start(testname)
# Add number of ranks and threads per rank
adiak_value("threads_per_rank", SpheralOpenMP.omp_get_num_threads())
adiak_value("num_ranks", mpi.procs)

# Add --adiakData inputs as Adiak metadata
if (args.adiakData):
for key, val in args.adiakData.items():
adiak_value(key, val)
return
2 changes: 1 addition & 1 deletion tests/functional/Damage/TensileDisk/TensileDisk-2d.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@
plotFlaws = False,
clearDirectories = False,
dataDirBase = "dumps-TensileDisk-2d",
outputFile = "None",
outputFile = None,

# Should we restart (-1 => find most advanced available restart)
restoreCycle = -1,
Expand Down
8 changes: 4 additions & 4 deletions tests/functional/Damage/TensileRod/TensileRod-1d.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,8 +171,8 @@ def restoreState(self, file, path):
clearDirectories = False,
referenceFile = "Reference/TensileRod-GradyKippOwen-1d-1proc-reproducing-20240816.gnu",
dataDirBase = "dumps-TensileRod-1d",
outputFile = "None",
comparisonFile = "None",
outputFile = None,
comparisonFile = None,
)

# On the IBM BlueOS machines we have some tolerance issues...
Expand Down Expand Up @@ -744,7 +744,7 @@ def restoreState(self, file, path):
#-------------------------------------------------------------------------------
# If requested, write out the state in a global ordering to a file.
#-------------------------------------------------------------------------------
if outputFile != "None":
if outputFile:
from SpheralTestUtilities import multiSort
state = State(db, integrator.physicsPackages())
outputFile = os.path.join(dataDir, outputFile)
Expand Down Expand Up @@ -786,7 +786,7 @@ def restoreState(self, file, path):
# Also we can optionally compare the current results with another file for
# bit level consistency.
#---------------------------------------------------------------------------
if comparisonFile != "None" and BuildData.cxx_compiler_id != "IntelLLVM":
if comparisonFile and BuildData.cxx_compiler_id != "IntelLLVM":
comparisonFile = os.path.join(dataDir, comparisonFile)
import filecmp
assert filecmp.cmp(outputFile, comparisonFile)
Expand Down
2 changes: 1 addition & 1 deletion tests/functional/Damage/TensileRod/TensileRod-2d.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ def restoreState(self, file, path):

clearDirectories = False,
dataDirBase = "dumps-TensileRod-2d",
outputFile = "None",
outputFile = None,
)

dx = xlength/nx
Expand Down
2 changes: 1 addition & 1 deletion tests/functional/Gravity/CollisionlessSphereCollapse.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@
#-------------------------------------------------------------------------------
# If requested, write out the profiles
#-------------------------------------------------------------------------------
if outputFile != "None" and mpi.rank == 0:
if outputFile and mpi.rank == 0:
outputFile = os.path.join(dataDir, outputFile)
f = open(outputFile, "w")
f.write(("# " + 14*"%15s " + "\n") % ("r", "x", "y", "z", "vx", "vy", "vz", "Hxx", "Hxy", "Hxz", "Hyy", "Hyz", "Hzz", "phi"))
Expand Down
Loading