Skip to content

Commit 2e4e7cd

Browse files
committed
Add hysteresis nonlinearity
1 parent 8f2f89b commit 2e4e7cd

File tree

3 files changed

+95
-1
lines changed

3 files changed

+95
-1
lines changed

docs/src/lib/nonlinear.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,39 @@ f2 = plot(step(feedback(duffing, C), 8), plotx=true, plot_title="Controlled wigg
229229
plot(f1,f2, size=(1300,800))
230230
```
231231

232+
### Hysteresis
233+
Here we demonstrate that we may use this simple framework to model also stateful nonlinearities, such as hysteresis. The `hysteresis` function internally creates a feedback interconnection between a fast first-order system and a `sign` or `tanh` nonlinearity to create a simple hysteresis loop. The width and amplitude of the loop can be adjusted through the parameters `width` and `amplitude`, respectively.
234+
```@example HYSTERESIS
235+
using ControlSystems, Plots
236+
import ControlSystemsBase: hysteresis
237+
238+
amplitude = 0.7
239+
width = 1.5
240+
sys_hyst = hysteresis(; width, amplitude)
241+
242+
t = 0:0.01:20
243+
ufun(y,x,t) = y .= 5.0 .* sin(t) ./ (1+t/5) # A sine wave that sweeps back and forth with decreasing amplitude
244+
245+
res = lsim(sys_hyst, ufun, t)
246+
247+
p1 = plot(res.u[:], res.y[:],
248+
title="Hysteresis Loop",
249+
xlabel="Input u(t)",
250+
ylabel="Output y(t)",
251+
lw=2, color=:blue, lab="", framestyle=:zerolines)
252+
253+
hline!([amplitude -amplitude], l=:dash, c=:red, label=["Amplitude" ""])
254+
vline!([width -width], l=:dash, c=:green, label=["Width" ""])
255+
256+
# Plotting time series to see the "jumps" in the response
257+
p2 = plot(t, [res.u[:] res.y[:]],
258+
title="Time Domain Response",
259+
label=["Input (Sine)" "Output (Hysteresis)"],
260+
xlabel="Time (s)",
261+
lw=2)
262+
263+
plot(p1, p2, layout=(1,2), size=(900, 400))
264+
```
232265

233266
## Limitations
234267
- Remember, this functionality is experimental and subject to breakage.
@@ -260,4 +293,5 @@ ControlSystemsBase.saturation
260293
ControlSystemsBase.ratelimit
261294
ControlSystemsBase.deadzone
262295
ControlSystemsBase.linearize
296+
ControlSystemsBase.hysteresis
263297
```

lib/ControlSystemsBase/src/nonlinear_components.jl

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,4 +131,42 @@ See [`ControlSystemsBase.offset`](@ref) for handling operating points.
131131
"""
132132
deadzone(args...) = nonlinearity(DeadZone(args...))
133133
deadzone(v::AbstractVector, args...) = nonlinearity(DeadZone.(v, args...))
134-
Base.show(io::IO, f::DeadZone) = f.u == -f.l ? print(io, "deadzone($(f.u))") : print(io, "deadzone($(f.l), $(f.u))")
134+
Base.show(io::IO, f::DeadZone) = f.u == -f.l ? print(io, "deadzone($(f.u))") : print(io, "deadzone($(f.l), $(f.u))")
135+
136+
137+
## Hysteresis ==================================================================
138+
139+
"""
140+
hysteresis(; amplitude, width, Tf, hardness)
141+
142+
Create a hysteresis nonlinearity. The signal switches between `±amplitude` when the input crosses `±width`. `Tf` controls the time constant of the internal state that tracks the hysteresis, and `hardness` controls how sharp the transition is between the two states.
143+
144+
```
145+
146+
y▲
147+
148+
amp┌───┼───┬─►
149+
│ │ ▲
150+
│ │ │
151+
──┼───┼───┼───► u
152+
│ │ │w
153+
▼ │ │
154+
◄─┴───┼───┘
155+
156+
```
157+
158+
$nonlinear_warning
159+
"""
160+
function hysteresis(; amplitude=1.0, width=1.0, Tf=0.001, hardness=20.0)
161+
T = promote_type(typeof(amplitude), typeof(width), typeof(Tf), typeof(hardness))
162+
G = tf(1, [Tf, 1])
163+
if isfinite(hardness)
164+
nl_func = y -> width * tanh(hardness*y)
165+
else
166+
nl_func = y -> width * sign(y)
167+
end
168+
nl = nonlinearity(nl_func)
169+
amplitude/width*(feedback(G, -nl) - 1)
170+
end
171+
172+

test/test_nonlinear_timeresp.jl

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,3 +235,25 @@ res = lsim(nl, (x,t)->2sin(t), 0:0.01:4pi)
235235
@test minimum(res.y) -0.6 rtol=1e-3
236236
@test maximum(res.y) 1.3 rtol=1e-3
237237

238+
## Hysteresis
239+
using ControlSystemsBase: hysteresis
240+
241+
# Construction
242+
@test hysteresis() isa HammersteinWienerSystem
243+
@test hysteresis(; amplitude=0.7, width=1.5) isa HammersteinWienerSystem
244+
245+
# Simulate with sine input and verify output bounded by amplitude
246+
amplitude = 0.7
247+
width = 1.5
248+
sys_hyst = hysteresis(; width, amplitude)
249+
t = 0:0.001:10
250+
res = lsim(sys_hyst, (x,t) -> 5sin(t), t)
251+
@test maximum(res.y) amplitude rtol=0.1
252+
@test minimum(res.y) -amplitude rtol=0.1
253+
254+
# Finite hardness (smooth tanh transition)
255+
sys_hyst_smooth = hysteresis(; width, amplitude, hardness=5.0)
256+
res_smooth = lsim(sys_hyst_smooth, (x,t) -> 5sin(t), t)
257+
@test maximum(res_smooth.y) amplitude rtol=0.15
258+
@test minimum(res_smooth.y) -amplitude rtol=0.15
259+

0 commit comments

Comments
 (0)