-
Notifications
You must be signed in to change notification settings - Fork 85
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
Switch u layout #480
Changes from 3 commits
e1bc88f
9c23ecf
7c80632
47264cf
ad98b33
4c2566d
f4eef18
dfa7918
a77de03
ef24346
a238dc7
9dc5097
ebab7f4
9236684
1c4ef6e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,18 +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)`""" | ||
`y` has size `(ny, length(t), nu)`, `x` has size `(nx, length(t), nu)`""" | ||
function Base.step(sys::AbstractStateSpace, t::AbstractVector; method=:cont) | ||
lt = length(t) | ||
ny, nu = size(sys) | ||
nx = sys.nx | ||
u = (x,t)->[one(eltype(t))] | ||
u = (x,t)->[one(eltype(t))] # 547.109 μs (5240 allocations: 748.75 KiB) | ||
#tmp = [one(eltype(t))] # Slightly less allocations but maybe no real difference, 539.624 μs (4916 allocations: 721.61 KiB) | ||
#u = (x,t)->tmp | ||
x0 = zeros(nx) | ||
if nu == 1 | ||
y, tout, x, _ = lsim(sys, u, t, x0=x0, method=method) | ||
else | ||
x = Array{Float64}(undef, lt, nx, nu) | ||
y = Array{Float64}(undef, lt, ny, nu) | ||
x = Array{Float64}(undef, nx, lt, nu) | ||
y = Array{Float64}(undef, ny, lt, nu) | ||
for i=1:nu | ||
y[:,:,i], tout, x[:,:,i],_ = lsim(sys[:,i], u, t, x0=x0, method=method) | ||
end | ||
|
@@ -41,7 +43,7 @@ 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)`""" | ||
`y` has size `(ny, length(t), nu)`, `x` has size `(nx, length(t), nu)`""" | ||
function impulse(sys::AbstractStateSpace, t::AbstractVector; method=:cont) | ||
T = promote_type(eltype(sys.A), Float64) | ||
lt = length(t) | ||
|
@@ -61,8 +63,8 @@ function impulse(sys::AbstractStateSpace, t::AbstractVector; method=:cont) | |
if nu == 1 # Why two cases # QUESTION: Not type stable? | ||
y, t, x,_ = lsim(sys, u, t, x0=x0s[:], method=method) | ||
else | ||
x = Array{T}(undef, lt, nx, nu) | ||
y = Array{T}(undef, lt, ny, nu) | ||
x = Array{T}(undef, nx, lt, nu) | ||
y = Array{T}(undef, ny, lt, nu) | ||
for i=1:nu | ||
y[:,:,i], t, x[:,:,i],_ = lsim(sys[:,i], u, t, x0=x0s[:,i], method=method) | ||
end | ||
|
@@ -81,7 +83,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` | ||
|
||
|
@@ -106,7 +108,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 lsim(sys::AbstractStateSpace, u::AbstractVecOrMat, t::AbstractVector; | ||
|
@@ -117,8 +119,10 @@ function lsim(sys::AbstractStateSpace, u::AbstractVecOrMat, t::AbstractVector; | |
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) == (length(t),) | ||
reshape!(u, :, 1) # Do we want to modify arguments? Or should this be copied? | ||
else size(u) != (nu, length(t)) | ||
error("u must be of size (nu, length(t))") | ||
albheim marked this conversation as resolved.
Show resolved
Hide resolved
|
||
end | ||
|
||
dt = Float64(t[2] - t[1]) | ||
|
@@ -135,7 +139,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 | ||
|
@@ -147,7 +151,7 @@ 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 | ||
|
||
|
@@ -165,7 +169,7 @@ function lsim(sys::AbstractStateSpace, u::Function, tfinal::Real, args...; kwarg | |
end | ||
|
||
function lsim(sys::AbstractStateSpace, u::Function, t::AbstractVector; | ||
x0::VecOrMat=zeros(sys.nx), method::Symbol=:cont) | ||
x0::VecOrMat=zeros(sys.nx, 1), method::Symbol=:cont) | ||
ny, nu = size(sys) | ||
nx = sys.nx | ||
u0 = u(x0,1) | ||
|
@@ -189,15 +193,12 @@ 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 | ||
f(dx,x,p,t) = dx .= sys.A*x .+ sys.B*u(x,t) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here we have an opportunity to do better in case arrays are static and we take up the direct dependence on StaticArrays. The There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I haven't used static arrays at all, so not familiar with why that would fail. What would be the static array friendly option? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A static array is static, hence trying to mutate it with f(x::SArray,p,t) = sys.A*x .+ sys.B*u(x,t) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ahh, thought you only meant that A and B should be static. But everything is going to be static. Sounds interesting, will have to have a look at that. Should it then be up to the user to define x0 and then the simulation is done with that array type for x? |
||
sol = solve(ODEProblem(f,x0,(t[1],t[end])), Tsit5(); saveat=t) | ||
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 | ||
|
||
|
@@ -223,25 +224,20 @@ If `u` is a function, then `u(x,i)` is called to calculate the control signal ev | |
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, | ||
|
@@ -255,7 +251,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: | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm. Is this how we want it?
I guess this is the best way wrt memory layout, but it does seem quite confusing and inconsistent with the order that would be natural for e.g., freqfresp, where we would have the frequency index last.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought this is what we said for the first two arguments, and the last one made sense to me to leave based on how it was accessed. What would the other alternative be, nx x nu x lt?
Currently freqresp has the frequency index in the first dimension if I'm not mistaken, why would it be the most natural to have it last?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also,
Float64
should probably be made generic?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
freqresp
probably needs a change corresponding to the one in this PR to place frequency in the last dimension, since a single call toevalfr
generates a matrixny x nu
of frequency responses at a single frequency.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One can always use
PermutedDimsArray
in the end to shape the array the way we want without copying. The user can then, if they choose, callcopy
on it to obtain a regular array, or use thePermutedDimsArray
like it was a regular arrayThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PermutedDimsArray
seems like a reasonable fix.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would probably be better to avoid introducing
lt
, consider just usinglength(t)
or possibly call itnt
instead.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seem reasonable, copied the way used in impulse.
I switched to using
length(t)
instead.