Features • Installation • License • Documentation
CuClarabel.jl is the GPU implementation of the Clarabel solver, which can solve conic problems of the following form:
with decision variables
- CuClarabel.jl can be added via the Julia package manager (type
]
):pkg> dev https://github.com/cvxgrp/CuClarabel.git
, (which will overwrite current use of Clarabel solver).
Modeling a conic optimization problem is the same as in the original Clarabel solver, except with the additional parameter direct_solve_method
. This can be set to :cudss
or :cudssmixed
. Here is a portfolio optimization problem modelled via JuMP:
using LinearAlgebra, SparseArrays, Random, JuMP
using Clarabel
## generate the data
rng = Random.MersenneTwister(1)
k = 5; # number of factors
n = k * 10; # number of assets
D = spdiagm(0 => rand(rng, n) .* sqrt(k))
F = sprandn(rng, n, k, 0.5); # factor loading matrix
μ = (3 .+ 9. * rand(rng, n)) / 100. # expected returns between 3% - 12%
γ = 1.0; # risk aversion parameter
d = 1 # we are starting from all cash
x0 = zeros(n);
a = 1e-3
b = 1e-1
γ = 1.0;
model = JuMP.Model(Clarabel.Optimizer)
set_optimizer_attribute(model, "direct_solve_method", :cudss)
@variable(model, x[1:n])
@variable(model, y[1:k])
@variable(model, s[1:n])
@variable(model, t[1:n])
@objective(model, Min, x' * D * x + y' * y - 1/γ * μ' * x);
@constraint(model, y .== F' * x);
@constraint(model, x .>= 0);
# transaction costs
@constraint(model, sum(x) + a * sum(s) == d + sum(x0) );
@constraint(model, [i = 1:n], x0[i] - x[i] == t[i])
@constraint(model, [i = 1:n], [s[i], t[i]] in MOI.SecondOrderCone(2));
JuMP.optimize!(model)
We can call julia code within a python file by using JuliaCall package. We can download the package by
pip install juliacall
Then, we load the package as a single variable jl
which represents the Main module in Julia, and we can write Julia code and call it via jl.seval()
in Python.
from juliacall import Main as jl
import numpy as np
import cupy as cp
from cupyx.scipy.sparse import csr_matrix
# Load Clarabel in Julia
jl.seval('using Clarabel, LinearAlgebra, SparseArrays')
jl.seval('using CUDA, CUDA.CUSPARSE')
Here we build up a simple optimization problem with a second-order cone, which is fully written by Julia.
jl.seval('''
P = CuSparseMatrixCSR(sparse([2.0 1.0 0.0;
1.0 2.0 0.0;
0.0 0.0 2.0]))
q = CuVector([0, -1., -1])
A = CuSparseMatrixCSR(SparseMatrixCSC([1. 0 0; -1 0 0; 0 -1 0; 0 0 -1]))
b = CuVector([1, 0., 0., 0.])
# 0-cone dimension 1, one second-order-cone of dimension 3
cones = Dict("f" => 1, "q"=> [3])
settings = Clarabel.Settings(direct_solve_method = :cudss)
solver = Clarabel.Solver(P,q,A,b,cones, settings)
Clarabel.solve!(solver)
# Extract solution
x = solver.solution
''')
It is also possible to call the julia functions directly via JuliaCall. For example, if we want to reuse the solver object and update only coefficients in the problem, we can call the following blocks,
# Update b vector
bpy = cp.array([2.0, 1.0, 1.0, 1.0], dtype=cp.float64)
bjl = jl.Clarabel.cupy_to_cuvector(jl.Float64, int(bpy.data.ptr), bpy.size)
# "_b" is the replacement of "!" in julia function
jl.Clarabel.update_b_b(jl.solver,bjl) #Clarabel.update_b!()
# Update P matrix
# Define a new CSR sparse matrix on GPU
Ppy = csr_matrix(cp.array([
[3.0, 0.5, 0.0],
[0.5, 2.0, 0.0],
[0.0, 0.0, 1.0]
], dtype=cp.float64))
# Extract the pointers (as integers)
data_ptr = int(Ppy.data.data.ptr)
indices_ptr = int(Ppy.indices.data.ptr)
indptr_ptr = int(Ppy.indptr.data.ptr)
# Get matrix shape and non-zero count
n_rows, n_cols = Ppy.shape
nnz = Ppy.nnz
jl.Pjl = jl.Clarabel.cupy_to_cucsrmat(jl.Float64, data_ptr, indices_ptr, indptr_ptr, n_rows, n_cols, nnz)
jl.Clarabel.update_P_b(jl.solver, jl.Pjl) #Clarabel.update_P!()
#Solve the new problem without creating memory
jl.Clarabel.solve_b(jl.solver) #Clarabel.solve!()
where we update the linear cost b
by values in a cupy vector bpy
and the quadratic cost P
by values in a cupy csr matrix Ppy
. Note that we need to replace !
in a julia function with _b
. Reversely, we can also extract value from a Julia object back to Python,
# Retrieve the solution from Julia to Python
solution = np.array(jl.solver.solution.x)
print("Solution:", solution)
The example file can be found under the python
folder.
Due to the just-in-time (JIT)
compilation in Julia, the first call of CuClarabel
will also be slow in python and it is recommended to solve a mini problem first to trigger the JIT-compilation and get full performance on the subsequent solve of the actual problem.
@misc{CuClarabel,
title={CuClarabel: GPU Acceleration for a Conic Optimization Solver},
author={Yuwen Chen and Danny Tse and Parth Nobel and Paul Goulart and Stephen Boyd},
year={2024},
eprint={2412.19027},
archivePrefix={arXiv},
primaryClass={math.OC},
url={https://arxiv.org/abs/2412.19027},
}
This project is licensed under the Apache License 2.0 - see the LICENSE.md file for details.