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

Add measure of convergence for tight coupling #1033

Merged
merged 43 commits into from
Jan 23, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
df348b0
initial commit
albeanth Nov 2, 2022
237e314
adding power as convergence option for global flux
albeanth Nov 3, 2022
6dde1a3
merge main + resolve merge conflicts
albeanth Dec 16, 2022
76c0105
Merge branch 'main' into tightCoupling_Convergence
albeanth Dec 16, 2022
167d23c
Merge branch 'main' into tightCoupling_Convergence
albeanth Dec 21, 2022
b0debd1
decrease default # of tight coupling iters
albeanth Dec 21, 2022
931763e
reviewer feedback
albeanth Dec 21, 2022
43f98e1
reviewer feedback
albeanth Dec 21, 2022
f8f8a8e
replace key with .join() + black formatting
albeanth Dec 22, 2022
57d5a60
Merge branch 'main' into tightCoupling_Convergence
albeanth Jan 3, 2023
7980951
rm tightCouplingReport at end of coupling loop
albeanth Jan 3, 2023
facb177
add unittests + rm _checkTightCouplingConvergence
albeanth Jan 3, 2023
d79f967
Update the implementation to:
jakehader Jan 4, 2023
e40104b
a couple simple typo fixes
albeanth Jan 5, 2023
e279efc
adding unit test for exception
albeanth Jan 5, 2023
22c4b06
simplify conditionals
albeanth Jan 5, 2023
668f87a
update coupling tests with new structure
albeanth Jan 5, 2023
beceef7
replace class attribute for TightCoupler attribute
albeanth Jan 5, 2023
914599d
chnge TightCoupler.param to TightCoupler.parameter
albeanth Jan 5, 2023
e01ecd0
update o.interactAllCoupled + unit testing
albeanth Jan 5, 2023
ceb13d8
add runLog.warning on maxNumIters + pylint cleanup
albeanth Jan 5, 2023
47d1021
adding unit tests +cleanup
albeanth Jan 6, 2023
5b79e5e
reviewer feedback for _setTightCouplerByInterfaceFunction
albeanth Jan 6, 2023
88770af
add unittest + coupler defaults for global flux interface
albeanth Jan 6, 2023
c7ea9b1
clean up import
albeanth Jan 6, 2023
b458f38
rm lingering "numCoupledIterations" + black
albeanth Jan 6, 2023
ba816ff
updating settings validators + adding additional notes for new tight …
albeanth Jan 6, 2023
e7200eb
black formatting
albeanth Jan 6, 2023
b2bd487
fix unit tests
albeanth Jan 6, 2023
949d7c6
reviewer comments to cleanup interfaces.py
albeanth Jan 9, 2023
1bf6db9
update constructor for tight coupling defaults
albeanth Jan 10, 2023
abe98ac
rm cs from setTightCouplingDefaults
albeanth Jan 11, 2023
bf79d0b
bug fixes for TightCoupler.isConverged()
albeanth Jan 12, 2023
d909f2e
release notes
albeanth Jan 13, 2023
ae34fbf
reviewer feedback on static method + private
albeanth Jan 13, 2023
0b5ab65
merge in main + resolve merge conflicts
albeanth Jan 13, 2023
77f8591
initial cut at revised physics coupling docs
albeanth Jan 16, 2023
dbd729b
Merge branch 'main' into tightCoupling_Convergence
albeanth Jan 17, 2023
7a6fa1c
it helps to add the illustrations used in the docs
albeanth Jan 17, 2023
024e5f9
slight revision to docs
albeanth Jan 17, 2023
48da0c8
rename assorted_guide to physics_coupling
albeanth Jan 17, 2023
a3a5ab2
update srsd
albeanth Jan 17, 2023
fd0b812
update copyright for new files
albeanth Jan 17, 2023
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: 1 addition & 1 deletion armi/bookkeeping/report/newReportUtils.py
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ def _setGeneralSimulationData(core, cs, coreDesignTable):
coreDesignTable.addRow([" ", ""])
coreDesignTable.addRow(["Full Core Model", "{}".format(core.isFullCore)])
coreDesignTable.addRow(
["Loose Physics Coupling Enabled", "{}".format(bool(cs["looseCoupling"]))]
["Tight Physics Coupling Enabled", "{}".format(bool(cs["tightCoupling"]))]
)
coreDesignTable.addRow(["Lattice Physics Enabled for", "{}".format(cs["genXS"])])
coreDesignTable.addRow(
Expand Down
4 changes: 2 additions & 2 deletions armi/bookkeeping/report/reportingUtils.py
Original file line number Diff line number Diff line change
Expand Up @@ -765,8 +765,8 @@ def _setGeneralSimulationData(core, cs, coreDesignTable):
"Full Core Model", "{}".format(core.isFullCore), coreDesignTable, report.DESIGN
)
report.setData(
"Loose Physics Coupling Enabled",
"{}".format(bool(cs["looseCoupling"])),
"Tight Physics Coupling Enabled",
"{}".format(bool(cs["tightCoupling"])),
coreDesignTable,
report.DESIGN,
)
Expand Down
9 changes: 9 additions & 0 deletions armi/interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,11 @@ def __init__(self, r, cs):
self.cs = settings.getMasterCs() if cs is None else cs
self.r = r
self.o = r.o if r else None
# default interface variables used for tight coupling
self.tightCouplingTolerance = 1e-4
john-science marked this conversation as resolved.
Show resolved Hide resolved
self.tightCouplingVariables = None
self.tightCouplingConvergeOn = None
john-science marked this conversation as resolved.
Show resolved Hide resolved
self.tightCouplingOldValue = None
albeanth marked this conversation as resolved.
Show resolved Hide resolved

def __repr__(self):
return "<Interface {0}>".format(self.name)
Expand Down Expand Up @@ -310,6 +315,10 @@ def interactCoupled(self, iteration):
"""Called repeatedly at each time node/subcycle when tight physics couping is active."""
pass

def getTightCouplingValue(self):
"""abstract method to retrive the value in which tight coupling is to converge on"""
pass

albeanth marked this conversation as resolved.
Show resolved Hide resolved
def interactError(self):
"""Called if an error occurs."""
pass
Expand Down
66 changes: 63 additions & 3 deletions armi/operators/operator.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@
import re
import shutil
import time
import collections
from tabulate import tabulate
jakehader marked this conversation as resolved.
Show resolved Hide resolved
from numpy.linalg import norm
from numpy import (
subtract,
inf,
ndarray,
)

from armi import context
from armi import interfaces
Expand Down Expand Up @@ -133,6 +141,7 @@ def __init__(self, cs):
self._maxBurnSteps = None
self._powerFractions = None
self._availabilityFactors = None
self.convergenceSummary = None

# Create the welcome headers for the case (case, input, machine, and some basic reactor information)
reportingUtils.writeWelcomeHeaders(self, cs)
Expand Down Expand Up @@ -376,10 +385,16 @@ def _timeNodeLoop(self, cycle, timeNode):
self.r.p.timeNode = timeNode
self.interactAllEveryNode(cycle, timeNode)
# perform tight coupling if requested
if self.cs["numCoupledIterations"]:
for coupledIteration in range(self.cs["numCoupledIterations"]):
if self.cs["tightCoupling"]:
albeanth marked this conversation as resolved.
Show resolved Hide resolved
self.convergenceSummary = collections.defaultdict(list)
for coupledIteration in range(self.cs["tightCouplingMaxNumIters"]):
self.r.core.p.coupledIteration = coupledIteration + 1
self._tightCouplingOldValues()
john-science marked this conversation as resolved.
Show resolved Hide resolved
self.interactAllCoupled(coupledIteration)
converged = self._computeTightCouplingConvergence()
if converged:
break
self._printTightCouplingReport()
# database has not yet been written, so we need to write it.
dbi = self.getInterface("database")
dbi.writeDBEveryNode(cycle, timeNode)
Expand Down Expand Up @@ -618,6 +633,51 @@ def interactAllCoupled(self, coupledIteration):
activeInterfaces = [ii for ii in self.interfaces if ii.enabled()]
self._interactAll("Coupled", activeInterfaces, coupledIteration)

def _tightCouplingOldValues(self):
activeInterfaces = [ii for ii in self.interfaces if ii.enabled()]
for interface in activeInterfaces:
interface.tightCouplingOldValue = interface.getTightCouplingValue()
jakehader marked this conversation as resolved.
Show resolved Hide resolved

def _computeTightCouplingConvergence(self):
jakehader marked this conversation as resolved.
Show resolved Hide resolved
activeInterfaces = [ii for ii in self.interfaces if ii.enabled()]
john-science marked this conversation as resolved.
Show resolved Hide resolved
convergence = []
for interface in activeInterfaces:
if interface.tightCouplingOldValue is not None:
jakehader marked this conversation as resolved.
Show resolved Hide resolved
key = interface.name+": "+interface.tightCouplingConvergeOn
if isinstance(interface.tightCouplingOldValue, float):
eps = abs(interface.tightCouplingOldValue - interface.getTightCouplingValue())
elif isinstance(interface.tightCouplingOldValue, ndarray):
newValue = interface.getTightCouplingValue()
epsVec = []
for old,new in zip(interface.tightCouplingOldValue, newValue):
epsVec.append(norm(subtract(old,new), ord=2))
eps = norm(epsVec, ord=inf)
else:
raise RuntimeError("Only currently set up to handle either floats or lists... Sorry.")
self.convergenceSummary[key].append(eps)
if eps > interface.tightCouplingTolerance:
convergence.append(False)
else:
convergence.append(True)
albeanth marked this conversation as resolved.
Show resolved Hide resolved

self._printTightCouplingReport() # it's usually useful to print convergence progress as it unfolds
return all(convergence)

def _checkTightCouplingConvergence(self):
for interface,convergenceList in self.convergenceSummary.items():
for value in convergenceList:
if value > interface.tightCouplingTolerance:
# one of the interfaces is not converged
return False
# all interfaces are converged!
return True

def _printTightCouplingReport(self):
runLog.info("Tight Coupling Convergence Summary: Norm Type = Inf")
runLog.info(
tabulate(self.convergenceSummary, headers="keys", showindex=True, tablefmt="armi")
)

def interactAllError(self):
"""Interact when an error is raised by any other interface. Provides a wrap-up option on the way to a crash."""
for i in self.interfaces:
Expand Down Expand Up @@ -1063,4 +1123,4 @@ def setStateToDefault(cs):

def couplingIsActive(self):
"""True if any kind of physics coupling is active."""
return self.cs["looseCoupling"] or self.cs["numCoupledIterations"] > 0
return self.cs["tightCoupling"]
21 changes: 5 additions & 16 deletions armi/operators/settingsValidation.py
Original file line number Diff line number Diff line change
Expand Up @@ -478,22 +478,11 @@ def _willBeCopiedFrom(fName):
)

self.addQuery(
lambda: not self.cs["looseCoupling"]
and self.cs["numCoupledIterations"] > 0,
"You have {0} coupled iterations selected, but have not activated loose coupling.".format(
self.cs["numCoupledIterations"]
),
"Set looseCoupling to True?",
lambda: self._assignCS("looseCoupling", True),
)

self.addQuery(
lambda: self.cs["numCoupledIterations"] > 0,
"You have {0} coupling iterations selected.".format(
self.cs["numCoupledIterations"]
),
"1 coupling iteration doubles run time (2 triples, etc). Do you want to use 0 instead? ",
lambda: self._assignCS("numCoupledIterations", 0),
lambda: (not self.cs["tightCoupling"] and self.cs["tightCouplingMaxNumIters"] != 15),
"You've requested a non default number of tight coupling iterations but left tightCoupling: False."
"Do you want to set tightCoupling to True?",
"",
lambda: self._assignCS("tightCoupling", True),
)

self.addQuery(
Expand Down
20 changes: 20 additions & 0 deletions armi/physics/neutronics/globalFlux/globalFluxInterface.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ def __init__(self, r, cs):
else:
self.nodeFmt = "1d" # produce ig001_1.inp.
self._bocKeff = None # for tracking rxSwing
# for tight coupling, overwrite base class
self.tightCouplingVariables = ["keff", "power"]
self.tightCouplingConvergeOn = "keff"

@staticmethod
def getHistoryParams():
Expand Down Expand Up @@ -222,6 +225,23 @@ def interactCoupled(self, iteration):

GlobalFluxInterface.interactCoupled(self, iteration)

def getTightCouplingValue(self):
if self.tightCouplingConvergeOn == "keff":
return self.r.core.p.keff
if self.tightCouplingConvergeOn == "power":
scaledCorePowerDistribution = []
for a in self.r.core.getChildren():
scaledPower = []
assemPower = sum(b.p.power for b in a)
for b in a:
scaledPower.append(b.p.power/assemPower)

scaledCorePowerDistribution.append(scaledPower)

return numpy.array(scaledCorePowerDistribution, dtype=object)
albeanth marked this conversation as resolved.
Show resolved Hide resolved

return None

@staticmethod
def getOptionsCls():
"""
Expand Down
28 changes: 12 additions & 16 deletions armi/settings/fwSettings/globalSettings.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@
CONF_MODULE_VERBOSITY = "moduleVerbosity"
CONF_MPI_TASKS_PER_NODE = "mpiTasksPerNode"
CONF_N_CYCLES = "nCycles"
CONF_NUM_COUPLED_ITERATIONS = "numCoupledIterations"
CONF_TIGHT_COUPLING = "tightCoupling"
CONF_TIGHT_COUPLING_MAX_ITERS = "tightCouplingMaxNumIters"
CONF_OPERATOR_LOCATION = "operatorLocation"
CONF_OUTPUT_FILE_EXTENSION = "outputFileExtension"
CONF_PLOTS = "plots"
Expand All @@ -96,7 +97,6 @@
CONF_FLUX_RECON = "fluxRecon" # strange coupling in fuel handlers
CONF_INDEPENDENT_VARIABLES = "independentVariables"
CONF_HCF_CORETYPE = "HCFcoretype"
CONF_LOOSE_COUPLING = "looseCoupling"
CONF_T_IN = "Tin"
CONF_T_OUT = "Tout"
CONF_DEFERRED_INTERFACES_CYCLE = "deferredInterfacesCycle"
Expand Down Expand Up @@ -572,12 +572,16 @@ def defineSettings() -> List[setting.Setting]:
description="Number of blocks with control for a REBUS poison search",
),
setting.Setting(
CONF_NUM_COUPLED_ITERATIONS,
default=0,
label="Tight Coupling Iterations",
description="Number of tight coupled physics iterations to occur at each "
"timestep",
schema=vol.All(vol.Coerce(int), vol.Range(min=0)),
CONF_TIGHT_COUPLING,
default=False,
label="Tight Coupling",
description="Boolean to turn on/off tight coupling",
),
setting.Setting(
CONF_TIGHT_COUPLING_MAX_ITERS,
default=15,
albeanth marked this conversation as resolved.
Show resolved Hide resolved
label="Maximum number of iterations for tight coupling.",
description="Maximum number of iterations for tight coupling."
),
setting.Setting(
CONF_OPERATOR_LOCATION,
Expand Down Expand Up @@ -719,14 +723,6 @@ def defineSettings() -> List[setting.Setting]:
"on design being analyzed",
options=["TWRC", "TWRP", "TWRC-HEX"],
),
setting.Setting(
CONF_LOOSE_COUPLING,
default=False,
label="Activate Loose Physics Coupling",
description="Update material densities and dimensions after running "
"thermal-hydraulics. Note: Thermal-hydraulics calculation is needed "
"to perform the loose physics coupling calculation.",
),
setting.Setting(
CONF_T_IN,
default=360.0,
Expand Down
1 change: 0 additions & 1 deletion armi/tests/armiRun.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ settings:
armi.reactor.reactors: info
nCycles: 6
nodeGroup: OnlineNodes,TP
numCoupledIterations: 0
outputFileExtension: png
percentNaReduction: 10.0
power: 100000000.0
Expand Down
1 change: 0 additions & 1 deletion armi/tests/detailedAxialExpansion/armiRun.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ settings:
moduleVerbosity:
armi.reactor.reactors: info
nCycles: 6
numCoupledIterations: 0
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's remember to open a TON of PRs with this change in downstream repos!

outputFileExtension: png
power: 100000000.0
shuffleLogic: ../refSmallReactorShuffleLogic.py
Expand Down
2 changes: 1 addition & 1 deletion doc/user/assorted_guide.rst
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
Physics Coupling
----------------
Tight coupled physics can be activated though the ``numCoupledIterations``
Tight coupled physics can be activated though the ``tightCoupling``
setting. This is handled in the :py:meth:`mainOperate <armi.operators.Operator.mainOperate>`
method.