LCOV - code coverage report
Current view: top level - src/controls - PIDTransientControl.C (source / functions) Hit Total Coverage
Test: idaholab/moose framework: #32971 (54bef8) with base c6cf66 Lines: 105 112 93.8 %
Date: 2026-05-29 20:35:17 Functions: 5 5 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : //* This file is part of the MOOSE framework
       2             : //* https://mooseframework.inl.gov
       3             : //*
       4             : //* All rights reserved, see COPYRIGHT for full restrictions
       5             : //* https://github.com/idaholab/moose/blob/master/COPYRIGHT
       6             : //*
       7             : //* Licensed under LGPL 2.1, please see LICENSE for details
       8             : //* https://www.gnu.org/licenses/lgpl-2.1.html
       9             : 
      10             : #include "PIDTransientControl.h"
      11             : #include "Function.h"
      12             : #include "TransientBase.h"
      13             : #include "TimeStepper.h"
      14             : #include "FEProblemBase.h"
      15             : #include "MooseApp.h"
      16             : 
      17             : registerMooseObject("MooseApp", PIDTransientControl);
      18             : 
      19             : InputParameters
      20        3253 : PIDTransientControl::validParams()
      21             : {
      22        3253 :   InputParameters params = Control::validParams();
      23        6506 :   params.addClassDescription(
      24             :       "Sets the value of a 'Real' input parameter (or postprocessor) based on a Proportional "
      25             :       "Integral Derivative control of a postprocessor to match a target a target value.");
      26       13012 :   params.addRequiredParam<PostprocessorName>(
      27             :       "postprocessor", "The postprocessor to watch for controlling the specified parameter.");
      28       13012 :   params.addRequiredParam<FunctionName>("target",
      29             :                                         "The target value 1D time function for the postprocessor");
      30       13012 :   params.addRequiredParam<Real>("K_integral", "The coefficient multiplying the integral term");
      31       13012 :   params.addRequiredParam<Real>("K_proportional",
      32             :                                 "The coefficient multiplying the difference term");
      33       13012 :   params.addRequiredParam<Real>("K_derivative", "The coefficient multiplying the derivative term");
      34       13012 :   params.addParam<std::string>(
      35             :       "parameter",
      36             :       "The input parameter(s) to control. Specify a single parameter name and all "
      37             :       "parameters in all objects matching the name will be updated");
      38       13012 :   params.addParam<std::string>("parameter_pp",
      39             :                                "The postprocessor to control. Should be accessed by reference by "
      40             :                                "the objects depending on its value.");
      41        9759 :   params.addParam<Real>(
      42        6506 :       "start_time", -std::numeric_limits<Real>::max(), "The time to start the PID controller at");
      43        9759 :   params.addParam<Real>(
      44        6506 :       "stop_time", std::numeric_limits<Real>::max(), "The time to stop the PID controller at");
      45        9759 :   params.addParam<bool>(
      46             :       "reset_every_timestep",
      47        6506 :       false,
      48             :       "Reset the PID integral when changing timestep, for coupling iterations within a timestep");
      49        9759 :   params.addParam<bool>("reset_integral_windup",
      50        6506 :                         true,
      51             :                         "Reset the PID integral when the error crosses zero and the integral is "
      52             :                         "larger than the error.");
      53             : 
      54        9759 :   params.addParam<Real>("maximum_output_value",
      55        6506 :                         std::numeric_limits<Real>::max(),
      56             :                         "Can be used to limit the maximum value output by the PID controller.");
      57        9759 :   params.addParam<Real>("minimum_output_value",
      58        6506 :                         -std::numeric_limits<Real>::max(),
      59             :                         "Can be used to limit the minimum value output by the PID controller.");
      60       13012 :   params.addRangeCheckedParam<Real>(
      61             :       "maximum_change_rate",
      62        6506 :       std::numeric_limits<Real>::max(),
      63             :       "maximum_change_rate>0",
      64             :       "Can be used to limit the absolute rate of change per second of value "
      65             :       "output by the PID controller.");
      66        3253 :   return params;
      67           0 : }
      68             : 
      69          96 : PIDTransientControl::PIDTransientControl(const InputParameters & parameters)
      70             :   : Control(parameters),
      71          96 :     _current(getPostprocessorValueByName(getParam<PostprocessorName>("postprocessor"))),
      72         192 :     _target(getFunction("target")),
      73         192 :     _Kint(getParam<Real>("K_integral")),
      74         192 :     _Kpro(getParam<Real>("K_proportional")),
      75         192 :     _Kder(getParam<Real>("K_derivative")),
      76         192 :     _start_time(getParam<Real>("start_time")),
      77         192 :     _stop_time(getParam<Real>("stop_time")),
      78         192 :     _reset_every_timestep(getParam<bool>("reset_every_timestep")),
      79         192 :     _reset_integral_windup(getParam<bool>("reset_integral_windup")),
      80         192 :     _maximum_output_value(getParam<Real>("maximum_output_value")),
      81         192 :     _minimum_output_value(getParam<Real>("minimum_output_value")),
      82         192 :     _maximum_change_rate(getParam<Real>("maximum_change_rate")),
      83         192 :     _integral(declareRestartableData<Real>("pid_integral", 0)),
      84         192 :     _integral_old(declareRestartableData<Real>("pid_integral_old", 0)),
      85         192 :     _value(declareRestartableData<Real>("pid_value", 0)),
      86         192 :     _value_old(declareRestartableData<Real>("pid_value_old", 0)),
      87         192 :     _t_step_old(declareRestartableData<int>("pid_tstep_old", -1)),
      88         288 :     _old_delta(declareRestartableData<Real>("pid_delta_old", 0)),
      89          96 :     _has_recovered(false)
      90             : {
      91          96 :   if (!_fe_problem.isTransient())
      92           0 :     mooseError("PIDTransientControl is only meant to be used when the problem is transient, for "
      93             :                "example with a Transient Executioner. Support for Steady "
      94             :                "Executioner can be added in the future, however certain parameters are currently "
      95             :                "not well defined for use with Picard iterations.");
      96             : 
      97         408 :   if (isParamValid("parameter") && isParamValid("parameter_pp"))
      98           0 :     paramError("parameter_pp",
      99             :                "Either a controllable parameter or a postprocessor to control should be specified, "
     100             :                "not both.");
     101         360 :   if (!isParamValid("parameter") && !isParamValid("parameter_pp"))
     102           0 :     mooseError("A parameter or a postprocessor to control should be specified.");
     103         288 :   if (isParamValid("parameter") && _reset_every_timestep)
     104           0 :     paramError(
     105             :         "reset_every_timestep",
     106             :         "Resetting the PID every time step is only supported using controlled postprocessors");
     107          96 :   if (_maximum_output_value <= _minimum_output_value)
     108           0 :     mooseError(
     109             :         "The parameters maximum_output_value and minimum_output_value are inconsistent. The value "
     110             :         "of maximum_output_value should be greater than the value of minimum_output_value.");
     111          96 : }
     112             : 
     113             : void
     114        1576 : PIDTransientControl::execute()
     115             : {
     116             :   // Dont execute on INITIAL again on a recover.
     117             :   // Executing on INITIAL in a regular simulation is fine, but on a recover we will get the integral
     118             :   // term wrong and set the wrong attributes for the time derivative too. Best to skip
     119        1576 :   if (_app.isRecovering() && _fe_problem.getCurrentExecuteOnFlag() == EXEC_INITIAL)
     120             :   {
     121           6 :     mooseInfo("Skipping execution on recover + INITIAL.");
     122           6 :     return;
     123             :   }
     124             : 
     125        1570 :   if (_t >= _start_time && _t < _stop_time)
     126             :   {
     127             :     // Get the current value of the controllable parameter
     128        4710 :     if (isParamValid("parameter"))
     129             :     {
     130             :       // If just recovering, we must use the restartable value as the parameter value is
     131             :       // not restarted
     132         874 :       if (_app.isRecovering() && !_has_recovered)
     133           5 :         _has_recovered = true;
     134             :       // else get the current value of the parameter
     135             :       else
     136        2607 :         _value = getControllableValue<Real>("parameter");
     137             :     }
     138             :     else
     139        2088 :       _value = getPostprocessorValueByName(getParam<std::string>("parameter_pp"));
     140             : 
     141             :     // Compute the delta between the current value of the postprocessor and the desired value
     142        1570 :     Real delta = _current - _target.value(_t);
     143             : 
     144             :     // Save integral and controlled value at each time step
     145             :     // if the solver fails, a smaller time step will be used but _t_step is unchanged
     146        1570 :     if (_t_step != _t_step_old)
     147             :     {
     148             :       // Reset the error integral if PID is only used within each timestep
     149        1551 :       if (_reset_every_timestep)
     150         231 :         _integral = 0;
     151             : 
     152        1551 :       _integral_old = _integral;
     153        1551 :       _value_old = _value;
     154        1551 :       _t_step_old = _t_step;
     155        1551 :       _delta_prev_tstep = delta;
     156        1551 :       _old_delta_prev_tstep = _old_delta;
     157             :     }
     158             : 
     159             :     // If there were coupling/Picard iterations during the transient and they failed,
     160             :     // we need to reset the controlled value and the error integral to their initial value at the
     161             :     // beginning of the coupling process
     162        1570 :     if (_app.getExecutioner()->fixedPointSolve().numFixedPointIts() == 1)
     163             :     {
     164        1570 :       _integral = _integral_old;
     165        1570 :       _value = _value_old;
     166        1570 :       delta = _delta_prev_tstep;
     167        1570 :       _old_delta = _old_delta_prev_tstep;
     168             :     }
     169             : 
     170             :     // If desired, reset integral of the error if the error crosses zero
     171        1570 :     if (_reset_integral_windup && delta * _old_delta < 0)
     172        1073 :       _integral = 0;
     173             : 
     174             :     // Compute the three error terms and add them to the controlled value
     175        1570 :     _integral += delta * _dt;
     176        1570 :     _value += _Kint * _integral + _Kpro * delta;
     177        1570 :     if (_dt > 0)
     178        1504 :       _value += _Kder * (delta - _old_delta) / _dt;
     179             : 
     180             :     // If the maximum rate of change by the pid is fixed
     181        1570 :     if (_maximum_change_rate != std::numeric_limits<Real>::max())
     182         232 :       _value = std::min(std::max(_value_old - _dt * _maximum_change_rate, _value),
     183         464 :                         _value_old + _dt * _maximum_change_rate);
     184             : 
     185             :     // Compute the value, within the bounds
     186        1570 :     _value = std::min(std::max(_minimum_output_value, _value), _maximum_output_value);
     187             : 
     188             :     // Set the new value of the parameter or postprocessor
     189        4710 :     if (isParamValid("parameter"))
     190        2622 :       setControllableValue<Real>("parameter", _value);
     191             :     else
     192        2088 :       _fe_problem.setPostprocessorValueByName(getParam<std::string>("parameter_pp"), _value);
     193             : 
     194             :     // Keep track of the previous delta for integral windup control
     195             :     // and for time derivative calculation
     196        1570 :     _old_delta = delta;
     197             :   }
     198             : }
     199             : 
     200             : void
     201          96 : PIDTransientControl::initialSetup()
     202             : {
     203          96 :   if (_app.isRecovering())
     204             :   {
     205          24 :     if (isParamValid("parameter"))
     206          15 :       setControllableValue<Real>("parameter", _value);
     207             :     else
     208           9 :       _fe_problem.setPostprocessorValueByName(getParam<std::string>("parameter_pp"), _value);
     209             :   }
     210          96 : }
     211             : 
     212             : void
     213        2952 : PIDTransientControl::timestepSetup()
     214             : {
     215        2952 :   const auto * t_ex = dynamic_cast<const TransientBase *>(_app.getExecutioner());
     216        2989 :   if (t_ex && t_ex->getTimeStepper()->justFailedTimeStep() &&
     217          37 :       getExecuteOnEnum().contains(EXEC_TIMESTEP_END))
     218             :   {
     219             :     // We need to revert to the timestep begin state
     220           1 :     _integral = _integral_old;
     221           1 :     _value = _value_old;
     222           1 :     _old_delta = _old_delta_prev_tstep;
     223           3 :     if (isParamValid("parameter"))
     224           3 :       setControllableValue<Real>("parameter", _value);
     225             :     else
     226           0 :       _fe_problem.setPostprocessorValueByName(getParam<std::string>("parameter_pp"), _value);
     227             :   }
     228        2952 : }

Generated by: LCOV version 1.14