Reactivity Perturbations and Feedback Coefficient Calculations

This application mode is used to calculate reactivity perturbations over different sequences of various state changes.

Note

This application mode is optimized to only extract reactivity differences. If the effect of perturbations on other parameters is also of interest, consider using the cases application mode instead.

Typical Use Cases

  1. Calculate temperature based reactivity feedback tables.

  2. Calculate reactivity tables from control rod movement.

  3. Perform local (per channel) state perturbations.

  4. Prepare complete reactivity tables for system codes such as RELAP.

Additional Input Parameters

In the following fbc denotes a feedback_coefficients parameter set, created as follows:

import applications.feedback_coefficients as app

fbc = app.parameters()

This parameters set is an extension of critical case and therefore supports all those input parameters (including the general parameters). Perturbation sequences are specified using the following two methods:

fbc.add_sequence(name, *seq)

Adds a sequence of perturbations.

Parameters:
  • name (str) – Tag that is used to identify this sequence of perturbations.

  • seq – The perturbations themselves. See the description below on the accepted values.

The entries in seq can either be:

  1. A single state_parameter or a complete state instance. For example

    >>> fbc.add_sequence('FT', state_parameters.fuel_temperature(24 * units.degC), state_parameters.fuel_temperature(80 * units.degC))
    

    or

    fbc.add_sequence('Temps', (state_parameters.fuel_temperature(24 * units.degC), state_parameters.moderator_temperature(24 * units.degC)),
                              (state_parameters.fuel_temperature(80 * units.degC), state_parameters.moderator_temperature(60 * units.degC)))
    
  2. A single travel instance or a dict of travel instances. For example, to move all banks

    >>> fbc.add_sequence('all-banks', control.percentage_inserted(10.), control.percentage_inserted(20.))
    

    or if only a single bank should be perturbed

    fbc.add_sequence('bank-1', {'bank-1': control.percentage_inserted(10.)},
                               {'bank-1': control.percentage_inserted(20.)})
    
  3. A labeledgrid or hexagonal grid containing a local (per channel) state perturbation. Each entry in the grid can again be a single state_parameter or a complete state instance. For example

    grids = list()
    
    for temp in [24 * units.degC, 40 * units.degC, 80 * units.degC]
    
        ft = state_parameters.fuel_temperature(temp)
    
        m = \
        [[_, 'A', 'B', 'C'],
         [1,   _,  ft,   _],
         [2,   _,   _,   -]]
    
        grids.append(m)
    
    fbc.add_sequence('B1-FT', *grids)
    
  4. A power value, for example

    >>> fbc.add_sequence('power', 1 * unit.MW, 1.2 * units.MW, 2 * units.MW)
    

    Note

    When performing power perturbations you would likely need to toggle the app_param.thermal_hydraulic_feedback option. This will however, affect all your calculations, and should then not be used in the same application input which also calculates other state perturbations (such as fuel temperature).

  5. A general function which accepts a critical_case parameter set as input, and performs an arbitrary perturbation on these parameters. For example

    def f1(parm):
       # do something
       pass
    
    def f2(parm):
       # do something else
       pass
    
    fbc.add_sequence('general', f1, f2)
    

    This functionality can be used to perform perturbations on the model of arbitrary complexity. See the last example for more detail.

    Note

    When passing an arbitrary function the actual state space is unknown, and the delta between two perturbations is assumed to be 1. To override this behavior, consider creating a function object derived from Custom and specifying the base and delta methods. See the examples.

Attention

The type of entries in a sequence should not be mixed. For example,

>>> fbc.add_sequence('bad-idea', state_parameter.fuel_temperature(100 * units.degC), control.percentage_inserted(50.0))

is invalid. Add two sequences instead:

>>> fbc.add_sequence('FT', state_parameter.fuel_temperature(100 * units.degC))
>>> fbc.add_sequence('ROD', control.percentage_inserted(50.0))
fbc.add_perturbations(name, param, *seq)

Convenience method that adds a number of perturbations to a specific state parameter.

Parameters:
  • name (str) – Tag that is used to identify this sequence of perturbations.

  • param (state_parameter) – The state parameter that should be perturbed.

  • seq – The sequence of (additive) perturbations.

Example:

>>> fbc.add_perturbations('FT', state_parameters.fuel_temperature, -10, +10, +50)

Attention

Since these perturbations are relative to the base state, it is important that when using this method the target param is set in the model.state.

Note

When adding to an existing sequence, the value is appended. Thus

fbc.add_sequence('my-seq', a, b)

is equivalent to:

fbc.add_sequence('my-seq', a)
fbc.add_sequence('my-seq', b)

Command Line Usage

This application supports all the standard application modes and options as described in General Application Command Line Interface (CLI).

Typical command line usage

oscar5 MY_REACTOR.projects.my_perturbations --target-mode <MODE> --config-file <CONFIG> run --threads 24

After output processing, a simple summary table is printed. See example below.

+-----------+-----------+-----------+-----------+---------------+
|Sequence   |Index      |Keff       |dR (pcm)   |dR/dx          |
+===========+===========+===========+===========+===============+
|Nominal    |           |1.51818    |           |               |
+-----------+-----------+-----------+-----------+---------------+
|FT         |0          |1.51859    |17.70      |-1.78 / kelvin |
+-----------+-----------+-----------+-----------+---------------+
|           |1          |1.51619    |-86.15     |-1.76 / kelvin |
+-----------+-----------+-----------+-----------+---------------+
|MT         |0          |1.51812    |-2.26      |0.23 / kelvin  |
+-----------+-----------+-----------+-----------+---------------+
|           |1          |1.51844    |11.32      |0.23 / kelvin  |
+-----------+-----------+-----------+-----------+---------------+

More information can be extracted from the result files using the provided output tokens.

Output Tokens

The output tokens for this application mode are listed below. See also Using output tokens for general guidelines on using these tokens.

Note

In all the output tokens, the parameters sequence and state_parameter entries are only required when using the token to also drive input generations (via other application modes such as reload). It does not directly affect the output processing, and can therefore be omitted when using the tokens to only extract output.

feedback_coefficients.multiplication_factors(tag, sequence=None, state_parameter=None, at=None, **kwargs)

Token used to extract the individual \(k_\mathrm{eff}\) values for a specified perturbation sequence.

Parameters:

The return type depends on the value of at:

  • If not specified (the default), a list of length equal to the perturbation sequence is returned.

  • If a single integer, only the \(k_\mathrm{eff}\) at this particular point in the sequence is returned.

  • Finally, if it is a list of integers, a list of length equal to at is returned, containing the \(k_\mathrm{eff}\) at these points in the sequence (in the order determined by at).

feedback_coefficients.reactivities(tag, sequence=None, state_parameter=None, at=None, **kwargs)

Behaves just like feedback_coefficients.multiplication_factors() but returns the total reactivity change (with respect to the unperturbed case) in pcm.

feedback_coefficients.integral_points(tag, interpolation='cubic', sequence=None, state_parameter=None, at=None, **kwargs)

Token used to extract a point-wise representation of the cumulative reactivity curve.

Parameters:
  • interpolation (str) – The type of interpolation scheme used between points not in the original perturbation sequence. All options listed in kind are supported.

  • at (list) – Customize on which state mesh the reactivities are calculated.

This will always return a pair of lists, the first being the state points (such as temperatures or bank positions), and the second the cumulative reactivities (in pcm) at these points. Exactly which points are returned depends on the value of at:

  • If not specified (the default), the points will correspond to the original perturbation sequence, but includes the base (unperturbed) point. Note that, in this case, the interpolation parameter is not used.

  • If it is a list of state points, these will be returned, with the corresponding cumulative reactivities. Thus at can be used to refine the mesh on which the curve is returned.

feedback_coefficients.feedback_coefficients(tag, interpolation='cubic', sequence=None, state_parameter=None, at=None, **kwargs)

Behaves just like feedback_coefficients.integral_points(), but the second sequence contains the reactivity rate of change (derivative of the cumulative reactivity curve) at the state points.

Note

The interpolation parameter is always used in this case.

feedback_coefficients.reactivity(tag, start, end, interpolation='cubic', sequence=None, state_parameter=None, at=None, **kwargs)

Token used to extract the reactivity change induced by moving from one point to another in the state space.

Parameters:
  • start – Starting point

  • end – End point

Returns the delta reactivity (in pcm), when moving from start to end.

Examples

The following example will calculate reactivity differences on two sequences, then print the feedback coefficients to a file on a finer mesh using the feedback_coefficients.feedback_coefficients().

import applications.feedback_coefficients as app
from ..configurations.my_config import model
from core import utilities, units, state_parameters

parameters = app.parameters()

parameters.project_name = 'feedback'
parameters.description = 'Calculate some reactivity perturbations'
parameters.working_directory = utilities.path_relative_to(__file__, 'FEEDBACK')


parameters.model = model
parameters.time_stamp = '01/01/2018 06:00:00'

model.state[state_parameters.fuel_temperature] = 30 * units.degC
model.state[state_parameters.moderator_temperature] = 30 * units.degC
model.state[state_parameters.moderator_density] = 0.998 * units.g/units.cc
model.facility_description.design_coolant_flow_direction = 'DOWNWARDS'


parameters.power_maps = True
parameters.power = 689655.0 * units.W

# Power feedback
# parameters.thermal_hydraulic_feedback = True
#
# parameters.add_sequence('power', 700 * units.kW, 1 * units.MW, 1.2 * units.MW)

parameters.add_perturbations('FT', state_parameters.fuel_temperature, -10, +10, +30, +50, +80)
parameters.add_perturbations('MT', state_parameters.moderator_temperature, -10, +10, +30, +50, +80)

parameters.particles = 64000
parameters.source_iteration = 100
parameters.max_iteration = 300


def fine_mesh(param, results):
    """
    Extract feedback coefficients on a fine mesh and export to file.
    """

    mesh = [(20 + 5 * i) * units.degC for i in range(16)]

    pnts, fb = app.feedback_coefficients(tag='FT', at=mesh).get(results)
    pnts, mb = app.feedback_coefficients(tag='MT', at=mesh).get(results)

    with open(utilities.path_relative_to(__file__, 'coefficients.txt'), 'w') as of:

        for p, f, m in zip(pnts, fb, mb):
            of.write('{:.1f}  {:.2f}  {:.2f}\n'.format(
                p.to('degC').magnitude, f.to('1/K').magnitude, m.to('1/K').magnitude))


parameters.add_result_hook(fine_mesh)

if __name__ == '__main__':
    app.main(parameters)

The next example performs local perturbation in specified list of core channels.

import applications.feedback_coefficients as app
from ..configurations.my_config import model
from core import utilities, units, state_parameters

parameters = app.parameters()

parameters.project_name = 'per-channel'
parameters.description = 'Calculate state perturbations in a number of local channels'
parameters.working_directory = utilities.path_relative_to(__file__, 'PER-CHANNEL')


parameters.model = model
parameters.time_stamp = '01/01/2018 06:00:00'

model.state[state_parameters.fuel_temperature] = 30 * units.degC
model.state[state_parameters.moderator_temperature] = 30 * units.degC
model.state[state_parameters.moderator_density] = 0.998 * units.g/units.cc

channels = ['A1', 'A2', 'B1', 'C3']
pert = [state_parameters.moderator_temperature(20 * units.degC),
        state_parameters.moderator_temperature(40 * units.degC)]

# global perturbation
parameters.add_sequence('mt', *pert)

# channel specific perturbations
for channel in channels:
    for p in pert:
        m = model.layout.new()
        m[channel] = p
        parameters.add_sequence(f'{channel}-mt', m)

if __name__ == '__main__':
    app.main(parameters)

The final example employs a custom perturbation to model a rig drop.

import applications.feedback_coefficients as app
from ..configurations.my_config import model
from ..model import assemblies
from core import utilities, geometry, units, state_parameters

parameters = app.parameters()

parameters.project_name = 'rig-drop'
parameters.description = 'Calculate reactivity perturbations as a rig drops'
parameters.working_directory = utilities.path_relative_to(__file__, 'RIG-DROP')

parameters.model = model
parameters.time_stamp = '01/01/2018 06:00:00'

model.state[state_parameters.fuel_temperature] = 30 * units.degC
model.state[state_parameters.moderator_temperature] = 30 * units.degC
model.state[state_parameters.moderator_density] = 0.998 * units.g/units.cc

class DropRig(app.Custom):
    @classmethod
    def base():
        return 50 * units.cm                          # completely outside the core

    def delta():
        return self.pos

    def __init__(pos)
        self.pos = pos

    def __call__(param)
        rig = assemblies.my_rig()
        rig.add_transformation(geometry.translation(z=self.pos)
        # load into position C5
        param.model.core_map['C5'].load_facility('my-facility', rig)


for pos in [40 * units.cm, 30 * units.cm, 20 * units.cm, 10 * units.cm, \
             5 * units.cm, 0.0 * units.cm, -10 * units.cm]:
   parameters.add_sequence('rig-drop', DropRig(pos))

if __name__ == '__main__':
    app.main(parameters)