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