Skip to content

Disciplined nonlinear programming (DNLP)#3108

Merged
PTNobel merged 161 commits intocvxpy:masterfrom
cvxgrp:cvxpy-merge
Mar 7, 2026
Merged

Disciplined nonlinear programming (DNLP)#3108
PTNobel merged 161 commits intocvxpy:masterfrom
cvxgrp:cvxpy-merge

Conversation

@Transurgeon
Copy link
Copy Markdown
Collaborator

@Transurgeon Transurgeon commented Feb 1, 2026

Add Disciplined Nonlinear Programming (DNLP) extension to CVXPY

Summary

This PR adds Disciplined Nonlinear Programming (DNLP) support to CVXPY, enabling users to formulate and solve general nonlinear programs by freely mixing smooth functions with nonsmooth convex and concave functions under disciplined composition rules.

For theoretical background, see: Disciplined Nonlinear Programming

Usage

import cvxpy as cp
import numpy as np

x = cp.Variable(n)
x.value = np.ones(n)  # initial point required
prob = cp.Problem(cp.Minimize(objective), constraints)
prob.solve(nlp=True, solver=cp.IPOPT)

What's included

DNLP expression system and composition rules

  • New expression properties: is_smooth(), is_linearizable_convex(), is_linearizable_concave() on Expression, Atom, Leaf, Objective, and constraint classes
  • is_dnlp() validation on Problem to check DNLP compliance
  • is_atom_smooth() method on atoms to classify smooth vs. non-smooth atoms

New atoms

  • Trigonometric: sin, cos, tan (cvxpy/atoms/elementwise/trig.py)
  • Hyperbolic: sinh, tanh, asinh, atanh (cvxpy/atoms/elementwise/hyperbolic.py)

Dnlp2Smooth reduction

  • Dnlp2Smooth reduction (cvxpy/reductions/dnlp2smooth/) that rewrites non-smooth atoms into smooth equivalents using auxiliary variables
  • 18 smooth canonicalizers for: abs, max, maximum, norm_inf, sum_largest, div, entr, exp, geo_mean, huber, kl_div, log, log_sum_exp, logistic, multiply, pnorm, power, prod, quad_over_lin, rel_entr, and trig/hyperbolic atoms

NLP solver infrastructure

  • Base NLPsolver class (cvxpy/reductions/solvers/nlp_solvers/nlp_solver.py) with:
    • Bounds: extracts variable and constraint bounds from the reduced problem
    • Oracles: provides function/derivative oracles (objective, gradient, constraints, Jacobian, Hessian of the Lagrangian)
  • NLP solving chain (cvxpy/reductions/solvers/nlp_solving_chain.py): orchestrates FlipObjective → CvxAttr2Constr → Dnlp2Smooth → NLPSolver, handles initial point construction (_set_nlp_initial_point), and best_of multi-start logic
  • Solver variant mapping (NLP_SOLVER_VARIANTS) for algorithm selection (e.g., "knitro_sqp")

Differentiation engine integration

  • C_problem wrapper (cvxpy/reductions/solvers/nlp_solvers/diff_engine/c_problem.py) around the SparseDiffPy C library, providing objective_forward(), gradient(), jacobian(), hessian() oracles
  • AST-to-C converters (converters.py) with ATOM_CONVERTERS dict mapping ~40 atom types to C constructors, including sparse parameter matmul fusion
  • Parameter support: parameters are registered with the C engine and values can be updated without rebuilding the expression tree

Four NLP solver interfaces

Solver File License
IPOPT ipopt_nlpif.py EPL-2.0 (open source)
Knitro knitro_nlpif.py Commercial
COPT copt_nlpif.py Commercial
Uno uno_nlpif.py Open source

CI

  • New test_nlp_solvers GitHub Actions workflow (.github/workflows/test_nlp_solvers.yml) that installs system IPOPT via uv and runs NLP solver tests

Documentation

  • DNLP tutorial (doc/source/tutorial/dnlp/index.rst)
  • CLAUDE.md for developer onboarding

Comprehensive test suite (29 test files, ~3,800 lines)

  • Derivative verification via DerivativeChecker (finite-difference checks of gradients, Jacobians, and Hessians)
  • Atom-level tests: abs, entropy/KL-div/rel-entropy, huber/sum-largest, hyperbolic, log-sum-exp, matmul, prod, sum, broadcast, power-flow
  • Problem-level tests: scalar and matrix problems, DNLP validation, initialization, best-of multi-start, quasi-Newton methods
  • Solver interface tests: cross-solver comparison (IPOPT, Knitro, COPT, Uno)
  • Application tests: risk parity, Sharpe ratio, ML Gaussian stress test
  • Diff engine stress tests: affine matrix/vector atoms, sparse matmul, sparse multiply, quad_form

Diff stats

  • 108 files changed, ~8,300 insertions, ~170 deletions
  • ~3,300 lines of core NLP infrastructure (solvers, reductions, diff engine)
  • ~3,900 lines of tests
  • ~500 lines of atom additions/modifications

Test plan

  • All existing CVXPY tests pass (no regressions to DCP/DGP/DQCP)
  • pytest cvxpy/tests/nlp_tests/ passes with IPOPT installed
  • NLP solver CI workflow (test_nlp_solvers) passes
  • Pre-commit checks (ruff, actionlint, jsonschema) pass
  • Cross-platform build (Ubuntu/macOS/Windows) succeeds

Transurgeon and others added 30 commits June 16, 2025 23:53
initial attempts at adding a smooth canon for maximum
* adds oracles and bounds class to ipopt interface

* adds some settings and solver lists changes for IPOPT

* adds nlp solver option and can call ipopt

* adds more experiments for integrating ipopt as a solver interface

* passing the problem through the inversion

* add some more extra changes

* adding nlmatrixstuffing

---------

Co-authored-by: William Zijie Zhang <william@gridmatic.com>
Co-authored-by: William Zijie Zhang <william@gridmatic.com>
* adding many tests, new smoothcanon for min, and improvements to ipopt_nlpif

* fixing last two tests

* add another example, qcp

* adding example for acopf

* add control of a car example done

---------

Co-authored-by: William Zijie Zhang <william@gridmatic.com>
* update solution statuses thanks to odow

* removes unusued solver information

---------

Co-authored-by: William Zijie Zhang <william@gridmatic.com>
* getting rocket landing example to work

* add changes to the jacobian computation

---------

Co-authored-by: William Zijie Zhang <william@gridmatic.com>
* adding many more example of non-convex functions

* making lots of progress on understanding good canonicalizations

---------

Co-authored-by: William Zijie Zhang <william@gridmatic.com>
Co-authored-by: William Zijie Zhang <william@gridmatic.com>
@github-actions
Copy link
Copy Markdown

github-actions bot commented Feb 27, 2026

Benchmarks that have improved:

   before           after         ratio
 [69b1ecdf]       [b2f3087b]
  •     1.20±0s          1.02±0s     0.85  gini_portfolio.Cajas.time_compile_problem
    

Benchmarks that have stayed the same:

   before           after         ratio
 [69b1ecdf]       [b2f3087b]
      738±0ms          774±0ms     1.05  simple_QP_benchmarks.LeastSquares.time_compile_problem
     38.5±0ms         39.4±0ms     1.02  matrix_stuffing.SmallMatrixStuffing.time_compile_problem
      5.02±0s          5.09±0s     1.01  optimal_advertising.OptimalAdvertising.time_compile_problem
      1.58±0s          1.60±0s     1.01  tv_inpainting.TvInpainting.time_compile_problem
     15.1±0ms         15.2±0ms     1.01  simple_QP_benchmarks.ParametrizedQPBenchmark.time_compile_problem
      670±0ms          673±0ms     1.00  matrix_stuffing.ConeMatrixStuffingBench.time_compile_problem
      879±0ms          882±0ms     1.00  simple_LP_benchmarks.SimpleScalarParametrizedLPBenchmark.time_compile_problem
      236±0ms          237±0ms     1.00  simple_QP_benchmarks.SimpleQPBenchmark.time_compile_problem
      20.5±0s          20.5±0s     1.00  sdp_segfault_1132_benchmark.SDPSegfault1132Benchmark.time_compile_problem
      310±0ms          310±0ms     1.00  gini_portfolio.Yitzhaki.time_compile_problem
      10.1±0s          10.1±0s     1.00  simple_LP_benchmarks.SimpleLPBenchmark.time_compile_problem
      3.91±0s          3.91±0s     1.00  huber_regression.HuberRegression.time_compile_problem
     14.4±0ms         14.4±0ms     1.00  simple_LP_benchmarks.SimpleFullyParametrizedLPBenchmark.time_compile_problem
      133±0ms          133±0ms     1.00  high_dim_convex_plasticity.ConvexPlasticity.time_compile_problem
      1.79±0s          1.78±0s     0.99  simple_QP_benchmarks.UnconstrainedQP.time_compile_problem
      12.2±0s          12.1±0s     0.99  finance.CVaRBenchmark.time_compile_problem
      282±0ms          281±0ms     0.99  matrix_stuffing.ParamSmallMatrixStuffing.time_compile_problem
      1.02±0s          1.01±0s     0.99  finance.FactorCovarianceModel.time_compile_problem
      1.41±0s          1.40±0s     0.99  matrix_stuffing.ParamConeMatrixStuffing.time_compile_problem
      229±0ms          226±0ms     0.99  gini_portfolio.Murray.time_compile_problem
      2.80±0s          2.75±0s     0.98  quantum_hilbert_matrix.QuantumHilbertMatrix.time_compile_problem
      4.56±0s          4.47±0s     0.98  svm_l1_regularization.SVMWithL1Regularization.time_compile_problem
      498±0ms          487±0ms     0.98  semidefinite_programming.SemidefiniteProgramming.time_compile_problem
      288±0ms          274±0ms     0.95  slow_pruning_1668_benchmark.SlowPruningBenchmark.time_compile_problem

dance858 and others added 7 commits March 3, 2026 06:57
* uses new coo handling

* added comment

* fix pre-commit

* fix error message if sample bounds are not set

* docs for DNLP

* remove whitespace

* split table up into two
* uses new coo handling

* added comment

* fix pre-commit

* fix error message if sample bounds are not set

* docs for DNLP

* remove whitespace

* split table up into two

* cache C problem in best of
- Remove `submodules: recursive` from CI workflows (build, test_nlp_solvers, test_backends)
- Revert `_grad` in affine_atom.py to upstream version
- Remove NaN-allowance block in leaf.py added for NLP structural jacobian
- Update is_dnlp() docstrings to use linearizable terminology
- Add normalize_shape() helper in converters.py, replacing 7 inline occurrences
- Remove unnecessary comment in nlp_solver.py
- Export UNO in cvxpy/__init__.py
- Revert README.md to upstream CVXPY version

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
#168)

* remove unnecessary point in dom + clean up C_problem + clean up oracle

* fix william's comments
…nology (#170)

- Revert whitespace-only changes in huber, abs, ceil, affine_atom, binary_operators
- Revert cosmetic rename and _jacobian_operator addition in index.py
- Revert isnan check in leaf.py back to original error handling
- Restore removed blank line in quad_form.py
- Update DNLP constraint docstrings to use "linearizable convex/concave" terminology

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
@Transurgeon
Copy link
Copy Markdown
Collaborator Author

Transurgeon commented Mar 3, 2026

@PTNobel @SteveDiamond @rileyjmurray could you have another look at this PR please?
The summary of changes are below:

  • we added the sparsediffpy package as a dependency to compute derivatives.
  • documentation for the NLP solver and dnlp2smooth reductions, sample_bounds attribute for variables and many more. Thanks @rileyjmurray for the remainder on this end.
  • one documentation page in the user guide, which follows the structure of the other disciplines.
  • we moved the derivative checker to the tests.. it shouldn't be used by users.
  • a lot of cleanups to the solving chain and other small things. Thanks again to @PTNobel for pointing them out.
  • caching the oracles class for multiple solves in best_of. Thanks @SteveDiamond for the suggestion/reminder.

@dance858 and I also did a thorough scan of the changes and we are quite happy about the state of the PR/fork.
Apart from test changes, and new canonicalization methods, there are very little changes to the existing CVXPY infrastructure.

One big question is moving the non-convex atoms to a new namespace. Would it be fine to do that in another PR? It would be nice if we could discuss this as well more. I agree that if we create many new atoms it would be good to put them under a new namespace, but maybe we could just keep the trigonometric ones for now?

The code is certainly not perfect, and we will definitely keep making many improvements over time. But I think we were able to integrate most of the big necessary changes for the project. It is also quite tedious to keep the fork synced (especially with all the recent PRs we have been doing). So to summarize, I think it is ready to be merged into CVXPY master!

@SteveDiamond
Copy link
Copy Markdown
Collaborator

Thanks for all your work on this PR! I agree we should focus on the code as is and defer further design decisions like the nlp namespace. There will be many rounds of revision of all new features in 1.9 before we release so we should take an iterative development approach.

- Remove unused `self.broadcast_type` attribute from `broadcast_to.__init__`
  (was previously used for derivative computation, now handled elsewhere)
- Revert CLAUDE.md to upstream master version (DNLP-specific changes were
  broader than intended for the PR)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Collaborator

@PTNobel PTNobel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GitHub is breaking on me so I'm only halfway done.

I'm really confused why we're not using the bounds propagation infrastructure; I think it's perfect for this

from cvxpy.expressions.variable import Variable


def sinh_canon(expr, args):
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They seem to still be duplicated

Copy link
Copy Markdown
Collaborator

@PTNobel PTNobel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All tests seem to be missing copyright headers

verbose=verbose, use_hessian=use_hessian)

nlp = cyipopt.Problem(
n=len(data["x0"]),
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this at the same indentation as the nlp =

elif isinstance(constraint, NonPos):
lower.extend([0.0] * constraint.size)
upper.extend([np.inf] * constraint.size)
new_constr.append(nonpos2nonneg(constraint))
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's fine with me for now then; I think this could go in ExactCone2Cone in the future

@PTNobel
Copy link
Copy Markdown
Collaborator

PTNobel commented Mar 4, 2026

Regarding your comments, I started my review before I saw it.

I don't think this PR is ready to merge. Notably, many files are messing copyright headers. I'm pretty sure the domain property of tan always errors when accessed.

Also, I think some edits made in response to my previous review never made it onto this branch possibly? There's a handful of comments you replied to saying something was done that wasn't.

I think for all of my code quality changes (of which there are 25 small changes or so), as long as they're listed on an open issue somewhere (could be here with a check list; could be on cvxgrp/DNLP) I'll be satisfied. I'm worried if they're only on this PR it'll be very hard to track them after merge.

Regarding my bigger concerns, (1. using the get_bounds infrastructure to narrow bounds, 2. deduplicating canon functions and having consistent implementation for the different smooth canons) I'm happy to just have an issue filed for each and for them to be addressed later.

@dance858
Copy link
Copy Markdown
Contributor

dance858 commented Mar 4, 2026

All tests seem to be missing copyright headers

Done

Co-authored-by: Parth Nobel <parthnobel@berkeley.edu>
@dance858
Copy link
Copy Markdown
Contributor

dance858 commented Mar 4, 2026

I fixed most of the comments but tagged @Transurgeon in a few that he'll fix later today. Then we'll update the PR. I also implemented a common canonicalizer for smooth atoms with full domain. Great suggestion. Many thanks for the review. Please check if my answers above make sense.

* Centralize sparsediffpy import into _bindings.py

Move the duplicated try/except import of sparsediffpy from c_problem.py
and converters.py into a single _bindings.py module.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* cleanup fix

* fix import issue

* fix import for c_problem as well

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
@PTNobel
Copy link
Copy Markdown
Collaborator

PTNobel commented Mar 4, 2026

Thanks Daniel! I looked at the PR in the other repo and it looks great. I think there's maybe 3 or 4 things to file issues about, merge your PR there, and port all the changes from master to cvxpy-merge on the other repo and then we'll be all set!

Really excited to get this all merged. It is super exciting.

@Transurgeon
Copy link
Copy Markdown
Collaborator Author

Transurgeon commented Mar 5, 2026

Thanks Daniel! I looked at the PR in the other repo and it looks great. I think there's maybe 3 or 4 things to file issues about, merge your PR there, and port all the changes from master to cvxpy-merge on the other repo and then we'll be all set!

Really excited to get this all merged. It is super exciting.

Thanks Parth! I'll go ahead and update this branch then. I'll then move over the issues to cvxpy master (there must be a way to do this somehow?). Then you guys could have a final look.. pinging @SteveDiamond and @rileyjmurray for approval as well.

dance858 and others added 2 commits March 4, 2026 22:52
* many fixes to PR feedback

* domain tan non-strict inequalities

* div canon raise warning nonneg + document it

* common canonicalizers
@Transurgeon
Copy link
Copy Markdown
Collaborator Author

@PTNobel sorry for the ping.. but I just moved all issues from DNLP to cvxpy (with a new tag for them).
Out of the things you mention above, it seems like only the get_bounds infrastructure change is missing?
I think @dance858 has worked on deduplicating the common canon files.
It would be nice if you could have a final look and approve this if it looks good!

@PTNobel
Copy link
Copy Markdown
Collaborator

PTNobel commented Mar 7, 2026

I think get_bounds is the main thing missing. I don't see an issue for it yet?

@Transurgeon
Copy link
Copy Markdown
Collaborator Author

I think get_bounds is the main thing missing. I don't see an issue for it yet?

Just opened one now.. feel free to add more info if needed.

@PTNobel PTNobel merged commit 7a1a620 into cvxpy:master Mar 7, 2026
47 of 49 checks passed
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.

7 participants