|
1 | | -# Algorithm — PQQA, CRA-PI-GNN, CPRA in one page |
| 1 | +# Algorithm — SA, PQQA, CRA-PI-GNN, CPRA in one page |
2 | 2 |
|
3 | | -QQA4CO ships **three solver families** that all share the same problem |
4 | | -catalogue and the same `AnnealResult` interface. They differ in the |
5 | | -representation of the relaxed variable and in how the relaxation |
6 | | -penalty is annealed. |
| 3 | +QQA4CO ships **four solver families** that all share the same problem |
| 4 | +catalogue and a common `AnnealResult` / `SAResult` interface. They differ |
| 5 | +in the representation of the candidate variable and in how the relaxation |
| 6 | +(or temperature) is annealed. |
7 | 7 |
|
8 | 8 | | Solver | Backend | Variable | Schedule | Diversity | |
9 | 9 | |---|---|---|---|---| |
| 10 | +| **SA** (baseline) | `qqa.simulated_annealing` | discrete `{0,1}` or `{−1,+1}` per chain | inverse-temperature `β` (geometric / linear) | independent parallel chains | |
10 | 11 | | **PQQA** (default) | `qqa.anneal` | parallel batch of `B` raw tensors | linear `bg` over epochs | optional `div_param` cross-replica term | |
11 | 12 | | **CRA-PI-GNN** | `qqa.pignn.train_cra_pi_gnn` | output of a 2-layer GCN over the problem graph | linear `γ` from `init_reg_param` (≈ −20) to ≥ 0 | none (single replica) | |
12 | 13 | | **CPRA** | `qqa.pignn.train_cpra_pi_gnn` | `R` GCN heads sharing one backbone | same as CRA-PI-GNN, optionally per-head | optional `vari_param` term, or per-head `replica_problems` | |
@@ -89,6 +90,53 @@ Source: `src/qqa/pignn/trainer.py:78`–`train_cra_pi_gnn`. The DGL |
89 | 90 | backbone of the reference is replaced with `torch_geometric` — see |
90 | 91 | [`docs/explanation/architecture.md`](architecture.md) for why. |
91 | 92 |
|
| 93 | +## SA — `qqa.simulated_annealing` |
| 94 | + |
| 95 | +The baseline. Implemented in `src/qqa/sa.py`; no learning, no |
| 96 | +relaxation — just classical Simulated Annealing parallelised across |
| 97 | +chains on GPU. |
| 98 | + |
| 99 | +For QUBO problems (every problem with a `Q_mat` attribute) we use a |
| 100 | +**Glauber-like parallel update**: at each sweep, the change in energy |
| 101 | +\(\Delta E_i\) for flipping bit \(i\) is computed in closed form, |
| 102 | + |
| 103 | +\[ |
| 104 | +\\Delta E_i \\;=\\; (1 - 2 x_i)\\,\\bigl(2(Q\\,x)_i - 2 x_i Q_{ii} + Q_{ii}\\bigr), |
| 105 | +\\] |
| 106 | + |
| 107 | +so a single matmul gives \(\\Delta E\\) for every bit. Each bit is then |
| 108 | +flipped independently with probability |
| 109 | +\(\\min\\bigl(1, e^{-\\beta\\,\\Delta E_i}\\bigr)\). This is *not* a strictly |
| 110 | +correct single-spin Metropolis chain — proposals are not conditional on |
| 111 | +neighbours updated earlier in the same sweep — but in the high-`β` |
| 112 | +limit it converges to the same Boltzmann distribution and matches the |
| 113 | +"parallel-tempered classical SA" baseline used throughout the QQA / CPRA |
| 114 | +literature. |
| 115 | + |
| 116 | +For non-QUBO problems (`Knapsack`, `MaxSAT3`, `BinaryPerceptron`, …) we |
| 117 | +fall back to **strict single-spin sequential Metropolis**: one |
| 118 | +`problem.loss_fn` call per bit per sweep. Correct, but \(N\\times\\) more |
| 119 | +expensive than the QUBO fast path. |
| 120 | + |
| 121 | +The β schedule is geometric by default (recommended for SA), |
| 122 | +\(\\beta_t = \\beta_0 (\\beta_T/\\beta_0)^{t/T}\\). Use the `beta_schedule="linear"` |
| 123 | +flag to disable. |
| 124 | + |
| 125 | +`sol_size` is the number of independent chains, run in parallel as the |
| 126 | +batch dimension on GPU. There is no diversity term — the only "diversity" |
| 127 | +is the random initialisation per chain, exactly as in textbook SA. |
| 128 | + |
| 129 | +When SA is the right choice: |
| 130 | + |
| 131 | +* **As a baseline.** If your fancy method (QQA / CRA / CPRA / your own) |
| 132 | + doesn't beat SA on a fixed compute budget, the relaxation isn't |
| 133 | + helping for that problem. |
| 134 | +* **Tiny instances** (`N ≲ 50`) where SA converges in a few hundred |
| 135 | + sweeps and the GCN training overhead of CRA-PI-GNN is wasted. |
| 136 | +* **Sanity check after problem changes.** SA's flat code path makes it |
| 137 | + much easier to debug a new `loss_fn` than the relaxed-then-annealed |
| 138 | + path of QQA. |
| 139 | + |
92 | 140 | ## CPRA — `qqa.pignn.train_cpra_pi_gnn` |
93 | 141 |
|
94 | 142 | Reference paper: Y. Ichikawa & H. Iwashita, *"Continuous Parallel |
@@ -126,6 +174,9 @@ matrix. |
126 | 174 |
|
127 | 175 | * **Default to PQQA.** It works on every problem in the catalogue, is |
128 | 176 | the cheapest by far, and is the most thoroughly tested. |
| 177 | +* **Reach for SA first** when you want a *baseline* — if QQA / CRA / |
| 178 | + CPRA can't beat SA on the same budget, the extra machinery isn't |
| 179 | + paying its way for your problem. |
129 | 180 | * **Switch to CRA-PI-GNN** when you need the GNN's smoothness prior on |
130 | 181 | large, sparse graph problems and you can afford a longer training |
131 | 182 | run. |
|
0 commit comments