open-standards/wai/wai-quantum/src/repro.rs
Transaction Science 5cd36795cd
Some checks failed
standards workspaces build + test / jouleclaw-rs (pull_request) Successful in 3m17s
standards workspaces build + test / aac-rs (pull_request) Successful in 3m0s
standards workspaces build + test / joule-code-rs (pull_request) Successful in 1m31s
standards workspaces build + test / joulecontract-rs (pull_request) Successful in 46s
standards workspaces build + test / map-rs (pull_request) Successful in 1m50s
no-vanity-metrics / Scan committed Markdown for vanity metrics (pull_request) Has been cancelled
no-vanity-metrics / WAI public text names no comparison (pull_request) Has been cancelled
no-vanity-metrics / WAI public text names no company or product (pull_request) Has been cancelled
standards workspaces build + test / arl-rs (pull_request) Has been cancelled
standards workspaces build + test / eoc-rs (pull_request) Has been cancelled
standards workspaces build + test / jcp-rs (pull_request) Has been cancelled
standards workspaces build + test / joule-ui-rs (pull_request) Has been cancelled
standards workspaces build + test / joulehook-rs (pull_request) Has been cancelled
standards workspaces build + test / openpay (pull_request) Has been cancelled
standards workspaces build + test / proof-rs (pull_request) Has been cancelled
standards workspaces build + test / sandbox-rs (pull_request) Has been cancelled
standards workspaces build + test / sense-rs (pull_request) Has been cancelled
standards workspaces build + test / smart-byte-rs (pull_request) Has been cancelled
standards workspaces build + test / macOS energy backends (pull_request) Has been cancelled
WAI conformance / cargo-class corpora · linux (pull_request) Has been cancelled
WAI conformance / cargo-class corpora · linux-arm64 (pull_request) Has been cancelled
WAI conformance / cargo-class corpora · macos (pull_request) Has been cancelled
WAI conformance / v2 vocabulary · manifests validate as stated (pull_request) Has been cancelled
WAI conformance / independent verifiers · claims, JWP, key discovery and integer payloads (Python) (pull_request) Has been cancelled
WAI conformance / cross-arch byte-equality · linux (pull_request) Has been cancelled
WAI conformance / cross-arch byte-equality · linux-arm64 (pull_request) Has been cancelled
WAI conformance / cross-arch byte-equality · macos (pull_request) Has been cancelled
WAI conformance / C2PA manifest emission · signed, read back, validated (pull_request) Has been cancelled
WAI energy receipts / energy receipts · linux (pull_request) Has been cancelled
WAI energy receipts / energy receipts · linux-arm64 (pull_request) Has been cancelled
WAI energy receipts / energy receipts · macos (pull_request) Has been cancelled
WAI JPEG-AI whole decode (pinned inputs, required) / fetch pinned JPEG-AI inputs → whole decode + pixel hash, required (pull_request) Has been cancelled
WAI neural sink gates / model-free refusals of the neural sink (pull_request) Has been cancelled
WAI mbt2018_mean integer decode (regenerated vectors) / regenerate mbt2018_mean integer vectors → Rust decode, required (pull_request) Has been cancelled
wai-web wasm32 / wai-web wasm32 build · corpus, menu and writers (pull_request) Has been cancelled
standards workspaces build + test / mesh-rs (pull_request) Has been cancelled
standards workspaces build + test / jouledesk-rs (pull_request) Has been cancelled
wai-quantum 0.3.37: complex neural quantum states, and referees that cannot stop on an excited state
Fix. In 0.3.36, Lanczos stopped a restart as soon as its lowest Ritz pair
converged. That is safe from DMRG's warm starts, but from the referees' cold
ones that pair can be an excited state. Exact diagonalisation of the 8-site
Majumdar-Ghosh ring returned -2.595 instead of -3, and a variational energy
came out below it. Early stopping is now opt-in, used by the DMRG sweeps only;
exact diagonalisation, full CI and the spin-model referee build every Krylov
vector. The DMRG and NQS pins were re-taken.

New: the RBM, VMC and stochastic reconfiguration are generic over real and
complex parameters, and the real path keeps its bits.
- The complex path adds exp, ln cosh and tanh, the Hermitian reconfiguration
  step in either space, and a Hermitian Cholesky.
- New lattices: J1-J2 models, plus next-nearest bonds on chains and diagonal
  bonds on square lattices.
- A complex network learns sign structures nobody supplied. On 8-site rings it
  gets within 2e-3 where a real one stays >10% off, and the CLI solves the
  8-site Majumdar-Ghosh ring to rounding. On 12-site rings: Majumdar-Ghosh
  2e-6, J1-J2 (0.3) 3.5e-4.
- Measured and documented:
  - 10-site rings settle on a momentum-zero excited eigenstate, because the
    ground state has momentum pi.
  - At J2 = 0.5 on the 4x4 torus, alpha = 1 stays about 3% above exact.

The NQS pin now covers a complex machine. All 22 pins pass natively and
under wasm32-wasip2. The nqs CLI gains --complex, --model j1j2 and
--periodic, byte-identical under WASI.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-06 17:15:52 -04:00

389 lines
15 KiB
Rust
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! Transcendental functions that give the same bits on every platform.
//!
//! The platform's libm may differ from another's in the last place. That is
//! enough to break a result promised bit-identical natively, in the browser and
//! at the edge, and it did: four layers of this crate computed different bits
//! natively and as a WASI component until they moved here. Everything below is
//! fdlibm's algorithm, as musl carries it, using only `+ − × ÷`, which Rust
//! never fuses.
#[cfg(any(
feature = "quantum_spd",
feature = "quantum_pauli",
feature = "quantum_mps",
feature = "quantum_vml",
feature = "quantum_phasor",
feature = "quantum_kernel",
feature = "quantum_tn",
feature = "quantum_sv",
feature = "quantum_tdvp",
feature = "quantum_nqs"
))]
mod trig;
#[cfg(any(
feature = "quantum_spd",
feature = "quantum_pauli",
feature = "quantum_mps",
feature = "quantum_vml",
feature = "quantum_phasor",
feature = "quantum_kernel",
feature = "quantum_tn",
feature = "quantum_sv",
feature = "quantum_tdvp",
feature = "quantum_nqs"
))]
pub use trig::sin_cos;
// ---------------------------------------------------------------------------
// Natural logarithm
// ---------------------------------------------------------------------------
#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel", feature = "quantum_resource", feature = "quantum_frame", feature = "quantum_tn", feature = "quantum_gbs", feature = "quantum_arch", feature = "quantum_qio", feature = "quantum_dmrg"))]
const LN2_HI: f64 = 6.931_471_803_691_238_164_90e-1;
#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel", feature = "quantum_resource", feature = "quantum_frame", feature = "quantum_tn", feature = "quantum_gbs", feature = "quantum_arch", feature = "quantum_qio", feature = "quantum_dmrg"))]
const LN2_LO: f64 = 1.908_214_929_270_587_700_02e-10;
#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel", feature = "quantum_resource", feature = "quantum_frame", feature = "quantum_tn", feature = "quantum_gbs", feature = "quantum_arch", feature = "quantum_qio", feature = "quantum_dmrg"))]
const LG1: f64 = 6.666_666_666_666_735_130e-1;
#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel", feature = "quantum_resource", feature = "quantum_frame", feature = "quantum_tn", feature = "quantum_gbs", feature = "quantum_arch", feature = "quantum_qio", feature = "quantum_dmrg"))]
const LG2: f64 = 3.999_999_999_940_941_908e-1;
#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel", feature = "quantum_resource", feature = "quantum_frame", feature = "quantum_tn", feature = "quantum_gbs", feature = "quantum_arch", feature = "quantum_qio", feature = "quantum_dmrg"))]
const LG3: f64 = 2.857_142_874_366_239_149e-1;
#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel", feature = "quantum_resource", feature = "quantum_frame", feature = "quantum_tn", feature = "quantum_gbs", feature = "quantum_arch", feature = "quantum_qio", feature = "quantum_dmrg"))]
const LG4: f64 = 2.222_219_843_214_978_396e-1;
#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel", feature = "quantum_resource", feature = "quantum_frame", feature = "quantum_tn", feature = "quantum_gbs", feature = "quantum_arch", feature = "quantum_qio", feature = "quantum_dmrg"))]
const LG5: f64 = 1.818_357_216_161_805_012e-1;
#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel", feature = "quantum_resource", feature = "quantum_frame", feature = "quantum_tn", feature = "quantum_gbs", feature = "quantum_arch", feature = "quantum_qio", feature = "quantum_dmrg"))]
const LG6: f64 = 1.531_383_769_920_937_332e-1;
#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel", feature = "quantum_resource", feature = "quantum_frame", feature = "quantum_tn", feature = "quantum_gbs", feature = "quantum_arch", feature = "quantum_qio", feature = "quantum_dmrg"))]
const LG7: f64 = 1.479_819_860_511_658_591e-1;
/// The natural logarithm, within an ulp of the true value: `−∞` at zero, NaN
/// below it.
#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel", feature = "quantum_resource", feature = "quantum_frame", feature = "quantum_tn", feature = "quantum_gbs", feature = "quantum_arch", feature = "quantum_qio", feature = "quantum_dmrg"))]
pub fn ln(x: f64) -> f64 {
let mut bits = x.to_bits();
let mut hx = (bits >> 32) as u32;
let mut k: i32 = 0;
let mut x = x;
if hx < 0x0010_0000 || hx >> 31 != 0 {
if bits << 1 == 0 {
return f64::NEG_INFINITY;
}
if hx >> 31 != 0 {
return f64::NAN;
}
// subnormal: scale up
k -= 54;
x *= 18_014_398_509_481_984.0; // 2⁵⁴
bits = x.to_bits();
hx = (bits >> 32) as u32;
} else if hx >= 0x7ff0_0000 {
return x;
} else if hx == 0x3ff0_0000 && bits << 32 == 0 {
return 0.0;
}
// reduce x into [√2/2, √2]
hx = hx.wrapping_add(0x3ff0_0000 - 0x3fe6_a09e);
k += (hx >> 20) as i32 - 0x3ff;
hx = (hx & 0x000f_ffff) + 0x3fe6_a09e;
let x = f64::from_bits((u64::from(hx) << 32) | (bits & 0xffff_ffff));
let f = x - 1.0;
let hfsq = 0.5 * f * f;
let s = f / (2.0 + f);
let z = s * s;
let w = z * z;
let t1 = w * (LG2 + w * (LG4 + w * LG6));
let t2 = z * (LG1 + w * (LG3 + w * (LG5 + w * LG7)));
let r = t2 + t1;
let dk = f64::from(k);
s * (hfsq + r) + dk * LN2_LO - hfsq + f + dk * LN2_HI
}
// ---------------------------------------------------------------------------
// Exponential
// ---------------------------------------------------------------------------
#[cfg(any(feature = "quantum_frame", feature = "quantum_tn", feature = "quantum_gbs", feature = "quantum_arch", feature = "quantum_qio", feature = "quantum_nqs"))]
const EXP_P: [f64; 5] = [
1.666_666_666_666_660_190_37e-1,
-2.777_777_777_701_559_338_42e-3,
6.613_756_321_437_934_361_17e-5,
-1.653_390_220_546_525_153_90e-6,
4.138_136_797_057_238_460_39e-8,
];
/// `eˣ`, bit-identical on every IEEE-754 platform: fdlibm's algorithm
/// (reduction by `ln 2` in two parts, a rational approximation on
/// `[−ln2/2, ln2/2]`), using only `+ − × ÷`. Within an ulp of the true value.
#[cfg(any(feature = "quantum_frame", feature = "quantum_tn", feature = "quantum_gbs", feature = "quantum_arch", feature = "quantum_qio", feature = "quantum_nqs"))]
pub fn exp(x: f64) -> f64 {
const LN2_HI_E: f64 = 6.931_471_803_691_238_164_90e-1;
const LN2_LO_E: f64 = 1.908_214_929_270_587_700_02e-10;
const INV_LN2: f64 = 1.442_695_040_888_963_387_00;
if x.is_nan() {
return x;
}
if x > 7.097_827_128_933_839_730_96e2 {
return f64::INFINITY;
}
if x < -7.451_332_191_019_411_084_20e2 {
return 0.0;
}
let ax = x.abs();
let (hi, lo, k): (f64, f64, i32);
if ax > 0.5 * core::f64::consts::LN_2 {
let kk = if ax < 1.5 * core::f64::consts::LN_2 {
if x < 0.0 { -1 } else { 1 }
} else {
(INV_LN2 * x + if x < 0.0 { -0.5 } else { 0.5 }) as i32
};
let t = f64::from(kk);
hi = x - t * LN2_HI_E;
lo = t * LN2_LO_E;
k = kk;
} else if ax < 3.725_290_298_461_914e-9 {
// |x| < 2⁻²⁸: 1 + x to the last bit.
return 1.0 + x;
} else {
hi = x;
lo = 0.0;
k = 0;
}
let r = hi - lo;
let t = r * r;
let c = r - t * (EXP_P[0] + t * (EXP_P[1] + t * (EXP_P[2] + t * (EXP_P[3] + t * EXP_P[4]))));
if k == 0 {
return 1.0 - ((r * c) / (c - 2.0) - r);
}
let y = 1.0 - ((lo - (r * c) / (2.0 - c)) - hi);
// y · 2ᵏ, in two steps where 2ᵏ alone would leave the normal range.
let pow2 = |e: i32| f64::from_bits(((e + 1023) as u64) << 52);
if k > 1023 {
y * pow2(1023) * pow2(k - 1023)
} else if k < -1021 {
y * pow2(k + 1000) * pow2(-1000)
} else {
y * pow2(k)
}
}
// ---------------------------------------------------------------------------
// Arctangent
// ---------------------------------------------------------------------------
#[cfg(any(feature = "quantum_qfhrr", feature = "quantum_nqs"))]
const ATANHI: [f64; 4] = [
4.636_476_090_008_060_935_15e-1,
7.853_981_633_974_482_789_99e-1,
9.827_937_232_473_290_540_82e-1,
1.570_796_326_794_896_558_00,
];
#[cfg(any(feature = "quantum_qfhrr", feature = "quantum_nqs"))]
const ATANLO: [f64; 4] = [
2.269_877_745_296_168_709_24e-17,
3.061_616_997_868_383_017_93e-17,
1.390_331_103_123_099_845_16e-17,
6.123_233_995_736_766_035_87e-17,
];
#[cfg(any(feature = "quantum_qfhrr", feature = "quantum_nqs"))]
const AT: [f64; 11] = [
3.333_333_333_333_293_180_27e-1,
-1.999_999_999_987_648_324_76e-1,
1.428_571_427_250_346_637_11e-1,
-1.111_111_040_546_235_578_80e-1,
9.090_887_133_436_506_561_96e-2,
-7.691_876_205_044_829_994_95e-2,
6.661_073_137_387_531_206_69e-2,
-5.833_570_133_790_573_486_45e-2,
4.976_877_994_615_932_360_17e-2,
-3.653_157_274_421_691_552_70e-2,
1.628_582_011_536_578_236_23e-2,
];
/// The arctangent, within an ulp of the true value.
#[cfg(any(feature = "quantum_qfhrr", feature = "quantum_nqs"))]
pub fn atan(x: f64) -> f64 {
let ix = (x.to_bits() >> 32) as u32;
let negative = ix >> 31 != 0;
let ix = ix & 0x7fff_ffff;
if ix >= 0x4410_0000 {
// |x| ≥ 2⁶⁶
if x.is_nan() {
return x;
}
let z = ATANHI[3];
return if negative { -z } else { z };
}
let mut x = x;
let id: i32;
if ix < 0x3fdc_0000 {
// |x| < 0.4375
if ix < 0x3e40_0000 {
return x;
}
id = -1;
} else {
x = x.abs();
if ix < 0x3ff3_0000 {
if ix < 0x3fe6_0000 {
id = 0;
x = (2.0 * x - 1.0) / (2.0 + x);
} else {
id = 1;
x = (x - 1.0) / (x + 1.0);
}
} else if ix < 0x4003_8000 {
id = 2;
x = (x - 1.5) / (1.0 + 1.5 * x);
} else {
id = 3;
x = -1.0 / x;
}
}
let z = x * x;
let w = z * z;
let s1 = z * (AT[0] + w * (AT[2] + w * (AT[4] + w * (AT[6] + w * (AT[8] + w * AT[10])))));
let s2 = w * (AT[1] + w * (AT[3] + w * (AT[5] + w * (AT[7] + w * AT[9]))));
if id < 0 {
return x - x * (s1 + s2);
}
let i = id as usize;
let z = ATANHI[i] - (x * (s1 + s2) - ATANLO[i] - x);
if negative { -z } else { z }
}
#[cfg(any(feature = "quantum_qfhrr", feature = "quantum_nqs"))]
const PI: f64 = 3.141_592_653_589_793_116;
#[cfg(any(feature = "quantum_qfhrr", feature = "quantum_nqs"))]
const PI_LO: f64 = 1.224_646_799_147_353_177_2e-16;
/// The angle of the point `(x, y)`, in `(−π, π]`, within an ulp of the true
/// value.
#[cfg(any(feature = "quantum_qfhrr", feature = "quantum_nqs"))]
pub fn atan2(y: f64, x: f64) -> f64 {
if x.is_nan() || y.is_nan() {
return x + y;
}
let (xb, yb) = (x.to_bits(), y.to_bits());
let (ix, lx) = ((xb >> 32) as u32, xb as u32);
let (iy, ly) = ((yb >> 32) as u32, yb as u32);
if (ix.wrapping_sub(0x3ff0_0000) | lx) == 0 {
return atan(y); // x = 1
}
let m = ((iy >> 31) & 1) | ((ix >> 30) & 2);
let (ix, iy) = (ix & 0x7fff_ffff, iy & 0x7fff_ffff);
if (iy | ly) == 0 {
return match m {
0 | 1 => y,
2 => PI,
_ => -PI,
};
}
if (ix | lx) == 0 {
return if m & 1 != 0 { -PI / 2.0 } else { PI / 2.0 };
}
if ix == 0x7ff0_0000 {
return if iy == 0x7ff0_0000 {
match m {
0 => PI / 4.0,
1 => -PI / 4.0,
2 => 3.0 * PI / 4.0,
_ => -3.0 * PI / 4.0,
}
} else {
match m {
0 => 0.0,
1 => -0.0,
2 => PI,
_ => -PI,
}
};
}
if ix.wrapping_add(64 << 20) < iy || iy == 0x7ff0_0000 {
return if m & 1 != 0 { -PI / 2.0 } else { PI / 2.0 };
}
let z = if m & 2 != 0 && iy.wrapping_add(64 << 20) < ix { 0.0 } else { atan((y / x).abs()) };
match m {
0 => z,
1 => -z,
2 => PI - (z - PI_LO),
_ => (z - PI_LO) - PI,
}
}
#[cfg(test)]
mod tests {
#[allow(unused_imports)]
use super::*;
#[cfg(any(feature = "quantum_frame", feature = "quantum_tn", feature = "quantum_gbs", feature = "quantum_arch", feature = "quantum_qio", feature = "quantum_nqs"))]
#[test]
fn exp_is_within_an_ulp_of_the_platform() {
let mut s = 0x9e37_79b9_7f4a_7c15u64;
for i in 0..400_000 {
s ^= s << 13;
s ^= s >> 7;
s ^= s << 17;
let u = (s >> 11) as f64 / (1u64 << 53) as f64;
let span = [1e-10, 0.3, 0.7, 2.0, 20.0, 700.0, 740.0][i % 7];
let x = (2.0 * u - 1.0) * span;
assert!(ulps(exp(x), x.exp()) <= 1, "exp({x:e}) = {:e} vs {:e}", exp(x), x.exp());
}
assert_eq!(exp(0.0), 1.0);
assert_eq!(exp(f64::INFINITY), f64::INFINITY);
assert_eq!(exp(f64::NEG_INFINITY), 0.0);
assert!(exp(f64::NAN).is_nan());
assert_eq!(exp(800.0), f64::INFINITY);
assert_eq!(exp(-800.0), 0.0);
}
#[allow(dead_code)]
fn ulps(a: f64, b: f64) -> u64 {
if a == b {
return 0;
}
(a.to_bits() as i64).abs_diff(b.to_bits() as i64)
}
/// A deterministic spread of inputs over many magnitudes.
#[allow(dead_code)]
fn inputs() -> impl Iterator<Item = f64> {
let mut s = 0x2545_f491_4f6c_dd1du64;
(0..200_000).map(move |i| {
s ^= s << 13;
s ^= s >> 7;
s ^= s << 17;
let u = (s >> 11) as f64 / (1u64 << 53) as f64;
let scale = [1e-300, 1e-8, 1e-3, 0.5, 1.0, 3.0, 1e3, 1e12, 1e300][i % 9];
u * scale
})
}
#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel", feature = "quantum_resource", feature = "quantum_frame", feature = "quantum_tn", feature = "quantum_gbs", feature = "quantum_arch", feature = "quantum_qio", feature = "quantum_dmrg"))]
#[test]
fn ln_is_within_an_ulp_of_the_platform() {
for x in inputs().filter(|&x| x > 0.0) {
assert!(ulps(ln(x), x.ln()) <= 1, "ln({x:e}) = {:e} vs {:e}", ln(x), x.ln());
}
assert_eq!(ln(1.0), 0.0);
assert_eq!(ln(0.0), f64::NEG_INFINITY);
assert!(ln(-1.0).is_nan());
assert_eq!(ln(f64::INFINITY), f64::INFINITY);
assert!(ulps(ln(f64::MIN_POSITIVE / 8.0), (f64::MIN_POSITIVE / 8.0).ln()) <= 1, "subnormal");
}
#[cfg(feature = "quantum_qfhrr")]
#[test]
fn atan2_is_within_an_ulp_of_the_platform() {
let xs: Vec<f64> = inputs().take(4000).collect();
for (i, &a) in xs.iter().enumerate() {
let b = xs[(i * 7 + 3) % xs.len()];
for (y, x) in [(a, b), (-a, b), (a, -b), (-a, -b)] {
assert!(ulps(atan2(y, x), y.atan2(x)) <= 1, "atan2({y:e}, {x:e}) = {:e} vs {:e}", atan2(y, x), y.atan2(x));
}
}
assert_eq!(atan2(0.0, -1.0), PI);
assert_eq!(atan2(1.0, 0.0), PI / 2.0);
assert_eq!(atan(f64::INFINITY), ATANHI[3]);
}
}