Skip to content

Commit bf96ce1

Browse files
committed
add describing function example
1 parent 3497f53 commit bf96ce1

1 file changed

Lines changed: 101 additions & 0 deletions

File tree

example/describing_function.jl

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
#=
2+
This example demonstrates how to compute and plot describing functions in the Nyquist plane
3+
=#
4+
using ControlSystems
5+
using QuadGK
6+
7+
"""
8+
compute_describing_function(f, A)
9+
10+
Numerically computes the describing function N(A) for a nonlinearity f(x)
11+
at input amplitude A. Returns a Complex number.
12+
"""
13+
function compute_describing_function(f, A)
14+
# Fundamental sine component coefficient (b1)
15+
# b1 = (1/π) * ∫ [f(A*sin(θ)) * sin(θ)] dθ from 0 to 2π
16+
b1_integral, _ = quadgk-> f(A * sin(θ)) * sin(θ), 0, 2π, atol=1e-6, rtol=1e-6)
17+
b1 = b1_integral / π
18+
19+
# Fundamental cosine component coefficient (a1)
20+
# a1 = (1/π) * ∫ [f(A*sin(θ)) * cos(θ)] dθ from 0 to 2π
21+
a1_integral, _ = quadgk-> f(A * sin(θ)) * cos(θ), 0, 2π, atol=1e-6, rtol=1e-6)
22+
a1 = a1_integral / π
23+
24+
# N(A) = (b1 + j*a1) / A
25+
return Complex(b1, a1) / A
26+
end
27+
28+
"""
29+
plot_negative_inverse_df(f; A_range=range(0.1, 10, length=100), label="Nonlinearity")
30+
Computes and plots -1/N(A) for a given function f over a range of amplitudes.
31+
"""
32+
function plot_negative_inverse_df(f; A_range=range(0.1, 10, length=100), label="Nonlinearity", fig=nothing)
33+
# Compute the negative inverse values
34+
vals = similar(A_range, ComplexF64)
35+
Threads.@threads for i = eachindex(A_range)
36+
A = A_range[i]
37+
vals[i] = -1.0 / compute_describing_function(f, A)
38+
end
39+
40+
# Extract real and imaginary parts for plotting
41+
re_vals = real.(vals)
42+
im_vals = imag.(vals)
43+
44+
# Create plot or add to existing one
45+
if fig === nothing
46+
fig = plot(title="Negative Inverse Describing Function Analysis",
47+
xlabel="Real", ylabel="Imaginary", grid=true, zeroline=true)
48+
end
49+
50+
# Plot the curve with arrows to show increasing amplitude direction
51+
plot!(fig, re_vals, im_vals, label="-1/N(A): $label", lw=2.5, arrow=true, hover=A_range)
52+
53+
# Mark the start (low A) and end (high A)
54+
scatter!(fig, [re_vals[1]], [im_vals[1]], label="Low A", markershape=:circle)
55+
scatter!(fig, [re_vals[end]], [im_vals[end]], label="High A", markershape=:square)
56+
57+
return fig
58+
end
59+
60+
# ==============================================================================
61+
## Examples of usage
62+
# ==============================================================================
63+
# 1. Sign (Relay) function: f(x) = sgn(x)
64+
relay(x) = sign(x)
65+
println("Relay N(2): ", compute_describing_function(relay, 2.0))
66+
# Theoretical check: 4/(π*A) = 4/(2π) ≈ 0.6366
67+
68+
# 2. Saturation function
69+
saturation(x, limit=1.0) = clamp(x, -limit, limit)
70+
println("Saturation N(2): ", compute_describing_function(x -> saturation(x, 1.0), 2.0))
71+
72+
# 3. Dead-zone function
73+
deadzone(x, d=0.1) = abs(x) < d ? 0.0 : x - sign(x)*d
74+
println("Dead-zone N(2): ", compute_describing_function(x -> deadzone(x, 0.1), 2.0))
75+
76+
77+
# ==============================================================================
78+
## Nyquist Plot with Describing Function Overlay
79+
# ==============================================================================
80+
81+
using ControlSystems, Plots
82+
83+
nonlin = saturation
84+
85+
s = tf("s")
86+
G = 10 / (s^3 + 2s^2 + s + 1)
87+
# Create the linear Nyquist plot first
88+
p_nyq = nyquistplot(G)
89+
# Overlay the describing function
90+
plot_negative_inverse_df(nonlin, label="deadzone"; fig=p_nyq,
91+
A_range=0.01:0.01:100)
92+
# The intersection between the Nyquist plot and -1/N(A) indicates potential limit cycles. The amplitude of the limit cycle can be estimated from the corresponding A value, use the plotly() backend to display the amplitude on hover.
93+
94+
# ==============================================================================
95+
## Simulation
96+
# We can simulate the closed-loop system with the nonlinearity to verify the limit cycle prediction.
97+
# ==============================================================================
98+
99+
nl = nonlinearity(nonlin)
100+
sys_cl = feedback(G*nl)
101+
plot(step(sys_cl, 200))

0 commit comments

Comments
 (0)