Skip to content

Commit f7cd141

Browse files
Manual sparse pattern for Jacobian and Hessian (#348)
* quick bench * moved pics * try to give jacobian sparse pattern * probable bug in jac pattern * solve ok for simple integrator but given patter is a bit worse than computed one dur to lagrange cost state * bench ok * improved jacobian pattern a bit (lagrange state) * try hessian * disable hessian for tests * use bool matrix for sparsity pattern generation * try to build sparse jac form indexes and avlues sets instead of dense matrix ? * hessian pattern (check cross terms) * goddard_all ok with manual sparse pattern * bench for manual sparse patterns * more tests * removed robbins from benchmark; updated markdown * updated markdown * updated markdown * markdown * removed no_hessian option * todo: midpoint and irk * manual sparsity for IRK (constant control) * todo: debug irk * fixed bug in jacobian pattern for irk * bench ok for irk with manual sparsity * todo: try to build sparse matrix directly (bool matrix too big for algal GL2 5000 for instance) * prepare pattern in sparse format * vector format for sparse matrix building (trapeze) * index vectors for IRK * todo: debug jacobian for irk * bench ok for GL2 manual * ok * bench midpoint ok * cleaned jacobian functions * code and markdown cleanup * md * jump * disable unused AD backends * update tests * tests update * ready for merge * index * api update * removed type qualif for OCP description in direct_transcription
1 parent 78a1c3f commit f7cd141

18 files changed

Lines changed: 913 additions & 205 deletions

Project.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
1010
HSL = "34c5aeac-e683-54a6-a0e9-6e0fdc586c50"
1111
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
1212
NLPModels = "a4795742-8479-5a88-8948-cc11e1c8c1a6"
13+
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
1314

1415
[weakdeps]
1516
MadNLP = "2621e9c9-9eb4-46b1-8089-e8c72242dfb6"
@@ -27,4 +28,5 @@ HSL = "0.5"
2728
MadNLP = "0.8"
2829
NLPModels = "0.21"
2930
NLPModelsIpopt = "0.10"
31+
SparseArrays = "1.10.0"
3032
julia = "1.10"

docs/src/index.md

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -82,15 +82,11 @@ LB \le C(X) \le UB
8282
\right.
8383
```
8484

85-
We use packages from [JuliaSmoothOptimizers](https://github.com/JuliaSmoothOptimizers) to solve the (NLP) problem.
85+
Solving the (NLP) problem is done using packages from [JuliaSmoothOptimizers](https://github.com/JuliaSmoothOptimizers), with Ipopt as the default solver.
8686

87-
As input of this package we use an [`OptimalControlModel`](@ref) structure from CTBase.
87+
On the input side of this package, we use an [`OptimalControlModel`](@ref) structure from CTBase to define the (OCP).
8888

89-
!!! note "Current limitations"
90-
91-
The current implemented is limited to
92-
- trapezoidal rule for the ODE discretization
93-
- `Ipopt` for the optimization software
89+
The direct transcription to build the (NLP) can use discretization schemes such as trapeze (default), midpoint, or Gauss-Legendre collocations.
9490

9591
!!! note "Related packages"
9692

ext/CTSolveExtIpopt.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ function CTDirect.solve_docp(
5959
tol = tol,
6060
max_iter = max_iter,
6161
sb = "yes",
62+
#check_derivatives_for_naninf = "yes",
6263
linear_solver = linear_solver;
6364
kwargs...,
6465
)

src/CTDirect.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ using DocStringExtensions
55
using ADNLPModels # docp model with AD
66
using LinearAlgebra # norm and misc
77
using HSL
8+
using SparseArrays
89

910
import CTBase: OptimalControlSolution, CTBase # extended
1011

src/default.jl

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,14 @@ The default value is `nothing`.
2424
"""
2525
__time_grid() = nothing
2626

27+
"""
28+
$(TYPEDSIGNATURES)
29+
30+
Used to set the default control type for IRK schemes
31+
The default value is `:constant`.
32+
"""
33+
__control_type() = :constant
34+
2735
"""
2836
$(TYPEDSIGNATURES)
2937
Used to set the default backend for AD in ADNLPModels.

src/disc/irk.jl

Lines changed: 217 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@ Internal layout for NLP variables:
55
..,
66
X_N-1, U_N-1, K_N-1^1..K_N-1^s,
77
X_N, U_N, V]
8-
with s the stage number and U given by either linear interpolation in [t_i, t_i+1]
9-
or constant interpolation for 1-stage methods or if specfied (U_N might end up unused)
10-
Path constraints are all evaluated at time steps
8+
with s the stage number and U piecewise constant equal to U_i in [t_i, t_i+1]
9+
or, for methods with s>1, piecewise linear if option control_type set to :linear
10+
NB. U_N may be removed at some point if we disable piecewise linear control
11+
Path constraints are all evaluated at time steps, including final time.
1112
=#
1213

1314

@@ -57,9 +58,9 @@ struct Gauss_Legendre_2 <: GenericIRK
5758
_step_variables_block::Int
5859
_state_stage_eqs_block::Int
5960
_step_pathcons_block::Int
60-
_constant_control::Bool
61+
_control_type::Symbol
6162

62-
function Gauss_Legendre_2(dim_NLP_steps, dim_NLP_x, dim_NLP_u, dim_NLP_v, dim_u_cons, dim_x_cons, dim_xu_cons, dim_boundary_cons, dim_v_cons, constant_control)
63+
function Gauss_Legendre_2(dim_NLP_steps, dim_NLP_x, dim_NLP_u, dim_NLP_v, dim_u_cons, dim_x_cons, dim_xu_cons, dim_boundary_cons, dim_v_cons, control_type)
6364

6465
stage = 2
6566

@@ -70,7 +71,7 @@ struct Gauss_Legendre_2 <: GenericIRK
7071
[0.5, 0.5],
7172
[(0.5 - sqrt(3) / 6), (0.5 + sqrt(3) / 6)],
7273
step_variables_block, state_stage_eqs_block, step_pathcons_block,
73-
constant_control
74+
control_type
7475
)
7576

7677
return disc, dim_NLP_variables, dim_NLP_constraints
@@ -93,9 +94,9 @@ struct Gauss_Legendre_3 <: GenericIRK
9394
_step_variables_block::Int
9495
_state_stage_eqs_block::Int
9596
_step_pathcons_block::Int
96-
_constant_control::Bool
97+
_control_type::Symbol
9798

98-
function Gauss_Legendre_3(dim_NLP_steps, dim_NLP_x, dim_NLP_u, dim_NLP_v, dim_u_cons, dim_x_cons, dim_xu_cons, dim_boundary_cons, dim_v_cons, constant_control)
99+
function Gauss_Legendre_3(dim_NLP_steps, dim_NLP_x, dim_NLP_u, dim_NLP_v, dim_u_cons, dim_x_cons, dim_xu_cons, dim_boundary_cons, dim_v_cons, control_type)
99100

100101
stage = 3
101102

@@ -107,7 +108,7 @@ struct Gauss_Legendre_3 <: GenericIRK
107108
(5/36 + sqrt(15) / 30) (2/9 + sqrt(15) / 15) (5.0/36.0)],
108109
[5.0/18.0, 4.0/9.0, 5.0/18.0],
109110
[0.5 - 0.1*sqrt(15), 0.5, 0.5 + 0.1*sqrt(15)],
110-
step_variables_block, state_stage_eqs_block, step_pathcons_block, constant_control
111+
step_variables_block, state_stage_eqs_block, step_pathcons_block, control_type
111112
)
112113

113114
return disc, dim_NLP_variables, dim_NLP_constraints
@@ -183,7 +184,7 @@ function get_OCP_control_at_time_step(xu, docp::DOCP{ <: GenericIRK, <: ScalVect
183184
return @view xu[(offset + 1):(offset + docp.dim_NLP_u)]
184185
end
185186
function get_OCP_control_at_time_stage(xu, docp::DOCP{ <: GenericIRK, <: ScalVect, ScalVariable, <: ScalVect}, i, cj)
186-
if (docp.discretization.stage == 1) || (docp.discretization._constant_control)
187+
if (docp.discretization.stage == 1) || (docp.discretization._control_type == :constant)
187188
# constant interpolation on step
188189
return get_OCP_control_at_time_step(xu, docp, i)
189190
else
@@ -194,7 +195,7 @@ function get_OCP_control_at_time_stage(xu, docp::DOCP{ <: GenericIRK, <: ScalVec
194195
end
195196
end
196197
function get_OCP_control_at_time_stage(xu, docp::DOCP{ <: GenericIRK, <: ScalVect, VectVariable, <: ScalVect}, i, cj)
197-
if (docp.discretization.stage == 1) || (docp.discretization._constant_control)
198+
if (docp.discretization.stage == 1) || (docp.discretization._control_type == :constant)
198199
# constant interpolation on step
199200
return get_OCP_control_at_time_step(xu, docp, i)
200201
else
@@ -251,8 +252,8 @@ $(TYPEDSIGNATURES)
251252
Set work array for all dynamics and lagrange cost evaluations
252253
"""
253254
function setWorkArray(docp::DOCP{ <: GenericIRK}, xu, time_grid, v)
254-
# work array layout: [x_ij ; sum_bk ; u_ij] ?
255-
work = similar(xu, docp.dim_OCP_x + docp.dim_NLP_x + docp.dim_NLP_u)
255+
# work array layout: [x_ij ; sum_bk]
256+
work = similar(xu, docp.dim_OCP_x + docp.dim_NLP_x)
256257
return work
257258
end
258259

@@ -264,12 +265,9 @@ Convention: 1 <= i <= dim_NLP_steps (+1)
264265
"""
265266
function setStepConstraints!(docp::DOCP{ <: GenericIRK}, c, xu, v, time_grid, i, work)
266267

267-
# work array layout: [x_ij ; sum_bk ; u_ij] ?
268+
# work array layout: [x_ij ; sum_bk]
268269
work_xij = @view work[1:docp.dim_OCP_x]
269270
work_sumbk = @view work[docp.dim_OCP_x+1:docp.dim_OCP_x+docp.dim_NLP_x]
270-
#work_sumbk .= zero(eltype(xu)) AD bug when affecting constant values...
271-
@views @. work_sumbk[1:docp.dim_NLP_x] = xu[1:docp.dim_NLP_x] * 0.
272-
#work_uij ?
273271

274272
# offset for previous steps
275273
offset = (i-1)*(docp.discretization._state_stage_eqs_block + docp.discretization._step_pathcons_block)
@@ -297,7 +295,11 @@ function setStepConstraints!(docp::DOCP{ <: GenericIRK}, c, xu, v, time_grid, i,
297295
kij = get_stagevars_at_time_step(xu, docp, i, j)
298296

299297
# update sum b_j k_i^j (w/ lagrange term) for state equation after loop
300-
@views @. work_sumbk[1:docp.dim_NLP_x] = work_sumbk[1:docp.dim_NLP_x] + docp.discretization.butcher_b[j] * kij[1:docp.dim_NLP_x]
298+
if j == 1
299+
@views @. work_sumbk[1:docp.dim_NLP_x] = docp.discretization.butcher_b[j] * kij[1:docp.dim_NLP_x]
300+
else
301+
@views @. work_sumbk[1:docp.dim_NLP_x] = work_sumbk[1:docp.dim_NLP_x] + docp.discretization.butcher_b[j] * kij[1:docp.dim_NLP_x]
302+
end
301303

302304
# state at stage: x_i^j = x_i + h_i sum a_jl k_i^l
303305
# +++ still some allocations here
@@ -312,8 +314,7 @@ function setStepConstraints!(docp::DOCP{ <: GenericIRK}, c, xu, v, time_grid, i,
312314
xij = work_xij
313315
end
314316

315-
# control at stage: interpolation between u_i and u_i+1
316-
# +++ use work aray to reduce allocs ?
317+
# control at stage
317318
uij = get_OCP_control_at_time_stage(xu, docp, i, cj)
318319

319320
# stage equations k_i^j = f(t_i^j, x_i^j, u_i, v) as c[] = k - f
@@ -342,3 +343,199 @@ function setStepConstraints!(docp::DOCP{ <: GenericIRK}, c, xu, v, time_grid, i,
342343
setPathConstraints!(docp, c, ti, xi, ui, v, offset)
343344

344345
end
346+
347+
348+
"""
349+
$(TYPEDSIGNATURES)
350+
351+
Build sparsity pattern for Jacobian of constraints
352+
"""
353+
function DOCP_Jacobian_pattern(docp::DOCP{ <: GenericIRK})
354+
355+
if docp.discretization._control_type != :constant
356+
error("Manual Jacobian sparsity pattern not supported for IRK scheme with piecewise linear control")
357+
end
358+
359+
# vector format for sparse matrix
360+
Is = Vector{Int}(undef, 0)
361+
Js = Vector{Int}(undef, 0)
362+
363+
s = docp.discretization.stage
364+
365+
# index alias for v
366+
v_start = docp.dim_NLP_variables - docp.dim_NLP_v + 1
367+
v_end = docp.dim_NLP_variables
368+
369+
# 1. main loop over steps
370+
for i = 1:docp.dim_NLP_steps
371+
372+
# constraints block and offset: state equation, stage equations, path constraints
373+
c_block = docp.discretization._state_stage_eqs_block + docp.discretization._step_pathcons_block
374+
c_offset = (i-1)*c_block
375+
376+
# contiguous variables blocks will be used when possible
377+
# x_i (l_i) u_i k_i x_i+1 (l_i+1)
378+
var_offset = (i-1)*docp.discretization._step_variables_block
379+
xi_start = var_offset + 1
380+
xi_end = var_offset + docp.dim_OCP_x
381+
ui_start = var_offset + docp.dim_NLP_x + 1
382+
ui_end = var_offset + docp.dim_NLP_x + docp.dim_NLP_u
383+
ki_start = var_offset + docp.dim_NLP_x + docp.dim_NLP_u + 1
384+
ki_end = var_offset + docp.discretization._step_variables_block
385+
xip1_end = var_offset + docp.discretization._step_variables_block + docp.dim_OCP_x
386+
li = var_offset + docp.dim_NLP_x
387+
lip1 = var_offset + docp.discretization._step_variables_block + docp.dim_NLP_x
388+
389+
# 1.1 state eq 0 = x_i+1 - (x_i + h_i sum_j b_j k_ij)
390+
# depends on x_i, k_ij, x_i+1, and v for h_i in variable times case !
391+
# skip l_i, u_i; should skip k_i[n+1] also but annoying...
392+
add_nonzero_block!(Is, Js, c_offset+1, c_offset+docp.dim_OCP_x, xi_start, xi_end)
393+
add_nonzero_block!(Is, Js, c_offset+1, c_offset+docp.dim_OCP_x, ki_start, xip1_end)
394+
add_nonzero_block!(Is, Js, c_offset+1, c_offset+docp.dim_OCP_x, v_start, v_end)
395+
# 1.2 lagrange part l_i+1 = l_i + h_i (sum_j b_j k_ij)[n+1]
396+
# depends on l_i, k_ij[n+1], l_i+1, and v for h_i in variable times case !
397+
if docp.is_lagrange
398+
add_nonzero_block!(Is, Js, c_offset+docp.dim_NLP_x, li)
399+
add_nonzero_block!(Is, Js, c_offset+docp.dim_NLP_x, lip1)
400+
for i=1:s
401+
kij_l = var_offset + docp.dim_NLP_x + docp.dim_NLP_u + i*docp.dim_NLP_x
402+
add_nonzero_block!(Is, Js, c_offset+docp.dim_NLP_x, kij_l)
403+
end
404+
add_nonzero_block!(Is, Js, c_offset+docp.dim_NLP_x, c_offset+docp.dim_NLP_x, v_start, v_end)
405+
end
406+
407+
# 1.3 stage equations k_ij = f(t_ij, x_ij, u_ij, v) (with lagrange part)
408+
# with x_ij = x_i + sum_l a_il k_jl and assuming u_ij = u_i
409+
# depends on x_i, u_i, k_i, and v; skip l_i (could skip k_ij[n+1] too...)
410+
add_nonzero_block!(Is, Js, c_offset+docp.dim_NLP_x+1, c_offset+(s+1)*docp.dim_NLP_x, xi_start, xi_end)
411+
add_nonzero_block!(Is, Js, c_offset+docp.dim_NLP_x+1, c_offset+(s+1)*docp.dim_NLP_x, ui_start, ki_end)
412+
add_nonzero_block!(Is, Js, c_offset+docp.dim_NLP_x+1, c_offset+(s+1)*docp.dim_NLP_x, v_start, v_end)
413+
414+
# 1.4 path constraint g(t_i, x_i, u_i, v)
415+
# depends on x_i, u_i, v; skip l_i
416+
add_nonzero_block!(Is, Js, c_offset+(s+1)*docp.dim_NLP_x+1, c_offset+c_block, xi_start, xi_end)
417+
add_nonzero_block!(Is, Js, c_offset+(s+1)*docp.dim_NLP_x+1, c_offset+c_block, ui_start, ui_end)
418+
add_nonzero_block!(Is, Js, c_offset+(s+1)*docp.dim_NLP_x+1, c_offset+c_block, v_start, v_end)
419+
end
420+
421+
# 2. final path constraints (xf, uf, v)
422+
c_offset = docp.dim_NLP_steps * (docp.discretization._state_stage_eqs_block + docp.discretization._step_pathcons_block)
423+
c_block = docp.discretization._step_pathcons_block
424+
var_offset = docp.dim_NLP_steps*docp.discretization._step_variables_block
425+
xf_start = var_offset + 1
426+
xf_end = var_offset + docp.dim_OCP_x
427+
uf_start = var_offset + docp.dim_NLP_x + 1
428+
uf_end = var_offset + docp.dim_NLP_x + docp.dim_NLP_u
429+
add_nonzero_block!(Is, Js, c_offset+1, c_offset+c_block, xf_start, xf_end)
430+
add_nonzero_block!(Is, Js, c_offset+1,c_offset+c_block, uf_start, uf_end)
431+
add_nonzero_block!(Is, Js, c_offset+1, c_offset+c_block, v_start, v_end)
432+
433+
# 3. boundary constraints (x0, xf, v)
434+
c_offset = docp.dim_NLP_steps * (docp.discretization._state_stage_eqs_block + docp.discretization._step_pathcons_block) + docp.discretization._step_pathcons_block
435+
c_block = docp.dim_boundary_cons + docp.dim_v_cons
436+
x0_start = 1
437+
x0_end = docp.dim_OCP_x
438+
add_nonzero_block!(Is, Js, c_offset+1, c_offset+c_block, x0_start, x0_end)
439+
add_nonzero_block!(Is, Js, c_offset+1, c_offset+c_block, xf_start, xf_end)
440+
add_nonzero_block!(Is, Js, c_offset+1, c_offset+c_block, v_start, v_end)
441+
# 3.4 null initial condition for lagrangian cost state l0
442+
if docp.is_lagrange
443+
add_nonzero_block!(Is, Js, docp.dim_NLP_constraints, docp.dim_NLP_x)
444+
end
445+
446+
# build and return sparse matrix
447+
nnzj = length(Is)
448+
Vs = ones(Bool, nnzj)
449+
return sparse(Is, Js, Vs, docp.dim_NLP_constraints, docp.dim_NLP_variables)
450+
end
451+
452+
453+
"""
454+
$(TYPEDSIGNATURES)
455+
456+
Build sparsity pattern for Hessian of Lagrangian
457+
"""
458+
function DOCP_Hessian_pattern(docp::DOCP{ <: GenericIRK})
459+
460+
if docp.discretization._control_type != :constant
461+
error("Manual Hessian sparsity pattern not supported for IRK scheme with piecewise linear control")
462+
end
463+
464+
# NB. need to provide full pattern for coloring, not just upper/lower part
465+
Is = Vector{Int}(undef, 0)
466+
Js = Vector{Int}(undef, 0)
467+
468+
s = docp.discretization.stage
469+
470+
# index alias for v
471+
v_start = docp.dim_NLP_variables - docp.dim_NLP_v + 1
472+
v_end = docp.dim_NLP_variables
473+
474+
# 0. objective
475+
# 0.1 mayer cost (x0, xf, v)
476+
# -> grouped with term 3. for boundary conditions
477+
# 0.2 lagrange case (lf)
478+
# -> 2nd order term is zero
479+
480+
# 1. main loop over steps
481+
# 1.0 v / v term
482+
add_nonzero_block!(Is, Js, v_start, v_end, v_start, v_end)
483+
484+
for i = 1:docp.dim_NLP_steps
485+
486+
# contiguous variables blocks will be used when possible
487+
# x_i (l_i) u_i k_i x_i+1 (l_i+1)
488+
var_offset = (i-1)*docp.discretization._step_variables_block
489+
xi_start = var_offset + 1
490+
xi_end = var_offset + docp.dim_OCP_x
491+
ui_start = var_offset + docp.dim_NLP_x + 1
492+
ui_end = var_offset + docp.dim_NLP_x + docp.dim_NLP_u
493+
ki_start = var_offset + docp.dim_NLP_x + docp.dim_NLP_u + 1
494+
ki_end = var_offset + (s+1)*docp.dim_NLP_x + docp.dim_NLP_u
495+
496+
# 1.1 state eq 0 = x_i+1 - (x_i + h_i sum_j b_j k_ij)
497+
# -> 2nd order terms are zero
498+
# 1.2 lagrange part 0 = l_i+1 - (l_i + h_i (sum_j b_j k_ij[n+1]))
499+
# -> 2nd order terms are zero
500+
501+
# 1.3 stage equations 0 = k_ij - f(t_ij, x_ij, u_ij, v) (with lagrange part)
502+
# with x_ij = x_i + sum_l a_il k_jl and assuming u_ij = u_i
503+
# depends on x_i, u_i, k_i, and v; skip l_i (could skip k_ij[n+1] too...)
504+
add_nonzero_block!(Is, Js, xi_start, xi_end, xi_start, xi_end)
505+
add_nonzero_block!(Is, Js, ui_start, ki_end, ui_start, ki_end)
506+
add_nonzero_block!(Is, Js, xi_start, xi_end, ui_start, ki_end; sym=true)
507+
add_nonzero_block!(Is, Js, xi_start, xi_end, v_start, v_end; sym=true)
508+
add_nonzero_block!(Is, Js, ui_start, ki_end, v_start, v_end; sym=true)
509+
510+
# 1.4 path constraint g(t_i, x_i, u_i, v)
511+
# -> included in 1.3
512+
end
513+
514+
# 2. final path constraints (xf, uf, v) (assume present)
515+
var_offset = docp.dim_NLP_steps*docp.discretization._step_variables_block
516+
xf_start = var_offset + 1
517+
xf_end = var_offset + docp.dim_OCP_x
518+
uf_start = var_offset + docp.dim_NLP_x + 1
519+
uf_end = var_offset + docp.dim_NLP_x + docp.dim_NLP_u
520+
add_nonzero_block!(Is, Js, xf_start, xf_end, xf_start, xf_end)
521+
add_nonzero_block!(Is, Js, uf_start, uf_end, uf_start, uf_end)
522+
add_nonzero_block!(Is, Js, xf_start, xf_end, uf_start, uf_end; sym=true)
523+
add_nonzero_block!(Is, Js, xf_start, xf_end, v_start, v_end; sym=true)
524+
add_nonzero_block!(Is, Js, uf_start, uf_end, v_start, v_end; sym=true)
525+
526+
# 3. boundary constraints (x0, xf, v) or mayer cost g0(x0, xf, v) (assume present)
527+
# -> x0 / x0, x0 / v terms included in first loop iteration
528+
# -> xf / xf, xf / v terms included in 2.
529+
x0_start = 1
530+
x0_end = docp.dim_OCP_x
531+
add_nonzero_block!(Is, Js, x0_start, x0_end, xf_start, xf_end; sym=true)
532+
533+
# 3.1 null initial condition for lagrangian cost state l0
534+
# -> 2nd order term is zero
535+
536+
# build and return sparse matrix
537+
nnzj = length(Is)
538+
Vs = ones(Bool, nnzj)
539+
return sparse(Is, Js, Vs, docp.dim_NLP_variables, docp.dim_NLP_variables)
540+
541+
end

0 commit comments

Comments
 (0)