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 14447 : PIDTransientControl::validParams()
19 : {
20 14447 : InputParameters params = Control::validParams();
21 14447 : 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 14447 : params.addRequiredParam<PostprocessorName>(
25 : "postprocessor", "The postprocessor to watch for controlling the specified parameter.");
26 14447 : params.addRequiredParam<FunctionName>("target",
27 : "The target value 1D time function for the postprocessor");
28 14447 : params.addRequiredParam<Real>("K_integral", "The coefficient multiplying the integral term");
29 14447 : params.addRequiredParam<Real>("K_proportional",
30 : "The coefficient multiplying the difference term");
31 14447 : params.addRequiredParam<Real>("K_derivative", "The coefficient multiplying the derivative term");
32 14447 : 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 14447 : 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 43341 : params.addParam<Real>(
40 28894 : "start_time", -std::numeric_limits<Real>::max(), "The time to start the PID controller at");
41 43341 : params.addParam<Real>(
42 28894 : "stop_time", std::numeric_limits<Real>::max(), "The time to stop the PID controller at");
43 43341 : params.addParam<bool>(
44 : "reset_every_timestep",
45 28894 : false,
46 : "Reset the PID integral when changing timestep, for coupling iterations within a timestep");
47 43341 : params.addParam<bool>("reset_integral_windup",
48 28894 : true,
49 : "Reset the PID integral when the error crosses zero and the integral is "
50 : "larger than the error.");
51 :
52 43341 : params.addParam<Real>("maximum_output_value",
53 28894 : std::numeric_limits<Real>::max(),
54 : "Can be used to limit the maximum value output by the PID controller.");
55 43341 : params.addParam<Real>("minimum_output_value",
56 28894 : -std::numeric_limits<Real>::max(),
57 : "Can be used to limit the minimum value output by the PID controller.");
58 43341 : params.addRangeCheckedParam<Real>(
59 : "maximum_change_rate",
60 28894 : 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 14447 : return params;
65 0 : }
66 :
67 91 : PIDTransientControl::PIDTransientControl(const InputParameters & parameters)
68 : : Control(parameters),
69 91 : _current(getPostprocessorValueByName(getParam<PostprocessorName>("postprocessor"))),
70 91 : _target(getFunction("target")),
71 91 : _Kint(getParam<Real>("K_integral")),
72 91 : _Kpro(getParam<Real>("K_proportional")),
73 91 : _Kder(getParam<Real>("K_derivative")),
74 91 : _start_time(getParam<Real>("start_time")),
75 91 : _stop_time(getParam<Real>("stop_time")),
76 91 : _reset_every_timestep(getParam<bool>("reset_every_timestep")),
77 91 : _reset_integral_windup(getParam<bool>("reset_integral_windup")),
78 91 : _maximum_output_value(getParam<Real>("maximum_output_value")),
79 91 : _minimum_output_value(getParam<Real>("minimum_output_value")),
80 91 : _maximum_change_rate(getParam<Real>("maximum_change_rate")),
81 91 : _integral(declareRestartableData<Real>("pid_integral", 0)),
82 91 : _integral_old(declareRestartableData<Real>("pid_integral_old", 0)),
83 91 : _value(declareRestartableData<Real>("pid_value", 0)),
84 91 : _value_old(declareRestartableData<Real>("pid_value_old", 0)),
85 91 : _t_step_old(declareRestartableData<int>("pid_tstep_old", -1)),
86 91 : _old_delta(declareRestartableData<Real>("pid_delta_old", 0)),
87 91 : _has_recovered(false)
88 : {
89 91 : 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 91 : 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 91 : if (!isParamValid("parameter") && !isParamValid("parameter_pp"))
100 0 : mooseError("A parameter or a postprocessor to control should be specified.");
101 91 : 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 91 : 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 91 : }
110 :
111 : void
112 1657 : 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 1657 : if (_app.isRecovering() && _fe_problem.getCurrentExecuteOnFlag() == EXEC_INITIAL)
118 : {
119 6 : mooseInfo("Skipping execution on recover + INITIAL.");
120 6 : return;
121 : }
122 :
123 1651 : if (_t >= _start_time && _t < _stop_time)
124 : {
125 : // Get the current value of the controllable parameter
126 1651 : if (isParamValid("parameter"))
127 : {
128 : // If just recovering, we must use the restartable value as the parameter value is
129 : // not restarted
130 892 : if (_app.isRecovering() && !_has_recovered)
131 4 : _has_recovered = true;
132 : // else get the current value of the parameter
133 : else
134 888 : _value = getControllableValue<Real>("parameter");
135 : }
136 : else
137 759 : _value = getPostprocessorValueByName(getParam<std::string>("parameter_pp"));
138 :
139 : // Compute the delta between the current value of the postprocessor and the desired value
140 1651 : Real delta = _current - _target.value(_t);
141 :
142 : // Save integral and controlled value at each time step
143 : // if the solver fails, a smaller time step will be used but _t_step is unchanged
144 1651 : if (_t_step != _t_step_old)
145 : {
146 : // Reset the error integral if PID is only used within each timestep
147 1632 : if (_reset_every_timestep)
148 252 : _integral = 0;
149 :
150 1632 : _integral_old = _integral;
151 1632 : _value_old = _value;
152 1632 : _t_step_old = _t_step;
153 1632 : _delta_prev_tstep = delta;
154 1632 : _old_delta_prev_tstep = _old_delta;
155 : }
156 :
157 : // If there were coupling/Picard iterations during the transient and they failed,
158 : // we need to reset the controlled value and the error integral to their initial value at the
159 : // beginning of the coupling process
160 1651 : if (_app.getExecutioner()->fixedPointSolve().numFixedPointIts() == 1)
161 : {
162 1651 : _integral = _integral_old;
163 1651 : _value = _value_old;
164 1651 : delta = _delta_prev_tstep;
165 1651 : _old_delta = _old_delta_prev_tstep;
166 : }
167 :
168 : // If desired, reset integral of the error if the error crosses zero
169 1651 : if (_reset_integral_windup && delta * _old_delta < 0)
170 1157 : _integral = 0;
171 :
172 : // Compute the three error terms and add them to the controlled value
173 1651 : _integral += delta * _dt;
174 1651 : _value += _Kint * _integral + _Kpro * delta;
175 1651 : if (_dt > 0)
176 1579 : _value += _Kder * (delta - _old_delta) / _dt;
177 :
178 : // If the maximum rate of change by the pid is fixed
179 1651 : if (_maximum_change_rate != std::numeric_limits<Real>::max())
180 253 : _value = std::min(std::max(_value_old - _dt * _maximum_change_rate, _value),
181 506 : _value_old + _dt * _maximum_change_rate);
182 :
183 : // Compute the value, within the bounds
184 1651 : _value = std::min(std::max(_minimum_output_value, _value), _maximum_output_value);
185 :
186 : // Set the new value of the postprocessor
187 1651 : if (isParamValid("parameter"))
188 892 : setControllableValue<Real>("parameter", _value);
189 : else
190 759 : _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 1651 : _old_delta = delta;
195 : }
196 : }
|