High Temperature Reactor (HTR) Designs
This chapter collects all macros and functions used to model typical HTR fuel and core designs. Common to all components is the notion of a dispersed particle distribution, which plays the role of a fueled primitive (just like plates are for MTR, and pins for LWR).
Note
It is important to note that the entire dispersed distribution is usually considered a single primitive, and not the individual coated particles, or pebbles. This design principle is followed because the number of individual fueled components (on their own), are usually orders more than what is encountered in other designs.
Generating Dispersed Particle Distributions
A dispersed particle distribution is defined by a number of particle (usually spheres) centers, sizes and identifiers defining the particle type. The pre processor has a number of options for creating or importing distributed particle distributions. This covers both the generation of coated particles inside fuel compacts, and pebbles in the core. These are described in the following sections.
All tools are contained in the core.disperse module.
Note
All particle distribution routines make use of the discrete element package YADE. See Installing YADE for instructions on how to obtain the tool.
Importing from external sources
There are a number of external tools that can generate both random or regular particle distributions. One example is using the disperser routine in Serpent. Output from these tools can be placed in a simple text file which is imported using the following function:
- read_ascii_spheres(filename, u=units.cm, predicate=None, id_map=None)
Read dispersed spherical particle distribution from a text file. The file is assumed to have the following format:
Each particle entry is separated by a new line break.
Each line contains at least the 5 entries:
The \(x\), \(y\), \(z\) coordinates of the sphere center (floating point values)
The radius of the sphere (floating point value)
Particle identifier (integer or string).
- Parameters:
filename (filepath) – Full path to target file name.
u – The units particle center coordinates are given in.
predicate – Used to filter particles. Must be a function object accepting five arguments (the particle center \(x\), \(y\), \(z\), size and id), returning True if the particle should be included, or False otherwise.
id_map (dict) – Used to change string ids present in the file to integer ids.
Attention
If the ids in filename are not integers, the id_map parameter is required.
Regular lattice distributions
Currently, the core.disperse can produce
hexagonal close-packed distribution using
- hcp(radius, shape, spacing=0.0 * units.cm, tag=0)
Create a hexagonal close-packed (HCP) particle distribution of equal shaped spheres.
- Parameters:
radius (length) – Radius of the spheres that will be packed.
shape (region) – The boundary of the region to which the lattice should be clipped.
spacing (length) – The distance between neighboring spheres. If 0, spheres will touch.
tag – How ids should be assigned to generated spheres. If a fixed integer value, all particles will have the same id, otherwise this parameter must be a function accepting the particle position (\(x\), \(y\), \(z\)) coordinates as input, and returning an integer.
Attention
The region must be bounded!
For example, the following will create a close packing of spheres with radius 1 mm in a cylindrical region:
>>> from csg import * >>> from core import disperse >>> spheres = disperse.hzp(1 * units.mm, Cylinder(radius=1 * units.cm, lower_bound=-10 * units.cm, upper_bound=10 * units.cm))
Note
This will create a explicit distribution of spheres, contained within the bounding region. Instead of creating such an explicit list, which is not supported by all attached target codes, the
HexagonalClosePacked, option can be used. Since it models the distribution as standard hexagonal lattice, it is supported by more attached solvers.
Closed packed spheres are also modelled using the HexagonalClosePacked or FaceCenteredCubic objects.
Random dense packing
A random dense packing (with spheres potentially touching), is produced by trying to fit as many randomly positioned spheres as possible into a specified region.
- rdp(radius, shape, tag=0)
Create a Random Dense Packing of equal sized spheres, clipped to a specified region.
- Parameters:
radius (length) – Radius of the spheres that will be packed.
shape (region) – The boundary of the region to which the lattice should be clipped.
tag – How ids should be assigned to generated spheres. If a fixed integer value, all particles will have the same id, otherwise this parameter must be a function accepting the particle position (\(x\), \(y\), \(z\)) coordinates as input, and returning an integer.
Note
Currently, there is no way to specify the target packing fraction or number of particles. If this is required, rather use a different tool Serpent disperser routine.
Gravity deposition
These distributions are generated using an explicit simulation of dropping particles into a container, and modelling their interaction under the relevant physical forces. The discrete element solver YADE can be used to perform such simulations.
Examples of simulation scripts can be found in the relevant HTR benchmark sets.
Coated Particles
The concept of a generic coated particle is represented by a CoatedParticle instance, which creates an
arbitrary number of spherical shells, with the inner most sphere the kernel (or core) region. It is located in the
fuel_assembly module, and apart from the standard component parameters:
-
cp.kernel_contents
: material or bundle_tag
The contents of the innermost region.
Note
If the coated particle will form part of a dispersed distribution, the kernel contents can also be specified when initiating the material of that distribution. Thus, it is not necessary to specify it when defining the coated particle.
Once the inner kernel size is specified, additional shells are added using:
- cp.add_shell(radius, contents)
Add a new layer to the coated particle.
- Parameters:
Attention
The
kernel_radiusmust be set prior to making this call.
For example, the following will create a simple particle with a single coating:
>>> from core import *
>>> from material_library.structural import SiliconCarbide
>>> cp = fuel_assembly.CoatedParticle(name='CP')
>>> cp.kernel_radius = 0.1 * units.mm
>>> cp.add_shell(0.15 * units.mm, SiliconCarbide())
The fuel_assembly also contains a macro for creating typical TRISO
fuel particles:
- fuel_assembly.TRISO(fuel_radius=0.2125 * units.mm, buffer_radius=0.3125 * units.mm, inner_pyc=0.3475 * units.mm, sic_radius=0.3825 * units.mm, outer_pyc=0.4225 * units.mm, buffer=PorousGraphite(), pyc=PyroliticGraphite(), sic=SiliconCarbide(), **kwargs)
Standard TRI-structural ISO-tropic fuel particle design.
- Parameters:
fuel_radius (length) – Radius of the inner fuel kernel.
buffer_radius (length) – Radius of the buffer zone.
inner_pyc (length) – Radius of the inner pyrolitic carbon layer.
sic_radius (length) – Radius of the silicon carbide layer.
outer_pyc (length) – Radius of the outer pyrolitic carbon layer. This is also the total size of the particle.
buffer (material) – Material used in the buffer zone.
pyc (material) – Material used in the pyrolitic carbon layers.
sic (material) – Material used in the silicon carbide layer.
Pebble Bed Designs
Pebbles
The Pebble class in the fuel_assembly can be used to model typical pebble designs. Apart from the
standard component parameters, it is defined by setting the following:
-
pebble.matrix
: material
= Graphite Material used in the coated particle compact (fueled region), that is, the material filling the region outside the coated particles.
Note
The default value used for reflector_material and matrix, is pure graphite (at 1.74 g/cc)
without any impurities. Any reactivity estimation will be quite sensitive to the material used here, so be sure
to replace the defaults with the correct composition.
Once all of the parameters are set, the actual fueled region is defined using:
- pebble.pack(distribution, fuel_zone=None, coated_particle=TRISO(), fuel=None, packing_fraction=None, bundle=fuel)
Add the dispersed fuel particles to the fueled region of the pebble.
- Parameters:
distribution –
Source describing the particle distribution. Can be produced by any of the mechanisms described in Generating Dispersed Particle Distributions:
Distribution
Description
Text file in a format supported by
read_ascii_spheres()HexagonalClosePacked()
HCP sphere distribution, modelled as a lattice.
FaceCenteredCubic()
FCC sphere distribution, modelled as a lattice.
fuel_zone (region) – Sub region bounding the active fuel region. If not specified, the full inner spherical region (with radius
radius-reflector_thickness) will be used.coated_particle (component or dict) – The particle(s) that should be used in the dispersion. If a single value, this particle will be used in all locations. Alternatively, a dict object with keys the ids appearing in the distribution and values the particle types.
fuel – The material that should fill the kernel region of the coated_particle. This parameter is optional here, as it can also be specified when initiating the burnable material(s).
packing_fraction (float) – The packing fraction of a close packed sphere distribution. This is only used if distribution is
HexagonalClosePackedorFaceCenteredCubic.
Note
The pre generated distribution must be clipped to the fuel_zone. In particular, if using the default zone, the dispersed distribution should be generated in a sphere with radius
radius-reflector_thickness.Attention
This call will create all the cells defining the pebble structure, thus all parameters must be specified prior to this call.
Note
The reflector region is now defined as the region inside the outer sphere, but inside the fuel_region.
For example, the following will create a standard pebble using the shipped kernel distribution (with packing fraction):
Note
Dummy pebbles, or pebbles that do not contain any fuel compacts, should not be generated using the Pebble macro. Instead they are defined as generic assemblies. For example, the following will create a solid graphite pebble:
>>> from core import *
>>> from csg import *
>>> from material_library.reflectors import Graphite
>>> from material_library.gasses import Helium
>>> dp = assembly.Assembly(name='Dummy-pebble')
>>> dp.add_cell(primitives.Sphere(radius=3 * units.cm), material=Graphite())
>>> dp.add_cell(~primitives.Sphere(radius=3 * units.cm), material=Helium())
Pebble bed core
The core component is modelled using the PebbleBed class available in the reactor_core module. It is
used in the build script as follows:
from core import *
from csg import *
from core.reactor_core import PebbleBed
from material_library.gasses import Helium
def build():
bed = PebbleBed(name='MY_HTR-pebble-bed')
bed.coolant = Helium()
The PebbleBed object is a standard assembly, with all the general parameters, and
also uses the following parameters and methods:
-
pebble.coolant
: material
= Helium The coolant used in the core. This is the material that surrounds the pebbles.
Similar to the coated particle distribution in pebbles, the distribution of pebbles in the core is set using the pack method:
- pebble_bed.pack(distribution, pebbles, cavity=None)
Set the distribution of pebbles in the core.
- Parameters:
distribution – Source describing the particle distribution. Can be produced by any of the mechanisms described in Generating Dispersed Particle Distributions. If this is a filepath, the file must be in the format readable by
read_ascii_spheres().pebbles – The pebbles(s) that should be used in the dispersion. If a single value, this pebble will be used in all locations. Alternatively, if more than one pebble type is present, a dict object with keys the ids appearing in the distribution and values the particle types.
cavity – Region in which the distribution should be placed. This parameter is optional here, as it can be specified later using the
pebble_bed.add_fueled_cell()method.
Although any distribution can be used, ones generated using gravity sphere drop simulation are the most appropriate in this case.
Additional cells containing the pebble distribution are specified using
pebble_bed.add_fueled_cell().
Prismatic Designs
A typical prismatic element is modelled using the following steps:
Create one or more fuel compacts.
Stack the compacts or pellets to form one or more fuel (or absorber) pins.
Distribute the pins, and specify the coolant holes, for one or more elements.
Stack the elements to form a complete assembly.
Cylindrical compacts
The CylindricalCompact class in the fuel_assembly can be used to model typical cylindrical compact
(pellet) designs. Apart from the standard component parameters, it is defined by setting
the following:
-
pellet.matrix
: material
= Graphite Material used in the coated particle compact (fueled region), that is, the material filling the region outside the coated particles.
Note
The default value used for matrix, is pure graphite (at 1.74 g/cc)
without any impurities. Any reactivity estimation will be quite sensitive to the material used here, so be sure
to replace the defaults with the correct composition.
Once all of the parameters are set, the actual fueled region is defined using the same approach as pebble.pack().
Pellet stacks
A stack of cylindrical compacts are modelled using CompactPin. Compacts (pellets), as well as plugs or spacers,
are added using the following interface:
- compact_pin.add_layer(lower=None, upper=None, size=None, target=None)
Adds a layer to the compact stack.
- Parameters:
lower (length) – The axial lower bound of the layer. Only needed for the first layer, as subsequent layers will assume the previous layer’s upper bound.
upper (length) – Upper bound of the axial layer.
size (length) – Size of the layer. Can be specified instead of upper.
target (component) – Contents of the layer. Usually a cylindrical compact, plug or spacer element.
Note
Layers must be added from top to bottom.
If lower is specified, a check is performed to see that it is the same as upper of the previous layer (if present).
The following example will create a simple pin with 5 pellets, and a graphite plug on the top and bottom:
from core.fuel_assembly import CompactPin, CylindricalCompact
from core.disperse import HexagonalClosePacked
from core.assembly import Assembly
from csg.primitives import Cylinder
from material_library.reflectors import Graphite
from material_library.gasses import Helium
# create the plug
plug = Assembly(name='Fuel_pin_plug')
plug.add_cell(Cylinder(radius=hole, lower_bound=0 * units.cm, upper_bound=2 * units.cm),
material=Graphite())
plug.add_cell(~Cylinder(radius=hole, lower_bound=0 * units.cm, upper_bound=2 * units.cm),
material=Helium())
# create the compact
p = (total_height - 4 * units.cm)/5.0
compact = CylindricalCompact(name='Test')
compact.radius = 0.98 * hole
compact.height = p
compact.pack(HexagonalClosePacked(), packing_fraction=0.1)
pin = CompactPin(name='Fuel-Pin')
pin.add_layer(-0.5 * total_height, size=2.0 * units.cm, target=plug)
for i in range(5):
pin.add_layer(size=p, target=compact)
pin.add_layer(size=2.0 * units.cm, target=plug)
Prismatic element
A single prismatic block is modelled using PrismaticElement available in the :module:`fuel_assembly` module.
Apart from the standard component parameters, it is defined by setting
the following:
-
prismatic_element.size
: length
= 36 cm The inner diameter of the hexagonal boundary of the element.
- prismatic_element.orientation = X_AXIS
The orientation of the bounding hexagonal shape.
-
prismatic_element.grid_cell
: region
Set a custom bounding region of the underlying hexagonal pin layout. This should be used if the element has cavities, such as a central hole, or a control rod position.
If not specified, it defaults to the radial bound (a hexagon with diameter
prismatic_element.sizeand orientationprismatic_element.orientation), extruded from 0 toprismatic_element.height.
-
prismatic_element.cell_pitch
: length
= 1.8796 cm The pitch of the underlying hexagonal pin lattice.
Note
Equal to the inner diameter of the hexagonal elements in the tesselation.
- prismatic_element.lattice_orientation = Y_AXIS
The orientation of the underlying hexagonal grid.
-
prismatic_element.fuel_hole_diameter
: length
= 1.27 cm Diameter of the hole containing fuel. Can be used in
prismatic_element.pin_cell().
-
prismatic_element.large_coolant_hole_diameter
: length
= 1.588 cm The diameter of a large coolant hole. Used by
prismatic_element.large_coolant_cell().
-
prismatic_element.small_coolant_hole_diameter
: length
= 1.27 cm The diameter of a small coolant hole. Used by
prismatic_element.small_coolant_cell().
-
prismatic_element.coolant
: material
= Helium The material that fills coolant holes, as well as the space outside the
prismatic_element.grid_cell.
-
prismatic_element.moderator
: material
= Graphite The material that fills the interior of
prismatic_element.grid_cell, that is, around all pins, and the filler region around the hexagonal lattice.
- prismatic_element.layout
Set the layout of pins within the hexagonal lattice. The pins are usually those produced by the functions,
prismatic_element.pin_cell(),prismatic_element.solid_cell(),prismatic_element.large_coolant_cell(), orprismatic_element.small_coolant_cell(). Layout is specified using a hexagonal grid, in either axial coordinates or using a grid. An empty grid is created usingprismatic_element.new_grid().For example:
elem = PrismaticElement(name='test-prismatic-block') elem.height = 70 * units.cm elem.size = 5 * elem.cell_pitch # set the layout grid = elem.new_grid() grid.orientation = 'y' fc = elem.pin_cell(elem.fuel_hole_diameter, pin) hl = elem.large_coolant_cell() # fill the grid grid[0] = -2, [fc, hl, fc, hl, fc] grid[1] = -2, [fc, hl, hl, fc] grid[2] = -2, [fc, fc, fc] grid.fold() # symmetric distribution elem.layout = grid elem.show()
Attention
Any new pin type created (using
prismatic_element.pin_cell()), and added to the layout, will be activated separately during burnup calculations. See depletion_mesh_prismatic.
- prismatic_element.pin_cell(diameter, pin, name=None)
Create a hexagonal pin cell.
- Parameters:
diameter (length) – The hole within the graphite body that will contain the pin.
pin – Pin that will be placed in the hole. Either a compact stack, or cylindrical stack.
name (string) – Name of the pin cell. If not specified, one will be created based on the name of pin.
Returns an element that can be used when specifying
prismatic_element.layout.
- prismatic_element.solid_cell(name=None)
Creates and returns a simple solid pin cell, that can be used when specifying
prismatic_element.layout.
- prismatic_element.large_coolant_cell()
Returns a hexagonal pin, with a
prismatic_element.large_coolant_hole_diametersize hole filled withprismatic_element.coolant.
- prismatic_element.small_coolant_cell()
Returns a hexagonal pin, with a
prismatic_element.small_coolant_hole_diametersize hole filled withprismatic_element.coolant.
Prismatic assembly
Models a complete prismatic
Customizing the depletion mesh for prismatic elements and assemblies
Custom Designs
Installing YADE
If you are working on Linux, follow the YADE installation instructions.
Note
It is our intention to also distribute YADE as a conda package once the compilation issues have been sorted out.
Examples
This section gives complete build script examples for some of the assembly types described in this chapter.
Pebble bed core build script
"""
Core Barrel and Central Reflector
=================================
"""
from core import *
from csg import *
from material_library.reflectors import Graphite
from core.reactor_core import PebbleBed
def build(*args, **kwargs):
bed = PebbleBed(name='PBMR-400-core')
# ------------------------------------------------------------------------------------------------------------------
# assign pebbles
axial = region_macros.axial_strip(0.0 * units.m, 11 * units.m)
inner_column = primitives.Cylinder(diameter=1.0 * units.m)
core_bound = primitives.Cylinder(diameter=3.7 * units.m)
# ------------------------------------------------------------------------------------------------------------------
# central reflector column (currently without SAS)
A = Graphite(mass_density=1.80)
A.name = 'Graphite (Grade A)'
axial = region_macros.axial_strip(-250 * units.cm, 11 * units.m + 150 * units.cm)
bed.add_cell(axial & inner_column & ~ primitives.Cylinder(diameter=0.2 * units.m),
material=A,
part='CentralReflector')
bed.add_cell(axial & primitives.Cylinder(diameter=0.2 * units.m),
material=bed.coolant)
return bed