Skip to content

Commit 93e1fd3

Browse files
Merge pull request #10 from Robbybp/rp/atlas
Use VectorNonlinearOracle for Atlas balancing
2 parents 58defd9 + 6d5b31d commit 93e1fd3

4 files changed

Lines changed: 172 additions & 111 deletions

File tree

examples/Atlas/Project.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
[deps]
2+
ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
23
HSL_jll = "017b0a0e-03f4-516a-9b91-836bbd1904dd"
4+
Ipopt = "b6b21f68-93f8-5de0-b562-5493be1d77c9"
35
JLD2 = "033835bb-8acc-5ee8-8aae-3f567f8a3819"
6+
JuMP = "4076af6c-e467-56ae-b986-b466b2749572"
47
MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee"
8+
MathProgIncidence = "892fab00-3092-4bd0-9c46-66676a93f84e"
59
MeshCat = "283c5d60-a78f-5afe-a0af-af636b173e11"
610
MeshCatMechanisms = "6ad125db-dd91-5488-b820-c1df6aab299d"
11+
Revise = "295af30f-e4ad-537b-8983-00126c2a3abe"
712
RigidBodyDynamics = "366cf18f-59d5-5db9-a4de-86a9f6786172"
813
Rotations = "6038ab10-8711-5258-84ad-4b1120ba62dc"
14+
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
915
StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"

examples/Atlas/atlas_balancing.jl

Lines changed: 160 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ using Revise
22
using JLD2
33
using SparseArrays
44
using MathOptInterface.Nonlinear.SymbolicAD
5+
import MathOptInterface as MOI
56
using JuMP, Ipopt, HSL_jll
7+
using LinearAlgebra
68

79
# include(joinpath(@__DIR__, "../mpc_utils.jl"))
810
include(joinpath(@__DIR__, "atlas_utils.jl"))
@@ -138,7 +140,8 @@ function atlas_dynamics(xu::T...) where {T<:Real}
138140
h = 0.01
139141
x = collect(xu[1:atlas.nx])
140142
u = collect(xu[atlas.nx+1:end])
141-
return rk4(atlas, x, u, h)
143+
#return rk4(atlas, x, u, h)
144+
return explicit_euler(atlas, x, u, h)
142145
end
143146

144147
# Lets define a jump model that solves the MPC problem
@@ -149,7 +152,8 @@ end
149152
function build_and_solve_mpc(atlas_obj::Atlas, x_ref::Vector{Float64}, X_start::Vector{Float64}, N::Int; optimizer=optimizer_with_attributes(Ipopt.Optimizer,
150153
# "print_level" => 0,
151154
"hsllib" => HSL_jll.libhsl_path,
152-
"linear_solver" => "MA27"
155+
"linear_solver" => "MA27",
156+
"max_iter" => 20,
153157
)
154158
)
155159
model = Model()
@@ -167,23 +171,66 @@ function build_and_solve_mpc(atlas_obj::Atlas, x_ref::Vector{Float64}, X_start::
167171

168172
# Build the memoized function array for the multi-output dynamics
169173
# so we can call the i-th dimension of next state individually
170-
dyn_ops = memoize_dynamics_and_jacobian(atlas_dynamics,
171-
atlas_obj.nx + atlas_obj.nu,
172-
atlas_obj.nx)
174+
#dyn_ops = memoize_dynamics_and_jacobian(atlas_dynamics,
175+
# atlas_obj.nx + atlas_obj.nu,
176+
# atlas_obj.nx)
177+
178+
VNO_dim = 2*atlas_obj.nx + atlas_obj.nu
179+
# The input variable to this function:
180+
# [ x[t+1,:], x[t,:], u[t,:] ]
181+
jacobian_structure = Tuple{Int,Int}[]
182+
append!(jacobian_structure, map(i -> (i,i), 1:atlas_obj.nx))
183+
for i in 1:atlas_obj.nx, j in 1:(atlas_obj.nx + atlas_obj.nu)
184+
push!(jacobian_structure, (i, j + atlas_obj.nx))
185+
end
186+
hessian_lagrangian_structure = [
187+
(i, j)
188+
for i in atlas_obj.nx+1:VNO_dim
189+
for j in atlas_obj.nx+1:VNO_dim
190+
]
191+
VNO = MOI.VectorNonlinearOracle(;
192+
dimension = VNO_dim,
193+
l = zeros(atlas_obj.nx),
194+
u = zeros(atlas_obj.nx),
195+
eval_f = (ret, x) -> begin
196+
ret[1:atlas_obj.nx] .= x[1:atlas_obj.nx] - atlas_dynamics(x[atlas_obj.nx + 1:VNO_dim]...)
197+
return
198+
end,
199+
jacobian_structure,
200+
eval_jacobian = (ret, x) -> begin
201+
dyn_jac = ForwardDiff.jacobian(x -> atlas_dynamics(x...), x[atlas_obj.nx + 1:VNO_dim])
202+
jnnz = length(jacobian_structure)
203+
ret[1:atlas_obj.nx] .= ones(atlas_obj.nx)
204+
nx = atlas_obj.nx; nu = atlas_obj.nu
205+
ret[atlas_obj.nx+1:jnnz] .= - reshape(dyn_jac', nx * (nx + nu))
206+
return
207+
end,
208+
hessian_lagrangian_structure,
209+
eval_hessian_lagrangian = (ret, x, λ) -> begin
210+
hess = ForwardDiff.hessian(x -> dot(λ, atlas_dynamics(x...)), x[atlas_obj.nx + 1:VNO_dim])
211+
hnnz = length(hessian_lagrangian_structure)
212+
ret[1:hnnz] .= - reshape(hess, hnnz)
213+
return
214+
end
215+
)
216+
for t in 1:(N-1)
217+
vars = vcat(x[t+1,:], x[t, :], u[t, :])
218+
JuMP.@constraint(model, vars in VNO)
219+
end
173220

174221
# Add constraints: x[t+1] = f(x[t], u[t]) for t=1 to N-1
175-
for t in 1:(N-1), i in 1:atlas_obj.nx
176-
scalar_fun, grad_fun! = dyn_ops[i]
177-
op = add_nonlinear_operator(
178-
model,
179-
atlas_obj.nx + atlas_obj.nu, # number of inputs
180-
scalar_fun, # f_i
181-
grad_fun!, # gradient callback
182-
name = Symbol("dyn_$(t)_$(i)")
183-
)
184-
# Bind the operator to the constraint x[t+1, i] == f_i( [x[t,:]; u[t,:]]... )
185-
@constraint(model, x[t+1,i] == op( [x[t, :]; u[t, :]]... ))
186-
end
222+
#for t in 1:(N-1), i in 1:atlas_obj.nx
223+
# scalar_fun, grad_fun! = dyn_ops[i]
224+
# op = add_nonlinear_operator(
225+
# model,
226+
# atlas_obj.nx + atlas_obj.nu, # number of inputs
227+
# scalar_fun, # f_i
228+
# grad_fun!, # gradient callback
229+
# name = Symbol("dyn_$(t)_$(i)")
230+
# )
231+
# # Bind the operator to the constraint x[t+1, i] == f_i( [x[t,:]; u[t,:]]... )
232+
# @constraint(model, x[t+1,i] == op( [x[t, :]; u[t, :]]... ))
233+
#end
187234

188235
# Possibly set an initial condition constraint: x[1, :] = x_ref
189236
@constraint(model, [j in 1:atlas_obj.nx], x[1,j] == X_start[j])
@@ -193,15 +240,16 @@ function build_and_solve_mpc(atlas_obj::Atlas, x_ref::Vector{Float64}, X_start::
193240
return model, value.(x), value.(u)
194241
end
195242

196-
N = 100
243+
N = 10
197244
X_start = deepcopy(x_ref)
198245
X_start[atlas.nq + 5] = 1.3
199246
model, X_solve, U_solve = build_and_solve_mpc(atlas, x_ref, X_start, N; optimizer=optimizer_with_attributes(Ipopt.Optimizer,
200247
# "print_level" => 0,
201248
"linear_solver" => "ma97",
202-
"hessian_approximation" => "limited-memory",
203-
"max_iter" => 300,
249+
#"hessian_approximation" => "limited-memory",
250+
#"max_iter" => 20,
204251
"mu_target" => 1e-8,
252+
"print_user_options" => "yes",
205253
)
206254
)
207255
termination_status(model)
@@ -269,92 +317,95 @@ error = sum( norm(X_solve[t,:] .- X[t]) for t in 1:N )
269317
# global X[k + 1] = rk4(atlas, X[k], clamp.(u_ref + U[k], -atlas.torque_limits, atlas.torque_limits), h)
270318
# end
271319

272-
X = [value.(x[t,:]) for t=1:N]
273-
animate!(atlas, mvis, X, Δt=h);
274-
readline()
275-
276-
277-
###############
278-
include(joinpath(@__DIR__, "rigidbodyutils.jl"))
279-
include(joinpath(@__DIR__, "atlas_utils.jl"))
280-
281-
model = Model()
282-
optimizer=optimizer_with_attributes(Ipopt.Optimizer,
283-
# "print_level" => 0,
284-
"hsllib" => HSL_jll.libhsl_path,
285-
"linear_solver" => "MA27"
286-
)
287-
set_optimizer(model, optimizer)
288-
289-
@variable(model, x[1:N, 1:atlas.nx])
290-
@variable(model, -atlas.torque_limits[i] <= u[t=1:N-1,i=1:atlas.nu] <= atlas.torque_limits[i])
291-
292-
dyn_result = dynamics(atlas, convert.(NonlinearExpr, x[1, :]), convert.(NonlinearExpr,u[1, :]))
293-
294-
function atlas_dynamics(x, u)
295-
h = 0.01
296-
return rk4(atlas, x, u, h)
297-
end
298-
299-
function build_and_solve_mpc(atlas_obj::Atlas, x_ref::Vector{Float64}, X_start::Vector{Float64}, N::Int; optimizer=optimizer_with_attributes(Ipopt.Optimizer,
300-
# "print_level" => 0,
301-
"hsllib" => HSL_jll.libhsl_path,
302-
"linear_solver" => "MA27"
303-
)
304-
)
305-
model = Model()
306-
set_optimizer(model, optimizer)
307-
308-
# For demonstration, let's define:
309-
# x[t=1:N, 1:atlas.nx]
310-
# u[t=1:N-1, 1:atlas.nu]
311-
@variable(model, x[1:N, 1:atlas_obj.nx])
312-
@variable(model, -atlas_obj.torque_limits[i] <= u[t=1:N-1,i=1:atlas.nu] <= atlas_obj.torque_limits[i])
313-
# ^ adapt the indexing if torque_limits is an array of length nu, etc.
314-
315-
# Objective: sum of squared error from x_ref
316-
@objective(model, Min, sum( (x[t,j] - x_ref[j])^2 for t in 2:N for j in 1:atlas_obj.nx ) )
317-
318-
for t in 1:(N-1)
319-
# Bindconstraint x[t+1, i] == f_i( [x[t,:]; u[t,:]]... )
320-
@constraint(model, x[t+1, :] == atlas_dynamics( convert.(NonlinearExpr, x[t, :]), convert.(NonlinearExpr,u[t, :])))
321-
end
322-
323-
# Possibly set an initial condition constraint: x[1, :] = x_ref
324-
@constraint(model, [j in 1:atlas_obj.nx], x[1,j] == X_start[j])
325-
326-
# Solve
327-
optimize!(model)
328-
return model, value.(x), value.(u)
329-
end
330-
331-
N = 2
332-
X_start = deepcopy(x_ref)
333-
X_start[atlas.nq + 5] = 1.3
334-
model, X_solve, U_solve = build_and_solve_mpc(atlas, x_ref, X_start, N; optimizer=optimizer_with_attributes(Ipopt.Optimizer,
335-
# "print_level" => 0,
336-
"linear_solver" => "ma97",
337-
"hessian_approximation" => "limited-memory",
338-
"max_iter" => 300,
339-
"mu_target" => 1e-8,
340-
)
341-
)
342-
termination_status(model)
343-
344-
# Now we can simulate the system with the controls U_solve
345-
X = [zeros(atlas.nx) for _ = 1:N];
346-
X[1] = deepcopy(x_ref);
347-
X[1][atlas.nq + 5] = 1.3; # Perturb i.c.
348-
349-
for k = 1:N - 1
350-
X[k + 1] = rk4(atlas, X[k], U_solve[k, :], h)
351-
end
352-
353-
animate!(atlas, mvis, X, Δt=h);
354-
355-
error = sum( norm(X_solve[t,:] .- X[t]) for t in 1:N )
356-
357-
358-
function foo!(q::AbstractVector{T}) where {T}
359-
q .= zero(T)
360-
end
320+
#X = [value.(x[t,:]) for t=1:N]
321+
#X = [X_solve[t, :] for t=1:N]
322+
#
323+
#animate!(atlas, mvis, X, Δt=h);
324+
#readline()
325+
#
326+
#
327+
################
328+
#include(joinpath(@__DIR__, "rigidbodyutils.jl"))
329+
#include(joinpath(@__DIR__, "atlas_utils.jl"))
330+
#
331+
#model = Model()
332+
#optimizer=optimizer_with_attributes(Ipopt.Optimizer,
333+
# # "print_level" => 0,
334+
# "hsllib" => HSL_jll.libhsl_path,
335+
# "linear_solver" => "MA27"
336+
#)
337+
#set_optimizer(model, optimizer)
338+
#
339+
#@variable(model, x[1:N, 1:atlas.nx])
340+
#@variable(model, -atlas.torque_limits[i] <= u[t=1:N-1,i=1:atlas.nu] <= atlas.torque_limits[i])
341+
#
342+
#dyn_result = dynamics(atlas, convert.(NonlinearExpr, x[1, :]), convert.(NonlinearExpr,u[1, :]))
343+
#
344+
#function atlas_dynamics(x, u)
345+
# h = 0.01
346+
# return rk4(atlas, x, u, h)
347+
#end
348+
#
349+
##function build_and_solve_mpc(atlas_obj::Atlas, x_ref::Vector{Float64}, X_start::Vector{Float64}, N::Int; optimizer=optimizer_with_attributes(Ipopt.Optimizer,
350+
## # "print_level" => 0,
351+
## "hsllib" => HSL_jll.libhsl_path,
352+
## "linear_solver" => "MA27"
353+
## )
354+
##)
355+
## model = Model()
356+
## set_optimizer(model, optimizer)
357+
##
358+
## # For demonstration, let's define:
359+
## # x[t=1:N, 1:atlas.nx]
360+
## # u[t=1:N-1, 1:atlas.nu]
361+
## @variable(model, x[1:N, 1:atlas_obj.nx])
362+
## @variable(model, -atlas_obj.torque_limits[i] <= u[t=1:N-1,i=1:atlas.nu] <= atlas_obj.torque_limits[i])
363+
## # ^ adapt the indexing if torque_limits is an array of length nu, etc.
364+
##
365+
## # Objective: sum of squared error from x_ref
366+
## @objective(model, Min, sum( (x[t,j] - x_ref[j])^2 for t in 2:N for j in 1:atlas_obj.nx ) )
367+
##
368+
## for t in 1:(N-1)
369+
## # Bindconstraint x[t+1, i] == f_i( [x[t,:]; u[t,:]]... )
370+
## @constraint(model, x[t+1, :] == atlas_dynamics( convert.(NonlinearExpr, x[t, :]), convert.(NonlinearExpr,u[t, :])))
371+
## end
372+
##
373+
## # Possibly set an initial condition constraint: x[1, :] = x_ref
374+
## @constraint(model, [j in 1:atlas_obj.nx], x[1,j] == X_start[j])
375+
##
376+
## # Solve
377+
## optimize!(model)
378+
## return model, value.(x), value.(u)
379+
##end
380+
#
381+
#N = 2
382+
#X_start = deepcopy(x_ref)
383+
#X_start[atlas.nq + 5] = 1.3
384+
#model, X_solve, U_solve = build_and_solve_mpc(atlas, x_ref, X_start, N; optimizer=optimizer_with_attributes(Ipopt.Optimizer,
385+
# # "print_level" => 0,
386+
# "linear_solver" => "ma97",
387+
# "hessian_approximation" => "limited-memory",
388+
# "max_iter" => 300,
389+
# "mu_target" => 1e-8,
390+
# "print_info_string" => "yes",
391+
# )
392+
#)
393+
#termination_status(model)
394+
#
395+
## Now we can simulate the system with the controls U_solve
396+
#X = [zeros(atlas.nx) for _ = 1:N];
397+
#X[1] = deepcopy(x_ref);
398+
#X[1][atlas.nq + 5] = 1.3; # Perturb i.c.
399+
#
400+
#for k = 1:N - 1
401+
# X[k + 1] = rk4(atlas, X[k], U_solve[k, :], h)
402+
#end
403+
#
404+
#animate!(atlas, mvis, X, Δt=h);
405+
#
406+
#error = sum( norm(X_solve[t,:] .- X[t]) for t in 1:N )
407+
#
408+
#
409+
#function foo!(q::AbstractVector{T}) where {T}
410+
# q .= zero(T)
411+
#end

examples/Atlas/atlas_ref.jld2

1.5 KB
Binary file not shown.

examples/Atlas/atlas_utils.jl

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ function dynamics(model::Atlas, x::AbstractVector{T1}, u::AbstractVector{T2}) wh
9696
T = promote_type(T1, T2)
9797
state = model.statecache[T]
9898
dyn_result = model.dynrescache[T]
99-
println("dyn_result.M: ", dyn_result.massmatrix)
99+
#println("dyn_result.M: ", dyn_result.massmatrix)
100100

101101
# Set the mechanism state
102102
copyto!(state, x)
@@ -107,6 +107,10 @@ function dynamics(model::Atlas, x::AbstractVector{T1}, u::AbstractVector{T2}) wh
107107
return [dyn_result.q̇; dyn_result.v̇]
108108
end
109109

110+
function explicit_euler(model::Atlas, x, u, h)
111+
return x + h * dynamics(model, x, u)
112+
end
113+
110114
function rk4(model::Atlas, x, u, h)
111115
k1 = dynamics(model, x, u)
112116
k2 = dynamics(model, x + h/2*k1, u)
@@ -136,4 +140,4 @@ function animate!(model::Atlas, mvis::MechanismVisualizer, qs; Δt=0.001)
136140
MeshCat.setanimation!(mvis, anim)
137141

138142
return anim
139-
end
143+
end

0 commit comments

Comments
 (0)