A modern .NET interface for IPOPT (Interior Point OPTimizer), a software library for large-scale nonlinear optimization. This library provides both a high-level modeling API with automatic differentiation and a low-level native wrapper.
Install the package via NuGet:
dotnet add package ipopt-netThe package includes native binaries for IPOPT 3.14.20 (with MUMPS 5.9.0):
- Windows (x64) —
ipopt-3.dll - Linux (x64) —
libipopt-3.so
Both are a single self-contained library: MUMPS, MKL Pardiso, BLAS/LAPACK and the GCC/Fortran runtimes are statically linked, so no other DLLs or shared objects are needed — and on Windows, no Visual C++ redistributable either.
Both are built from the same sources by the scripts in build/, which
document the process. The Windows DLL is cross-compiled from WSL with mingw-w64,
so the two platforms stay on matching IPOPT and MUMPS versions.
- Modeling API: Define nonlinear optimization problems using C# expressions with natural syntax
- Automatic Differentiation: Gradients and Hessians computed automatically via reverse-mode AD
- Intelligent Matrix Caching: Automatically detects and pre-computes constant matrices for LP/QP/QCP problems
- Automatic Partitioning: Detects models that split into independent sub-problems and solves each separately
- High-level Wrapper: Clean, disposable
IpoptSolverclass for direct API access - Native Performance: Uses .NET 10
LibraryImportfor efficient C API calls - Expression Support: Arithmetic, trigonometric, exponential, logarithmic, and power operations
- Flexible Constraints: Equality, inequality, and bound constraints
The modeling API allows you to define optimization problems with automatic differentiation:
using IpoptNet.Modelling;
// Create a model
var model = new Model();
// Configure IPOPT (optional)
model.Options.LinearSolver = LinearSolver.PardisoMkl;
model.Options.HessianApproximation = HessianApproximation.LimitedMemory;
// Add variables with bounds and optional initial guesses
var x = model.AddVariable(1, 5);
var y = model.AddVariable(1, 5) { Start = 3.7 };
var z = model.AddVariable(1, 5);
var w = model.AddVariable(1, 5);
// Set objective: minimize x*w*(x+y+z) + z (expressions can be built incrementally)
var expr = x * (x + y + z);
expr *= w;
model.SetObjective(expr + z);
// Add constraints
model.AddConstraint(x * y * z * w >= 25);
model.AddConstraint(x*x + y*y + z*z + w*w == 40);
// Solve
var result = model.Solve();
if (result.Status == ApplicationReturnStatus.SolveSucceeded)
{
Console.WriteLine($"x = {result.Solution[x]:F3}");
Console.WriteLine($"y = {result.Solution[y]:F3}");
Console.WriteLine($"z = {result.Solution[z]:F3}");
Console.WriteLine($"w = {result.Solution[w]:F3}");
Console.WriteLine($"Objective = {result.ObjectiveValue:F3}");
}Output:
x = 1.000
y = 4.743
z = 3.821
w = 1.379
Objective = 17.014
The expression system supports:
- Arithmetic:
+,-,*,/, unary- - Power:
Expr.Pow(x, n),Expr.Sqrt(x) - Trigonometric:
Expr.Sin(x),Expr.Cos(x),Expr.Tan(x) - Exponential/Log:
Expr.Exp(x),Expr.Log(x) - Constraints:
>=,<=,==
Many models decompose into independent sub-problems that share no variable through any constraint, implicit block, or objective term. Because IPOPT's linear-algebra cost grows superlinearly with problem size, solving each sub-problem separately is both exact and considerably cheaper.
This feature is enabled by default. result.Partitions exposes the individual
sub-problems when a model does decompose:
var model = new Model();
// ... build the model ...
var result = model.Solve();
foreach (var partition in result.Partitions)
Console.WriteLine($"{partition.Status}: {partition.ObjectiveValue}");Set Model.EnablePartitioning = false to force a single whole-model solve, skipping the decomposition analysis entirely.
Status, Solution, ObjectiveValue and Statistics on the returned ModelResult are
model-level aggregates, so a partitioned solve reads the same as an unpartitioned one; the
individual sub-problem results are on Partitions. Every partition is always attempted, so one
failing sub-problem never suppresses the others.
// before: Func<SolveStatistics, bool>
model.IntermediateCallback = (stats, partition) =>
{
// stats always describes the whole model: ObjectiveValue accounts for partitions already
// solved, the one currently iterating, and the ones not yet started; IterationCount is
// cumulative. So best-so-far tracking needs no changes.
// partition.Index / .Count identify the sub-problem; partition.LocalStatistics has the raw
// per-partition numbers. With partitioning off these are 0 and 1.
return !cancelled;
};Iteration and time limits are treated differently, on purpose:
| Option | Scope | Why |
|---|---|---|
MaxIterations |
per partition | It guards against one sub-problem spinning forever. |
MaxWallTime, MaxCpuTime |
model-wide | These are deadlines. Each partition is handed what remains of the budget, so N partitions cannot take N times as long as you allowed. |
Elapsed wall time is measured exactly. Elapsed CPU time is taken from the process total, which over-counts when other threads in your application are busy — it therefore errs toward stopping sooner, never toward overrunning the budget.
IPOPT returns its final iterate, which is not always its best one. A run that ends on
MaximumIterationsExceeded, RestorationFailed, or a caller-requested stop can finish somewhere
worse than it passed through earlier. Every solve therefore records the best point it saw:
var result = model.Solve();
var best = result.BestIterate;
if (best is not null && best.IsFeasible)
Console.WriteLine($"best objective {best.ObjectiveValue} at iteration {best.IterationCount}");BestIterate.Solution covers every variable, implicit-block-eliminated ones included, and under
partitioning it is the whole model's — no partition bookkeeping required.
"Best" is feasibility-first, not lowest-objective: the lowest-objective iterate whose constraint
violation is within ConstraintViolationTolerance, falling back to the least-infeasible point (with
IsFeasible false) when nothing feasible was ever seen.
A variable defined by an equality it appears in linearly can be moved out of IPOPT's decision vector
and computed from that equality instead. Model.FindEliminableVariables() reports what qualifies without changing anything:
foreach (var c in model.FindEliminableVariables())
Console.WriteLine($"x[{c.Variable.Index}] could be defined by its constraint (coefficient {c.Coefficient})");
model.EnableAutomaticElimination = true; // off by defaultA pair qualifies when the constraint is an equality of the form expression == 0, the variable's
partial derivative of it is a non-zero constant, and the variable is unbounded.
This is off by default. Unlike partitioning it is not a free win: the reduced problem has the same optimum in exact arithmetic but is a different problem for IPOPT to walk, with different conditioning, and each eliminated variable enters it nonlinearly through its block. Measure before adopting it.
The solver automatically detects problem structure and optimizes matrix computations:
For certain problem types, derivative matrices remain constant throughout the solution process. The library automatically detects these cases and pre-computes matrices once:
| Problem Type | Constant Matrices | Description |
|---|---|---|
| Linear Programming (LP) | Gradient, Jacobian | All derivatives are constant coefficients |
| Quadratic Programming (QP) | Jacobian, Hessian | Linear constraints have constant gradients; quadratic terms have constant second derivatives |
| Quadratically Constrained (QCP) | Hessian contributions | Quadratic constraints contribute constant Hessian terms |
Example - Linear Program:
var model = new Model();
var x = model.AddVariable(0, 10);
var y = model.AddVariable(0, 10);
// Linear objective and constraints - matrices computed once
model.SetObjective(2*x + 3*y);
model.AddConstraint(x + 2*y <= 10);
model.AddConstraint(3*x + y <= 12);
var result = model.Solve();Example - Quadratic Program:
var model = new Model();
var x = model.AddVariable();
var y = model.AddVariable();
// Quadratic objective, linear constraints - Hessian and Jacobian computed once
model.SetObjective(x*x + y*y - 4*x - 6*y);
model.AddConstraint(x + y <= 5);
var result = model.Solve();This optimization is completely automatic - no code changes required. The solver analyzes the expression structure and applies the appropriate strategy.
var model = new Model();
var x = model.AddVariable();
var y = model.AddVariable();
// Minimize (1-x)^2 + 100*(y-x^2)^2
model.SetObjective(Expr.Pow(1 - x, 2) + 100 * Expr.Pow(y - x*x, 2));
var result = model.Solve();
// Converges to x=1, y=1var model = new Model();
var x = model.AddVariable();
var y = model.AddVariable();
// Minimize x^2 + y^2
model.SetObjective(x*x + y*y);
// Subject to x + y = 4
model.AddConstraint(x + y == 4);
var result = model.Solve();
// Solution: x=2, y=2, objective=8var model = new Model();
var x = model.AddVariable(-Math.PI, Math.PI);
// Minimize -sin(x)
model.SetObjective(-Expr.Sin(x));
var result = model.Solve();
// Converges to x=π/2The modeling API exposes all IPOPT configuration options through a strongly-typed API with enums:
var model = new Model();
// Configure solver options using enums (type-safe with IntelliSense)
model.Options.LinearSolver = LinearSolver.PardisoMkl; // Use Intel MKL Pardiso
model.Options.HessianApproximation = HessianApproximation.Exact;
model.Options.MuStrategy = MuStrategy.Adaptive;
// Configure termination criteria
model.Options.Tolerance = 1e-7;
model.Options.MaxIterations = 100;
model.Options.MaxWallTime = 60.0; // seconds
// Configure output verbosity
model.Options.PrintLevel = 5; // 0=no output, 5=default, 12=verbose
model.Options.OutputFile = "ipopt.log";
// Configure NLP scaling
model.Options.NlpScalingMethod = NlpScalingMethod.GradientBased;
// Use custom options for advanced features
model.Options.SetCustomOption("bound_push", 0.01);
model.Options.SetCustomOption("acceptable_tol", 1e-5);
// Define and solve your problem...
// ...
var result = model.Solve();LinearSolver.Mumps- Default, included with IPOPTLinearSolver.PardisoMkl- Intel MKL Pardiso, included with IPOPTLinearSolver.PardisoProject- Pardiso from pardiso-project.org (often faster, requires external library)LinearSolver.Ma27,Ma57,Ma77,Ma86,Ma97- HSL solvers (require external library)LinearSolver.Wsmp- Watson Sparse Matrix Package (requires external library)LinearSolver.Spral- Sparse Parallel Robust Algorithms Library (requires external library)
- Termination:
Tolerance,MaxIterations,MaxWallTime,MaxCpuTime - Output:
PrintLevel,OutputFile,PrintUserOptions - Algorithm:
LinearSolver,HessianApproximation,MuStrategy - Scaling:
NlpScalingMethod,LinearSystemScaling - Tolerances:
ConstraintViolationTolerance,DualInfeasibilityTolerance
For advanced users who want direct control over the IPOPT solver:
using IpoptNet;
// Define callback functions
EvalFCallback evalF = (n, x, newX, objValue, userData) =>
{
*objValue = x[0] * x[3] * (x[0] + x[1] + x[2]) + x[2];
// Note: If a callback cannot be evaluated at a given point (e.g. division by zero),
// it should return false. IPOPT will then attempt to backtrack to a valid point.
// If it cannot recover, the solve will terminate with InvalidNumberDetected.
return true;
};
// Define gradient, constraint, Jacobian, and Hessian callbacks...
// Create solver
using var solver = new IpoptSolver(
n: 4, xL, xU,
m: 2, gL, gU,
jacobianNonZeros, hessianNonZeros,
evalF, evalGradF, evalG, evalJacG, evalH);
// Set options
solver.SetOption("print_level", 5);
solver.SetOption("tol", 1e-7);
// Solve
var x = new double[] { 1, 5, 5, 1 };
var status = solver.Solve(x, out var objValue);IPOPT solves nonlinear optimization problems of the form:
minimize f(x)
subject to g_L ≤ g(x) ≤ g_U
x_L ≤ x ≤ x_U
where:
f(x)is the objective functiong(x)are constraint functionsxare the optimization variables- Bounds can be infinite for unconstrained dimensions
- IPOPT Project: https://coin-or.github.io/Ipopt/
- IPOPT Documentation: https://coin-or.github.io/Ipopt/DOCUMENTATION.html
- IPOPT Paper: Wächter & Biegler (2006), "On the implementation of an interior-point filter line-search algorithm for large-scale nonlinear programming"
This .NET wrapper is provided as-is. IPOPT itself is released under the Eclipse Public License (EPL).
IPOPT is developed and maintained by the COIN-OR project. This wrapper provides a convenient .NET interface with automatic differentiation capabilities.
The native binaries bundled with this package include statically-linked Intel oneAPI Math Kernel Library (MKL) components (mkl_intel_lp64, mkl_sequential, mkl_core) redistributed under the Intel Simplified Software License.