Skip to content

Commit 5660076

Browse files
ferrolhoHenrique Ferrolho
authored andcommitted
[WIP] Add ability to constrain the orientation of mechanism links
1 parent 181d407 commit 5660076

File tree

8 files changed

+164
-13
lines changed

8 files changed

+164
-13
lines changed

Project.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ PrecompileTools = "aea7be01-6a6a-4083-8856-8a6e6704d82a"
1818
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
1919
Requires = "ae029012-a4dd-5104-9daa-d747884805df"
2020
RigidBodyDynamics = "366cf18f-59d5-5db9-a4de-86a9f6786172"
21+
Rotations = "6038ab10-8711-5258-84ad-4b1120ba62dc"
2122
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
2223
SparseDiffTools = "47a9eef4-7e08-11e9-0b38-333d64bd3804"
2324
StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
@@ -34,6 +35,7 @@ Plots = "1"
3435
PrecompileTools = "1"
3536
Requires = "1"
3637
RigidBodyDynamics = "2"
38+
Rotations = "1"
3739
SparseDiffTools = "2"
3840
StaticArrays = "1"
3941
julia = "1.10"

interoperability/python_calls_julia.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ def init():
1212
"""
1313
# Load package dependencies
1414
jl.seval("using MeshCat")
15+
jl.seval("using Rotations")
1516
jl.seval("using TORA")
1617

1718
# Call the greet() function from the TORA.jl package
@@ -55,7 +56,7 @@ def main():
5556

5657
jl_robot = init()
5758

58-
trajectory_discretisation = 100 # in Hertz
59+
trajectory_discretisation = 10 # in Hertz
5960
trajectory_duration = 2.0 # in seconds
6061

6162
trajectory_num_knots = int(trajectory_discretisation * trajectory_duration) + 1 # in units (number of knots)
@@ -95,11 +96,18 @@ def main():
9596
jl.TORA.fix_joint_velocities_b(jl_problem, jl_robot, jl_problem.num_knots, np.zeros(jl_robot.n_v))
9697

9798
# Define the end-effector position that we want to achieve
98-
py_eff_pos = [0.5, 0.2, 0.1]
99+
py_eff_pos = [0.5, 0.2, 0.8]
99100

100101
# Constrain the end-effector position at the last knot of the trajectory
101102
jl.TORA.constrain_ee_position_b(jl_problem, jl_problem.num_knots, py_eff_pos)
102103

104+
# Define the end-effector orientation that we want to achieve
105+
body_name = "panda_link7" # this is the fixed parent link of "panda_hand_tcp"
106+
jl_quaternion = jl.QuatRotation(0.27, 0.65, 0.27, 0.65) # Quaternion order is (w, x, y, z)
107+
108+
# Constrain the end-effector orientation at the last knot of the trajectory
109+
jl.TORA.add_constraint_body_orientation_b(jl_problem, jl_robot, body_name, jl_problem.num_knots, jl_quaternion)
110+
103111
# Show a summary of the problem instance (this can be commented out)
104112
jl.TORA.show_problem_info(jl_problem)
105113

interoperability/python_setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
# Add package dependencies
1313
jl.seval('Pkg.add("MeshCat")')
14+
jl.seval('Pkg.add("Rotations")')
1415

1516
# Develop the local package of TORA.jl
1617
jl.seval('Pkg.develop(path="../../TORA.jl")')

src/TORA.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ using NPZ
1010
using Plots
1111
using Requires
1212
using RigidBodyDynamics
13+
using Rotations
1314
using SparseArrays
1415
using SparseDiffTools
1516
using StaticArrays

src/constraints/end_effector.jl

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,17 @@ function show_ee_path(vis::Visualizer, ee_positions::Matrix{Float64})
6464
setobject!(vis["ee path"], Object(PointCloud(points), material, "Line"))
6565
nothing
6666
end
67+
68+
function body_orientation!(dx, robot, x::AbstractVector{T}) where {T}
69+
state = robot.statecache[T]
70+
set_configuration!(state, x)
71+
72+
body_tf = transform_to_root(state, robot.frame_ee)
73+
mrp = MRP(rotation(body_tf))
74+
75+
dx[1] = mrp.x
76+
dx[2] = mrp.y
77+
dx[3] = mrp.z
78+
79+
dx
80+
end

src/precompile.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ using PrecompileTools: @setup_workload, @compile_workload
3939
fix_joint_velocities!(problem, robot, 1, zeros(robot.n_v))
4040
constrain_ee_position!(problem, 1, zeros(3))
4141

42+
body_name = "panda_link7" # this is the fixed parent link of "panda_hand_tcp"
43+
quaternion = QuatRotation(0.27, 0.65, 0.27, 0.65)
44+
add_constraint_body_orientation!(problem, robot, body_name, 1, quaternion)
45+
4246
initial_guess = zeros(problem.num_knots * (robot.n_q + robot.n_v + robot.n_τ) - robot.n_τ)
4347

4448
cpu_time, x, solver_log = solve_with_ipopt(problem, robot, initial_guess=initial_guess, user_options=ipopt_options, use_inv_dyn=false, minimise_τ=false)

src/problem.jl

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ mutable struct Problem
2222

2323
ee_pos::Dict{Int64,Point{3,Float64}} # End-effector target positions
2424

25+
# Dictionaries indexing: knot → body_name → value
26+
constraints_body_orientation::Dict{Int64,Dict{String,Rotation}}
27+
2528
# Jacobian data (e.g., for sparse Jacobian of dynamics with AD)
2629
const jacdata_fwd_dyn::JacobianData
2730
const jacdata_inv_dyn::JacobianData
@@ -58,6 +61,7 @@ mutable struct Problem
5861
Dict{Int64,Vector{Float64}}(),
5962
Dict{Int64,Vector{Float64}}(),
6063
Dict{Int64,Point{3,Float64}}(),
64+
Dict{Int64,Dict{String,Rotation}}(),
6165
jacdata_fwd_dyn,
6266
jacdata_inv_dyn,
6367
jacdata_ee_position
@@ -121,6 +125,55 @@ function constrain_ee_position!(problem::Problem, knot, position)
121125
return problem
122126
end
123127

128+
"""
129+
add_constraint_body_orientation!(problem, robot, body_name, knot, rotation; force=false)
130+
131+
Constrain the orientation of a body (kinematic link) of the robot.
132+
133+
See also: [`add_constraint_body_position!`](@ref)
134+
"""
135+
function add_constraint_body_orientation!(problem::Problem, robot::Robot, body_name::String, knot::Integer, rotation::Rotation; force=false)
136+
@assert body_name map(x -> x.name, bodies(robot.mechanism))
137+
@assert 1 <= knot <= problem.num_knots
138+
139+
d = problem.constraints_body_orientation
140+
141+
if !haskey(d, knot)
142+
d[knot] = Dict{String,Rotation}()
143+
end
144+
145+
dₖ = d[knot]
146+
147+
if !haskey(dₖ, body_name) || force
148+
dₖ[body_name] = rotation
149+
elseif haskey(dₖ, body_name)
150+
@warn """
151+
You tried to add a constraint for `$(body_name)` at knot=$(knot),
152+
but you have already defined a constraint for that knot before.
153+
If you wish to overwrite previous constraints, use `force=true`.
154+
"""
155+
end
156+
157+
return problem
158+
end
159+
160+
"""
161+
body_orientation_constraints_per_knot(problem)
162+
163+
Count number of transcription constraints needed to enforce the
164+
orientation constraints of the high-level problem definition.
165+
"""
166+
function body_orientation_constraints_per_knot(problem::Problem)
167+
map(1:problem.num_knots) do knot
168+
counter = 0
169+
dₖ = get(problem.constraints_body_orientation, knot, Dict())
170+
for (body_name, rotation) dₖ
171+
counter += 3 # MRP values
172+
end
173+
counter
174+
end
175+
end
176+
124177
"""
125178
show_problem_info(problem)
126179
@@ -129,12 +182,15 @@ Output a summary of the problem, including the number of knots with constraints.
129182
function show_problem_info(problem::Problem)
130183
t = (problem.num_knots - 1) * problem.dt
131184

185+
println("Discretization ....................................... $(problem.num_knots) knots")
132186
println("Motion duration ...................................... $(t) seconds")
133-
println("Number of knots ...................................... $(problem.num_knots)")
187+
println("Time step length ..................................... $(problem.dt) seconds")
188+
println()
134189
println("Number of knots with constrained joint positions ..... $(length(problem.fixed_q))")
135190
println("Number of knots with constrained joint velocities .... $(length(problem.fixed_v))")
136191
println("Number of knots with constrained joint torques ....... $(length(problem.fixed_τ))")
137192
println("Number of knots with constrained ee position ......... $(length(problem.ee_pos))")
193+
println("Number of knots with body orientation constraints .... $(length(problem.constraints_body_orientation))")
138194
end
139195

140196
"""

src/transcription/ipopt.jl

Lines changed: 75 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -73,11 +73,13 @@ function solve_with_ipopt(problem::Problem, robot::Robot;
7373

7474
use_m₁ = true # nonlinear equality: dynamics
7575
use_m₂ = true # nonlinear equality: ee xyz-position
76+
use_m₃ = true # nonlinear equality: ee rotation
7677

7778
m₁ = !use_m₁ ? 0 : (robot.n_q + robot.n_v) * (problem.num_knots - 1) # equations of motion
7879
m₂ = !use_m₂ ? 0 : 3 * length(problem.ee_pos) # xyz-position (3 NL equality)
80+
m₃ = !use_m₃ ? 0 : body_orientation_constraints_per_knot(problem) |> sum
7981

80-
m = m₁ + m₂ # total number of constraints
82+
m = m₁ + m₂ + m₃ # total number of constraints
8183

8284
g_L, g_U = Float64[], Float64[]
8385

@@ -93,7 +95,28 @@ function solve_with_ipopt(problem::Problem, robot::Robot;
9395
append!(g_U, vec(con_ee))
9496
end
9597

96-
@assert length(g_L) == length(g_U)
98+
body_name = "panda_link7" # TODO ... hard-coded for now.
99+
# We want to iterate only through the constrained knots for this robot body/link.
100+
knots = filter(1:problem.num_knots) do knotᵢ
101+
knotᵢ keys(problem.constraints_body_orientation) &&
102+
body_name keys(problem.constraints_body_orientation[knotᵢ])
103+
end
104+
105+
jacdata = JacobianData((out, x) -> body_orientation!(out, robot, x), rand(3), rand(robot.n_q))
106+
107+
if use_m₃
108+
# These are the target values for the equalities of Knitro.
109+
con_vals = mapreduce(hcat, knots; init=Matrix{Float64}(undef, 3, 0)) do k
110+
dₖ = problem.constraints_body_orientation[k]
111+
mrp = MRP(dₖ[body_name])
112+
[mrp.x, mrp.y, mrp.z]
113+
end
114+
115+
append!(g_L, con_vals)
116+
append!(g_U, con_vals)
117+
end
118+
119+
@assert length(g_L) == length(g_U) == m
97120
@assert eltype(g_L) == eltype(g_U) == Float64
98121

99122
# dimension of each mesh point
@@ -122,14 +145,25 @@ function solve_with_ipopt(problem::Problem, robot::Robot;
122145
end
123146

124147
if use_m₂
125-
length_c = 3
148+
length_c = size(problem.jacdata_ee_position.jac, 1) # length of constraint (per knot)
126149
for (i, k) = enumerate(sort(collect(keys(problem.ee_pos))))
127150
ind_vars = range(1 + (k - 1) * nₓ, length=robot.n_q) # indices of the decision variables
128151
offset_con = (i - 1) * length_c + (m₁)
129152
ind_cons = (1:length_c) .+ offset_con
130153
@views ee_position!(g[ind_cons], robot, x[ind_vars]) # constraint evaluation at that point
131154
end
132155
end
156+
157+
if use_m₃
158+
length_c = size(jacdata.jac, 1) # length of constraint (per knot)
159+
offset_con = m₁ + m₂
160+
for i = knots .- 1
161+
ind_cons = (1:length_c) .+ offset_con # Indices of the respective constraints
162+
ind_vars = range(1 + i * nₓ, length=robot.n_q) # Indices of the decision variables
163+
@views body_orientation!(g[ind_cons], robot, x[ind_vars]) # Constraint evaluation at that point
164+
offset_con += length_c
165+
end
166+
end
133167
end
134168

135169
jacIndexConsCB = Cint[]
@@ -167,6 +201,22 @@ function solve_with_ipopt(problem::Problem, robot::Robot;
167201
@assert length(jacIndexConsCB) == length(jacIndexVarsCB)
168202
end
169203

204+
if use_m₃
205+
ind_offset = m₁ + m₂
206+
for k = knots
207+
inds = rowvals(jacdata.jac) .+ ind_offset
208+
ind_offset += size(jacdata.jac, 1)
209+
append!(jacIndexConsCB, inds)
210+
end
211+
212+
for k = knots
213+
vals = vcat([fill(j + (k - 1) * nₓ, length(nzrange(jacdata.jac, j)))
214+
for j = 1:size(jacdata.jac, 2)]...)
215+
append!(jacIndexVarsCB, vals)
216+
end
217+
218+
@assert length(jacIndexConsCB) == length(jacIndexVarsCB)
219+
end
170220

171221
function eval_jac_g(x, rows, cols, values::Union{Nothing,Vector{Float64}})
172222
if isnothing(values)
@@ -186,22 +236,36 @@ function solve_with_ipopt(problem::Problem, robot::Robot;
186236
# Evaluate the Jacobian at that point
187237
jacdata_dyn(x[ind_vars])
188238

189-
# Pass the Jacobian to Knitro
190-
offset_jac = i * jacdata_dyn.jac_length
191-
ind_jac = (1:jacdata_dyn.jac_length) .+ offset_jac
239+
# Pass the Jacobian to Ipopt
240+
ind_jac = (1:jacdata_dyn.jac_length) .+ offset_prev
192241
values[ind_jac] = nonzeros(jacdata_dyn.jac)
242+
offset_prev += jacdata_dyn.jac_length
193243
end
194-
offset_prev += (problem.num_knots - 1) * jacdata_dyn.jac_length
195244
end
196245

197246
if use_m₂
198247
# End-effector constraints
199248
for (i, k) = enumerate(sort(collect(keys(problem.ee_pos))))
200249
ind_qᵢ = range(1 + (k - 1) * nₓ, length=robot.n_q) # indices of the decision variables
201250
problem.jacdata_ee_position(x[ind_qᵢ]) # jacobian evaluation at that point
202-
offset_jac = (i - 1) * problem.jacdata_ee_position.jac_length + offset_prev
203-
ind_jac = (1:problem.jacdata_ee_position.jac_length) .+ offset_jac
204-
values[ind_jac] = nonzeros(problem.jacdata_ee_position.jac) # pass jacobian to Knitro
251+
252+
# Pass the Jacobian to Ipopt
253+
ind_jac = (1:problem.jacdata_ee_position.jac_length) .+ offset_prev
254+
values[ind_jac] = nonzeros(problem.jacdata_ee_position.jac)
255+
offset_prev += problem.jacdata_ee_position.jac_length
256+
end
257+
end
258+
259+
if use_m₃
260+
# End-effector constraints (rotation)
261+
for k = knots
262+
ind_qᵢ = range(1 + (k - 1) * nₓ, length=robot.n_q) # Indices of the decision variables
263+
jacdata(x[ind_qᵢ]) # Evaluate the Jacobian at that point
264+
265+
# Pass the Jacobian to Ipopt
266+
ind_jac = (1:jacdata.jac_length) .+ offset_prev
267+
values[ind_jac] = nonzeros(jacdata.jac)
268+
offset_prev += jacdata.jac_length
205269
end
206270
end
207271
end
@@ -244,6 +308,7 @@ function solve_with_ipopt(problem::Problem, robot::Robot;
244308
nele_jac = 0
245309
nele_jac += !use_m₁ ? 0 : jacdata_dyn.jac_length * (problem.num_knots - 1)
246310
nele_jac += !use_m₂ ? 0 : problem.jacdata_ee_position.jac_length * length(problem.ee_pos)
311+
nele_jac += !use_m₃ ? 0 : jacdata.jac_length * length(knots)
247312

248313
prob = Ipopt.CreateIpoptProblem(n, vec(x_L), vec(x_U), m, g_L, g_U, nele_jac, 0,
249314
eval_f, eval_g, eval_grad_f, eval_jac_g, nothing)

0 commit comments

Comments
 (0)