Skip to content

Commit c223c22

Browse files
onufermgjarrett
authored andcommitted
udpate density to account for material change
1 parent bea1e25 commit c223c22

File tree

4 files changed

+98
-19
lines changed

4 files changed

+98
-19
lines changed

armi/reactor/blocks.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2441,10 +2441,18 @@ def getWettedPerimeter(self):
24412441
)
24422442

24432443
# flags pertaining to circular pin components where the exterior of the circle is wetted
2444-
wettedPinComponentFlags = (Flags.CLAD, Flags.WIRE, Flags.CLAD | Flags.DEPLETABLE, Flags.WIRE | Flags.DEPLETABLE)
2444+
wettedPinComponentFlags = (
2445+
Flags.CLAD,
2446+
Flags.WIRE,
2447+
Flags.CLAD | Flags.DEPLETABLE,
2448+
Flags.WIRE | Flags.DEPLETABLE,
2449+
)
24452450

24462451
# flags pertaining to components where both the interior and exterior are wetted
2447-
wettedHollowComponentFlags = (Flags.DUCT | Flags.INNER, Flags.DUCT | Flags.INNER | Flags.DEPLETABLE,)
2452+
wettedHollowComponentFlags = (
2453+
Flags.DUCT | Flags.INNER,
2454+
Flags.DUCT | Flags.INNER | Flags.DEPLETABLE,
2455+
)
24482456

24492457
# obtain all wetted components based on type
24502458
wettedHollowHexagonComponents = []

armi/reactor/components/component.py

Lines changed: 83 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -375,11 +375,20 @@ def applyMaterialMassFracsToNumberDensities(self):
375375
# `density` is 3D density
376376
# call getProperty to cache and improve speed
377377
density = self.material.getProperty("pseudoDensity", Tc=self.temperatureInC)
378-
379378
self.p.numberDensities = densityTools.getNDensFromMasses(
380379
density, self.material.massFrac
381380
)
382381

382+
# Sometimes the material thermal expansion depends on its parents composition (eg Pu frac) so
383+
# setting number densities can sometimes change thermal expansion behavior.
384+
# so call again so that the material has access to its parents comp when providing the reference initial density.
385+
densityBasedOnParentComposition = self.material.getProperty(
386+
"pseudoDensity", Tc=self.temperatureInC
387+
)
388+
self.p.numberDensities = densityTools.getNDensFromMasses(
389+
densityBasedOnParentComposition, self.material.massFrac
390+
)
391+
383392
# material needs to be expanded from the material's cold temp to hot,
384393
# not components cold temp, so we don't use mat.linearExpansionFactor or
385394
# component.getThermalExpansionFactor.
@@ -721,12 +730,7 @@ def setNumberDensity(self, nucName, val):
721730
val : float
722731
Number density to set in atoms/bn-cm (heterogeneous)
723732
"""
724-
self.p.numberDensities[nucName] = val
725-
self.p.assigned = parameters.SINCE_ANYTHING
726-
# necessary for syncMpiState
727-
parameters.ALL_DEFINITIONS[
728-
"numberDensities"
729-
].assigned = parameters.SINCE_ANYTHING
733+
self.updateNumberDensities({nucName: val})
730734

731735
def setNumberDensities(self, numberDensities):
732736
"""
@@ -747,12 +751,19 @@ def setNumberDensities(self, numberDensities):
747751
748752
Notes
749753
-----
750-
We don't just call setNumberDensity for each nuclide because we don't want to call ``getVolumeFractions``
751-
for each nuclide (it's inefficient).
754+
Note that sometimes volume/dimensions can change due to the number density change when the material thermal
755+
expansion depends on the component's composition (eg its plutonium fraction). In this case, changing the
756+
density will implicitly change the area/volume. Since it its very difficult to predict the new dims ahead of time,
757+
and perturbation/depletion calculations are almost exclusively done assuming constant volume,
758+
the densities sent are automatically perturbed to conserve mass with the original dimensions.
759+
That is, the components densities are not exactly as passed, but whatever they would need to be to preserve volume
760+
integrated number densities (moles) from the pre-perturbed components volume/dims.
761+
Note this has no effect if the material thermal expansion has no dependence on component composition fracs.
762+
If this is not desired, `self.p.numberDensities` can be set directly.
752763
"""
753-
self.p.numberDensities = numberDensities
764+
self.updateNumberDensities(numberDensities, wipe=True)
754765

755-
def updateNumberDensities(self, numberDensities):
766+
def updateNumberDensities(self, numberDensities, wipe=False):
756767
"""
757768
Set one or more multiple number densities. Leaves unlisted number densities alone.
758769
@@ -761,12 +772,61 @@ def updateNumberDensities(self, numberDensities):
761772
numberDensities : dict
762773
nucName: ndens pairs.
763774
775+
Notes
776+
-----
777+
Note that sometimes volume/dimensions can change due to the number density change when the material thermal
778+
expansion depends on the component's composition (eg its plutonium fraction). In this case, changing the
779+
density will implicitly change the area/volume. Since it its very difficult to predict the new dims ahead of time,
780+
and perturbation/depletion calculations are almost exclusively done assuming constant volume,
781+
the densities sent are automatically perturbed to conserve mass with the original dimensions.
782+
That is, the components densities are not exactly as passed, but whatever they would need to be to preserve volume
783+
integrated number densities (moles) from the pre-perturbed components volume/dims.
784+
Note this has no effect if the material thermal expansion has no dependence on component composition fracs.
785+
If this is not desired, `self.p.numberDensities` can be set directly.
764786
"""
787+
# prepare to change the densities with knowledge that dims could change due to
788+
# material thermal expansion dependence on composition
789+
if len(self.p.numberDensities) > 0:
790+
dLLprev = (
791+
self.material.linearExpansionPercent(Tc=self.temperatureInC) / 100.0
792+
)
793+
else:
794+
dLLprev = 0.0
795+
try:
796+
vol = self.getVolume()
797+
except:
798+
# either no parent to get height or parent's height is None
799+
# which would be AttributeError and TypeError respectively, but other errors could be possible
800+
vol = None
801+
area = self.getArea()
802+
803+
# change the densities
804+
if wipe:
805+
self.p.numberDensities = {}
765806
self.p.numberDensities.update(numberDensities)
766-
# since we're updating the object the param points to but not the param itself, we have to inform
767-
# the param system to flag it as modified so it properly syncs during ``syncMpiState``.
768-
self.p.assigned = parameters.SINCE_ANYTHING
769-
self.p.paramDefs["numberDensities"].assigned = parameters.SINCE_ANYTHING
807+
808+
# check if thermal expansion changed
809+
dLLnew = self.material.linearExpansionPercent(Tc=self.temperatureInC) / 100.0
810+
if dLLprev != dLLnew and dLLprev != 0.0:
811+
# the thermal expansion changed so the volume change is happening at same time as
812+
# density change was requested. Attempt to make mass consistent with old dims
813+
# (since the density change was for the old volume and otherwise mass wouldn't be conserved)
814+
815+
# enable recalculation of volume, otherwise it uses stored.
816+
self.clearLinkedCache()
817+
if vol is not None:
818+
factor = vol / self.getVolume()
819+
else:
820+
factor = area / self.getArea()
821+
self.changeNDensByFactor(factor)
822+
823+
def changeNDensByFactor(self, factor):
824+
"""Change the number density of all nuclides within the object by a multiplicative factor."""
825+
newDensities = {
826+
nuc: dens * factor for nuc, dens in self.p.numberDensities.items()
827+
}
828+
self.p.numberDensities = newDensities
829+
self._changeOtherDensParamsByFactor(factor)
770830

771831
def getEnrichment(self):
772832
"""Get the mass enrichment of this component, as defined by the material."""
@@ -1247,7 +1307,9 @@ def getIntegratedMgFlux(self, adjoint=False, gamma=False):
12471307
if not self.parent:
12481308
return np.zeros(1)
12491309

1250-
volumeFraction = (self.getVolume() / self.parent.getSymmetryFactor() ) / self.parent.getVolume()
1310+
volumeFraction = (
1311+
self.getVolume() / self.parent.getSymmetryFactor()
1312+
) / self.parent.getVolume()
12511313
return volumeFraction * self.parent.getIntegratedMgFlux(adjoint, gamma)
12521314

12531315
# pin-level flux is available. Note that it is NOT integrated on the param level.
@@ -1262,7 +1324,11 @@ def getIntegratedMgFlux(self, adjoint=False, gamma=False):
12621324
else:
12631325
pinFluxes = self.parent.p.pinMgFluxes
12641326

1265-
return pinFluxes[self.p.pinNum - 1] * self.getVolume() / self.parent.getSymmetryFactor()
1327+
return (
1328+
pinFluxes[self.p.pinNum - 1]
1329+
* self.getVolume()
1330+
/ self.parent.getSymmetryFactor()
1331+
)
12661332

12671333
def getPinMgFluxes(
12681334
self, adjoint: Optional[bool] = False, gamma: Optional[bool] = False

armi/reactor/composites.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1575,6 +1575,10 @@ def changeNDensByFactor(self, factor):
15751575
nuc: val * factor for nuc, val in self.getNumberDensities().items()
15761576
}
15771577
self.setNumberDensities(densitiesScaled)
1578+
self._changeOtherDensParamsByFactor(factor)
1579+
1580+
def _changeOtherDensParamsByFactor(self, factor):
1581+
"""Change the other density storage params by factor."""
15781582
# Update detailedNDens
15791583
if self.p.detailedNDens is not None:
15801584
self.p.detailedNDens *= factor

armi/reactor/converters/geometryConverters.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1568,6 +1568,7 @@ def scaleParamsRelatedToSymmetry(core, paramsToScaleSubset=None):
15681568
for b, bSymmetric in zip(a, aSymmetric):
15691569
_scaleParamsInBlock(b, bSymmetric, completeListOfParamsToScale)
15701570

1571+
15711572
def _generateListOfParamsToScale(core, paramsToScaleSubset):
15721573
fluxParamsToScale = (
15731574
core.getFirstBlock()

0 commit comments

Comments
 (0)