Skip to content

Commit d79f8b2

Browse files
Merge pull request #592 from control-toolbox/sparsity-irk-stagewise
Manual sparsity patterns for generic IRK with stagewise controls
2 parents 44e76c8 + 3be4b24 commit d79f8b2

4 files changed

Lines changed: 201 additions & 34 deletions

File tree

src/ode/irk.jl

Lines changed: 19 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,7 @@ function DOCP_Jacobian_pattern(docp::DOCP{<: GenericIRK})
341341
path_end = c_offset + c_block
342342

343343
# contiguous variables blocks will be used when possible
344-
# x_i (l_i) u_i k_i x_i+1 (l_i+1)
344+
# x_i u_i k_ij x_i+1
345345
var_offset = (i-1)*disc._step_variables_block
346346
xi_start = var_offset + 1
347347
xi_end = var_offset + dims.NLP_x
@@ -350,16 +350,14 @@ function DOCP_Jacobian_pattern(docp::DOCP{<: GenericIRK})
350350
ki_start = var_offset + dims.NLP_x + dims.NLP_u + 1
351351
ki_end = var_offset + disc._step_variables_block
352352
xip1_end = var_offset + disc._step_variables_block + dims.NLP_x
353-
li = var_offset + dims.NLP_x
354-
lip1 = var_offset + disc._step_variables_block + dims.NLP_x
355353

356354
# 1.1 state eq 0 = x_i+1 - (x_i + h_i sum_j b_j k_ij)
357355
# depends on x_i, k_ij, x_i+1, and v for h_i in variable times case !
358-
# skip l_i, u_i; should skip k_i[n+1] also but annoying...
356+
# skip u_i; should skip k_i[n+1] also but annoying...
359357
add_nonzero_block!(Is, Js, dyn_start, dyn_end, xi_start, xi_end)
360358
add_nonzero_block!(Is, Js, dyn_start, dyn_end, ki_start, xip1_end)
361359
add_nonzero_block!(Is, Js, dyn_start, dyn_end, v_start, v_end)
362-
# 1.2 lagrange part l_i+1 = l_i + h_i (sum_j b_j k_ij)[n+1]
360+
#= 1.2 lagrange part l_i+1 = l_i + h_i (sum_j b_j k_ij)[n+1]
363361
# depends on l_i, k_ij[n+1], l_i+1, and v for h_i in variable times case !
364362
if docp.flags.lagrange
365363
add_nonzero_block!(Is, Js, dyn_lag, li)
@@ -369,19 +367,17 @@ function DOCP_Jacobian_pattern(docp::DOCP{<: GenericIRK})
369367
add_nonzero_block!(Is, Js, dyn_lag, kij_l)
370368
end
371369
add_nonzero_block!(Is, Js, dyn_lag, dyn_lag, v_start, v_end)
372-
end
370+
end=#
373371

374-
# 1.3 stage equations k_ij = f(t_ij, x_ij, u_i, v) (with lagrange part)
372+
# 1.3 stage equations k_ij = f(t_ij, x_ij, u_i, v)
375373
# with x_ij = x_i + sum_l a_il k_jl
376-
# depends on x_i, u_i, k_i, and v; skip l_i (could skip k_ij[n+1] too...)
377-
add_nonzero_block!(Is, Js, stage_start, stage_end, xi_start, xi_end)
378-
add_nonzero_block!(Is, Js, stage_start, stage_end, ui_start, ki_end)
374+
# depends on x_i, u_i, k_ij, and v; (could skip k_ij[n+1] too...)
375+
add_nonzero_block!(Is, Js, stage_start, stage_end, xi_start, ki_end)
379376
add_nonzero_block!(Is, Js, stage_start, stage_end, v_start, v_end)
380377

381378
# 1.4 path constraint g(t_i, x_i, u_i, v)
382-
# depends on x_i, u_i, v; skip l_i
383-
add_nonzero_block!(Is, Js, path_start, path_end, xi_start, xi_end)
384-
add_nonzero_block!(Is, Js, path_start, path_end, ui_start, ui_end)
379+
# depends on x_i, u_i, v
380+
add_nonzero_block!(Is, Js, path_start, path_end, xi_start, ui_end)
385381
add_nonzero_block!(Is, Js, path_start, path_end, v_start, v_end)
386382
end
387383

@@ -408,10 +404,10 @@ function DOCP_Jacobian_pattern(docp::DOCP{<: GenericIRK})
408404
add_nonzero_block!(Is, Js, c_offset+1, c_offset+c_block, x0_start, x0_end)
409405
add_nonzero_block!(Is, Js, c_offset+1, c_offset+c_block, xf_start, xf_end)
410406
add_nonzero_block!(Is, Js, c_offset+1, c_offset+c_block, v_start, v_end)
411-
# 3.4 null initial condition for lagrangian cost state l0
407+
#= 3.4 null initial condition for lagrangian cost state l0
412408
if docp.flags.lagrange
413409
add_nonzero_block!(Is, Js, docp.dim_NLP_constraints, dims.NLP_x)
414-
end
410+
end=#
415411

416412
# build and return sparse matrix
417413
nnzj = length(Is)
@@ -441,8 +437,8 @@ function DOCP_Hessian_pattern(docp::DOCP{<: GenericIRK})
441437
# 0. objective
442438
# 0.1 mayer cost (x0, xf, v)
443439
# -> grouped with term 3. for boundary conditions
444-
# 0.2 lagrange case (lf)
445-
# -> 2nd order term is zero
440+
# 0.2 lagrange case sum h_i l(ti, xi, ui, v)
441+
# -> included in stage equations terms see 1.2
446442

447443
# 1. main loop over steps
448444
# 1.0 v / v term
@@ -451,7 +447,7 @@ function DOCP_Hessian_pattern(docp::DOCP{<: GenericIRK})
451447
for i in 1:docp.time.steps
452448

453449
# contiguous variables blocks will be used when possible
454-
# x_i (l_i) u_i k_i x_i+1 (l_i+1)
450+
# x_i u_i k_i x_i+1
455451
var_offset = (i-1)*disc._step_variables_block
456452
xi_start = var_offset + 1
457453
xi_end = var_offset + dims.NLP_x
@@ -462,19 +458,14 @@ function DOCP_Hessian_pattern(docp::DOCP{<: GenericIRK})
462458

463459
# 1.1 state eq 0 = x_i+1 - (x_i + h_i sum_j b_j k_ij)
464460
# -> 2nd order terms are zero
465-
# 1.2 lagrange part 0 = l_i+1 - (l_i + h_i (sum_j b_j k_ij[n+1]))
466-
# -> 2nd order terms are zero
467461

468-
# 1.3 stage equations 0 = k_ij - f(t_ij, x_ij, u_i, v) (with lagrange part)
462+
# 1.2 stage equations 0 = k_ij - f(t_ij, x_ij, u_i, v)
469463
# with x_ij = x_i + sum_l a_il k_jl
470-
# 2nd order terms depend on x_i, u_i, k_i, and v; skip l_i (could skip k_ij[n+1]...)
471-
add_nonzero_block!(Is, Js, xi_start, xi_end, xi_start, xi_end)
472-
add_nonzero_block!(Is, Js, ui_start, ki_end, ui_start, ki_end)
473-
add_nonzero_block!(Is, Js, xi_start, xi_end, ui_start, ki_end; sym=true)
474-
add_nonzero_block!(Is, Js, xi_start, xi_end, v_start, v_end; sym=true)
475-
add_nonzero_block!(Is, Js, ui_start, ki_end, v_start, v_end; sym=true)
464+
# 2nd order terms depend on x_i, u_i, k_i, and v; (could distinguish each j...)
465+
add_nonzero_block!(Is, Js, xi_start, ki_end, xi_start, ki_end)
466+
add_nonzero_block!(Is, Js, xi_start, ki_end, v_start, v_end; sym=true)
476467

477-
# 1.4 path constraint g(t_i, x_i, u_i, v)
468+
# 1.3 path constraint g(t_i, x_i, u_i, v)
478469
# -> included in 1.3
479470
end
480471

@@ -498,9 +489,6 @@ function DOCP_Hessian_pattern(docp::DOCP{<: GenericIRK})
498489
x0_end = dims.NLP_x
499490
add_nonzero_block!(Is, Js, x0_start, x0_end, xf_start, xf_end; sym=true)
500491

501-
# 3.1 null initial condition for lagrangian cost state l0
502-
# -> 2nd order term is zero
503-
504492
# build and return sparse matrix
505493
nnzj = length(Is)
506494
Vs = ones(Bool, nnzj)

src/ode/irk_stagewise.jl

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -458,3 +458,181 @@ function stepStateConstraints!(
458458

459459
return nothing
460460
end
461+
462+
463+
"""
464+
$(TYPEDSIGNATURES)
465+
466+
Build sparsity pattern for Jacobian of constraints
467+
"""
468+
function DOCP_Jacobian_pattern(docp::DOCP{<: GenericIRKStagewise})
469+
disc = disc_model(docp)
470+
dims = docp.dims
471+
472+
# vector format for sparse matrix
473+
Is = Vector{Int}(undef, 0)
474+
Js = Vector{Int}(undef, 0)
475+
476+
s = disc.stage
477+
478+
# index alias for v
479+
v_start = docp.dim_NLP_variables - dims.NLP_v + 1
480+
v_end = docp.dim_NLP_variables
481+
482+
# 1. main loop over steps
483+
for i in 1:docp.time.steps
484+
485+
# constraints block and offset: state equation, stage equations, path constraints
486+
c_block = disc._state_stage_eqs_block + disc._step_pathcons_block
487+
c_offset = (i-1)*c_block
488+
dyn_start = c_offset + 1
489+
dyn_end = c_offset + dims.NLP_x
490+
dyn_lag = c_offset + dims.NLP_x
491+
stage_start = c_offset + dims.NLP_x + 1
492+
stage_end = c_offset + (s+1) * dims.NLP_x
493+
path_start = c_offset + (s+1) * dims.NLP_x + 1
494+
path_end = c_offset + c_block
495+
496+
# contiguous variables blocks will be used when possible
497+
# x_i u_ij k_ij x_i+1
498+
var_offset = (i-1)*disc._step_variables_block
499+
xi_start = var_offset + 1
500+
xi_end = var_offset + dims.NLP_x
501+
ui_start = var_offset + dims.NLP_x + 1
502+
ui_end = var_offset + dims.NLP_x + dims.NLP_u*s
503+
ki_start = var_offset + dims.NLP_x + dims.NLP_u*s + 1
504+
ki_end = var_offset + disc._step_variables_block
505+
xip1_end = var_offset + disc._step_variables_block + dims.NLP_x
506+
507+
# 1.1 state eq 0 = x_i+1 - (x_i + h_i sum_j b_j k_ij)
508+
# depends on x_i, k_ij, x_i+1, and v for h_i in variable times case !
509+
# skip u_ij; should skip k_i[n+1] also but annoying...
510+
add_nonzero_block!(Is, Js, dyn_start, dyn_end, xi_start, xi_end)
511+
add_nonzero_block!(Is, Js, dyn_start, dyn_end, ki_start, xip1_end)
512+
add_nonzero_block!(Is, Js, dyn_start, dyn_end, v_start, v_end)
513+
514+
# 1.3 stage equations k_ij = f(t_ij, x_ij, u_ij, v)
515+
# with x_ij = x_i + sum_l a_il k_jl
516+
# depends on x_i, u_ij, k_ij, and v; (we could distinguish each j...)
517+
add_nonzero_block!(Is, Js, stage_start, stage_end, xi_start, ki_end)
518+
add_nonzero_block!(Is, Js, stage_start, stage_end, v_start, v_end)
519+
520+
# 1.4 path constraint g(t_i, x_i, u_i, v)
521+
# depends on x_i, u_i (check actual value used), v;
522+
add_nonzero_block!(Is, Js, path_start, path_end, xi_start, ui_end)
523+
add_nonzero_block!(Is, Js, path_start, path_end, v_start, v_end)
524+
end
525+
526+
# 2. final path constraints (xf, uf, v)
527+
c_offset = docp.time.steps * (disc._state_stage_eqs_block + disc._step_pathcons_block)
528+
c_block = disc._step_pathcons_block
529+
var_offset = docp.time.steps*disc._step_variables_block
530+
xf_start = var_offset + 1
531+
xf_end = var_offset + dims.NLP_x
532+
# NB convention u(tf) = U_N-1
533+
uf_start = var_offset - disc._step_variables_block + dims.NLP_x + 1
534+
uf_end = var_offset - disc._step_variables_block + dims.NLP_x + dims.NLP_u*s
535+
add_nonzero_block!(Is, Js, c_offset+1, c_offset+c_block, xf_start, xf_end)
536+
add_nonzero_block!(Is, Js, c_offset+1, c_offset+c_block, uf_start, uf_end)
537+
add_nonzero_block!(Is, Js, c_offset+1, c_offset+c_block, v_start, v_end)
538+
539+
# 3. boundary constraints (x0, xf, v)
540+
c_offset =
541+
docp.time.steps * (disc._state_stage_eqs_block + disc._step_pathcons_block) +
542+
disc._step_pathcons_block
543+
c_block = dims.boundary_cons
544+
x0_start = 1
545+
x0_end = dims.NLP_x
546+
add_nonzero_block!(Is, Js, c_offset+1, c_offset+c_block, x0_start, x0_end)
547+
add_nonzero_block!(Is, Js, c_offset+1, c_offset+c_block, xf_start, xf_end)
548+
add_nonzero_block!(Is, Js, c_offset+1, c_offset+c_block, v_start, v_end)
549+
# 3.4 null initial condition for lagrangian cost state l0
550+
if docp.flags.lagrange
551+
add_nonzero_block!(Is, Js, docp.dim_NLP_constraints, dims.NLP_x)
552+
end
553+
554+
# build and return sparse matrix
555+
nnzj = length(Is)
556+
Vs = ones(Bool, nnzj)
557+
return SparseArrays.sparse(Is, Js, Vs, docp.dim_NLP_constraints, docp.dim_NLP_variables)
558+
end
559+
560+
"""
561+
$(TYPEDSIGNATURES)
562+
563+
Build sparsity pattern for Hessian of Lagrangian
564+
"""
565+
function DOCP_Hessian_pattern(docp::DOCP{<: GenericIRKStagewise})
566+
disc = disc_model(docp)
567+
dims = docp.dims
568+
569+
# NB. need to provide full pattern for coloring, not just upper/lower part
570+
Is = Vector{Int}(undef, 0)
571+
Js = Vector{Int}(undef, 0)
572+
573+
s = disc.stage
574+
575+
# index alias for v
576+
v_start = docp.dim_NLP_variables - dims.NLP_v + 1
577+
v_end = docp.dim_NLP_variables
578+
579+
# 0. objective
580+
# 0.1 mayer cost (x0, xf, v)
581+
# -> grouped with term 3. for boundary conditions
582+
# 0.2 lagrange case sum h_i l(ti, xi, ui, v)
583+
# -> included in stage equations terms see 1.2
584+
585+
# 1. main loop over steps
586+
# 1.0 v / v term
587+
add_nonzero_block!(Is, Js, v_start, v_end, v_start, v_end)
588+
589+
for i in 1:docp.time.steps
590+
591+
# contiguous variables blocks will be used when possible
592+
# x_i u_ij k_ij x_i+1
593+
var_offset = (i-1)*disc._step_variables_block
594+
xi_start = var_offset + 1
595+
xi_end = var_offset + dims.NLP_x
596+
ui_start = var_offset + dims.NLP_x + 1
597+
ui_end = var_offset + dims.NLP_x + dims.NLP_u*s
598+
ki_start = var_offset + dims.NLP_x + dims.NLP_u*s + 1
599+
ki_end = var_offset + disc._step_variables_block
600+
601+
# 1.1 state eq 0 = x_i+1 - (x_i + h_i sum_j b_j k_ij)
602+
# -> 2nd order terms are zero
603+
604+
# 1.2 stage equations 0 = k_ij - f(t_ij, x_ij, u_ij, v)
605+
# with x_ij = x_i + sum_l a_il k_jl
606+
# 2nd order terms depend on x_i, u_ij, k_ij, and v; (we could distinguish each j...)
607+
add_nonzero_block!(Is, Js, xi_start, ki_end, xi_start, ki_end)
608+
add_nonzero_block!(Is, Js, xi_start, ki_end, v_start, v_end; sym=true)
609+
610+
# 1.3 path constraint g(t_i, x_i, u_i, v)
611+
# -> included in 1.3
612+
end
613+
614+
# 2. final path constraints (xf, uf, v) (assume present) +++ done in 1.4 above ?
615+
var_offset = docp.time.steps * disc._step_variables_block
616+
xf_start = var_offset + 1
617+
xf_end = var_offset + dims.NLP_x
618+
# NB convention u(tf) = U_N-1
619+
uf_start = var_offset - disc._step_variables_block + dims.NLP_x + 1
620+
uf_end = var_offset - disc._step_variables_block + dims.NLP_x + dims.NLP_u*s
621+
add_nonzero_block!(Is, Js, xf_start, xf_end, xf_start, xf_end)
622+
add_nonzero_block!(Is, Js, uf_start, uf_end, uf_start, uf_end)
623+
add_nonzero_block!(Is, Js, xf_start, xf_end, uf_start, uf_end; sym=true)
624+
add_nonzero_block!(Is, Js, xf_start, uf_end, v_start, v_end; sym=true)
625+
add_nonzero_block!(Is, Js, uf_start, uf_end, v_start, v_end; sym=true)
626+
627+
# 3. boundary constraints (x0, xf, v) or mayer cost g0(x0, xf, v) (assume present)
628+
# -> x0 / x0, x0 / v terms included in first loop iteration
629+
# -> xf / xf, xf / v terms included in 2.
630+
x0_start = 1
631+
x0_end = dims.NLP_x
632+
add_nonzero_block!(Is, Js, x0_start, x0_end, xf_start, xf_end; sym=true)
633+
634+
# build and return sparse matrix
635+
nnzj = length(Is)
636+
Vs = ones(Bool, nnzj)
637+
return SparseArrays.sparse(Is, Js, Vs, docp.dim_NLP_variables, docp.dim_NLP_variables)
638+
end

test/benchmark.jl

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -201,10 +201,9 @@ end
201201
# perform benchmark
202202
function bench(;
203203
verbose=1,
204-
target_list=:all,
204+
target_list=:easy,
205205
grid_size_list=[250, 500, 1000],
206206
solver=:ipopt,
207-
timer=false,
208207
return_sols=false,
209208
save_sols=false,
210209
timer=false,

test/manual_test.jl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ include("./problems/double_integrator.jl")
99
include("./problems/truck_trailer.jl")
1010

1111
sol = solve_problem(goddard(); display=true, scheme=:gauss_legendre_2)
12-
plot(sol)
12+
sol1 = solve_problem(goddard(); display=true, scheme=:gauss_legendre_2, adnlp_backend=:manual)
13+
sol2 = solve_problem(goddard(); display=true, scheme=:gauss_legendre_2_constant_control, adnlp_backend=:manual)
14+
1315

1416
#=sol0 = solve_problem(truck_trailer(); display=false)
1517
println("J ", objective(sol0), " tf ", variable(sol0), " iter ", iterations(sol0))

0 commit comments

Comments
 (0)