File size: 6,438 Bytes
9860743 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 | # FFOLayer — A Fully First-Order Layer for Differentiable Optimization
`FFOLayer` is a PyTorch-friendly library for **differentiable optimization layers** that computes hypergradients using **only first-order information**. It is designed as a practical, drop-in alternative to implicit-differentiation-based layers when memory or backward-time is the bottleneck.
---
## Installation
FFOLayer is available on pip:
```bash
pip install ffolayer
```
You may also need to install cvxtorch:
```bash
git clone https://github.com/cvxpy/cvxtorch.git
cd cvxtorch
pip install -e .
```
---
## Usage
FFOLayer follows the same workflow as differentiable layers like [CvxpyLayer](https://github.com/cvxpy/cvxpylayers/):
> define a [CVXPY](https://github.com/cvxpy/cvxpy) problem → wrap it as a layer → call it in PyTorch → backprop
### API
#### `FFOLayer(problem, parameters, variables, eps=..., **kwargs)`
**Arguments**
- `problem`: a `cvxpy.Problem` (must satisfy DPP when using CVXPY parameters).
- `parameters`: CVXPY `Parameter` (the inputs to the layer).
- `variables`: CVXPY `Variable` (the outputs returned by the layer).
- `alpha`: perturbation scale (\delta in Eq.4) used in the finite-difference hypergradient approximation. Bigger alpha leads to a smaller delta: less bias, more numerical noise.
- `eps`: solver tolerence for forward pass
- `backward_eps`: solver tolerence for backward pass
- `max_workers`: maximum number of worker threads/processes used to parallelize solver calls
#### Calling the layer
```python
(outputs,) = layer(*torch_parameters, solver_args={...})
```
**Arguments**
- `*torch_parameters`: PyTorch tensors matching `parameters` in shape (optionally batched).
- `solver_args` (optional): forwarded to `problem.solve(...)` inside CVXPY.
**Return value**
- A tuple of PyTorch tensors corresponding to `variables`.
### Solver-agnostic differentiation!!!
FFOLayer is **solver-agnostic**: it treats the solver as a **black box** and computes hypergradients by re-solving **perturbed** problems, instead of differentiating through solver internals.
This means you can use **any CVXPY solver** (e.g., GUROBI, MOSEK, ECOS, SCS) without requiring custom backward implementations.
```python
(solution,) = layer(*torch_parameters, solver_args={"solver": cp.GUROBI, "eps": 1e-5})
```
#### Example
```python
import torch
from ffolayer import FFOLayer
batch, n, m = 8, 2, 3
x = cp.Variable(n)
A = cp.Parameter((m, n))
b = cp.Parameter(m)
constraints = [x >= 0]
objective = cp.Minimize(0.5 * cp.pnorm(A @ x - b, p=1))
problem = cp.Problem(objective, constraints)
assert problem.is_dpp()
# layer = CvxpyLayer(problem, parameters=[A, b], variables=[x])
layer_ffo = FFOLayer(problem, parameters=[A, b], variables=[x], eps=1e-8)
A_tch = torch.randn(batch, m, n, requires_grad=True)
b_tch = torch.randn(batch, m, requires_grad=True)
# solve the problem
(solution,) = layer_ffo(A_tch, b_tch)
# compute the gradient of the sum of the solution with respect to A, b
solution.sum().backward()
```
---
## Code Structure
- `src/`: core layer implementations (FFOCP / FFOQP variants)
- `synthetic_task/`: synthetic decision-focused learning (QP) benchmark
- `sudoku/`: Sudoku as an optimization layer benchmark
- `baselines/`: reference baselines used in experiments
- `tests/`: basic checks / utilities
- `plot_results_*.ipynb`: notebooks for plotting paper figures
### Variants in this repo
We provide two main variants (same core idea, different specialization):
- **FFOCP:** applies to general convex programs.
- **FFOQP:** specializes to QP layers, exploiting quadratic structure for efficiency.
---
## Reproducing the paper experiments
### 1) Synthetic Qradratic Program (QP)
**Key files**
- `synthetic_task/main_synthetic.py`: entrypoint for all methods
- `models.py`: all models' definitions and settings
To run the code, if your cluster supports SLURM, please use:
```bash
sh scripts/loop_synthetic_per_seed.sh
```
If not, please use:
```bash
python synthetic_task/main_synthetic.py --method ffocp_eq --ydim 800 --epoch 1 --batch_size 8
python synthetic_task/main_synthetic.py --method ffoqp_eq --ydim 800 --epoch 1 --batch_size 8
python synthetic_task/main_synthetic.py --method lpgd --ydim 800 --epoch 1 --batch_size 8
python synthetic_task/main_synthetic.py --method cvxpylayer --ydim 800 --epoch 1 --batch_size 8
python synthetic_task/main_synthetic.py --method qpth --ydim 800 --epoch 1 --batch_size 8
python synthetic_task/main_synthetic.py --method bpqp --ydim 800 --epoch 1 --batch_size 8
python synthetic_task/main_synthetic.py --method dqp --ydim 800 --epoch 1 --batch_size 8
```
To plot the results, please run `plot_results_synthetic.ipynb`.
---
### 2) Sudoku task
**Key files**
- `sudoku/main_sudoku.py`: entrypoint for all methods
- `models_sudoku.py`: all models' definitions and settings
To run the code, if your cluster supports SLURM, please use:
```bash
sh scripts/loop_sudoku_per_seed.sh
```
If not, please use:
```bash
python sudoku/main_sudoku.py --method ffocp_eq --n 3 --epoch 1 --batch_size 8
python sudoku/main_sudoku.py --method ffoqp_eq --n 3 --epoch 1 --batch_size 8
python sudoku/main_sudoku.py --method lpgd --n 3 --epoch 1 --batch_size 8
python sudoku/main_sudoku.py --method cvxpylayer --n 3 --epoch 1 --batch_size 8
python sudoku/main_sudoku.py --method qpth --n 3 --epoch 1 --batch_size 8
python sudoku/main_sudoku.py --method bpqp --n 3 --epoch 1 --batch_size 8
python sudoku/main_sudoku.py --method dqp --n 3 --epoch 1 --batch_size 8
```
To plot the results, please use
```bash
python sudoku/plot_results.py
```
---
### 3) `Synthetic Second-order Cone Progrem (SOCP))`
**Key files**
- `synthetic_task/main_synthetic_general.py`: entrypoint for all methods
- `models.py`: all models' definitions and settings
To run the code, if your cluster supports SLURM, please use:
```bash
sh scripts/loop_synthetic_general_per_seed.sh
```
If not, please use:
```bash
python synthetic_task/main_synthetic_general.py --method ffocp_eq --ydim 800 --epoch 1 --batch_size 8
python synthetic_task/main_synthetic_general.py --method lpgd --ydim 800 --epoch 1 --batch_size 8
python synthetic_task/main_synthetic_general.py --method cvxpylayer --ydim 800 --epoch 1 --batch_size 8
python synthetic_task/main_synthetic_general.py --method bpqp --ydim 800 --epoch 1 --batch_size 8
```
To plot the results, please run `plot_results_synthetic.ipynb`.
---
|