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 : }
|