Skip to content

Commit

Permalink
Merge pull request #312 from LLNL/bugfix/fix_none_type_options
Browse files Browse the repository at this point in the history
Parsing and testing improvements
  • Loading branch information
ldowen authored Nov 6, 2024
2 parents 50eec5f + 2148816 commit 7104104
Show file tree
Hide file tree
Showing 72 changed files with 439 additions and 325 deletions.
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

0 comments on commit 7104104

Please sign in to comment.