# DC3: A learning method for optimization with hard constraints

This repository is by 
[Priya L. Donti](https://www.priyadonti.com),
[David Rolnick](https://davidrolnick.com/),
and [J. Zico Kolter](https://zicokolter.com)
and contains the [PyTorch](https://pytorch.org) source code to
reproduce the experiments in our paper
"[DC3: A learning method for optimization with hard constraints](https://arxiv.org/abs/2104.12225)."

If you find this repository helpful in your publications,
please consider citing our paper.

```
@inproceedings{donti2021dc3,
  title={DC3: A learning method for optimization with hard constraints},
  author={Donti, Priya and Rolnick, David and Kolter, J Zico},
  booktitle={International Conference on Learning Representations},
  year={2021}
}
```

## Introduction

Large optimization problems with hard constraints arise in many settings, yet classical solvers are often prohibitively slow, motivating the use of deep networks as cheap "approximate solvers." Unfortunately, naive deep learning approaches typically cannot enforce the hard constraints of such problems, leading to infeasible solutions. In this work, we present Deep Constraint Completion and Correction (DC3), an algorithm to address this challenge. Specifically, this method enforces feasibility via a differentiable procedure, which implicitly completes partial solutions to satisfy equality constraints and unrolls gradient-based corrections to satisfy inequality constraints. We demonstrate the effectiveness of DC3 in both synthetic optimization tasks and the real-world setting of AC optimal power flow, where hard constraints encode the physics of the electrical grid. In both cases, DC3 achieves near-optimal objective values while preserving feasibility.

## Dependencies

+ Python 3.x
+ [PyTorch](https://pytorch.org) >= 1.8
+ numpy/scipy/pandas
+ [osqp](https://osqp.org/): *State-of-the-art QP solver*
+ [qpth](https://github.com/locuslab/qpth): *Differentiable QP solver for PyTorch*
+ [ipopt](https://coin-or.github.io/Ipopt/): *Interior point solver*
+ [pypower](https://pypi.org/project/PYPOWER/): *Power flow and optimal power flow solvers*
+ [argparse](https://docs.python.org/3/library/argparse.html): *Input argument parsing*
+ [pickle](https://docs.python.org/3/library/pickle.html): *Object serialization*
+ [hashlib](https://docs.python.org/3/library/hashlib.html): *Hash functions (used to generate folder names)*
+ [setproctitle](https://pypi.org/project/setproctitle/): *Set process titles*
+ [waitGPU](https://github.com/riceric22/waitGPU) (optional): *Intelligently set `CUDA_VISIBLE_DEVICES`*


## Instructions

### Dataset generation

Datasets for the experiments presented in our paper are available in the `datasets` folder. These datasets can be generated by running the Python script `make_dataset.py` within each subfolder (`simple`, `nonconvex`, and `acopf`) corresponding to the different problem types we test.

### Running experiments

Our method and baselines can be run using the following Python files:
+ `method.py`: Our method (DC3)
+ `baseline_nn.py`: Simple deep learning baseline (NN)
+ `baseline_eq_nn.py`: Supervised deep learning baseline with completion (Eq. NN)
+ `baseline_opt.py`: Traditional optimizers (Optimizer)

See each file for relevant flags to set the problem type and method parameters. Notably:
+ `--probType`: Problem setting to test (`simple`, `nonconvex`, or `acopf57`)
+ `--simpleVar`, `--simpleIneq`, `simpleEq`, `simpleEx`: If the problem setting is `simple`, the number of decision variables, inequalities, equalities, and datapoints, respectively.
+ `--nonconvexVar`, `--nonconvexIneq`, `nonconvexEq`, `nonconvexEx`: If the problem setting is `nonconvex`, the number of decision variables, inequalities, equalities, and datapoints, respectively.

#### Reproducing paper experiments

You can reproduce the experiments run in our paper (including baselines and ablations) via the bash script `run_expers.sh`. For instance, the following commands can be used to run these experiments, 8 jobs at a time:
```
bash run_expers.sh > commands
cat commands | xargs -n1 -P8 -I{} /bin/sh -c "{}"
```

The script `load_results.py` can be run to aggregate these results (both while experiments are running, and after they are done). In particular, this script outputs a summary of results across different replicates of the same experiment (`results_summary.dict`) and information on how many jobs of each type are running or done (`exper_status.dict`).

### Generating tables

Tables can be generated via the Jupyter notebook `ResultsViz.ipynb`. This notebook expects the dictionary `results_summary.dict` as input; the version of this dictionary generated while running the experiments in the paper is available in this repository.
