Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into drewj/bu-rotate-with-…
Browse files Browse the repository at this point in the history
…pin-dep

* origin/main:
  Improving calculation for total job memory (#2018)
  Cleaning out unnecessary uses of r.o (#1999)
  • Loading branch information
drewj-tp committed Nov 22, 2024
2 parents 596b7ca + 2afaa22 commit 69519ad
Show file tree
Hide file tree
Showing 8 changed files with 54 additions and 72 deletions.
15 changes: 6 additions & 9 deletions armi/bookkeeping/memoryProfiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@
https://pythonhosted.org/psutil/
https://docs.python.org/3/library/gc.html#gc.garbage
"""
from math import floor
from os import cpu_count
from typing import Optional
import gc
Expand Down Expand Up @@ -71,14 +70,11 @@ def describeInterfaces(cs):
return (MemoryProfiler, {})


def getTotalJobMemory(nTasksPerNode):
def getTotalJobMemory(nTasks, cpusPerTask):
"""Function to calculate the total memory of a job. This is a constant during a simulation."""
cpuPerNode = cpu_count()
ramPerCpuGB = psutil.virtual_memory().total / (1024**3) / cpuPerNode
if nTasksPerNode == 0:
nTasksPerNode = cpuPerNode
cpusPerTask = floor(cpuPerNode / nTasksPerNode)
jobMem = nTasksPerNode * cpusPerTask * ramPerCpuGB
jobMem = nTasks * cpusPerTask * ramPerCpuGB
return jobMem


Expand Down Expand Up @@ -137,14 +133,15 @@ def interactEOL(self):
def printCurrentMemoryState(self):
"""Print the current memory footprint and available memory."""
try:
nTasksPerNode = self.cs["nTasksPerNode"]
cpusPerTask = self.cs["cpusPerTask"]
except NonexistentSetting:
runLog.extra(
"To view memory consumed, remaining available, and total allocated for a case, "
"add the setting 'nTasksPerNode' to your application."
"add the setting 'cpusPerTask' to your application."
)
return
totalMemoryInGB = getTotalJobMemory(nTasksPerNode)
nTasks = self.cs["nTasks"]
totalMemoryInGB = getTotalJobMemory(nTasks, cpusPerTask)
currentMemoryUsageInGB = getCurrentMemoryUsage() / 1024
availableMemoryInGB = totalMemoryInGB - currentMemoryUsageInGB
runLog.info(
Expand Down
29 changes: 22 additions & 7 deletions armi/bookkeeping/tests/test_memoryProfiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,9 +139,18 @@ def test_getTotalJobMemory(self, mockCpuCount, mockVMem):
vMem.total = (1024**3) * 50
mockVMem.return_value = vMem

expectedArrangement = {0: 50, 1: 50, 2: 50, 3: 45, 4: 40, 5: 50}
for nTasksPerNode, jobMemory in expectedArrangement.items():
self.assertEqual(getTotalJobMemory(nTasksPerNode), jobMemory)
expectedArrangement = {
(10, 1): 50,
(1, 10): 50,
(2, 5): 50,
(3, 3): 45,
(4, 1): 20,
(2, 4): 40,
(5, 2): 50,
}
for compReq, jobMemory in expectedArrangement.items():
# compReq[0] is nTasks and compReq[1] is cpusPerTask
self.assertEqual(getTotalJobMemory(compReq[0], compReq[1]), jobMemory)

@patch("armi.bookkeeping.memoryProfiler.PrintSystemMemoryUsageAction")
@patch("armi.bookkeeping.memoryProfiler.SystemAndProcessMemoryUsage")
Expand All @@ -166,20 +175,26 @@ def test_printCurrentMemoryState(
mockVMem.return_value = vMem
self._setMemUseMock(mockPrintSysMemUseAction)
with mockRunLogs.BufferLog() as mockLogs:
csMock = MagicMock()
csMock.__getitem__.return_value = 2
self.memPro.cs = csMock
self.memPro.cs = {"cpusPerTask": 1, "nTasks": 10}
self.memPro.printCurrentMemoryState()
stdOut = mockLogs.getStdout()
self.assertIn("Currently using 6.0 GB of memory.", stdOut)
self.assertIn("There is 44.0 GB of memory left.", stdOut)
self.assertIn("There is a total allocation of 50.0 GB", stdOut)
# Try another for funzies where we only use half the available resources on the node
mockLogs.emptyStdout()
self.memPro.cs = {"cpusPerTask": 5, "nTasks": 1}
self.memPro.printCurrentMemoryState()
stdOut = mockLogs.getStdout()
self.assertIn("Currently using 6.0 GB of memory.", stdOut)
self.assertIn("There is 19.0 GB of memory left.", stdOut)
self.assertIn("There is a total allocation of 25.0 GB", stdOut)

def test_printCurrentMemoryState_noSetting(self):
"""Test that the try/except works as it should."""
expectedStr = (
"To view memory consumed, remaining available, and total allocated for a case, "
"add the setting 'nTasksPerNode' to your application."
"add the setting 'cpusPerTask' to your application."
)
with mockRunLogs.BufferLog() as mockLogs:
self.memPro.printCurrentMemoryState()
Expand Down
6 changes: 2 additions & 4 deletions armi/mpiActions.py
Original file line number Diff line number Diff line change
Expand Up @@ -505,7 +505,7 @@ def __init__(self, skipInterfaces=False):
self._skipInterfaces = skipInterfaces

def invokeHook(self):
r"""Sync up all nodes with the reactor, the cs, and the interfaces.
"""Sync up all nodes with the reactor, the cs, and the interfaces.
Notes
-----
Expand Down Expand Up @@ -604,9 +604,7 @@ def _distributeReactor(self, cs):

self.r.o = self.o

runLog.debug(
"The reactor has {} assemblies".format(len(self.r.core.getAssemblies()))
)
runLog.debug(f"The reactor has {len(self.r.core.getAssemblies())} assemblies")
# attach here so any interface actions use a properly-setup reactor.
self.o.reattach(self.r, cs) # sets r and cs

Expand Down
4 changes: 2 additions & 2 deletions armi/physics/fuelCycle/tests/test_fuelHandlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -482,7 +482,7 @@ def test_repeatShuffles(self):
self.assertEqual(a.getLocation(), "SFP")

# do some shuffles
fh = self.r.o.getInterface("fuelHandler")
fh = self.o.getInterface("fuelHandler")
self.runShuffling(fh) # changes caseTitle

# Make sure the generated shuffles file matches the tracked one. This will need to be
Expand All @@ -505,7 +505,7 @@ def test_repeatShuffles(self):
newSettings["explicitRepeatShuffles"] = "armiRun-SHUFFLES.txt"
self.o.cs = self.o.cs.modified(newSettings=newSettings)

fh = self.r.o.getInterface("fuelHandler")
fh = self.o.getInterface("fuelHandler")

self.runShuffling(fh)

Expand Down
23 changes: 11 additions & 12 deletions armi/physics/neutronics/macroXSGenerationInterface.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@
from armi import mpiActions
from armi import runLog
from armi.nuclearDataIO import xsCollections
from armi.utils import iterables
from armi.physics.neutronics.settings import CONF_MINIMUM_NUCLIDE_DENSITY
from armi.utils import getBurnSteps, iterables


class MacroXSGenerator(mpiActions.MpiAction):
Expand Down Expand Up @@ -112,12 +112,12 @@ def invokeHook(self):

class MacroXSGenerationInterface(interfaces.Interface):
"""
Builds macroscopic cross sections on all blocks.
.. warning::
This probably shouldn't be an interface since it has no interactXYZ methods
It should probably be converted to an MpiAction.
Builds macroscopic cross sections on all Blocks.
Warning
-------
This probably shouldn't be an interface since it has no interactXYZ methods. It should probably
be converted to an MpiAction.
"""

name = "macroXsGen"
Expand All @@ -136,8 +136,7 @@ def buildMacros(
libType="micros",
):
"""
Builds block-level macroscopic cross sections for making diffusion
equation matrices.
Builds block-level macroscopic cross sections for making diffusion equation matrices.
This will use MPI if armi.context.MPI_SIZE > 1
Expand Down Expand Up @@ -188,8 +187,9 @@ def buildMacros(
block: either "micros" for neutron XS or "gammaXS" for gamma XS.
"""
cycle = self.r.p.cycle
burnSteps = getBurnSteps(self.cs)
self.macrosLastBuiltAt = (
sum([self.r.o.burnSteps[i] + 1 for i in range(cycle)]) + self.r.p.timeNode
sum([burnSteps[i] + 1 for i in range(cycle)]) + self.r.p.timeNode
)

runLog.important("Building macro XS")
Expand All @@ -205,10 +205,8 @@ def buildMacros(
xsGen.invoke(self.o, self.r, self.cs)


# helper functions for mpi communication


def _scatterList(lst):
"""Helper functions for mpi communication."""
if context.MPI_RANK == 0:
chunked = iterables.split(lst, context.MPI_SIZE)
else:
Expand All @@ -217,6 +215,7 @@ def _scatterList(lst):


def _gatherList(localList):
"""Helper functions for mpi communication."""
globalList = context.MPI_COMM.gather(localList, root=0)
if context.MPI_RANK == 0:
globalList = iterables.flatten(globalList)
Expand Down
27 changes: 11 additions & 16 deletions armi/reactor/assemblies.py
Original file line number Diff line number Diff line change
Expand Up @@ -1018,28 +1018,24 @@ def getParamValuesAtZ(
This caches interpolators for each param and must be cleared if new params are
set or new heights are set.
WARNING:
Fails when requested to extrapolate.With higher order splines it is possible
to interpolate non-physical values, for example a negative flux or dpa. Please
use caution when going off default in interpType and be certain that
interpolated values are physical.
Warning
-------
Fails when requested to extrapolate. With higher order splines it is possible to interpolate
non-physical values, for example, a negative flux or dpa. Please use caution when going off
default in interpType and be certain that interpolated values are physical.
Parameters
----------
param : str
the parameter to interpolate
elevations : array of float
the elevations from the bottom of the assembly in cm at which you want the
point.
the elevations from the bottom of the assembly in cm at which you want the point.
interpType: str or int
used in interp1d. interp1d documention: Specifies the kind of interpolation
as a string ('linear', 'nearest', 'zero', 'slinear', 'quadratic', 'cubic'
where 'slinear', 'quadratic' and 'cubic' refer to a spline interpolation of
first, second or third order) or as an integer specifying the order of the
spline interpolator to use. Default is 'linear'.
fillValue: str
Rough pass through to scipy.interpolate.interp1d. If 'extend', then the
lower and upper bounds are used as the extended value. If 'extrapolate',
Expand Down Expand Up @@ -1070,23 +1066,22 @@ def getParamOfZFunction(self, param, interpType="linear", fillValue=np.NaN):
This caches interpolators for each param and must be cleared if new params are
set or new heights are set.
WARNING: Fails when requested to extrapololate. With higher order splines it is
possible to interpolate nonphysical values, for example a negative flux or dpa.
Please use caution when going off default in interpType and be certain that
interpolated values are physical.
Warning
-------
Fails when requested to extrapololate. With higher order splines it is possible to
interpolate nonphysical values, for example, a negative flux or dpa. Please use caution when
going off default in interpType and be certain that interpolated values are physical.
Parameters
----------
param : str
the parameter to interpolate
interpType: str or int
used in interp1d. interp1d documention: Specifies the kind of interpolation
as a string ('linear', 'nearest', 'zero', 'slinear', 'quadratic', 'cubic'
where 'slinear', 'quadratic' and 'cubic' refer to a spline interpolation of
first, second or third order) or as an integer specifying the order of the
spline interpolator to use. Default is 'linear'.
fillValue: float
Rough pass through to scipy.interpolate.interp1d. If 'extend', then the
lower and upper bounds are used as the extended value. If 'extrapolate',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,6 @@ def convert(self, r=None):
)


class CustomModifier(ParameterSweepConverter):
"""Invoke the shuffle logic `applyCustomPerturbation` method to make a custom setting."""

def convert(self, r=None):
ParameterSweepConverter.convert(self, r)
fh = r.o.getInterface("fuelHandler")
fh.applyCustomPerturbation(self._parameter)


class NeutronicConvergenceModifier(ParameterSweepConverter):
"""Adjusts the neutronics convergence parameters."""

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@

from armi.tests import TEST_ROOT
from armi.reactor.converters.parameterSweeps.generalParameterSweepConverters import (
CustomModifier,
NeutronicConvergenceModifier,
ParameterSweepConverter,
SettingsModifier,
Expand Down Expand Up @@ -64,15 +63,3 @@ def test_settingsModifier(self):
# NOTE: Settings objects are not modified, but we point to new objects
self.assertIn("Simple test input", self.cs["comment"])
self.assertEqual(con._cs["comment"], "FakeParam")

def test_customModifier(self):
"""Super basic test of the Custom Modifier."""
con = CustomModifier(self.cs, "FakeParam")
self.assertEqual(con._parameter, "FakeParam")

# For testing purposes, we need to add a dummy perturbation
fh = self.r.o.getInterface("fuelHandler")
fh.applyCustomPerturbation = lambda val: val

con.convert(self.r)
self.assertEqual(con._sourceReactor, self.r)

0 comments on commit 69519ad

Please sign in to comment.