A FIRST SIMPLE OSCAR-5 MODEL

../_images/banner4.png

Welcome to what is likely your first touch to the OSCAR system. This tutorial will approach the process in two steps.

We will firstly build a very simple, two assembly reactor core, using the various constructs available in the OSCAR system. We will do this all in one simple python script, and in the process identify the major components which have to be put together to create a core model, load it with fuel assemblies (MTR type), and then run a steady-state flux calculation on it.

Note

The approach of putting all these aspects together in a single file is not the typical way in which OSCAR is used, but it is a completely valid approach and often is the best way to quickly construct and test some ideas.

For more industrial reactor model development tasks, the OSCAR system prescribes a folder structure and decentralised file management approach to assist with keeping better control, and not duplicating code input. This approach is demonstrated in the second part of this tutorial.

Overview

OSCAR input file components

When generating an OSCAR-5 model and associated calculations, there are essentially three major steps involved:

  1. Decide the type of calculation you want to perform, and set the required calculational parameters associated with that type of calculation. This includes assigning a model.

  2. To define the model (reactor geometry and materials) to be used, we follow three steps:

    • Develop the individual component models for the different types of reactor core components (fuel, control, reflector, etc.)

    • Develop a full core model, which is built up from arranging the available components into some core design or grid.

    • Customize the core for the specific conditions of interest - this could include loading the core at a given date with components with actual names, setting nominal core conditions, etc.

  3. Select the target physics solver of interest, execute the calculation, and then post-process the results.

In this first part of the tutorial, we combine the OSCAR-5 input for all these steps into a single file. You can find this file at in the projects/demo_simple_integrated_model.py file within the SAFARI-1 benchmark problem set.

Input file walk-through - general input

Let us examine the code input for each of the items above. We start with setting up the calculation that we would like to perform.

from applications.critical_case import *
from core import *

parameters.model = build_core()

# then setup critical case parameters
parameters.project_name = 'C1'
parameters.description = 'Flux calc C1'
parameters.working_directory = utilities.path_relative_to(__file__, 'DEMO_C1')

# finally set calculational parameters
parameters.threads = 20
parameters.particles = 64000
parameters.source_iteration = 50
parameters.max_iteration = 100

The input snippet show the portion of the script which sets the basic calculational parameters. Note the following:

  1. We begin by selecting (via import) the appropriate application from the OSCAR system - in this case we choose the critical case application, which aims at calculating a steady-state flux profile and associated system k-eff. We import all public classes and functions exposed by the core package for direct use.

  2. Next we set the reactor model via calling the build_core() function. This is a local function specified elsewhere in this file to create the geometry and material layout of the reactor. We will discuss this step in the following section.

  3. We set a number of administrative options (project name, working directory, etc.)

  4. Finally we define some general computational parameters to be used with the variety of neutronics solvers available.

Input file walk-through - building the core

We proceed to inspect the build_core function, to understand how a simple reactor model is designed in OSCAR-5. Here we will build a small reactor core with only two fuel assemblies (plate-type), surrounded by reflective boundary conditions.

../_images/single_file_tut.png

Two fuel element reactor model

The code extract for the building the core is already a little more involved and touches on aspects of the system which will discussed in much more detail in future tutorials (such as Single Assembly tutorial). For now it is not essential to understand every line, but at least to get a feel of how model building is approached.

def build_core():

    from applications.configuration import model
    import core.state
    from material_library.moderators import LightWater

    # this function builds a simple 2 assembly core
    model.facility_description.name = 'ToyReactor'
    model.facility_description.site = 'ToyReactor'
    model.facility_description.unit = 'ToyReactor'
    model.facility_description.type = 'MTR'
    model.facility_description.design_power = 2 * units.MW

    model.state[core.state.fuel_temperature] = 60 * units.degC
    model.state[core.state.moderator_temperature] = 40 * units.degC
    model.state[core.state.moderator_density] = LightWater.density_at(40 * units.degC,
                                                                      1.8 * units.bars)
    model.boundary_condition = boundary_conditions.reflective()

    model.core_map = \
        [[0,    1,  2],
         ['A', _p, _p]]
    model.core_pitches = (7.71 * units.cm, 8.1 * units.cm)

    mft = build_fuel_assembly()  # read mft as `my fuel type`
    mlft = model.inventory_manager.add_loadable_assembly(mft) #read mlft as `my loadable fuel type`
    model.inventory_manager.load_map = \
        [[0,       1,     2],
         ['A',  mlft,  mlft]]

    model.load_map = \
        [[0,      1,    2],
         ['A', 'F1', 'F2']]

    return model

We begin by importing an empty model from the configuration application, and set its basic administrative and state parameters. The we add the following:

  1. A core_map is specified, which defines the positions where core components can be loaded. You will note that nothing is loaded in the core, and a simple placeholder _p is used. The core pitches are also set.

  2. We then define two so-called load maps - to load the core with fuel elements:

    • The first (model.inventory_manager.load_map), which is a member of the inventory manager class, is populated with a type of fuel. This type of fuel is built by a function called build_fuel_assembly which we discuss in the next section. The concept of an inventory manager will also be further discussed in Single assembly tutorial.

    • The second model.load map is a map specified with actual assembly names. These will be actual instances (created elements of the given type), for which information such as burn-up or nuclide densities can be stored over time.

Hint

The fact that assembly 'F1' is in the core position where mlft is placed, allows the natural inference that it is of that type.

Input file walk-through - building an assembly

We encountered the function build_fuel_assembly while building the core model. Lets see what it does.

def build_fuel_assembly(ass_name='F1'):

    from ..model import materials
    from material_library.moderators import LightWater
    from core.fuel_assembly import PlateAssembly
    from applications.threading_tools import num_cores

    # This function builds a single plate type fuel assembly,
    # and returns an instance to it
    fa = PlateAssembly(name=ass_name)
    fa.description = 'Test LEU fuel assembly'

    ag3ne = materials.al_clad()
    lwt = LightWater(mass_density=0.9920 * units.g / units.cc)

    fa.number_of_plates = 19
    fa.pitches = 2.95 * units.mm + 1.275 * units.mm  # coolant gap plus plate width
    fa.meat_width = 0.051 * units.cm
    fa.meat_length = 6.30 * units.cm
    fa.meat_height = 59.37 * units.cm
    fa.plate_width = 1.275 * units.mm
    fa.plate_length = 71.68 * units.mm
    fa.plate_height = 625.48 * units.mm

    fa.grid_plate_pitch = 66.8 * units.mm
    fa.slot_insert_depth = 0.5 * (2.74 + 2.59 + 0.45 + 0.55) * units.mm
    fa.slot_insert_width = 1.45 * units.mm
    fa.box = [-0.5 * 75.9 * units.mm, 0.5 * 75.9 * units.mm, -0.5 * 80.15 * units.mm, 0.5 * 80.15 * units.mm]
    fa.axial_region = [-0.5 * 682.9 * units.mm, 0.5 * 682.9 * units.mm]

    fa.clad_material = ag3ne
    fa.grid_material = ag3ne
    fa.moderator_material = lwt

    fa.create_bundle_place_holder()

    fa.complete_universe(material=lwt)
    threads = num_cores()
    tree, stack, new_cells = fa.construct_search_tree(update_archive=False, threads=threads)

    fa.construct_bundle()
    fa.fuel_bundle().depletion_mesh.initial_material_distribution = \
        materials.uranium_silicide(plate_volume=0.051 * 6.30 * 59.37 * units.cc)

    return fa

The assembly build function has a few parts. The first part defines the basic assembly geometry - in this case we use a macro (method that builds the geometry from a few typical parameters) to build a PlateAssembly. OSCAR-5 contains a variety of these macros to assist in building typical reactor core components. The result of applying such a macro is the construction of the fuel element via a set of CSG (Constructive Solid Geometry) objects, which are combined via various set operations (such as union or intersection). You are of course free to build objects directly from the available set of underlying primitives.

We will not dwell on each of these macro parameters for the PlateAssembly at this stage, as they are fully described in Single Assembly tutorial. However, take a look at the parameters and try to imagine how they might relate to the creation of an MTR fuel assembly as can be seen in your model image.

Note

You will note that all specifications have associated units specified, as OSCAR-5 is unit-aware.

The materials for clad, side-plates and moderator are also defined. You will note that the fuel material is treated somewhat differently, as it is defined as part of a so-called bundle, as an initial distribution. In OSCAR-5 burnable components are grouped into bundles, with a particular burnup mesh associated with the material.

You also note a section in the input file referring to completion of the universe. This is a particular useful feature which fills the space outside of the constructed geometry with a specified material by building cells iteratively. Without this feature the user would have to manually define and build these cells, which could be a time-consuming and error-prone task.

We now move on to some step-by-step actions we can perform on this script.

Step-by-step

Let us now use the developed input script, and aim to accomplish the following tasks:

  1. Visualise the model we have prepared.

  2. Generate an input file for use in the Serpent Monte Carlo code to calculate the k-eff of the system.

  3. Run the Serpent calculation.

  4. Post process the results.

We begin by calling the OSCAR-5 system to visualize the prepared geometry. Run the following command:

>>> oscar5 SAFARI_1.projects.demo_simple_integrated_model visualization

Hint

More advanced interactive options exist for viewing and interacting with the geometry, which will be covered in later parts of this tutorial.

Following some mesh processing, a graphical depiction of the assembly should appear, which can be manipulated in space by dragging the mouse.

Next we proceed to generate a Serpent input file for the problem:

>>> oscar5 SAFARI_1.projects.demo_simple_integrated_model --target-mode SERPENT pre --force

Hint

A complete set of available command line options can be requested (at each point in command) by including --help in the command.

You can inspect the produced input file in the defined working directory, which in this case should be within the projects/DEMO_C1/SERPENT folder. This input file is produced via the Serpent code specific input generation plugin within OSCAR-5.

Next we launch the Serpent calculation, and do so on a separate computational server:

>>> oscar5 SAFARI_1.projects.demo_simple_integrated_model --target-mode SERPENT --config-file neclnx002.cfg execute --force --threads 48

Although this is a Monte Carlo code run, it takes only a few minutes if supplied with enough cores, which is specified in the command line (in this example 48).

We continue to post process the results:

>>> oscar5 SAFARI_1.projects.demo_simple_integrated_model --target-mode SERPENT --config-file neclnx002.cfg post --force

This command should yield the following output:

keff =   1.52362

Fractional power map:

         1      2
  A 100.00 100.00

Beta Effective                    : 0.00657585 (+- 0.00010942214399999999)
Generation Time                   : 1.94149e-05 second (+- 1.0289897e-08)
Run produced 1 warning(s) and 0 error(s)

Note in this output the reference to warnings and errors. These are written to the system log file, which is located in the top level reactor model folder, named system.log.

Discussion

This simple model combines the input associated with the model building, core loading, and calculational setup. Although putting these elements together is tempting from a simplicity perspective, it could lead to significant duplication if many calculations are associated with a central model, or if many core loadings exist. To circumvent this problem, a distributed modelling approach is used in OSCAR-5, as shown in the table below.

Mapping of modelling components to OSCAR-5 folder locations

Model Element

Location

Description

Component models

model folder

Contains a .py file for each reactor component

Core model

configurations folder

Contains a .py file for each reactor core layout

Core loadings

Facility database file

Contains a date-time stamped history of where elements reside

Calculational options

projects folder

Contains a py file setting up each calculational option. Some applications have their own dedicated folder.

In the subsequent parts of this tutorial, we will use the distributed model approach, and demonstrate various calculational applications which are supported by the OSCAR-5 system. Note that in other OSCAR-5 tutorials, some of these applications will be discussed in further detail.