// wai-quantum fabric: the gate kernel for OpenCL devices.
//
// A transcription of `lanes::exact_row` and `lanes::apply_words` in 64-bit
// integers, which OpenCL C's `long` is on every full-profile device. Amplitudes
// are pairs of 32-bit fixed-point values (30 fractional bits). Each product
// `fx(a, b) = (a·b + 2^29) >> 30` is exact in 64 bits (a 32×32 product is at
// most 2^62 in size), and so is each row's sum of four products. The lane
// stores the low 32 bits of each sum// gate's op-log index in `report[0]`, the lowest such index winning, so the
// host refuses the run with the gate the CPU reference names.
//
// Nothing here is floating point, depends on the order work-items run in, or
// reads an amplitude another work-item of the same launch writes: each
// work-item owns one pair (i, j), and a launch is one gate.
//
// OpenCL C fills the vacated bits of `>>` on a negative signed value with ones,
// as Rust's `>>` on i64 does// the arithmetic, before a device is a member.
typedef long i64typedef ulong u64
// The exact fixed-point product.
inline i64 fx(int a, int b) {
return ((i64)a * (i64)b + ((i64)1 << 29)) >> 30}
// The low 32 bits of `v`, as the lane keeps them.
inline int low32(i64 v) {
return (int)(uint)(u64)v}
// Whether `v` fits a 32-bit lane.
inline int fits32(i64 v) {
return v >= -(i64)2147483648L && v <= (i64)2147483647L}
// One gate on a state of `2 * pairs` lanes, one work-item per pair.
//
// `m0..m7` are the gate's words: row 0 is (m0 + i·m1)·a0 + (m2 + i·m3)·a1,
// row 1 is (m4 + i·m5)·a0 + (m6 + i·m7)·a1. `tbit` is 1 << target// the OR of the controls' bits. `index` is the gate's position in the op
// log. The host checks the words before a launch (`apply_words` refuses the
// same malformed words), so the kernel trusts them.
__kernel void apply_gate(
__global int2* restrict amps,
ulong pairs,
uint tbit,
uint ctrl,
int m0, int m1, int m2, int m3, int m4, int m5, int m6, int m7,
uint index,
__global uint* restrict report)
{
u64 p = (u64)get_global_id(0) u64 stride = (u64)get_global_size(0) for ( // The p-th index whose target bit is clear: p with a zero inserted at
// the target's position.
u64 low = p & ((u64)tbit - 1) u64 i = ((p - low) << 1) | low if ((i & (u64)ctrl) != (u64)ctrl) {
continue }
u64 j = i | (u64)tbit int2 a0 = amps[i] int2 a1 = amps[j] i64 o0r = fx(m0, a0.x) - fx(m1, a0.y) + fx(m2, a1.x) - fx(m3, a1.y) i64 o0i = fx(m0, a0.y) + fx(m1, a0.x) + fx(m2, a1.y) + fx(m3, a1.x) i64 o1r = fx(m4, a0.x) - fx(m5, a0.y) + fx(m6, a1.x) - fx(m7, a1.y) i64 o1i = fx(m4, a0.y) + fx(m5, a0.x) + fx(m6, a1.y) + fx(m7, a1.x) if (!(fits32(o0r) && fits32(o0i) && fits32(o1r) && fits32(o1i))) {
atomic_min(&report[0], index) }
amps[i] = (int2)(low32(o0r), low32(o0i)) amps[j] = (int2)(low32(o1r), low32(o1i)) }
}
// The arithmetic probe: for each case (a, b), the 64-bit two's-complement
// product a·b and the exact fx(a, b), each as (high, low) words, the outputs
// of `lanes::mul_wide` and `lanes::fxmul_wide`.
__kernel void probe(
__global const int2* restrict cases,
__global uint4* restrict out,
ulong count)
{
u64 k = (u64)get_global_id(0) u64 stride = (u64)get_global_size(0) for ( int2 c = cases[k] u64 prod = (u64)((i64)c.x * (i64)c.y) u64 f = (u64)fx(c.x, c.y) out[k] = (uint4)((uint)(prod >> 32), (uint)prod, (uint)(f >> 32), (uint)f) }
}