LCOV - code coverage report
Current view: top level - src/controls - PIDTransientControl.C (source / functions) Hit Total Coverage
Test: idaholab/moose framework: 2bf808 Lines: 85 91 93.4 %
Date: 2025-07-17 01:28:37 Functions: 3 3 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 "Transient.h"
      13             : #include "FEProblemBase.h"
      14             : 
      15             : registerMooseObject("MooseApp", PIDTransientControl);
      16             : 
      17             : InputParameters
      18       14433 : PIDTransientControl::validParams()
      19             : {
      20       14433 :   InputParameters params = Control::validParams();
      21       14433 :   params.addClassDescription(
      22             :       "Sets the value of a 'Real' input parameter (or postprocessor) based on a Proportional "
      23             :       "Integral Derivative control of a postprocessor to match a target a target value.");
      24       14433 :   params.addRequiredParam<PostprocessorName>(
      25             :       "postprocessor", "The postprocessor to watch for controlling the specified parameter.");
      26       14433 :   params.addRequiredParam<FunctionName>("target",
      27             :                                         "The target value 1D time function for the postprocessor");
      28       14433 :   params.addRequiredParam<Real>("K_integral", "The coefficient multiplying the integral term");
      29       14433 :   params.addRequiredParam<Real>("K_proportional",
      30             :                                 "The coefficient multiplying the difference term");
      31       14433 :   params.addRequiredParam<Real>("K_derivative", "The coefficient multiplying the derivative term");
      32       14433 :   params.addParam<std::string>(
      33             :       "parameter",
      34             :       "The input parameter(s) to control. Specify a single parameter name and all "
      35             :       "parameters in all objects matching the name will be updated");
      36       14433 :   params.addParam<std::string>("parameter_pp",
      37             :                                "The postprocessor to control. Should be accessed by reference by "
      38             :                                "the objects depending on its value.");
      39       43299 :   params.addParam<Real>(
      40       28866 :       "start_time", -std::numeric_limits<Real>::max(), "The time to start the PID controller at");
      41       43299 :   params.addParam<Real>(
      42       28866 :       "stop_time", std::numeric_limits<Real>::max(), "The time to stop the PID controller at");
      43       43299 :   params.addParam<bool>(
      44             :       "reset_every_timestep",
      45       28866 :       false,
      46             :       "Reset the PID integral when changing timestep, for coupling iterations within a timestep");
      47       43299 :   params.addParam<bool>("reset_integral_windup",
      48       28866 :                         true,
      49             :                         "Reset the PID integral when the error crosses zero and the integral is "
      50             :                         "larger than the error.");
      51             : 
      52       43299 :   params.addParam<Real>("maximum_output_value",
      53       28866 :                         std::numeric_limits<Real>::max(),
      54             :                         "Can be used to limit the maximum value output by the PID controller.");
      55       43299 :   params.addParam<Real>("minimum_output_value",
      56       28866 :                         -std::numeric_limits<Real>::max(),
      57             :                         "Can be used to limit the minimum value output by the PID controller.");
      58       43299 :   params.addRangeCheckedParam<Real>(
      59             :       "maximum_change_rate",
      60       28866 :       std::numeric_limits<Real>::max(),
      61             :       "maximum_change_rate>0",
      62             :       "Can be used to limit the absolute rate of change per second of value "
      63             :       "output by the PID controller.");
      64       14433 :   return params;
      65           0 : }
      66             : 
      67          84 : PIDTransientControl::PIDTransientControl(const InputParameters & parameters)
      68             :   : Control(parameters),
      69          84 :     _current(getPostprocessorValueByName(getParam<PostprocessorName>("postprocessor"))),
      70          84 :     _target(getFunction("target")),
      71          84 :     _Kint(getParam<Real>("K_integral")),
      72          84 :     _Kpro(getParam<Real>("K_proportional")),
      73          84 :     _Kder(getParam<Real>("K_derivative")),
      74          84 :     _start_time(getParam<Real>("start_time")),
      75          84 :     _stop_time(getParam<Real>("stop_time")),
      76          84 :     _reset_every_timestep(getParam<bool>("reset_every_timestep")),
      77          84 :     _reset_integral_windup(getParam<bool>("reset_integral_windup")),
      78          84 :     _maximum_output_value(getParam<Real>("maximum_output_value")),
      79          84 :     _minimum_output_value(getParam<Real>("minimum_output_value")),
      80          84 :     _maximum_change_rate(getParam<Real>("maximum_change_rate")),
      81          84 :     _integral(declareRestartableData<Real>("pid_integral", 0)),
      82          84 :     _integral_old(declareRestartableData<Real>("pid_integral_old", 0)),
      83          84 :     _value(declareRestartableData<Real>("pid_value", 0)),
      84          84 :     _value_old(declareRestartableData<Real>("pid_value_old", 0)),
      85          84 :     _t_step_old(declareRestartableData<int>("pid_tstep_old", -1)),
      86          84 :     _old_delta(declareRestartableData<Real>("pid_delta_old", 0)),
      87          84 :     _has_recovered(false)
      88             : {
      89          84 :   if (!_fe_problem.isTransient())
      90           0 :     mooseError("PIDTransientControl is only meant to be used when the problem is transient, for "
      91             :                "example with a Transient Executioner. Support for Steady "
      92             :                "Executioner can be added in the future, however certain parameters are currently "
      93             :                "not well defined for use with Picard iterations.");
      94             : 
      95          84 :   if (isParamValid("parameter") && isParamValid("parameter_pp"))
      96           0 :     paramError("parameter_pp",
      97             :                "Either a controllable parameter or a postprocessor to control should be specified, "
      98             :                "not both.");
      99          84 :   if (!isParamValid("parameter") && !isParamValid("parameter_pp"))
     100           0 :     mooseError("A parameter or a postprocessor to control should be specified.");
     101          84 :   if (isParamValid("parameter") && _reset_every_timestep)
     102           0 :     paramError(
     103             :         "reset_every_timestep",
     104             :         "Resetting the PID every time step is only supported using controlled postprocessors");
     105          84 :   if (_maximum_output_value <= _minimum_output_value)
     106           0 :     mooseError(
     107             :         "The parameters maximum_output_value and minimum_output_value are inconsistent. The value "
     108             :         "of maximum_output_value should be greater than the value of minimum_output_value.");
     109          84 : }
     110             : 
     111             : void
     112        1513 : PIDTransientControl::execute()
     113             : {
     114             :   // Dont execute on INITIAL again on a recover.
     115             :   // Executing on INITIAL in a regular simulation is fine, but on a recover we will get the integral
     116             :   // term wrong and set the wrong attributes for the time derivative too. Best to skip
     117        1513 :   if (_app.isRecovering() && _fe_problem.getCurrentExecuteOnFlag() == EXEC_INITIAL)
     118             :   {
     119           6 :     mooseInfo("Skipping execution on recover + INITIAL.");
     120           6 :     return;
     121             :   }
     122             : 
     123        1507 :   if (_t >= _start_time && _t < _stop_time)
     124             :   {
     125             :     // Get the current value of the controllable parameter
     126        1507 :     if (isParamValid("parameter"))
     127             :     {
     128             :       // If just recovering, we must use the restartable value as the parameter value is
     129             :       // not restarted
     130         814 :       if (_app.isRecovering() && !_has_recovered)
     131           4 :         _has_recovered = true;
     132             :       // else get the current value of the parameter
     133             :       else
     134         810 :         _value = getControllableValue<Real>("parameter");
     135             :     }
     136             :     else
     137         693 :       _value = getPostprocessorValueByName(getParam<std::string>("parameter_pp"));
     138             : 
     139             :     // Save integral and controlled value at each time step
     140             :     // if the solver fails, a smaller time step will be used but _t_step is unchanged
     141        1507 :     if (_t_step != _t_step_old)
     142             :     {
     143             :       // Reset the error integral if PID is only used within each timestep
     144        1496 :       if (_reset_every_timestep)
     145         231 :         _integral = 0;
     146             : 
     147        1496 :       _integral_old = _integral;
     148        1496 :       _value_old = _value;
     149        1496 :       _t_step_old = _t_step;
     150             :     }
     151             : 
     152             :     // If there were coupling/Picard iterations during the transient and they failed,
     153             :     // we need to reset the controlled value and the error integral to their initial value at the
     154             :     // beginning of the coupling process
     155        1507 :     if (_app.getExecutioner()->fixedPointSolve().numFixedPointIts() == 1)
     156             :     {
     157        1507 :       _integral = _integral_old;
     158        1507 :       _value = _value_old;
     159             : 
     160             :       // Note we do not restore _old_delta because we dont have a way to keep track of it at the
     161             :       // moment. And we really need to, it comes in the time derivative term. In the future, we will
     162             :       // reset restartableData on new fixed point iterations.
     163             :     }
     164             : 
     165             :     // Compute the delta between the current value of the postprocessor and the desired value
     166        1507 :     Real delta = _current - _target.value(_t);
     167             : 
     168             :     // If desired, reset integral of the error if the error crosses zero
     169        1507 :     if (_reset_integral_windup && delta * _old_delta < 0)
     170        1023 :       _integral = 0;
     171             : 
     172             :     // Compute the three error terms and add them to the controlled value
     173        1507 :     _integral += delta * _dt;
     174        1507 :     _value += _Kint * _integral + _Kpro * delta;
     175        1507 :     if (_dt > 0)
     176        1441 :       _value += _Kder * (delta - _old_delta) / _dt;
     177             : 
     178             :     // If the maximum rate of change by the pid is fixed
     179        1507 :     if (_maximum_change_rate != std::numeric_limits<Real>::max())
     180         231 :       _value = std::min(std::max(_value_old - _dt * _maximum_change_rate, _value),
     181         462 :                         _value_old + _dt * _maximum_change_rate);
     182             : 
     183             :     // Compute the value, within the bounds
     184        1507 :     _value = std::min(std::max(_minimum_output_value, _value), _maximum_output_value);
     185             : 
     186             :     // Set the new value of the postprocessor
     187        1507 :     if (isParamValid("parameter"))
     188         814 :       setControllableValue<Real>("parameter", _value);
     189             :     else
     190         693 :       _fe_problem.setPostprocessorValueByName(getParam<std::string>("parameter_pp"), _value);
     191             : 
     192             :     // Keep track of the previous delta for integral windup control
     193             :     // and for time derivative calculation
     194        1507 :     _old_delta = delta;
     195             :   }
     196             : }

Generated by: LCOV version 1.14