Skip to content

Commit

Permalink
Merge branch 'main' into tmp_space
Browse files Browse the repository at this point in the history
  • Loading branch information
john-science committed Mar 6, 2025
2 parents fa80df4 + 66cac55 commit 627c773
Show file tree
Hide file tree
Showing 42 changed files with 2,448 additions and 393 deletions.
22 changes: 19 additions & 3 deletions .github/workflows/docs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,30 @@ jobs:
run: sudo apt-get -y install pandoc
- name: Setup Graphviz
uses: ts-graphviz/[email protected]
- name: Make HTML Docs
- name: Make html/pdf Docs
continue-on-error: true
run: |
sudo apt-get install texlive-xetex
sudo apt-get install texlive-latex-base
sudo apt-get install texlive-fonts-recommended
sudo apt-get install texlive-latex-extra
sudo apt-get install texlive-full
pip install -e .[memprof,mpi,test,docs]
python -c "from armi.bookkeeping.report.reportingUtils import getSystemInfo;print(getSystemInfo())" > system_info.log
date > python_details.log
python --version >> python_details.log
pip freeze >> python_details.log
pytest --junit-xml=test_results.xml -v -n 4 armi > pytest_verbose.log
cd doc
git submodule init
git submodule update
echo "make html:"
make html
make simplepdf
echo "make latex:"
make latex
cd _build/latex/
echo "latexmk -pdf -f -interaction=nonstopmode ARMI.tex:"
latexmk -pdf -f -interaction=nonstopmode ARMI.tex
- name: Deploy
if: github.ref == 'refs/heads/main'
uses: JamesIves/[email protected]
Expand All @@ -58,5 +74,5 @@ jobs:
uses: actions/upload-artifact@v4
with:
name: pdf-docs
path: doc/_build/simplepdf/ARMI.pdf
path: doc/_build/latex/ARMI.pdf
retention-days: 5
9 changes: 8 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
*.pyx

# No build artifacts
*.aux
*.dll
*.fdb_latexmk
*.fls
*.lib
.apidocs/
/bin
armi/tests/tutorials/case-suite
bin/*
Expand All @@ -17,6 +19,7 @@ coverage.xml
coverage_results.*
dist-*/
dist/
doc/.apidocs
doc/_build
doc/anl-afci-177
doc/gallery
Expand All @@ -26,6 +29,7 @@ doc/tutorials/case-suite
doc/user/tutorials
htmlcov/
monkeytype.*
test_results.*
wheelhouse

# No workspace crumbs
Expand Down Expand Up @@ -60,8 +64,11 @@ armi-venv/*
dump-temp-*
dump-tests*
phabricator-lint.txt
pytest_verbose.log
pytestdebug.log
python_details.log
reportsOutputFiles/
system_info.log
tags
temp-*
venv*/
Expand Down
127 changes: 118 additions & 9 deletions armi/reactor/components/component.py
Original file line number Diff line number Diff line change
Expand Up @@ -382,11 +382,20 @@ def applyMaterialMassFracsToNumberDensities(self):
# `density` is 3D density
# call getProperty to cache and improve speed
density = self.material.getProperty("pseudoDensity", Tc=self.temperatureInC)

self.p.numberDensities = densityTools.getNDensFromMasses(
density, self.material.massFrac
)

# Sometimes material thermal expansion depends on its parent's composition (e.g. Pu frac) so
# setting number densities can sometimes change thermal expansion behavior. Call again so
# the material has access to its parent's comp when providing the reference initial density.
densityBasedOnParentComposition = self.material.getProperty(
"pseudoDensity", Tc=self.temperatureInC
)
self.p.numberDensities = densityTools.getNDensFromMasses(
densityBasedOnParentComposition, self.material.massFrac
)

# material needs to be expanded from the material's cold temp to hot,
# not components cold temp, so we don't use mat.linearExpansionFactor or
# component.getThermalExpansionFactor.
Expand Down Expand Up @@ -719,12 +728,7 @@ def setNumberDensity(self, nucName, val):
val : float
Number density to set in atoms/bn-cm (heterogeneous)
"""
self.p.numberDensities[nucName] = val
self.p.assigned = parameters.SINCE_ANYTHING
# necessary for syncMpiState
parameters.ALL_DEFINITIONS[
"numberDensities"
].assigned = parameters.SINCE_ANYTHING
self.updateNumberDensities({nucName: val})

def setNumberDensities(self, numberDensities):
"""
Expand All @@ -747,23 +751,93 @@ def setNumberDensities(self, numberDensities):
We don't just call setNumberDensity for each nuclide because we don't want to call
``getVolumeFractions`` for each nuclide (it's inefficient).
"""
self.p.numberDensities = numberDensities
self.updateNumberDensities(numberDensities, wipe=True)

def updateNumberDensities(self, numberDensities):
def updateNumberDensities(self, numberDensities, wipe=False):
"""
Set one or more multiple number densities. Leaves unlisted number densities alone.
Parameters
----------
numberDensities : dict
nucName: ndens pairs.
wipe : bool, optional
Controls whether the old number densities are wiped. Any nuclide densities not
provided in numberDensities will be effectively set to 0.0.
Notes
-----
Sometimes volume/dimensions change due to number density change when the material thermal
expansion depends on the component's composition (e.g. its plutonium fraction). In this
case, changing the density will implicitly change the area/volume. Since it is difficult to
predict the new dimensions, and perturbation/depletion calculations almost exclusively
assume constant volume, the densities sent are automatically adjusted to conserve mass with
the original dimensions. That is, the component's densities are not exactly as passed, but
whatever they would need to be to preserve volume integrated number densities (moles) from
the pre-perturbed component's volume/dimensions.
This has no effect if the material thermal expansion has no dependence on component
composition. If this is not desired, `self.p.numberDensities` can be set directly.
"""
# prepare to change the densities with knowledge that dims could change due to
# material thermal expansion dependence on composition
if len(self.p.numberDensities) > 0:
dLLprev = (
self.material.linearExpansionPercent(Tc=self.temperatureInC) / 100.0
)
materialExpansion = True
else:
dLLprev = 0.0
materialExpansion = False

try:
vol = self.getVolume()
except (AttributeError, TypeError):
# either no parent to get height or parent's height is None
# which would be AttributeError and TypeError respectively, but other errors could be possible
vol = None
area = self.getArea()

# change the densities
if wipe:
self.p.numberDensities = {} # clear things not passed
self.p.numberDensities.update(numberDensities)

# check if thermal expansion changed
dLLnew = self.material.linearExpansionPercent(Tc=self.temperatureInC) / 100.0
if dLLprev != dLLnew and materialExpansion:
# the thermal expansion changed so the volume change is happening at same time as
# density change was requested. Attempt to make mass consistent with old dims (since the
# density change was for the old volume and otherwise mass wouldn't be conserved).

self.clearLinkedCache() # enable recalculation of volume, otherwise it uses cached
if vol is not None:
factor = vol / self.getVolume()
else:
factor = area / self.getArea()
self.changeNDensByFactor(factor)

# since we're updating the object the param points to but not the param itself, we have to inform
# the param system to flag it as modified so it properly syncs during ``syncMpiState``.
self.p.assigned = parameters.SINCE_ANYTHING
self.p.paramDefs["numberDensities"].assigned = parameters.SINCE_ANYTHING

def changeNDensByFactor(self, factor):
"""Change the number density of all nuclides within the object by a multiplicative factor."""
newDensities = {
nuc: dens * factor for nuc, dens in self.p.numberDensities.items()
}
self.p.numberDensities = newDensities
self._changeOtherDensParamsByFactor(factor)

def _changeOtherDensParamsByFactor(self, factor):
"""Change the number density of all nuclides within the object by a multiplicative factor."""
if self.p.detailedNDens is not None:
self.p.detailedNDens *= factor
# Update pinNDens
if self.p.pinNDens is not None:
self.p.pinNDens *= factor

def getEnrichment(self):
"""Get the mass enrichment of this component, as defined by the material."""
return self.getMassEnrichment()
Expand Down Expand Up @@ -1221,6 +1295,41 @@ def adjustMassEnrichment(self, massFraction):
)
self.setMassFracs(adjustedMassFracs)

def getMgFlux(self, adjoint=False, average=False, volume=None, gamma=False):
"""
Return the multigroup neutron flux in [n/cm^2/s].
The first entry is the first energy group (fastest neutrons). Each additional
group is the next energy group, as set in the ISOTXS library.
Parameters
----------
adjoint : bool, optional
Return adjoint flux instead of real
average : bool, optional
If True, will return average flux between latest and previous. Doesn't work
for pin detailed.
volume: float, optional
The volume-integrated flux is divided by volume before
being returned. The user may specify a volume here, or the function
will obtain the block volume directly.
gamma : bool, optional
Whether to return the neutron flux or the gamma flux.
Returns
-------
flux : np.ndarray
multigroup neutron flux in [n/cm^2/s]
"""
if average:
raise NotImplementedError(
"Component has no method for producing average MG flux -- try"
"using blocks"
)

volume = volume or self.getVolume() / self.parent.getSymmetryFactor()
return self.getIntegratedMgFlux(adjoint=adjoint, gamma=gamma) / volume

def getIntegratedMgFlux(self, adjoint=False, gamma=False):
"""
Return the multigroup neutron tracklength in [n-cm/s].
Expand Down
4 changes: 2 additions & 2 deletions armi/reactor/composites.py
Original file line number Diff line number Diff line change
Expand Up @@ -2079,8 +2079,8 @@ def getMgFlux(self, adjoint=False, average=False, volume=None, gamma=False):
for pin detailed yet
volume: float, optional
If average=True, the volume-integrated flux is divided by volume before
being returned. The user may specify a volume here, or the function will
The volume-integrated flux is divided by volume before being
returned. The user may specify a volume here, or the function will
obtain the block volume directly.
gamma : bool, optional
Expand Down
49 changes: 37 additions & 12 deletions armi/reactor/tests/test_blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -1262,29 +1262,54 @@ def test_adjustDensity(self):
def test_getMgFlux(self, mock_sf):
# calculate Mg Flux with a Symmetry Factor of 3
mock_sf.return_value = 3
self.block.p.mgFlux = np.array([1, 1, 1, 1, 1])
self.block.p.mgFluxGamma = np.array([2, 2, 2, 2])
neutronFlux = 1.0
gammaFlux = 2.0
self.block.p.mgFlux = np.full(5, neutronFlux)
self.block.p.mgFluxGamma = np.full(4, gammaFlux)
fuel = self.block.getComponent(Flags.FUEL)
blockVol = self.block.getVolume()
fuelVol = fuel.getVolume()
# compute volume fraction of component; need symmetry factor
volFrac = fuelVol / blockVol / self.block.getSymmetryFactor()
neutronFlux = fuel.getIntegratedMgFlux()
gammaFlux = fuel.getIntegratedMgFlux(gamma=True)
np.testing.assert_almost_equal(neutronFlux, np.ones(5) * volFrac)
np.testing.assert_almost_equal(gammaFlux, np.ones(4) * volFrac * 2.0)
neutronFluxInt = fuel.getIntegratedMgFlux()
gammaFluxInt = fuel.getIntegratedMgFlux(gamma=True)
# getIntegratedMgFlux should be scaled by the component volume fraction
np.testing.assert_almost_equal(
neutronFluxInt, np.full(5, neutronFlux * volFrac)
)
np.testing.assert_almost_equal(gammaFluxInt, np.full(4, gammaFlux * volFrac))

# getMgFlux should return regular, non-integrated flux
neutronMgFlux = fuel.getMgFlux()
gammaMgFlux = fuel.getMgFlux(gamma=True)
np.testing.assert_almost_equal(
neutronMgFlux, np.full(5, neutronFlux / blockVol)
)
np.testing.assert_almost_equal(gammaMgFlux, np.full(4, gammaFlux / blockVol))

# calculate Mg Flux with a Symmetry Factor of 1
mock_sf.return_value = 1
self.block.p.mgFlux = np.array([1, 1, 1, 1, 1])
self.block.p.mgFluxGamma = np.array([2, 2, 2, 2])
self.block.p.mgFlux = np.full(5, neutronFlux)
self.block.p.mgFluxGamma = np.full(4, gammaFlux)
fuel = self.block.getComponent(Flags.FUEL)
blockVol = self.block.getVolume()
fuelVol = fuel.getVolume()
volFrac = fuelVol / blockVol / self.block.getSymmetryFactor()
neutronFlux = fuel.getIntegratedMgFlux()
gammaFlux = fuel.getIntegratedMgFlux(gamma=True)
np.testing.assert_almost_equal(neutronFlux, np.ones(5) * volFrac)
np.testing.assert_almost_equal(gammaFlux, np.ones(4) * volFrac * 2.0)
neutronFluxInt = fuel.getIntegratedMgFlux()
gammaFluxInt = fuel.getIntegratedMgFlux(gamma=True)
# getIntegratedMgFlux should be scaled by the component volume fraction
np.testing.assert_almost_equal(
neutronFluxInt, np.full(5, neutronFlux * volFrac)
)
np.testing.assert_almost_equal(gammaFluxInt, np.full(4, gammaFlux * volFrac))

# getMgFlux should return regular, non-integrated flux
neutronMgFlux = fuel.getMgFlux()
gammaMgFlux = fuel.getMgFlux(gamma=True)
np.testing.assert_almost_equal(
neutronMgFlux, np.full(5, neutronFlux / blockVol)
)
np.testing.assert_almost_equal(gammaMgFlux, np.full(4, gammaFlux / blockVol))

@patch.object(blocks.HexBlock, "getSymmetryFactor")
def test_completeInitialLoading(self, mock_sf):
Expand Down
Loading

0 comments on commit 627c773

Please sign in to comment.