Combine cuts to create a 3D reactor model

The final step in a cOMPoSe input module is to stack cuts so that they form a complete three dimensional model. This involves two parts. The first is to specify the nodal structure of all loadable assemblies. These are frequently configuration independent, and can be set up in the loadable assembly’s individual modules. However, since each core configuration yields a single homogenized library, the main module must import all the loadable assembly mixtures. This is covered in Defining loadable assemblies. The second part defines the rest of nodalized model by specifying all remaining static positions. This is covered in `Building core configuration`_.

Data for the final nodal configuration is set using a new parameter pack, which is created using the create_nodal_configuration method of the parent parameter set. The method accepts the following arguments:

  • name: Descriptive name of the configuration. Used to name the generated LINX library file.

  • tag: Very short (two characters or less) tag used to identify the nodal model. By default, the tag parameter of the heterogeneous configuration will be used. In this case, the nodal model will be automatically associated with that configuration. If this parameter was not set during model creation, or if you choose not to directly couple the nodal model with the heterogeneous model, you can customize the tag in this call.

Example:

# To use the model tag set in the heterogeneous model
nodal_model = parameters.create_nodal_configuration(name='MY_REACTOR_configuration_1')

# Or use a custom tag
nodal_model = parameters.create_nodal_configuration(name='MY_REACTOR_configuration_1',
                                                    tag='C1')

Note

Since the tag will be used to automatically name position dependent base types, the restricting to two characters for the tag and homogenization_grid labels stem from the 6 character naming restriction in MGRAC.

The object returned by this call has the following parameters:

axial_mesh

: list [length]

Axial mesh of the three dimensional model used by the nodal solver for flux calculations. The list passed to this parameter contains the size of each axial mesh, so that the total number of axial meshes is equal to the length of the list, and the total height of the nodal model is equal to the sum of the elements in the list.

The finer the meshing, the more axial detail will be available, but the calculational time will also increase.

Note

This parameter is independent of the material meshes defined later. However, it is recommended to the set this mesh so that each exposure material mesh for loadable assemblies is only in one axial mesh segment.

assembly_library

: core.assembly.AssemblyLibrary

!

The heterogeneous assembly library. This is needed so that the system can store the defined nodal configurations in the library for easy access and distribution. It is usually just the assembly container imported from the model package:

from ..model import assemblies

nodal_model.assembly_library = assemblies
save_assemblies

: bool

= ``True``

Flag indicating if loadable assembly nodal definitions should be saved to the archives. This flag can be set to False if the loadable definitions have already been saved in another cOMPoSe module, and only the mixtures are required.

active_height

: length

= Derived

Total height of fissionable materials. This parameter is usually derived correctly from the heterogenuous model, and and should only be set manually if the nodal model active height deviates (for some reason) from the underlying model.

active_bottom

: length

= Derived

Start of the active region relative to the model axial bottom, which is at 0. If not specified, this parameter is deduced by taking the minimum fueled mixture position over all loadable assemblies.

Attention

Unlike the heterogeneous model, whose axial coordinate system is frequently defined by the core center line, the nodal model always has the bottom of the entire model as the axial zero position. This inconsistency was introduced to more closely align cOMPoSe generated models with legacy MGRAC models.

bank_map

: labeledgrid [placeholder | string]

Define the nodal model’s control element banks. Must be a map with the same shape and labels as homogenization_grid. This parameter only needs to be set if the nodal model has a different bank structure than the underlying heterogeneous model, or if the overlay mesh intersected bank positions.

Defining loadable assemblies

A normal loadable assembly, which fills an entire core pitch channel, is registered using the add_assembly method, which has the following arguments:

  • base_name: Short (6 characters or less) name used to identify the base nodal configuration for the assembly. This parameter is currently required, as there is no consistent way to deduce a short name from the more descriptive one specified during assembly creation. Moreover, multiple nodal models might be associated with a given heterogeneous assembly, and each must have a unique base name.

  • lib_assembly: Heterogeneous assembly this loadable is associated with. This parameter is required.

  • seating: Axial seating of this model relative to the bottom of the nodal model, which is the axial zero position. It defaults to 0, that is, the bottom of the assembly mixture mesh aligns with the bottom of the nodal model.

  • type: Assembly type tag. Defaults to the lib_assembly type.

The following shows a typical example:

fa = assemblies.MY_REACTOR_assembly_type_1()

hf = nodal_model.add_assembly(base_name='TYPE_1',
                              lib_assembly=fa)

Meshed loadable assemblies, that is, assemblies for which a number of segments in homogenization_grid must be combined in order to fill a full core pitch, are added using the add_meshed_assembly method. It accepts all the above arguments, as well as the following:

  • pitches: List of sub-segment sizes.

Attention

If your model is an extension of an OSCAR-3 or OSCAR-4 model, use the same base names for loadable assemblies that are already in use. This will ensure compatibility with existing history files.

Both calls return a data object, with the following additional parameters:

material_mesh

: list

Axial stack of homogenized mixtures which defines the assembly’s material mesh. This parameter is a list of tuple’s, with 2 or 3 values each:

  1. The first value is the length of the segment.

  2. The second value is a reference to the mixture that should fill this segment. Mixture references are obtained using the [] method of a cut reference, or the value returned by the import_library method.

  3. An optional third value, which defines the mixture type. Currently, you only need to specify this value if the mixture has a non-zero fission cross section. In this case, the value should be an instance of the material type tag core.material.fuel.

The order of the material mesh is from bottom to top, that is, the first mixture is at the axial top of the homogenized assembly, and the last mixture is at the axial bottom. For example, the following will create a material mesh of total length 72 cm, with three different mixtures:

h = parameters.generator.mode.active_height()
upper = parameters.add_axial_reflector_cut('TOP',
                                            rng=(0.5 * h, 0.5 * h + 6 * units.cm),
                                            description='Baffle region above active fuel')

act, data = parameters.add_lattice_cut('ACT',
                                       rng=(-0.5 * h, 0.5 * h),
                                       description='Active region')

# Set additional data
# ...

lower = parameters.add_axial_reflector_cut('BOT',
                                            rng=(-0.5 * h - 6 * units.cm, -0.5 * h),
                                            description='Baffle region below active fuel')

hf.material_mesh = \
[(6  * units.cm, upper['A1']),
 (60 * units.cm, active['A1'], core.material.fuel()),
 (6  * units.cm, lower['A1'])]

Note that, apart from defining cross sections, the material mesh is also the assembly exposure mesh, defining how the assembly will be depleted. Thus, the above example will deplete the entire active region on a single mesh. A better approach would be to subdivide this region into a number of sub-segments:

hf.material_mesh = \
[(6  * units.cm, upper['A1'])] + \
[(6 * units.cm, active['A1'], core.material.fuel())] * 10 + \
[(6  * units.cm, lower['A1'])]

which burn the active region in 10 segments of equal length. If the axial_exposure_mesh parameter of the target heterogeneous assembly is set, the exposure tag can also be used:

fa = assemblies.MY_REACTOR_assembly_type_1()

hf = nodal_model.add_assembly(base_name='TYPE_1',
                              lib_assembly=fa)

fa.axial_exposure_mesh = 10

hf.material_mesh = \
[(6  * units.cm, upper['A1']),
 exposure(active['A1']),
 (6  * units.cm, lower['A1'])]

which will also expand to 10 equal segments.

Note

Since loadable assemblies are frequently common to different core configurations, a lot of repetition can be avoided by defining the assembly’s nodal configuration directly in the input script were homogenization for the assembly was performed. This is achieved by defining a special method, called add_to in the module. For example in the inf_1 module:

h = parameters.generator.mode.active_height()
upper = parameters.add_axial_reflector_cut('TOP',
                                            rng=(0.5 * h, 0.5 * h + 6 * units.cm),
                                            description='Baffle region above active fuel')

act, data = parameters.add_lattice_cut('ACT',
                                       rng=(-0.5 * h, 0.5 * h),
                                       description='Active region')


lower = parameters.add_axial_reflector_cut('BOT',
                                            rng=(-0.5 * h - 6 * units.cm, -0.5 * h),
                                            description='Baffle region below active fuel')


def add_to(nodal_model):

    fa = assemblies.MY_REACTOR_assembly_type_1()

    hf = nodal_model.add_assembly(base_name='TYPE_1',
                                  lib_assembly=fa)
    hf.material_mesh = \
     [(6  * units.cm, upper['A1'])] + \
     [(6 * units.cm, active['A1'], core.material.fuel())] * 10 + \
     [(6  * units.cm, lower['A1'])]

Then, in the core input module (e.g. operational), the following will add all the mixtures without having to re-specify material meshes:

import inf_1

nodal_model = parameters.create_nodal_configuration(name='MY_REACTOR_configuration_1')

inf_1.add_to(nodal_model)

The add_to must take the nodal configuration as a parameter, but it can off course also include any additional parameters you want to pass to the inf_1 module.

Building the core configuration

The final step in the cOMPoSe nodal configuration construction is to define the static (non-loadable) channels in the homogenization_grid. This is accomplished using the build method of the nodal_model parameter pack, which requires the following two arguments:

  • pred: A predicate which determine the channels in the homogenization_grid that should be included. The system will loop through all entries x in your homogenization_grid, and only consider channels for which pred(x) evaluates to True. For example, to only build mixtures for channels marked with a 0 and 4, use lambda x : x in [0, 4].

  • layers: The axial material mesh that will be applied to all channels. It has exactly the same form as the material_mesh parameter used for loadable assemblies, except that, instead of passing a single mixture as the second argument, you pass a map of mixtures. This is usually accomplished by passing the entire cut placeholder.

For example:

h = parameters.generator.mode.active_height()
upper = parameters.add_axial_reflector_cut('TOP',
                                            rng=(0.5 * h, 0.5 * h + 6 * units.cm),
                                            description='Baffle region above active fuel')

act_u = parameters.add_cut('ACT-U',
                            rng=(0.0, 0.5 * h),
                            description='Upper active region')

act_l = parameters.add_cut('ACT-L',
                            rng=(-0.5 * h, 0.0),
                            description='Upper active region')


lower = parameters.add_axial_reflector_cut('BOT',
                                           rng=(-0.5 * h - 6 * units.cm, -0.5 * h),
                                           description='Baffle region below active fuel')


# ...

nodal_model = parameters.create_nodal_configuration(name='MY_REACTOR_configuration_1')

# ...

material_mesh = \
    [(6  * units.cm, upper),
     (0.5 * h, act_u),
     (0.5 * h, act_l),
     (6  * units.cm, lower)]

nodal_model.build(lambda x : x in [0,4],
                  material_mesh)