Skip to content

Commit aa99181

Browse files
author
Yuma Ichikawa
committed
docs(sa): add SA section to algorithm.md and head-to-head benchmark notebook
- `docs/explanation/algorithm.md`: new "SA — `qqa.simulated_annealing`" section explaining the QUBO fast path (Glauber-like parallel ΔE), the generic sequential fallback, the geometric β schedule, and when to reach for SA vs QQA / CRA / CPRA. Top-of-page comparison table is expanded from three to four solver families. - `notebooks/benchmark_sa_vs_qqa_vs_pignn.ipynb`: new head-to-head benchmark on a single 200-node random 3-regular MIS instance, comparing SA, QQA, CRA-PI-GNN and CPRA at a controlled compute budget. Produces a summary table and convergence-curve plot. Made-with: Cursor
1 parent d80e3ed commit aa99181

2 files changed

Lines changed: 378 additions & 5 deletions

File tree

docs/explanation/algorithm.md

Lines changed: 56 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1-
# Algorithm — PQQA, CRA-PI-GNN, CPRA in one page
1+
# Algorithm — SA, PQQA, CRA-PI-GNN, CPRA in one page
22

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.
77

88
| Solver | Backend | Variable | Schedule | Diversity |
99
|---|---|---|---|---|
10+
| **SA** (baseline) | `qqa.simulated_annealing` | discrete `{0,1}` or `{−1,+1}` per chain | inverse-temperature `β` (geometric / linear) | independent parallel chains |
1011
| **PQQA** (default) | `qqa.anneal` | parallel batch of `B` raw tensors | linear `bg` over epochs | optional `div_param` cross-replica term |
1112
| **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) |
1213
| **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
8990
backbone of the reference is replaced with `torch_geometric` — see
9091
[`docs/explanation/architecture.md`](architecture.md) for why.
9192

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+
92140
## CPRA — `qqa.pignn.train_cpra_pi_gnn`
93141

94142
Reference paper: Y. Ichikawa & H. Iwashita, *"Continuous Parallel
@@ -126,6 +174,9 @@ matrix.
126174

127175
* **Default to PQQA.** It works on every problem in the catalogue, is
128176
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.
129180
* **Switch to CRA-PI-GNN** when you need the GNN's smoothness prior on
130181
large, sparse graph problems and you can afford a longer training
131182
run.

0 commit comments

Comments
 (0)