HPC-Quantize / hpc_amplitude.h
CompressedGemma's picture
It's only calibrated for Gemma, atm.
07b428c verified
Raw
History Blame Contribute Delete
17.3 kB
/*
* hpc_amplitude.h β€” On-Demand State Vector
*
* The state vector has D^N entries. We never materialize it.
* Instead, we compute exactly what's needed, when it's needed.
*
* Three modes of access:
*
* 1. POINT QUERY: ψ(i₁,...,iβ‚™) β†’ O(N+E) β€” one amplitude
* 2. SPARSE RECON: All |ψ| > threshold β†’ O(?) β€” importance sampling
* 3. EXPECTATION: ⟨ψ|O|ψ⟩ β†’ O(samplesΓ—(N+E)) β€” Monte Carlo
*
* The Devil computes only what you ask for. Nothing more.
* The rest of the state vector does not exist until observed.
*/
#ifndef HPC_AMPLITUDE_H
#define HPC_AMPLITUDE_H
#include "hpc_graph.h"
#include "hpc_contract.h"
#include <math.h>
#include <stdlib.h>
#include <string.h>
/* ═══════════════════════════════════════════════════════════════════════
* SPARSE STATE VECTOR ENTRY
* ═══════════════════════════════════════════════════════════════════════ */
typedef struct {
uint32_t *indices; /* Site indices: [n_sites] */
double re, im; /* Amplitude value */
double prob; /* |amplitude|Β² */
} HPCSparseEntry;
typedef struct {
HPCSparseEntry *entries;
uint64_t count;
uint64_t capacity;
uint64_t n_sites; /* For index array sizing */
double total_prob; /* Sum of captured probability */
double threshold; /* Minimum |ψ|² captured */
} HPCSparseVector;
/* ═══════════════════════════════════════════════════════════════════════
* SPARSE VECTOR LIFECYCLE
* ═══════════════════════════════════════════════════════════════════════ */
static inline HPCSparseVector *hpc_sv_create(uint64_t n_sites,
uint64_t initial_cap)
{
HPCSparseVector *sv = (HPCSparseVector *)calloc(1, sizeof(HPCSparseVector));
if (!sv) return NULL;
sv->n_sites = n_sites;
sv->capacity = initial_cap;
sv->entries = (HPCSparseEntry *)calloc(initial_cap, sizeof(HPCSparseEntry));
for (uint64_t i = 0; i < initial_cap; i++)
sv->entries[i].indices = (uint32_t *)calloc(n_sites, sizeof(uint32_t));
return sv;
}
static inline void hpc_sv_destroy(HPCSparseVector *sv)
{
if (!sv) return;
for (uint64_t i = 0; i < sv->capacity; i++)
free(sv->entries[i].indices);
free(sv->entries);
free(sv);
}
static inline void hpc_sv_grow(HPCSparseVector *sv)
{
if (sv->count < sv->capacity) return;
uint64_t new_cap = sv->capacity * 2;
sv->entries = (HPCSparseEntry *)realloc(sv->entries,
new_cap * sizeof(HPCSparseEntry));
for (uint64_t i = sv->capacity; i < new_cap; i++) {
sv->entries[i].indices = (uint32_t *)calloc(sv->n_sites, sizeof(uint32_t));
sv->entries[i].re = 0; sv->entries[i].im = 0; sv->entries[i].prob = 0;
}
sv->capacity = new_cap;
}
static inline void hpc_sv_add(HPCSparseVector *sv,
const uint32_t *indices,
double re, double im)
{
hpc_sv_grow(sv);
HPCSparseEntry *e = &sv->entries[sv->count];
memcpy(e->indices, indices, sv->n_sites * sizeof(uint32_t));
e->re = re;
e->im = im;
e->prob = re * re + im * im;
sv->total_prob += e->prob;
sv->count++;
}
/* ═══════════════════════════════════════════════════════════════════════
* BRUTE-FORCE SPARSE RECONSTRUCTION
*
* For small N: enumerate all D^N configurations, keep those above
* threshold. Returns a sparse vector of significant amplitudes.
*
* Cost: O(D^N Γ— (N+E)) β€” exponential, small N only.
* This is the reference implementation for verification.
* ═══════════════════════════════════════════════════════════════════════ */
static inline HPCSparseVector *hpc_sparse_brute(const HPCGraph *g,
double threshold,
uint64_t max_entries)
{
if (g->n_sites > 8) {
fprintf(stderr, "hpc_sparse_brute: N=%lu too large\n", g->n_sites);
return NULL;
}
HPCSparseVector *sv = hpc_sv_create(g->n_sites, 256);
if (!sv) return NULL;
sv->threshold = threshold;
uint64_t total_configs = 1;
for (uint64_t i = 0; i < g->n_sites; i++) total_configs *= HPC_D;
uint32_t indices[8];
for (uint64_t cfg = 0; cfg < total_configs && sv->count < max_entries; cfg++) {
uint64_t tmp = cfg;
for (uint64_t i = 0; i < g->n_sites; i++) {
indices[i] = tmp % HPC_D;
tmp /= HPC_D;
}
double re, im;
hpc_amplitude(g, indices, &re, &im);
double prob = re * re + im * im;
if (prob >= threshold)
hpc_sv_add(sv, indices, re, im);
}
return sv;
}
/* ═══════════════════════════════════════════════════════════════════════
* TREE-PRUNED SPARSE RECONSTRUCTION
*
* For larger N: build the state vector site-by-site, pruning branches
* whose cumulative probability falls below threshold.
*
* At each site k, we have a set of "live" partial configurations
* (i₁,...,i_k) with accumulated amplitude. For site k+1, we extend
* each live config to all D values, compute the new amplitude, and
* prune low-probability branches.
*
* Cost: O(active_branches Γ— D Γ— E_local) per site.
* For sparse states: active_branches << D^k β†’ exponential speedup.
*
* This is the practical reconstruction method for N > 8.
* ═══════════════════════════════════════════════════════════════════════ */
typedef struct {
uint32_t *indices; /* Partial index vector [n_sites] */
double re, im; /* Accumulated amplitude */
} HPCTreeNode;
static inline HPCSparseVector *hpc_sparse_tree(const HPCGraph *g,
double threshold,
uint64_t max_branches)
{
HPCSparseVector *sv = hpc_sv_create(g->n_sites, 256);
if (!sv) return NULL;
sv->threshold = threshold;
/* Initial pool: one root node with no sites assigned */
uint64_t pool_cap = max_branches * HPC_D + 16;
HPCTreeNode *current = (HPCTreeNode *)calloc(pool_cap, sizeof(HPCTreeNode));
HPCTreeNode *next = (HPCTreeNode *)calloc(pool_cap, sizeof(HPCTreeNode));
for (uint64_t i = 0; i < pool_cap; i++) {
current[i].indices = (uint32_t *)calloc(g->n_sites, sizeof(uint32_t));
next[i].indices = (uint32_t *)calloc(g->n_sites, sizeof(uint32_t));
}
/* Seed: one root node */
uint64_t n_current = 1;
current[0].re = 1.0;
current[0].im = 0.0;
/* Grow site by site */
for (uint64_t site = 0; site < g->n_sites; site++) {
uint64_t n_next = 0;
const TrialityQuhit *q = &g->locals[site];
for (uint64_t b = 0; b < n_current; b++) {
for (int v = 0; v < HPC_D; v++) {
/* Extend branch with site=v */
double a_re = q->edge_re[v];
double a_im = q->edge_im[v];
/* Multiply accumulated amplitude by local amplitude */
double new_re = current[b].re * a_re - current[b].im * a_im;
double new_im = current[b].re * a_im + current[b].im * a_re;
/* Apply phase contributions from edges connecting
* this site to already-assigned sites */
for (uint64_t e = 0; e < g->n_edges; e++) {
uint64_t sa = g->edges[e].site_a;
uint64_t sb = g->edges[e].site_b;
int partner_site = -1;
if (sa == site && sb < site) partner_site = (int)sb;
else if (sb == site && sa < site) partner_site = (int)sa;
if (partner_site >= 0) {
uint32_t pv = current[b].indices[partner_site];
double w_re, w_im;
if (g->edges[e].type == HPC_EDGE_CZ) {
uint32_t phase_idx = ((uint32_t)v * pv) % HPC_D;
w_re = HPC_W6_RE[phase_idx];
w_im = HPC_W6_IM[phase_idx];
} else {
if (sa == site) {
w_re = g->edges[e].w_re[v][pv];
w_im = g->edges[e].w_im[v][pv];
} else {
w_re = g->edges[e].w_re[pv][v];
w_im = g->edges[e].w_im[pv][v];
}
}
double tmp_re = new_re * w_re - new_im * w_im;
double tmp_im = new_re * w_im + new_im * w_re;
new_re = tmp_re;
new_im = tmp_im;
}
}
/* Prune: skip if amplitude is too small */
double prob = new_re * new_re + new_im * new_im;
if (prob < threshold && site < g->n_sites - 1) continue;
/* Accept this branch */
if (n_next < pool_cap) {
memcpy(next[n_next].indices, current[b].indices,
g->n_sites * sizeof(uint32_t));
next[n_next].indices[site] = v;
next[n_next].re = new_re;
next[n_next].im = new_im;
n_next++;
}
}
}
/* Swap pools */
HPCTreeNode *tmp = current;
current = next;
next = tmp;
n_current = n_next;
/* Sort by probability and truncate to max_branches */
if (n_current > max_branches && site < g->n_sites - 1) {
/* Simple selection: keep top max_branches by probability */
/* Partial sort using partition around threshold */
for (uint64_t i = max_branches; i < n_current; i++) {
/* Find minimum in kept set */
uint64_t min_idx = 0;
double min_prob = current[0].re * current[0].re +
current[0].im * current[0].im;
for (uint64_t j = 1; j < max_branches; j++) {
double p = current[j].re * current[j].re +
current[j].im * current[j].im;
if (p < min_prob) { min_prob = p; min_idx = j; }
}
/* Swap if current[i] is larger */
double p_i = current[i].re * current[i].re +
current[i].im * current[i].im;
if (p_i > min_prob) {
HPCTreeNode swap = current[min_idx];
current[min_idx] = current[i];
current[i] = swap;
}
}
n_current = max_branches;
}
}
/* All remaining branches are complete configurations */
for (uint64_t b = 0; b < n_current; b++) {
double prob = current[b].re * current[b].re +
current[b].im * current[b].im;
if (prob >= threshold)
hpc_sv_add(sv, current[b].indices, current[b].re, current[b].im);
}
/* Cleanup */
for (uint64_t i = 0; i < pool_cap; i++) {
free(current[i].indices);
free(next[i].indices);
}
free(current);
free(next);
return sv;
}
/* ═══════════════════════════════════════════════════════════════════════
* MONTE CARLO EXPECTATION VALUE
*
* Computes ⟨ψ|O|ψ⟩ via importance sampling without materializing |ψ⟩.
*
* Strategy:
* 1. Sample configurations by measuring each site sequentially
* using Born probabilities (marginals from the graph)
* 2. For each sample, evaluate ψ(config) and O(config)
* 3. Average over samples
*
* For diagonal observables O = Σ_i o(i)|i⟩⟨i|:
* ⟨O⟩ = Ξ£_i |ψ(i)|Β² o(i) β‰ˆ (1/S) Ξ£_{samples} o(i_s)
*
* Cost: O(n_samples Γ— (N + E))
* ═══════════════════════════════════════════════════════════════════════ */
typedef double (*HPCObservable)(const uint32_t *indices, uint64_t n_sites,
void *ctx);
static inline double hpc_expectation(const HPCGraph *g,
HPCObservable obs, void *obs_ctx,
int n_samples, uint64_t rng_seed)
{
/* Simple LCG for reproducible sampling */
uint64_t rng = rng_seed;
#define HPC_LCG(r) ((r) = (r) * 6364136223846793005ULL + 1442695040888963407ULL)
#define HPC_RAND(r) (((double)((r) >> 11)) * 0x1.0p-53)
double sum_obs = 0.0;
int valid_samples = 0;
for (int s = 0; s < n_samples; s++) {
/* Generate a configuration by sampling site-by-site */
uint32_t config[256]; /* max sites for MC */
if (g->n_sites > 256) break;
/* Simple approach: sample each site from its local distribution.
* This is approximate for entangled states but fast. */
for (uint64_t site = 0; site < g->n_sites; site++) {
const TrialityQuhit *q = &g->locals[site];
/* Local probability distribution */
double probs[HPC_D];
double total = 0;
for (int v = 0; v < HPC_D; v++) {
probs[v] = q->edge_re[v] * q->edge_re[v] +
q->edge_im[v] * q->edge_im[v];
total += probs[v];
}
/* Sample from local distribution */
HPC_LCG(rng);
double r = HPC_RAND(rng) * total;
double cumul = 0;
config[site] = HPC_D - 1;
for (int v = 0; v < HPC_D; v++) {
cumul += probs[v];
if (r <= cumul) { config[site] = v; break; }
}
}
/* Compute importance weight: |ψ(config)|² / q(config)
* where q = Ξ _k p_k(config[k]) is the proposal distribution */
double prob_psi = hpc_probability(g, config);
double prob_q = 1.0;
for (uint64_t site = 0; site < g->n_sites; site++) {
const TrialityQuhit *q = &g->locals[site];
uint32_t v = config[site];
double p = q->edge_re[v] * q->edge_re[v] +
q->edge_im[v] * q->edge_im[v];
prob_q *= p;
}
if (prob_q > 1e-30) {
double weight = prob_psi / prob_q;
double obs_val = obs(config, g->n_sites, obs_ctx);
sum_obs += weight * obs_val;
valid_samples++;
}
}
#undef HPC_LCG
#undef HPC_RAND
return (valid_samples > 0) ? sum_obs / valid_samples : 0.0;
}
/* ═══════════════════════════════════════════════════════════════════════
* PRINT SPARSE VECTOR
* ═══════════════════════════════════════════════════════════════════════ */
static inline void hpc_sv_print(const HPCSparseVector *sv, int max_show)
{
printf("── Sparse State Vector ──\n");
printf(" Entries: %lu, Captured prob: %.6f, Threshold: %.2e\n",
sv->count, sv->total_prob, sv->threshold);
uint64_t show = sv->count;
if (max_show > 0 && show > (uint64_t)max_show) show = max_show;
for (uint64_t i = 0; i < show; i++) {
printf(" |");
for (uint64_t s = 0; s < sv->n_sites; s++)
printf("%u", sv->entries[i].indices[s]);
printf("⟩ β†’ %.6f%+.6fi (P=%.6e)\n",
sv->entries[i].re, sv->entries[i].im, sv->entries[i].prob);
}
if (show < sv->count)
printf(" ... (%lu more entries)\n", sv->count - show);
}
#endif /* HPC_AMPLITUDE_H */