A fast, pure-C# reimplementation of the classic SLSQP (Sequential Least-Squares Quadratic Programming) optimiser — written in 100% managed .NET 10 — featuring high-performance Span-based APIs and zero native/unmanaged dependencies.
cslsqp delivers the robust non-linear constrained optimization of SLSQP directly to C# developers, eliminating native library marshalling and deployment baggage.
- 🚀 100% Pure C# solver core — the entire iteration loop, BFGS update, QP sub-problem, and line-search run in fully compiled, managed .NET 10.
- 🪶 Zero native dependencies — pure C# with no need for Fortran, C/C++ libraries, or external native binary DLLs.
- ⚡ Zero-allocation — leverages
ReadOnlySpan<double>andSpan<double>delegates to avoid heap allocations in critical function and gradient evaluation loops. - 🧮 SIMD Acceleration — utilizes
System.Numerics.Tensorsfor hardware-accelerated vector and matrix operations. - 🔌 Simple ease-of-use — a standard static
SlsqpSolver.Optimizemethod accepts traditional array-based delegates or high-performance Span-based delegates. - 🛠 Flexible configuration — supports analytical gradients or finite differences (Forward, Backward, Central), inexact or exact line-search, and NNLS/BVLS sub-solvers.
Add the package via NuGet:
dotnet add package cslsqpRuntime requirement: .NET 10.0+.
For quick and easy setup, use standard array-based delegates:
using System;
using cslsqp;
using cslsqp.Types;
// Objective function: Rosenbrock function
Func<double[], double> objective = (x) =>
100.0 * Math.Pow(x[1] - x[0] * x[0], 2) + Math.Pow(1.0 - x[0], 2);
// Constraint: c(x) >= 0 (inequality)
Func<double[], double[]> constraints = (x) =>
new double[] { 1.0 - x[0] * x[0] - x[1] * x[1] };
double[] x0 = { 0.1, 0.1 };
double[] xl = { -1.0, -1.0 }; // lower bounds
double[] xu = { 1.0, 1.0 }; // upper bounds
SlsqpResult result = SlsqpSolver.Optimize(
objective,
x0,
constraints: constraints,
xl: xl,
xu: xu,
gradientMode: GradientMode.Central
);
Console.WriteLine($"Status: {result.Status} (Success: {result.Success})");
Console.WriteLine($"Function value: {result.Fun}");
Console.WriteLine($"Solution: [{string.Join(", ", result.X)}]");To avoid heap allocations in inner loops, use the high-performance Span-based interface:
using System;
using cslsqp;
using cslsqp.Types;
// Zero-allocation objective
Func<ReadOnlySpan<double>, double> objective = (x) =>
100.0 * Math.Pow(x[1] - x[0] * x[0], 2) + Math.Pow(1.0 - x[0], 2);
// Zero-allocation constraints (write directly into the output Span)
Action<ReadOnlySpan<double>, Span<double>> constraints = (x, c) =>
{
c[0] = 1.0 - x[0] * x[0] - x[1] * x[1]; // inequality: c[0] >= 0
};
double[] x0 = { 0.1, 0.1 };
double[] xl = { -1.0, -1.0 };
double[] xu = { 1.0, 1.0 };
SlsqpResult result = SlsqpSolver.Optimize(
objective,
x0,
m: 1, // specify total number of constraints
constraints: constraints,
xl: xl,
xu: xu,
gradientMode: GradientMode.Central
);
Console.WriteLine($"Status: {result.Status} (Success: {result.Success})");
Console.WriteLine($"Function value: {result.Fun}");| Enum | Values | Description |
|---|---|---|
GradientMode |
User, Backward, Forward, Central |
How gradients are supplied or approximated |
LinesearchMode |
Inexact, Exact |
Line-search strategy |
NnlsMode |
Nnls, Bvls |
Non-negative least-squares sub-solver |
SlsqpStatus |
Converged, MaxIterationsReached, FuncEvalRequired, … |
Solver exit status |
| Class | Description |
|---|---|
SlsqpSolver |
High-level static entry point — configure and run Optimize(...) |
SlsqpResult |
Output containing X (solution), Fun (minimum value), Constraints (final constraint values), Status, Message, Iterations, Success, NFev, and NJev |
Below are the benchmark results from an Apple M1 Pro (10 runs, 2 warm-ups, matching rslsqp benchmarks):
| Problem | n | Constraints | Wall-Clock (ms) | nit | nfev | njev | Success |
|---|---|---|---|---|---|---|---|
| Rosenbrock unconstrained | 50 | 0 | 38.09 | 229 | 626 | 229 | ✓ |
| Rosenbrock unconstrained | 100 | 0 | 373.57 | 451 | 1236 | 451 | ✓ |
| Rosenbrock unconstrained | 200 | 0 | 2955.57 | 501 | 1583 | 501 | ⚠ |
| Rosenbrock constrained | 50 | 2 | 13.32 | 85 | 312 | 85 | ✓ |
| Rosenbrock constrained | 100 | 2 | 162.62 | 171 | 701 | 171 | ✓ |
| Portfolio optimisation | 50 | 2 | 8.78 | 47 | 88 | 47 | ✓ |
| Portfolio optimisation | 100 | 2 | 63.48 | 51 | 99 | 51 | ✓ |
| Portfolio optimisation | 200 | 2 | 280.48 | 33 | 62 | 33 | ✓ |
| Quadratic + 100 ineq | 50 | 100 | 26.86 | 38 | 114 | 34 | ⚠ |
| Quadratic + 200 ineq | 100 | 200 | 361.64 | 52 | 210 | 48 | ⚠ |
| Least-squares fitting | 20 | 1 | 0.43 | 5 | 13 | 5 | ✓ |
| Least-squares fitting | 40 | 1 | 0.34 | 2 | 2 | 2 | ✓ |
To run the direct benchmarks on your machine:
dotnet run --project cslsqp.Benchmarks -c ReleaseISC — see LICENSE for details.
Based on the SLSQP algorithm by Dieter Kraft (1988), modernised in Fortran by Jacob Williams (slsqp, BSD-3-Clause).
