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

Switch u layout #480

Merged
merged 15 commits into from
May 12, 2021
2 changes: 1 addition & 1 deletion docs/src/examples/example.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ u(x,t) = -L*x .+ 1.5(t>=2.5)# Form control law (u is a function of t and x), a
t =0:Ts:5
x0 = [1,0]
y, t, x, uout = lsim(sys,u,t,x0=x0)
Plots.plot(t,x, lab=["Position" "Velocity"], xlabel="Time [s]")
Plots.plot(t,x', lab=["Position" "Velocity"], xlabel="Time [s]")

save_docs_plot("lqrplot.svg"); # hide

Expand Down
2 changes: 1 addition & 1 deletion docs/src/man/creating_systems.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ D =
Continuous-time state-space model

julia> HeteroStateSpace(sys, to_sized)
HeteroStateSpace{Continuous, SizedMatrix{2, 2, Int64, 2}, SizedMatrix{2, 1, Int64, 2}, SizedMatrix{1, 2, Int64, 2}, SizedMatrix{1, 1, Int64, 2}}
HeteroStateSpace{Continuous, SizedMatrix{2, 2, Int64, 2, Matrix{Int64}}, SizedMatrix{2, 1, Int64, 2, Matrix{Int64}}, SizedMatrix{1, 2, Int64, 2, Matrix{Int64}}, SizedMatrix{1, 1, Int64, 2, Matrix{Int64}}}
A =
-5 0
0 -5
Expand Down
10 changes: 5 additions & 5 deletions src/delay_systems.jl
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ function _lsim(sys::DelayLtiSystem{T,S}, Base.@nospecialize(u!), t::AbstractArra
y[:,k] .= sv.saveval[k][2]
end

return y', t, x'
return y, t, x
end

# We have to default to something, look at the sys.P.P and delays
Expand Down Expand Up @@ -244,8 +244,8 @@ function Base.step(sys::DelayLtiSystem{T}, t::AbstractVector; kwargs...) where T
if nu == 1
y, tout, x = lsim(sys, u, t; x0=x0, kwargs...)
else
x = Array{T}(undef, length(t), nstates(sys), nu)
y = Array{T}(undef, length(t), noutputs(sys), nu)
x = Array{T}(undef, nstates(sys), length(t), nu)
y = Array{T}(undef, noutputs(sys), length(t), nu)
for i=1:nu
y[:,:,i], tout, x[:,:,i] = lsim(sys[:,i], u, t; x0=x0, kwargs...)
end
Expand All @@ -264,8 +264,8 @@ function impulse(sys::DelayLtiSystem{T}, t::AbstractVector; alg=MethodOfSteps(BS
if nu == 1
y, tout, x = lsim(sys, u, t; alg=alg, x0=sys.P.B[:,1], kwargs...)
else
x = Array{T}(undef, length(t), nstates(sys), nu)
y = Array{T}(undef, length(t), noutputs(sys), nu)
x = Array{T}(undef, nstates(sys), length(t), nu)
y = Array{T}(undef, noutputs(sys), length(t), nu)
for i=1:nu
y[:,:,i], tout, x[:,:,i] = lsim(sys[:,i], u, t; alg=alg, x0=sys.P.B[:,i], kwargs...)
end
Expand Down
4 changes: 2 additions & 2 deletions src/plotting.jl
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ lsimplot
title --> "System Response"
subplot --> s2i(1,i)
label --> "\$G_{$(si)}\$"
t, y[:, i]
t, y[i, :]
end
end
end
Expand Down Expand Up @@ -208,7 +208,7 @@ for (func, title, typ) = ((step, "Step Response", Stepplot), (impulse, "Impulse
y,t = func(s, p.args[2:end]...)
for i=1:ny
for j=1:nu
ydata = reshape(y[:, i, j], size(t, 1))
ydata = reshape(y[i, :, j], size(t, 1))
style = iscontinuous(s) ? :path : :steppost
ttext = (nu > 1 && i==1) ? title*" from: u($j) " : title
titles[s2i(i,j)] = ttext
Expand Down
4 changes: 2 additions & 2 deletions src/synthesis.jl
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ u(x,t) = -L*x # Form control law,
t=0:0.1:5
x0 = [1,0]
y, t, x, uout = lsim(sys,u,t,x0=x0)
plot(t,x, lab=["Position" "Velocity"], xlabel="Time [s]")
plot(t,x', lab=["Position" "Velocity"], xlabel="Time [s]")
```
"""
function lqr(A, B, Q, R)
Expand Down Expand Up @@ -95,7 +95,7 @@ u(x,t) = -L*x # Form control law,
t=0:Ts:5
x0 = [1,0]
y, t, x, uout = lsim(sys,u,t,x0=x0)
plot(t,x, lab=["Position" "Velocity"], xlabel="Time [s]")
plot(t,x', lab=["Position" "Velocity"], xlabel="Time [s]")
```
"""
function dlqr(A, B, Q, R)
Expand Down
99 changes: 48 additions & 51 deletions src/timeresp.jl
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,20 @@ Calculate the step response of system `sys`. If the final time `tfinal` or time
vector `t` is not provided, one is calculated based on the system pole
locations.

`y` has size `(length(t), ny, nu)`, `x` has size `(length(t), nx, nu)`"""
function Base.step(sys::AbstractStateSpace, t::AbstractVector; method=:cont)
lt = length(t)
`y` has size `(ny, length(t), nu)`, `x` has size `(nx, length(t), nu)`"""
function Base.step(sys::AbstractStateSpace, t::AbstractVector; method=:cont, kwargs...)
T = promote_type(eltype(sys.A), Float64)
ny, nu = size(sys)
nx = sys.nx
nx = nstates(sys)
u = (x,t)->[one(eltype(t))]
x0 = zeros(nx)
if nu == 1
y, tout, x, _ = lsim(sys, u, t, x0=x0, method=method)
y, tout, x, _ = lsim(sys, u, t; x0=x0, method=method, kwargs...)
else
x = Array{Float64}(undef, lt, nx, nu)
y = Array{Float64}(undef, lt, ny, nu)
x = Array{T}(undef, nx, length(t), nu)
y = Array{T}(undef, ny, length(t), nu)
for i=1:nu
y[:,:,i], tout, x[:,:,i],_ = lsim(sys[:,i], u, t, x0=x0, method=method)
y[:,:,i], tout, x[:,:,i],_ = lsim(sys[:,i], u, t; x0=x0, method=method, kwargs...)
end
end
return y, t, x
Expand All @@ -41,12 +41,11 @@ Calculate the impulse response of system `sys`. If the final time `tfinal` or ti
vector `t` is not provided, one is calculated based on the system pole
locations.

`y` has size `(length(t), ny, nu)`, `x` has size `(length(t), nx, nu)`"""
function impulse(sys::AbstractStateSpace, t::AbstractVector; method=:cont)
`y` has size `(ny, length(t), nu)`, `x` has size `(nx, length(t), nu)`"""
function impulse(sys::AbstractStateSpace, t::AbstractVector; method=:cont, kwargs...)
T = promote_type(eltype(sys.A), Float64)
lt = length(t)
ny, nu = size(sys)
nx = sys.nx
nx = nstates(sys)
if iscontinuous(sys) #&& method === :cont
u = (x,t) -> [zero(T)]
# impulse response equivalent to unforced response of
Expand All @@ -59,20 +58,20 @@ function impulse(sys::AbstractStateSpace, t::AbstractVector; method=:cont)
x0s = zeros(T, nx, nu)
end
if nu == 1 # Why two cases # QUESTION: Not type stable?
y, t, x,_ = lsim(sys, u, t, x0=x0s[:], method=method)
y, t, x,_ = lsim(sys, u, t; x0=x0s[:], method=method, kwargs...)
else
x = Array{T}(undef, lt, nx, nu)
y = Array{T}(undef, lt, ny, nu)
x = Array{T}(undef, nx, length(t), nu)
y = Array{T}(undef, ny, length(t), nu)
for i=1:nu
y[:,:,i], t, x[:,:,i],_ = lsim(sys[:,i], u, t, x0=x0s[:,i], method=method)
y[:,:,i], t, x[:,:,i],_ = lsim(sys[:,i], u, t; x0=x0s[:,i], method=method, kwargs...)
end
end
return y, t, x
end

impulse(sys::LTISystem, tfinal::Real; kwags...) = impulse(sys, _default_time_vector(sys, tfinal); kwags...)
impulse(sys::LTISystem; kwags...) = impulse(sys, _default_time_vector(sys); kwags...)
impulse(sys::TransferFunction, t::AbstractVector; kwags...) = impulse(ss(sys), t; kwags...)
impulse(sys::LTISystem, tfinal::Real; kwargs...) = impulse(sys, _default_time_vector(sys, tfinal); kwargs...)
impulse(sys::LTISystem; kwargs...) = impulse(sys, _default_time_vector(sys); kwargs...)
impulse(sys::TransferFunction, t::AbstractVector; kwargs...) = impulse(ss(sys), t; kwargs...)

"""
y, t, x = lsim(sys, u[, t]; x0, method])
Expand All @@ -81,7 +80,7 @@ impulse(sys::TransferFunction, t::AbstractVector; kwags...) = impulse(ss(sys), t
Calculate the time response of system `sys` to input `u`. If `x0` is ommitted,
a zero vector is used.

`y`, `x`, `uout` has time in the first dimension. Initial state `x0` defaults to zero.
`y`, `x`, `uout` has time in the second dimension. Initial state `x0` defaults to zero.

Continuous time systems are simulated using an ODE solver if `u` is a function. If `u` is an array, the system is discretized before simulation. For a lower level inteface, see `?Simulator` and `?solve`

Expand All @@ -106,19 +105,19 @@ u(x,t) = -L*x # Form control law,
t=0:0.1:5
x0 = [1,0]
y, t, x, uout = lsim(sys,u,t,x0=x0)
plot(t,x, lab=["Position" "Velocity"], xlabel="Time [s]")
plot(t,x', lab=["Position" "Velocity"], xlabel="Time [s]")
```
"""
function lsim(sys::AbstractStateSpace, u::AbstractVecOrMat, t::AbstractVector;
x0::AbstractVector=zeros(Bool, sys.nx), method::Symbol=:unspecified)
x0::AbstractVecOrMat=zeros(Bool, nstates(sys)), method::Symbol=:unspecified)
ny, nu = size(sys)
nx = sys.nx

if length(x0) != nx
error("size(x0) must match the number of states of sys")
end
if !(size(u) in [(length(t), nu) (length(t),)])
error("u must be of size (length(t), nu)")
if size(u) != (nu, length(t))
error("u must be of size (nu, length(t))")
end

dt = Float64(t[2] - t[1])
Expand All @@ -135,7 +134,7 @@ function lsim(sys::AbstractStateSpace, u::AbstractVecOrMat, t::AbstractVector;
dsys = c2d(sys, dt, :zoh)
elseif method === :foh
dsys, x0map = c2d_x0map(sys, dt, :foh)
x0 = x0map*[x0; transpose(u)[:,1]]
x0 = x0map*[x0; u[:,1]]
else
error("Unsupported discretization method")
end
Expand All @@ -147,25 +146,31 @@ function lsim(sys::AbstractStateSpace, u::AbstractVecOrMat, t::AbstractVector;
end

x = ltitr(dsys.A, dsys.B, u, x0)
y = transpose(sys.C*transpose(x) + sys.D*transpose(u))
y = sys.C*x + sys.D*u
return y, t, x
end

function lsim(sys::AbstractStateSpace{<:Discrete}, u::AbstractVecOrMat; kwargs...)
t = range(0, length=size(u, 1), step=sys.Ts)
t = range(0, length=size(u, 2), step=sys.Ts)
lsim(sys, u, t; kwargs...)
end

@deprecate lsim(sys, u, t, x0) lsim(sys, u, t; x0=x0)
@deprecate lsim(sys, u, t, x0, method) lsim(sys, u, t; x0=x0, method=method)

function lsim(sys::AbstractStateSpace, u::Function, tfinal::Real, args...; kwargs...)
function lsim(sys::AbstractStateSpace, u::Function, tfinal::Real; kwargs...)
t = _default_time_vector(sys, tfinal)
lsim(sys, u, t, args...; kwargs...)
lsim(sys, u, t; kwargs...)
end

# Function for DifferentialEquations lsim
function f_lsim(dx, x, p, t)
A, B, u = p
dx .= A * x .+ B * u(x, t)
end

function lsim(sys::AbstractStateSpace, u::Function, t::AbstractVector;
x0::VecOrMat=zeros(sys.nx), method::Symbol=:cont)
x0::AbstractVecOrMat=zeros(Bool, nstates(sys)), method::Symbol=:cont, alg = Tsit5(), kwargs...)
ny, nu = size(sys)
nx = sys.nx
u0 = u(x0,1)
Expand All @@ -189,20 +194,17 @@ function lsim(sys::AbstractStateSpace, u::Function, t::AbstractVector;
end
x,uout = ltitr(dsys.A, dsys.B, u, t, T.(x0))
else
s = Simulator(sys, u)
sol = solve(s, T.(x0), (t[1],t[end]), Tsit5())
x = sol(t)'
uout = Array{eltype(x)}(undef, length(t), ninputs(sys))
for (i,ti) in enumerate(t)
uout[i,:] = u(x[i,:],ti)'
end
p = (sys.A, sys.B, u)
sol = solve(ODEProblem(f_lsim, x0, (t[1], t[end]), p), alg; saveat=t, kwargs...)
x = reduce(hcat, sol.u)
uout = reduce(hcat, u(x[:, i], t[i]) for i in eachindex(t))
end
y = transpose(sys.C*transpose(x) + sys.D*transpose(uout))
y = sys.C*x + sys.D*uout
return y, t, x, uout
end


lsim(sys::TransferFunction, u, t, args...; kwargs...) = lsim(ss(sys), u, t, args...; kwargs...)
lsim(sys::TransferFunction, u, t; kwargs...) = lsim(ss(sys), u, t; kwargs...)


"""
Expand All @@ -218,34 +220,29 @@ e.g, `x0` should prefereably not be a sparse vector.
If `u` is a function, then `u(x,i)` is called to calculate the control signal every iteration. This can be used to provide a control law such as state feedback `u=-Lx` calculated by `lqr`. In this case, an integrer `iters` must be provided that indicates the number of iterations.
"""
@views function ltitr(A::AbstractMatrix, B::AbstractMatrix, u::AbstractVecOrMat,
x0::AbstractVector=zeros(eltype(A), size(A, 1)))
x0::AbstractVecOrMat=zeros(eltype(A), size(A, 1)))

T = promote_type(LinearAlgebra.promote_op(LinearAlgebra.matprod, eltype(A), eltype(x0)),
LinearAlgebra.promote_op(LinearAlgebra.matprod, eltype(B), eltype(u)))

n = size(u, 1)

# Transposing u allows column-wise operations, which apparently is faster.
ut = transpose(u)
n = size(u, 2)

# Using similar instead of Matrix{T} to allow for CuArrays to be used.
# This approach is problematic if x0 is sparse for example, but was considered
# to be good enough for now
x = similar(x0, T, (length(x0), n))

x[:,1] .= x0
mul!(x[:, 2:end], B, transpose(u[1:end-1, :])) # Do all multiplications B*u[:,k] to save view allocations
mul!(x[:, 2:end], B, u[:, 1:end-1]) # Do all multiplications B*u[:,k] to save view allocations

tmp = similar(x0, T) # temporary vector for storing A*x[:,k]
for k=1:n-1
mul!(tmp, A, x[:,k])
x[:,k+1] .+= tmp
mul!(x[:, k+1], A, x[:,k], true, true)
end
return transpose(x)
return x
end

function ltitr(A::AbstractMatrix{T}, B::AbstractMatrix{T}, u::Function, t,
x0::VecOrMat=zeros(T, size(A, 1))) where T
x0::AbstractVecOrMat=zeros(T, size(A, 1))) where T
iters = length(t)
x = similar(A, size(A, 1), iters)
uout = similar(A, size(B, 2), iters)
Expand All @@ -255,7 +252,7 @@ function ltitr(A::AbstractMatrix{T}, B::AbstractMatrix{T}, u::Function, t,
uout[:,i] .= u(x0,t[i])
x0 = A * x0 + B * uout[:,i]
end
return transpose(x), transpose(uout)
return x, uout
end

# HELPERS:
Expand Down
2 changes: 1 addition & 1 deletion src/utilities.jl
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ function to_abstract_matrix(A::AbstractArray)
end
return A
end
to_abstract_matrix(A::Vector) = reshape(A, length(A), 1)
to_abstract_matrix(A::AbstractVector) = reshape(A, length(A), 1)
to_abstract_matrix(A::Number) = fill(A, 1, 1)

# Do no sorting of eigenvalues
Expand Down
26 changes: 13 additions & 13 deletions test/test_delayed_systems.jl
Original file line number Diff line number Diff line change
Expand Up @@ -166,21 +166,21 @@ println("Simulating first delay system:")
@time step(delay(1)*tf(1,[1,1]))

@time y1, t1, x1 = step([s11;s12], 10)
@time @test y1[:,2] ≈ step(s12, t1)[1] rtol = 1e-14
@time @test y1[2:2,:] ≈ step(s12, t1)[1] rtol = 1e-14

t = 0.0:0.1:10
y2, t2, x2 = step(s1, t)
# TODO Figure out which is inexact here
@test y2[:,1,1:1] + y2[:,1,2:2] ≈ step(s11, t)[1] + step(s12, t)[1] rtol=1e-14
@test y2[1:1,:,1:1] + y2[1:1,:,2:2] ≈ step(s11, t)[1] + step(s12, t)[1] rtol=1e-14

y3, t3, x3 = step([s11; s12], t)
@test y3[:,1,1] ≈ step(s11, t)[1] rtol = 1e-14
@test y3[:,2,1] ≈ step(s12, t)[1] rtol = 1e-14
@test y3[1:1,:,1] ≈ step(s11, t)[1] rtol = 1e-14
@test y3[2:2,:,1] ≈ step(s12, t)[1] rtol = 1e-14

y1, t1, x1 = step(DelayLtiSystem([1.0/s 2/s; 3/s 4/s]), t)
y2, t2, x2 = step([1.0/s 2/s; 3/s 4/s], t)
@test y1 ≈ y2 rtol=1e-14
@test size(x1,1) == length(t)
@test size(x1,2) == length(t)
@test size(x1,3) == 2


Expand All @@ -202,7 +202,7 @@ function y_expected(t, K)
end
end

@test ystep ≈ y_expected.(t, K) atol = 1e-12
@test ystep' ≈ y_expected.(t, K) atol = 1e-12

function dy_expected(t, K)
if t < 1
Expand All @@ -219,13 +219,13 @@ end
y_impulse, t, _ = impulse(sys_known, 3)

# TODO Better accuracy for impulse
@test y_impulse ≈ dy_expected.(t, K) rtol=1e-13
@test maximum(abs, y_impulse - dy_expected.(t, K)) < 1e-12
@test y_impulse' ≈ dy_expected.(t, K) rtol=1e-13
@test maximum(abs, y_impulse' - dy_expected.(t, K)) < 1e-12

y_impulse, t, _ = impulse(sys_known, 3, alg=MethodOfSteps(Tsit5()))
# Two orders of magnitude better with BS3 in this case, which is default for impulse
@test y_impulse ≈ dy_expected.(t, K) rtol=1e-5
@test maximum(abs, y_impulse - dy_expected.(t, K)) < 1e-5
@test y_impulse' ≈ dy_expected.(t, K) rtol=1e-5
@test maximum(abs, y_impulse' - dy_expected.(t, K)) < 1e-5

## Test delay with D22 term
t = 0:0.01:4
Expand All @@ -234,16 +234,16 @@ sys = delay(1)

y, t, x = step(sys, t)
@test y[:] ≈ [zeros(100); ones(301)] atol = 1e-12
@test size(x) == (401,0)
@test size(x) == (0,401)

sys = delay(1)*delay(1)*1/s

y, t, x = step(sys, t)

y_sol = [zeros(200);0:0.01:2]
y_sol = [zeros(200);0:0.01:2]'

@test maximum(abs,y-y_sol) < 1e-13
@test maximum(abs,x-collect(0:0.01:4)) < 1e-15
@test maximum(abs,x-collect(0:0.01:4)') < 1e-15

# TODO For some reason really bad accuracy here
# Looks like a lag in time
Expand Down
Loading