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

Zones overhaul #943

Merged
merged 29 commits into from
Oct 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
3b014c9
Removed all references to hot zones
john-science Aug 3, 2022
faeb477
Minor cleanup
john-science Aug 3, 2022
d4cac99
Removing byOrifice zone strategy
john-science Aug 3, 2022
9705565
Removing splitZones stuff
john-science Aug 3, 2022
89a2d54
Removed everyFA zoning strat
john-science Aug 3, 2022
6bd8e25
Removing byFuelType zoning strat
john-science Aug 4, 2022
ba3f22c
Code cleanup
john-science Aug 4, 2022
ee73f92
Removing byRing zoning strat
john-science Aug 6, 2022
e19b748
Removing unused imports
john-science Aug 6, 2022
34a166c
Removing addRing - the Reactor might not have rings
john-science Aug 6, 2022
474d388
Removing unused Zone.symmetry param
john-science Aug 6, 2022
f13f152
Adding a type to the zone (Assem or Block)
john-science Aug 6, 2022
b303bb4
First pass: complete rewrite of the Zone/Zones classes
john-science Aug 9, 2022
55faaf0
Fixing docstrings
john-science Aug 9, 2022
2e5fd73
Working placeholder for zoning Strat plugin
john-science Aug 10, 2022
1b50679
ArmiPlugin.defineZoningStrat now in place
john-science Aug 10, 2022
5fac427
Testing defineZoningStrategy plugin hook
john-science Aug 10, 2022
e890e8c
Change name to applyZoningStrategy
john-science Aug 11, 2022
06b47c7
Cleaning up Zones.summary
john-science Aug 11, 2022
c744f65
Adding removeXYZ functions to Zone and Zones classes in armi.reactor.…
joshuavictorchen Aug 25, 2022
b73f595
Merge commit '9a510ca' into zones_overhaul
john-science Aug 31, 2022
0dd52c9
Zones overhaul updates (#938)
joshuavictorchen Oct 12, 2022
999cb3e
merge latest main into zones_overhaul
joshuavictorchen Oct 13, 2022
110301a
merge actual main tip into zones_overhaul
joshuavictorchen Oct 13, 2022
b1536f6
Merge branch 'main' into zones_overhaul
joshuavictorchen Oct 17, 2022
72a9f3a
bump version number and update release notes
joshuavictorchen Oct 18, 2022
70743d8
Merge branch 'main' into zones_overhaul
john-science Oct 22, 2022
791131e
Removing false TODO
john-science Oct 22, 2022
6986c22
Removing unused imports
john-science Oct 22, 2022
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: 0 additions & 2 deletions armi/bookkeeping/report/reportInterface.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,6 @@ def interactBOL(self):
runLog.info(report.ALL[report.RUN_META])

def interactEveryNode(self, cycle, node):
if self.cs["zoneFlowSummary"]:
reportingUtils.summarizeZones(self.r.core, self.cs)
if self.cs["assemPowSummary"]:
reportingUtils.summarizePower(self.r.core)

Expand Down
86 changes: 0 additions & 86 deletions armi/bookkeeping/report/reportingUtils.py
Original file line number Diff line number Diff line change
Expand Up @@ -594,92 +594,6 @@ def summarizePower(core):
)


def summarizeZones(core, cs):
r"""Summarizes the active zone and other zone.

Parameters
----------
core: armi.reactor.reactors.Core
cs: armi.settings.caseSettings.Settings

"""

totPow = core.getTotalBlockParam("power")
if not totPow:
# protect against divide-by-zero
return
powList = [] # eventually will be a sorted list of power
for a in core.getAssemblies():
if a.hasFlags(Flags.FUEL):
aPow = a.calcTotalParam("power")
powList.append((aPow / totPow, a))
powList.sort() # lowest power assems first.

# now build "low power region" and high power region.
# at BOL (cycle 0) just take all feeds as low power. (why not just use power fractions?,
# oh, because if you do that, a few igniters will make up the 1st 5% of the power.)
totFrac = 0.0
lowPow = []
highPow = []
pFracList = [] # list of power fractions in the high power zone.

for pFrac, a in powList:
if core.r.p.cycle > 0 and totFrac <= cs["lowPowerRegionFraction"]:
lowPow.append(a)
elif (
core.r.p.cycle == 0
and a.hasFlags(Flags.FEED | Flags.FUEL)
and a.getMaxUraniumMassEnrich() > 0.01
):
lowPow.append(a)
else:
highPow.append(a)
pFracList.append(pFrac)
totFrac += pFrac

if not pFracList:
# sometimes this is empty (why?), which causes an error below when
# calling max(pFracList)
return

if abs(totFrac - 1.0) < 1e-4:
runLog.warning("total power fraction not equal to sum of assembly powers.")

peak = max(pFracList) # highest power assembly
peakIndex = pFracList.index(peak)
peakAssem = highPow[peakIndex]

avgPFrac = sum(pFracList) / len(pFracList) # true mean power fraction
# the closest-to-average pfrac in the list
_avgAssemPFrac, avgIndex = findClosest(pFracList, avgPFrac, indx=True)
avgAssem = highPow[avgIndex] # the actual average assembly

# ok, now need counts, and peak and avg. flow and power in high power region.
mult = core.powerMultiplier

summary = "Zone Summary For Safety Analysis cycle {0}\n".format(core.r.p.cycle)
summary += " Assemblies in high-power zone: {0}\n".format(len(highPow) * mult)
summary += " Assemblies in low-power zone: {0}\n".format(len(lowPow) * mult)
summary += " " * 13 + "{0:15s} {1:15s} {2:15s} {3:15s}\n".format(
"Location", "Power (W)", "Flow (kg/s)", "Pu frac"
)

for lab, a in [("Peak", peakAssem), ("Average", avgAssem)]:
flow = a.p.THmassFlowRate
if not flow:
runLog.warning("No TH data. Reporting zero flow.")
# no TH for some reason
flow = 0.0
puFrac = a.getPuFrac()
ring, pos = a.spatialLocator.getRingPos()
summary += (
" {0:10s} ({ring:02d}, {pos:02d}) {1:15.6E} {2:15.6E} {pu:15.6E}\n".format(
lab, a.calcTotalParam("power"), flow, ring=ring, pos=pos, pu=puFrac
)
)
runLog.important(summary)


def makeCoreDesignReport(core, cs):
r"""Builds report to summarize core design inputs

Expand Down
6 changes: 0 additions & 6 deletions armi/bookkeeping/report/tests/test_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
summarizePinDesign,
summarizePower,
summarizePowerPeaking,
summarizeZones,
writeAssemblyMassSummary,
writeCycleSummary,
)
Expand Down Expand Up @@ -123,11 +122,6 @@ def test_reactorSpecificReporting(self):
self.assertIn("End of Cycle", mock._outputStream)
mock._outputStream = ""

# this report won't do much for the test reactor - improve test reactor
summarizeZones(r.core, o.cs)
self.assertEqual(len(mock._outputStream), 0)
mock._outputStream = ""

# this report won't do much for the test reactor - improve test reactor
makeBlockDesignReport(r)
self.assertEqual(len(mock._outputStream), 0)
Expand Down
2 changes: 1 addition & 1 deletion armi/meta.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@
Metadata describing an ARMI distribution.
"""

__version__ = "0.2.4"
__version__ = "0.2.5"
10 changes: 0 additions & 10 deletions armi/operators/settingsValidation.py
Original file line number Diff line number Diff line change
Expand Up @@ -363,16 +363,6 @@ def _inspectSettings(self):
lambda: self._assignCS("outputFileExtension", "png"),
)

self.addQuery(
lambda: (
self.cs[globalSettings.CONF_ZONING_STRATEGY] == "manual"
and not self.cs["zoneDefinitions"]
),
"`manual` zoningStrategy requires that `zoneDefinitions` setting be defined. Run will have "
"no zones.",
"",
self.NO_ACTION,
)
self.addQuery(
lambda: (
(
Expand Down
1 change: 1 addition & 0 deletions armi/physics/fuelCycle/fuelHandlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -724,6 +724,7 @@ def getParamWithBlockLevelMax(a, paramName):
# this assembly is in the excluded location list. skip it.
continue

# only continue of the Assembly is in a Zone
if zoneList:
found = False # guilty until proven innocent
for zone in zoneList:
Expand Down
9 changes: 8 additions & 1 deletion armi/reactor/converters/geometryConverters.py
Original file line number Diff line number Diff line change
Expand Up @@ -1296,16 +1296,23 @@ def convert(self, r):
for a in self._sourceReactor.core.getAssemblies():
# make extras and add them too. since the input is assumed to be 1/3 core.
otherLocs = grid.getSymmetricEquivalents(a.spatialLocator.indices)
thisZone = (
self._sourceReactor.core.zones.findZoneItIsIn(a)
if len(self._sourceReactor.core.zones) > 0
else None
)
angle = 2 * math.pi / (len(otherLocs) + 1)
count = 1
for i, j in otherLocs:
newAssem = copy.deepcopy(a)
newAssem.makeUnique()
newAssem.rotate(count * angle)
count = count + 1
count += 1
self._sourceReactor.core.add(
newAssem, self._sourceReactor.core.spatialGrid[i, j, 0]
)
if thisZone:
thisZone.addLoc(newAssem.getLocation())
self._newAssembliesAdded.append(newAssem)

if a.getLocation() == "001-001":
Expand Down
8 changes: 4 additions & 4 deletions armi/reactor/grids.py
Original file line number Diff line number Diff line change
Expand Up @@ -1704,21 +1704,21 @@ def generateSortedHexLocationList(self, nLocs):
nLocs = int(nLocs) # need to make this an integer

# next, generate a list of locations and corresponding distances
locList = []
locs = []
for ring in range(1, hexagon.numRingsToHoldNumCells(nLocs) + 1):
positions = self.getPositionsInRing(ring)
for position in range(1, positions + 1):
i, j = self.getIndicesFromRingAndPos(ring, position)
locList.append(self[(i, j, 0)])
locs.append(self[(i, j, 0)])
# round to avoid differences due to floating point math
locList.sort(
locs.sort(
key=lambda loc: (
round(numpy.linalg.norm(loc.getGlobalCoordinates()), 6),
loc.i, # loc.i=ring
loc.j,
)
) # loc.j= pos
return locList[:nLocs]
return locs[:nLocs]

# TODO: this is only used by testing and another method that just needs the count of assemblies
# in a ring, not the actual positions
Expand Down
60 changes: 45 additions & 15 deletions armi/reactor/reactors.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ def __init__(self, name):
self.locParams = {} # location-based parameters
# overridden in case.py to include pre-reactor time.
self.timeOfStart = time.time()
self.zones = None
self.zones = zones.Zones() # initialize with empty Zones object
# initialize the list that holds all shuffles
self.moveList = {}
self.scalarVals = {}
Expand Down Expand Up @@ -1023,15 +1023,14 @@ def getAssemblies(
includeAll : bool, optional
Will include ALL assemblies.

zones : str or iterable, optional
Only include assemblies that are in this zone/these zones
zones : iterable, optional
Only include assemblies that are in this these zones

Notes
-----
Attempts have been made to make this a generator but there were some Cython
incompatibilities that we could not get around and so we are sticking with a
list.

"""
if includeAll:
includeBolAssems = includeSFP = True
Expand Down Expand Up @@ -1215,9 +1214,6 @@ def regenAssemblyLists(self):
"""
self._getAssembliesByName()
self._genBlocksByName()
runLog.important("Regenerating Core Zones")
# TODO: this call is questionable... the cs should correspond to analysis
self.buildZones(settings.getMasterCs())
self._genChildByLocationLookupTable()

def getAllXsSuffixes(self):
Expand Down Expand Up @@ -1764,11 +1760,6 @@ def getAssembliesOnSymmetryLine(self, symmetryLineID):
assembliesOnLine.sort(key=lambda a: a.spatialLocator.getRingPos())
return assembliesOnLine

def buildZones(self, cs):
"""Update the zones on the reactor."""
self.zones = zones.buildZones(self, cs)
self.zones = zones.splitZones(self, cs, self.zones)

def getCoreRadius(self):
"""Returns a radius that the core would fit into."""
return self.getNumRings(indexBased=True) * self.getFirstBlock().getPitch()
Expand Down Expand Up @@ -2315,15 +2306,54 @@ def processLoading(self, cs, dbLoad: bool = False):

self.stationaryBlockFlagsList = stationaryBlockFlags

# Perform initial zoning task
self.buildZones(cs)

self.p.maxAssemNum = self.getMaxParam("assemNum")

getPluginManagerOrFail().hook.onProcessCoreLoading(
core=self, cs=cs, dbLoad=dbLoad
)

def buildManualZones(self, cs):
"""
Build the Zones that are defined manually in the given CaseSettings file,
in the `zoneDefinitions` setting.

Parameters
----------
cs : CaseSettings
The standard ARMI settings object

Examples
--------
Manual zones will be defined in a special string format, e.g.:

zoneDefinitions:
- ring-1: 001-001
- ring-2: 002-001, 002-002
- ring-3: 003-001, 003-002, 003-003

Notes
-----
This function will just define the Zones it sees in the settings, it does
not do any validation against a Core object to ensure those manual zones
make sense.
"""
runLog.debug(
"Building Zones by manual definitions in `zoneDefinitions` setting"
)
stripper = lambda s: s.strip()
self.zones = zones.Zones()

# parse the special input string for zone definitions
for zoneString in cs["zoneDefinitions"]:
zoneName, zoneLocs = zoneString.split(":")
zoneLocs = zoneLocs.split(",")
zone = zones.Zone(zoneName.strip())
zone.addLocs(map(stripper, zoneLocs))
self.zones.addZone(zone)

if not len(self.zones):
runLog.debug("No manual zones defined in `zoneDefinitions` setting")

def _applyThermalExpansion(
self, assems: list, dbLoad: bool, referenceAssembly=None
):
Expand Down
26 changes: 26 additions & 0 deletions armi/reactor/tests/test_reactors.py
Original file line number Diff line number Diff line change
Expand Up @@ -931,6 +931,32 @@ def test_updateBlockBOLHeights_DBLoad(self):
for param in equalParameters:
self.assertAlmostEqual(oldBlockParameters[param][b], b.p[param])

def test_buildManualZones(self):
# define some manual zones in the settings
newSettings = {}
newSettings["zoneDefinitions"] = [
"ring-1: 001-001",
"ring-2: 002-001, 002-002",
"ring-3: 003-001, 003-002, 003-003",
]
cs = self.o.cs.modified(newSettings=newSettings)
self.r.core.buildManualZones(cs)

zonez = self.r.core.zones
self.assertEqual(len(list(zonez)), 3)
self.assertIn("002-001", zonez["ring-2"])
self.assertIn("003-002", zonez["ring-3"])

def test_buildManualZonesEmpty(self):
# ensure there are no zone definitions in the settings
newSettings = {}
newSettings["zoneDefinitions"] = []
cs = self.o.cs.modified(newSettings=newSettings)

# verify that buildZones behaves well when no zones are defined
self.r.core.buildManualZones(cs)
self.assertEqual(len(list(self.r.core.zones)), 0)


class CartesianReactorTests(ReactorTests):
def setUp(self):
Expand Down
Loading