cvxrust 0.1.0

A Rust implementation of Disciplined Convex Programming
Documentation
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Build Commands

```bash
cargo build              # Build the library
cargo test               # Run all tests (159 tests)
cargo test test_name     # Run a specific test
cargo test -- --nocapture  # Run tests with stdout visible
cargo clippy             # Run linter
cargo doc --open         # Generate and view documentation
```

## Running Examples

```bash
cargo run --example portfolio        # Portfolio optimization with dual variables
cargo run --example least_squares    # Least squares regression
cargo run --example quadratic_program # Basic QP
cargo run --example basic_lp         # Linear programming
cargo run --example lasso            # L1-regularized regression
```

## Architecture

cvxrust is a Disciplined Convex Programming (DCP) library that transforms optimization problems into standard form and solves them via Clarabel.

### Core Data Flow

1. **Expression Building**: Users construct `Expr` enum trees using atoms (`sum`, `norm2`, etc.) and operator overloads
2. **DCP Verification**: `Curvature` (convex/concave/affine) and `Sign` (nonneg/nonpos) are computed via composition rules
3. **Canonicalization**: `Expr``LinExpr`/`QuadExpr` + `ConeConstraint` (Zero, NonNeg, SOC, ExpCone, PowerCone)
4. **Matrix Stuffing**: Build sparse matrices P, q, A, b for Clarabel's standard form
5. **Solve**: Clarabel returns solution, mapped back to user variables

### Module Structure

```
src/
├── lib.rs              # Main library entry point and prelude
├── expr/               # Expression types and constructors
│   ├── expression.rs   # Core Expr enum with all expression variants
│   ├── variable.rs     # Variable creation with builder pattern
│   ├── constant.rs     # Constant creation helpers
│   └── shape.rs        # Shape/dimension tracking
├── atoms/              # Building blocks for expressions
│   ├── affine.rs       # Affine operations and operator overloading
│   └── nonlinear.rs    # Convex/concave atoms (norms, exp, log, power, etc.)
├── dcp/                # Disciplined Convex Programming analysis
│   ├── curvature.rs    # Curvature tracking (convex/concave/affine/constant)
│   └── sign.rs         # Sign tracking (nonnegative/nonpositive)
├── constraints/        # Constraint types
│   └── constraint.rs   # Constraint definitions, DCP verification, constraint! macro
├── canon/              # Canonicalization (expression → standard form)
│   ├── canonicalizer.rs # Main canonicalization logic
│   └── lin_expr.rs     # Linear and quadratic expression forms
├── solver/             # Solver interface
│   ├── clarabel.rs     # Clarabel solver wrapper, Solution type
│   └── stuffing.rs     # Matrix stuffing for standard form
├── problem.rs          # Problem definition and builder
├── sparse.rs           # Sparse matrix utilities
└── error.rs            # Error types (CvxError)
```

### Key Types

- `Expr` (expr/expression.rs): Enum with all expression variants (Variable, Constant, Add, Norm2, Exp, Log, etc.)
- `LinExpr` (canon/lin_expr.rs): Affine form `sum_i(A_i * x_i) + b` with sparse coefficient matrices
- `QuadExpr` (canon/lin_expr.rs): Quadratic form `(1/2) x' P x + q' x + r` for native QP support
- `ConeConstraint` (canon/canonicalizer.rs): Zero (equality), NonNeg (inequality), SOC, ExpCone, PowerCone
- `Solution` (solver/clarabel.rs): Contains primal values, dual values, status, and objective value

### Constraint API

Use the `constraint!` macro for natural syntax:
```rust
constraint!(x >= 0.0)        // x >= 0
constraint!(x <= 10.0)       // x <= 10
constraint!((sum(&x)) == 1.0) // sum(x) = 1
```

Or method syntax:
```rust
x.ge(0.0)    // x >= 0
x.le(10.0)   // x <= 10
sum(&x).eq(1.0)
```

### DCP Composition Rules

Curvature propagation in `dcp/curvature.rs`:
- `convex + convex = convex`, `concave + concave = concave`
- `nonneg * convex = convex`, `nonpos * convex = concave`
- `convex(affine) = convex`, `convex(concave) = unknown`

Sign propagation in `dcp/sign.rs`:
- `nonneg + nonneg = nonneg`, `nonpos + nonpos = nonpos`
- `nonneg * nonneg = nonneg`, `nonneg * nonpos = nonpos`

### Implemented Atoms

**Affine** (preserve curvature): `sum`, `sum_axis`, `cumsum`, `reshape`, `flatten`, `vstack`, `hstack`, `transpose`, `diag`, `matmul`, `dot`, `trace`, `index`, `slice`, `+`, `-`, `*scalar`, `/scalar`

**Convex**: `norm1`, `norm2`, `norm_inf`, `norm`, `abs`, `pos`, `neg_part`, `maximum`, `max2`, `quad_form` (PSD), `sum_squares`, `quad_over_lin`, `exp`, `power` (p >= 1 or p < 0)

**Concave**: `minimum`, `min2`, `quad_form` (NSD), `log`, `entropy`, `sqrt`, `power` (0 < p < 1)

### Clarabel Sign Convention

In `solver/stuffing.rs`, constraints use Clarabel's form `Ax + s = b, s ∈ K`:
- **Zero cone** (equality): `Ax = b` where `b = -constant`
- **NonNeg cone**: `expr >= 0` becomes `-Ax <= constant` (negate coefficients)
- **SOC**: `||x||_2 <= t` becomes `[t; x] ∈ K_soc` (also negated)
- **ExpCone**: `(x, y, z) ∈ K_exp` means `y * exp(x/y) <= z`
- **PowerCone**: `(x, y, z) ∈ K_pow(α)` means `x^α * y^(1-α) >= |z|`

## Solver Integration

Only Clarabel is supported. The solver expects:
- P: Quadratic cost matrix (upper triangular, symmetric)
- q: Linear cost vector
- A: Constraint matrix
- b: Constraint vector
- Cones specified as `SupportedConeT` variants

## Common Patterns

### Detecting constant expressions
Use `expr.variables().is_empty()` to check if an expression is constant (has no variables), rather than `expr.constant_value()` which only matches the `Constant` variant directly.

### Accessing dual variables
```rust
let solution = problem.solve()?;
let duals = solution.duals();           // All duals as slice
let dual_i = solution.constraint_dual(i); // Dual for constraint i
```

### Error handling
- `CvxError::NotDcp` - Problem violates DCP rules
- `CvxError::SolverError` - Clarabel failed (infeasible, unbounded, etc.)
- `CvxError::InvalidProblem` - Malformed problem (dimension mismatch, etc.)