Skip to content

Move OSQP to a weak dependency (package extension) - #165

Open
hakkelt wants to merge 3 commits into
JuliaFirstOrder:masterfrom
hakkelt:osqp-weakdep
Open

Move OSQP to a weak dependency (package extension)#165
hakkelt wants to merge 3 commits into
JuliaFirstOrder:masterfrom
hakkelt:osqp-weakdep

Conversation

@hakkelt

@hakkelt hakkelt commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Implements "Option 2" from #163.

Goal

OSQP and its dependencies add ~2s to using ProximalOperators, but OSQP is
only used by IndPolyhedralOSQP — the default backend of IndPolyhedral.
This makes OSQP optional so only users who need that operator pay the cost.

Changes

  • OSQP moved from [deps] to [weakdeps], registered as the extension
    ProximalOperatorsOSQPExt.
  • src/functions/indPolyhedralOSQP.jl is removed. The main package keeps only
    the IndPolyhedralOSQP type and a stub constructor that raises an
    informative error when OSQP is not loaded; both are now inlined into
    src/functions/indPolyhedral.jl.
  • The extension holds everything that needs OSQP: the constructors, prox!,
    the function evaluation and prox_naive — i.e. every method operating on an
    IndPolyhedralOSQP instance.
  • IndPolyhedral docstring documents the new requirement.

Effect

Constructing IndPolyhedral(...) / IndPolyhedral(...; solver=:osqp) now
requires using OSQP first; without it, a clear error is thrown instead of
a MethodError. This is a minor breaking API change. All other operators are
unaffected and load without OSQP.

The version is left at 0.17.0 for the maintainers to set.

🤖 Generated with Claude Code

https://claude.ai/code/session_01X9Xuox9ELuSiEbxwJq84vJ

Loading OSQP and its dependencies accounted for ~2s of ProximalOperators'
load time, yet OSQP is only used by IndPolyhedralOSQP (the default backend
of IndPolyhedral). This implements "Option 2" from JuliaFirstOrder#163: OSQP becomes a weak
dependency and the solver-backed implementation lives in the new extension
ProximalOperatorsOSQPExt.

- Project.toml: OSQP moved from [deps] to [weakdeps]; register
  ProximalOperatorsOSQPExt = "OSQP".
- src/functions/indPolyhedralOSQP.jl: keeps the IndPolyhedralOSQP type (now
  parametric on the model type), the function-value and prox_naive methods,
  and a stub constructor that raises an informative error when OSQP is not
  loaded. An explicit inner constructor suppresses the auto-generated
  4-arg outer constructor that would otherwise shadow the extension's
  (l, A, xmin, xmax) method.
- ext/ProximalOperatorsOSQPExt.jl: OSQP-backed constructors and prox!.
- Users must `using OSQP` before constructing IndPolyhedral(...; solver=:osqp);
  documented in the IndPolyhedral docstring.
- test: add OSQP to the test env and `using OSQP` in test_indPolyhedral.jl.
- Bump version to 0.18.0 (minor breaking API change).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X9Xuox9ELuSiEbxwJq84vJ
@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Benchmark Results (Julia v1)

Time benchmarks
master 0c17c73... master / 0c17c73...
IndBallL1/Float32 4.88 ± 5.1 μs 4.79 ± 0.44 μs 1.02 ± 1.1
IndBallL1/Float64 5.48 ± 1.6 μs 5.6 ± 1.6 μs 0.98 ± 0.4
IndBox/Float32 10.5 ± 0.37 μs 10.5 ± 0.33 μs 0.996 ± 0.047
IndBox/Float64 11.4 ± 1.5 μs 11.9 ± 0.97 μs 0.961 ± 0.15
IndExpPrimal/Float32 0.209 ± 0.012 ms 0.205 ± 0.012 ms 1.02 ± 0.084
IndExpPrimal/Float64 0.209 ± 0.015 ms 0.206 ± 0.012 ms 1.02 ± 0.094
IndNonnegative/Float32 11.7 ± 0.37 μs 11.6 ± 0.84 μs 1.01 ± 0.08
IndNonnegative/Float64 7.7 ± 0.58 μs 7.1 ± 1.5 μs 1.08 ± 0.24
IndPSD/("ComplexF64", 10) 19.5 ± 1.7 μs 19.4 ± 1.6 μs 1.01 ± 0.12
IndPSD/("ComplexF64", 20) 0.0725 ± 0.001 ms 0.0725 ± 0.00097 ms 1 ± 0.019
IndPSD/("ComplexF64", 50) 0.604 ± 0.014 ms 0.602 ± 0.014 ms 1 ± 0.033
IndPSD/("Float64", 10) 15.5 ± 0.52 μs 15.4 ± 0.52 μs 1 ± 0.048
IndPSD/("Float64", 20) 0.0503 ± 0.0012 ms 0.05 ± 0.0041 ms 1.01 ± 0.086
IndPSD/("Float64", 50) 0.307 ± 0.01 ms 0.301 ± 0.011 ms 1.02 ± 0.05
IndSimplex/Float32 25.9 ± 0.52 μs 25.7 ± 0.63 μs 1.01 ± 0.032
IndSimplex/Float64 30.6 ± 2.4 μs 30.2 ± 2.9 μs 1.01 ± 0.13
LeastSquares/("ComplexF64", "(11, 5)", 0, 0) 1.19 ± 0.03 μs 1.19 ± 0.031 μs 1 ± 0.036
LeastSquares/("ComplexF64", "(11, 5)", 0, 1) 1.54 ± 0.13 μs 1.56 ± 0.14 μs 0.987 ± 0.12
LeastSquares/("ComplexF64", "(11, 5)", 1, 0) 9.82 ± 0.53 μs 9.74 ± 0.44 μs 1.01 ± 0.071
LeastSquares/("ComplexF64", "(11, 5)", 1, 1) 1.57 ± 0.11 μs 1.54 ± 0.1 μs 1.02 ± 0.098
LeastSquares/("ComplexF64", "(5, 11)", 0, 0) 1.39 ± 0.05 μs 1.38 ± 0.05 μs 1.01 ± 0.051
LeastSquares/("ComplexF64", "(5, 11)", 0, 1) 1.7 ± 0.12 μs 1.75 ± 0.12 μs 0.972 ± 0.096
LeastSquares/("ComplexF64", "(5, 11)", 1, 0) 10.1 ± 0.48 μs 10 ± 0.43 μs 1.01 ± 0.065
LeastSquares/("ComplexF64", "(5, 11)", 1, 1) 1.78 ± 0.12 μs 1.79 ± 0.14 μs 0.994 ± 0.1
LeastSquares/("Float64", "(11, 5)", 0, 0) 1.02 ± 0.021 μs 0.971 ± 0.03 μs 1.05 ± 0.039
LeastSquares/("Float64", "(11, 5)", 0, 1) 1.33 ± 0.13 μs 1.31 ± 0.13 μs 1.02 ± 0.14
LeastSquares/("Float64", "(11, 5)", 1, 0) 9.54 ± 0.41 μs 9.59 ± 0.38 μs 0.995 ± 0.058
LeastSquares/("Float64", "(11, 5)", 1, 1) 1.24 ± 0.13 μs 1.24 ± 0.14 μs 1 ± 0.15
LeastSquares/("Float64", "(5, 11)", 0, 0) 1.18 ± 0.039 μs 1.12 ± 0.031 μs 1.05 ± 0.045
LeastSquares/("Float64", "(5, 11)", 0, 1) 1.48 ± 0.11 μs 1.45 ± 0.11 μs 1.02 ± 0.11
LeastSquares/("Float64", "(5, 11)", 1, 0) 9.67 ± 0.39 μs 9.73 ± 0.36 μs 0.994 ± 0.055
LeastSquares/("Float64", "(5, 11)", 1, 1) 1.47 ± 0.14 μs 1.42 ± 0.15 μs 1.04 ± 0.15
NormL2/Float32 4.23 ± 0.39 μs 4.19 ± 0.35 μs 1.01 ± 0.13
NormL2/Float64 11.5 ± 0.82 μs 11.5 ± 0.75 μs 0.994 ± 0.096
time_to_load 1.1 ± 0.0026 s 0.279 ± 0.0017 s 3.96 ± 0.025
Memory benchmarks
master 0c17c73... master / 0c17c73...
IndBallL1/Float32 10 allocs: 10.4 kB 10 allocs: 10.4 kB 1
IndBallL1/Float64 10 allocs: 20.5 kB 10 allocs: 20.5 kB 1
IndBox/Float32 1 allocs: 16 B 1 allocs: 16 B 1
IndBox/Float64 1 allocs: 16 B 1 allocs: 16 B 1
IndExpPrimal/Float32 11 k allocs: 0.179 MB 11 k allocs: 0.179 MB 1
IndExpPrimal/Float64 11 k allocs: 0.179 MB 11 k allocs: 0.179 MB 1
IndNonnegative/Float32 1 allocs: 16 B 1 allocs: 16 B 1
IndNonnegative/Float64 1 allocs: 16 B 1 allocs: 16 B 1
IndPSD/("ComplexF64", 10) 19 allocs: 11.9 kB 19 allocs: 11.9 kB 1
IndPSD/("ComplexF64", 20) 22 allocs: 29.2 kB 22 allocs: 29.2 kB 1
IndPSD/("ComplexF64", 50) 23 allocs: 0.116 MB 23 allocs: 0.116 MB 1
IndPSD/("Float64", 10) 16 allocs: 5.88 kB 16 allocs: 5.88 kB 1
IndPSD/("Float64", 20) 18 allocs: 14 kB 18 allocs: 14 kB 1
IndPSD/("Float64", 50) 19 allocs: 0.0564 MB 19 allocs: 0.0564 MB 1
IndSimplex/Float32 24 allocs: 0.0344 MB 24 allocs: 0.0344 MB 1
IndSimplex/Float64 26 allocs: 0.0685 MB 26 allocs: 0.0685 MB 1
LeastSquares/("ComplexF64", "(11, 5)", 0, 0) 7 allocs: 1.05 kB 7 allocs: 1.05 kB 1
LeastSquares/("ComplexF64", "(11, 5)", 0, 1) 17 allocs: 1.14 kB 17 allocs: 1.14 kB 1
LeastSquares/("ComplexF64", "(11, 5)", 1, 0) 0.059 k allocs: 4.6 kB 0.059 k allocs: 4.6 kB 1
LeastSquares/("ComplexF64", "(11, 5)", 1, 1) 17 allocs: 1.17 kB 17 allocs: 1.17 kB 1
LeastSquares/("ComplexF64", "(5, 11)", 0, 0) 7 allocs: 1.05 kB 7 allocs: 1.05 kB 1
LeastSquares/("ComplexF64", "(5, 11)", 0, 1) 17 allocs: 1.14 kB 17 allocs: 1.14 kB 1
LeastSquares/("ComplexF64", "(5, 11)", 1, 0) 0.059 k allocs: 4.6 kB 0.059 k allocs: 4.6 kB 1
LeastSquares/("ComplexF64", "(5, 11)", 1, 1) 17 allocs: 1.17 kB 17 allocs: 1.17 kB 1
LeastSquares/("Float64", "(11, 5)", 0, 0) 7 allocs: 0.609 kB 7 allocs: 0.609 kB 1
LeastSquares/("Float64", "(11, 5)", 0, 1) 17 allocs: 0.984 kB 17 allocs: 0.984 kB 1
LeastSquares/("Float64", "(11, 5)", 1, 0) 0.059 k allocs: 3.96 kB 0.059 k allocs: 3.96 kB 1
LeastSquares/("Float64", "(11, 5)", 1, 1) 17 allocs: 1.02 kB 17 allocs: 1.02 kB 1
LeastSquares/("Float64", "(5, 11)", 0, 0) 7 allocs: 0.609 kB 7 allocs: 0.609 kB 1
LeastSquares/("Float64", "(5, 11)", 0, 1) 17 allocs: 0.984 kB 17 allocs: 0.984 kB 1
LeastSquares/("Float64", "(5, 11)", 1, 0) 0.059 k allocs: 3.96 kB 0.059 k allocs: 3.96 kB 1
LeastSquares/("Float64", "(5, 11)", 1, 1) 17 allocs: 1.02 kB 17 allocs: 1.02 kB 1
NormL2/Float32 1 allocs: 16 B 1 allocs: 16 B 1
NormL2/Float64 1 allocs: 16 B 1 allocs: 16 B 1
time_to_load 0.149 k allocs: 11.2 kB 0.149 k allocs: 11.2 kB 1

…_naive to ext

Follow-up to the previous commit:

- Delete src/functions/indPolyhedralOSQP.jl. The IndPolyhedralOSQP struct, its
  inner constructor, the is_proximable trait and the not-loaded stub
  constructor are now inlined into src/functions/indPolyhedral.jl, so the whole
  non-OSQP surface of IndPolyhedral lives in one file.
- Move the function evaluation `(f::IndPolyhedralOSQP)(x)` and `prox_naive`
  into ext/ProximalOperatorsOSQPExt.jl. The extension now holds everything that
  operates on an IndPolyhedralOSQP instance; the main package only defines the
  type and the error stub.

No behavioural change: an instance can only exist once OSQP is loaded, so
moving its methods to the extension is transparent.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X9Xuox9ELuSiEbxwJq84vJ
@hakkelt
hakkelt marked this pull request as ready for review September 8, 2026 09:54
Leave the version at 0.17.0; the maintainers can decide the release number.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X9Xuox9ELuSiEbxwJq84vJ
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant