[ANN] Clarabel.jl: New convex optimization solver

Dear Julia Optimizers,

We are happy to announce the release of Clarabel.jl v0.1.0, a new interior point code for conic optimization written entirely in Julia.

The code currently supports LP/QP/SOCP/SDP problems and can be accessed through JuMP or Convex.jl.

Documentation and examples can be found here. It is installable via the Julia package manager, or downloadable directly from our group’s research repository here:

https://github.com/oxfordcontrol/Clarabel.jl

Enjoy!

52 Likes

It looks like the solver is pure Julia, not calling a C library? If so, that is super cool and a great contributions to the community!

Yes, it is a pure Julia language solver. It even uses our pure Julia LDL factorization package QDLDL.jl for the linear solve step by default, although MKL and Cholmod are also supported as options.

11 Likes

this is really cool! how does qldl stack up vs other solvers wrt performance?

We find that QDLDL is generally faster than either Pardiso or ldlt/Cholmod, at least for problems of moderate size. At some large scale I think MKL overtakes it, but we haven’t done extensive benchmarking to find the crossover point.

The caveat though is that QDLDL should only be used for a sign definite or quasidefinite system. If you use it on a general sparse symmetric matrix it may work anyway, but it’s not really safe to do so. MKL and Cholmod also probably both do a better job with badly conditioned systems.

2 Likes

Wow, I’m noticing a nearly 6x improvement over SCS for the minimal ellipses example from the JuMP documentation:

julia> versioninfo()
Julia Version 1.8.0-rc1
Commit 6368fdc656 (2022-05-27 18:33 UTC)
Platform Info:
  OS: Linux (x86_64-pc-linux-gnu)
  CPU: 4 × Intel(R) Core(TM) i5-3210M CPU @ 2.50GHz
  WORD_SIZE: 64
  LIBM: libopenlibm
  LLVM: libLLVM-13.0.1 (ORCJIT, ivybridge)
  Threads: 1 on 4 virtual cores

julia> minimum(solve_clarabel() for _ in 1:5)
0.008435284000000001

julia> minimum(solve_SCS() for _ in 1:5)
0.047278745000000004

(solve_clarabel() and solve_SCS() just wrap the code included on the linked page and then return solve_time(model). I couldn’t use BenchmarkTools because this would add in the noise time of reading in the model, rather than solving it.)

I will be making lots of use of Clarabel in the future!

1 Like

I’m intrigued but having trouble installing (maybe this isn’t the right place)

(@v1.7) pkg> add Clarabel
    Updating registry at `~/.julia/registries/General.toml`
   Resolving package versions...
ERROR: Unsatisfiable requirements detected for package DataFrames [a93c6f00]:
 DataFrames [a93c6f00] log:
 ├─possible versions are: 0.11.7-1.3.4 or uninstalled
 ├─restricted to versions * by an explicit requirement, leaving only versions 0.11.7-1.3.4
 ├─restricted by compatibility requirements with StatsBase [2913bbd2] to versions: 0.19.1-1.3.4 or uninstalled, leaving only versions: 0.19.1-1.3.4
 │ └─StatsBase [2913bbd2] log:
 │   ├─possible versions are: 0.24.0-0.33.18 or uninstalled
 │   └─restricted to versions 0.33.1-0.33 by NaiveBayes [9bbee03b], leaving only versions 0.33.1-0.33.18
 │     └─NaiveBayes [9bbee03b] log:
 │       ├─possible versions are: 0.5.2 or uninstalled
 │       └─NaiveBayes [9bbee03b] is fixed to version 0.5.2
 ├─restricted by compatibility requirements with Compat [34da2185] to versions: 0.20.0-1.3.4 or uninstalled, leaving only versions: 0.20.0-1.3.4
 │ └─Compat [34da2185] log:
 │   ├─possible versions are: 1.0.0-4.1.0 or uninstalled
 │   ├─restricted to versions * by an explicit requirement, leaving only versions 1.0.0-4.1.0
 │   ├─restricted by compatibility requirements with BlackBoxOptim [a134a8b2] to versions: 1.0.0-3.45.0
 │   │ └─BlackBoxOptim [a134a8b2] log:
 │   │   ├─possible versions are: 0.4.0-0.6.1 or uninstalled
 │   │   ├─restricted to versions * by an explicit requirement, leaving only versions 0.4.0-0.6.1
 │   │   └─restricted by compatibility requirements with Compat [34da2185] to versions: 0.5.0-0.6.1 or uninstalled, leaving only versions: 0.5.0-0.6.1
 │   │     └─Compat [34da2185] log: see above
 │   └─restricted by compatibility requirements with GaussianMixtures [cc18c42c] to versions: 3.6.0-3.45.0
 │     └─GaussianMixtures [cc18c42c] log:
 │       ├─possible versions are: 0.3.0-0.3.6 or uninstalled
 │       ├─restricted to versions * by an explicit requirement, leaving only versions 0.3.0-0.3.6
 │       └─restricted by compatibility requirements with StatsBase [2913bbd2] to versions: 0.3.3-0.3.6 or uninstalled, leaving only versions: 0.3.3-0.3.6
 │         └─StatsBase [2913bbd2] log: see above
 ├─restricted by compatibility requirements with Lathe [38d8eb38] to versions: 0.11.7-0.22.7, leaving only versions: 0.20.0-0.22.7
 │ └─Lathe [38d8eb38] log:
 │   ├─possible versions are: 0.0.3-0.1.8 or uninstalled
 │   └─restricted to versions * by an explicit requirement, leaving only versions 0.0.3-0.1.8
 └─restricted by compatibility requirements with Clarabel [61c947e1] to versions: 1.0.0-1.3.4 — no versions left
   └─Clarabel [61c947e1] log:
     ├─possible versions are: 0.1.0-0.1.1 or uninstalled
     └─restricted to versions * by an explicit requirement, leaving only versions 0.1.0-0.1.1

julia> using Clarabel
 │ Package Clarabel not found, but a package named Clarabel is available from a registry. 
 │ Install package?
 │   (@v1.7) pkg> add Clarabel 
 └ (y/n) [y]: 
   Resolving package versions...
ERROR: Unsatisfiable requirements detected for package DataFrames [a93c6f00]:
 DataFrames [a93c6f00] log:
 ├─possible versions are: 0.11.7-1.3.4 or uninstalled
 ├─restricted to versions * by an explicit requirement, leaving only versions 0.11.7-1.3.4
 ├─restricted by compatibility requirements with StatsBase [2913bbd2] to versions: 0.19.1-1.3.4 or uninstalled, leaving only versions: 0.19.1-1.3.4
 │ └─StatsBase [2913bbd2] log:
 │   ├─possible versions are: 0.24.0-0.33.18 or uninstalled
 │   └─restricted to versions 0.33.1-0.33 by NaiveBayes [9bbee03b], leaving only versions 0.33.1-0.33.18
 │     └─NaiveBayes [9bbee03b] log:
 │       ├─possible versions are: 0.5.2 or uninstalled
 │       └─NaiveBayes [9bbee03b] is fixed to version 0.5.2
 ├─restricted by compatibility requirements with Compat [34da2185] to versions: 0.20.0-1.3.4 or uninstalled, leaving only versions: 0.20.0-1.3.4
 │ └─Compat [34da2185] log:
 │   ├─possible versions are: 1.0.0-4.1.0 or uninstalled
 │   ├─restricted to versions * by an explicit requirement, leaving only versions 1.0.0-4.1.0
 │   ├─restricted by compatibility requirements with BlackBoxOptim [a134a8b2] to versions: 1.0.0-3.45.0
 │   │ └─BlackBoxOptim [a134a8b2] log:
 │   │   ├─possible versions are: 0.4.0-0.6.1 or uninstalled
 │   │   ├─restricted to versions * by an explicit requirement, leaving only versions 0.4.0-0.6.1
 │   │   └─restricted by compatibility requirements with Compat [34da2185] to versions: 0.5.0-0.6.1 or uninstalled, leaving only versions: 0.5.0-0.6.1
 │   │     └─Compat [34da2185] log: see above
 │   └─restricted by compatibility requirements with GaussianMixtures [cc18c42c] to versions: 3.6.0-3.45.0
 │     └─GaussianMixtures [cc18c42c] log:
 │       ├─possible versions are: 0.3.0-0.3.6 or uninstalled
 │       ├─restricted to versions * by an explicit requirement, leaving only versions 0.3.0-0.3.6
 │       └─restricted by compatibility requirements with StatsBase [2913bbd2] to versions: 0.3.3-0.3.6 or uninstalled, leaving only versions: 0.3.3-0.3.6
 │         └─StatsBase [2913bbd2] log: see above
 ├─restricted by compatibility requirements with Lathe [38d8eb38] to versions: 0.11.7-0.22.7, leaving only versions: 0.20.0-0.22.7
 │ └─Lathe [38d8eb38] log:
 │   ├─possible versions are: 0.0.3-0.1.8 or uninstalled
 │   └─restricted to versions * by an explicit requirement, leaving only versions 0.0.3-0.1.8
 └─restricted by compatibility requirements with Clarabel [61c947e1] to versions: 1.0.0-1.3.4 — no versions left
   └─Clarabel [61c947e1] log:
     ├─possible versions are: 0.1.0-0.1.1 or uninstalled
     └─restricted to versions * by an explicit requirement, leaving only versions 0.1.0-0.1.1
Stacktrace:
  [1] propagate_constraints!(graph::Pkg.Resolve.Graph, sources::Set{Int64}; log_events::Bool)
    @ Pkg.Resolve /Applications/Julia-1.7.app/Contents/Resources/julia/share/julia/stdlib/v1.7/Pkg/src/Resolve/graphtype.jl:1063
  [2] propagate_constraints! (repeats 2 times)
    @ /Applications/Julia-1.7.app/Contents/Resources/julia/share/julia/stdlib/v1.7/Pkg/src/Resolve/graphtype.jl:1000 [inlined]
  [3] simplify_graph!(graph::Pkg.Resolve.Graph, sources::Set{Int64}; clean_graph::Bool)
    @ Pkg.Resolve /Applications/Julia-1.7.app/Contents/Resources/julia/share/julia/stdlib/v1.7/Pkg/src/Resolve/graphtype.jl:1519
  [4] simplify_graph! (repeats 2 times)
    @ /Applications/Julia-1.7.app/Contents/Resources/julia/share/julia/stdlib/v1.7/Pkg/src/Resolve/graphtype.jl:1519 [inlined]
  [5] resolve_versions!(env::Pkg.Types.EnvCache, registries::Vector{Pkg.Registry.RegistryInstance}, pkgs::Vector{Pkg.Types.PackageSpec}, julia_version::VersionNumber)
    @ Pkg.Operations /Applications/Julia-1.7.app/Contents/Resources/julia/share/julia/stdlib/v1.7/Pkg/src/Operations.jl:335
  [6] targeted_resolve(env::Pkg.Types.EnvCache, registries::Vector{Pkg.Registry.RegistryInstance}, pkgs::Vector{Pkg.Types.PackageSpec}, preserve::Pkg.Types.PreserveLevel, julia_version::VersionNumber)
    @ Pkg.Operations /Applications/Julia-1.7.app/Contents/Resources/julia/share/julia/stdlib/v1.7/Pkg/src/Operations.jl:1154
  [7] tiered_resolve(env::Pkg.Types.EnvCache, registries::Vector{Pkg.Registry.RegistryInstance}, pkgs::Vector{Pkg.Types.PackageSpec}, julia_version::VersionNumber)
    @ Pkg.Operations /Applications/Julia-1.7.app/Contents/Resources/julia/share/julia/stdlib/v1.7/Pkg/src/Operations.jl:1139
  [8] _resolve(io::Base.TTY, env::Pkg.Types.EnvCache, registries::Vector{Pkg.Registry.RegistryInstance}, pkgs::Vector{Pkg.Types.PackageSpec}, preserve::Pkg.Types.PreserveLevel, julia_version::VersionNumber)
    @ Pkg.Operations /Applications/Julia-1.7.app/Contents/Resources/julia/share/julia/stdlib/v1.7/Pkg/src/Operations.jl:1160
  [9] add(ctx::Pkg.Types.Context, pkgs::Vector{Pkg.Types.PackageSpec}, new_git::Set{Base.UUID}; preserve::Pkg.Types.PreserveLevel, platform::Base.BinaryPlatforms.Platform)
    @ Pkg.Operations /Applications/Julia-1.7.app/Contents/Resources/julia/share/julia/stdlib/v1.7/Pkg/src/Operations.jl:1176
 [10] add(ctx::Pkg.Types.Context, pkgs::Vector{Pkg.Types.PackageSpec}; preserve::Pkg.Types.PreserveLevel, platform::Base.BinaryPlatforms.Platform, kwargs::Base.Pairs{Symbol, Base.TTY, Tuple{Symbol}, NamedTuple{(:io,), Tuple{Base.TTY}}})
    @ Pkg.API /Applications/Julia-1.7.app/Contents/Resources/julia/share/julia/stdlib/v1.7/Pkg/src/API.jl:268
 [11] add(pkgs::Vector{Pkg.Types.PackageSpec}; io::Base.TTY, kwargs::Base.Pairs{Symbol, Union{}, Tuple{}, NamedTuple{(), Tuple{}}})
    @ Pkg.API /Applications/Julia-1.7.app/Contents/Resources/julia/share/julia/stdlib/v1.7/Pkg/src/API.jl:149
 [12] add(pkgs::Vector{Pkg.Types.PackageSpec})
    @ Pkg.API /Applications/Julia-1.7.app/Contents/Resources/julia/share/julia/stdlib/v1.7/Pkg/src/API.jl:144
 [13] #add#27
    @ /Applications/Julia-1.7.app/Contents/Resources/julia/share/julia/stdlib/v1.7/Pkg/src/API.jl:142 [inlined]
 [14] add
    @ /Applications/Julia-1.7.app/Contents/Resources/julia/share/julia/stdlib/v1.7/Pkg/src/API.jl:142 [inlined]
 [15] try_prompt_pkg_add(pkgs::Vector{Symbol})
    @ Pkg.REPLMode /Applications/Julia-1.7.app/Contents/Resources/julia/share/julia/stdlib/v1.7/Pkg/src/REPLMode/REPLMode.jl:710
 [16] #invokelatest#2
    @ ./essentials.jl:716 [inlined]
 [17] invokelatest
    @ ./essentials.jl:714 [inlined]
 [18] check_for_missing_packages_and_run_hooks(ast::Any)
    @ REPL /Applications/Julia-1.7.app/Contents/Resources/julia/share/julia/stdlib/v1.7/REPL/src/REPL.jl:175
 [19] eval_user_input(ast::Any, backend::REPL.REPLBackend)
    @ REPL /Applications/Julia-1.7.app/Contents/Resources/julia/share/julia/stdlib/v1.7/REPL/src/REPL.jl:145
 [20] repl_backend_loop(backend::REPL.REPLBackend)
    @ REPL /Applications/Julia-1.7.app/Contents/Resources/julia/share/julia/stdlib/v1.7/REPL/src/REPL.jl:244
 [21] start_repl_backend(backend::REPL.REPLBackend, consumer::Any)
    @ REPL /Applications/Julia-1.7.app/Contents/Resources/julia/share/julia/stdlib/v1.7/REPL/src/REPL.jl:229
 [22] run_repl(repl::REPL.AbstractREPL, consumer::Any; backend_on_current_task::Bool)
    @ REPL /Applications/Julia-1.7.app/Contents/Resources/julia/share/julia/stdlib/v1.7/REPL/src/REPL.jl:362
 [23] run_repl(repl::REPL.AbstractREPL, consumer::Any)
    @ REPL /Applications/Julia-1.7.app/Contents/Resources/julia/share/julia/stdlib/v1.7/REPL/src/REPL.jl:349
 [24] (::Base.var"#930#932"{Bool, Bool, Bool})(REPL::Module)
    @ Base ./client.jl:394
 [25] #invokelatest#2
    @ ./essentials.jl:716 [inlined]
 [26] invokelatest
    @ ./essentials.jl:714 [inlined]
 [27] run_main_repl(interactive::Bool, quiet::Bool, banner::Bool, history_file::Bool, color_set::Bool)
    @ Base ./client.jl:379
 [28] exec_options(opts::Base.JLOptions)
    @ Base ./client.jl:309
 [29] _start()
    @ Base ./client.jl:495

julia> 

The problem appears to be that the solver wants the DataFrames package version 1 or higher, but this is causing a conflict with other packages that you are using (e.g. NaiveBayes, GaussianMixtures and maybe a few others).

DataFrames is only used by the solver to nicely format the list of solver options when writing to the terminal and is probably overkill. I also don’t have any particular justification for having listed the required version as 1 or higher. You could try editing the Project.toml file to accept earlier versions, and it may well work.

Failing that, you could open this as an issue here and we can take a look.

DataFrames uses PrettyTables.jl as the printing backend, which works with a wide variety of data structures. Probably you could drop DataFrames and only depend on PrettyTables.

4 Likes

Hi. I didn’t intend to distract the discussion here. This looks like a great contribution. I had a thought to use it in my classes (I teach large classes) but I (and my students) use all the other packages mentioned above (esp. DataFrames) and can’t afford to risk breaking everything else.
Looks like I will have to keep an eye on this for potential inclusion in my classes next Autumn (2023) if the dependencies get ironed out by then.
Looks like a great contribution, though!

I have released a patched version v0.1.2 that removes the dependency on DataFrames as suggested by @tbeason.

2 Likes

Thank you so much! I’ll try it! [worked!!]

PS.I benchmarked it on the sample problem on the JuMP documentation
https://jump.dev/JuMP.jl/stable/tutorials/getting_started/getting_started_with_JuMP/
and found similar timing (.1 to .6 s, avg. ~ .3 s) to HiGHS. Is that what you would have expected?

Just a comment (after playing around - not sure this is the best forum).

The abbreviated output of HiGHS has both objective value and feasibility status (which I find very useful), but the (default) abbreviated output of Clarabel does not. If a future version could include these, I would regard this as a great improvement, from a casual user point of view.

1 Like

Clarabel.jl seems to work very well. Thanks a lot.

I just noticed this while testing:

  1. it does not seem to like b[i] = (-)Inf
  2. [NonnegativeConeT(2)] has no collect method, which makes it cumbersome to filter out all restrictions where b[i] = (-)Inf.

Background: I wrote a wrapper to convert the restrictions to OSQP style: lower <= Ax <= upper and that sometimes involves non-binding constraints ((-)Inf)

@Paul_Soderlind You are right, it fails ungracefully with an infinite bound in b. For the moment the only option is to pre-filter those constraints before passing to the solver. I will open an issue on it though in github, since it should at least give a clear warning or error in that case.

Regarding the use of collect : I don’t understand what you want to do there. What call are you trying to make, and what output are you expecting? I’m happy to implement the method if it’s useful, so please do open an issue if you like.

1 Like

@compleat When you say “abbreviated output” do you mean with verbose = false? The standard output already shows the progress of the primal/dual objectives and gives the final solver status at the end. With the verbose printing off it doesn’t print anything, but you can still get both objective value and status in the solver’s result field.

Maybe you mean via JuMP or something here though. Please do open an issue on github with an MWE and the output you hope to see and I can take a look.

PS.I benchmarked it on the sample problem on the JuMP documentation and found similar timing (.1 to .6 s, avg. ~ .3 s) to HiGHS. Is that what you would have expected?

I have benchmarked against that solver, but if the problem you tried was an LP then it is not that surprising I think.

1 Like

Yes, but it is no big deal. Regarding the benchmark, it really was more of quick check for the example (LP) problem to make sure everything was working OK, not a criticism.
I’m sure whatever gains have been achieved on bigger problems will carry through on JuMP.

Regarding the use of collect

To filter out the b[i]==Inf cases, I currently need to build the vector cones in a loop, so it has as many elements as length(b). Then, I can index into it with cones[.!isinf.(b)].

It would nice if I could create this vector by collect([NonnegativeConeT(2)]) or similarly to allow the indexing.

Dear Julia Optimizers,

A further announcement about this solver. We have released a pure Rust implementation of this solver to Github, along with a Python interface to the Rust version.

There is also now a common documentation site for all versions here https://github.com/oxfordcontrol/ClarabelDocs.

There you will find example code showing how to solve problems through all three interfaces.

Since someone is likely to ask: no, we have not yet benchmarked the performance of the Julia version vs the Rust version. My impression is that Rust is a bit faster, but not by much.

We will continue to develop both the Julia and Rust versions in parallel for the forseeable future. We will also continue to support the Python interface and are working on adding support for it through cvxpy.

Enjoy!

12 Likes

Thanks for the solver!

Having a rust implementation really helps in easily trying the solver in some codebases I work at.

I am curious: how do plan to avoid having the two parallel implementations (rust & julia) from diverging?