Skip to content

Fix stale docstring examples; make power curvature follow the rational approximation - #3527

Open
tschm wants to merge 2 commits into
cvxpy:masterfrom
tschm:fix-stale-docstring-examples
Open

tschm wants to merge 2 commits into
cvxpy:masterfrom
tschm:fix-stale-docstring-examples

Conversation

@tschm

@tschm tschm commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

Description

Five docstrings shipped >>> examples that no longer ran. Fixing the last of them
surfaced a real behaviour bug in Power, which is also fixed here.

Docstring fixes

  • cvxpy/utilities/einsum_utilities.py — the vendored NumPy helpers were renamed to
    drop their leading underscore, but the examples still called _find_contraction,
    _optimal_path, _greedy_path and _parse_einsum_input, so every one raised
    NameError. They were also nondeterministic by construction: set repr ordering and the
    generated einsum index symbols both vary between processes, and the # may vary marker
    is a NumPy refguide-check convention that plain doctest does not honour. They now
    assert sorted and structural output instead.
  • cvxpy/constraints/constraint.pyConstraint.set_label had no
    import cvxpy as cp, so all four examples raised NameError. The variable is now named
    so its repr does not depend on the global variable counter.
  • cvxpy/atoms/geo_mean.py — zero-weight terms are dropped from w, so the expected
    output no longer lists the 0.
  • cvxpy/reductions/dgp2dcp/dgp2dcp.py — variables were defined as x1..x3 but used
    as x_1..x_3; cvxpy.reductions was referenced with only cp imported; and three
    calls had no expected output.

Behaviour fix in Power

The Power docstring states that domain, sign, monotonicity and curvature are determined
by the rational approximation p_used, not the input exponent. is_incr/is_decr
already did this, but is_atom_convex, is_atom_concave and sign_from_args still read
self.p.value. So two exponents that reduce to the same p_used were classified
differently:

                  before                after
power(x, 1)       AFFINE, UNKNOWN       AFFINE, UNKNOWN
power(x, 1.0001)  CONVEX, NONNEGATIVE   AFFINE, UNKNOWN
power(x, 1.001)   CONVEX, NONNEGATIVE   CONVEX, NONNEGATIVE

power(x, 1.0001) has p_used == 1 — mathematically identical to power(x, 1) after
approximation — yet was treated as convex with an implicit x >= 0. Those three methods
now consult p_used, following the pattern already established by is_incr/is_decr.
Parametrized exponents are unaffected: p_used is None keeps the previous fallback.

Note this is a semantic change to DCP curvature analysis and deserves a careful look,
even though the suite is green.

Issue link (if applicable): n/a

Type of change

  • New feature (backwards compatible)
  • New feature (breaking API changes)
  • Bug fix
  • Other (Documentation, CI, ...)

Contribution checklist

  • Add our license to new files.
  • Check that your code adheres to our coding style.
  • Write unittests.
  • Run the unittests and check that they're passing.
  • Run the benchmarks to make sure your change doesn't introduce a regression.

Checklist notes, for transparency:

  • Add our license to new files — no new files are added, so this does not apply.
  • Write unitteststest_power_curvature_follows_rational_approximation in
    cvxpy/tests/test_atoms.py covers the curvature change. It was verified to fail against
    the previous behaviour (AssertionError: 'CONVEX' != 'AFFINE') and pass after. The
    docstring fixes are covered by the restored doctests themselves.
  • Run the unittests — 2670 passed, 879 skipped, 0 failed locally. The 879 skips are
    solver-gated
    : only CLARABEL, SCS, SCIPY, HIGHS and OSQP were installed, so the
    commercial-solver paths through power were not exercised here and rely on CI.
  • Run the benchmarksnot run. Left unchecked deliberately.

Also verified: pyright reports 0 errors, and all pre-commit hooks pass.

🤖 Generated with Claude Code

tschm and others added 2 commits September 11, 2026 14:36
Five docstrings carried `>>>` examples that no longer ran:

- `einsum_utilities`: the vendored NumPy helpers were renamed to drop
  their leading underscore, but the examples still called
  `_find_contraction`, `_optimal_path`, `_greedy_path` and
  `_parse_einsum_input`. The examples were also nondeterministic --
  set repr ordering and generated einsum index symbols both vary
  between processes -- so they now assert sorted/structural output.
- `Constraint.set_label`: no `import cvxpy as cp`, so every example
  raised NameError; the variable is now named so its repr is stable.
- `geo_mean`: zero-weight terms are dropped from `w`, so the expected
  output no longer lists the `0`.
- `Dgp2Dcp`: variables were defined as `x1..x3` but used as
  `x_1..x_3`, `cvxpy.reductions` was referenced with only `cp`
  imported, and three calls had no expected output.

`Power` needed more than a doc update. Its note states that domain,
sign, monotonicity and curvature follow the rational approximation
`p_used` rather than the input exponent. `is_incr`/`is_decr` already
did, but `is_atom_convex`, `is_atom_concave` and `sign_from_args`
still read `self.p.value`, so `power(x, 1.0001)` and `power(x, 1)`
were classified differently despite both reducing to `p_used == 1`:

    power(x, 1)       AFFINE, UNKNOWN
    power(x, 1.0001)  CONVEX, NONNEGATIVE   <- before
    power(x, 1.0001)  AFFINE, UNKNOWN       <- after

Those three now consult `p_used`, matching the documented behaviour
and the existing monotonicity logic. Parametrized exponents are
unaffected (`p_used is None` keeps the previous fallback).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Asserts that an exponent approximating to exactly 1 (1.0001) is affine
like power(x, 1), that one which does not (1.001) stays convex and
nonnegative, and that a parametrized exponent is still unknown.

Verified to fail against the previous behaviour ('CONVEX' != 'AFFINE').

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

Benchmarks that have stayed the same:

Change Before [96edf96] After [139c64c] Ratio Benchmark (Parameter)
97.5±1ms 98.1±2ms 1.01 high_dim_convex_plasticity.ConvexPlasticity.time_compile_problem
1.97±0.07s 1.99±0.05s 1.01 matrix_stuffing.ParamSmallMatrixStuffing.time_compile_problem
6.54±0.01s 6.58±0.02s 1.01 simple_LP_benchmarks.SimpleLPBenchmark.time_compile_problem
1.17±0.01s 1.17±0.02s 1 finance.FactorCovarianceModel.time_compile_problem
1.50±0.01s 1.51±0.01s 1 gini_portfolio.Cajas.time_compile_problem
737±5ms 736±10ms 1 gini_portfolio.Murray.time_compile_problem
792±5ms 792±8ms 1 gini_portfolio.Yitzhaki.time_compile_problem
2.52±0s 2.52±0s 1 huber_regression.HuberRegression.time_compile_problem
1.85±0s 1.85±0s 1 matrix_stuffing.ConeMatrixStuffingBench.time_compile_problem
807±8ms 808±4ms 1 matrix_stuffing.ParamConeMatrixStuffing.time_compile_problem
1.27±0s 1.27±0s 1 matrix_stuffing.SmallMatrixStuffing.time_compile_problem
2.02±0.01s 2.03±0.01s 1 optimal_advertising.OptimalAdvertising.time_compile_problem
1.22±0.01s 1.22±0s 1 quantum_hilbert_matrix.QuantumHilbertMatrix.time_compile_problem
13.6±0s 13.6±0.02s 1 sdp_segfault_1132_benchmark.SDPSegfault1132Benchmark.time_compile_problem
854±6ms 857±10ms 1 semidefinite_programming.SemidefiniteProgramming.time_compile_problem
443±1ms 444±1ms 1 simple_LP_benchmarks.SimpleFullyParametrizedLPBenchmark.time_compile_problem
1.28±0.01s 1.28±0s 1 simple_QP_benchmarks.LeastSquares.time_compile_problem
1.67±0s 1.67±0.01s 1 simple_QP_benchmarks.ParametrizedQPBenchmark.time_compile_problem
2.72±0.02s 2.72±0.01s 1 simple_QP_benchmarks.SimpleQPBenchmark.time_compile_problem
2.17±0s 2.17±0.02s 1 simple_QP_benchmarks.UnconstrainedQP.time_compile_problem
2.80±0.01s 2.80±0.01s 1 svm_l1_regularization.SVMWithL1Regularization.time_compile_problem
806±0.8ms 808±4ms 1 tv_inpainting.TvInpainting.time_compile_problem
1.46±0.01s 1.45±0s 0.99 slow_pruning_1668_benchmark.SlowPruningBenchmark.time_compile_problem
8.45±0.03s 8.29±0.01s 0.98 finance.CVaRBenchmark.time_compile_problem
1.10±0.03s 1.08±0.03s 0.98 simple_LP_benchmarks.SimpleScalarParametrizedLPBenchmark.time_compile_problem

This branch has not been deployed

No deployments
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.

1 participant