Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integral Windup #76

Open
terryjmyers opened this issue Apr 16, 2018 · 25 comments
Open

Integral Windup #76

terryjmyers opened this issue Apr 16, 2018 · 25 comments

Comments

@terryjmyers
Copy link

If Output is maxed due to a very large error (like when the controller first starts), it looks like Integral will continue to wind up (outputSum increasing) until it ALSO hits the outMax. This woudl then require outMax worth of integral needing to be integrated away. Why is the integral term processed at all when the output is clamped at either end?

Isn't this what is required (line 69):
if (output< outMax && output > outMin) outputSum+= (ki * error);
I realize it will be evaluating the LAST iterations output, but this is probably good enough.

@br3ttb
Copy link
Owner

br3ttb commented Apr 16, 2018 via email

@terryjmyers
Copy link
Author

In my scenario above there was no room for the output to move, in other words the output would be clamped at Max. However the integral some would continue to increment which doesn't seem desirable

@terryjmyers
Copy link
Author

In my testing I'm getting a much more desirable response (less overshoot) by not letting the integral term increment when the output is at 100:

	   if (*myOutput < outMax ) {
		   if (ki != 0) outputSum += kp * (1 / ki * error);
	   }

(Note I switched to dependant gain sets, but its basically don't sum when *myOutput < outMax).
I mean..the way the code is right now, I basically gets to max, even while P is doing all of the heavy lifting at the start of a process.
Also note that my process is mostly an integrating process (reflow oven with as little heat loss as possible).

@br3ttb
Copy link
Owner

br3ttb commented Apr 17, 2018 via email

@mike-matera
Copy link

I have something to add to this conversation. I've been validating my PID controller by random coefficient testing. The testing has helped my tighten up my code and I want to repay all the help you've given me.

Here's a case where ArduinoPID varies significantly from my reference implementation:

arduinopid-p2 423-i2 148-d2 075

What's happening in the graph is that the initial step causes a big shift in the output because the coefficients are crazy. In the reference implementation there is no limit on the integral sum and the integral winds up and it takes several steps under the setpoint to wind down.
I've traced through your code and found that what's happening is that the integral windup is clamped to the output limits.

https://github.com/br3ttb/Arduino-PID-Library/blob/master/PID_v1.cpp#L74

      if(outputSum > outMax) outputSum= outMax;
      else if(outputSum < outMin) outputSum= outMin;

This means that ArduinoPID will forget a large error quickly, perhaps too quickly. I think you might consider making the integral windup limit separate from the output limit so that the integral can have a bit longer memory for large errors, especially since you have a floating point number to work with.

@terryjmyers
Copy link
Author

Yes this makes sense. My whole issue with it was that when there is a large error (on startup) its appropriate to let P be the main driving force. This is why I didn't want any I at all.
But its entirely appropriate for I to start doing SOMETHING..as its going to be needed in the end anyway. I just didn't want it ramping up to 100%. I think ideally you would want to clamp it at what the output WILL be IF you could somehow know the future. I.E. Output will eventually be 40% and stable, therefore let I get up to 40% at the start even while P is maxed out just trying to get up to the SP in the first place. Something like that.

@mike-matera
Copy link

We should be careful when we talk about what the PID controller should do. What it should do is follow the equations we all know and understand even if that makes it unfit for a particular circumstance or condition. Inside of that @br3ttb has software engineering decisions to make, like whether and how to clamp integral windup, which is a feature of many controllers.

Try commenting out the lines that I highlighted. If that helps your use case you have a need for more flexibility with integral windup. If not you might consider using a feed-forward controller to minimize first-step errors.

@madhephaestus
Copy link

Just thinking about the math in terms of what its supposed to represent, the Integral should be cleared when the error changes from negative to positive, or from positive to negative. This allows for the integral tuning term to be high enough for the integral to be useful, while at the same time preventing the integral oscillation the occurs when the offset is large for a long time. If a PR would be welcome, i would be happy to add this feature.

@SimoDax
Copy link

SimoDax commented Dec 22, 2018

I've just needed to check if anti-windup was implemented in this library because of a project so here's my thought about the thread:

Windup mitigation works by limiting the integral to the maximum output while allowing it to begin decreasing as soon as the error sign changes, and clamping is a simple way of doing it. This also implies that if the Proportional term is high, which usually happens on startup (like @terryjmyers has experienced), the output will be clamped but the integral will not, because the same threshold outMax is used to limit both terms (and rightfully so, there's no windup in the current implementation).
As an example, there may be cases such as this: once the controlled system is steady and very close to setpoint level, the output is only the Integral, so it must be allowed to grow up to outMax if needed. If you prevent its growth when the output is clamped (i.e. when P is still acting) then it will need to do it when it's unclamped (i.e. when P has become smaller), slowing down the controller and degrading its dynamic performance. The benefit, in the OP particular case, come from the fact that you know your system and built a "custom" controller for it. Not all system are equal though

      Just thinking about the math in terms of what its supposed to represent, the Integral should be cleared when the error changes from negative to positive, or from positive to negative. This allows for the integral tuning term to be high enough for the integral to be useful, while at the same time preventing the integral oscillation the occurs when the offset is large for a long time. If a PR would be welcome, i would be happy to add this feature.

If you clear the integral on error sign change it's not a PID anymore, since you're suddently forgetting all the system history. I agree with @mike-matera, controllers are built on a strong math basis and messing with them according to convenience can backfire, unless they're made from scratch to fit a particular application

@br3ttb
Copy link
Owner

br3ttb commented Dec 23, 2018 via email

@tablatronix
Copy link

tablatronix commented Jun 10, 2020

I was having the same problem, what does it mean if the controller is still applying effort after setpoint for a large pulse with integration?

How can I do integral resets? to see if its a windup issue?

@riozebratubo
Copy link

I would be nice to have an API to tinker with the internal calculations and one solely to reset the pid.

@tablatronix
Copy link

I wound up just hacking in an integrator reset at 75% in my loop, calling SetTunings, ideally i should be using a smith predictor, but this is working for now

@br3ttb
Copy link
Owner

br3ttb commented Aug 22, 2020 via email

@tablatronix
Copy link

I am doing that also for startup for example, but i had a problem using it for this for some reason, of course I forget what...
I will try it again and see what it was, i didn't comment it..

@Feargus
Copy link

Feargus commented Sep 21, 2020

I have a similar issue. A large set point change on initial start up, leads to integral windup.

Windup mitigation is definitely implemented in the library

Here http://brettbeauregard.com/blog/2011/04/improving-the-beginner%E2%80%99s-pid-reset-windup/ you explain your anti-windup, but I don't think it takes care of all cases. Particularly in this case, mentioned in the first post, it does nothing. In the above link, the second to last comment says the same as the person opening this ticket, and the same as me.

I think there's confusion as to what it means for the integral term to "wind up." wind up means that the integral term is growing even when the output isn't changing

For example, in the attached image, output is clamped at 100, yet the I-term accumulates, even when the output is clamped.
It is immediately obvious, that this effect is stronger, if the initial setpoint (temperature) were higher, since it would take longer to reach. Conversely, if the initial setpoint were slightly lower, output would never have to be clamped, and everything would work "normally".

One could solve this with a simple hack, to reset the i-term once you approach the setpoint, but that is simply not how it is supposed to be done, I agree.

But I don't understand why it is happening here, and why it is allowed to continue?
In the worst, case, the I-term accumulates up to 100, which is simply ridiculous.

Conversely, when other implementations mention "clamping", they also talk about "conditional integration", which I understand to mean: "Only integrate if the output can be changed", just as you said above

For example here (ctrl+f "conditional integration")
https://www.mathworks.com/help/simulink/slref/anti-windup-control-using-a-pid-controller.html
or here
https://www.scilab.org/pid-anti-windup-schemes

So in summary: If I am not mistaken, you say that the integral term should not increase if the output cannot be changed. That can be achieved by conditional integration. Yet you did not implement conditional integration. Just clamping the I-term to stop windup in all cases, as can be seen from the plot

I am just trying understand where the error lies. It may very well be my understanding of PID controllers in general, or of your implementation in particular.

Could you please elaborate on why you did not implement conditional integration?
Is this windup on startup expected behaviour?

Thank you for your time

R1-pid

@br3ttb
Copy link
Owner

br3ttb commented Sep 21, 2020 via email

@Feargus
Copy link

Feargus commented Sep 22, 2020

I'm sorry to insist, but it seems we are not completely understanding each other.

The integral action should not grow once the limit is reached

Which limit do you mean? I agree, if you mean "Once the output reaches the limit (<=> P+I+D > limit)". If you mean something else (for example I-term > limit) please say so, so I can understand where I am wrong.

If you're referring to the first two images in my post

I was referring to the image I attached to my post. 3 subplots. In the first, the P, I, D-terms are plotted. In the second, the output (= their sum, clamped to 0...100). The last plots the control value, a temperature, as well as the temperature setpoint. I attached the image, since earlier in these posts, you were asking for data

Can you show plots before and after

I only have the one, which is illustrating the same problem that the thread-starter has. A needlessly large I-term, due to a (too) large setpoint change on startup.

If you look at the first subplot of the image I posted earlier, the I-term (in green) is growing, even though the P-term is >100, and the output is clamped at 100. So the I-term has no effect, yet is growing

Why is this allowed to happen in your implementation? Why did you not implement conditional integration (= only allow changes to the I-term if the output is not clamped)
Does this simple change have any disadvantages I am not seeing? It's not like This is something new, or uncommon (see the 2 references above)
In my example, it would allow the output to decrease earlier, since there is no needlessly large I-term that has to be unwound, and some of the overshoot could be avoided.

The latter can generally be solved by [...] using something instead of/in addition to pid.

Yes, but it can also simply be solved by "conditional integration".

I am simply trying to understand if your opinion is:

  1. "You are wrong, conditional integration solves nothing" (That way, at least I learn something)
  2. "I refuse to implement conditional integration because of X" (So I can let this rest, and look for another implementation)
  3. "Oh, okay"

Thanks again.

@br3ttb
Copy link
Owner

br3ttb commented Sep 22, 2020 via email

@Feargus
Copy link

Feargus commented Oct 9, 2020

In regards to limiting, the library does it twice. The I term is clamped (to prevent windup)

I understand. What I am trying to say, that clamping can prevent some windup, It does not prevent as much as other, simple methods.
In the case discussed here, the I-term increases even though ideally it should not (because the controller output is already at the maximum, and will not increase anyway)
In this case (on an initial large setpoint change), a better option than simple clamping is conditional integration, as described above.

@tablatronix
Copy link

I think this is the issue I was having, not just overshoot, actual power being applied way after target was reached. Which should not happen in a pid controller.

@kjyv
Copy link

kjyv commented Jun 23, 2022

It seems no resolution to this issue has been found in the long time since most of the discussion has happened. I'm also not happy with the current anti-windup code in the library. It will only be effective with very large windup. There are other methods though that try to prevent windup earlier, see also https://en.wikipedia.org/wiki/Integral_windup.
Why not simply add various options for common features in advanced PID controllers and let the user decide? There are a few methods for anti-windup, some are suggested here, some not. The user might want to enable one or more of these:

  • the suggested method of not changing the integrator if the output (before clamping) is outside of the allowed output range
  • reset the integrator to zero on error zero-crossings
  • limit the integrator to a smaller (user settable) range, separate from the output range so that wind down is faster
  • only allow the integrator to change if within a smaller (user settable) error range around the setpoint. This way, the I part only starts building up shortly before or when it becomes necessary to reduce the remaining offset of the P part
  • (possibly others)

All of these need to be understood and decided for by the user of the library of course, but it would be great if a general purpose PID library already contains them. By default they can stay disabled so the behaviour will not change for backwards compatibility.

Also a good candidate for an optional feature I think is low-pass filtering the input for the derivative (but not filtering the overall input, so it can't be added from the code that uses the unchanged library). It currently is simply using the current and previous input value, which is usually not enough for noisy sensor data and will add jumpy D part contributions to the output. The filter could be a simple to implement low-pass implementation in the library (e.g. the exponential moving average like filtered = k * filtered + (1-k)*newValue with k e.g. 0.99) but also optionally allow for passing in a filter function pointer for custom filters. This would also benefit the PonM mode. I could supply a PR for some of these if there is interest by @br3ttb to add optional features to the library.

@drf5n
Copy link

drf5n commented Feb 26, 2023

Just thinking about the math in terms of what its supposed to represent, the Integral should be cleared when the error changes from negative to positive, or from positive to negative. This allows for the integral tuning term to be high enough for the integral to be useful, while at the same time preventing the integral oscillation the occurs when the offset is large for a long time. If a PR would be welcome, i would be happy to add this feature.

The integration should not be set ot zero on zero crossings. Consider tiny fluctuations around zero error: the proportional contribution is negligible, the derivative term is negligible, so the only term driving output would be the integral term. If you are tryiing to operate far from SP=0, you will produce a sawtooth as the integral sums back up to deliver the ControlOutput.

@drf5n
Copy link

drf5n commented Feb 26, 2023

There are other methods though that try to prevent windup earlier, see also https://en.wikipedia.org/wiki/Integral_windup. Why not simply add various options for common features in advanced PID controllers and let the user decide? There are a few methods for anti-windup, some are suggested here, some not. The user might want to enable one or more of these:

  • the suggested method of not changing the integrator if the output (before clamping) is outside of the allowed output range
  • reset the integrator to zero on error zero-crossings
  • limit the integrator to a smaller (user settable) range, separate from the output range so that wind down is faster
  • only allow the integrator to change if within a smaller (user settable) error range around the setpoint. This way, the I part only starts building up shortly before or when it becomes necessary to reduce the remaining offset of the P part
  • (possibly others)

The names for these are:

The main "possibly others" is back-calculation, essentially dynamically limiting the integral to the un-saturated portion of the CO. If you are starting far from the proportional zone, where error > CO_max/kP , then error * kP > CO_max and saturates the system by itself. Back-calculation would inhibit the integral term from contributing to the Control Output until you get into the proportional zone where there's some slack between CO_max and error*kP. https://folk.ntnu.no/skoge/prost/proceedings/PID-2018/0061.PDF and https://www.cds.caltech.edu/~murray/courses/cds101/fa02/caltech/astrom-ch6.pdf are good references. The old pneumatic controller's integrators essentially had this dynamic limit built-in because their pneumatic memory couldn't integrate beyond the Control Output pressure because it was fed by the control output pressure.

github-actions bot pushed a commit to arduino/library-registry that referenced this issue Feb 26, 2023
The PID_v1_bc library is a fork of the https://github.com/br3ttb/Arduino-PID-Library /  PID / PID_v1.h library that adds the back-calculation method of integral windup control.  See https://github.com/drf5n/Arduino-PID-Library and br3ttb/Arduino-PID-Library#116 and br3ttb/Arduino-PID-Library#76 (comment) for more info.
@drf5n
Copy link

drf5n commented Apr 22, 2023

I would be nice to have an API to tinker with the internal calculations and one solely to reset the pid.

You can get the tinkering ability by moving outputSum from private: to public:

PR #133

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

10 participants