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

Draft: Particle fuel modeling #477

Closed

Conversation

drewj-usnctech
Copy link
Contributor

Provides a yaml-interface for

  • modeling particle fuel with arbitrary layers
  • multiple particle fuel types in the reactor
  • assigning particle fuel as children to some parent component

We have been decently successful with these changes internally in that
downstream plugins can see Component.particleFuel and perform actions
based on their content. What follows is an overview of the interface,
implementation, and a discussion of where to go next.

Related to #228 and
would support modeling the MHTGR-350 benchmark
#224

This patch is submitted to kick-start a discussion on better ways to add
this feature into ARMI, leveraging the domain knowledge of the ARMI
developers and the "particle fuel aware" plugins internally developed at
USNC Tech.

Input interface

particle fuel:
    demo:
        kernel:
            material: UO2
            id: 0
            od: 0.6
            Tinput: 900
            Thot: 900
            flags: DEPLETABLE
        buffer:
            material: SiC
            id: 0.6
            od: 0.61
            Tinput: 900
            Thot: 900
matrix:
    shape: Circle
    material: Graphite
    Tinput: 1200
    Thot: 1200
    id: 0.0
    od: 2.2
    latticeIDs: [F]
    flags: DEPLETABLE
    particleFuelSpec: demo
    particleFuelPackingFraction: 0.4

With this interface it's possible to define several specifications
in the model and assign them to different cylindrical components.

Implementation

The particle fuel is stored as a child of the parent component, such
that <Circle: Matrix>.children is used to dynamically find the particle
fuel spec. We can't store the specification as an attribute because we
have to support potentially dynamic addition and removal of children to this
component. Something like self.particleFuel = spec that makes spec a
child would also have to understand what happens if we remove the spec
from the parent, e.g., self.remove(self.particleFuel). What is then the
outcome of self.particleFuel unless we always check the children of the
matrix?

By making the particle fuel spec a Composite and placing it in the
.children of the parent, the spec is able to be written to and read
from the database.

Adds Component.setParticleMultiplicity. The method is called during
block construction when the block's height is available to the matrix
component (particle's parent). The multiplicity is determined from the
matrix volume and target packing fraction.

Note: the particle mult is, by design, for a single component.

Unresolved issues

  • Volume of the parent matrix is not reduced by the volume occupied
    by the particles.
  • No support for homogenizing regions that contain particle fuel
  • Various homogenization properties don't account for particle fuel
    (e.g., Core.getHM*)
  • Particle fuel is not included in some text-reporting, leading to
    statements like
[info] Nuclide categorization for cross section temperature assignments:
       ------------------  ------------------------------------------------------
       Nuclide Category    Nuclides
       ------------------  ------------------------------------------------------
       Fuel

and

[warn] The system has no heavy metal and therefore is not a nuclear reactor. Please make sure that this is intended and not a input error.
  • No provided routines for packing the particles into their parent. This
    could be facilitated with a dedicated packing plugin and an unstructured
    3-D SpatialGrid class. It's burdensome to expect the user to define
    the exact location of every particle every time. But, if some Plugin
    performs the packing and creates this spatial grid, the multiplicity is
    tackled, and you potentially avoid adding or removing particles as their
    parent expands or contracts.
  • Unsure if material modifications make their way down to the materials
    in the particle fuel spec. The implementation suggests it as the matMods
    argument is passed into the particle fuel YAML object constructor. But
    we have yet to stress test that

Next steps

This patch is submitted because we continue to find places where this
approach does not play well with the rest of the ARMI stack. While Blocks
that contain particle fuel are correctly able to compute their heavy
metal mass by iterating over their children, which in turn finds the
particle fuel. However, higher-level actions like Core.getHM* do not
go down to the sub-block level, instead asking to homogenize Blocks.
The homogenization methods are not yet aware of the particle fuel because
they rely on Component.getNuclides which reports the nuclides for it's
Material, and does not include the children.

This approach is sensible because if I'm writing a neutronic input file
and I can exactly model the matrix and it's particle fuel, I would expect
matrix.getNuclides to return the nuclides for just the matrix. Then,
being informed of the particle fuel, I can write those materials and geometry
uniquely. However, codes that cannot handle particle fuel and/or work
with homogenized regions (e.g., nodal diffusion) would need this
homogenized data. Allowing Component.getNuclides to return the nuclides
on the child particle fuel would support this case, but not the previous case.

My speculation is that the optimal strategy lies somewhere in making the
matrix object not a Component but a Block and having the ARMI Composite
tree accept blocks that potentially contain blocks. I think this is valid
using the API but the user interface would need some work. Having the matrix
be a block would provide a better interface for homogenization and exact
representation of the particle fuel. I think...

Other related changes

Added Sphere.getBoundingCircleOuterDiameter so that the particle fuel
composites can be properly added to the database. They need to be
"sortable" other wise Database3._createLayout breaks trying to sort
components. This enables the particle fuel spec to be added to and read
from the HDF data file

The material constructor is a more public function as it is needed both
in creating Components but also in creating the particle fuel spec.

Added MATRIX flag

Signed-off-by: Andrew Johnson [email protected]

Provides a yaml-interface for
- modeling particle fuel with arbitrary layers
- multiple particle fuel types in the reactor
- assigning particle fuel as children to some parent component

We have been decently successful with these changes internally in that
downstream plugins can see `Component.particleFuel` and perform actions
based on their content. What follows is an overview of the interface,
implementation, and a discussion of where to go next.

Related to terrapower#228 and
would support modeling the MHTGR-350 benchmark
terrapower#224

This patch is submitted to kick-start a discussion on better ways to add
this feature into ARMI, leveraging the domain knowledge of the ARMI
developers and the "particle fuel aware" plugins internally developed at
USNC Tech.

Input interface
---------------

```yaml
particle fuel:
    demo:
        kernel:
            material: UO2
            id: 0
            od: 0.6
            Tinput: 900
            Thot: 900
            flags: DEPLETABLE
        buffer:
            material: SiC
            id: 0.6
            od: 0.61
            Tinput: 900
            Thot: 900
```

```yaml
matrix:
    shape: Circle
    material: Graphite
    Tinput: 1200
    Thot: 1200
    id: 0.0
    od: 2.2
    latticeIDs: [F]
    flags: DEPLETABLE
    particleFuelSpec: demo
    particleFuelPackingFraction: 0.4

```

With this interface it's possible to define several specifications
in the model and assign them to different cylindrical components.

Implementation
--------------

The particle fuel is stored as a child of the parent component, such
that `<Circle: Matrix>.children` is used to dynamically find the particle
fuel spec. We can't store the specification as an attribute because we
have to support potentially dynamic addition and removal of children to this
component. Something like `self.particleFuel = spec` that makes spec a
child would also have to understand what happens if we remove the spec
from the parent, e.g., `self.remove(self.particleFuel)`. What is then the
outcome of `self.particleFuel` unless we always check the children of the
matrix?

By making the particle fuel spec a `Composite` and placing it in the
`.children` of the parent, the spec is able to be written to and read
from the database.

Adds `Component.setParticleMultiplicity`. The method is called during
block construction when the block's height is available to the matrix
component (particle's parent). The multiplicity is determined from the
matrix volume and target packing fraction.

Note: the particle mult is, by design, for a single component.

Unresolved issues
-----------------

- Volume of the parent matrix is not reduced by the volume occupied
by the particles.
- No support for homogenizing regions that contain particle fuel
- Various homogenization properties don't account for particle fuel
  (e.g., `Core.getHM*`)
- Particle fuel is not included in some text-reporting, leading to
statements like

```
[info] Nuclide categorization for cross section temperature assignments:
       ------------------  ------------------------------------------------------
       Nuclide Category    Nuclides
       ------------------  ------------------------------------------------------
       Fuel
```
and
```
[warn] The system has no heavy metal and therefore is not a nuclear reactor. Please make sure that this is intended and not a input error.
```

- No provided routines for packing the particles into their parent. This
  could be facilitated with a dedicated packing plugin and an unstructured
  3-D `SpatialGrid` class. It's burdensome to expect the user to define
  the exact location of _every_ particle every time. But, if some `Plugin`
  performs the packing and creates this spatial grid, the multiplicity is
  tackled, and you potentially avoid adding or removing particles as their
  parent expands or contracts.
- Unsure if material modifications make their way down to the materials
  in the particle fuel spec. The implementation suggests it as the `matMods`
  argument is passed into the particle fuel YAML object constructor. But
  we have yet to stress test that

Next steps
----------

This patch is submitted because we continue to find places where this
approach does not play well with the rest of the ARMI stack. While Blocks
that contain particle fuel are correctly able to compute their heavy
metal mass by iterating over their children, which in turn finds the
particle fuel. However, higher-level actions like `Core.getHM*` do not
go down to the sub-block level, instead asking to homogenize Blocks.
The homogenization methods are not yet aware of the particle fuel because
they rely on `Component.getNuclides` which reports the nuclides for it's
`Material`, and does not include the children.

This approach is sensible because if I'm writing a neutronic input file
and I can exactly model the matrix and it's particle fuel, I would expect
`matrix.getNuclides` to return the nuclides for _just the matrix_. Then,
being informed of the particle fuel, I can write those materials and geometry
uniquely. However, codes that cannot handle particle fuel and/or work
with homogenized regions (e.g., nodal diffusion) would need this
homogenized data. Allowing `Component.getNuclides` to return the nuclides
on the child particle fuel would support this case, but not the previous case.

My speculation is that the optimal strategy lies somewhere in making the
matrix object not a `Component` but a `Block` and having the ARMI Composite
tree accept blocks that potentially contain blocks. I think this is valid
using the API but the user interface would need some work. Having the matrix
be a block would provide a better interface for homogenization and exact
representation of the particle fuel. I think...

Other related changes
---------------------

Added `Sphere.getBoundingCircleOuterDiameter` so that the particle fuel
composites can be properly added to the database. They need to be
"sortable" other wise `Database3._createLayout` breaks trying to sort
components. This enables the particle fuel spec to be added to and read
from the HDF data file

The material constructor is a more public function as it is needed both
in creating `Components` but also in creating the particle fuel spec.

Added `MATRIX` flag

Signed-off-by: Andrew Johnson <[email protected]>
@CLAassistant
Copy link

CLAassistant commented Nov 17, 2021

CLA assistant check
All committers have signed the CLA.

@drewj-usnctech drewj-usnctech marked this pull request as draft November 17, 2021 19:07
@john-science
Copy link
Member

@drewj-usnctech Thanks so much!

Sorry, we had some staff turn-over recently, so between that and Thanksgiving I haven't been at my usual response rate. This is really interesting though.

Thanks for all the work you put into this. Nick and I were looking at it over lunch. Hopefully we'll be able to think about this "next steps" and "unresolved issues" a bit more.

@ntouran ntouran added the enhancement New feature or request label Dec 1, 2021
Copy link
Member

@ntouran ntouran left a comment

Choose a reason for hiding this comment

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

Thank you so much for this amazing contribution! We're really excited with this level of engagement. Being able to represent TRISO fuel is a dream of ours and here you are making it happen. So awesome.

There are a few things in here that I commented on that I think warrant a bit of discussion and exploration. We have a few related discussions that I've just posted today publicly that I think tie into this really well and can be combined.

I'm adding these comments here for now to help facilitate our discussion. we should set up a call again soon to discuss the particular of how to move forward.

@@ -56,6 +57,8 @@
Operator_MasterMachine = "Master Machine:"
Operator_Date = "Date and Time:"
Operator_CaseDescription = "Case Description:"
# Convert a value in centimeters to micrometers
CM_TO_MICRO_METER = 10000
Copy link
Member

Choose a reason for hiding this comment

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

that might be best put in utils/units.py

Comment on lines +1020 to +1021
if not r.blueprints.particleFuelDesigns:
return
Copy link
Member

Choose a reason for hiding this comment

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

we might want to consider making a more abstract r.containsParticleFuel() method that would be less coupled from the specific implementation of how particle fuel is defined in input.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If we add it to the Composite class, that would allow clients to check if a specific block or assembly contains particle fuel as well, right? Some possible pathways

  • Component.containsParticleFuel always returns False as they would be leafs on the composite tree and thus don't contain anything
  • Reactor and assembly iterate over children, calling child.containsParticleFuel or any(c.containsParticleFuel() for c in self)
  • Then most of the work happens on the Block as a thing that contains components or potentially other blocks?

Comment on lines +189 to +191
particleFuelDesigns = yamlize.Attribute(
key="particle fuel", type=ParticleFuelKeyedList, default=None
)
Copy link
Member

Choose a reason for hiding this comment

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

I'm hoping we can come up with a slightly more generic name for this that would still meet the need that you have.

We have been having some related internal discussions that I have now put in #503 and #504 that I think are very related to this.

In this case, one could imagine having a section at this level called something like Mixtures.

@@ -168,6 +168,14 @@ def construct(
b.autoCreateSpatialGrids()
except (ValueError, NotImplementedError) as e:
runLog.warning(str(e), single=True)
# check if particle fuel exists and set particle mult
# Note: these changes occur here during block construction instead of during
# component contruction because component parent parameters (i.e., height)
Copy link
Member

Choose a reason for hiding this comment

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

excellent explanatory comment, thanks!

Comment on lines +163 to +164
particleFuelSpec = yamlize.Attribute(type=str, default=None)
particleFuelPackingFraction = yamlize.Attribute(type=float, default=None)
Copy link
Member

Choose a reason for hiding this comment

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

I'm trying to think of a way we could make these attributes on a subclass that is specialized for particle fuel. Still thinking about it though.

Comment on lines +373 to +383
class _ParticleFuelLayer(yamlize.Object):
"""Component-like specification for a single layer in particle fuel"""

name = yamlize.Attribute(key="name", type=str)
material = yamlize.Attribute(type=str)
innerDiam = yamlize.Attribute(key="id", type=float, default=0)
od = yamlize.Attribute(key="od", type=float)
Tinput = yamlize.Attribute(type=float)
Thot = yamlize.Attribute(type=float)
# Need this to pick up flags like depletable
flags = yamlize.Attribute(type=str, default=None)
Copy link
Member

Choose a reason for hiding this comment

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

this is so tantalizingly close to a sphere that I really wonder if we could use it, probably as a subclass (ParticleFuelSphere) that has the extra code you have below.

Advantages might include needing less code to set it up in general (since shared w/ component and it's blueprints), and the fact that you could either use blueprints or not more directly (e.g. for programmatically building models with fewer blueprints)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm definitely worth investigating. Since there isn't really a SphereBlueprint, maybe just use normal component blueprints but enforce shape=sphere? Or when constructing the "parent" particle fuel, make spheres no matter what, rather than pulling the class from shape?

Comment on lines +433 to +443
class ParticleFuelSpec(yamlize.KeyedList):
"""Specification for a single particle fuel type"""

item_type = _ParticleFuelLayer
key_attr = _ParticleFuelLayer.name
name = yamlize.Attribute(type=str)

def construct(self, blueprint, matMods):
"""Produce a particle fuel instance that can be attached to a component"""
bounds = set()

Copy link
Member

Choose a reason for hiding this comment

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

As mentioned above my dream here is that we can make a slightly more generic thing (mixture?) that would give you the capability needed while also potentially being applicable to highly related needs (e.g. the kinds of models mentioned in #503).

We should discuss further.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Excellent discussion post! I'll contribute some thoughts / musings

Comment on lines +496 to +505

particle fuel:
<specifier>:
<layer>: # not strictly in increasing order
material: <string>
id: <float>
od: <float>
Tinput: <float>
Thot: <float>
flags: <optional list of strings>
Copy link
Member

Choose a reason for hiding this comment

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

fantastic docstring here. very clear what you're doing.

Comment on lines +322 to +329
FULL_BP_PARTICLES = """
blocks:
block: &block
flags: fuel test
duct:
shape: Hexagon
material: Graphite
Tinput: 600
Copy link
Member

Choose a reason for hiding this comment

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

glorious unit tests. THANKS!

Comment on lines +22 to +24
layers : tuple of :class:`~armi.reactor.components.Sphere`
Each layer of the specification in order of increasing outer
diameter
Copy link
Member

Choose a reason for hiding this comment

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

tantalizingly similar to children on a Composite (as mentioned in #503)

Comment on lines +511 to +527

particle fuel:
TRISO:
kernel:
material: UO2
id: 0.6
od: 0.61
Tinput: 900
Thot: 900
flags: DEPLETABLE
buffer:
material: SiC
id: 0.6
od: 0.62
Tinput: 900
Thot: 900
...
Copy link
Member

Choose a reason for hiding this comment

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

Just a high-level question here. What if we allowed "free" component definitions (e.g. not in blocks), along the lines of:

components:
      kernel:
          material: UO2
          id: 0.6
          od: 0.61
          Tinput: 900
          Thot: 900
          flags: DEPLETABLE
      buffer:
          material: SiC
          id: 0.6
          od: 0.62
          Tinput: 900
          Thot: 900
      matrix:
          material: Graphite
          Tinput: 900
          Thot: 900

(ignoring the shape/dimension issue for the moment)

and then upgraded the blocks input to allow mixtures, along the lines of:

        <block>:
            fuel:
                name:  fuelmatrix
                shape: Mixture
                mix: 
                  - comp: matrix
                    frac: 0.6
                  - comp: triso
                    frac: 0.4

And then have the input processing code take that and build a block that has mixes of those. Would that potentially be workable here? I'm thinking yes as long as you could mix matrix with TRISO, and let TRISO itself also be a composite (e.g. your ParticleFuel object below)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Having free components (#505) is an awesome move.

One potential issue with the mixture snippet above would be the triso component would need to be something more like a block as it would contain all the child layers. Or including multiple elements in the mix section

mix:
  - comp: matrix
     frac: 0.6
  - comp: kernel
  - comp: buffer

Providing the fraction of kernel and buffer is not difficult, just using 40% times the volume percent of each layer in a sphere. But that's some additional steps required if whipping up an input by hand

@drewj-usnctech
Copy link
Contributor Author

Thank you for the fantastic comments. I'm going to direct most of the higher level comments to #503 and maybe sometime next week we could arrange a longer conversation

@drewj-usnctech
Copy link
Contributor Author

(comments originally for the discussion got too focused on this implementation so moving them here)

I think the larger goal of this #503 (blocks containing composites not just components) would help greatly here. You could then model the particle fuel compact as a Block containing

  • matrix cylinder to give fixed spatial boundaries (id > 0)
  • matrix derived shape to fill in the space inside the bounding cylinder
  • individual components making up the spherical layers

Then the parent block(for something like MHTGR-350 // #224) could have children like

  • bounding hexagonal duct
  • derived shape for the graphite block
  • circles for the coolant holes
  • sub-block representing the compact

The YAML builders could be updated to expect either a shape argument to build a component or another key (sub-block?) to refer to another block that is already defined in the blueprint, e.g., compact.

And then, because the packing fraction is so specific to this application, it might need it's own section just to say "that block that uses this specific sub-block, use this packing / volume fraction." Other plugin changes would likely be needed in order to convert the packing fraction to spatial locations. But if there's some 3-D grid structure that acts like armi.reactor.grids.Grid, then you could get multiplicity and packed locations inside the compact from that.

@jakehader
Copy link
Member

@ntouran - want to make sure that this is still in progress as it's been open for some time now. I see that you have a separate PR open, but are there any other changes here that are needed once that PR lands?

@drewj-usnctech
Copy link
Contributor Author

@jakehader I think this is largely going to be resolved through #702

@drewj-usnctech
Copy link
Contributor Author

Closing in favor of #702

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.

5 participants