# Handling of saturated integrator with anti-windeup

**URL:** <https://discourse.julialang.org/t/handling-of-saturated-integrator-with-anti-windeup/124819>\
**Category:** Modelling & Simulations\
**Tags:** diffeq, modelingtoolkit\
**Created:** [January 16, 2025, 9:22am UTC](https://discourse.julialang.org/t/handling-of-saturated-integrator-with-anti-windeup/124819 "2025-01-16T09:22:11Z")\
**Posts on this page:** 8\
**Page:** 1

<div class="post-metadata">

**Author:** ![hexaeder](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/hexaeder/32/24403_2.png) [@hexaeder](https://discourse.julialang.org/u/hexaeder)\
**Post date:** [January 16, 2025, 9:22am UTC](https://discourse.julialang.org/t/handling-of-saturated-integrator-with-anti-windeup/124819/1 "2025-01-16T09:22:11Z")

</div>

I want to model a saturated integrator

T\dot{u} ~ = r\_\mathrm{ref}-u

in which u follows r\_\mathrm{ref} but is also bounded to stay within u\in(u\_\mathrm{min}, u\_\mathrm{max}). To prevent windeup effects, the integrator state u should not grow above or below the limits.

One approach is to do something like this

```julia
D(u) ~ insat * (ref - u)

```

and then switch parameter `insat` in a callback between 0 and 1 depending on `u`, `u_max` and `u_min`.

From a ODE solver perspective, is this equivalent to defining

```julia
D(u) ~ ifelse(((u > u_max) & (ref > u)) | ((u < u_min) & (ref < u)), 0, ref - u)

```

with a callback, which forces rootfinding on `u==u_max` and `u==u_min` with empty `affect!`? Or is `ifelse` bad because the root-finding algo might step on both sides of the discontinuity?

Are there any other simple ways of implementing this that I am overlooking? The problem seems fairly standard in control context. For example, is there a smooth approximation of the system which is used commonly?  
I looked at the `LimPI` implementation in `ModelingToolkitStandardLibrary,` but it does not seem to include any explicit handling of the discontinuity.

---

<div class="post-metadata">

**Author:** ![baggepinnen](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/baggepinnen/32/693_2.png) [@baggepinnen](https://discourse.julialang.org/u/baggepinnen)\
**Post date:** [January 16, 2025, 2:10pm UTC](https://discourse.julialang.org/t/handling-of-saturated-integrator-with-anti-windeup/124819/2 "2025-01-16T14:10:53Z")

</div>

There is work ongoing to detect the discontinuity and handle it automatically

> **[test: add tests for if-lifting · SciML/ModelingToolkit.jl@838ad80](https://github.com/SciML/ModelingToolkit.jl/commit/838ad8034b83bd7e935b64c31e034ecd94bd0fff)**
>
> An acausal modeling framework for automatically parallelized scientific machine learning (SciML) in Julia. A computer algebra system for integrated symbolics for physics-informed machine learning and automated transformations of differential...

> [@hexaeder](#):
>
> PT1

most people, including me (and I know what _anti-windup_ refers to), don’t know what this means 😉

> [@hexaeder](#):
>
> I looked at the `LimPI` implementation in `ModelingToolkitStandardLibrary,` but it does not seem to include any explicit handling of the discontinuity.

Does it cause you an issue? I know it does sometimes, but often not. The MTKdtdlib is more or less waiting for the automatic handling of discontinuities.

---

<div class="post-metadata">

**Author:** ![hexaeder](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/hexaeder/32/24403_2.png) [@hexaeder](https://discourse.julialang.org/u/hexaeder)\
**Post date:** [January 16, 2025, 2:56pm UTC](https://discourse.julialang.org/t/handling-of-saturated-integrator-with-anti-windeup/124819/3 "2025-01-16T14:56:05Z")

</div>

> most people, including me (and I know what _anti-windup_ refers to), don’t know what this means 😉

Good point, I guess the naming PT1 is somewhat standard in german control theory but not much wider. It just refers to a first order integrator which follows a reference T\dot{x}=u-x. I’ll adapt the post.

Good to know about the automatic handling. I presume the if lifting will essentially generate callbacks for that? Unfortunately, my project is not using MTK end-to-end, instead we’re using MTK to generate systems with open inputs and outputs, use Symbolics to build Julia functions and connect a potential large number of such systems together “traditionally”. Therefore I am left out on some of the more advanced MTK magic, most notably symbolic callbacks.

> Does it cause you an issue? I know it does sometimes, but often not.

So far, not handling the discontinuities explicitly hasn’t led to any problems, this is what I am currently doing. Its more the abstract knowledge that the solvers typically assume continuous rhs and jacobians.

---

<div class="post-metadata">

**Author:** ![baggepinnen](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/baggepinnen/32/693_2.png) [@baggepinnen](https://discourse.julialang.org/u/baggepinnen)\
**Post date:** [January 16, 2025, 3:24pm UTC](https://discourse.julialang.org/t/handling-of-saturated-integrator-with-anti-windeup/124819/4 "2025-01-16T15:24:47Z")

</div>

> [@hexaeder](#):
>
> I presume the if lifting will essentially generate callbacks for that?

yes

> [@hexaeder](#):
>
> For example, is there a smooth approximation of the system which is used commonly?

modelica has the option to smooth discontinuities, saturation included. Unfortunately, [it’s not obvious from the model code how it’s done](https://github.com/modelica/ModelicaStandardLibrary/blob/master/Modelica/Blocks/Nonlinear.mo#L33C2-L38C18)

---

<div class="post-metadata">

**Author:** ![apo383](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/apo383/32/11272_2.png) [@apo383](https://discourse.julialang.org/u/apo383)\
**Post date:** [January 16, 2025, 7:08pm UTC](https://discourse.julialang.org/t/handling-of-saturated-integrator-with-anti-windeup/124819/5 "2025-01-16T19:08:22Z")

</div>

One way to get smoothness is to use feedback rather than placing a discrete limit on `u` as you have done. [In Simulink they provide both options](https://www.mathworks.com/help/simulink/slref/anti-windup-control-using-a-pid-controller.html), called back-calculation and clamping, respectively. Back-calculation is simply a negative feedback gain on an error, which is the amount the signal exceeds its saturation, illustrated here

 ![Block diagram with back-calculation](https://global.discourse-cdn.com/julialang/original/3X/d/0/d05429bc9431d03de52147f02e5b35e0b850217c.png).  
It does nothing when not saturated, and smoothly pushes down on `u` by the exceeding amount (EDIT: although still with a discontinuity when hitting the limit, see @baggepinnen). The higher the gain, the less the max error and the faster the dynamics (and stiffer the system).

ODE solvers have an analogous trade-off. `DifferentialEquations.jl` shows examples with non-smooth signals. Presumably adaptive solvers just approximate (say) a square wave with polynomials, using small steps to meet tolerance. The tighter the tolerance, the stiffer the system, here defined as the dynamical system composed of above “plant” plus its solver.

My interpretation is that “if lifting” is in between. You can have `if` within the plant dynamics or within the ODE solver which is naive to what’s going on. MTK is another layer that is more cognizant and can potentially make better decisions. (I would appreciation correction if this interpretation is wrong!)

---

<div class="post-metadata">

**Author:** ![baggepinnen](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/baggepinnen/32/693_2.png) [@baggepinnen](https://discourse.julialang.org/u/baggepinnen)\
**Post date:** [January 16, 2025, 7:24pm UTC](https://discourse.julialang.org/t/handling-of-saturated-integrator-with-anti-windeup/124819/6 "2025-01-16T19:24:51Z")

</div>

Back calculation does not change the fact that the derivative of a signal being integrated is discontinuous, you can see directly in the simulink diagram that the input to the integrator will have a discontinuous derivative.

---

<div class="post-metadata">

**Author:** ![apo383](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/apo383/32/11272_2.png) [@apo383](https://discourse.julialang.org/u/apo383)\
**Post date:** [January 16, 2025, 7:26pm UTC](https://discourse.julialang.org/t/handling-of-saturated-integrator-with-anti-windeup/124819/7 "2025-01-16T19:26:16Z")

</div>

> [@baggepinnen](#):
>
> the derivative of a signal being integrated is discontinuous

Thanks, I’ll edit to correct. There’s still a sharp corner when you hit the limit, but it’s smooth both below and above.

---

<div class="post-metadata">

**Author:** ![apo383](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/apo383/32/11272_2.png) [@apo383](https://discourse.julialang.org/u/apo383)\
**Post date:** [January 16, 2025, 7:40pm UTC](https://discourse.julialang.org/t/handling-of-saturated-integrator-with-anti-windeup/124819/8 "2025-01-16T19:40:32Z")

</div>

> [@baggepinnen](#):
>
> modelica has the option to smooth discontinuities, saturation included. Unfortunately, [it’s not obvious from the model code how it’s done](https://github.com/modelica/ModelicaStandardLibrary/blob/master/Modelica/Blocks/Nonlinear.mo#L33C2-L38C18)

My interpretation is that they don’t smooth discontinuities, they just know where they are. Your link shows `smooth(0,if u > uMax then uMax else if u < uMin then uMin else u)` which doesn’t do any smoothing since 0 is the order of differentiability: not at all. When \>0 the solver can take better advantage of known order, but here all it can do is know that there can be a sharp corner and be careful about it. The alternative branch to `smooth` is `homotopy` which is aware of things like `UpperLimit` and `LowerLimit`. This probably lets them avoid extra if statements because the solver is cognizant.

It appears Modelica can do things several ways. (1) Naively using adaptive ODE solver, where corners are tight-stepped. (2) Aware of corners and degree of smoothness which may help with polynomial order and stepping. (3) High-level symbolically aware when a signal is at limit which may help avoid if statements or discrete callbacks. (As usual I could be wrong!)

I have never looked at Modelica or MTK code before. In both cases I’m super impressed that everything is readable!
