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

Allow components to have children in support of particle fuel modeling #702

Open
wants to merge 7 commits into
base: main
Choose a base branch
from

Conversation

ntouran
Copy link
Member

@ntouran ntouran commented Jun 7, 2022

Description

This changes the ARMI data model to allow components to have children. It also adds a input
capability to allow users to input particle type fuel (e.g. TRISO).

This is a proof-of-concept at the moment, intended for getting feedback from @drewj-usnctech
wrt #477 .

Mostly, take a look at the input file and let us know if it satisfies the need. We will need
to add more testing and documentation to verify that everything is working
and explained.


Checklist

  • The code is understandable and maintainable to people beyond the author.
  • Tests have been added/updated to verify that the new or changed code works.
  • There is no commented out code in this PR.
  • The commit message follows good practices.
  • All docstrings are still up-to-date with these changes.

If user exposed functionality was added/changed:

  • Documentation added/updated in the doc folder.
  • New or updated dependencies have been added to setup.py.

@ntouran ntouran requested a review from john-science June 7, 2022 22:55
@ntouran ntouran linked an issue Jun 7, 2022 that may be closed by this pull request
@keckler
Copy link
Member

keckler commented Jun 7, 2022

@ntouran

I don't have a good enough grasp of these changes to know the answer, but does this change account for this comment you had made about the material modifications?

#517 (review)

And a further question -- Do we want to allow different material modifications at this additional layer as well? Sounds messy...

Copy link
Contributor

@drewj-usnctech drewj-usnctech left a comment

Choose a reason for hiding this comment

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

@ntouran thanks for putting this together. The input file changes look good.

Maybe a hyperbolic situation, but if I had a Component that had two blended component groups, how could I separate out the children? Or maybe a better way to put it: how can plugins know that all (or some) of the children on a component belong together in the same particle fuel component group?

I don't have great answers for these but hopefully the questions are helpful.

Comment on lines +204 to +210
children = {c.name: c for c in constructedObject.getChildren()}
for child in children.values():
child.resolveLinkedDims(children)
Copy link
Contributor

Choose a reason for hiding this comment

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

This enables individual layers to have linked dimensions, right? Nice!

Copy link
Member Author

Choose a reason for hiding this comment

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

yeah it should. though we should make more unit tests proving it.

Comment on lines +1001 to +1047
else:
if self.hasFlags(typeSpec, exact):
yield self
Copy link
Contributor

Choose a reason for hiding this comment

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

This removes the ability for component.iterComponents to yield itself. I think

if self.hasFlags(typeSpec, exact):
    yield self
# If no children, this loop isn't executed
for child in self._children:
    for c in child.iterComponents(typeSpec, exact):
        yield c

might pick up the arbitrary depth and also return the child component based on the child's flags not the flags of the parent

Copy link
Member Author

Choose a reason for hiding this comment

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

very nice, that does seem better. I'll try your suggestion.

Copy link
Member Author

@ntouran ntouran Jun 14, 2022

Choose a reason for hiding this comment

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

ok so I tried With that suggested iterComponents change, and my test_trisoCase.py unit test fails with max recursion errors.

I tried this kind of thing for a while and just couldn't quite get it working. I'm sure it can be done... it's just hard.

    [c.getNumberDensity(nuc) for nuc in nucNames]
  File "/home/nick/repos/armi/armi/reactor/composites.py", line 1256, in <listcomp>
    [c.getNumberDensity(nuc) for nuc in nucNames]
  File "/home/nick/repos/armi/armi/reactor/composites.py", line 1233, in getNumberDensity
    return self.getNuclideNumberDensities([nucName])[0]
  File "/home/nick/repos/armi/armi/reactor/components/component.py", line 605, in getNuclideNumberDensities
    composites.Composite.getNuclideNumberDensities(self, nucNames),
  File "/home/nick/repos/armi/armi/reactor/composites.py", line 1255, in getNuclideNumberDensities
    [
  File "/home/nick/repos/armi/armi/reactor/composites.py", line 1256, in <listcomp>
    [c.getNumberDensity(nuc) for nuc in nucNames]
  File "/home/nick/repos/armi/armi/reactor/composites.py", line 1256, in <listcomp>
    [c.getNumberDensity(nuc) for nuc in nucNames]
  File "/home/nick/repos/armi/armi/reactor/composites.py", line 1233, in getNumberDensity
    return self.getNuclideNumberDensities([nucName])[0]
  File "/home/nick/repos/armi/armi/reactor/components/component.py", line 605, in getNuclideNumberDensities
    composites.Composite.getNuclideNumberDensities(self, nucNames),
  File "/home/nick/repos/armi/armi/reactor/composites.py", line 1244, in getNuclideNumberDensities
    [
  File "/home/nick/repos/armi/armi/reactor/composites.py", line 1245, in <listcomp>
    c.getVolume() / (c.parent.getSymmetryFactor() if c.parent else 1.0)
  File "/home/nick/repos/armi/armi/reactor/components/component.py", line 430, in getVolume
    if self.p.volume is None:
  File "/home/nick/repos/armi/armi/reactor/parameters/parameterDefinitions.py", line 293, in __get__
    return self._getter(obj)
RecursionError: maximum recursion depth exceeded

Copy link
Contributor

Choose a reason for hiding this comment

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

Ooooo that's rough.

Looks like the problem could stem from Composite.getNumberDensity relying on Composite.getNuclideNumberDensities?

Comment on lines 82 to 93
component groups:
triso particle:
kernel:
mult: 1000
buffer:
mult: 1000
ipyc:
mult: 1000
sic:
mult: 1000
opyc:
mult: 1000
Copy link
Contributor

Choose a reason for hiding this comment

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

Should the multiplicity here be 1 for each component? In that there is one kernel in a triso particle but maybe thousands of triso particle components in the triso compact component with the blend.

Copy link
Member Author

Choose a reason for hiding this comment

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

well based on the names, what you say seems more right. But the way it's being interpreted has the number of compacts in a block set to 20 and the number of particles per compact set to 1000. I guess I should try to put more realistic numbers in here. Anyway I wonder how I could adjust this to make it more intuitive.

Copy link
Contributor

Choose a reason for hiding this comment

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

Having mult attached to the compact matrix component feel intuitive. Then you would also be able to get the multiplicity from a spatial grid.

Is the sticking point that we can't define the mult of the triso particle in the compact with a blend fraction until we know the volume of the matrix?

Comment on lines +601 to +647
if self._children:
childDens = dict(
zip(
nucNames,
composites.Composite.getNuclideNumberDensities(self, nucNames),
)
)
# get volume of children by using the composite method which loops over all children
childVol = composites.Composite.getVolume(self)
# get self volume by calling the shape-specific volume method of the background shape
totalVol = self.getVolume()
childFrac = childVol / totalVol
selfFrac = 1.0 - childFrac
return [
self.p.numberDensities.get(nucName, 0.0) * selfFrac
+ childDens.get(nucName, 0.0) * childFrac
for nucName in nucNames
]
else:
return [self.p.numberDensities.get(nucName, 0.0) for nucName in nucNames]
Copy link
Contributor

Choose a reason for hiding this comment

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

How would a plugin be able to get the number densities of just the matrix component with this change?

On one hand this is consistent with how the design of the composite tree: Composite.getNuclideNumberDensities gives the densities for all children on a node. So this would match well with Block.getNuclideNumberDensities for some nodal homogenization methods. But modeling this Component exactly in a monte carlo code would be problematic w/o the number densities of just the Component and not its children

Copy link
Member Author

Choose a reason for hiding this comment

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

Great point. You would have to access the matrix's .p.numberDensities parameter directly in order to differentiate between it and its children with this change as written. Willing to consider more elegant options.

Copy link
Contributor

Choose a reason for hiding this comment

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

@ntouran is

matrix's .p.numberDensities

still the recommended approach here? I've been testing this out over the last few days and had a hard time remembering this thread.

Would something like Component.getNuclideNumberDensities(children=False) be acceptable? I tend to be hesitant pulling data from these parameter containers as the feel (maybe incorrectly) more private than a public method

Comment on lines -585 to +616
return list(self.p.numberDensities.keys())
nucs = set(self.p.numberDensities.keys())
if self._children:
childNuclides = set(composites.Composite.getNuclides(self))
nucs |= childNuclides
return nucs
Copy link
Contributor

Choose a reason for hiding this comment

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

Same concern on getting nuclides just in the component vs. component and all the children

Copy link
Member Author

Choose a reason for hiding this comment

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

yeah that's a good concern. I'd hate to make you basically re-write these methods for accessing the details of the matrix itself. We could add in a flag or something as a method argument? getNuclides(includeChildren=False) or something?

Copy link
Contributor

Choose a reason for hiding this comment

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

includeChildren=False feels like a good solution. Then whatever application or plugin is doing the writing could check if a Component has children prior to writing the material definitions

@drewj-usnctech
Copy link
Contributor

To the question by @keckler (and I might be off base w/ the intent or background) but I'd still hope to have the ability to use the material modifications section for the particle fuel. A great example would be the enrichment of the kernels.

But I don't have a lot of background on the component-wise material modifications in #517 (which looks awesome btw)

@john-science john-science added the enhancement New feature or request label Jun 8, 2022
@john-science
Copy link
Member

Bump.

Needs black formatting. (I'd do it for your, but it's in your repo fork.)

@drewj-usnctech
Copy link
Contributor

@ntouran I feel decently good about the internals. My biggest request comes from not requiring the user to input number of particles per compact in the blueprints file. There are some occasions where you might know the exact number of particles in a compact, especially as a design matures. But for more preliminary studies, or where you have compacts of multiple sizes but the same volume fraction, requiring the user to know the number of particles at the start can be burdensome

@ntouran
Copy link
Member Author

ntouran commented Jul 5, 2022

@drewj-usnctech ok I have made some small changes that now auto-derive the mult completely based on the blendFrac. So you put in dummy mults of 1.0 and the system updates the kernel mults during init to match the expected blend frac.

One weird thing here is that the blendfrac is applied to the hot dimensions at init, (but will stay constant after init even if temperatures change)

@drewj-usnctech
Copy link
Contributor

@ntouran thanks for the update. I've been out for a bit and am finally catching up.

One weird thing here is that the blendfrac is applied to the hot dimensions at init

This was my suspicion as well. We've been having some internal discussion about how thermal expansion impacts our particle fuel modeling and guess this might be a problem. I imagine this component expansion is done very early in the blueprint loading and there isn't a way to delay it? Or wholesale disable it?

@jakehader
Copy link
Member

@ntouran thanks for the update. I've been out for a bit and am finally catching up.

One weird thing here is that the blendfrac is applied to the hot dimensions at init

This was my suspicion as well. We've been having some internal discussion about how thermal expansion impacts our particle fuel modeling and guess this might be a problem. I imagine this component expansion is done very early in the blueprint loading and there isn't a way to delay it? Or wholesale disable it?

You can disable thermal expansion by setting Thot to be the same as Tinput in the blueprints!

@drewj-usnctech
Copy link
Contributor

@jakehader

You can disable thermal expansion by setting Thot to be the same as Tinput in the blueprints!

🤦 yeah, I forgot about that when writing that comment. Didn't mean to bring a larger discussion (#766) into this feature.

But there is still hiccup if you decide to enable thermal expansion and model particle fuel here. As @ntouran mentioned

One weird thing here is that the blendfrac is applied to the hot dimensions at init

This means the number of particles packed into the compact would reflect the post-expansion dimension rather than the as built dimension. There would be an opaque change in fuel loading due to this thermal expansion.

FWIW, as the main advocate for this feature, I'm okay saying this pre-expansion should not be a blocker for this feature yet. We're still trying to sort out thermal expansion and the various properties and knock-on effects internally, and are probably a ways from fully diving in. Hence my posting of questions like #766 and #694

@john-science
Copy link
Member

@ntouran This probably just needs to be merged with main.

@drewj-usnctech This PR has been open too long, how do you feel about it? I'd like to move this forward.

@drewj-usnctech
Copy link
Contributor

@john-science This has been up a while and we've started some internal discussions about adjacent modifications to the composite tree. I don't have anything I can provide at this point, but I'll hopefully be reaching out to the dev team in a week or so about how a larger enhancement. This could capture both the particle fuel modeling and provide some additional modeling benefits. We think. It's still in a conceptual phase and we'd like to give it some more time before sending it off

@drewj-usnctech
Copy link
Contributor

(fully aware that I am somewhat responsible for the delay)

@drewj-usnctech
Copy link
Contributor

This is on our task list for things to do this week and report back if we find any thing that seriously blocks / breaks our internal plugins.

One thing that I'm thinking about, while I'm thinking about composite tree stuff (#503 // #960), does this break the notion that a Component is a leaf in the composite tree now? It was kind of the implied consequence of Component.iter_children needing to be updated to yield itself but also any potential particle fuel children. Could this be the motivating case to introduce a non-block group of components?

Comment on lines +1041 to +1047
if self._children:
for child in self._children:
if self.hasFlags(typeSpec, exact):
yield child
else:
if self.hasFlags(typeSpec, exact):
yield self
Copy link
Contributor

Choose a reason for hiding this comment

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

@ntouran we've found that the mass of the matrix material is not accounted for when doing getMass calls. I think due to this section here

We modified the triso mass test blueprint file such that each element only appears in one Component. Then, block.getMass("C12") (in theory) picks up mass from the triso compact component and should be non-zero. Unfortunately this is not the case 😬

# A full blueprint file that contains assems made with ComponentGroups
blocks:
    fuel2: &block_fuel2
        triso compact: 
            shape: Circle
            blends: 
              - triso particle
            blendFracs: 
              - 0.70
            material: Graphite
            Tinput: 600.0
            Thot: 600.0
            mult: 1
            id: 0.0
            od: 1.2446
        duct: 
            shape: Hexagon
            material: Cu
            Tinput: 600.0
            Thot: 600.0
            ip: 9.0
            mult: 1.0
            op: 10.0
        coolant: 
            shape: DerivedShape
            material: NaCl
            Tinput: 25.0
            Thot: 600.0

components:
    # triso dims from Table 4 in TRISO benchmark
    kernel:
        shape: Sphere
        material: Lead
        Tinput: 600.0
        Thot: 600.0
        id: 0.0
        mult: 1.0
        od: 0.010625
    buffer:
        shape: Sphere
        material: Lead
        Tinput: 600.0
        Thot: 600.0
        id: kernel.od
        mult: 1.0
        od: 0.015625
    ipyc:
        shape: Sphere
        material: Lead
        Tinput: 600.0
        Thot: 600.0
        id: buffer.od
        mult: 1.0
        od: 0.017375
    sic:
        shape: Sphere
        material: Lead
        Tinput: 600.0
        Thot: 600.0
        id: ipyc.od
        mult: 1.0
        od: 0.019125
    opyc:
        shape: Sphere
        material: Lead
        Tinput: 600.0
        Thot: 600.0
        id: sic.od
        mult: 1.0
        od: 0.021125

component groups:
    triso particle:
      kernel:
        mult: 1
      buffer:
        mult: 1
      ipyc:
        mult: 1
      sic:
        mult: 1
      opyc:
        mult: 1


assemblies:
    fuel c: &assembly_c
        specifier: OC
        blocks: [*block_fuel2]
        height: [1.0]
        axial mesh points: [1]
        xs types: [A]
systems:
    core:
        grid name: core
        origin:
            x: 0.0
            y: 0.0
            z: 0.0
grids:
    core:
        geom: hex
        symmetry: full
        lattice map: |
          OC


nuclide flags:
    B: &nuc_flags {burn: false, xs: true}
    C: *nuc_flags
    PB: *nuc_flags
    CU63: *nuc_flags
    CU65: *nuc_flags
    CL35: *nuc_flags
    CL37: *nuc_flags
    CL37: *nuc_flags
    NA23: *nuc_flags

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks @drewj-usnctech I will bring in your modified blueprints file and update the unit test so it fails as you've found and then try to fix.

This updates the iteration and number density
methods to expect children and blend them with
the component itself as a background composition.

This is being developed specifically to help support
TRISO particle-type fuel that has important fine
structure within the fuel pins.
Initial TRISO unit tests now pass.
This allows users to not need to know anything about mults for
e.g. particle fuel and should get the appropriate mults derived
during init.
@drewj-usnctech
Copy link
Contributor

This might be related to the mass check on the matrix, but we also found that the volume of the matrix component did not change when we changed the blend frac.

This feels like a weird composite tree dilemma. If I have a Component that represents the matrix and the contained children particle fuel, what should Component.getVolume() return? If we're thinking of this Component as a block-like thing, then it should always be the total volume of the contained children (so matrix + particle fuel). But, if we're thinking about the matrix Component as a thing that contains the matrix material, then Component.getVolume() should produce just the volume of the compact occupied by the matrix (total volume - particle fuel)

@ntouran
Copy link
Member Author

ntouran commented Nov 15, 2022

Yeah so I think I need to re-implement this to follow a more robust version of the Composite design pattern. This will take a bit more work, sorry, but should be more robust.

I have been adding to the unit tests and splitting them out to cover a few expected behaviors. We probably will want to add even more. They currently fail, as they should.

Current plan to get them running is to:

  • Go back to having Components be the Leaf of the composite pattern and not have children (sorry I we have the names goofed compared to the offical pattern)
  • Have Composites only be made of children. So composite pattern participants will either be completely made of children or be a leaf. Having things that have their own component and then also have child components is not a good idea, turns out.
  • Re-implement this change to make triso compact Composite have children be:
    • background Component (matrix) (with mult reduced to make room for the particles based on the blend frac)
    • particle Composite with children:
      • Various spherical particle components with mults set based on blend frac

I think the net result will be better support of arbitrary nesting.

@drewj-usnctech
Copy link
Contributor

@ntouran sounds like a good plan. Let me know how we can help, either in developing or providing tests / requirements.

One question

with mult reduced to make room for the particles based on the blend frac

Are you thinking about having Matrix.mult be less than one to account for the space taken up by particles? Would ARMI allow a non-integer mult?

@keckler
Copy link
Member

keckler commented Nov 16, 2022

Are you thinking about having Matrix.mult be less than one to account for the space taken up by particles? Would ARMI allow a non-integer mult?

ARMI handles non-integer mults. See #542

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Make standard way to represent TRISO fuel
5 participants