Engee documentation
Notebook

Automated adjustment of the regulator

Introduction

Setting up a regulator for a control system is a classic task that is studied as part of a TAU course. However, all the methods that are studied in this course take a lot of time. In this publication, we will look at the application of optimization algorithms to speed up and automate control settings.

We will use the stepfun model for the demonstration.:

image.png

Preparatory work

We will need two libraries: ControlSystemBase and Optim. Let's install them:

In [ ]:
import Pkg
Pkg.add(["ControlSystemsBase","Optim"])
   Resolving package versions...
   Installed ControlSystems ─ v1.15.4
Warning: detected a stack overflow; program state may be corrupted, so further execution might be unreliable.
     Project No packages added to or removed from `~/.project/Project.toml`
    Manifest No packages added to or removed from `~/.project/Manifest.toml`

Now, let's create a function to simulate the model. The simulation results will be passed to the stepinfo() function from ControlSystemBase. This function calculates the characteristics of the transition process and generates a StepInfo type object that can be visualized. We will return such an object:

In [ ]:
gr()
engee.load(joinpath(@__DIR__,"stepfun.engee"))
using ControlSystemsBase
function sim_parameterized()
    simres = engee.run("stepfun")
    y = collect(simres["y"]);
    u = collect(simres["u"]);
    yv = reshape(y.value,(1,length(y.value)))
    rt = ControlSystemsBase.SimResult(yv,y.time,yv,u.value,ss(tf([1.0],[1.0, 0.0])))
    si = stepinfo(rt)
    return si
end
Out[0]:
sim_parameterized (generic function with 1 method)

Getting started

Let's look at how the system works with the default settings of the controller.:

In [ ]:
    engee.set_param!("stepfun/reg","P"=>1.0)
    engee.set_param!("stepfun/reg","I"=>1.0)
    si = sim_parameterized()
    ref = plot(si)
    display(ref)
No description has been provided for this image

Let's explore the system by iterating through the parameters

Iterating through parameters is a fairly common method for investigating the effect of a parameter on the behavior of a system. The disadvantage of this method is that it takes a long time to iterate through the parameters. Even if we parallelize the search, we will wait an unacceptably long time for the result on large samples. And if the parameter varies in large leaps, the quality of the analysis will decrease. But even this can help in our task. Let's see what effect a change in the gain factor in the integrating link of the regulator will have.:

In [ ]:
par_variation_l = 10;
param = collect(range(0.5,4.5,par_variation_l));
In [ ]:
using ControlSystemsBase
# mySystem = feedback(tf([1.0],[1.0,1.0])*tf([1.0],[1.0, 0.0]))

K = 1.0

overshoot = zeros(par_variation_l);
setting_time = zeros(par_variation_l);
rise_time = zeros(par_variation_l);

for i in 1:par_variation_l
    engee.set_param!("stepfun/reg","I"=>"$(param[i])")
    si = sim_parameterized()
    overshoot[i] = si.overshoot;
    setting_time[i] = si.settlingtime;
    rise_time[i] = si.risetime;
end
Warning: detected a stack overflow; program state may be corrupted, so further execution might be unreliable.

Let's examine the results of iterating through the parameters

The easiest way to explore any data is to look at it through their eyes. Let's look at the overshoot (overshoot), rise time (rise_time), and setpoint time (setting_time) graphs.

In [ ]:
p1 = plot(param, overshoot,label="overshoot")
p2 = plot(param,rise_time,color=:red,label="rise_time")
p3 = plot(param,setting_time,color=:green,label="setting_time")
plot(p1,p2,p3,layout=(3,1))
Out[0]:
No description has been provided for this image

What matters here is not quantitative results, but how the system behaves. We see that as the gain in the integration link increases, overshoot and setpoint time increase. Thus, we can say that the desired optimal coefficient should be less than 1.5. If we carry out such an analysis for the starting link, then we will also be able to put forward a requirement for its reinforcement coefficient.

Automatic adjustment of the regulator parameters

We will use optimization methods to automatically adjust the regulator.

To apply optimization, you need to create an objective function, which we will look for the minimum. Let's use the StepInfo object to get the characteristics of the transient process from the results, and the function will return the sum of these characteristics multiplied by the weights. Weights are assigned based on the requirements for the transition process. Let's say we want to reduce the setpoint time while minimizing overshoot, but the rise time is not important to us. Then, our objective function will take the form:

In [ ]:
function cost_fun(p::Vector{Float64})
    
    engee.set_param!("stepfun/reg","P"=>p[1])
    engee.set_param!("stepfun/reg","I"=>p[2])
    si = sim_parameterized()
    
    return 2*si.overshoot + 0.5*si.risetime + 10*si.settlingtime
end
Out[0]:
cost_fun (generic function with 1 method)

Before you can start optimization and adjust the controller, you need to adjust the optimization itself. We want to limit the search for parameters, since we found out that the parameters have values less than 1.5. And their lower bound will be 0.5. Next, we need to limit the algorithm itself.

The optimization task will use two algorithms at once - Fminbox as the "external" one, and gradient descent as the "internal" one. At each step of the external algorithm, several steps of the internal algorithm will be run, and if we do not limit the number of steps of both algorithms, then optimization will take a long time or even freeze. For a more detailed description of the settings, refer to the documentation.

After completing all the settings, we will start optimization.:

In [ ]:
using Optim
lowbound = [0.5, 0.5];
highbound = [1.5, 1.5];
opt = Optim.Options(
	iterations = 5,
	outer_iterations = 25,
	f_abstol = 1e-5,
	f_reltol = 1e-2,
	show_trace = true);
res = optimize(cost_fun, lowbound, highbound, [1.0, 1.0], Fminbox(GradientDescent()),opt)
Warning: detected a stack overflow; program state may be corrupted, so further execution might be unreliable.
Fminbox
-------
Initial mu = 0.0

Fminbox iteration 1
-------------------
Calling inner optimizer with mu = 0.0

(numbers below include barrier contribution)
Iter     Function value   Gradient norm 
     0     2.248455e+02     8.399782e+01
 * time: 0.037448883056640625
Warning: detected a stack overflow; program state may be corrupted, so further execution might be unreliable.
Warning: detected a stack overflow; program state may be corrupted, so further execution might be unreliable.
     1     5.645585e+01     3.724740e+01
 * time: 139.6843729019165
Warning: detected a stack overflow; program state may be corrupted, so further execution might be unreliable.
     2     5.495090e+01     8.098034e+01
 * time: 207.34265685081482
Warning: detected a stack overflow; program state may be corrupted, so further execution might be unreliable.
Warning: detected a stack overflow; program state may be corrupted, so further execution might be unreliable.
Warning: detected a stack overflow; program state may be corrupted, so further execution might be unreliable.
     3     5.494912e+01     4.866916e+02
 * time: 362.8312578201294
     4     5.494560e+01     7.379368e+01
 * time: 379.5791518688202
Warning: detected a stack overflow; program state may be corrupted, so further execution might be unreliable.
Exiting inner optimizer with x = [1.340378007208218, 0.5002145746699308]
Current distance to box: 0.000214575
Decreasing barrier term μ.
Out[0]:
 * Status: success

 * Candidate solution
    Final objective value:     5.478000e+01

 * Found with
    Algorithm:     Fminbox with Gradient Descent

 * Convergence measures
    |x - x'|               = 5.00e-01 ≰ 0.0e+00
    |x - x'|/|x'|          = 3.73e-01 ≰ 0.0e+00
    |f(x) - f(x')|         = 1.70e+02 ≰ 1.0e-05
    |f(x) - f(x')|/|f(x')| = 3.10e+00 ≰ 1.0e-02
    |g(x)|                 = 0.00e+00 ≤ 1.0e-08

 * Work counters
    Seconds run:   401  (vs limit Inf)
    Iterations:    1
    f(x) calls:    23
    ∇f(x) calls:   23
    ∇f(x)ᵀv calls: 0

The following values were found.

In [ ]:
optimal_params = Optim.minimizer(res)
Out[0]:
2-element Vector{Float64}:
 1.340378007208218
 0.5002145746699308

Let's build graphs of transients for the initial and configured regulators.:

In [ ]:
    engee.set_param!("stepfun/reg","P"=>optimal_params[1])
    engee.set_param!("stepfun/reg","I"=>optimal_params[2])
    si = sim_parameterized()
    fin = plot(si)
    plot(ref,fin,layout=(2,1))
Out[0]:
No description has been provided for this image

Conclusions

We looked at the process of automating the adjustment of the regulator. By iterating through the parameters, the boundaries within which the parameters of the regulator lie were obtained, and then their exact values were found.