From 578569fd02e837bb2916364ebae4d6ef907b8e0a Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Fri, 6 Nov 2026 07:15:15 -0500 Subject: [PATCH 1/4] wai-quantum: a WebGPU member of the landscape fabric (work in progress) The settle kernel (one invocation a cell, a dispatch a sweep) and the sampling kernel (one invocation a chain, its cells in order, the reference's draws in pairs of 32-bit words and the host's cut points) in WGSL, and a coloured kernel that can be statistical at most. A member declines, before anything runs, a workload beyond its 32-bit lanes (Member::takes), and job::call_placed gives the job to the next member, naming the refusal. The host hands a member the starts of several seeds at once. Certification holds a member to every vector it takes and to every vector whose fields fit 32 bits. Co-Authored-By: Claude Opus 5.5 --- wai/wai-quantum/Cargo.toml | 9 +- wai/wai-quantum/src/quantum_landscape.rs | 2 + .../src/quantum_landscape/conformance.rs | 19 +- wai/wai-quantum/src/quantum_landscape/job.rs | 149 ++++- .../src/quantum_landscape/landscape.wgsl | 233 +++++++ .../src/quantum_landscape/member.rs | 9 +- wai/wai-quantum/src/quantum_landscape/run.rs | 124 +++- .../src/quantum_landscape/webgpu.rs | 632 ++++++++++++++++++ wai/wai-quantum/tests/landscape_webgpu.rs | 159 +++++ 9 files changed, 1273 insertions(+), 63 deletions(-) create mode 100644 wai/wai-quantum/src/quantum_landscape/landscape.wgsl create mode 100644 wai/wai-quantum/src/quantum_landscape/webgpu.rs create mode 100644 wai/wai-quantum/tests/landscape_webgpu.rs diff --git a/wai/wai-quantum/Cargo.toml b/wai/wai-quantum/Cargo.toml index 2aa7c4ba9..e9ee74bcc 100644 --- a/wai/wai-quantum/Cargo.toml +++ b/wai/wai-quantum/Cargo.toml @@ -14,7 +14,7 @@ readme = "README.md" # quantum-conformance/qasm3/, quantum-conformance/qec-text/, the answer # layer's conformance set and quantum-conformance/formats/) to the crate; # they run in the repository only. -exclude = ["tests/landscape_conformance.rs", "tests/fabric_expected.rs", "tests/qasm3_conformance.rs", "tests/qec_text_conformance.rs", "tests/answer_conformance.rs", "tests/formats_conformance.rs", "tests/formats_mutation.rs"] +exclude = ["tests/landscape_conformance.rs", "tests/landscape_webgpu.rs", "tests/fabric_expected.rs", "tests/qasm3_conformance.rs", "tests/qec_text_conformance.rs", "tests/answer_conformance.rs", "tests/formats_conformance.rs", "tests/formats_mutation.rs"] # docs.rs builds with DEFAULT features only, which would document 3 of 21 # modules and hide the entire operational + ML layers. Document everything. @@ -143,6 +143,9 @@ quantum_qio = [] # as the reference, every arrangement checked where a program is small, the joules # priced, and standard classical methods on the same instance (extensions/quantum-landscape.md). quantum_landscape = ["quantum_qio", "serde_json/float_roundtrip"] +# The landscape kernels on a GPU through WebGPU, natively (quantum_landscape::webgpu): the +# settle kernel and the sampling kernel bit-exact in 32-bit lanes, and a coloured kernel. +quantum_landscape_webgpu = ["quantum_landscape", "quantum_fabric_webgpu"] # Union-find decoding in near-linear time over the matching graph. quantum_uf = ["quantum_frame"] # Noisy circuits: quantum trajectories on the state vector, an exact density- @@ -252,6 +255,10 @@ required-features = ["quantum_landscape"] name = "landscape_float_read" required-features = ["quantum_landscape"] +[[test]] +name = "landscape_webgpu" +required-features = ["quantum_landscape_webgpu"] + [[test]] name = "webgpu_member" required-features = ["quantum_fabric_webgpu"] diff --git a/wai/wai-quantum/src/quantum_landscape.rs b/wai/wai-quantum/src/quantum_landscape.rs index eaf4973cb..fb7f3290d 100644 --- a/wai/wai-quantum/src/quantum_landscape.rs +++ b/wai/wai-quantum/src/quantum_landscape.rs @@ -95,6 +95,8 @@ pub mod settle; pub mod table; pub mod trial; pub mod truth; +#[cfg(all(feature = "quantum_landscape_webgpu", not(target_arch = "wasm32")))] +pub mod webgpu; /// The protocol of a job and its answer (`extensions/quantum-landscape.md`). pub const PROTOCOL: &str = "wai.quantum.landscape/1"; diff --git a/wai/wai-quantum/src/quantum_landscape/conformance.rs b/wai/wai-quantum/src/quantum_landscape/conformance.rs index 55552a048..63532243c 100644 --- a/wai/wai-quantum/src/quantum_landscape/conformance.rs +++ b/wai/wai-quantum/src/quantum_landscape/conformance.rs @@ -119,10 +119,24 @@ pub fn settle_cases() -> Vec { out } -/// Whether a member's settle kernel gives every `settle` vector exactly. +/// The widest field bound a member must take: every workload whose fields fit 32 bits. +pub const MUST_TAKE_FIELD_BOUND: i64 = i32::MAX as i64; + +/// Whether a member declines a vector it may decline, before running it: one +/// whose field bound is above [`MUST_TAKE_FIELD_BOUND`]. `None` when it takes +/// the vector, `Some(true)` when it may skip it, `Some(false)` when it must not. +fn declines(m: &dyn Member, w: &super::settle::Workload, sampling: bool) -> Option { + m.takes(w, sampling).err().map(|_| w.field_bound() > MUST_TAKE_FIELD_BOUND) +} + +/// Whether a member's settle kernel gives every `settle` vector it takes +/// exactly, and takes every one whose fields fit 32 bits. pub fn settle_vectors_hold(m: &dyn Member) -> bool { settle_cases().iter().all(|c| { let w = c.landscape.lower(); + if let Some(may) = declines(m, &w, false) { + return may; + } let want = w.settle(&c.start, c.bound); m.settle(&w, &c.start, c.bound).is_ok_and(|o| check_settle(&o, &w, c.bound).is_ok() && o == want) }) @@ -228,6 +242,9 @@ pub fn chain_vectors_hold(m: &dyn Member) -> bool { chain_cases().iter().all(|c| { let w = c.landscape.lower(); let temperatures = schedule(c.t0, c.sweeps); + if let Some(may) = declines(m, &w, temperatures.iter().any(|&t| t > 0.0)) { + return may; + } let chains = c.chains(); let want: Vec = chains.iter().map(|ch| anneal_with(&w, &ch.start, &temperatures, ch.seed)).collect(); m.chains(&w, &chains, &temperatures).is_ok_and(|got| got.len() == want.len() && got.iter().all(|o| check_chain(o, &w, &temperatures).is_ok()) && got == want) diff --git a/wai/wai-quantum/src/quantum_landscape/job.rs b/wai/wai-quantum/src/quantum_landscape/job.rs index 74775b6c2..c7a1e1645 100644 --- a/wai/wai-quantum/src/quantum_landscape/job.rs +++ b/wai/wai-quantum/src/quantum_landscape/job.rs @@ -93,17 +93,28 @@ pub fn run_text(program: &str, seed: u64) -> Result { call("run", &json!({ "program": program, "seed": seed.to_string() })) } -/// Answer a request on a certified member, holding no more than `max_bytes` -/// (and never more than [`MAX_JOB_BYTES`]). `op` is `run`, `sweep`, `compare`, -/// `replay` or `examples`. -pub fn call_certified(c: &Certified<'_>, op: &str, request: &Value, max_bytes: u64) -> Result { +/// A request read and its budget checked: the job, ready to run. +pub struct Prepared { + pub job: Job, + op: String, + seed: u64, + temperature: Option, + seeds: u32, + temperatures: Vec, + /// Whether any sweep of the job is above temperature 0. + pub sampling: bool, +} + +/// Read a request and check every bound of its job before any of it runs: +/// `None` for `examples`, which runs nothing. +pub fn prepare(op: &str, request: &Value, max_bytes: u64) -> Result, Refusal> { if let Some(p) = request.get("protocol") && p != PROTOCOL { return Err(Refusal::general(format!("This reads {PROTOCOL}, not {p}."))); } if op == "examples" { - return Ok(examples()); + return Ok(None); } if !matches!(op, "run" | "sweep" | "compare" | "replay") { return Err(Refusal::general(format!("`{op}` is not an operation here: run, sweep, compare, replay and examples are."))); @@ -112,22 +123,24 @@ pub fn call_certified(c: &Certified<'_>, op: &str, request: &Value, max_bytes: u let p = read(source)?; let seed = seed_of(request)?; let temperature = temperature_of(request)?; + let (mut seeds, mut temperatures) = (1u32, Vec::new()); // What the job will run, and how many times, before any of it runs. let (runs, lines): (Vec<(Plan, u64)>, u64) = match op { "run" => (vec![(Plan::of(&p, temperature)?, 1)], 0), "sweep" => { - let temperatures = temperatures_of(request, &p)?; - (run::sweep_plans(&p, &temperatures, seeds_of(request, 20)?)?.into_iter().map(|(_, plan, k)| (plan, u64::from(k))).collect(), 0) + temperatures = temperatures_of(request, &p)?; + seeds = seeds_of(request, 20)?; + (run::sweep_plans(&p, &temperatures, seeds)?.into_iter().map(|(_, plan, k)| (plan, u64::from(k))).collect(), 0) } "compare" => { - let seeds = u64::from(seeds_of(request, 20)?); + seeds = seeds_of(request, 20)?; let plan = run::compare_plan(&p, temperature)?; // The emulator twice, and three classical methods at the sampling run's budget. - (vec![(plan, seeds * 5)], 0) + (vec![(plan, u64::from(seeds) * 5)], 0) } _ => { - let seeds = u64::from(seeds_of(request, 20)?); - (vec![(Plan::of(&p, temperature)?, seeds)], seeds) + seeds = seeds_of(request, 20)?; + (vec![(Plan::of(&p, temperature)?, u64::from(seeds))], u64::from(seeds)) } }; let operations = run::job_operations(&p, &runs); @@ -146,35 +159,109 @@ pub fn call_certified(c: &Certified<'_>, op: &str, request: &Value, max_bytes: u if bytes > most { return Err(Refusal::general(format!("This job could hold up to {} bytes, and this host allows {}.", super::price::group(bytes), super::price::group(most)))); } - let job = Job::new(p); - match op { - "run" => run::run_on(c, &job, seed, temperature), - "sweep" => run::sweep_on(c, &job, &temperatures_of(request, &job.program)?, seeds_of(request, 20)?, seed), - "compare" => run::compare_on(c, &job, seeds_of(request, 20)?, seed, temperature), + let sampling = op == "compare" || runs.iter().any(|(plan, _)| plan.mode() == super::price::Mode::Sample); + Ok(Some(Prepared { job: Job::new(p), op: op.into(), seed, temperature, seeds, temperatures, sampling })) +} + +/// Answer a prepared job on a certified member. +pub fn answer(c: &Certified<'_>, prepared: &Prepared) -> Result { + let Prepared { job, op, seed, temperature, seeds, temperatures, .. } = prepared; + let (seed, temperature, seeds) = (*seed, *temperature, *seeds); + match op.as_str() { + "run" => run::run_on(c, job, seed, temperature), + "sweep" => run::sweep_on(c, job, temperatures, seeds, seed), + "compare" => run::compare_on(c, job, seeds, seed, temperature), _ => { // Many seeds, one line each: the seed, a digest of that run's whole answer, the - // state, its energy, the verdict, the accumulates and the replay digest. - let seeds = seeds_of(request, 20)?; + // state, its energy, the verdict, the accumulates and the replay digest. The + // starts of several seeds go to the member together. + let plan = Plan::of(&job.program, temperature)?; + let all: Vec = (0..seeds).map(|k| seed.wrapping_add(u64::from(k))).collect(); let mut lines = String::new(); - for k in 0..seeds { - let s = seed.wrapping_add(u64::from(k)); - let a = run::run_on(c, &job, s, temperature)?; - let text = a.to_string(); - lines.push_str(&format!( - "{s} {} {} {} {} {} {}\n", - &blake3::hash(text.as_bytes()).to_hex()[..16], - a["state"].as_str().unwrap_or(""), - a["energy"], - a["verdict"].as_str().unwrap_or(""), - a["work"]["accumulates"], - &a["replay"].as_str().unwrap_or("")[..16], - )); + for group in all.chunks(run::seeds_a_batch(job, plan)) { + for (s, found) in group.iter().zip(run::execute_seeds(job, plan, group, c)?) { + let a = run::answer_of(c, job, plan, *s, &found); + let text = a.to_string(); + lines.push_str(&format!( + "{s} {} {} {} {} {} {}\n", + &blake3::hash(text.as_bytes()).to_hex()[..16], + a["state"].as_str().unwrap_or(""), + a["energy"], + a["verdict"].as_str().unwrap_or(""), + a["work"]["accumulates"], + &a["replay"].as_str().unwrap_or("")[..16], + )); + } } Ok(json!({ "protocol": PROTOCOL, "seeds": seeds, "lines": lines })) } } } +/// Answer a request on a certified member, holding no more than `max_bytes` +/// (and never more than [`MAX_JOB_BYTES`]). `op` is `run`, `sweep`, `compare`, +/// `replay` or `examples`. +pub fn call_certified(c: &Certified<'_>, op: &str, request: &Value, max_bytes: u64) -> Result { + match prepare(op, request, max_bytes)? { + None => Ok(examples()), + Some(p) => answer(c, &p), + } +} + +/// Answer a request on the first of `members` that takes its workload +/// ([`super::member::Member::takes`]): each that does not is named in the +/// answer's `refusals` with why, and the next is asked. Put the CPU reference +/// last, and every job has a member. +pub fn call_placed(members: &[Certified<'_>], op: &str, request: &Value, max_bytes: u64) -> Result { + let Some(p) = prepare(op, request, max_bytes)? else { return Ok(examples()) }; + let mut refusals = Vec::new(); + for c in members { + match c.member().takes(&p.job.workload, p.sampling) { + Ok(()) => { + let mut a = answer(c, &p)?; + if !refusals.is_empty() { + a["refusals"] = Value::Array(refusals); + } + return Ok(a); + } + Err(why) => refusals.push(json!({ "member": c.member().id(), "why": why })), + } + } + Err(Refusal::general(format!("No member offered takes this job: {}.", refusals.iter().map(|r| format!("{}: {}", r["member"].as_str().unwrap_or(""), r["why"].as_str().unwrap_or(""))).collect::>().join("; ")))) +} + +/// A digest of what a member gives for every worked example at its own +/// temperature, at 0 and at 0.5, over `seeds` seeds, that does not depend on +/// which member it was: each run's state, energy, verdict, work and replay +/// digest. Two members that give the same bits give the same digest. +pub fn corpus_bits_on(c: &Certified<'_>, seeds: u32) -> String { + let mut h = blake3::Hasher::new(); + for (name, text) in EXAMPLES { + for temperature in [Value::Null, json!(0.0), json!(0.5)] { + h.update(name.as_bytes()); + h.update(&[0]); + let req = json!({ "program": text, "seed": 0, "seeds": seeds, "temperature": temperature }); + match prepare("replay", &req, MAX_JOB_BYTES).and_then(|p| { + let p = p.expect("a replay runs"); + let plan = Plan::of(&p.job.program, p.temperature)?; + let all: Vec = (0..seeds).map(u64::from).collect(); + let mut out = String::new(); + for group in all.chunks(run::seeds_a_batch(&p.job, plan)) { + for (s, found) in group.iter().zip(run::execute_seeds(&p.job, plan, group, c)?) { + let a = run::answer_of(c, &p.job, plan, *s, &found); + out.push_str(&format!("{s} {} {} {} {} {} {}\n", a["state"], a["energy"], a["verdict"], a["work"]["accumulates"], a["work"]["draws"], a["replay"])); + } + } + Ok(out) + }) { + Ok(lines) => h.update(lines.as_bytes()), + Err(r) => h.update(r.plain().as_bytes()), + }; + } + } + h.finalize().to_hex().to_string() +} + /// The worked examples: each with what it is there to show. pub fn examples() -> Value { let note = |text: &str, key: &str| -> Value { diff --git a/wai/wai-quantum/src/quantum_landscape/landscape.wgsl b/wai/wai-quantum/src/quantum_landscape/landscape.wgsl new file mode 100644 index 000000000..e1cb557a2 --- /dev/null +++ b/wai/wai-quantum/src/quantum_landscape/landscape.wgsl @@ -0,0 +1,233 @@ +// The two kernels of wai.quantum.landscape/1 in WGSL (extensions/quantum-landscape.md §4). +// +// Every value a cell's field can take fits a 32-bit lane: the host gives a workload only when +// its field bound and cost do (quantum_landscape::webgpu). Fields are whole numbers, so a sum +// is the same whatever order a device adds it in, and the settle kernel gives the reference's +// bits. The sampling kernel gives them too: its draws are the reference's (SplitMix64 by +// counter, in pairs of 32-bit words), its cut points come from the host, and each chain runs +// its cells in order. `chains_coloured` moves a colour class of cells at once: its order is +// not the reference's, so its parity can only be statistical. +// +// At most four storage buffers an entry point, the WebGPU floor: the couplings, the fields and +// the offsets in one (`graph`), every chain's cells and scratch in another (`cells`), and the +// seeds, the window's sweeps, the cut points and a colour class's cells in a third (`draws`). + +struct Shape { + n: u32, + cost: i32, + chains: u32, + // A settle: the most sweeps. Chains: the field bound B of the cut-point table. + bound: u32, + // Chains: the first sweep of this window, and one past its last. + k0: u32, + k1: u32, + // Where each part of `graph` starts: rows at 0, then columns, values and fields. + cols: u32, + vals: u32, + fields: u32, + // Where each part of `draws` starts: seeds at 0, then the sweeps and the table. + sweeps: u32, + table: u32, + // A colour class: where its cells start in `draws`, how many it has, its sweep and kind. + class_first: u32, + class_count: u32, + class_k: u32, + class_kind: u32, + pad: u32, +} + +@group(0) @binding(0) var graph: array; +@group(0) @binding(1) var shape: Shape; + +fn threshold(h: i32, c: i32) -> i32 { + if (h > c) { return 1; } + if (h < -c) { return -1; } + return 0; +} + +// ── settle: one invocation a cell, one dispatch a sweep, then one to advance ──────────────── + +// Three states in turn: s_k is at k mod 3. +@group(0) @binding(2) var ring: array; +// [sweeps run, how it ended (1 rest, 2 two-cycle, 3 bound), moved, moved from two back] +@group(0) @binding(3) var status: array, 4>; + +fn ring_field(base: u32, i: u32) -> i32 { + var h = bitcast(graph[shape.fields + i]); + for (var k = graph[i]; k < graph[i + 1u]; k = k + 1u) { + h = h + bitcast(graph[shape.vals + k]) * ring[base + graph[shape.cols + k]]; + } + return h; +} + +@compute @workgroup_size(64) +fn settle_sweep(@builtin(global_invocation_id) id: vec3, @builtin(num_workgroups) groups: vec3) { + let i = id.x + id.y * groups.x * 64u; + if (i >= shape.n || atomicLoad(&status[1]) != 0u) { return; } + let n = shape.n; + let done = atomicLoad(&status[0]); + let now = (done % 3u) * n; + let next = ((done + 1u) % 3u) * n; + let back = ((done + 2u) % 3u) * n; + let s = threshold(ring_field(now, i), shape.cost); + ring[next + i] = s; + if (s != ring[now + i]) { atomicOr(&status[2], 1u); } + if (s != ring[back + i]) { atomicOr(&status[3], 1u); } +} + +@compute @workgroup_size(1) +fn settle_advance() { + if (atomicLoad(&status[1]) != 0u) { return; } + let k = atomicLoad(&status[0]) + 1u; + atomicStore(&status[0], k); + if (atomicLoad(&status[2]) == 0u) { + atomicStore(&status[1], 1u); + } else if (k >= 2u && atomicLoad(&status[3]) == 0u) { + atomicStore(&status[1], 2u); + } else if (k >= shape.bound) { + atomicStore(&status[1], 3u); + } + atomicStore(&status[2], 0u); + atomicStore(&status[3], 0u); +} + +// ── chains ────────────────────────────────────────────────────────────────────────────────── + +// Every chain's cells, then the same again as scratch for a sweep at 0. +@group(0) @binding(4) var cells: array; +// Each chain's seed (low word, high word); each sweep of the window (0 for a sweep at +// temperature 0, else 1 + its first table entry); the table, four words an entry (a's low and +// high words, then b's) for each field from −B to B; a colour class's cells. +@group(0) @binding(5) var draws: array; + +fn cell_field(base: u32, i: u32) -> i32 { + var h = bitcast(graph[shape.fields + i]); + for (var k = graph[i]; k < graph[i + 1u]; k = k + 1u) { + h = h + bitcast(graph[shape.vals + k]) * cells[base + graph[shape.cols + k]]; + } + return h; +} + +// 64-bit words as (low, high). +fn add64(a: vec2, b: vec2) -> vec2 { + let lo = a.x + b.x; + return vec2(lo, a.y + b.y + select(0u, 1u, lo < a.x)); +} + +// The full product of two 32-bit words. +fn mul32(a: u32, b: u32) -> vec2 { + let a0 = a & 0xffffu; + let a1 = a >> 16u; + let b0 = b & 0xffffu; + let b1 = b >> 16u; + let p00 = a0 * b0; + let p01 = a0 * b1; + let p10 = a1 * b0; + let p11 = a1 * b1; + let mid = p01 + p10; + let mid_carry = select(0u, 0x10000u, mid < p01); + let lo = p00 + (mid << 16u); + let lo_carry = select(0u, 1u, lo < p00); + return vec2(lo, p11 + (mid >> 16u) + mid_carry + lo_carry); +} + +// The low 64 bits of a product of two 64-bit words. +fn mul64(a: vec2, b: vec2) -> vec2 { + let low = mul32(a.x, b.x); + return vec2(low.x, low.y + a.x * b.y + a.y * b.x); +} + +fn shr64(a: vec2, s: u32) -> vec2 { + return vec2((a.x >> s) | (a.y << (32u - s)), a.y >> s); +} + +fn xor64(a: vec2, b: vec2) -> vec2 { + return vec2(a.x ^ b.x, a.y ^ b.y); +} + +// The j-th output of SplitMix64 started at s. +fn splitmix(s: vec2, j: vec2) -> vec2 { + var x = add64(s, mul64(j, vec2(0x7F4A7C15u, 0x9E3779B9u))); + x = xor64(x, shr64(x, 30u)); + x = mul64(x, vec2(0x1CE4E5B9u, 0xBF58476Du)); + x = xor64(x, shr64(x, 27u)); + x = mul64(x, vec2(0x133111EBu, 0x94D049BBu)); + return xor64(x, shr64(x, 31u)); +} + +fn below(u: vec2, a: vec2) -> bool { + return u.y < a.y || (u.y == a.y && u.x < a.x); +} + +fn pick(u: vec2, entry: u32) -> i32 { + let e = shape.table + entry * 4u; + if (below(u, vec2(draws[e], draws[e + 1u]))) { return -1; } + if (below(u, vec2(draws[e + 2u], draws[e + 3u]))) { return 0; } + return 1; +} + +// The draw of cell i at sweep k: out(chain, k·n + i + 1) >> 11. +fn draw(seed: vec2, k: u32, i: u32) -> vec2 { + let j = add64(mul32(k, shape.n), vec2(i + 1u, 0u)); + return shr64(splitmix(seed, j), 11u); +} + +fn seed_of(c: u32) -> vec2 { + return vec2(draws[c * 2u], draws[c * 2u + 1u]); +} + +@compute @workgroup_size(64) +fn chains_in_order(@builtin(global_invocation_id) id: vec3, @builtin(num_workgroups) groups: vec3) { + let c = id.x + id.y * groups.x * 64u; + if (c >= shape.chains) { return; } + let n = shape.n; + let base = c * n; + let spare = shape.chains * n + base; + let seed = seed_of(c); + let b = i32(shape.bound); + for (var k = shape.k0; k < shape.k1; k = k + 1u) { + let kind = draws[shape.sweeps + k - shape.k0]; + if (kind == 0u) { + for (var i = 0u; i < n; i = i + 1u) { + cells[spare + i] = threshold(cell_field(base, i), shape.cost); + } + for (var i = 0u; i < n; i = i + 1u) { + cells[base + i] = cells[spare + i]; + } + } else { + for (var i = 0u; i < n; i = i + 1u) { + let h = cell_field(base, i); + cells[base + i] = pick(draw(seed, k, i), kind - 1u + u32(h + b)); + } + } + } +} + +// ── chains, a colour class at a time: statistical parity at most ──────────────────────────── + +// One invocation a (chain, cell of the class). No two cells of a class are coupled, so each +// reads fields no other invocation of this dispatch writes. A sweep at 0 is two dispatches: +// every cell from the old state into scratch, then `chains_copy`. +@compute @workgroup_size(64) +fn chains_coloured(@builtin(global_invocation_id) id: vec3, @builtin(num_workgroups) groups: vec3) { + let t = id.x + id.y * groups.x * 64u; + let n = shape.n; + if (shape.class_kind == 0u) { + if (t >= shape.chains * n) { return; } + cells[shape.chains * n + t] = threshold(cell_field((t / n) * n, t % n), shape.cost); + return; + } + if (t >= shape.chains * shape.class_count) { return; } + let c = t / shape.class_count; + let i = draws[shape.class_first + t % shape.class_count]; + let base = c * n; + let h = cell_field(base, i); + cells[base + i] = pick(draw(seed_of(c), shape.class_k, i), shape.class_kind - 1u + u32(h + i32(shape.bound))); +} + +@compute @workgroup_size(64) +fn chains_copy(@builtin(global_invocation_id) id: vec3, @builtin(num_workgroups) groups: vec3) { + let t = id.x + id.y * groups.x * 64u; + if (t >= shape.chains * shape.n) { return; } + cells[t] = cells[shape.chains * shape.n + t]; +} diff --git a/wai/wai-quantum/src/quantum_landscape/member.rs b/wai/wai-quantum/src/quantum_landscape/member.rs index d0505c96b..1e6ae4944 100644 --- a/wai/wai-quantum/src/quantum_landscape/member.rs +++ b/wai/wai-quantum/src/quantum_landscape/member.rs @@ -90,10 +90,17 @@ pub trait Member { /// ([`super::sample::anneal_with`]): a sweep at 0 is the deterministic one. /// A device may run the chains side by side. fn chains(&self, w: &Workload, chains: &[Chain], temperatures: &[f64]) -> Result, String>; - /// A reading of what the run just made spent, if it has a meter. + /// A reading of what its last call spent, if it has a meter. fn meter(&self) -> Option { None } + /// Whether it takes a workload, said before anything runs: the reason + /// where it does not (a field too wide for its lanes, a table too large), + /// and the host then gives the job to the next member it was offered. + /// `sampling` is whether any sweep is above temperature 0. + fn takes(&self, _w: &Workload, _sampling: bool) -> Result<(), String> { + Ok(()) + } } /// The reference: this crate's kernels, on this machine's CPU. diff --git a/wai/wai-quantum/src/quantum_landscape/run.rs b/wai/wai-quantum/src/quantum_landscape/run.rs index 6509df261..2f63def0d 100644 --- a/wai/wai-quantum/src/quantum_landscape/run.rs +++ b/wai/wai-quantum/src/quantum_landscape/run.rs @@ -279,13 +279,26 @@ fn replay_digest(p: &Program, seed: u64, plan: Plan, state: &[i8], energy: i64) } /// What the kernels leave, over every start of a run. -struct Found { +#[derive(Clone)] +pub(crate) struct Found { state: Vec, energy: i64, accumulates: u64, draws: u64, sweeps: u64, term: Option, + /// A meter's reading over the member's calls for this run alone; none + /// where a call covered other runs too, or a call gave no reading. + metered: Option, +} + +/// Sum the readings of a run's calls: none if any call gave none. +fn add_reading(total: &mut Option>, reading: Option) { + *total = match (total.take(), reading) { + (None, r) => Some(r), + (Some(Some(a)), Some(b)) => Some(Some(member::Metered { joules_micro: a.joules_micro + b.joules_micro, class: a.class })), + _ => Some(None), + }; } fn execute(job: &Job, plan: Plan, seed: u64, c: &Certified<'_>) -> Result { @@ -298,7 +311,7 @@ fn execute(job: &Job, plan: Plan, seed: u64, c: &Certified<'_>) -> Result run_chains(job, &plan.temperatures(), restarts, seed, c), } @@ -306,19 +319,29 @@ fn execute(job: &Job, plan: Plan, seed: u64, c: &Certified<'_>) -> Result) -> Result { + Ok(chains_for_seeds(job, temperatures, restarts, &[seed], c, true)?.remove(0)) +} + +/// The runs of several seeds, their starts handed to the member together a +/// batch at a time, so that a device can run them side by side. Each seed's run +/// is the one it would be alone: the same chains, reduced in the same order. +/// A reading is kept only where every call covered one run (`alone`). +fn chains_for_seeds(job: &Job, temperatures: &[f64], restarts: u32, seeds: &[u64], c: &Certified<'_>, alone: bool) -> Result, Refusal> { let w = &job.workload; let n = w.n(); let m = c.member(); let refused = |e: String| Refusal::general(format!("The member {} refused or failed: {e}.", m.id())); - let batch = (CHUNK_CELLS / n.max(1)).clamp(1, CHUNK_CHAINS) as u64; - let (mut accumulates, mut draws) = (0u64, 0u64); - let mut best: Option<(Vec, i64)> = None; - let mut r = 0u64; - while r < u64::from(restarts) { - let end = (r + batch).min(u64::from(restarts)); - let chains: Vec = (r..end) + let batch = (CHUNK_CELLS / n.max(1)).clamp(1, CHUNK_CHAINS); + let r = u64::from(restarts); + // Every (seed, start) in order: seed after seed, start after start. + let total = seeds.len() as u64 * r; + let mut found: Vec<(Option<(Vec, i64)>, u64, u64, Option>)> = vec![(None, 0, 0, None); seeds.len()]; + let mut at = 0u64; + while at < total { + let end = (at + batch as u64).min(total); + let chains: Vec = (at..end) .map(|k| { - let s = sample::chain_seed(seed, k); + let s = sample::chain_seed(seeds[(k / r) as usize], k % r); Chain { start: sample::start(s, n), seed: s } }) .collect(); @@ -326,20 +349,50 @@ fn run_chains(job: &Job, temperatures: &[f64], restarts: u32, seed: u64, c: &Cer if outcomes.len() != chains.len() { return Err(refused(format!("it returned {} chains for {}", outcomes.len(), chains.len()))); } - for o in outcomes { + let reading = m.meter(); + let one_run = alone || (at / r == (end - 1) / r && at % r == 0 && end % r == 0); + for (k, o) in (at..end).zip(outcomes) { member::check_chain(&o, w, temperatures).map_err(refused)?; - accumulates += o.accumulates; - draws += o.draws; + let f = &mut found[(k / r) as usize]; + f.1 += o.accumulates; + f.2 += o.draws; let e = w.energy(&o.state); - if best.as_ref().is_none_or(|b| e < b.1) { - best = Some((o.state, e)); + if f.0.as_ref().is_none_or(|b| e < b.1) { + f.0 = Some((o.state, e)); } } - r = end; + for s in (at / r)..=((end - 1) / r) { + add_reading(&mut found[s as usize].3, if one_run { reading.clone() } else { None }); + } + at = end; } - let (state, _) = best.expect("a run has at least one start"); - let energy = job.program.landscape.energy(&state); - Ok(Found { state, energy, accumulates, draws, sweeps: u64::from(restarts) * temperatures.len() as u64, term: None }) + Ok(found + .into_iter() + .map(|(best, accumulates, draws, metered)| { + let (state, _) = best.expect("a run has at least one start"); + let energy = job.program.landscape.energy(&state); + Found { state, energy, accumulates, draws, sweeps: u64::from(restarts) * temperatures.len() as u64, term: None, metered: metered.flatten() } + }) + .collect()) +} + +/// Every seed's run of a plan, the starts of several seeds handed to the member +/// together. A settle has no randomness: it runs once, and every seed has its answer. +pub(crate) fn execute_seeds(job: &Job, plan: Plan, seeds: &[u64], c: &Certified<'_>) -> Result, Refusal> { + match plan { + Plan::Settle { .. } => { + let f = execute(job, plan, seeds.first().copied().unwrap_or(DEFAULT_SEED), c)?; + Ok(vec![f; seeds.len()]) + } + Plan::Quench { restarts, .. } | Plan::Anneal { restarts, .. } => chains_for_seeds(job, &plan.temperatures(), restarts, seeds, c, seeds.len() == 1), + } +} + +/// How many seeds go to a member together: as many as one batch of chains holds. +pub(crate) fn seeds_a_batch(job: &Job, plan: Plan) -> usize { + let n = job.workload.n().max(1); + let chains = (CHUNK_CELLS / n).clamp(1, CHUNK_CHAINS) as u64; + (chains / plan.restarts().max(1)).max(1) as usize } fn truth_json(t: &Truth) -> Value { @@ -447,10 +500,15 @@ fn declared_json(real: &Weights, p: &Program, state: &[i8], exact: &Exact) -> (V /// Run a program on a member: the answer of `extensions/quantum-landscape.md` §3. pub fn run_on(c: &Certified<'_>, job: &Job, seed: u64, temperature: Option) -> Result { + let plan = Plan::of(&job.program, temperature)?; + let found = execute(job, plan, seed, c)?; + Ok(answer_of(c, job, plan, seed, &found)) +} + +/// The answer of `extensions/quantum-landscape.md` §3, from what a run's kernels left. +pub(crate) fn answer_of(c: &Certified<'_>, job: &Job, plan: Plan, seed: u64, found: &Found) -> Value { let (p, exact) = (&job.program, &job.exact); let m = c.member(); - let plan = Plan::of(p, temperature)?; - let found = execute(job, plan, seed, c)?; let n = p.landscape.len(); let reached = exact.truth.as_ref().map(|t| found.energy as f64 == t.lowest); let (declared, declared_reached, declared_energy) = match &p.declared { @@ -461,7 +519,7 @@ pub fn run_on(c: &Certified<'_>, job: &Job, seed: u64, temperature: Option) None => (None, None, None), }; let checks = grade_all(p, &found.state); - let (verdict, summary) = judge(&found, exact, reached, declared_reached, declared_energy, &checks); + let (verdict, summary) = judge(found, exact, reached, declared_reached, declared_energy, &checks); let groups: Vec = [(&p.notes.plus, 1i8), (&p.notes.minus, -1), (&p.notes.zero, 0)] .iter() .filter_map(|(name, value)| name.as_ref().map(|name| json!({ "name": name, "items": (0..n).filter(|&i| found.state[i] == *value).collect::>() }))) @@ -478,11 +536,11 @@ pub fn run_on(c: &Certified<'_>, job: &Job, seed: u64, temperature: Option) }; let rests = job.workload.rests(&found.state); let mut joules_statement = price::priced(found.accumulates, plan.mode(), found.sweeps); - joules_statement["metered"] = match m.meter() { + joules_statement["metered"] = match &found.metered { Some(r) => json!({ "joules_micro": r.joules_micro, "class": r.class }), None => Value::Null, }; - Ok(json!({ + json!({ "protocol": PROTOCOL, "label": m.label(), "member": member::describe(c, plan.kernel()), @@ -521,7 +579,7 @@ pub fn run_on(c: &Certified<'_>, job: &Job, seed: u64, temperature: Option) "joules": joules_statement, "state_hash": format!("{:016x}", state_hash(&found.state)), "replay": replay_digest(p, seed, plan, &found.state, found.energy), - })) + }) } // ── the temperature sweep ──────────────────────────────────────────────────────────────────── @@ -560,8 +618,12 @@ pub fn sweep_on(c: &Certified<'_>, job: &Job, temperatures: &[f64], seeds: u32, let mut rows = Vec::with_capacity(plans.len()); for (t, plan, runs) in plans { let (mut best, mut sum, mut reached, mut passed, mut accumulates, mut sweeps) = (i64::MAX, 0i128, 0u32, 0u32, 0u64, 0u64); - for k in 0..runs { - let found = execute(job, plan, first_seed.wrapping_add(u64::from(k)), c)?; + let seeds: Vec = (0..runs).map(|k| first_seed.wrapping_add(u64::from(k))).collect(); + let mut founds = Vec::with_capacity(seeds.len()); + for group in seeds.chunks(seeds_a_batch(job, plan)) { + founds.extend(execute_seeds(job, plan, group, c)?); + } + for found in founds { best = best.min(found.energy); sum += i128::from(found.energy); accumulates = found.accumulates; @@ -612,8 +674,12 @@ pub fn compare_on(c: &Certified<'_>, job: &Job, seeds: u32, first_seed: u64, tem for (name, temperature) in [(sampling.as_str(), t0), ("emulator, deterministic", 0.0)] { let plan_here = if temperature > 0.0 { plan } else { Plan::Quench { restarts, sweeps } }; let mut t = (name.to_string(), false, Vec::new(), 0u32, 0u64, 0u64); - for k in 0..seeds { - let found = execute(job, plan_here, first_seed.wrapping_add(u64::from(k)), c)?; + let all: Vec = (0..seeds).map(|k| first_seed.wrapping_add(u64::from(k))).collect(); + let mut founds = Vec::with_capacity(all.len()); + for group in all.chunks(seeds_a_batch(job, plan_here)) { + founds.extend(execute_seeds(job, plan_here, group, c)?); + } + for found in founds { t.2.push(found.energy); t.3 += u32::from(check(&found.state)); t.5 += found.accumulates; diff --git a/wai/wai-quantum/src/quantum_landscape/webgpu.rs b/wai/wai-quantum/src/quantum_landscape/webgpu.rs new file mode 100644 index 000000000..93ac1cf63 --- /dev/null +++ b/wai/wai-quantum/src/quantum_landscape/webgpu.rs @@ -0,0 +1,632 @@ +//! A GPU member of the landscape fabric, through WebGPU: the kernels of +//! `landscape.wgsl` on a platform GPU (Metal, Vulkan or DX12), natively. +//! +//! - **The settle kernel** runs one invocation a cell, one dispatch a sweep, +//! with a one-invocation dispatch after each that records whether the sweep +//! came to rest, closed a two-cycle or reached the bound. Its fields are +//! whole numbers in 32-bit lanes, so it gives the reference's bits. +//! - **The sampling kernel** runs one invocation a chain, its cells in order, +//! the reference's draws (SplitMix64 by counter, in pairs of 32-bit words) +//! and the reference's cut points, which the host works out for every field +//! the workload can give and ships as a table a window of sweeps at a time. +//! So it gives the reference's bits too, and many chains run side by side: +//! the starts of a run, and the seeds of a sweep, a comparison or a replay. +//! - **The coloured kernel** ([`WebGpuLandscape::coloured`]) moves a colour +//! class of cells at once, every chain in one dispatch: no two cells of a +//! class are coupled, but the order is not the reference's, so its parity +//! can only be statistical, and it has to pass the battery to get that. +//! +//! A workload whose field bound or cost does not fit a 32-bit lane, or whose +//! cut-point table for one sweep is larger than [`MAX_TABLE_ENTRIES`], is +//! refused before anything runs, with the reason ([`Member::takes`]): the host +//! then runs it on the next member, the CPU reference, and the answer says so. +//! +//! The member says nothing of its own parity: the host certifies it +//! ([`super::member::certify`]). It is native only: a page's WebGPU is +//! asynchronous, and the member's calls block. + +use super::member::{Chain, Member, Metered}; +use super::sample::cuts; +use super::settle::{Outcome, Term, Workload}; +use crate::quantum_fabric::RunMeter; +use crate::quantum_fabric_webgpu::blocking::resolve; +use std::borrow::Cow; +use std::sync::{Arc, Mutex}; +use std::time::Duration; + +/// The kernels. +pub const SHADER: &str = include_str!("landscape.wgsl"); + +/// The most cut-point entries one sweep's table may hold: a field bound of about two million. +pub const MAX_TABLE_ENTRIES: u64 = 1 << 22; + +/// The work one chain does in one dispatch, at most: cells × (couplings + 1) × sweeps. +const WINDOW_WORK: u64 = 1 << 22; + +/// Sweeps a settle submits before it reads where it got to. +const SETTLE_BATCH: u32 = 64; + +/// How long one wait on the device may take. +const POLL_TIMEOUT: Duration = Duration::from_secs(300); + +/// How the sampling kernel orders its cells. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Order { + /// Each chain's cells one at a time, in order: the reference's order. + InOrder, + /// A colour class at a time: statistical at most. + Coloured, +} + +#[derive(Default)] +struct Sink { + errors: Vec, + lost: Option, +} + +#[derive(Clone)] +struct Pipelines { + settle_sweep: wgpu::ComputePipeline, + settle_advance: wgpu::ComputePipeline, + in_order: wgpu::ComputePipeline, + coloured: wgpu::ComputePipeline, + copy: wgpu::ComputePipeline, +} + +/// One WebGPU adapter as a landscape member. +pub struct WebGpuLandscape { + device: wgpu::Device, + queue: wgpu::Queue, + pipelines: Pipelines, + id: String, + class: &'static str, + adapter: String, + backend: &'static str, + order: Order, + max_binding: u64, + sink: Arc>, + meter: Option>>, + reading: Mutex>, +} + +impl std::fmt::Debug for WebGpuLandscape { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("WebGpuLandscape").field("id", &self.id).field("adapter", &self.adapter).field("order", &self.order).finish() + } +} + +fn lock(m: &Mutex) -> std::sync::MutexGuard<'_, T> { + m.lock().unwrap_or_else(|p| p.into_inner()) +} + +fn backend_label(b: wgpu::Backend) -> Option<&'static str> { + match b { + wgpu::Backend::Metal => Some("metal"), + wgpu::Backend::Vulkan => Some("vulkan"), + wgpu::Backend::Dx12 => Some("dx12"), + _ => None, + } +} + +/// A member id from what the adapter reports: `gpu:..<4 hex>`, at +/// most 64 characters, the hex from the vendor, device and driver. +fn member_id(class: &str, backend: &str, info: &wgpu::AdapterInfo) -> String { + id_of(class, backend, &info.name, &format!("{}/{}/{}/{}", info.vendor, info.device, info.driver, info.driver_info)) +} + +fn id_of(class: &str, backend: &str, name: &str, hardware: &str) -> String { + let mut slug = String::new(); + for c in name.to_lowercase().chars() { + if c.is_ascii_lowercase() || c.is_ascii_digit() { + slug.push(c); + } else if !slug.ends_with('-') && !slug.is_empty() { + slug.push('-'); + } + } + let slug = slug.trim_end_matches('-'); + let mut h = 0xcbf2_9ce4_8422_2325u64; + for b in hardware.bytes() { + h = (h ^ u64::from(b)).wrapping_mul(0x0100_0000_01b3); + } + let room = 64 - class.len() - 1 - backend.len() - 1 - 5; + let slug: String = slug.chars().take(room).collect(); + format!("{class}:{backend}.{}.{:04x}", slug.trim_end_matches('-'), h & 0xffff) +} + +/// Every adapter of the platform's primary GPU interfaces, opened, each a +/// member or the reason it is not one. +pub fn discover() -> Vec> { + if wgpu::Instance::enabled_backend_features().is_empty() { + return Vec::new(); + } + let mut d = wgpu::InstanceDescriptor::new_without_display_handle(); + d.backends = wgpu::Backends::PRIMARY; + d.flags = wgpu::InstanceFlags::empty(); + let instance = wgpu::Instance::new(d); + let Ok(adapters) = resolve(instance.enumerate_adapters(wgpu::Backends::PRIMARY)) else { return Vec::new() }; + adapters.into_iter().map(open).collect() +} + +fn open(adapter: wgpu::Adapter) -> Result { + let info = adapter.get_info(); + let backend = backend_label(info.backend).ok_or_else(|| format!("{}: backend {:?} is not one this member runs on", info.name, info.backend))?; + if !adapter.get_downlevel_capabilities().flags.contains(wgpu::DownlevelFlags::COMPUTE_SHADERS) { + return Err(format!("{}: no compute shaders", info.name)); + } + let offered = adapter.limits(); + let mut short = None; + wgpu::Limits::downlevel_defaults().check_limits_with_fail_fn(&offered, true, |name, needed, has| { + short.get_or_insert_with(|| format!("{}: limit {name}: the kernels need {needed}, the adapter offers {has}", info.name)); + }); + if let Some(why) = short { + return Err(why); + } + let required_limits = wgpu::Limits { max_storage_buffer_binding_size: offered.max_storage_buffer_binding_size, max_buffer_size: offered.max_buffer_size, ..wgpu::Limits::downlevel_defaults() }; + let descriptor = wgpu::DeviceDescriptor { label: Some("wai-quantum landscape"), required_features: wgpu::Features::empty(), required_limits, ..Default::default() }; + let (device, queue) = resolve(adapter.request_device(&descriptor)).map_err(|e| e.to_string())?.map_err(|e| format!("{}: {e}", info.name))?; + let sink = Arc::new(Mutex::new(Sink::default())); + let errors = Arc::clone(&sink); + device.on_uncaptured_error(Arc::new(move |e: wgpu::Error| lock(&errors).errors.push(e.to_string()))); + let lost = Arc::clone(&sink); + device.set_device_lost_callback(move |reason, message| { + let mut s = lock(&lost); + if s.lost.is_none() { + s.lost = Some(format!("device lost ({reason:?}): {message}")); + } + }); + let scope = device.push_error_scope(wgpu::ErrorFilter::Validation); + let module = device.create_shader_module(wgpu::ShaderModuleDescriptor { label: Some("wai-quantum landscape"), source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(SHADER)) }); + let pipeline = |entry: &str| { + device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { label: Some(entry), layout: None, module: &module, entry_point: Some(entry), compilation_options: Default::default(), cache: None }) + }; + let pipelines = Pipelines { + settle_sweep: pipeline("settle_sweep"), + settle_advance: pipeline("settle_advance"), + in_order: pipeline("chains_in_order"), + coloured: pipeline("chains_coloured"), + copy: pipeline("chains_copy"), + }; + if let Some(e) = resolve(scope.pop()).map_err(|e| e.to_string())? { + return Err(format!("{}: the kernels did not compile: {e}", info.name)); + } + let class = if info.device_type == wgpu::DeviceType::Cpu { "cpu" } else { "gpu" }; + let max_binding = device.limits().max_storage_buffer_binding_size; + Ok(WebGpuLandscape { + id: member_id(class, backend, &info), + class, + adapter: info.name.clone(), + backend, + device, + queue, + pipelines, + order: Order::InOrder, + max_binding, + sink, + meter: None, + reading: Mutex::new(None), + }) +} + +fn words(v: &[u32]) -> Vec { + v.iter().flat_map(|w| w.to_le_bytes()).collect() +} + +/// The workload in one buffer of words: rows, columns, values, fields; and +/// where each part starts. +fn graph(w: &Workload) -> (Vec, [u32; 3]) { + let n = w.n(); + let zero = vec![0i8; n]; + let mut rows = Vec::with_capacity(n + 1); + let (mut cols, mut vals) = (Vec::new(), Vec::new()); + rows.push(0u32); + for i in 0..n { + for (j, c) in w.row(i) { + cols.push(j as u32); + vals.push(c as i32 as u32); + } + rows.push(cols.len() as u32); + } + let fields: Vec = (0..n).map(|i| w.field(&zero, i) as i32 as u32).collect(); + let at_cols = rows.len() as u32; + let at_vals = at_cols + cols.len() as u32; + let at_fields = at_vals + vals.len() as u32; + let mut out = rows; + out.extend(cols); + out.extend(vals); + out.extend(fields); + (out, [at_cols, at_vals, at_fields]) +} + +/// The cut points of one sweep, for every field from `−bound` to `bound`. +fn table(cost: i64, t: f64, bound: i64, out: &mut Vec) { + let beta = 1.0 / t; + for h in -bound..=bound { + let (a, b) = cuts(h, cost, beta); + out.extend([a as u32, (a >> 32) as u32, b as u32, (b >> 32) as u32]); + } +} + +/// A greedy colouring of the coupling graph, in order of cell: each cell the +/// lowest colour none of its earlier neighbours has. +fn colouring(w: &Workload) -> Vec> { + let n = w.n(); + let mut colour = vec![usize::MAX; n]; + let mut classes: Vec> = Vec::new(); + for i in 0..n { + let taken: Vec = w.row(i).map(|(j, _)| colour[j]).collect(); + let c = (0..).find(|k| !taken.contains(k)).unwrap_or(0); + colour[i] = c; + if classes.len() <= c { + classes.resize(c + 1, Vec::new()); + } + classes[c].push(i as u32); + } + classes +} + +impl WebGpuLandscape { + /// The same device running the coloured kernel: a member of its own. + pub fn coloured(&self) -> WebGpuLandscape { + let id = format!("{}-coloured", &self.id[..self.id.len().min(55)]); + WebGpuLandscape { + device: self.device.clone(), + queue: self.queue.clone(), + pipelines: self.pipelines.clone(), + id, + class: self.class, + adapter: self.adapter.clone(), + backend: self.backend, + order: Order::Coloured, + max_binding: self.max_binding, + sink: Arc::clone(&self.sink), + meter: None, + reading: Mutex::new(None), + } + } + + /// Read a meter around each call: its reading stands beside the priced figures. + pub fn with_meter(mut self, meter: Box) -> WebGpuLandscape { + self.meter = Some(Mutex::new(meter)); + self + } + + /// The adapter's name, as the platform reports it. + pub fn adapter(&self) -> &str { + &self.adapter + } + + /// `metal`, `vulkan` or `dx12`. + pub fn backend(&self) -> &'static str { + self.backend + } + + pub fn order(&self) -> Order { + self.order + } + + fn settled(&self) -> Result<(), String> { + let mut s = lock(&self.sink); + if let Some(why) = &s.lost { + return Err(why.clone()); + } + if s.errors.is_empty() { Ok(()) } else { Err(std::mem::take(&mut s.errors).join("; ")) } + } + + fn wait(&self, index: wgpu::SubmissionIndex) -> Result<(), String> { + let poll = || self.device.poll(wgpu::PollType::Wait { submission_index: Some(index), timeout: Some(POLL_TIMEOUT) }); + match std::panic::catch_unwind(std::panic::AssertUnwindSafe(poll)) { + Ok(Ok(_)) => Ok(()), + Ok(Err(e)) => Err(format!("the device did not finish: {e}")), + Err(_) => { + let mut s = lock(&self.sink); + s.lost.get_or_insert_with(|| "the device poll failed".into()); + Err("the device poll failed".into()) + } + } + } + + fn buffer(&self, label: &str, bytes: &[u8], usage: wgpu::BufferUsages) -> wgpu::Buffer { + let b = self.device.create_buffer(&wgpu::BufferDescriptor { label: Some(label), size: (bytes.len() as u64).max(16).next_multiple_of(4), usage: usage | wgpu::BufferUsages::COPY_DST, mapped_at_creation: false }); + if !bytes.is_empty() { + self.queue.write_buffer(&b, 0, bytes); + } + b + } + + /// The words of `src`, `count` of them, read back. + fn read(&self, src: &wgpu::Buffer, count: u64) -> Result, String> { + let bytes = count * 4; + let staging = self.device.create_buffer(&wgpu::BufferDescriptor { label: Some("wai-quantum landscape readback"), size: bytes.max(4), usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST, mapped_at_creation: false }); + let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("readback") }); + encoder.copy_buffer_to_buffer(src, 0, &staging, 0, bytes); + let index = self.queue.submit([encoder.finish()]); + let done = Arc::new(Mutex::new(None)); + let filled = Arc::clone(&done); + staging.map_async(wgpu::MapMode::Read, 0..bytes, move |r| *lock(&filled) = Some(r)); + self.wait(index)?; + match lock(&done).take() { + Some(Ok(())) => {} + Some(Err(e)) => return Err(e.to_string()), + None => return Err("the readback did not complete".into()), + } + let out = { + let view = staging.get_mapped_range(0..bytes).map_err(|e| e.to_string())?; + view.chunks_exact(4).map(|w| u32::from_le_bytes([w[0], w[1], w[2], w[3]])).collect() + }; + staging.unmap(); + self.settled()?; + Ok(out) + } + + fn bind(&self, pipeline: &wgpu::ComputePipeline, entries: &[(u32, &wgpu::Buffer)]) -> wgpu::BindGroup { + let layout = pipeline.get_bind_group_layout(0); + let entries: Vec> = entries.iter().map(|&(binding, b)| wgpu::BindGroupEntry { binding, resource: b.as_entire_binding() }).collect(); + self.device.create_bind_group(&wgpu::BindGroupDescriptor { label: Some("wai-quantum landscape"), layout: &layout, entries: &entries }) + } + + fn dispatch(encoder: &mut wgpu::CommandEncoder, pipeline: &wgpu::ComputePipeline, bind: &wgpu::BindGroup, invocations: u64) { + let groups = invocations.div_ceil(64).max(1); + let (x, y) = if groups <= 65_535 { (groups as u32, 1) } else { (65_535, groups.div_ceil(65_535) as u32) }; + let mut pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { label: Some("wai-quantum landscape"), timestamp_writes: None }); + pass.set_pipeline(pipeline); + pass.set_bind_group(0, bind, &[]); + pass.dispatch_workgroups(x, y, 1); + } + + fn shape(w: &Workload, at: [u32; 3], chains: u32, bound: u32, extra: [u32; 9]) -> Vec { + let mut s = vec![w.n() as u32, w.cost() as i32 as u32, chains, bound]; + s.extend(extra[..2].iter()); + s.extend(at); + s.extend(extra[2..].iter()); + words(&s) + } + + /// Whether the workload fits the kernels: every field and the cost in a + /// 32-bit lane, and one sweep's cut points within [`MAX_TABLE_ENTRIES`]. + fn fits(&self, w: &Workload, sampling: bool) -> Result<(), String> { + let bound = w.field_bound(); + if bound > i64::from(i32::MAX) || w.cost() > i64::from(i32::MAX) { + return Err(format!("its fields reach {bound}, beyond the 32-bit lanes this member runs in")); + } + if sampling && (2 * bound as u64 + 1) > MAX_TABLE_ENTRIES { + return Err(format!("its fields reach {bound}, and a sweep's cut points for every field would be more than {MAX_TABLE_ENTRIES} entries")); + } + let n = w.n() as u64; + if 3 * n * 4 > self.max_binding || (w.nonzeros() * 2 + 2 * n + 1) * 4 > self.max_binding { + return Err("the workload is larger than a storage binding on this device".into()); + } + Ok(()) + } + + fn metered(&self, run: impl FnOnce() -> Result) -> Result { + let Some(meter) = &self.meter else { return run() }; + let mut meter = lock(meter); + meter.start()?; + let out = run(); + let reading = meter.stop()?; + *lock(&self.reading) = reading.map(|(joules_micro, class)| Metered { joules_micro, class: serde_json::Value::Object(class.json_fields().into_iter().map(|(k, v)| (k.to_string(), v)).collect()) }); + out + } + + fn settle_on_device(&self, w: &Workload, start: &[i8], bound: u32) -> Result { + self.fits(w, false)?; + let n = w.n(); + let (g, at) = graph(w); + let graph = self.buffer("graph", &words(&g), wgpu::BufferUsages::STORAGE); + let shape = self.buffer("shape", &Self::shape(w, at, 0, bound, [0; 9]), wgpu::BufferUsages::UNIFORM); + let mut ring_words = vec![0u32; 3 * n]; + for (i, &s) in start.iter().enumerate() { + ring_words[i] = i32::from(s) as u32; + } + let ring = self.buffer("ring", &words(&ring_words), wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC); + let status = self.buffer("status", &words(&[0; 4]), wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC); + let sweep = self.bind(&self.pipelines.settle_sweep, &[(0, &graph), (1, &shape), (2, &ring), (3, &status)]); + let advance = self.bind(&self.pipelines.settle_advance, &[(1, &shape), (3, &status)]); + let (mut done, mut ended) = (0u32, 0u32); + while ended == 0 && done < bound { + let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("settle") }); + for _ in 0..SETTLE_BATCH.min(bound - done) { + Self::dispatch(&mut encoder, &self.pipelines.settle_sweep, &sweep, n as u64); + Self::dispatch(&mut encoder, &self.pipelines.settle_advance, &advance, 1); + } + self.queue.submit([encoder.finish()]); + let s = self.read(&status, 4)?; + (done, ended) = (s[0], s[1]); + } + let ring_words = self.read(&ring, 3 * n as u64)?; + let third = (done % 3) as usize * n; + let state: Vec = ring_words[third..third + n].iter().map(|&v| v as i32 as i8).collect(); + let term = match ended { + 1 => Term::FixedPoint, + 2 => Term::TwoCycle, + _ => Term::Bound, + }; + Ok(Outcome { state, sweeps: done, term, accumulates: w.nonzeros() * u64::from(done), draws: 0 }) + } + + fn chains_on_device(&self, w: &Workload, chains: &[Chain], temperatures: &[f64]) -> Result, String> { + let sampling = temperatures.iter().any(|&t| t > 0.0); + self.fits(w, sampling)?; + let n = w.n(); + let count = chains.len(); + if count == 0 { + return Ok(Vec::new()); + } + if (2 * count * n * 4) as u64 > self.max_binding { + return Err("these chains are larger than a storage binding on this device".into()); + } + let bound = w.field_bound(); + let per_sweep = if sampling { 2 * bound as u64 + 1 } else { 0 }; + let work_per_sweep = (n as u64) * (w.nonzeros() / (n as u64).max(1) + 2); + let mut window = (WINDOW_WORK / work_per_sweep.max(1)).max(1); + if per_sweep > 0 { + window = window.min((MAX_TABLE_ENTRIES / per_sweep).max(1)); + } + let (g, at) = graph(w); + let graph = self.buffer("graph", &words(&g), wgpu::BufferUsages::STORAGE); + let mut cell_words = vec![0u32; 2 * count * n]; + for (c, chain) in chains.iter().enumerate() { + if chain.start.len() != n { + return Err("a chain's start is not one cell a variable".into()); + } + for (i, &s) in chain.start.iter().enumerate() { + cell_words[c * n + i] = i32::from(s) as u32; + } + } + let cells = self.buffer("cells", &words(&cell_words), wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC); + drop(cell_words); + let seeds: Vec = chains.iter().flat_map(|c| [c.seed as u32, (c.seed >> 32) as u32]).collect(); + let classes = if self.order == Order::Coloured { colouring(w) } else { Vec::new() }; + let members: Vec = classes.iter().flatten().copied().collect(); + let mut k0 = 0usize; + while k0 < temperatures.len() { + let k1 = (k0 + window as usize).min(temperatures.len()); + // The window's draws: seeds, the sweeps, the colour classes' cells, the table. + let mut d = seeds.clone(); + let at_sweeps = d.len() as u32; + d.resize(d.len() + (k1 - k0), 0); + let at_members = d.len() as u32; + d.extend(&members); + let at_table = d.len() as u32; + let mut entries = 0u32; + for k in k0..k1 { + if temperatures[k] > 0.0 { + d[at_sweeps as usize + k - k0] = 1 + entries; + let mut t = Vec::new(); + table(w.cost(), temperatures[k], bound, &mut t); + entries += (t.len() / 4) as u32; + d.extend(t); + } + } + if (d.len() as u64) * 4 > self.max_binding { + return Err("a window's cut points are larger than a storage binding on this device".into()); + } + let draws = self.buffer("draws", &words(&d), wgpu::BufferUsages::STORAGE); + let b = u32::try_from(bound).unwrap_or(0); + match self.order { + Order::InOrder => { + let shape = self.buffer("shape", &Self::shape(w, at, count as u32, b, [k0 as u32, k1 as u32, at_sweeps, at_table, 0, 0, 0, 0, 0]), wgpu::BufferUsages::UNIFORM); + let bind = self.bind(&self.pipelines.in_order, &[(0, &graph), (1, &shape), (4, &cells), (5, &draws)]); + let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("chains") }); + Self::dispatch(&mut encoder, &self.pipelines.in_order, &bind, count as u64); + self.queue.submit([encoder.finish()]); + } + Order::Coloured => { + for k in k0..k1 { + let kind = d[at_sweeps as usize + k - k0]; + let passes: Vec<(u32, u32)> = if kind == 0 { + vec![(0, 0)] + } else { + let mut first = at_members; + classes + .iter() + .map(|c| { + let p = (first, c.len() as u32); + first += c.len() as u32; + p + }) + .collect() + }; + for (first, len) in passes { + let extra = [k0 as u32, k1 as u32, at_sweeps, at_table, first, len, k as u32, kind, 0]; + let shape = self.buffer("shape", &Self::shape(w, at, count as u32, b, extra), wgpu::BufferUsages::UNIFORM); + let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("coloured") }); + let bind = self.bind(&self.pipelines.coloured, &[(0, &graph), (1, &shape), (4, &cells), (5, &draws)]); + let invocations = if kind == 0 { (count * n) as u64 } else { count as u64 * u64::from(len) }; + Self::dispatch(&mut encoder, &self.pipelines.coloured, &bind, invocations); + if kind == 0 { + let copy = self.bind(&self.pipelines.copy, &[(1, &shape), (4, &cells)]); + Self::dispatch(&mut encoder, &self.pipelines.copy, ©, (count * n) as u64); + } + self.queue.submit([encoder.finish()]); + } + } + } + } + self.settled()?; + k0 = k1; + } + let got = self.read(&cells, (count * n) as u64)?; + let draws_due = (n as u64) * temperatures.iter().filter(|&&t| t > 0.0).count() as u64; + Ok(got + .chunks_exact(n.max(1)) + .take(count) + .map(|c| { + let state: Vec = c.iter().map(|&v| v as i32 as i8).collect(); + let term = if w.rests(&state) { Term::FixedPoint } else { Term::Bound }; + Outcome { state, sweeps: temperatures.len() as u32, term, accumulates: w.nonzeros() * temperatures.len() as u64, draws: draws_due } + }) + .collect()) + } +} + +impl Member for WebGpuLandscape { + fn id(&self) -> String { + self.id.clone() + } + + fn class(&self) -> &'static str { + self.class + } + + fn label(&self) -> String { + let order = match self.order { + Order::InOrder => "its cells in the reference's order", + Order::Coloured => "a colour class of cells at a time", + }; + let joules = if self.meter.is_some() { + "The joule line is this run's operation count priced at rates measured on an FPGA, with a published figure for one integer add beside it for scale, and a reading of this GPU's own energy counter beside those." + } else { + "The joule line is this run's operation count priced at rates measured on an FPGA, with a published figure for one integer add beside it for scale." + }; + format!( + "A classical emulator of the settling model of a planned energy-based processor, running on this machine's GPU, {} through {} ({}), {order}. Energies are computed exactly. {joules} No photonic hardware, custom chip or quantum effect is involved in this run.", + self.adapter, + "WebGPU", + self.backend + ) + } + + fn takes(&self, w: &Workload, sampling: bool) -> Result<(), String> { + self.fits(w, sampling) + } + + fn settle(&self, w: &Workload, start: &[i8], bound: u32) -> Result { + self.metered(|| self.settle_on_device(w, start, bound)) + } + + fn chains(&self, w: &Workload, chains: &[Chain], temperatures: &[f64]) -> Result, String> { + self.metered(|| self.chains_on_device(w, chains, temperatures)) + } + + fn meter(&self) -> Option { + lock(&self.reading).clone() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn an_id_is_a_member_id() { + let id = id_of("gpu", "metal", "Apple M3 Ultra (some driver build)", "4203/1/drv/1.2"); + assert!(id.starts_with("gpu:metal.apple-m3-ultra-some-driver-build."), "{id}"); + assert!(id.len() <= 64); + assert!(id_of("gpu", "vulkan", &"x".repeat(200), "").len() <= 64); + } + + #[test] + fn a_greedy_colouring_never_puts_two_coupled_cells_together() { + let mut l = super::super::energy::Landscape::new(6); + for (a, b) in [(0, 1), (1, 2), (0, 2), (2, 3), (3, 4), (4, 5), (0, 5)] { + l.couple(a, b, 1); + } + let w = l.lower(); + for class in colouring(&w) { + for &i in &class { + assert!(w.row(i as usize).all(|(j, _)| !class.contains(&(j as u32)))); + } + } + } +} diff --git a/wai/wai-quantum/tests/landscape_webgpu.rs b/wai/wai-quantum/tests/landscape_webgpu.rs new file mode 100644 index 000000000..3847d3a57 --- /dev/null +++ b/wai/wai-quantum/tests/landscape_webgpu.rs @@ -0,0 +1,159 @@ +//! The landscape kernels on every WebGPU adapter this machine has +//! (`quantum_landscape::webgpu`), held to the reference as a host holds a member: +//! certified on the conformance set, then every worked example over many seeds, +//! bit for bit. With `WAI_REQUIRE_GPU` set, a machine with no adapter fails. +#![cfg(not(target_arch = "wasm32"))] + +use serde_json::json; +use std::time::Instant; +use wai_quantum::quantum_landscape::energy::Landscape; +use wai_quantum::quantum_landscape::job; +use wai_quantum::quantum_landscape::member::{self, Certified, Chain, CpuReference, KERNEL_SAMPLE, KERNEL_SETTLE, Member, Parity}; +use wai_quantum::quantum_landscape::sample::{chain_seed, schedule, start}; +use wai_quantum::quantum_landscape::webgpu::{self, WebGpuLandscape}; +use wai_quantum::quantum_landscape::MAX_JOB_BYTES; + +fn members() -> Vec { + let mut out = Vec::new(); + for r in webgpu::discover() { + match r { + Ok(m) => out.push(m), + Err(why) => eprintln!("not a member: {why}"), + } + } + if out.is_empty() { + assert!(std::env::var_os("WAI_REQUIRE_GPU").is_none(), "no WebGPU adapter here, and WAI_REQUIRE_GPU is set"); + eprintln!("no WebGPU adapter here: nothing to hold"); + } + out +} + +#[test] +fn every_adapter_is_certified_bit_exact_for_both_kernels() { + for m in members() { + let c = member::certify(&m).unwrap(); + eprintln!("{} ({}, {}): settle {}, sample {}", m.id(), m.adapter(), m.backend(), c.parity(KERNEL_SETTLE).label(), c.parity(KERNEL_SAMPLE).label()); + assert_eq!(c.parity(KERNEL_SETTLE), Parity::BitExact, "{}", m.id()); + assert_eq!(c.parity(KERNEL_SAMPLE), Parity::BitExact, "{}", m.id()); + } +} + +/// Every worked example at three temperatures over 50 seeds, the starts of +/// many seeds on the device together: the reference's bits. +#[test] +fn every_worked_example_gives_the_reference_s_bits() { + let want = job::corpus_bits_on(&Certified::reference(), 50); + for m in members() { + let c = member::certify(&m).unwrap(); + assert_eq!(job::corpus_bits_on(&c, 50), want, "{}", m.id()); + // And a whole answer: only what names the member differs. + let talks = wai_quantum::quantum_landscape::EXAMPLES[0].1; + let req = json!({ "program": talks, "seed": "7" }); + let cpu = job::call("run", &req).unwrap(); + let gpu = job::call_certified(&c, "run", &req, MAX_JOB_BYTES).unwrap(); + for key in ["state", "energy", "verdict", "work", "replay", "truth", "joules"] { + assert_eq!(cpu[key], gpu[key], "{}: {key}", m.id()); + } + assert_eq!(gpu["member"]["id"], m.id()); + assert_eq!(gpu["member"]["parity"], "bit-exact"); + } +} + +#[test] +fn the_coloured_kernel_earns_statistical_and_no_more() { + for m in members() { + let coloured = m.coloured(); + let c = member::certify(&coloured).unwrap(); + for r in wai_quantum::quantum_landscape::conformance::run_battery(&coloured) { + eprintln!("{} {:<22} chi-square {:>8.1} against {:>6.1}: {}", coloured.id(), r.name, r.chi_square, r.critical, if r.passed { "pass" } else { "FAIL" }); + } + assert_eq!(c.parity(KERNEL_SETTLE), Parity::BitExact, "{}", coloured.id()); + assert_eq!(c.parity(KERNEL_SAMPLE), Parity::Statistical, "{}", coloured.id()); + } +} + +/// A field beyond 32 bits is refused before anything runs, and the job runs on +/// the reference, which the answer names, with the refusal beside it. +#[test] +fn a_workload_too_wide_goes_to_the_reference_and_the_answer_says_so() { + for m in members() { + let placed = [member::certify(&m).unwrap(), Certified::reference()]; + let wide = json!({ "program": "vars 2\nfield 0 1099511627776\nagree 0 1\nminimize\n" }); + let a = job::call_placed(&placed, "run", &wide, MAX_JOB_BYTES).unwrap(); + assert_eq!(a["member"]["id"], "cpu:reference"); + assert_eq!(a["refusals"][0]["member"], m.id()); + assert!(a["refusals"][0]["why"].as_str().unwrap().contains("32-bit"), "{a}"); + let narrow = json!({ "program": "vars 2\nfield 0 3\nagree 0 1\nminimize\n" }); + let a = job::call_placed(&placed, "run", &narrow, MAX_JOB_BYTES).unwrap(); + assert_eq!(a["member"]["id"], m.id()); + assert!(a.get("refusals").is_none()); + } +} + +fn sparse(n: usize, degree: usize, seed: u64) -> Landscape { + let mut l = Landscape::new(n); + let mut k = 0u64; + for i in 0..n { + for _ in 0..degree / 2 { + k += 1; + let j = (wai_quantum::quantum_landscape::sample::out(seed, k) % n as u64) as usize; + if j != i { + l.couple(i, j, if k % 3 == 0 { -1 } else { 1 }); + } + } + } + l.set_cost(1); + l +} + +fn time(f: impl FnOnce() -> T) -> (T, f64) { + let t = Instant::now(); + let v = f(); + (v, t.elapsed().as_secs_f64()) +} + +/// Wall-clock seconds on this machine, CPU reference against each adapter, for +/// the same work and the same bits. Run with `--ignored --nocapture`. +#[test] +#[ignore] +fn timings() { + let ms = members(); + let cases = [(256usize, 6usize, 4096usize, 40u32), (1024, 6, 4096, 40), (4096, 8, 1024, 40)]; + for (n, degree, chains, sweeps) in cases { + let l = sparse(n, degree, 0x7177); + let w = l.lower(); + let temperatures = schedule(l.default_temperature(), sweeps); + let cs: Vec = (0..chains as u64).map(|r| { + let c = chain_seed(9, r); + Chain { start: start(c, n), seed: c } + }).collect(); + let (cpu, t_cpu) = time(|| CpuReference.chains(&w, &cs, &temperatures).unwrap()); + println!("chains n={n} degree≈{degree} chains={chains} sweeps={sweeps}: cpu:reference (one thread) {t_cpu:.3} s"); + for m in &ms { + let _ = m.chains(&w, &cs[..64.min(cs.len())], &temperatures); + let (gpu, t) = time(|| m.chains(&w, &cs, &temperatures).unwrap()); + println!(" {} {t:.3} s, the same bits: {}", m.id(), gpu == cpu); + } + } + for n in [1024usize, 4096] { + let l = sparse(n, 32, 0x5e77); + let w = l.lower(); + let from: Vec = start(3, n); + let (cpu, t_cpu) = time(|| CpuReference.settle(&w, &from, 200).unwrap()); + println!("settle n={n} degree≈32: cpu:reference {t_cpu:.3} s ({} sweeps, {:?})", cpu.sweeps, cpu.term); + for m in &ms { + let (gpu, t) = time(|| m.settle(&w, &from, 200).unwrap()); + println!(" {} {t:.3} s, the same bits: {}", m.id(), gpu == cpu); + } + } + let talks = wai_quantum::quantum_landscape::EXAMPLES[0].1; + let req = json!({ "program": talks, "seed": "1", "seeds": 2000 }); + let (cpu, t_cpu) = time(|| job::call("replay", &req).unwrap()); + println!("replay of the talks, 2,000 seeds: cpu:reference {t_cpu:.3} s"); + for m in &ms { + let c = member::certify(m).unwrap(); + let (gpu, t) = time(|| job::call_certified(&c, "replay", &req, MAX_JOB_BYTES).unwrap()); + let same = cpu["lines"].as_str().unwrap().lines().zip(gpu["lines"].as_str().unwrap().lines()).all(|(a, b)| a.split(' ').skip(2).eq(b.split(' ').skip(2))); + println!(" {} {t:.3} s, the same states, energies, verdicts, work and replay digests: {same}", m.id()); + } +} -- 2.54.0 (Apple Git-157) From d9277c8563f217d223693fa55c69d5fcb675b40e Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Fri, 6 Nov 2026 08:19:14 -0500 Subject: [PATCH 2/4] wai-quantum: CUDA and Metal members of the landscape fabric, a meter bridge, and the decline rule MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - quantum_landscape::cuda and quantum_landscape::metal run the settle kernel (one thread a cell, a launch a sweep and one to advance) and the sampling kernel (one thread a chain, its cells in order, the reference's draws and the host's cut points) with 64-bit fields, so a settle of any workload is taken. quantum_landscape::device holds what the GPU members share. WebGPU members are named gpu:wgpu-.… so that none reads as a native backend's. - A member may decline only the vectors whose fields do not fit 32 bits; declining any other is failing it. Tested on the CPU with a stand-in of narrow lanes, and job::call_placed's refusals with it. - wai::sink_meter::RunCounter makes a run meter of an on-chip counter (IOReport's GPU channel, NVML), labelled OnChipCounter with filtering Unknown and the uncertainty its caller declares; it gives no figure for a window under the profile's minimum. The hardware test that reads it is run by hand. - The extension states declining, refusals, batches and the meter's reach; the schema takes refusals; the docs, the READMEs and a changelog entry say what is here; wai-quantum-landscape-devices.yml holds every member on jetson-hub, framework-128gb and the Mac Studio. Co-Authored-By: Claude Opus 5.5 --- .../wai-quantum-landscape-devices.yml | 114 +++++ .github/workflows/wai-quantum-landscape.yml | 16 +- .../2026-11-06-landscape-gpu-members.md | 6 + wai/extensions/quantum-landscape.md | 26 +- wai/wai-quantum/Cargo.toml | 10 +- wai/wai-quantum/README.md | 1 + wai/wai-quantum/README.zh-CN.md | 1 + wai/wai-quantum/docs/landscape.md | 33 ++ wai/wai-quantum/schemas/landscape.v1.json | 57 +++ wai/wai-quantum/src/quantum_landscape.rs | 6 + .../src/quantum_landscape/conformance.rs | 3 +- wai/wai-quantum/src/quantum_landscape/cuda.rs | 282 +++++++++++++ .../src/quantum_landscape/device.rs | 203 +++++++++ wai/wai-quantum/src/quantum_landscape/job.rs | 55 +++ .../src/quantum_landscape/metal.rs | 399 ++++++++++++++++++ wai/wai-quantum/src/quantum_landscape/run.rs | 8 +- .../src/quantum_landscape/webgpu.rs | 91 +--- ...ndscape_webgpu.rs => landscape_devices.rs} | 89 ++-- wai/wai-rs/Cargo.lock | 27 +- wai/wai-rs/Cargo.toml | 11 + wai/wai-rs/src/sink_meter.rs | 99 +++++ wai/wai-rs/tests/NOT-IN-CI.txt | 1 + wai/wai-rs/tests/landscape_meter.rs | 84 ++++ 23 files changed, 1497 insertions(+), 125 deletions(-) create mode 100644 .github/workflows/wai-quantum-landscape-devices.yml create mode 100644 wai/changes/2026-11-06-landscape-gpu-members.md create mode 100644 wai/wai-quantum/src/quantum_landscape/cuda.rs create mode 100644 wai/wai-quantum/src/quantum_landscape/device.rs create mode 100644 wai/wai-quantum/src/quantum_landscape/metal.rs rename wai/wai-quantum/tests/{landscape_webgpu.rs => landscape_devices.rs} (63%) create mode 100644 wai/wai-rs/tests/landscape_meter.rs diff --git a/.github/workflows/wai-quantum-landscape-devices.yml b/.github/workflows/wai-quantum-landscape-devices.yml new file mode 100644 index 000000000..6c881e07a --- /dev/null +++ b/.github/workflows/wai-quantum-landscape-devices.yml @@ -0,0 +1,114 @@ +name: wai-quantum energy-landscape GPU members + +# The energy-landscape kernels on GPUs (wai_quantum::quantum_landscape::{webgpu, cuda, metal}; +# extensions/quantum-landscape.md §5), each device held to the reference as a host holds a member: +# certified through member::certify on the conformance set, bit-exact for the settle kernel and +# the sampling kernel; every worked example at three temperatures over 50 seeds giving the +# reference's bits; the coloured WebGPU kernel earning statistical and no more; and a sampling run +# whose cut points would not fit declined before anything runs and placed on the reference, the +# answer naming the refusal. WAI_REQUIRE_GPU makes a host with no device a failure. The timings +# that follow are wall-clock on that host and are printed, not checked. +# +# - jetson-hub (linux-hw): its laptop GPU through Vulkan and through CUDA, its integrated GPU +# and the software Vulkan driver through WebGPU. +# - framework-128gb (linux-amd): its integrated GPU and the software Vulkan driver through WebGPU. +# - the Mac Studio (macos-m3-ultra): its GPU through WebGPU on Metal, and through Metal itself. + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +on: + pull_request: + paths: + - 'wai/wai-quantum/src/quantum_landscape/**' + - 'wai/wai-quantum/src/quantum_landscape.rs' + - 'wai/wai-quantum/tests/landscape_devices.rs' + - 'wai/wai-quantum/Cargo.toml' + - 'wai/wai-quantum/Cargo.lock' + - 'wai/quantum-conformance/landscape/vectors.json' + - 'rust-toolchain.toml' + - '.github/workflows/wai-quantum-landscape-devices.yml' + schedule: + - cron: '17 8 * * *' + +jobs: + relevant: + name: does this change touch it + runs-on: linux + outputs: + run: ${{ steps.gate.outputs.run }} + steps: + - uses: actions/checkout@v5 + with: + fetch-depth: 0 + - id: gate + run: | + set -euo pipefail + r=$(python3 scripts/pr-touches-paths.py .github/workflows/wai-quantum-landscape-devices.yml \ + "${{ github.event.pull_request.base.sha }}" \ + "${{ github.event.pull_request.head.sha }}") + echo "this change touches the landscape GPU members: $r" + echo "run=$r" >> "$GITHUB_OUTPUT" + + devices: + name: members · ${{ matrix.host }} + needs: relevant + if: needs.relevant.outputs.run == 'true' + runs-on: ${{ matrix.runner }} + timeout-minutes: 90 + strategy: + fail-fast: false + matrix: + include: + - host: jetson-hub + runner: linux-hw + features: quantum_landscape_webgpu,quantum_landscape_cuda + - host: framework-128gb + runner: linux-amd + features: quantum_landscape_webgpu + - host: mac-studio + runner: macos-m3-ultra + features: quantum_landscape_webgpu,quantum_landscape_metal + env: + WAI_REQUIRE_GPU: '1' + CARGO_PROFILE_DEV_DEBUG: '0' + CARGO_BUILD_JOBS: '8' + steps: + - name: Fetch the source + shell: bash + run: | + set -euo pipefail + want="${{ github.event.pull_request.head.sha || github.sha }}" + rm -rf src && mkdir src && cd src + git init -q + # Fetch the commit itself, not the ref. The host names its own route to the forge. + git fetch -q --depth 1 "${WAI_FORGE_URL:-http://100.64.224.0:3400}/Transaction-Science/open-standards.git" "$want" + git checkout -q FETCH_HEAD + got="$(git rev-parse HEAD)" + [ "$got" = "$want" ] || { echo "::error::fetched $got, not $want"; exit 1; } + # A module filter is satisfied by any one test, so the count is pinned and each test named. + - name: Every member, certified and held to the reference + shell: bash + working-directory: src/wai/wai-quantum + run: | + set -euo pipefail + export PATH="$HOME/.cargo/bin:$PATH" + CARGO_TARGET_DIR="$HOME/hw-target/wai-quantum-landscape" nice -n 10 \ + cargo test --locked --release --features "${{ matrix.features }}" --test landscape_devices -- --test-threads=1 --nocapture 2>&1 | tee "$RUNNER_TEMP/devices.log" + grep -qE '^test result: ok\. 4 passed; 0 failed' "$RUNNER_TEMP/devices.log" || { echo "::error::not every device test ran and passed (4 expected)"; exit 1; } + for t in every_device_is_certified_bit_exact_for_both_kernels \ + every_worked_example_gives_the_reference_s_bits \ + the_coloured_kernel_earns_statistical_and_no_more \ + a_workload_a_device_declines_goes_to_the_reference_and_the_answer_says_so; do + grep -q "^test $t \.\.\. ok$" "$RUNNER_TEMP/devices.log" || { echo "::error::$t did not run and pass"; exit 1; } + done + grep -E ': settle bit-exact, sample bit-exact$' "$RUNNER_TEMP/devices.log" || { echo "::error::no member was certified"; exit 1; } + - name: Timings, wall-clock on this host (printed, not checked) + shell: bash + working-directory: src/wai/wai-quantum + run: | + set -euo pipefail + export PATH="$HOME/.cargo/bin:$PATH" + CARGO_TARGET_DIR="$HOME/hw-target/wai-quantum-landscape" nice -n 10 \ + cargo test --locked --release --features "${{ matrix.features }}" --test landscape_devices -- --ignored --nocapture --test-threads=1 timings 2>&1 | grep -E 'chains n=|settle n=|replay|^ ' diff --git a/.github/workflows/wai-quantum-landscape.yml b/.github/workflows/wai-quantum-landscape.yml index 7a7fc3bb8..b516d774a 100644 --- a/.github/workflows/wai-quantum-landscape.yml +++ b/.github/workflows/wai-quantum-landscape.yml @@ -36,6 +36,7 @@ on: - 'wai/wai-quantum-cli/Cargo.toml' - 'wai/wai-quantum-cli/Cargo.lock' - 'wai/wai-web/src/quantum_engine.rs' + - 'wai/wai-rs/src/sink_meter.rs' - 'wai/wai-web/Cargo.toml' - 'wai/wai-web/Cargo.lock' - 'wai/wai-web/demo/verify_landscape_wasm.mjs' @@ -61,6 +62,7 @@ on: - 'wai/wai-quantum-cli/Cargo.toml' - 'wai/wai-quantum-cli/Cargo.lock' - 'wai/wai-web/src/quantum_engine.rs' + - 'wai/wai-rs/src/sink_meter.rs' - 'wai/wai-web/Cargo.toml' - 'wai/wai-web/Cargo.lock' - 'wai/wai-web/demo/verify_landscape_wasm.mjs' @@ -111,7 +113,7 @@ jobs: run: | set -euo pipefail cargo test --locked --release --features full --lib -- quantum_landscape:: 2>&1 | tee "$RUNNER_TEMP/landscape.log" - grep -qE '^test result: ok\. 83 passed; 0 failed' "$RUNNER_TEMP/landscape.log" || { echo "::error::the landscape tests did not all pass (83 expected)"; exit 1; } + grep -qE '^test result: ok\. 85 passed; 0 failed' "$RUNNER_TEMP/landscape.log" || { echo "::error::the landscape tests did not all pass (85 expected)"; exit 1; } for t in quantum_landscape::classical::tests::a_solver_keeps_to_its_budget_and_repeats_itself \ quantum_landscape::classical::tests::each_solver_finds_the_exact_lowest_on_small_instances_with_a_generous_budget \ quantum_landscape::classical::tests::with_no_budget_a_solver_misses \ @@ -124,6 +126,8 @@ jobs: quantum_landscape::energy::tests::the_lowered_energy_is_the_declared_one \ quantum_landscape::energy::tests::the_regime_reads_the_cost_against_the_couplings \ quantum_landscape::job::tests::a_bad_request_is_refused_plainly \ + quantum_landscape::job::tests::a_job_goes_to_the_first_member_that_takes_it \ + quantum_landscape::job::tests::a_member_may_decline_only_what_does_not_fit_32_bits \ quantum_landscape::job::tests::a_sealed_answer_verifies_and_follows_its_schema \ quantum_landscape::job::tests::a_seed_keeps_all_64_bits \ quantum_landscape::job::tests::a_temperature_in_text_keeps_every_bit \ @@ -212,6 +216,14 @@ jobs: set -euo pipefail cargo test --locked --release --no-default-features --features quantum_landscape --test landscape_float_read 2>&1 | tee "$RUNNER_TEMP/float.log" grep -qE '^test result: ok\. 1 passed; 0 failed' "$RUNNER_TEMP/float.log" || { echo "::error::the float reading test did not pass"; exit 1; } + # The bridge that makes a run meter of an on-chip counter, which a GPU member reads. + - name: The meter bridge (wai-rs sink_meter::RunCounter) + shell: bash + working-directory: wai/wai-rs + run: | + set -euo pipefail + cargo test --locked --release --no-default-features --features sink_energy,quantum_fabric --lib -- sink_meter::tests::a_counter_meters_a_run_and_says_what_it_read 2>&1 | tee "$RUNNER_TEMP/meter.log" + grep -qE '^test result: ok\. 1 passed; 0 failed' "$RUNNER_TEMP/meter.log" || { echo "::error::the meter bridge's test did not run and pass"; exit 1; } - name: The command line's verb shell: bash working-directory: wai/wai-quantum-cli @@ -320,7 +332,7 @@ jobs: run: | set -euo pipefail cargo test --locked --target wasm32-wasip2 --no-default-features --features quantum_engine,quantum_receipt --lib -- quantum_landscape:: 2>&1 | tee "$RUNNER_TEMP/wasip2.log" - grep -qE '^test result: ok\. 82 passed; 0 failed' "$RUNNER_TEMP/wasip2.log" || { echo "::error::the landscape tests did not all pass under wasip2 (82 expected)"; exit 1; } + grep -qE '^test result: ok\. 84 passed; 0 failed' "$RUNNER_TEMP/wasip2.log" || { echo "::error::the landscape tests did not all pass under wasip2 (84 expected)"; exit 1; } # The browser build's answers against the command line's, byte for byte, in Node. parity: diff --git a/wai/changes/2026-11-06-landscape-gpu-members.md b/wai/changes/2026-11-06-landscape-gpu-members.md new file mode 100644 index 000000000..f8771743d --- /dev/null +++ b/wai/changes/2026-11-06-landscape-gpu-members.md @@ -0,0 +1,6 @@ +### wai-quantum: energy-landscape jobs on a GPU, through WebGPU, CUDA and Metal + +- **`quantum_landscape_webgpu`, `quantum_landscape_cuda` and `quantum_landscape_metal`** run the settle kernel (one thread a cell, one dispatch a sweep) and the sampling kernel (one thread a chain, its cells in the reference's order, the reference's draws and the host's cut points) on a GPU, each a member the host certifies through `member::certify`: bit-exact for both kernels on every device tried: a desktop GPU through Metal and through WebGPU on Metal, a laptop GPU through CUDA and through Vulkan, two integrated GPUs through Vulkan, and the software Vulkan driver. The WebGPU member's coloured kernel, a colour class of cells at a time, earns `statistical` and no more. +- **A member may decline a workload before anything runs** (`Member::takes`): `job::call_placed` gives the job to the first member that takes it, and the answer names each member that declined, with why, under `refusals`. A member is held to every vector it takes, and may decline only those whose field bound is above 2³¹ − 1. +- **The host hands a member the starts of several seeds at once**, so a sweep, a comparison or a replay runs its chains side by side; each run is the one it would be alone. +- **`wai::sink_meter::RunCounter`** makes a run meter of an on-chip counter (IOReport's GPU channel, NVML), labelled `OnChipCounter`, which a GPU member reads around its calls; its reading stands beside the priced figures for a run whose calls covered it alone. diff --git a/wai/extensions/quantum-landscape.md b/wai/extensions/quantum-landscape.md index d7dd2007d..b9c8f9035 100644 --- a/wai/extensions/quantum-landscape.md +++ b/wai/extensions/quantum-landscape.md @@ -376,6 +376,18 @@ batch of chains at a time (§2.0). What a member gives back, for a settle or each chain: the state, the sweeps, how it ended, the accumulates and the draws. +**Declining.** Before anything runs, a member MAY decline a workload it cannot run, +saying why: a field too wide for its lanes, a table of cut points too large. A host +offered several members gives the job to the first that takes it, and its answer lists +each member that declined, in the order they were offered, under `refusals`, with +why; with no refusal, `refusals` is absent. Where no member takes it, the job is refused. +Offering the reference last gives every job a member. + +**Batches.** A host MAY give a member the starts of several seeds' runs in one call, +up to one batch (§2.0). Each run is the one it would be alone: the same chains, its +best state chosen in the same order. A meter's reading (§4.4) stands for a run only +where every call of that run carried that run alone; elsewhere `metered` is null. + A host MUST check what comes back, and refuse the answer with one plain sentence if any of these does not hold: @@ -392,6 +404,13 @@ only by returning a worse one, and it cannot price itself low. How many sweeps a took before it came to rest can be checked only by running it again: a `bit-exact` certificate is what vouches for it. +The reference implementation has three GPU members besides the CPU reference, each +certified by the host on its own run of the conformance set (informative): through WebGPU +(32-bit lanes; it declines a workload whose field bound or cost is above 2³¹ − 1, and its +coloured kernel can be `statistical` at most), through CUDA and through Metal (64-bit +fields). All three run the sampling kernel one chain a thread, the cells in the reference's +order, with the reference's draws and the host's cut points, and give its bits. + A member names itself `class:name`, with `class` one of the fabric's (`cpu`, `gpu`, `fpga`) and the class it gives, and `name` lower-case letters, digits, `.`, `_` and `-`, at most 64 characters in all. `cpu:reference` is the reference's, and no other member's. @@ -405,10 +424,15 @@ run used (`member.kernel`, `member.parity`). | parity | granted when | |---|---| | `reference` | to the host's own reference kernels, and to nothing else | -| `bit-exact` | settle: the member gives every `settle` vector exactly. Sample: it gives every `chains` vector exactly, each case's chains in one call | +| `bit-exact` | settle: the member gives every `settle` vector it takes exactly. Sample: it gives every `chains` vector it takes exactly, each case's chains in one call. Either way it takes every vector whose field bound is at most 2³¹ − 1 | | `statistical` | sample: not `bit-exact`, and every workload of the `statistical` battery passes (§7.2) | | `uncertified` | anything else, or a member the host was asked to run by name without certifying it | +A member is held to every vector it takes. It MAY decline a vector whose field bound +is above 2³¹ − 1, which a device with 32-bit lanes cannot hold; declining any other +vector is failing it. A host gives a member only the workloads it takes, so a member +certified on fewer vectors is never given one it was not held to. + A settle kernel can earn `bit-exact` on any device. A sampling kernel can, if it uses the reference's draws and order and the cut points of §4.2. diff --git a/wai/wai-quantum/Cargo.toml b/wai/wai-quantum/Cargo.toml index e9ee74bcc..0a6da5f1d 100644 --- a/wai/wai-quantum/Cargo.toml +++ b/wai/wai-quantum/Cargo.toml @@ -14,7 +14,7 @@ readme = "README.md" # quantum-conformance/qasm3/, quantum-conformance/qec-text/, the answer # layer's conformance set and quantum-conformance/formats/) to the crate; # they run in the repository only. -exclude = ["tests/landscape_conformance.rs", "tests/landscape_webgpu.rs", "tests/fabric_expected.rs", "tests/qasm3_conformance.rs", "tests/qec_text_conformance.rs", "tests/answer_conformance.rs", "tests/formats_conformance.rs", "tests/formats_mutation.rs"] +exclude = ["tests/landscape_conformance.rs", "tests/landscape_devices.rs", "tests/fabric_expected.rs", "tests/qasm3_conformance.rs", "tests/qec_text_conformance.rs", "tests/answer_conformance.rs", "tests/formats_conformance.rs", "tests/formats_mutation.rs"] # docs.rs builds with DEFAULT features only, which would document 3 of 21 # modules and hide the entire operational + ML layers. Document everything. @@ -146,6 +146,10 @@ quantum_landscape = ["quantum_qio", "serde_json/float_roundtrip"] # The landscape kernels on a GPU through WebGPU, natively (quantum_landscape::webgpu): the # settle kernel and the sampling kernel bit-exact in 32-bit lanes, and a coloured kernel. quantum_landscape_webgpu = ["quantum_landscape", "quantum_fabric_webgpu"] +# The landscape kernels on an NVIDIA GPU through CUDA (quantum_landscape::cuda), and on an Apple +# GPU through Metal (quantum_landscape::metal): 64-bit fields, both kernels bit-exact. +quantum_landscape_cuda = ["quantum_landscape", "quantum_fabric_cuda"] +quantum_landscape_metal = ["quantum_landscape", "quantum_fabric_metal"] # Union-find decoding in near-linear time over the matching graph. quantum_uf = ["quantum_frame"] # Noisy circuits: quantum trajectories on the state vector, an exact density- @@ -256,8 +260,8 @@ name = "landscape_float_read" required-features = ["quantum_landscape"] [[test]] -name = "landscape_webgpu" -required-features = ["quantum_landscape_webgpu"] +name = "landscape_devices" +required-features = ["quantum_landscape"] [[test]] name = "webgpu_member" diff --git a/wai/wai-quantum/README.md b/wai/wai-quantum/README.md index badcab1a7..c2e1a085e 100644 --- a/wai/wai-quantum/README.md +++ b/wai/wai-quantum/README.md @@ -80,6 +80,7 @@ never creates information. | `quantum_resource_receipt` | a signed, energy-accounted receipt over an estimate, re-checkable bit for bit on any machine | | `quantum_qio` | **quantum-inspired optimisation**: Ising and Max-Cut problems (G-set reader, spin-glass generator), simulated bifurcation in its ballistic, discrete and heated forms (batched eight trials per pass, bit-identical to one at a time), simulated annealing, single-flip descent, an exact Gray-code referee up to 34 spins, and step-to-solution. Reaches the best-known cut on seven G-set instances | | `quantum_landscape` | **energy-landscape jobs**: a program in epuML states an energy over variables of three states (spin-1, with a single-ion cost), run on a classical emulator of the settling model: a deterministic kernel in whole numbers and a sampling kernel of one draw between two cut points, both the same bits on every platform; every arrangement checked up to 16 variables, each lowest one graded against the problem's own check; the joules priced at rates whose sources are named, never measured here; the same instance on simulated annealing, tabu search and greedy descent at the same budget, with the published table; and the seam a GPU or an FPGA board runs behind (`extensions/quantum-landscape.md`). `quantum_engine` and `full` both turn it on | +| `quantum_landscape_webgpu`, `quantum_landscape_cuda`, `quantum_landscape_metal` | **energy-landscape jobs on a GPU**: the settle and sampling kernels through WebGPU (32-bit lanes, and a coloured kernel that can be statistical at most), CUDA and Metal (64-bit fields), each member certified by the host on the conformance set and given only the workloads it takes (`job::call_placed`) | | `quantum_uf` | **union-find decoding in near-linear time** over the same matching graph: event-driven cluster growth on integer weights (a radix heap orders the fill events), merges, and peeling. Measured against exact matching on the same shots: within 0.92–1.18× the failures, up to 77× faster at `d = 11` | | `quantum_dmrg` | **ground states of spin chains by DMRG**: matrix-product operators for nearest-neighbour chains (XXZ Heisenberg in a field, transverse-field Ising), two-site sweeps with Lanczos and density-matrix truncation, entanglement entropies, a symmetric eigensolver, and two exact referees: full diagonalisation up to 22 sites, and the free-fermion solution of the Ising chain at any length. The critical Ising chain is reproduced to about `10⁻¹¹` per site at 200 sites | | `quantum_tdvp` | **time evolution of spin chains by TDVP**: two-site sweeps forward and back, Lanczos exponentials that halve a step they cannot converge, bonds split by singular value decomposition, and every basis completed with null vectors so a product state's small bonds do not hold the evolution back. Measures site and bond observables, entanglement at every bond, energy and the Loschmidt echo. Held to two exact referees: Krylov evolution of all amplitudes up to 20 sites, and the free-fermion solution of an Ising quench at any length. On a 100-site critical quench at bond 64 it stays within `10⁻⁹` of the exact answer until the entanglement fills the bond, and past that its error follows the discarded weight it reports | diff --git a/wai/wai-quantum/README.zh-CN.md b/wai/wai-quantum/README.zh-CN.md index 677db6cb5..6de0769f1 100644 --- a/wai/wai-quantum/README.zh-CN.md +++ b/wai/wai-quantum/README.zh-CN.md @@ -63,6 +63,7 @@ wai-quantum lab qutrit # d = 3,不只是量子比特 | `quantum_resource_receipt` | 针对一次估算的、带能耗核算的签名回执,可在任何机器上逐位重新校验 | | `quantum_qio` | **量子启发式优化**:Ising 与 Max-Cut 问题(G-set 读取器、自旋玻璃生成器),弹道式、离散式和加热式的模拟分岔(每遍批量运行八次试验,与逐次运行的结果逐位一致),模拟退火,单翻转下降,最多 34 个自旋的精确 Gray 码参照,以及求解所需步数(step-to-solution)。在七个 G-set 实例上达到已知最优割 | | `quantum_landscape` | **能量景观作业**:epuML 程序对三态变量(自旋 1,带单离子代价)给出能量,在沉降模型的经典模拟器上运行:整数运算的确定性核,以及在两个切点之间做一次抽样的采样核,两者在每个平台上比特一致;最多 16 个变量时检查每一种排列,并按问题自身的检查为每个最低能量状态评分;焦耳按注明来源的费率定价,这里从不测量;同一实例上以相同预算运行模拟退火、禁忌搜索和贪心下降,并发布结果表;以及 GPU 或 FPGA 板运行所经由的接缝(`extensions/quantum-landscape.md`)。`quantum_engine` 和 `full` 都会启用它 | +| `quantum_landscape_webgpu`、`quantum_landscape_cuda`、`quantum_landscape_metal` | **GPU 上的能量景观作业**:沉降核与采样核分别通过 WebGPU(32 位通道,另有至多只能达到统计一致的着色核)、CUDA 与 Metal(64 位场)运行,每个成员都由主机在一致性集合上认证,且只接收它能承接的工作负载(`job::call_placed`) | | `quantum_uf` | **近线性时间的并查集(union-find)解码**,基于同一个匹配图:在整数权重上由事件驱动的簇生长(用基数堆为填充事件排序)、合并以及剥离(peeling)。在相同的采样上与精确匹配对比:失败次数为其 0.92–1.18 倍,在 `d = 11` 时最多快 77 倍 | | `quantum_dmrg` | **用 DMRG 求自旋链的基态**:最近邻链的矩阵乘积算符(外场中的 XXZ Heisenberg 链、横场 Ising 链),带 Lanczos 和密度矩阵截断的双格点扫描,纠缠熵,一个对称矩阵本征求解器,以及两个精确参照:最多 22 个格点的完全对角化,以及任意长度 Ising 链的自由费米子解。在 200 个格点上,临界 Ising 链的复现精度约为每格点 `10⁻¹¹` | | `quantum_tdvp` | **用 TDVP 计算自旋链的时间演化**:前后往返的双格点扫描,遇到无法收敛的步长就将其减半的 Lanczos 指数运算,用奇异值分解拆分键,并用零空间向量补全每个基,使乘积态的小键不会阻碍演化。测量格点和键上的可观测量、每个键上的纠缠、能量以及 Loschmidt 回波。以两个精确参照为准:最多 20 个格点的全振幅 Krylov 演化,以及任意长度 Ising 淬火的自由费米子解。在键维数 64、100 个格点的临界淬火中,直到纠缠填满键之前,它与精确解的偏差都保持在 `10⁻⁹` 以内;超过之后,其误差随它所报告的舍弃权重变化 | diff --git a/wai/wai-quantum/docs/landscape.md b/wai/wai-quantum/docs/landscape.md index 1699e3f23..83fa73824 100644 --- a/wai/wai-quantum/docs/landscape.md +++ b/wai/wai-quantum/docs/landscape.md @@ -170,6 +170,39 @@ In a page, the browser build answers the same request: `QuantumEngine.landscape( answer with the page's key, and `QuantumEngine.landscape_answer(op, request)` gives the bytes without a seal. `wai-web/demo/landscape.html` is the page. +## On a GPU + +The kernels run on a GPU too, as members the host certifies and checks like any other: + +| feature | member | lanes | parity it can earn | +|---|---|---|---| +| `quantum_landscape_webgpu` | `quantum_landscape::webgpu`: Metal, Vulkan or DX12 through WebGPU | 32-bit: it declines a workload whose fields do not fit | bit-exact for both kernels; its coloured kernel, a colour class of cells at once, statistical at most | +| `quantum_landscape_cuda` | `quantum_landscape::cuda`: an NVIDIA GPU, compiled at run time | 64-bit | bit-exact for both kernels | +| `quantum_landscape_metal` | `quantum_landscape::metal`: an Apple GPU, macOS | 64-bit | bit-exact for both kernels | + +The settle kernel runs one thread a cell and one dispatch a sweep. The sampling kernel runs one +thread a chain, its cells in the reference's order, with the reference's draws and the cut points the +host works out for every field the workload can give: so its bits are the reference's, and many +chains run side by side, the starts of a run and the seeds of a sweep, a comparison or a replay. +A member declines, before anything runs, a sampling run whose cut points for one sweep would be more +than 4,194,304 entries. + +```rust norun=account +use wai_quantum::quantum_landscape::{job, member, webgpu, MAX_JOB_BYTES}; +use serde_json::json; + +let gpu = webgpu::discover().into_iter().find_map(Result::ok).expect("a GPU"); +let placed = [member::certify(&gpu).unwrap(), member::Certified::reference()]; +let answer = job::call_placed(&placed, "run", &json!({ "program": "vars 2\noppose 0 1\nsolve\n" }), MAX_JOB_BYTES).unwrap(); +// answer["member"] names the GPU and the parity the host found; answer["refusals"], if any, +// names each member that declined the job, with why. +``` + +A member can carry a meter: `with_meter` takes the engine's run meter, and `wai::sink_meter::RunCounter` +makes one of a machine's own on-chip counter (the GPU rail through IOReport on macOS, the board through +NVML on Linux), labelled `OnChipCounter`. Its reading stands beside the priced figures in `joules.metered`, +for a run whose calls each covered that run alone and lasted the counter's minimum window. + ## A member of your own A member is what runs the kernels: the trait diff --git a/wai/wai-quantum/schemas/landscape.v1.json b/wai/wai-quantum/schemas/landscape.v1.json index 968df348f..88feb4ba3 100644 --- a/wai/wai-quantum/schemas/landscape.v1.json +++ b/wai/wai-quantum/schemas/landscape.v1.json @@ -627,6 +627,25 @@ "replay": { "type": "string", "description": "BLAKE3 of the protocol, the program, the seed, the plan, the state and its energy." + }, + "refusals": { + "type": "array", + "description": "Members that declined this job before anything ran, each with why, in the order they were offered; absent when the first member took it (extensions/quantum-landscape.md §5).", + "items": { + "type": "object", + "required": [ + "member", + "why" + ], + "properties": { + "member": { + "type": "string" + }, + "why": { + "type": "string" + } + } + } } } }, @@ -768,6 +787,25 @@ } } } + }, + "refusals": { + "type": "array", + "description": "Members that declined this job before anything ran, each with why, in the order they were offered; absent when the first member took it (extensions/quantum-landscape.md §5).", + "items": { + "type": "object", + "required": [ + "member", + "why" + ], + "properties": { + "member": { + "type": "string" + }, + "why": { + "type": "string" + } + } + } } } }, @@ -892,6 +930,25 @@ } } } + }, + "refusals": { + "type": "array", + "description": "Members that declined this job before anything ran, each with why, in the order they were offered; absent when the first member took it (extensions/quantum-landscape.md §5).", + "items": { + "type": "object", + "required": [ + "member", + "why" + ], + "properties": { + "member": { + "type": "string" + }, + "why": { + "type": "string" + } + } + } } } } diff --git a/wai/wai-quantum/src/quantum_landscape.rs b/wai/wai-quantum/src/quantum_landscape.rs index fb7f3290d..92354ddcf 100644 --- a/wai/wai-quantum/src/quantum_landscape.rs +++ b/wai/wai-quantum/src/quantum_landscape.rs @@ -95,8 +95,14 @@ pub mod settle; pub mod table; pub mod trial; pub mod truth; +#[cfg(all(any(feature = "quantum_landscape_webgpu", feature = "quantum_landscape_cuda", feature = "quantum_landscape_metal"), not(target_arch = "wasm32")))] +pub mod device; #[cfg(all(feature = "quantum_landscape_webgpu", not(target_arch = "wasm32")))] pub mod webgpu; +#[cfg(all(feature = "quantum_landscape_cuda", not(target_arch = "wasm32")))] +pub mod cuda; +#[cfg(all(feature = "quantum_landscape_metal", not(target_arch = "wasm32")))] +pub mod metal; /// The protocol of a job and its answer (`extensions/quantum-landscape.md`). pub const PROTOCOL: &str = "wai.quantum.landscape/1"; diff --git a/wai/wai-quantum/src/quantum_landscape/conformance.rs b/wai/wai-quantum/src/quantum_landscape/conformance.rs index 63532243c..8a03c75bb 100644 --- a/wai/wai-quantum/src/quantum_landscape/conformance.rs +++ b/wai/wai-quantum/src/quantum_landscape/conformance.rs @@ -410,7 +410,8 @@ pub fn run_battery(m: &dyn Member) -> Vec { let w = case.landscape.lower(); let expected = case.exact(); let mut counts = vec![0u64; expected.len()]; - let mut ok = true; + // Every workload of the battery fits 32 bits: a member that declines one fails it. + let mut ok = m.takes(&w, true).is_ok(); for chunk in case.chains(k).chunks(4096) { match m.chains(&w, chunk, &case.temperatures) { Ok(got) if got.len() == chunk.len() && got.iter().all(|o| check_chain(o, &w, &case.temperatures).is_ok()) => { diff --git a/wai/wai-quantum/src/quantum_landscape/cuda.rs b/wai/wai-quantum/src/quantum_landscape/cuda.rs new file mode 100644 index 000000000..4fdd0eea4 --- /dev/null +++ b/wai/wai-quantum/src/quantum_landscape/cuda.rs @@ -0,0 +1,282 @@ +//! A CUDA member of the landscape fabric: the kernels of [`SOURCE`] on an +//! NVIDIA GPU, compiled at run time for the device's architecture. +//! +//! The kernels are those of `landscape.wgsl` with native 64-bit words: the +//! settle kernel one thread a cell, one launch a sweep and one to advance; the +//! sampling kernel one thread a chain, its cells in order, the reference's +//! draws and the host's cut points. Fields are 64-bit, so a settle of any +//! workload the extension allows is taken; a sampling run is declined only +//! when one sweep's cut points would be more than +//! [`device::MAX_TABLE_ENTRIES`]. Every launch is on one stream, so each sees +//! what the one before it wrote. +//! +//! The driver and the runtime compiler are loaded when first asked for, so a +//! build with this feature runs on a host with no CUDA and finds no member. +//! The member says nothing of its own parity: the host certifies it. + +use super::device::{self, MeterSlot, SETTLE_BATCH}; +use super::member::{Chain, Member, Metered}; +use super::settle::{Outcome, Workload}; +use crate::quantum_fabric::RunMeter; +use cudarc::driver::{CudaContext, CudaFunction, CudaStream, LaunchConfig, PushKernelArg}; +use cudarc::nvrtc::{CompileOptions, compile_ptx_with_opts}; +use std::sync::Arc; + +/// The kernels, in CUDA C. +pub const SOURCE: &str = r#" +typedef unsigned long long u64; +typedef long long i64; + +__device__ __forceinline__ int threshold(i64 h, i64 c) { + return h > c ? 1 : (h < -c ? -1 : 0); +} + +__device__ __forceinline__ i64 field_of(const unsigned* rows, const unsigned* cols, const int* vals, const i64* fields, const int* s, unsigned i) { + i64 h = fields[i]; + for (unsigned k = rows[i]; k < rows[i + 1]; ++k) { + h += (i64)vals[k] * (i64)s[cols[k]]; + } + return h; +} + +extern "C" __global__ void settle_sweep(const unsigned* rows, const unsigned* cols, const int* vals, const i64* fields, i64 cost, unsigned n, int* ring, unsigned* status) { + unsigned i = blockIdx.x * blockDim.x + threadIdx.x; + volatile unsigned* st = status; + if (i >= n || st[1] != 0u) return; + unsigned done = st[0]; + unsigned now = (done % 3u) * n, next = ((done + 1u) % 3u) * n, back = ((done + 2u) % 3u) * n; + int s = threshold(field_of(rows, cols, vals, fields, ring + now, i), cost); + ring[next + i] = s; + if (s != ring[now + i]) atomicOr(&status[2], 1u); + if (s != ring[back + i]) atomicOr(&status[3], 1u); +} + +extern "C" __global__ void settle_advance(unsigned bound, unsigned* status) { + if (blockIdx.x != 0 || threadIdx.x != 0 || status[1] != 0u) return; + unsigned k = status[0] + 1u; + status[0] = k; + if (status[2] == 0u) status[1] = 1u; + else if (k >= 2u && status[3] == 0u) status[1] = 2u; + else if (k >= bound) status[1] = 3u; + status[2] = 0u; + status[3] = 0u; +} + +__device__ __forceinline__ u64 splitmix(u64 s, u64 j) { + u64 x = s + j * 0x9E3779B97F4A7C15ull; + x ^= x >> 30; x *= 0xBF58476D1CE4E5B9ull; + x ^= x >> 27; x *= 0x94D049BB133111EBull; + return x ^ (x >> 31); +} + +extern "C" __global__ void chains_in_order(const unsigned* rows, const unsigned* cols, const int* vals, const i64* fields, i64 cost, unsigned n, unsigned chains, i64 bound, unsigned k0, unsigned k1, const u64* seeds, const unsigned* sweeps, const u64* table, int* cells) { + unsigned c = blockIdx.x * blockDim.x + threadIdx.x; + if (c >= chains) return; + int* s = cells + (u64)c * n; + int* spare = cells + (u64)chains * n + (u64)c * n; + u64 seed = seeds[c]; + for (unsigned k = k0; k < k1; ++k) { + unsigned kind = sweeps[k - k0]; + if (kind == 0u) { + for (unsigned i = 0; i < n; ++i) spare[i] = threshold(field_of(rows, cols, vals, fields, s, i), cost); + for (unsigned i = 0; i < n; ++i) s[i] = spare[i]; + } else { + for (unsigned i = 0; i < n; ++i) { + i64 h = field_of(rows, cols, vals, fields, s, i); + u64 u = splitmix(seed, (u64)k * n + i + 1) >> 11; + u64 e = (u64)(kind - 1u) + (u64)(h + bound); + s[i] = u < table[2 * e] ? -1 : (u < table[2 * e + 1] ? 0 : 1); + } + } + } +} +"#; + +/// Threads a block. +const BLOCK: u32 = 256; + +fn launch(work: u64) -> LaunchConfig { + LaunchConfig { grid_dim: (work.div_ceil(u64::from(BLOCK)).max(1) as u32, 1, 1), block_dim: (BLOCK, 1, 1), shared_mem_bytes: 0 } +} + +struct Kernels { + settle_sweep: CudaFunction, + settle_advance: CudaFunction, + chains: CudaFunction, +} + +/// One CUDA device as a landscape member. +pub struct CudaLandscape { + _ctx: Arc, + stream: Arc, + kernels: Kernels, + id: String, + name: String, + meter: MeterSlot, +} + +impl std::fmt::Debug for CudaLandscape { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("CudaLandscape").field("id", &self.id).field("name", &self.name).finish() + } +} + +fn driver(e: cudarc::driver::DriverError) -> String { + format!("the CUDA driver: {e}") +} + +/// Every device the driver lists, opened and compiled for, each a member or +/// the reason it is not one. Nothing, with the reason, where there is no CUDA. +pub fn discover() -> Vec> { + if let Err(why) = crate::quantum_fabric_cuda::available() { + return vec![Err(why)]; + } + match CudaContext::device_count() { + Ok(count) => (0..usize::try_from(count).unwrap_or(0)).map(open).collect(), + Err(e) => vec![Err(driver(e))], + } +} + +fn open(ordinal: usize) -> Result { + let ctx = CudaContext::new(ordinal).map_err(driver)?; + let name = ctx.name().map_err(driver)?; + let (major, minor) = ctx.compute_capability().map_err(driver)?; + let uuid: String = ctx.uuid().map_err(driver)?.bytes.iter().fold(String::new(), |mut s, b| { + s.push_str(&format!("{:02x}", *b as u8)); + s + }); + let arch: &'static str = Box::leak(format!("compute_{major}{minor}").into_boxed_str()); + let opts = CompileOptions { arch: Some(arch), options: vec!["--std=c++17".into(), "--fmad=false".into()], ..Default::default() }; + let ptx = compile_ptx_with_opts(SOURCE, opts).map_err(|e| format!("{name}: the kernels did not compile: {e}"))?; + let module = ctx.load_module(ptx).map_err(driver)?; + let kernels = Kernels { + settle_sweep: module.load_function("settle_sweep").map_err(driver)?, + settle_advance: module.load_function("settle_advance").map_err(driver)?, + chains: module.load_function("chains_in_order").map_err(driver)?, + }; + Ok(CudaLandscape { stream: ctx.default_stream(), _ctx: ctx, kernels, id: device::id_of("gpu", "cuda", &name, &format!("{uuid}/sm_{major}{minor}")), name, meter: MeterSlot::default() }) +} + +impl CudaLandscape { + /// Read a meter around each call: its reading stands beside the priced figures. + pub fn with_meter(mut self, meter: Box) -> CudaLandscape { + self.meter = MeterSlot::new(meter); + self + } + + /// The device's name, as the driver reports it. + pub fn name(&self) -> &str { + &self.name + } + + fn settle_on_device(&self, w: &Workload, start: &[i8], bound: u32) -> Result { + let n = w.n(); + let r = device::rows(w); + let s = &self.stream; + let (rows, cols, vals, fields) = (s.clone_htod(&r.rows).map_err(driver)?, s.clone_htod(&nonempty(&r.cols)).map_err(driver)?, s.clone_htod(&nonempty(&r.vals)).map_err(driver)?, s.clone_htod(&r.fields).map_err(driver)?); + let mut init = vec![0i32; 3 * n]; + for (i, &v) in start.iter().enumerate() { + init[i] = i32::from(v); + } + let mut ring = s.clone_htod(&init).map_err(driver)?; + let mut status = s.alloc_zeros::(4).map_err(driver)?; + let (cost, n32) = (w.cost(), n as u32); + let (mut done, mut ended) = (0u32, 0u32); + while ended == 0 && done < bound { + for _ in 0..SETTLE_BATCH.min(bound - done) { + let mut a = s.launch_builder(&self.kernels.settle_sweep); + a.arg(&rows).arg(&cols).arg(&vals).arg(&fields).arg(&cost).arg(&n32).arg(&mut ring).arg(&mut status); + // SAFETY: the buffers are sized as the kernel reads them: n + 1 rows, the couplings, + // n fields, 3n cells of ring and four status words. + unsafe { a.launch(launch(n as u64)) }.map_err(driver)?; + let mut b = s.launch_builder(&self.kernels.settle_advance); + b.arg(&bound).arg(&mut status); + // SAFETY: four status words. + unsafe { b.launch(launch(1)) }.map_err(driver)?; + } + let st = s.clone_dtoh(&status).map_err(driver)?; + (done, ended) = (st[0], st[1]); + } + let all = s.clone_dtoh(&ring).map_err(driver)?; + let third = (done % 3) as usize * n; + Ok(device::settled(w, done, ended, all[third..third + n].iter().map(|&v| v as i8).collect())) + } + + fn chains_on_device(&self, w: &Workload, chains: &[Chain], temperatures: &[f64]) -> Result, String> { + let sampling = temperatures.iter().any(|&t| t > 0.0); + if sampling { + device::table_fits(w)?; + } + let (n, count) = (w.n(), chains.len()); + if count == 0 { + return Ok(Vec::new()); + } + let r = device::rows(w); + let s = &self.stream; + let (rows, cols, vals, fields) = (s.clone_htod(&r.rows).map_err(driver)?, s.clone_htod(&nonempty(&r.cols)).map_err(driver)?, s.clone_htod(&nonempty(&r.vals)).map_err(driver)?, s.clone_htod(&r.fields).map_err(driver)?); + let mut init = vec![0i32; 2 * count * n]; + for (c, chain) in chains.iter().enumerate() { + if chain.start.len() != n { + return Err("a chain's start is not one cell a variable".into()); + } + for (i, &v) in chain.start.iter().enumerate() { + init[c * n + i] = i32::from(v); + } + } + let mut cells = s.clone_htod(&init).map_err(driver)?; + drop(init); + let seeds = s.clone_htod(&chains.iter().map(|c| c.seed).collect::>()).map_err(driver)?; + let (cost, n32, count32, bound) = (w.cost(), n as u32, count as u32, w.field_bound()); + let window = device::window(w, sampling); + let mut k0 = 0usize; + while k0 < temperatures.len() { + let k1 = (k0 + window).min(temperatures.len()); + let (sweeps, table) = device::window_sweeps(w, &temperatures[k0..k1]); + let (sweeps, table) = (s.clone_htod(&sweeps).map_err(driver)?, s.clone_htod(&nonempty(&table)).map_err(driver)?); + let (a0, a1) = (k0 as u32, k1 as u32); + let mut a = s.launch_builder(&self.kernels.chains); + a.arg(&rows).arg(&cols).arg(&vals).arg(&fields).arg(&cost).arg(&n32).arg(&count32).arg(&bound).arg(&a0).arg(&a1).arg(&seeds).arg(&sweeps).arg(&table).arg(&mut cells); + // SAFETY: 2·count·n cells, a seed a chain, a word a sweep of the window, and the + // window's table of (a, b) pairs for every field from −bound to bound. + unsafe { a.launch(launch(count as u64)) }.map_err(driver)?; + k0 = k1; + } + let got = s.clone_dtoh(&cells).map_err(driver)?; + Ok(device::outcomes(w, &got, count, temperatures)) + } +} + +/// A buffer the driver can make: at least one element. +fn nonempty(v: &[T]) -> Vec { + if v.is_empty() { vec![T::default()] } else { v.to_vec() } +} + +impl Member for CudaLandscape { + fn id(&self) -> String { + self.id.clone() + } + + fn class(&self) -> &'static str { + "gpu" + } + + fn label(&self) -> String { + device::label(&self.name, "CUDA", "its cells in the reference's order", self.meter.is_metered()) + } + + fn takes(&self, w: &Workload, sampling: bool) -> Result<(), String> { + if sampling { device::table_fits(w) } else { Ok(()) } + } + + fn settle(&self, w: &Workload, start: &[i8], bound: u32) -> Result { + self.meter.around(|| self.settle_on_device(w, start, bound)) + } + + fn chains(&self, w: &Workload, chains: &[Chain], temperatures: &[f64]) -> Result, String> { + self.meter.around(|| self.chains_on_device(w, chains, temperatures)) + } + + fn meter(&self) -> Option { + self.meter.last() + } +} diff --git a/wai/wai-quantum/src/quantum_landscape/device.rs b/wai/wai-quantum/src/quantum_landscape/device.rs new file mode 100644 index 000000000..7f30140f8 --- /dev/null +++ b/wai/wai-quantum/src/quantum_landscape/device.rs @@ -0,0 +1,203 @@ +//! What the GPU members of the landscape fabric share: the workload in rows, +//! the cut-point tables the host works out, the windows of sweeps a dispatch +//! runs, the outcomes a member returns, its id, its label and its meter. +//! The kernels themselves are each member's ([`super::webgpu`], [`super::cuda`], +//! [`super::metal`]). + +use super::member::Metered; +use super::sample::cuts; +use super::settle::{Outcome, Term, Workload}; +use crate::quantum_fabric::RunMeter; +use std::sync::Mutex; + +/// The most cut-point entries one sweep's table may hold: a field bound of about two million. +pub const MAX_TABLE_ENTRIES: u64 = 1 << 22; + +/// The work one chain does in one dispatch, at most: cells × (couplings + 1) × sweeps. +pub const WINDOW_WORK: u64 = 1 << 22; + +/// Sweeps a settle submits before it reads where it got to. +pub const SETTLE_BATCH: u32 = 64; + +/// The workload as a device reads it: each cell's first coupling (and one +/// past the last), the couplings' columns and values, and the fields. +pub struct Rows { + pub rows: Vec, + pub cols: Vec, + pub vals: Vec, + pub fields: Vec, +} + +pub fn rows(w: &Workload) -> Rows { + let n = w.n(); + let zero = vec![0i8; n]; + let mut r = Rows { rows: Vec::with_capacity(n + 1), cols: Vec::new(), vals: Vec::new(), fields: (0..n).map(|i| w.field(&zero, i)).collect() }; + r.rows.push(0); + for i in 0..n { + for (j, c) in w.row(i) { + r.cols.push(j as u32); + r.vals.push(c as i32); + } + r.rows.push(r.cols.len() as u32); + } + r +} + +/// One sweep's cut points at temperature `t`, `(a, b)` for every field from +/// `−bound` to `bound`: the reference's own ([`cuts`]). +pub fn cut_pairs(cost: i64, t: f64, bound: i64, out: &mut Vec) { + let beta = 1.0 / t; + for h in -bound..=bound { + let (a, b) = cuts(h, cost, beta); + out.extend([a, b]); + } +} + +/// Whether a sampling run's table fits: one sweep's cut points within [`MAX_TABLE_ENTRIES`]. +pub fn table_fits(w: &Workload) -> Result<(), String> { + let bound = w.field_bound(); + if (2 * bound as u64).saturating_add(1) > MAX_TABLE_ENTRIES { + return Err(format!("its fields reach {bound}, and a sweep's cut points for every field would be more than {MAX_TABLE_ENTRIES} entries")); + } + Ok(()) +} + +/// How many sweeps one dispatch runs: within [`WINDOW_WORK`] for each chain, +/// and within [`MAX_TABLE_ENTRIES`] of cut points. +pub fn window(w: &Workload, sampling: bool) -> usize { + let n = w.n() as u64; + let per_sweep = n * (w.nonzeros() / n.max(1) + 2); + let mut window = (WINDOW_WORK / per_sweep.max(1)).max(1); + if sampling { + window = window.min((MAX_TABLE_ENTRIES / (2 * w.field_bound() as u64 + 1)).max(1)); + } + window as usize +} + +/// The sweeps of a window as a device reads them, and their tables: 0 for a +/// sweep at temperature 0, else 1 + where its table starts, in entries. +pub fn window_sweeps(w: &Workload, temperatures: &[f64]) -> (Vec, Vec) { + let mut sweeps = Vec::with_capacity(temperatures.len()); + let mut table = Vec::new(); + for &t in temperatures { + if t > 0.0 { + sweeps.push(1 + (table.len() / 2) as u32); + cut_pairs(w.cost(), t, w.field_bound(), &mut table); + } else { + sweeps.push(0); + } + } + (sweeps, table) +} + +/// The outcomes of `count` chains from their cells: the state, every sweep +/// run, the work and the draws the schedule comes to, and whether it rests. +pub fn outcomes(w: &Workload, cells: &[i32], count: usize, temperatures: &[f64]) -> Vec { + let n = w.n(); + let draws = (n as u64) * temperatures.iter().filter(|&&t| t > 0.0).count() as u64; + (0..count) + .map(|c| { + let state: Vec = cells[c * n..(c + 1) * n].iter().map(|&v| v as i8).collect(); + let term = if w.rests(&state) { Term::FixedPoint } else { Term::Bound }; + Outcome { state, sweeps: temperatures.len() as u32, term, accumulates: w.nonzeros() * temperatures.len() as u64, draws } + }) + .collect() +} + +/// A settle's outcome from where it ended: the sweeps run, how it ended +/// (1 rest, 2 two-cycle, else the bound) and the state the last sweep computed. +pub fn settled(w: &Workload, done: u32, ended: u32, state: Vec) -> Outcome { + let term = match ended { + 1 => Term::FixedPoint, + 2 => Term::TwoCycle, + _ => Term::Bound, + }; + Outcome { state, sweeps: done, term, accumulates: w.nonzeros() * u64::from(done), draws: 0 } +} + +/// A member id: `:..<4 hex>`, at most 64 characters, the +/// name as the platform reports it in lower case and the hex from what tells +/// two of the same name apart. +pub fn id_of(class: &str, backend: &str, name: &str, hardware: &str) -> String { + let mut slug = String::new(); + for c in name.to_lowercase().chars() { + if c.is_ascii_lowercase() || c.is_ascii_digit() { + slug.push(c); + } else if !slug.ends_with('-') && !slug.is_empty() { + slug.push('-'); + } + } + let mut h = 0xcbf2_9ce4_8422_2325u64; + for b in hardware.bytes() { + h = (h ^ u64::from(b)).wrapping_mul(0x0100_0000_01b3); + } + let room = 64usize.saturating_sub(class.len() + 1 + backend.len() + 1 + 5); + let slug: String = slug.trim_end_matches('-').chars().take(room).collect(); + format!("{class}:{backend}.{}.{:04x}", slug.trim_end_matches('-'), h & 0xffff) +} + +/// What a GPU member's answers say of themselves. +pub fn label(device: &str, through: &str, order: &str, metered: bool) -> String { + let joules = if metered { + "The joule line is this run's operation count priced at rates measured on an FPGA, with a published figure for one integer add beside it for scale, and a reading of this GPU's own energy counter beside those." + } else { + "The joule line is this run's operation count priced at rates measured on an FPGA, with a published figure for one integer add beside it for scale." + }; + format!( + "A classical emulator of the settling model of a planned energy-based processor, running on this machine's GPU, {device}, through {through}, {order}. Energies are computed exactly. {joules} No photonic hardware, custom chip or quantum effect is involved in this run." + ) +} + +/// A member's meter, read around each call, and the last call's reading. +#[derive(Default)] +pub struct MeterSlot { + meter: Option>>, + reading: Mutex>, +} + +impl MeterSlot { + pub fn new(meter: Box) -> MeterSlot { + MeterSlot { meter: Some(Mutex::new(meter)), reading: Mutex::new(None) } + } + + pub fn is_metered(&self) -> bool { + self.meter.is_some() + } + + /// Run `call` inside the meter's window, keeping its reading. + pub fn around(&self, call: impl FnOnce() -> Result) -> Result { + let Some(meter) = &self.meter else { return call() }; + let mut meter = meter.lock().unwrap_or_else(|p| p.into_inner()); + meter.start()?; + let out = call(); + let reading = meter.stop()?; + *self.reading.lock().unwrap_or_else(|p| p.into_inner()) = + reading.map(|(joules_micro, class)| Metered { joules_micro, class: serde_json::Value::Object(class.json_fields().into_iter().map(|(k, v)| (k.to_string(), v)).collect()) }); + out + } + + pub fn last(&self) -> Option { + self.reading.lock().unwrap_or_else(|p| p.into_inner()).clone() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn an_id_is_a_member_id() { + let id = id_of("gpu", "metal", "Apple M3 Ultra (some driver build)", "4203/1/drv/1.2"); + assert!(id.starts_with("gpu:metal.apple-m3-ultra-some-driver-build."), "{id}"); + assert!(id.len() <= 64); + assert!(id_of("gpu", "vulkan", &"x".repeat(200), "").len() <= 64); + } + + #[test] + fn the_table_is_the_reference_s_cut_points() { + let mut t = Vec::new(); + cut_pairs(1, 0.7, 3, &mut t); + assert_eq!(t.len(), 14); + assert_eq!((t[6], t[7]), cuts(0, 1, 1.0 / 0.7)); + } +} diff --git a/wai/wai-quantum/src/quantum_landscape/job.rs b/wai/wai-quantum/src/quantum_landscape/job.rs index c7a1e1645..9cb56db13 100644 --- a/wai/wai-quantum/src/quantum_landscape/job.rs +++ b/wai/wai-quantum/src/quantum_landscape/job.rs @@ -528,6 +528,61 @@ mod tests { assert_eq!(a["rows"][0]["mean_energy"].as_f64(), Some(want), "{}", a["rows"][0]); } + /// A stand-in member with narrow lanes: it declines a workload whose field + /// bound is above `widest`, and runs the reference's kernels otherwise. + struct Narrow(i64); + + impl super::super::member::Member for Narrow { + fn id(&self) -> String { + format!("fpga:narrow-{}", self.0) + } + fn class(&self) -> &'static str { + "fpga" + } + fn label(&self) -> String { + "A board with narrow lanes.".into() + } + fn settle(&self, w: &super::super::settle::Workload, start: &[i8], bound: u32) -> Result { + Ok(w.settle(start, bound)) + } + fn chains(&self, w: &super::super::settle::Workload, chains: &[super::super::member::Chain], temperatures: &[f64]) -> Result, String> { + super::super::member::CpuReference.chains(w, chains, temperatures) + } + fn takes(&self, w: &super::super::settle::Workload, _: bool) -> Result<(), String> { + if w.field_bound() > self.0 { Err(format!("its fields reach {}, beyond {}", w.field_bound(), self.0)) } else { Ok(()) } + } + } + + /// A job goes to the first member that takes it, and the answer names every member that declined. + #[test] + fn a_job_goes_to_the_first_member_that_takes_it() { + use super::super::member::certify; + let lanes32 = Narrow(i64::from(i32::MAX)); + let placed = [certify(&lanes32).unwrap(), Certified::reference()]; + let wide = call_placed(&placed, "run", &json!({ "program": "vars 2\nfield 0 1099511627776\nagree 0 1\nminimize\n" }), MAX_JOB_BYTES).unwrap(); + assert_eq!(wide["member"]["id"], "cpu:reference"); + assert_eq!(wide["refusals"], json!([{ "member": "fpga:narrow-2147483647", "why": "its fields reach 1099511627777, beyond 2147483647" }])); + let narrow = call_placed(&placed, "run", &json!({ "program": example("talks"), "seed": "3" }), MAX_JOB_BYTES).unwrap(); + assert_eq!(narrow["member"]["id"], "fpga:narrow-2147483647"); + assert!(narrow.get("refusals").is_none()); + assert_eq!(narrow["replay"], call("run", &json!({ "program": example("talks"), "seed": "3" })).unwrap()["replay"]); + let none = call_placed(&placed[..1], "run", &json!({ "program": "vars 2\nfield 0 1099511627776\nminimize\n" }), MAX_JOB_BYTES).unwrap_err(); + assert!(none.message.starts_with("No member offered takes this job"), "{}", none.message); + } + + /// A member may decline only the vectors whose fields do not fit 32 bits: + /// declining one that does is failing it. + #[test] + fn a_member_may_decline_only_what_does_not_fit_32_bits() { + use super::super::member::{KERNEL_SAMPLE, KERNEL_SETTLE, Parity, certify}; + let lanes32 = Narrow(i64::from(i32::MAX)); + let c = certify(&lanes32).unwrap(); + assert_eq!((c.parity(KERNEL_SETTLE), c.parity(KERNEL_SAMPLE)), (Parity::BitExact, Parity::BitExact)); + let lanes8 = Narrow(10); + let c = certify(&lanes8).unwrap(); + assert_eq!((c.parity(KERNEL_SETTLE), c.parity(KERNEL_SAMPLE)), (Parity::Uncertified, Parity::Uncertified), "it declines vectors whose fields fit 32 bits"); + } + #[test] fn a_seed_keeps_all_64_bits() { let a = call("run", &json!({ "program": example("chain"), "seed": "18446744073709551615" })).unwrap(); diff --git a/wai/wai-quantum/src/quantum_landscape/metal.rs b/wai/wai-quantum/src/quantum_landscape/metal.rs new file mode 100644 index 000000000..3321e8747 --- /dev/null +++ b/wai/wai-quantum/src/quantum_landscape/metal.rs @@ -0,0 +1,399 @@ +//! A Metal member of the landscape fabric: the kernels of [`SOURCE`] on an +//! Apple GPU, compiled at run time with safe math. +//! +//! The kernels are those of `landscape.wgsl` with native 64-bit words, as the +//! CUDA member's: the settle kernel one thread a cell, one dispatch a sweep and +//! one to advance; the sampling kernel one thread a chain, its cells in order, +//! the reference's draws and the host's cut points. Fields are 64-bit, so a +//! settle of any workload the extension allows is taken; a sampling run is +//! declined only when one sweep's cut points would be more than +//! [`device::MAX_TABLE_ENTRIES`]. The dispatches of a command buffer run one +//! after another, each seeing what the one before it wrote. +//! +//! The member says nothing of its own parity: the host certifies it. On a +//! platform other than macOS there is no device, and [`discover`] says so. + +use super::device::{self, MeterSlot}; +use super::member::{Chain, Member, Metered}; +use super::settle::{Outcome, Workload}; +use crate::quantum_fabric::RunMeter; + +/// The kernels, in the Metal shading language. +pub const SOURCE: &str = r#" +#include +using namespace metal; + +struct Shape { + uint n; + uint chains; + uint bound_sweeps; + uint k0; + uint k1; + uint pad; + long cost; + long bound; +}; + +inline int threshold(long h, long c) { + return h > c ? 1 : (h < -c ? -1 : 0); +} + +inline long field_of(device const uint* rows, device const uint* cols, device const int* vals, device const long* fields, device const int* s, uint i) { + long h = fields[i]; + for (uint k = rows[i]; k < rows[i + 1]; ++k) { + h += long(vals[k]) * long(s[cols[k]]); + } + return h; +} + +kernel void settle_sweep(device const uint* rows [[buffer(0)]], device const uint* cols [[buffer(1)]], device const int* vals [[buffer(2)]], + device const long* fields [[buffer(3)]], constant Shape& sh [[buffer(4)]], device int* ring [[buffer(5)]], + device atomic_uint* status [[buffer(6)]], uint i [[thread_position_in_grid]]) { + if (i >= sh.n || atomic_load_explicit(&status[1], memory_order_relaxed) != 0u) return; + uint n = sh.n; + uint done = atomic_load_explicit(&status[0], memory_order_relaxed); + uint now = (done % 3u) * n, next = ((done + 1u) % 3u) * n, back = ((done + 2u) % 3u) * n; + int s = threshold(field_of(rows, cols, vals, fields, ring + now, i), sh.cost); + ring[next + i] = s; + if (s != ring[now + i]) atomic_fetch_or_explicit(&status[2], 1u, memory_order_relaxed); + if (s != ring[back + i]) atomic_fetch_or_explicit(&status[3], 1u, memory_order_relaxed); +} + +kernel void settle_advance(constant Shape& sh [[buffer(4)]], device atomic_uint* status [[buffer(6)]], uint i [[thread_position_in_grid]]) { + if (i != 0u || atomic_load_explicit(&status[1], memory_order_relaxed) != 0u) return; + uint k = atomic_load_explicit(&status[0], memory_order_relaxed) + 1u; + atomic_store_explicit(&status[0], k, memory_order_relaxed); + if (atomic_load_explicit(&status[2], memory_order_relaxed) == 0u) { + atomic_store_explicit(&status[1], 1u, memory_order_relaxed); + } else if (k >= 2u && atomic_load_explicit(&status[3], memory_order_relaxed) == 0u) { + atomic_store_explicit(&status[1], 2u, memory_order_relaxed); + } else if (k >= sh.bound_sweeps) { + atomic_store_explicit(&status[1], 3u, memory_order_relaxed); + } + atomic_store_explicit(&status[2], 0u, memory_order_relaxed); + atomic_store_explicit(&status[3], 0u, memory_order_relaxed); +} + +inline ulong splitmix(ulong s, ulong j) { + ulong x = s + j * 0x9E3779B97F4A7C15ul; + x ^= x >> 30; x *= 0xBF58476D1CE4E5B9ul; + x ^= x >> 27; x *= 0x94D049BB133111EBul; + return x ^ (x >> 31); +} + +kernel void chains_in_order(device const uint* rows [[buffer(0)]], device const uint* cols [[buffer(1)]], device const int* vals [[buffer(2)]], + device const long* fields [[buffer(3)]], constant Shape& sh [[buffer(4)]], device const ulong* seeds [[buffer(7)]], + device const uint* sweeps [[buffer(8)]], device const ulong* table [[buffer(9)]], device int* cells [[buffer(10)]], + uint c [[thread_position_in_grid]]) { + if (c >= sh.chains) return; + uint n = sh.n; + device int* s = cells + ulong(c) * n; + device int* spare = cells + ulong(sh.chains) * n + ulong(c) * n; + ulong seed = seeds[c]; + for (uint k = sh.k0; k < sh.k1; ++k) { + uint kind = sweeps[k - sh.k0]; + if (kind == 0u) { + for (uint i = 0; i < n; ++i) spare[i] = threshold(field_of(rows, cols, vals, fields, s, i), sh.cost); + for (uint i = 0; i < n; ++i) s[i] = spare[i]; + } else { + for (uint i = 0; i < n; ++i) { + long h = field_of(rows, cols, vals, fields, s, i); + ulong u = splitmix(seed, ulong(k) * n + i + 1) >> 11; + ulong e = ulong(kind - 1u) + ulong(h + sh.bound); + s[i] = u < table[2 * e] ? -1 : (u < table[2 * e + 1] ? 0 : 1); + } + } + } +} +"#; + +/// The uniform the kernels read, as the shading language lays it out. +#[repr(C)] +#[derive(Clone, Copy)] +struct Shape { + n: u32, + chains: u32, + bound_sweeps: u32, + k0: u32, + k1: u32, + pad: u32, + cost: i64, + bound: i64, +} + +/// One Metal device as a landscape member. +pub struct MetalLandscape { + gpu: sys::Gpu, + id: String, + name: String, + meter: MeterSlot, +} + +impl std::fmt::Debug for MetalLandscape { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("MetalLandscape").field("id", &self.id).field("name", &self.name).finish() + } +} + +/// Every Metal device, opened and compiled for, each a member or the reason it is not one. +pub fn discover() -> Vec> { + sys::discover() +} + +impl MetalLandscape { + /// Read a meter around each call: its reading stands beside the priced figures. + pub fn with_meter(mut self, meter: Box) -> MetalLandscape { + self.meter = MeterSlot::new(meter); + self + } + + /// The device's name, as the platform reports it. + pub fn name(&self) -> &str { + &self.name + } + + fn chains_on_device(&self, w: &Workload, chains: &[Chain], temperatures: &[f64]) -> Result, String> { + let sampling = temperatures.iter().any(|&t| t > 0.0); + if sampling { + device::table_fits(w)?; + } + if chains.is_empty() { + return Ok(Vec::new()); + } + if chains.iter().any(|c| c.start.len() != w.n()) { + return Err("a chain's start is not one cell a variable".into()); + } + sys::chains(&self.gpu, w, chains, temperatures, device::window(w, sampling)) + } +} + +impl Member for MetalLandscape { + fn id(&self) -> String { + self.id.clone() + } + + fn class(&self) -> &'static str { + "gpu" + } + + fn label(&self) -> String { + device::label(&self.name, "Metal", "its cells in the reference's order", self.meter.is_metered()) + } + + fn takes(&self, w: &Workload, sampling: bool) -> Result<(), String> { + if sampling { device::table_fits(w) } else { Ok(()) } + } + + fn settle(&self, w: &Workload, start: &[i8], bound: u32) -> Result { + self.meter.around(|| sys::settle(&self.gpu, w, start, bound)) + } + + fn chains(&self, w: &Workload, chains: &[Chain], temperatures: &[f64]) -> Result, String> { + self.meter.around(|| self.chains_on_device(w, chains, temperatures)) + } + + fn meter(&self) -> Option { + self.meter.last() + } +} + +#[cfg(target_os = "macos")] +mod sys { + use super::{MetalLandscape, SOURCE, Shape}; + use crate::quantum_landscape::device::{self, MeterSlot, SETTLE_BATCH}; + use crate::quantum_landscape::member::Chain; + use crate::quantum_landscape::settle::{Outcome, Workload}; + use objc2::rc::{Retained, autoreleasepool}; + use objc2::runtime::ProtocolObject; + use objc2_foundation::{NSError, NSString}; + use objc2_metal::{ + MTLBuffer, MTLCommandBuffer, MTLCommandBufferStatus, MTLCommandEncoder, MTLCommandQueue, MTLCompileOptions, MTLComputeCommandEncoder, MTLComputePipelineState, + MTLCopyAllDevices, MTLDevice, MTLLanguageVersion, MTLLibrary, MTLMathMode, MTLResourceOptions, MTLSize, + }; + use std::ptr::NonNull; + + type Pipeline = Retained>; + type Buffer = Retained>; + type Encoder = ProtocolObject; + + pub struct Gpu { + device: Retained>, + queue: Retained>, + settle_sweep: Pipeline, + settle_advance: Pipeline, + chains: Pipeline, + } + + fn ns_error(e: &NSError) -> String { + e.localizedDescription().to_string() + } + + pub fn discover() -> Vec> { + autoreleasepool(|_| { + let devices = MTLCopyAllDevices(); + if devices.count() == 0 { + return vec![Err("the platform lists no Metal device".into())]; + } + (0..devices.count()).map(|k| open(devices.objectAtIndex(k))).collect() + }) + } + + fn open(device: Retained>) -> Result { + let name = device.name().to_string(); + let queue = device.newCommandQueue().ok_or_else(|| format!("{name}: the device gave no command queue"))?; + let options = MTLCompileOptions::new(); + options.setLanguageVersion(MTLLanguageVersion((3 << 16) as usize)); + options.setMathMode(MTLMathMode::Safe); + let library = device.newLibraryWithSource_options_error(&NSString::from_str(SOURCE), Some(&options)).map_err(|e| format!("{name}: the kernels did not compile: {}", ns_error(&e)))?; + let pipeline = |entry: &str| -> Result { + let f = library.newFunctionWithName(&NSString::from_str(entry)).ok_or_else(|| format!("{name}: no entry point {entry}"))?; + device.newComputePipelineStateWithFunction_error(&f).map_err(|e| format!("{name}: {entry}: {}", ns_error(&e))) + }; + let gpu = Gpu { settle_sweep: pipeline("settle_sweep")?, settle_advance: pipeline("settle_advance")?, chains: pipeline("chains_in_order")?, queue, device }; + let id = device::id_of("gpu", "metal", &name, &format!("{:x}", gpu.device.registryID())); + Ok(MetalLandscape { gpu, id, name, meter: MeterSlot::default() }) + } + + fn upload(gpu: &Gpu, data: &[T]) -> Result { + let bytes = std::mem::size_of_val(data); + let b = gpu.device.newBufferWithLength_options(bytes.max(16), MTLResourceOptions::StorageModeShared).ok_or_else(|| format!("a buffer of {bytes} bytes"))?; + // SAFETY: the buffer holds at least `bytes` bytes, and no command using it has been encoded. + unsafe { std::ptr::copy_nonoverlapping(data.as_ptr().cast::(), b.contents().as_ptr().cast::(), bytes) }; + Ok(b) + } + + fn read(b: &Buffer, n: usize) -> Vec { + let mut out: Vec = Vec::with_capacity(n); + // SAFETY: the buffer was made for at least `n` values of `T`, and the command buffer that wrote it has completed. + unsafe { + std::ptr::copy_nonoverlapping(b.contents().as_ptr().cast::(), out.as_mut_ptr(), n); + out.set_len(n); + } + out + } + + fn submit(gpu: &Gpu, encode: impl FnOnce(&Encoder)) -> Result<(), String> { + let cb = gpu.queue.commandBuffer().ok_or("the queue gave no command buffer")?; + let encoder = cb.computeCommandEncoder().ok_or("the command buffer gave no compute encoder")?; + encode(&encoder); + encoder.endEncoding(); + cb.commit(); + cb.waitUntilCompleted(); + if cb.status() != MTLCommandBufferStatus::Completed { + return Err(format!("the command buffer did not complete: {}", cb.error().map(|e| ns_error(&e)).unwrap_or_default())); + } + Ok(()) + } + + fn bind(e: &Encoder, b: &Buffer, index: usize) { + // SAFETY: each buffer is bound at the index its kernel parameter names, sized for what the kernel reads. + unsafe { e.setBuffer_offset_atIndex(Some(b), 0, index) }; + } + + fn shape(e: &Encoder, s: &Shape) { + // SAFETY: the encoder copies the bytes of `s`, laid out as the kernels' `Shape`. + unsafe { e.setBytes_length_atIndex(NonNull::from(s).cast(), std::mem::size_of::(), 4) }; + } + + fn dispatch(e: &Encoder, p: &Pipeline, threads: u64) { + e.setComputePipelineState(p); + let size = |w: u64| MTLSize { width: w as usize, height: 1, depth: 1 }; + e.dispatchThreadgroups_threadsPerThreadgroup(size(threads.div_ceil(64).max(1)), size(64)); + } + + fn graph(gpu: &Gpu, w: &Workload) -> Result<[Buffer; 4], String> { + let r = device::rows(w); + Ok([upload(gpu, &r.rows)?, upload(gpu, &r.cols)?, upload(gpu, &r.vals)?, upload(gpu, &r.fields)?]) + } + + pub fn settle(gpu: &Gpu, w: &Workload, start: &[i8], bound: u32) -> Result { + autoreleasepool(|_| { + let n = w.n(); + let g = graph(gpu, w)?; + let mut init = vec![0i32; 3 * n]; + for (i, &v) in start.iter().enumerate() { + init[i] = i32::from(v); + } + let ring = upload(gpu, &init)?; + let status = upload(gpu, &[0u32; 4])?; + let s = Shape { n: n as u32, chains: 0, bound_sweeps: bound, k0: 0, k1: 0, pad: 0, cost: w.cost(), bound: 0 }; + let (mut done, mut ended) = (0u32, 0u32); + while ended == 0 && done < bound { + submit(gpu, |e| { + for (k, b) in g.iter().enumerate() { + bind(e, b, k); + } + shape(e, &s); + bind(e, &ring, 5); + bind(e, &status, 6); + for _ in 0..SETTLE_BATCH.min(bound - done) { + dispatch(e, &gpu.settle_sweep, n as u64); + dispatch(e, &gpu.settle_advance, 1); + } + })?; + let st: Vec = read(&status, 4); + (done, ended) = (st[0], st[1]); + } + let all: Vec = read(&ring, 3 * n); + let third = (done % 3) as usize * n; + Ok(device::settled(w, done, ended, all[third..third + n].iter().map(|&v| v as i8).collect())) + }) + } + + pub fn chains(gpu: &Gpu, w: &Workload, chains: &[Chain], temperatures: &[f64], window: usize) -> Result, String> { + autoreleasepool(|_| { + let (n, count) = (w.n(), chains.len()); + let g = graph(gpu, w)?; + let mut init = vec![0i32; 2 * count * n]; + for (c, chain) in chains.iter().enumerate() { + for (i, &v) in chain.start.iter().enumerate() { + init[c * n + i] = i32::from(v); + } + } + let cells = upload(gpu, &init)?; + drop(init); + let seeds = upload(gpu, &chains.iter().map(|c| c.seed).collect::>())?; + let mut k0 = 0usize; + while k0 < temperatures.len() { + let k1 = (k0 + window).min(temperatures.len()); + let (sweeps, table) = device::window_sweeps(w, &temperatures[k0..k1]); + let (sweeps, table) = (upload(gpu, &sweeps)?, upload(gpu, &table)?); + let s = Shape { n: n as u32, chains: count as u32, bound_sweeps: 0, k0: k0 as u32, k1: k1 as u32, pad: 0, cost: w.cost(), bound: w.field_bound() }; + submit(gpu, |e| { + for (k, b) in g.iter().enumerate() { + bind(e, b, k); + } + shape(e, &s); + bind(e, &seeds, 7); + bind(e, &sweeps, 8); + bind(e, &table, 9); + bind(e, &cells, 10); + dispatch(e, &gpu.chains, count as u64); + })?; + k0 = k1; + } + Ok(device::outcomes(w, &read::(&cells, count * n), count, temperatures)) + }) + } +} + +#[cfg(not(target_os = "macos"))] +mod sys { + use super::MetalLandscape; + use crate::quantum_landscape::member::Chain; + use crate::quantum_landscape::settle::{Outcome, Workload}; + + pub enum Gpu {} + + pub fn discover() -> Vec> { + vec![Err("this platform has no Metal; the member runs on macOS".into())] + } + + pub fn settle(gpu: &Gpu, _: &Workload, _: &[i8], _: u32) -> Result { + match *gpu {} + } + + pub fn chains(gpu: &Gpu, _: &Workload, _: &[Chain], _: &[f64], _: usize) -> Result, String> { + match *gpu {} + } +} diff --git a/wai/wai-quantum/src/quantum_landscape/run.rs b/wai/wai-quantum/src/quantum_landscape/run.rs index 2f63def0d..982e59b6b 100644 --- a/wai/wai-quantum/src/quantum_landscape/run.rs +++ b/wai/wai-quantum/src/quantum_landscape/run.rs @@ -322,6 +322,10 @@ fn run_chains(job: &Job, temperatures: &[f64], restarts: u32, seed: u64, c: &Cer Ok(chains_for_seeds(job, temperatures, restarts, &[seed], c, true)?.remove(0)) } +/// One seed's run as its batches come back: the best state and its energy, the +/// accumulates, the draws, and the readings of its calls. +type SeedRun = (Option<(Vec, i64)>, u64, u64, Option>); + /// The runs of several seeds, their starts handed to the member together a /// batch at a time, so that a device can run them side by side. Each seed's run /// is the one it would be alone: the same chains, reduced in the same order. @@ -335,7 +339,7 @@ fn chains_for_seeds(job: &Job, temperatures: &[f64], restarts: u32, seeds: &[u64 let r = u64::from(restarts); // Every (seed, start) in order: seed after seed, start after start. let total = seeds.len() as u64 * r; - let mut found: Vec<(Option<(Vec, i64)>, u64, u64, Option>)> = vec![(None, 0, 0, None); seeds.len()]; + let mut found: Vec = vec![(None, 0, 0, None); seeds.len()]; let mut at = 0u64; while at < total { let end = (at + batch as u64).min(total); @@ -350,7 +354,7 @@ fn chains_for_seeds(job: &Job, temperatures: &[f64], restarts: u32, seeds: &[u64 return Err(refused(format!("it returned {} chains for {}", outcomes.len(), chains.len()))); } let reading = m.meter(); - let one_run = alone || (at / r == (end - 1) / r && at % r == 0 && end % r == 0); + let one_run = alone || (at / r == (end - 1) / r && at.is_multiple_of(r) && end.is_multiple_of(r)); for (k, o) in (at..end).zip(outcomes) { member::check_chain(&o, w, temperatures).map_err(refused)?; let f = &mut found[(k / r) as usize]; diff --git a/wai/wai-quantum/src/quantum_landscape/webgpu.rs b/wai/wai-quantum/src/quantum_landscape/webgpu.rs index 93ac1cf63..e2bf7f0ef 100644 --- a/wai/wai-quantum/src/quantum_landscape/webgpu.rs +++ b/wai/wai-quantum/src/quantum_landscape/webgpu.rs @@ -25,6 +25,7 @@ //! ([`super::member::certify`]). It is native only: a page's WebGPU is //! asynchronous, and the member's calls block. +use super::device::{self, MAX_TABLE_ENTRIES, MeterSlot, SETTLE_BATCH}; use super::member::{Chain, Member, Metered}; use super::sample::cuts; use super::settle::{Outcome, Term, Workload}; @@ -37,15 +38,6 @@ use std::time::Duration; /// The kernels. pub const SHADER: &str = include_str!("landscape.wgsl"); -/// The most cut-point entries one sweep's table may hold: a field bound of about two million. -pub const MAX_TABLE_ENTRIES: u64 = 1 << 22; - -/// The work one chain does in one dispatch, at most: cells × (couplings + 1) × sweeps. -const WINDOW_WORK: u64 = 1 << 22; - -/// Sweeps a settle submits before it reads where it got to. -const SETTLE_BATCH: u32 = 64; - /// How long one wait on the device may take. const POLL_TIMEOUT: Duration = Duration::from_secs(300); @@ -85,8 +77,7 @@ pub struct WebGpuLandscape { order: Order, max_binding: u64, sink: Arc>, - meter: Option>>, - reading: Mutex>, + meter: MeterSlot, } impl std::fmt::Debug for WebGpuLandscape { @@ -108,29 +99,10 @@ fn backend_label(b: wgpu::Backend) -> Option<&'static str> { } } -/// A member id from what the adapter reports: `gpu:..<4 hex>`, at -/// most 64 characters, the hex from the vendor, device and driver. +/// A member id from what the adapter reports ([`device::id_of`]): `gpu:wgpu-.…`, +/// so that it never reads as a member of the native backend of the same name. fn member_id(class: &str, backend: &str, info: &wgpu::AdapterInfo) -> String { - id_of(class, backend, &info.name, &format!("{}/{}/{}/{}", info.vendor, info.device, info.driver, info.driver_info)) -} - -fn id_of(class: &str, backend: &str, name: &str, hardware: &str) -> String { - let mut slug = String::new(); - for c in name.to_lowercase().chars() { - if c.is_ascii_lowercase() || c.is_ascii_digit() { - slug.push(c); - } else if !slug.ends_with('-') && !slug.is_empty() { - slug.push('-'); - } - } - let slug = slug.trim_end_matches('-'); - let mut h = 0xcbf2_9ce4_8422_2325u64; - for b in hardware.bytes() { - h = (h ^ u64::from(b)).wrapping_mul(0x0100_0000_01b3); - } - let room = 64 - class.len() - 1 - backend.len() - 1 - 5; - let slug: String = slug.chars().take(room).collect(); - format!("{class}:{backend}.{}.{:04x}", slug.trim_end_matches('-'), h & 0xffff) + device::id_of(class, &format!("wgpu-{backend}"), &info.name, &format!("{}/{}/{}/{}", info.vendor, info.device, info.driver, info.driver_info)) } /// Every adapter of the platform's primary GPU interfaces, opened, each a @@ -202,8 +174,7 @@ fn open(adapter: wgpu::Adapter) -> Result { order: Order::InOrder, max_binding, sink, - meter: None, - reading: Mutex::new(None), + meter: MeterSlot::default(), }) } @@ -279,14 +250,13 @@ impl WebGpuLandscape { order: Order::Coloured, max_binding: self.max_binding, sink: Arc::clone(&self.sink), - meter: None, - reading: Mutex::new(None), + meter: MeterSlot::default(), } } /// Read a meter around each call: its reading stands beside the priced figures. pub fn with_meter(mut self, meter: Box) -> WebGpuLandscape { - self.meter = Some(Mutex::new(meter)); + self.meter = MeterSlot::new(meter); self } @@ -351,7 +321,7 @@ impl WebGpuLandscape { } let out = { let view = staging.get_mapped_range(0..bytes).map_err(|e| e.to_string())?; - view.chunks_exact(4).map(|w| u32::from_le_bytes([w[0], w[1], w[2], w[3]])).collect() + view.as_chunks::<4>().0.iter().map(|w| u32::from_le_bytes(*w)).collect() }; staging.unmap(); self.settled()?; @@ -398,16 +368,6 @@ impl WebGpuLandscape { Ok(()) } - fn metered(&self, run: impl FnOnce() -> Result) -> Result { - let Some(meter) = &self.meter else { return run() }; - let mut meter = lock(meter); - meter.start()?; - let out = run(); - let reading = meter.stop()?; - *lock(&self.reading) = reading.map(|(joules_micro, class)| Metered { joules_micro, class: serde_json::Value::Object(class.json_fields().into_iter().map(|(k, v)| (k.to_string(), v)).collect()) }); - out - } - fn settle_on_device(&self, w: &Workload, start: &[i8], bound: u32) -> Result { self.fits(w, false)?; let n = w.n(); @@ -456,12 +416,7 @@ impl WebGpuLandscape { return Err("these chains are larger than a storage binding on this device".into()); } let bound = w.field_bound(); - let per_sweep = if sampling { 2 * bound as u64 + 1 } else { 0 }; - let work_per_sweep = (n as u64) * (w.nonzeros() / (n as u64).max(1) + 2); - let mut window = (WINDOW_WORK / work_per_sweep.max(1)).max(1); - if per_sweep > 0 { - window = window.min((MAX_TABLE_ENTRIES / per_sweep).max(1)); - } + let window = device::window(w, sampling) as u64; let (g, at) = graph(w); let graph = self.buffer("graph", &words(&g), wgpu::BufferUsages::STORAGE); let mut cell_words = vec![0u32; 2 * count * n]; @@ -574,17 +529,7 @@ impl Member for WebGpuLandscape { Order::InOrder => "its cells in the reference's order", Order::Coloured => "a colour class of cells at a time", }; - let joules = if self.meter.is_some() { - "The joule line is this run's operation count priced at rates measured on an FPGA, with a published figure for one integer add beside it for scale, and a reading of this GPU's own energy counter beside those." - } else { - "The joule line is this run's operation count priced at rates measured on an FPGA, with a published figure for one integer add beside it for scale." - }; - format!( - "A classical emulator of the settling model of a planned energy-based processor, running on this machine's GPU, {} through {} ({}), {order}. Energies are computed exactly. {joules} No photonic hardware, custom chip or quantum effect is involved in this run.", - self.adapter, - "WebGPU", - self.backend - ) + device::label(&self.adapter, &format!("WebGPU ({})", self.backend), order, self.meter.is_metered()) } fn takes(&self, w: &Workload, sampling: bool) -> Result<(), String> { @@ -592,15 +537,15 @@ impl Member for WebGpuLandscape { } fn settle(&self, w: &Workload, start: &[i8], bound: u32) -> Result { - self.metered(|| self.settle_on_device(w, start, bound)) + self.meter.around(|| self.settle_on_device(w, start, bound)) } fn chains(&self, w: &Workload, chains: &[Chain], temperatures: &[f64]) -> Result, String> { - self.metered(|| self.chains_on_device(w, chains, temperatures)) + self.meter.around(|| self.chains_on_device(w, chains, temperatures)) } fn meter(&self) -> Option { - lock(&self.reading).clone() + self.meter.last() } } @@ -608,14 +553,6 @@ impl Member for WebGpuLandscape { mod tests { use super::*; - #[test] - fn an_id_is_a_member_id() { - let id = id_of("gpu", "metal", "Apple M3 Ultra (some driver build)", "4203/1/drv/1.2"); - assert!(id.starts_with("gpu:metal.apple-m3-ultra-some-driver-build."), "{id}"); - assert!(id.len() <= 64); - assert!(id_of("gpu", "vulkan", &"x".repeat(200), "").len() <= 64); - } - #[test] fn a_greedy_colouring_never_puts_two_coupled_cells_together() { let mut l = super::super::energy::Landscape::new(6); diff --git a/wai/wai-quantum/tests/landscape_webgpu.rs b/wai/wai-quantum/tests/landscape_devices.rs similarity index 63% rename from wai/wai-quantum/tests/landscape_webgpu.rs rename to wai/wai-quantum/tests/landscape_devices.rs index 3847d3a57..da849fff8 100644 --- a/wai/wai-quantum/tests/landscape_webgpu.rs +++ b/wai/wai-quantum/tests/landscape_devices.rs @@ -1,38 +1,49 @@ -//! The landscape kernels on every WebGPU adapter this machine has -//! (`quantum_landscape::webgpu`), held to the reference as a host holds a member: -//! certified on the conformance set, then every worked example over many seeds, -//! bit for bit. With `WAI_REQUIRE_GPU` set, a machine with no adapter fails. +//! The landscape kernels on every GPU this machine has, through each backend +//! this build holds (`quantum_landscape::{webgpu, cuda, metal}`), held to the +//! reference as a host holds a member: certified on the conformance set, then +//! every worked example over many seeds, bit for bit. With `WAI_REQUIRE_GPU` +//! set, a machine where no backend finds a device fails. #![cfg(not(target_arch = "wasm32"))] use serde_json::json; use std::time::Instant; +use wai_quantum::quantum_landscape::MAX_JOB_BYTES; use wai_quantum::quantum_landscape::energy::Landscape; use wai_quantum::quantum_landscape::job; use wai_quantum::quantum_landscape::member::{self, Certified, Chain, CpuReference, KERNEL_SAMPLE, KERNEL_SETTLE, Member, Parity}; use wai_quantum::quantum_landscape::sample::{chain_seed, schedule, start}; -use wai_quantum::quantum_landscape::webgpu::{self, WebGpuLandscape}; -use wai_quantum::quantum_landscape::MAX_JOB_BYTES; -fn members() -> Vec { - let mut out = Vec::new(); - for r in webgpu::discover() { - match r { - Ok(m) => out.push(m), - Err(why) => eprintln!("not a member: {why}"), +/// Every member this build's backends find: each with its backend's name. +#[allow(unused_mut)] +fn members() -> Vec> { + let mut out: Vec> = Vec::new(); + let mut take = |backend: &str, found: Vec, String>>| { + for r in found { + match r { + Ok(m) => out.push(m), + Err(why) => eprintln!("{backend}: not a member: {why}"), + } } - } + }; + #[cfg(feature = "quantum_landscape_webgpu")] + take("webgpu", wai_quantum::quantum_landscape::webgpu::discover().into_iter().map(|r| r.map(|m| Box::new(m) as Box)).collect()); + #[cfg(feature = "quantum_landscape_cuda")] + take("cuda", wai_quantum::quantum_landscape::cuda::discover().into_iter().map(|r| r.map(|m| Box::new(m) as Box)).collect()); + #[cfg(feature = "quantum_landscape_metal")] + take("metal", wai_quantum::quantum_landscape::metal::discover().into_iter().map(|r| r.map(|m| Box::new(m) as Box)).collect()); + let _ = &mut take; if out.is_empty() { - assert!(std::env::var_os("WAI_REQUIRE_GPU").is_none(), "no WebGPU adapter here, and WAI_REQUIRE_GPU is set"); - eprintln!("no WebGPU adapter here: nothing to hold"); + assert!(std::env::var_os("WAI_REQUIRE_GPU").is_none(), "no device here, and WAI_REQUIRE_GPU is set"); + eprintln!("no device here: nothing to hold"); } out } #[test] -fn every_adapter_is_certified_bit_exact_for_both_kernels() { +fn every_device_is_certified_bit_exact_for_both_kernels() { for m in members() { - let c = member::certify(&m).unwrap(); - eprintln!("{} ({}, {}): settle {}, sample {}", m.id(), m.adapter(), m.backend(), c.parity(KERNEL_SETTLE).label(), c.parity(KERNEL_SAMPLE).label()); + let c = member::certify(m.as_ref()).unwrap(); + eprintln!("{}: settle {}, sample {}", m.id(), c.parity(KERNEL_SETTLE).label(), c.parity(KERNEL_SAMPLE).label()); assert_eq!(c.parity(KERNEL_SETTLE), Parity::BitExact, "{}", m.id()); assert_eq!(c.parity(KERNEL_SAMPLE), Parity::BitExact, "{}", m.id()); } @@ -44,9 +55,8 @@ fn every_adapter_is_certified_bit_exact_for_both_kernels() { fn every_worked_example_gives_the_reference_s_bits() { let want = job::corpus_bits_on(&Certified::reference(), 50); for m in members() { - let c = member::certify(&m).unwrap(); + let c = member::certify(m.as_ref()).unwrap(); assert_eq!(job::corpus_bits_on(&c, 50), want, "{}", m.id()); - // And a whole answer: only what names the member differs. let talks = wai_quantum::quantum_landscape::EXAMPLES[0].1; let req = json!({ "program": talks, "seed": "7" }); let cpu = job::call("run", &req).unwrap(); @@ -59,9 +69,11 @@ fn every_worked_example_gives_the_reference_s_bits() { } } +#[cfg(feature = "quantum_landscape_webgpu")] #[test] fn the_coloured_kernel_earns_statistical_and_no_more() { - for m in members() { + for r in wai_quantum::quantum_landscape::webgpu::discover() { + let Ok(m) = r else { continue }; let coloured = m.coloured(); let c = member::certify(&coloured).unwrap(); for r in wai_quantum::quantum_landscape::conformance::run_battery(&coloured) { @@ -72,18 +84,19 @@ fn the_coloured_kernel_earns_statistical_and_no_more() { } } -/// A field beyond 32 bits is refused before anything runs, and the job runs on -/// the reference, which the answer names, with the refusal beside it. +/// A sampling run whose cut points would not fit a table is declined before +/// anything runs, and the job runs on the reference, which the answer names, +/// with each refusal beside it. A narrow one runs on the device. #[test] -fn a_workload_too_wide_goes_to_the_reference_and_the_answer_says_so() { +fn a_workload_a_device_declines_goes_to_the_reference_and_the_answer_says_so() { for m in members() { - let placed = [member::certify(&m).unwrap(), Certified::reference()]; - let wide = json!({ "program": "vars 2\nfield 0 1099511627776\nagree 0 1\nminimize\n" }); + let placed = [member::certify(m.as_ref()).unwrap(), Certified::reference()]; + let wide = json!({ "program": "vars 2\nfield 0 1099511627776\nagree 0 1\nsolve\n" }); let a = job::call_placed(&placed, "run", &wide, MAX_JOB_BYTES).unwrap(); assert_eq!(a["member"]["id"], "cpu:reference"); assert_eq!(a["refusals"][0]["member"], m.id()); - assert!(a["refusals"][0]["why"].as_str().unwrap().contains("32-bit"), "{a}"); - let narrow = json!({ "program": "vars 2\nfield 0 3\nagree 0 1\nminimize\n" }); + assert!(!a["refusals"][0]["why"].as_str().unwrap().is_empty(), "{a}"); + let narrow = json!({ "program": "vars 2\nfield 0 3\nagree 0 1\nsolve\n" }); let a = job::call_placed(&placed, "run", &narrow, MAX_JOB_BYTES).unwrap(); assert_eq!(a["member"]["id"], m.id()); assert!(a.get("refusals").is_none()); @@ -112,21 +125,23 @@ fn time(f: impl FnOnce() -> T) -> (T, f64) { (v, t.elapsed().as_secs_f64()) } -/// Wall-clock seconds on this machine, CPU reference against each adapter, for -/// the same work and the same bits. Run with `--ignored --nocapture`. +/// Wall-clock seconds on this machine, the CPU reference on one thread against +/// each device, for the same work, with whether the bits are the same. Run +/// with `--ignored --nocapture`. #[test] #[ignore] fn timings() { let ms = members(); - let cases = [(256usize, 6usize, 4096usize, 40u32), (1024, 6, 4096, 40), (4096, 8, 1024, 40)]; - for (n, degree, chains, sweeps) in cases { + for (n, degree, chains, sweeps) in [(256usize, 6usize, 4096usize, 40u32), (1024, 6, 4096, 40), (4096, 8, 1024, 40)] { let l = sparse(n, degree, 0x7177); let w = l.lower(); let temperatures = schedule(l.default_temperature(), sweeps); - let cs: Vec = (0..chains as u64).map(|r| { - let c = chain_seed(9, r); - Chain { start: start(c, n), seed: c } - }).collect(); + let cs: Vec = (0..chains as u64) + .map(|r| { + let c = chain_seed(9, r); + Chain { start: start(c, n), seed: c } + }) + .collect(); let (cpu, t_cpu) = time(|| CpuReference.chains(&w, &cs, &temperatures).unwrap()); println!("chains n={n} degree≈{degree} chains={chains} sweeps={sweeps}: cpu:reference (one thread) {t_cpu:.3} s"); for m in &ms { @@ -151,7 +166,7 @@ fn timings() { let (cpu, t_cpu) = time(|| job::call("replay", &req).unwrap()); println!("replay of the talks, 2,000 seeds: cpu:reference {t_cpu:.3} s"); for m in &ms { - let c = member::certify(m).unwrap(); + let c = member::certify(m.as_ref()).unwrap(); let (gpu, t) = time(|| job::call_certified(&c, "replay", &req, MAX_JOB_BYTES).unwrap()); let same = cpu["lines"].as_str().unwrap().lines().zip(gpu["lines"].as_str().unwrap().lines()).all(|(a, b)| a.split(' ').skip(2).eq(b.split(' ').skip(2))); println!(" {} {t:.3} s, the same states, energies, verdicts, work and replay digests: {same}", m.id()); diff --git a/wai/wai-rs/Cargo.lock b/wai/wai-rs/Cargo.lock index ca778ae8a..b5391477a 100644 --- a/wai/wai-rs/Cargo.lock +++ b/wai/wai-rs/Cargo.lock @@ -163,7 +163,7 @@ version = "0.38.0+1.3.281" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f" dependencies = [ - "libloading", + "libloading 0.8.9", ] [[package]] @@ -1047,6 +1047,15 @@ dependencies = [ "subtle", ] +[[package]] +name = "cudarc" +version = "0.19.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7359bf1de037ddada72729c3acec9b040f3468400c6e9e740b01ba0cd2e89562" +dependencies = [ + "libloading 0.9.0", +] + [[package]] name = "curve25519-dalek" version = "4.1.3" @@ -2340,6 +2349,16 @@ dependencies = [ "windows-link", ] +[[package]] +name = "libloading" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "754ca22de805bb5744484a5b151a9e1a8e837d5dc232c2d7d8c2e3492edc8b60" +dependencies = [ + "cfg-if", + "windows-link", +] + [[package]] name = "libm" version = "0.2.16" @@ -4771,7 +4790,11 @@ name = "wai-quantum" version = "0.8.1" dependencies = [ "blake3", + "cudarc", "ed25519-dalek 2.2.0", + "objc2", + "objc2-foundation", + "objc2-metal", "serde", "serde_json", "wgpu", @@ -4993,7 +5016,7 @@ dependencies = [ "gpu-allocator", "hashbrown 0.17.1", "libc", - "libloading", + "libloading 0.8.9", "log", "naga", "naga-types", diff --git a/wai/wai-rs/Cargo.toml b/wai/wai-rs/Cargo.toml index 00dbceb50..238b31683 100644 --- a/wai/wai-rs/Cargo.toml +++ b/wai/wai-rs/Cargo.toml @@ -1026,6 +1026,11 @@ quantum_fabric = ["quantum", "quantum_ops", "wai-quantum/quantum_fabric"] # `wai_quantum_vectors verify --webgpu`. Opt-in: it is the one feature that # reaches a platform interface. quantum_fabric_webgpu = ["quantum_fabric", "wai-quantum/quantum_fabric_webgpu"] +# The engine's energy-landscape kernels on a GPU (wai_quantum::quantum_landscape::{webgpu, cuda, +# metal}), which sink_meter::RunCounter meters with an on-chip counter (with sink_energy). +quantum_landscape_webgpu = ["quantum_fabric_webgpu", "wai-quantum/quantum_landscape_webgpu"] +quantum_landscape_cuda = ["quantum_fabric", "wai-quantum/quantum_landscape_cuda"] +quantum_landscape_metal = ["quantum_fabric", "wai-quantum/quantum_landscape_metal"] # Variational quantum machine learning (extensions/quantum-sim § VML, # `wai.quantum.vml`): the interference-based ML the thermodynamic-computing pivot # left behind — a Quantum Circuit Born Machine (P_θ(x)=|ψ_θ(x)|²) trained by the @@ -2329,6 +2334,12 @@ name = "sink_energy" path = "tests/sink_energy.rs" required-features = ["sink_energy"] +# The energy-landscape GPU members metered by an on-chip counter: hardware only, run by hand. +[[test]] +name = "landscape_meter" +path = "tests/landscape_meter.rs" +required-features = ["sink_energy", "quantum_fabric"] + [[test]] name = "browser_sink" path = "tests/browser_sink.rs" diff --git a/wai/wai-rs/src/sink_meter.rs b/wai/wai-rs/src/sink_meter.rs index 76d89a63d..8599fbaf2 100644 --- a/wai/wai-rs/src/sink_meter.rs +++ b/wai/wai-rs/src/sink_meter.rs @@ -617,6 +617,70 @@ pub fn survey(children: &Children, every: Duration, intervals: u32) -> (Vec, + relative_ppm: u32, + minimum: Duration, + start: Option<(u64, Instant)>, +} + +#[cfg(feature = "quantum_fabric")] +impl RunCounter { + /// A meter over `counter`, whose figures carry the relative uncertainty `relative_ppm`, in + /// parts per million, as the caller evaluates it. Refused for an uncertainty of 0 and for a + /// counter whose profile energy-measurement §9 does not give. + pub fn new(counter: Box, relative_ppm: u32) -> Result { + if relative_ppm == 0 { + return Err("an on-chip counter's figure declares its uncertainty".into()); + } + let (_, minimum_us) = crate::sink_energy::profile(counter.profile()).ok_or_else(|| format!("{}: no profile gives this counter's minimum window", counter.profile()))?; + Ok(RunCounter { counter, relative_ppm, minimum: Duration::from_micros(minimum_us), start: None }) + } + + /// The same meter with another minimum window, for tests. + #[doc(hidden)] + pub fn with_minimum(mut self, minimum: Duration) -> RunCounter { + self.minimum = minimum; + self + } +} + +#[cfg(feature = "quantum_fabric")] +impl wai_quantum::quantum_fabric::RunMeter for RunCounter { + fn start(&mut self) -> Result<(), String> { + let c = self.counter.read_nj().ok_or_else(|| format!("{}: the counter did not read", self.counter.profile()))?; + self.start = Some((c, Instant::now())); + Ok(()) + } + + fn stop(&mut self) -> Result, String> { + use wai_quantum::quantum_energy::{CounterFiltering, EnergyClass, Uncertainty}; + let Some((c0, t0)) = self.start.take() else { return Err("the meter was stopped without being started".into()) }; + let span = t0.elapsed(); + let Some(c1) = self.counter.read_nj() else { return Ok(None) }; + let nj = advance(c0, c1, self.counter.wrap_nj()); + if span < self.minimum || nj < 1000 { + return Ok(None); + } + let uncertainty = Uncertainty { relative_ppm: self.relative_ppm, window_us: span.as_micros() as u64 }; + Ok(Some((nj / 1000, EnergyClass::OnChipCounter { uncertainty, filtering: CounterFiltering::Unknown }))) + } +} + #[cfg(test)] mod tests { use super::*; @@ -654,6 +718,41 @@ mod tests { assert_eq!(liveness(&mut Scripted(failing, 0), 10, d), CounterStatus::Frozen, "a read that fails is no reading"); } + /// A counter as a run's meter: the advance in microjoules, labelled as an on-chip reading + /// with the declared uncertainty and the window; none for a window below the profile's + /// minimum; refused with no uncertainty or no profile. + #[cfg(feature = "quantum_fabric")] + #[test] + fn a_counter_meters_a_run_and_says_what_it_read() { + use wai_quantum::quantum_energy::{CounterFiltering, EnergyClass}; + use wai_quantum::quantum_fabric::RunMeter; + struct Steps(u64, &'static str); + impl Counter for Steps { + fn profile(&self) -> &'static str { + self.1 + } + fn detail(&self) -> String { + "steps".into() + } + fn read_nj(&mut self) -> Option { + self.0 += 2_500_000; + Some(self.0) + } + } + assert!(RunCounter::new(Box::new(Steps(0, "macos-gpu")), 0).is_err()); + assert!(RunCounter::new(Box::new(Steps(0, "no-such-profile")), 10_000).is_err()); + let mut m = RunCounter::new(Box::new(Steps(0, "macos-gpu")), 50_000).unwrap().with_minimum(Duration::ZERO); + assert!(m.stop().is_err(), "stopped before it started"); + m.start().unwrap(); + let (uj, class) = m.stop().unwrap().expect("a reading"); + assert_eq!(uj, 2_500); + let EnergyClass::OnChipCounter { uncertainty, filtering } = class else { panic!("{class:?}") }; + assert_eq!((uncertainty.relative_ppm, filtering), (50_000, CounterFiltering::Unknown)); + let mut short = RunCounter::new(Box::new(Steps(0, "linux-nvml")), 50_000).unwrap(); + short.start().unwrap(); + assert_eq!(short.stop().unwrap(), None, "a window under the profile's 5 s gives no figure"); + } + /// An advance across a counter's wrap, and none where a counter with no wrap goes back. #[test] fn an_advance_crosses_the_wrap() { diff --git a/wai/wai-rs/tests/NOT-IN-CI.txt b/wai/wai-rs/tests/NOT-IN-CI.txt index 171a826fc..c57897717 100644 --- a/wai/wai-rs/tests/NOT-IN-CI.txt +++ b/wai/wai-rs/tests/NOT-IN-CI.txt @@ -36,3 +36,4 @@ jpegai_scope_ort reads the JPEG AI assets root (wai/jpegai-vm-assets, not commi jpegai_sigma_index_calibration reads a fixture of the reference decoder's intermediate values, named by an environment variable and held outside the tree; without it the test returns without checking anything jpegai_synthesis_calibration reads a fixture of the reference decoder's intermediate values, named by an environment variable and held outside the tree; without it the test returns without checking anything jpegai_uv_calibration reads a fixture of the reference decoder's intermediate values, named by an environment variable and held outside the tree; without it the test returns without checking anything +landscape_meter hardware only: a GPU and its on-chip counter, each test ignored and run by hand with --ignored; its bridge, sink_meter::RunCounter, is tested in the library diff --git a/wai/wai-rs/tests/landscape_meter.rs b/wai/wai-rs/tests/landscape_meter.rs new file mode 100644 index 000000000..19065e006 --- /dev/null +++ b/wai/wai-rs/tests/landscape_meter.rs @@ -0,0 +1,84 @@ +//! The engine's energy-landscape GPU members metered by this machine's own on-chip +//! counter (`sink_meter::RunCounter`): the GPU rail through IOReport on macOS, the board +//! through NVML on Linux. Hardware only, run by hand with `--ignored --nocapture`. +//! +//! The uncertainty these tests declare, 100,000 ppm, is the caller's coarse choice for a +//! hand run, not an evaluation under energy-measurement §2.3. The figure is the whole rail +//! or board over the call's window, idle draw included. +#![allow(dead_code, unused_imports)] + +use std::time::Instant; +use wai::sink_meter::RunCounter; +use wai_quantum::quantum_landscape::energy::Landscape; +use wai_quantum::quantum_landscape::member::{Chain, CpuReference, Member}; +use wai_quantum::quantum_landscape::sample::{chain_seed, out, schedule, start}; +use wai_quantum::quantum_landscape::settle::Workload; + +const DECLARED_PPM: u32 = 100_000; + +fn work(n: usize, chains: u64, sweeps: u32) -> (Workload, Vec, Vec) { + let mut l = Landscape::new(n); + let mut k = 0u64; + for i in 0..n { + for _ in 0..4 { + k += 1; + let j = (out(0x7177, k) % n as u64) as usize; + if j != i { + l.couple(i, j, if k % 3 == 0 { -1 } else { 1 }); + } + } + } + l.set_cost(1); + let cs = (0..chains) + .map(|r| { + let c = chain_seed(9, r); + Chain { start: start(c, n), seed: c } + }) + .collect(); + (l.lower(), cs, schedule(l.default_temperature(), sweeps)) +} + +fn metered(m: &dyn Member, w: &Workload, cs: &[Chain], temperatures: &[f64]) { + let t = Instant::now(); + let got = m.chains(w, cs, temperatures).unwrap(); + let wall = t.elapsed().as_secs_f64(); + let reading = m.meter(); + println!("{}: {} chains, {} cells, {} sweeps in {wall:.2} s wall-clock", m.id(), cs.len(), w.n(), temperatures.len()); + match &reading { + Some(r) => println!(" read: {} µJ, class {}", r.joules_micro, r.class), + None => println!(" no reading"), + } + let r = reading.expect("a call longer than the profile's minimum window gives a reading"); + assert_eq!(r.class["energy_provenance"], "OnChipCounter", "{}", r.class); + // The same bits as the reference, on a slice of the chains. + assert_eq!(got[..4], CpuReference.chains(w, &cs[..4], temperatures).unwrap()[..]); +} + +#[cfg(all(target_os = "macos", feature = "quantum_landscape_metal"))] +#[test] +#[ignore] +fn a_metal_member_reads_the_gpu_rail() { + let counter = wai::sink_meter::IoReportGpu::open().expect("IOReport's GPU Energy channel"); + let m = wai_quantum::quantum_landscape::metal::discover().into_iter().find_map(Result::ok).expect("a Metal device"); + let m = m.with_meter(Box::new(RunCounter::new(Box::new(counter), DECLARED_PPM).unwrap())); + let (w, cs, t) = work(4096, 1024, 400); + metered(&m, &w, &cs, &t); +} + +#[cfg(all(target_os = "linux", feature = "quantum_landscape_cuda"))] +#[test] +#[ignore] +fn a_cuda_member_reads_the_board() { + let mut counter = wai::sink_meter::Nvml::open().expect("NVML"); + let (ok, seen) = counter.plausible(5, std::time::Duration::from_secs(1)); + println!("NVML plausible: {ok} ({seen:?}, counter rate and power reading in mW)"); + if !ok { + // energy-measurement §9.3: an implausible counter gives no figure, so none is asked of it. + println!("NVML is implausible here: no figure, as §9.3 requires"); + return; + } + let m = wai_quantum::quantum_landscape::cuda::discover().into_iter().find_map(Result::ok).expect("a CUDA device"); + let m = m.with_meter(Box::new(RunCounter::new(Box::new(counter), DECLARED_PPM).unwrap())); + let (w, cs, t) = work(4096, 1024, 1200); + metered(&m, &w, &cs, &t); +} -- 2.54.0 (Apple Git-157) From 0675813de4efa1c132560f6b046be6f96f928816 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Fri, 6 Nov 2026 14:32:12 -0500 Subject: [PATCH 3/4] wai-quantum: the landscape GPU members held to what large jobs reach, placed only where certified, metered over what they cover The review of #672 (35 findings), each fix with a test that fails first: - The conformance set reaches what large jobs reach: settles past a workgroup and past a batch of sweeps; chains held hot to their last sweep, at the seeds where a 32-bit port of the generator first goes wrong, past a workgroup of chains, across a device's windows by table and by work; fields only their full width tells apart; field bounds at 2^31 - 1 and just past it. The battery adds a ring of six, eight triangles side by side, a walked law that a schedule's end alone does not give, and fields past 16 and 32 bits. Each fault a port can have, put into the kernels on a device, costs them the grade. - Placement: call_placed passes over an uncertified kernel, a declining member and one that fails the job, naming each under refusals; no member is run on what it declines however it is reached; every answer says who ran it at what parity. - Budget: a sweep's cut points cover the fields the cells can reach (reach.rs, at most 4P + n), counted in a job's operations and bytes; WebGPU waits once a window, and a member that times out passes the job on. - RunCounter reads the counter through each call, gives a figure only from a live window of the profile's minimum, adds its own terms to the declared uncertainty, holds NVML to its plausibility itself, and says what its figure covers; readings are kept per thread and a run's windows are summed. - The spec states one decline rule and defines the field bound; the schema, docs, READMEs and changelog follow; the devices workflow declares each host's members, reads a report rather than test output, runs the members' unit tests, and keeps the Mac's target off its internal disk. Co-Authored-By: Claude Opus 5.5 --- .../wai-quantum-landscape-devices.yml | 90 +- .github/workflows/wai-quantum-landscape.yml | 32 +- .../2026-11-06-landscape-gpu-members.md | 9 +- wai/extensions/quantum-landscape.md | 166 +- wai/quantum-conformance/landscape/README.md | 14 +- .../landscape/vectors.json | 39754 +++++++++++++++- wai/wai-quantum/README.md | 2 +- wai/wai-quantum/README.zh-CN.md | 2 +- wai/wai-quantum/docs/landscape.md | 40 +- wai/wai-quantum/schemas/landscape.v1.json | 218 +- wai/wai-quantum/src/quantum_fabric.rs | 13 +- wai/wai-quantum/src/quantum_landscape.rs | 1 + .../src/quantum_landscape/conformance.rs | 371 +- wai/wai-quantum/src/quantum_landscape/cuda.rs | 67 +- .../src/quantum_landscape/device.rs | 177 +- wai/wai-quantum/src/quantum_landscape/job.rs | 161 +- .../src/quantum_landscape/landscape.wgsl | 20 +- .../src/quantum_landscape/member.rs | 65 +- .../src/quantum_landscape/metal.rs | 60 +- .../src/quantum_landscape/reach.rs | 207 + wai/wai-quantum/src/quantum_landscape/run.rs | 96 +- .../src/quantum_landscape/webgpu.rs | 233 +- .../tests/landscape_conformance.rs | 366 +- wai/wai-quantum/tests/landscape_devices.rs | 370 +- wai/wai-rs/Cargo.toml | 9 +- wai/wai-rs/src/sink_meter.rs | 243 +- wai/wai-rs/tests/landscape_meter.rs | 32 +- 27 files changed, 42214 insertions(+), 604 deletions(-) create mode 100644 wai/wai-quantum/src/quantum_landscape/reach.rs diff --git a/.github/workflows/wai-quantum-landscape-devices.yml b/.github/workflows/wai-quantum-landscape-devices.yml index 6c881e07a..ab8544573 100644 --- a/.github/workflows/wai-quantum-landscape-devices.yml +++ b/.github/workflows/wai-quantum-landscape-devices.yml @@ -3,16 +3,25 @@ name: wai-quantum energy-landscape GPU members # The energy-landscape kernels on GPUs (wai_quantum::quantum_landscape::{webgpu, cuda, metal}; # extensions/quantum-landscape.md §5), each device held to the reference as a host holds a member: # certified through member::certify on the conformance set, bit-exact for the settle kernel and -# the sampling kernel; every worked example at three temperatures over 50 seeds giving the -# reference's bits; the coloured WebGPU kernel earning statistical and no more; and a sampling run -# whose cut points would not fit declined before anything runs and placed on the reference, the -# answer naming the refusal. WAI_REQUIRE_GPU makes a host with no device a failure. The timings -# that follow are wall-clock on that host and are printed, not checked. +# the sampling kernel; every worked example at three temperatures over 50 seeds, and jobs past +# every window and workgroup the devices split them into, giving the reference's bits; each +# fault a port of the kernels can have (a dropped carry, a high-word compare, fields held in 16 +# or 32 bits, a draw counter that starts again each window, cells last to first, a settle that +# stops at one workgroup), put into the kernels on the device, costing them their grade; the +# coloured WebGPU kernel earning statistical and no more; a WebGPU wait covering one window, and +# a job a member cannot finish going on to the reference; a workload a device declines placed +# on the reference, the answer naming the refusal. Each host declares the members it must have +# (WAI_LANDSCAPE_EXPECT): one missing, or whose kernels no longer compile, fails its leg. The +# timings that follow are wall-clock on that shared host, with its load printed beside them; +# their bits are asserted and their times are not. # -# - jetson-hub (linux-hw): its laptop GPU through Vulkan and through CUDA, its integrated GPU -# and the software Vulkan driver through WebGPU. +# - jetson-hub (linux-hw): its laptop GPU through CUDA and through Vulkan, its integrated GPU and +# the software Vulkan driver through WebGPU. This is the only leg with a CUDA device: while its +# laptop GPU is off its bus, the leg fails, naming the members it is missing, and nothing here +# runs the CUDA kernels. The CUDA kernels' compile test still runs there. # - framework-128gb (linux-amd): its integrated GPU and the software Vulkan driver through WebGPU. # - the Mac Studio (macos-m3-ultra): its GPU through WebGPU on Metal, and through Metal itself. +# No leg runs WebGPU on DX12, and no parity is claimed for it. concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -23,6 +32,13 @@ on: paths: - 'wai/wai-quantum/src/quantum_landscape/**' - 'wai/wai-quantum/src/quantum_landscape.rs' + - 'wai/wai-quantum/src/quantum_fabric.rs' + - 'wai/wai-quantum/src/quantum_fabric/**' + - 'wai/wai-quantum/src/quantum_fabric_webgpu.rs' + - 'wai/wai-quantum/src/quantum_fabric_cuda.rs' + - 'wai/wai-quantum/src/quantum_fabric_metal.rs' + - 'wai/wai-quantum/src/quantum_energy.rs' + - 'wai/wai-quantum/src/lib.rs' - 'wai/wai-quantum/tests/landscape_devices.rs' - 'wai/wai-quantum/Cargo.toml' - 'wai/wai-quantum/Cargo.lock' @@ -56,22 +72,36 @@ jobs: needs: relevant if: needs.relevant.outputs.run == 'true' runs-on: ${{ matrix.runner }} - timeout-minutes: 90 + timeout-minutes: 120 strategy: fail-fast: false matrix: include: + # Each host's members, declared: a prefix of each member id it must have. A member + # missing, for any reason, fails the leg with why. - host: jetson-hub runner: linux-hw features: quantum_landscape_webgpu,quantum_landscape_cuda + expect: gpu:cuda.nvidia,gpu:wgpu-vulkan.nvidia,gpu:wgpu-vulkan.intel,cpu:wgpu-vulkan.llvmpipe + units: 9 + target: hw-target - host: framework-128gb runner: linux-amd features: quantum_landscape_webgpu + expect: gpu:wgpu-vulkan.radeon,cpu:wgpu-vulkan.llvmpipe + units: 8 + target: hw-target - host: mac-studio runner: macos-m3-ultra features: quantum_landscape_webgpu,quantum_landscape_metal + expect: gpu:metal.apple-m3-ultra,gpu:wgpu-metal.apple-m3-ultra + units: 8 + # The job's own workspace, on the Studio's external volume: never its internal disk. + target: '' env: WAI_REQUIRE_GPU: '1' + WAI_LANDSCAPE_EXPECT: ${{ matrix.expect }} + WAI_LANDSCAPE_NVRTC: ${{ contains(matrix.features, 'cuda') && '1' || '' }} CARGO_PROFILE_DEV_DEBUG: '0' CARGO_BUILD_JOBS: '8' steps: @@ -87,28 +117,44 @@ jobs: git checkout -q FETCH_HEAD got="$(git rev-parse HEAD)" [ "$got" = "$want" ] || { echo "::error::fetched $got, not $want"; exit 1; } - # A module filter is satisfied by any one test, so the count is pinned and each test named. - - name: Every member, certified and held to the reference + - name: The members' own unit tests (ids, labels, the meter's slot, colouring, bindings, the CUDA kernels compiling) shell: bash working-directory: src/wai/wai-quantum run: | set -euo pipefail export PATH="$HOME/.cargo/bin:$PATH" - CARGO_TARGET_DIR="$HOME/hw-target/wai-quantum-landscape" nice -n 10 \ - cargo test --locked --release --features "${{ matrix.features }}" --test landscape_devices -- --test-threads=1 --nocapture 2>&1 | tee "$RUNNER_TEMP/devices.log" - grep -qE '^test result: ok\. 4 passed; 0 failed' "$RUNNER_TEMP/devices.log" || { echo "::error::not every device test ran and passed (4 expected)"; exit 1; } - for t in every_device_is_certified_bit_exact_for_both_kernels \ - every_worked_example_gives_the_reference_s_bits \ - the_coloured_kernel_earns_statistical_and_no_more \ - a_workload_a_device_declines_goes_to_the_reference_and_the_answer_says_so; do - grep -q "^test $t \.\.\. ok$" "$RUNNER_TEMP/devices.log" || { echo "::error::$t did not run and pass"; exit 1; } + if [ -n "${{ matrix.target }}" ]; then export CARGO_TARGET_DIR="$HOME/${{ matrix.target }}/wai-quantum-landscape"; fi + nice -n 10 cargo test --locked --release --features "${{ matrix.features }}" --lib -- \ + quantum_landscape::device:: quantum_landscape::webgpu:: quantum_landscape::cuda:: quantum_landscape::metal:: 2>&1 | tee "$RUNNER_TEMP/units.log" + grep -qE '^test result: ok\. ${{ matrix.units }} passed; 0 failed' "$RUNNER_TEMP/units.log" || { echo "::error::not every unit test of the members ran and passed (${{ matrix.units }} expected)"; exit 1; } + # Each test that certifies a member writes its certificates to a report, which is read + # here rather than the test's output, so that no test's printing can hide a line. + - name: Every member, certified, held to the reference past every window, and every fault losing its grade + shell: bash + working-directory: src/wai/wai-quantum + run: | + set -euo pipefail + export PATH="$HOME/.cargo/bin:$PATH" + if [ -n "${{ matrix.target }}" ]; then export CARGO_TARGET_DIR="$HOME/${{ matrix.target }}/wai-quantum-landscape"; fi + report="$RUNNER_TEMP/landscape-report.txt" + rm -f "$report" + WAI_LANDSCAPE_REPORT="$report" nice -n 10 \ + cargo test --locked --release --features "${{ matrix.features }}" --test landscape_devices -- --test-threads=1 2>&1 | tee "$RUNNER_TEMP/devices.log" + grep -qE '^test result: ok\. 7 passed; 0 failed' "$RUNNER_TEMP/devices.log" || { echo "::error::not every device test ran and passed (7 expected)"; exit 1; } + cat "$report" + IFS=',' read -ra want <<< "$WAI_LANDSCAPE_EXPECT" + for p in "${want[@]}"; do + grep -qE "^certified ${p}[^ ]*: settle bit-exact, sample bit-exact$" "$report" || { echo "::error::$p was not certified bit-exact for both kernels"; exit 1; } + n=$(grep -cE "^fault .* in ${p}[^ ]*: " "$report" || true) + [ "$n" -ge 3 ] || { echo "::error::$p lost its grade under $n faults, and 3 or more are put into every backend's kernels"; exit 1; } done - grep -E ': settle bit-exact, sample bit-exact$' "$RUNNER_TEMP/devices.log" || { echo "::error::no member was certified"; exit 1; } - - name: Timings, wall-clock on this host (printed, not checked) + grep -qE "^waits [^ ]+: 40 windows" "$report" || { echo "::error::no WebGPU member showed a wait covering one window"; exit 1; } + - name: Timings, wall-clock on this host, with its load (the bits asserted, the times printed) shell: bash working-directory: src/wai/wai-quantum run: | set -euo pipefail export PATH="$HOME/.cargo/bin:$PATH" - CARGO_TARGET_DIR="$HOME/hw-target/wai-quantum-landscape" nice -n 10 \ - cargo test --locked --release --features "${{ matrix.features }}" --test landscape_devices -- --ignored --nocapture --test-threads=1 timings 2>&1 | grep -E 'chains n=|settle n=|replay|^ ' + if [ -n "${{ matrix.target }}" ]; then export CARGO_TARGET_DIR="$HOME/${{ matrix.target }}/wai-quantum-landscape"; fi + nice -n 10 cargo test --locked --release --features "${{ matrix.features }}" --test landscape_devices -- --ignored --nocapture --test-threads=1 timings 2>&1 | tee "$RUNNER_TEMP/timings.log" + grep -qE '^test result: ok\. 1 passed; 0 failed' "$RUNNER_TEMP/timings.log" || { echo "::error::the timings' bits differ from the reference's"; exit 1; } diff --git a/.github/workflows/wai-quantum-landscape.yml b/.github/workflows/wai-quantum-landscape.yml index b516d774a..561456f18 100644 --- a/.github/workflows/wai-quantum-landscape.yml +++ b/.github/workflows/wai-quantum-landscape.yml @@ -37,6 +37,12 @@ on: - 'wai/wai-quantum-cli/Cargo.lock' - 'wai/wai-web/src/quantum_engine.rs' - 'wai/wai-rs/src/sink_meter.rs' + - 'wai/wai-rs/src/sink_energy.rs' + - 'wai/wai-rs/src/energy_report.rs' + - 'wai/wai-rs/Cargo.toml' + - 'wai/wai-rs/Cargo.lock' + - 'wai/wai-quantum/src/quantum_fabric.rs' + - 'wai/wai-quantum/src/quantum_energy.rs' - 'wai/wai-web/Cargo.toml' - 'wai/wai-web/Cargo.lock' - 'wai/wai-web/demo/verify_landscape_wasm.mjs' @@ -63,6 +69,12 @@ on: - 'wai/wai-quantum-cli/Cargo.lock' - 'wai/wai-web/src/quantum_engine.rs' - 'wai/wai-rs/src/sink_meter.rs' + - 'wai/wai-rs/src/sink_energy.rs' + - 'wai/wai-rs/src/energy_report.rs' + - 'wai/wai-rs/Cargo.toml' + - 'wai/wai-rs/Cargo.lock' + - 'wai/wai-quantum/src/quantum_fabric.rs' + - 'wai/wai-quantum/src/quantum_energy.rs' - 'wai/wai-web/Cargo.toml' - 'wai/wai-web/Cargo.lock' - 'wai/wai-web/demo/verify_landscape_wasm.mjs' @@ -113,7 +125,7 @@ jobs: run: | set -euo pipefail cargo test --locked --release --features full --lib -- quantum_landscape:: 2>&1 | tee "$RUNNER_TEMP/landscape.log" - grep -qE '^test result: ok\. 85 passed; 0 failed' "$RUNNER_TEMP/landscape.log" || { echo "::error::the landscape tests did not all pass (85 expected)"; exit 1; } + grep -qE '^test result: ok\. 95 passed; 0 failed' "$RUNNER_TEMP/landscape.log" || { echo "::error::the landscape tests did not all pass (95 expected)"; exit 1; } for t in quantum_landscape::classical::tests::a_solver_keeps_to_its_budget_and_repeats_itself \ quantum_landscape::classical::tests::each_solver_finds_the_exact_lowest_on_small_instances_with_a_generous_budget \ quantum_landscape::classical::tests::with_no_budget_a_solver_misses \ @@ -127,6 +139,10 @@ jobs: quantum_landscape::energy::tests::the_regime_reads_the_cost_against_the_couplings \ quantum_landscape::job::tests::a_bad_request_is_refused_plainly \ quantum_landscape::job::tests::a_job_goes_to_the_first_member_that_takes_it \ + quantum_landscape::job::tests::a_member_never_runs_what_it_declines \ + quantum_landscape::job::tests::a_member_that_fails_a_job_passes_it_on \ + quantum_landscape::job::tests::an_uncertified_kernel_is_never_placed \ + quantum_landscape::job::tests::every_answer_says_who_ran_it_at_what_parity \ quantum_landscape::job::tests::a_member_may_decline_only_what_does_not_fit_32_bits \ quantum_landscape::job::tests::a_sealed_answer_verifies_and_follows_its_schema \ quantum_landscape::job::tests::a_seed_keeps_all_64_bits \ @@ -162,11 +178,17 @@ jobs: quantum_landscape::program::tests::temperatures \ quantum_landscape::program::tests::warnings_come_when_a_program_is_read \ quantum_landscape::program::tests::weights_go_through_quantisation_and_say_what_was_lost \ + quantum_landscape::reach::tests::a_table_holds_only_what_the_cells_can_reach \ + quantum_landscape::reach::tests::a_table_is_bounded_by_the_smaller_of_the_two_forms \ + quantum_landscape::reach::tests::a_window_ends_at_its_work_or_at_its_table \ + quantum_landscape::reach::tests::no_job_can_outgrow_a_table \ + quantum_landscape::reach::tests::the_table_is_the_reference_s_cut_points \ quantum_landscape::run::tests::a_comparison_gives_every_solver_the_same_budget \ quantum_landscape::run::tests::a_job_runs_on_another_member_and_its_answer_names_it \ quantum_landscape::run::tests::a_member_is_held_to_its_ending \ quantum_landscape::run::tests::a_member_s_bad_answer_is_refused \ quantum_landscape::run::tests::a_member_s_claim_of_parity_is_not_taken_on_its_word \ + quantum_landscape::run::tests::a_run_s_reading_is_its_calls_added_up \ quantum_landscape::run::tests::a_run_too_big_is_refused_before_it_starts \ quantum_landscape::run::tests::a_sweep_keeps_the_budget_and_the_cold_row_of_a_minimize_runs_once \ quantum_landscape::run::tests::the_same_seed_gives_the_same_answer_and_another_seed_another_run \ @@ -207,7 +229,7 @@ jobs: run: | set -euo pipefail cargo test --locked --release --features quantum_landscape --test landscape_conformance 2>&1 | tee "$RUNNER_TEMP/conformance.log" - grep -qE '^test result: ok\. 8 passed; 0 failed' "$RUNNER_TEMP/conformance.log" || { echo "::error::the conformance tests did not all pass (8 expected)"; exit 1; } + grep -qE '^test result: ok\. 12 passed; 0 failed' "$RUNNER_TEMP/conformance.log" || { echo "::error::the conformance tests did not all pass (12 expected)"; exit 1; } # With the module's feature alone: a temperature given as a JSON number is read to the nearest double. - name: Numbers read to the nearest double shell: bash @@ -222,7 +244,7 @@ jobs: working-directory: wai/wai-rs run: | set -euo pipefail - cargo test --locked --release --no-default-features --features sink_energy,quantum_fabric --lib -- sink_meter::tests::a_counter_meters_a_run_and_says_what_it_read 2>&1 | tee "$RUNNER_TEMP/meter.log" + cargo test --locked --release --no-default-features --features sink_energy,quantum_fabric --lib -- sink_meter::tests::a_counter_meters_a_run_only_where_it_was_live_and_says_what_it_covers 2>&1 | tee "$RUNNER_TEMP/meter.log" grep -qE '^test result: ok\. 1 passed; 0 failed' "$RUNNER_TEMP/meter.log" || { echo "::error::the meter bridge's test did not run and pass"; exit 1; } - name: The command line's verb shell: bash @@ -283,7 +305,7 @@ jobs: quantum_landscape::settle::tests::four_recall_vectors_are_pinned 2>&1 | tee "$RUNNER_TEMP/bits.log" grep -qE '^test result: ok\. 2 passed; 0 failed' "$RUNNER_TEMP/bits.log" || { echo "::error::the pinned bits differ here ($(uname -sm), ${profile:-debug})"; exit 1; } nice -n 10 cargo test --locked $profile --no-default-features --features quantum_landscape --test landscape_conformance 2>&1 | tee "$RUNNER_TEMP/vectors.log" - grep -qE '^test result: ok\. 8 passed; 0 failed' "$RUNNER_TEMP/vectors.log" || { echo "::error::the conformance set differs here ($(uname -sm), ${profile:-debug})"; exit 1; } + grep -qE '^test result: ok\. 12 passed; 0 failed' "$RUNNER_TEMP/vectors.log" || { echo "::error::the conformance set differs here ($(uname -sm), ${profile:-debug})"; exit 1; } done wasip2: @@ -332,7 +354,7 @@ jobs: run: | set -euo pipefail cargo test --locked --target wasm32-wasip2 --no-default-features --features quantum_engine,quantum_receipt --lib -- quantum_landscape:: 2>&1 | tee "$RUNNER_TEMP/wasip2.log" - grep -qE '^test result: ok\. 84 passed; 0 failed' "$RUNNER_TEMP/wasip2.log" || { echo "::error::the landscape tests did not all pass under wasip2 (84 expected)"; exit 1; } + grep -qE '^test result: ok\. 94 passed; 0 failed' "$RUNNER_TEMP/wasip2.log" || { echo "::error::the landscape tests did not all pass under wasip2 (94 expected)"; exit 1; } # The browser build's answers against the command line's, byte for byte, in Node. parity: diff --git a/wai/changes/2026-11-06-landscape-gpu-members.md b/wai/changes/2026-11-06-landscape-gpu-members.md index f8771743d..9e28eacf9 100644 --- a/wai/changes/2026-11-06-landscape-gpu-members.md +++ b/wai/changes/2026-11-06-landscape-gpu-members.md @@ -1,6 +1,7 @@ ### wai-quantum: energy-landscape jobs on a GPU, through WebGPU, CUDA and Metal -- **`quantum_landscape_webgpu`, `quantum_landscape_cuda` and `quantum_landscape_metal`** run the settle kernel (one thread a cell, one dispatch a sweep) and the sampling kernel (one thread a chain, its cells in the reference's order, the reference's draws and the host's cut points) on a GPU, each a member the host certifies through `member::certify`: bit-exact for both kernels on every device tried: a desktop GPU through Metal and through WebGPU on Metal, a laptop GPU through CUDA and through Vulkan, two integrated GPUs through Vulkan, and the software Vulkan driver. The WebGPU member's coloured kernel, a colour class of cells at a time, earns `statistical` and no more. -- **A member may decline a workload before anything runs** (`Member::takes`): `job::call_placed` gives the job to the first member that takes it, and the answer names each member that declined, with why, under `refusals`. A member is held to every vector it takes, and may decline only those whose field bound is above 2³¹ − 1. -- **The host hands a member the starts of several seeds at once**, so a sweep, a comparison or a replay runs its chains side by side; each run is the one it would be alone. -- **`wai::sink_meter::RunCounter`** makes a run meter of an on-chip counter (IOReport's GPU channel, NVML), labelled `OnChipCounter`, which a GPU member reads around its calls; its reading stands beside the priced figures for a run whose calls covered it alone. +- **`quantum_landscape_webgpu`, `quantum_landscape_cuda` and `quantum_landscape_metal`** run the settle kernel (one thread a cell, one dispatch a sweep) and the sampling kernel (one thread a chain, its cells in the reference's order, the reference's draws and the host's cut points for the fields the cells can reach) on a GPU, a window of sweeps a dispatch, each a member the host certifies through `member::certify`. Each certifies bit-exact for both kernels on the devices it ran on here, and each fault a port of the kernels can have, put into them on those devices, costs them the grade. The WebGPU member's coloured kernel, a colour class of cells at a time, earns `statistical` and no more. +- **The conformance set reaches what large jobs reach**: settles past one workgroup of cells and past one batch of sweeps; chains that cross a device's windows of sweeps, more chains than a workgroup, chains that stop hot; one-cell chains at the seeds where a port of the generator in 32-bit words first goes wrong; fields that only their full width tells apart; and field bounds at the decline line and just past it. The battery adds workloads past four cells, one whose schedule matters to its law, and fields past 16 and 32 bits. +- **Declining and placing.** A workload's field bound is the largest field any state can give a cell. A member may decline a workload only where that is above 2³¹ − 1, and the host never runs it on one it declines. `job::call_placed` passes over a member whose kernel for the job is not certified, one that declines it, and one that fails it, naming each under `refusals`. Every answer says who ran it at what parity: a replay its member, a sweep and a comparison each kernel they used. +- **A job's budget holds a device's work**: a sweep's table of cut points holds the fields its cells can reach, at most `4P + n` entries, counted in the job's operations and memory. +- **`wai::sink_meter::RunCounter`** makes a run meter of an on-chip counter (IOReport's GPU channel; NVML once it has shown itself plausible) which reads the counter through each call, gives a figure only from a live window of the profile's minimum or more, and says what it covers: the whole rail or board, its idle draw and any other work on the device in the window. An answer carries that beside the figure; a run's calls' figures and windows are added up. diff --git a/wai/extensions/quantum-landscape.md b/wai/extensions/quantum-landscape.md index b9c8f9035..75dbab1d4 100644 --- a/wai/extensions/quantum-landscape.md +++ b/wai/extensions/quantum-landscape.md @@ -84,12 +84,15 @@ With `n` variables, `P` coupled pairs, `C` items in the checks and a plan of `R` starts of `S` sweeps, the operations of a job are `n²` (reading the couplings into rows), plus, where `n ≤ 16`, `3ⁿ·(n + 1 + C)` (the exhaustive truth) and, for a program with `weight`s, `3ⁿ·n²` more; plus, for each run, `(2P + n)·R·S` (the -sweeps) `+ (2P + n)·R` (each start's energy) `+ n²`, or `2n²` with weights (the -answer's energy) `+ C` (its checks). A `run` is one run; a `replay` and each row of +sweeps) `+ (4P + n)·S·⌈R / D⌉` for a plan above temperature 0, with `D` the smaller of +4096 and `⌊1,048,576 / n⌋` (one sweep's cut points as a device works them out, §4.2, +once for each batch of starts, §5) `+ (2P + n)·R` (each start's energy) `+ n²`, or +`2n²` with weights (the answer's energy) `+ C` (its checks). A `run` is one run; a `replay` and each row of a `sweep` are one run a seed, a settle row one run; a `compare` is five runs a seed of its sampling plan. The memory is the program's bytes `+ n²`, `+ 8n²` with -weights, `+ 32P + 64n`, `+ 2,359,296` (a batch of chains, §5), `+ 256` a line of a -`replay`, `+ 1,048,576`. +weights, `+ 32P + 64n`, `+ 64P + 16n` (one sweep's cut points as a device holds +them, §4.2), `+ 2,359,296` (a batch of chains, §5), `+ 256` a line of a `replay`, +`+ 1,048,576`. A host does not hold more than one batch of chains at once: at most 1,048,576 cells and at most 4096 chains a call to a member. @@ -191,6 +194,13 @@ A `run` answers with these fields (the schema has them all): | `work` | `sweeps`, `accumulates`, `cell_updates`, `draws`, `ended`, `rests` | | `joules` | §4.4 | | `state_hash`, `replay` | §3.2 | +| `refusals` | each member that a placing host passed over, with why (§5); absent where there was none | + +A `replay` answers with `protocol`, `label`, `member` (`id`, `class`, `kernel`, +`parity`), `seeds` and `lines`. A `sweep` and a `compare` give `member` as `id`, +`class` and `kernels`, each kernel the job used with the parity the host found for it; +each row of a `sweep` gives its own `kernel` and `parity`, and each of a `compare` the +member's `parity`, null for a classical method. Each carries `refusals` as a `run` does. An accumulate is one read of one coupling into one cell's field, and is the unit of work. The accumulates of a sweep are the non-zero entries of `W`, counting each @@ -258,6 +268,10 @@ the state reported is the one the last sweep computed) or the bound. Everything on the order a field is added in, on how many lanes a device has or on how it splits the cells. A settle kernel MUST give the reference's bits. +A workload's **field bound** is the largest `|h_i|` any state can give a cell: +`max_i (|b_i| + deg_i)`, with `deg_i` the number of cells coupled to `i`. It, and not +the largest `|b_i|`, says how wide a lane must be to hold every field. + A `quench` runs chains through the kernel of §4.2 at temperature 0, whose every sweep is this one. @@ -298,6 +312,9 @@ and `exp` the function of Appendix A. A 53-bit number `u` gives −1 if `u < a`, 0 if `u < b`, else +1. A device needs a table of cut points for the fields it can see at each sweep's temperature and two comparisons a cell. It needs no exponential and no floating point. The host may give it the table. +Cell `i` only ever sees `b_i − deg_i` to `b_i + deg_i`, so a table of those fields, where +two overlap taken once, holds at most `4P + n` entries a sweep however wide the fields +are, and at most `2B + 1` for a field bound `B`. **The reference's draws.** They come from a counter, not from a state carried along: @@ -345,10 +362,16 @@ multiplied by a rate measured elsewhere, with that rate's source named. A host: - MUST NOT print an uncertainty it does not have. A member with a meter puts its reading in `joules.metered`: `{ "joules_micro": …, -"class": … }`, with the class and the evidence energy-measurement asks of that class. -The reading stands beside the priced figures and does not replace them, so the two can -be compared. Where there is no meter, `metered` is null. No figure is `0` for being -unmeasured. +"window_us": …, "class": …, "scope": … }`, with the class and the evidence +energy-measurement asks of that class. Over a run of several calls the figure and the +window are each the calls' added up, and so is the window of the class's uncertainty; +its relative part is the largest a call declared. `scope` says what the figure covers +where that is more than the run: a counter that reads a whole rail or board holds its +idle draw and any other work on the device in the window, and says so; null is the run +alone. The reading stands beside the priced figures and does not replace them, so the +two can be compared, and the answer's `label` says it is there and what it covers only +where it is. Where there is no meter or no reading, `metered` is null. No figure is `0` +for being unmeasured. The reference's rates: @@ -376,17 +399,28 @@ batch of chains at a time (§2.0). What a member gives back, for a settle or each chain: the state, the sweeps, how it ended, the accumulates and the draws. -**Declining.** Before anything runs, a member MAY decline a workload it cannot run, -saying why: a field too wide for its lanes, a table of cut points too large. A host -offered several members gives the job to the first that takes it, and its answer lists -each member that declined, in the order they were offered, under `refusals`, with -why; with no refusal, `refusals` is absent. Where no member takes it, the job is refused. -Offering the reference last gives every job a member. +**Declining.** Before anything runs, a member MAY decline a workload whose field bound +(§4.1) is above 2³¹ − 1, which a device with 32-bit lanes cannot hold, saying why in +words that are true of the workload. It MAY decline no other workload a job can give; +§2.0's limits keep every other need of a job, such as a sweep's table (§4.2), within +what any member can hold. A member may decline for one kernel and not the other: the +rule is the same for each. A host runs a member on no workload it declines, however the +member was reached. The reference declines nothing. + +**Placing a job.** A host offered several members gives the job to the first that can +take it: one whose kernel is certified for every kernel the job runs (§5.1), that does +not decline the workload, and that gives an answer the host's checks keep. Each member +passed over, for any of those, is named under `refusals`, in the order offered, with +why; with none, `refusals` is absent. A member that took the job and failed it, or +whose answer the checks refused, is passed over the same way and the next is asked. +Where no member answers, the job is refused, naming each with why. Offering the +reference last gives every job an answer. **Batches.** A host MAY give a member the starts of several seeds' runs in one call, up to one batch (§2.0). Each run is the one it would be alone: the same chains, its best state chosen in the same order. A meter's reading (§4.4) stands for a run only -where every call of that run carried that run alone; elsewhere `metered` is null. +where every call of that run carried that run alone, and a settle that answers several +seeds from one call carried none of them alone; elsewhere `metered` is null. A host MUST check what comes back, and refuse the answer with one plain sentence if any of these does not hold: @@ -406,10 +440,13 @@ certificate is what vouches for it. The reference implementation has three GPU members besides the CPU reference, each certified by the host on its own run of the conformance set (informative): through WebGPU -(32-bit lanes; it declines a workload whose field bound or cost is above 2³¹ − 1, and its -coloured kernel can be `statistical` at most), through CUDA and through Metal (64-bit -fields). All three run the sampling kernel one chain a thread, the cells in the reference's -order, with the reference's draws and the host's cut points, and give its bits. +(32-bit lanes; it declines a workload whose field bound is above 2³¹ − 1, holds a cost +above every field at its lanes' top, where it holds every cell at 0 as it does at its +own size, and its coloured kernel can be `statistical` at most), through CUDA and through +Metal (64-bit fields, so they take every workload). All three run the sampling kernel one +chain a thread, the cells in the reference's order, with the reference's draws and the +host's cut points for the fields the cells can reach, a window of sweeps a dispatch, each +window waited for before the next is sent; and give its bits. A member names itself `class:name`, with `class` one of the fabric's (`cpu`, `gpu`, `fpga`) and the class it gives, and `name` lower-case letters, digits, `.`, `_` and `-`, @@ -424,21 +461,22 @@ run used (`member.kernel`, `member.parity`). | parity | granted when | |---|---| | `reference` | to the host's own reference kernels, and to nothing else | -| `bit-exact` | settle: the member gives every `settle` vector it takes exactly. Sample: it gives every `chains` vector it takes exactly, each case's chains in one call. Either way it takes every vector whose field bound is at most 2³¹ − 1 | +| `bit-exact` | settle: the member gives every `settle` vector it takes exactly. Sample: it gives every `chains` vector it takes exactly, each case's chains in one call. Either way it takes every vector §5 does not let it decline | | `statistical` | sample: not `bit-exact`, and every workload of the `statistical` battery passes (§7.2) | | `uncertified` | anything else, or a member the host was asked to run by name without certifying it | -A member is held to every vector it takes. It MAY decline a vector whose field bound -is above 2³¹ − 1, which a device with 32-bit lanes cannot hold; declining any other -vector is failing it. A host gives a member only the workloads it takes, so a member -certified on fewer vectors is never given one it was not held to. +A member is held to every vector it takes, and declining a vector §5 does not let it +decline is failing that vector. The set holds vectors on both sides of the line, at a +field bound of exactly 2³¹ − 1 and just above it by the couplings alone, so that a +member is held to the edge of what it takes. A settle kernel can earn `bit-exact` on any device. A sampling kernel can, if it uses the reference's draws and order and the cut points of §4.2. -The fabric's placement never places an uncertified kernel. A host MAY still run one when -it is asked for that member by name, which is how a board gets its first answers; the -answer says `uncertified`, and nothing should be concluded from its state but its energy. +A host placing a job never places an uncertified kernel (§5). A host MAY still run one +when it is asked for that member by name, without placing it, which is how a board gets +its first answers; the answer says `uncertified`, and nothing should be concluded from +its state but its energy. ### 5.2. The receipt @@ -459,7 +497,10 @@ reference's, word for word: > scale. No photonic hardware, custom chip or quantum effect is involved in this run. A member states its own, and MUST say what device ran the job, whether its joules are -priced or read from a meter, and that no quantum effect is involved where none is. +priced or read from a meter, and that no quantum effect is involved where none is. It +calls a device what it is: a software driver that runs on the processor is not a GPU. +An answer adds that a reading stands beside the priced figures, and what it covers, only +where that answer carries one (§4.4). ## 6. The comparison @@ -482,19 +523,35 @@ way, is caught. ### 7.1. Bits -- `settle`: whole workloads, starts and bounds, and the state, sweeps, ending, - accumulates and state hash the settle kernel gives: the four recall vectors, every - ending, a two-cycle with fields, a bound of one sweep and fields at 2⁴⁰. A settle - kernel is `bit-exact` when it gives every one exactly. +- `settle`: whole workloads, starts and bounds, each with its field bound, and the state, + sweeps, ending, accumulates and state hash the settle kernel gives: the four recall + vectors, every ending, a two-cycle with fields, a bound of one sweep, fields at 2⁴⁰; + waves down paths of 65 to 300 cells, which run past one workgroup of cells and past + 64 sweeps, one stopped at its bound in a later batch, a two-cycle of 70 cells and 1000 + cells of mixed couplings; fields that only their full width tells apart past 16 and + 32 bits; and field bounds at 2³¹ − 1 and just above it (§5.1). A settle kernel is + `bit-exact` when it gives every one it takes exactly and takes every one §5 does not + let it decline. - `exp`: the bits of Appendix A's function at the arguments the cut points use and at its edges, as `[x, exp(x)]` pairs of 16 hexadecimal digits. - `schedule`: the bits of every sweep's temperature for starting temperatures and chain lengths that tell `T₀ − T₀·q` from other ways of writing it. - `cells`: the two cut points for fields from −40 to 40, costs from 0 to 8 and temperatures from 0.25 to 25, the temperature given by its bits too. +- `draws`: the generator's outputs `out(s, j)` at several seeds, and at each seed of the + `a-draw` chains below. - `chains`: whole chains from their starts and chain seeds, with every start of some runs, - cooling from starting temperatures that include one that is not a short decimal. A - sampling kernel is `bit-exact` when it gives every case's chains exactly, from one call. + cooling from starting temperatures that include one that is not a short decimal, or + held at temperatures given sweep by sweep (`held`), which need not end at 0, so that + the last sweep's draws are in the final state. Among them: one cell at seeds where a + port of the generator in 32-bit words first goes wrong (a carry out of the middle of a + product, a cut point compared by its high word alone); 16,384 draws in one sweep; + more chains than a workgroup; chains that stop hot, in which the order of the cells + shows; chains long enough to cross a device's windows of sweeps, by table and by + work, hot in every window; and the same fields past 16 and 32 bits and at the line as + the settle vectors, at 0 and at a temperature of their own size. A sampling kernel is + `bit-exact` when it gives every case it takes exactly, each from one call, and takes + every one §5 does not let it decline. - `jobs`: whole requests and what their answers hold, refusals included: every worked example at seed 1, and requests that pin a rule each (a `minimize` above temperature 0, a temperature given as text, a pin's size, quantised weights, the state a two-cycle @@ -505,24 +562,35 @@ way, is caught. ### 7.2. The battery -A sampling kernel with draws of its own is held to `statistical`: workloads of two to four -cells, each with a schedule. Workload `k` runs `statistical.chains` (20,000) chains, chain -`r` with the chain seed `out(statistical.seed + k, r + 1)` and its start from that seed -(§4.2), through the workload's temperatures. The final states are counted over the `3ⁿ` -states, in the order `Σ (s_i + 1)·3ⁱ`, and held to `exact`: the Boltzmann distribution at -the schedule's last temperature above 0, carried through one deterministic sweep where the -schedule ends at 0. The statistic is Pearson's, every state expected fewer than -`statistical.pool_below` (5) times pooled into one bin; with `df` the bins less one, a -workload passes when it is at most `df + 5·√(2·df)`. +A sampling kernel with draws of its own is held to `statistical`: workloads of two to six +cells, and one of eight blocks of three side by side, none coupled to another, each with +a schedule. A member may decline a workload of the battery only where §5 lets it decline +a vector, and it then passes that workload without running it. Workload `k` runs `statistical.chains` (20,000) chains, chain `r` with the +chain seed `out(statistical.seed + k, r + 1)` and its start from that seed (§4.2), +through the workload's temperatures. The final states of each `block` of cells are +counted over its `3ⁿ` states, in the order `Σ (s_i + 1)·3ⁱ`, and held to `exact`. Where +`law` is `settled`, that is the Boltzmann distribution at the schedule's last temperature +above 0, reached on a plateau, carried through one deterministic sweep where the schedule +ends at 0. Where it is `walked`, it is the law of the walk itself from the start's +uniform law, every sweep of the schedule cell by cell in order, on a block whose cells +are all alike; there the counts are read over how many cells of the block hold each +state, which any order of the cells leaves the same. The statistic is Pearson's, every +bin expected fewer than `statistical.pool_below` (5) times pooled into one; with `df` the +bins less one, a block passes when it is at most `df + 5·√(2·df)`, and a workload when +every block does. The workloads hold pairs of coupled cells next to each other, two apart and three apart, a triangle no two colours can split, fields to 40 and a cost of 8 at a high temperature, a -small field at a low one, a schedule that ends at 0, and one that cools from 6 to 0.7. A -kernel that moves two coupled cells at once in any order of cells, that ignores the -schedule, that samples the last sweep rather than running it at 0, or whose table is wrong -for large fields fails at least one of them; the reference's own kernel, and one that -colours its cells properly, pass every one. A test holds both statements, with kernels -that are wrong in each of those ways. +small field at a low one, a schedule that ends at 0, one that cools from 6 to 0.7, a ring +of six cells, eight triangles side by side, a triangle pulled one way that is warm +and then cold, whose end alone gives another law, and fields past 16 bits and past 32 at +a temperature of their own size. A kernel that moves two coupled cells +at once in any order of cells, that ignores the schedule or keeps only its end, that +samples the last sweep rather than running it at 0, whose table is wrong for large +fields, that holds its fields in 16 or 32 bits, or that leaves cells from the fifth on +where they started fails at least one of them; the reference's own kernel, one that colours its cells properly, and one that +takes its cells last to first pass every one. A test holds both statements, with +kernels that are wrong in each of those ways. ## 8. What does not run on this model diff --git a/wai/quantum-conformance/landscape/README.md b/wai/quantum-conformance/landscape/README.md index 261ac579e..37865089a 100644 --- a/wai/quantum-conformance/landscape/README.md +++ b/wai/quantum-conformance/landscape/README.md @@ -132,8 +132,12 @@ of board power the priced line leaves out. A run's joules are not measured here, ## A member's conformance A member is held to [`vectors.json`](vectors.json) as §7 of the extension says, and the host finds its parity by running its kernels there: `settle` bit for bit -for a `bit-exact` settle kernel; `chains` bit for bit for a `bit-exact` sampling kernel; otherwise the `statistical` battery, eight small workloads of 20,000 chains each, -their final states against the exact distribution. `wai-quantum/tests/landscape_conformance.rs` reads the file as a member's checker would, from the file alone, -and runs the battery on the reference's own kernel, which passes; on five kernels each wrong in one way (cells moved all at once, even then odd, the schedule -ignored, a table clamped at 5, the last sweep sampled), each of which fails at least one workload; and on one that colours its cells properly, which passes -and is granted `statistical`. +for a `bit-exact` settle kernel, every vector it takes, and it takes every vector whose field bound is at most 2³¹ − 1; `chains` bit for bit for a `bit-exact` +sampling kernel, the same way; otherwise the `statistical` battery, thirteen workloads of 20,000 chains each, each block's final states against its exact +distribution. `wai-quantum/tests/landscape_conformance.rs` reads the file as a member's checker would, from the file alone, and holds both parts to kernels +that are wrong in one way each. The vectors refuse a dropped carry in a 32 by 32 product, a cut point compared by its high word, fields held in 16 or 32 +bits, a check of the fields that forgets the couplings, cells last to first, a draw counter that starts again at each of a device's windows of sweeps, a +run that stops after one window, chains past the first 64 left alone, a settle whose cells past 64 or 256 never move or that reports its end a sweep late, +and a member that declines below the line. The battery passes the reference's own kernel, one that colours its cells properly and one that takes its +cells last to first, and refuses kernels that move cells all at once, even then odd, ignore the schedule or keep only its end, clamp the table at 5, +sample the last sweep, hold their fields in 16 or 32 bits, or leave cells from the fifth on where they started. diff --git a/wai/quantum-conformance/landscape/vectors.json b/wai/quantum-conformance/landscape/vectors.json index 388157b95..448b05fe3 100644 --- a/wai/quantum-conformance/landscape/vectors.json +++ b/wai/quantum-conformance/landscape/vectors.json @@ -46,6 +46,7 @@ ], "cost": 0 }, + "field_bound": 3, "start": "+++-", "bound": 40, "expect": { @@ -214,6 +215,7 @@ ], "cost": 0 }, + "field_bound": 7, "start": "+++-+-+-", "bound": 40, "expect": { @@ -850,6 +852,7 @@ ], "cost": 0 }, + "field_bound": 15, "start": "+++-+---+-+-+-+-", "bound": 40, "expect": { @@ -3382,6 +3385,7 @@ ], "cost": 0 }, + "field_bound": 31, "start": "+++-+---+-+++-+---+-+-+-+-+-+-+-", "bound": 40, "expect": { @@ -3409,6 +3413,7 @@ ], "cost": 0 }, + "field_bound": 1, "start": "++", "bound": 20, "expect": { @@ -3436,6 +3441,7 @@ ], "cost": 0 }, + "field_bound": 1, "start": "+-", "bound": 20, "expect": { @@ -3487,6 +3493,7 @@ ], "cost": 0 }, + "field_bound": 4, "start": "000000", "bound": 3, "expect": { @@ -3538,6 +3545,7 @@ ], "cost": 0 }, + "field_bound": 4, "start": "000000", "bound": 20, "expect": { @@ -3589,6 +3597,7 @@ ], "cost": 0 }, + "field_bound": 4, "start": "000000", "bound": 1, "expect": { @@ -3639,6 +3648,7 @@ ], "cost": 1 }, + "field_bound": 3, "start": "+++00", "bound": 20, "expect": { @@ -3689,6 +3699,7 @@ ], "cost": 1 }, + "field_bound": 3, "start": "0-0+-", "bound": 20, "expect": { @@ -3728,6 +3739,7 @@ ], "cost": 1099511627776 }, + "field_bound": 1099511627777, "start": "+0-+", "bound": 20, "expect": { @@ -3755,6 +3767,7 @@ ], "cost": 0 }, + "field_bound": 2, "start": "00", "bound": 60, "expect": { @@ -3764,6 +3777,21499 @@ "accumulates": 4, "state_hash": "082f2307b4e88e77" } + }, + { + "name": "path/n=65", + "landscape": { + "n": 65, + "couplings": [ + [ + 0, + 1, + 1 + ], + [ + 1, + 2, + 1 + ], + [ + 2, + 3, + 1 + ], + [ + 3, + 4, + 1 + ], + [ + 4, + 5, + 1 + ], + [ + 5, + 6, + 1 + ], + [ + 6, + 7, + 1 + ], + [ + 7, + 8, + 1 + ], + [ + 8, + 9, + 1 + ], + [ + 9, + 10, + 1 + ], + [ + 10, + 11, + 1 + ], + [ + 11, + 12, + 1 + ], + [ + 12, + 13, + 1 + ], + [ + 13, + 14, + 1 + ], + [ + 14, + 15, + 1 + ], + [ + 15, + 16, + 1 + ], + [ + 16, + 17, + 1 + ], + [ + 17, + 18, + 1 + ], + [ + 18, + 19, + 1 + ], + [ + 19, + 20, + 1 + ], + [ + 20, + 21, + 1 + ], + [ + 21, + 22, + 1 + ], + [ + 22, + 23, + 1 + ], + [ + 23, + 24, + 1 + ], + [ + 24, + 25, + 1 + ], + [ + 25, + 26, + 1 + ], + [ + 26, + 27, + 1 + ], + [ + 27, + 28, + 1 + ], + [ + 28, + 29, + 1 + ], + [ + 29, + 30, + 1 + ], + [ + 30, + 31, + 1 + ], + [ + 31, + 32, + 1 + ], + [ + 32, + 33, + 1 + ], + [ + 33, + 34, + 1 + ], + [ + 34, + 35, + 1 + ], + [ + 35, + 36, + 1 + ], + [ + 36, + 37, + 1 + ], + [ + 37, + 38, + 1 + ], + [ + 38, + 39, + 1 + ], + [ + 39, + 40, + 1 + ], + [ + 40, + 41, + 1 + ], + [ + 41, + 42, + 1 + ], + [ + 42, + 43, + 1 + ], + [ + 43, + 44, + 1 + ], + [ + 44, + 45, + 1 + ], + [ + 45, + 46, + 1 + ], + [ + 46, + 47, + 1 + ], + [ + 47, + 48, + 1 + ], + [ + 48, + 49, + 1 + ], + [ + 49, + 50, + 1 + ], + [ + 50, + 51, + 1 + ], + [ + 51, + 52, + 1 + ], + [ + 52, + 53, + 1 + ], + [ + 53, + 54, + 1 + ], + [ + 54, + 55, + 1 + ], + [ + 55, + 56, + 1 + ], + [ + 56, + 57, + 1 + ], + [ + 57, + 58, + 1 + ], + [ + 58, + 59, + 1 + ], + [ + 59, + 60, + 1 + ], + [ + 60, + 61, + 1 + ], + [ + 61, + 62, + 1 + ], + [ + 62, + 63, + 1 + ], + [ + 63, + 64, + 1 + ] + ], + "field": [ + 3, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "cost": 0 + }, + "field_bound": 4, + "start": "00000000000000000000000000000000000000000000000000000000000000000", + "bound": 130, + "expect": { + "state": "+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++", + "sweeps": 66, + "term": "fixed-point", + "accumulates": 8448, + "state_hash": "07af0b693e806b45" + } + }, + { + "name": "path/n=130", + "landscape": { + "n": 130, + "couplings": [ + [ + 0, + 1, + 1 + ], + [ + 1, + 2, + 1 + ], + [ + 2, + 3, + 1 + ], + [ + 3, + 4, + 1 + ], + [ + 4, + 5, + 1 + ], + [ + 5, + 6, + 1 + ], + [ + 6, + 7, + 1 + ], + [ + 7, + 8, + 1 + ], + [ + 8, + 9, + 1 + ], + [ + 9, + 10, + 1 + ], + [ + 10, + 11, + 1 + ], + [ + 11, + 12, + 1 + ], + [ + 12, + 13, + 1 + ], + [ + 13, + 14, + 1 + ], + [ + 14, + 15, + 1 + ], + [ + 15, + 16, + 1 + ], + [ + 16, + 17, + 1 + ], + [ + 17, + 18, + 1 + ], + [ + 18, + 19, + 1 + ], + [ + 19, + 20, + 1 + ], + [ + 20, + 21, + 1 + ], + [ + 21, + 22, + 1 + ], + [ + 22, + 23, + 1 + ], + [ + 23, + 24, + 1 + ], + [ + 24, + 25, + 1 + ], + [ + 25, + 26, + 1 + ], + [ + 26, + 27, + 1 + ], + [ + 27, + 28, + 1 + ], + [ + 28, + 29, + 1 + ], + [ + 29, + 30, + 1 + ], + [ + 30, + 31, + 1 + ], + [ + 31, + 32, + 1 + ], + [ + 32, + 33, + 1 + ], + [ + 33, + 34, + 1 + ], + [ + 34, + 35, + 1 + ], + [ + 35, + 36, + 1 + ], + [ + 36, + 37, + 1 + ], + [ + 37, + 38, + 1 + ], + [ + 38, + 39, + 1 + ], + [ + 39, + 40, + 1 + ], + [ + 40, + 41, + 1 + ], + [ + 41, + 42, + 1 + ], + [ + 42, + 43, + 1 + ], + [ + 43, + 44, + 1 + ], + [ + 44, + 45, + 1 + ], + [ + 45, + 46, + 1 + ], + [ + 46, + 47, + 1 + ], + [ + 47, + 48, + 1 + ], + [ + 48, + 49, + 1 + ], + [ + 49, + 50, + 1 + ], + [ + 50, + 51, + 1 + ], + [ + 51, + 52, + 1 + ], + [ + 52, + 53, + 1 + ], + [ + 53, + 54, + 1 + ], + [ + 54, + 55, + 1 + ], + [ + 55, + 56, + 1 + ], + [ + 56, + 57, + 1 + ], + [ + 57, + 58, + 1 + ], + [ + 58, + 59, + 1 + ], + [ + 59, + 60, + 1 + ], + [ + 60, + 61, + 1 + ], + [ + 61, + 62, + 1 + ], + [ + 62, + 63, + 1 + ], + [ + 63, + 64, + 1 + ], + [ + 64, + 65, + 1 + ], + [ + 65, + 66, + 1 + ], + [ + 66, + 67, + 1 + ], + [ + 67, + 68, + 1 + ], + [ + 68, + 69, + 1 + ], + [ + 69, + 70, + 1 + ], + [ + 70, + 71, + 1 + ], + [ + 71, + 72, + 1 + ], + [ + 72, + 73, + 1 + ], + [ + 73, + 74, + 1 + ], + [ + 74, + 75, + 1 + ], + [ + 75, + 76, + 1 + ], + [ + 76, + 77, + 1 + ], + [ + 77, + 78, + 1 + ], + [ + 78, + 79, + 1 + ], + [ + 79, + 80, + 1 + ], + [ + 80, + 81, + 1 + ], + [ + 81, + 82, + 1 + ], + [ + 82, + 83, + 1 + ], + [ + 83, + 84, + 1 + ], + [ + 84, + 85, + 1 + ], + [ + 85, + 86, + 1 + ], + [ + 86, + 87, + 1 + ], + [ + 87, + 88, + 1 + ], + [ + 88, + 89, + 1 + ], + [ + 89, + 90, + 1 + ], + [ + 90, + 91, + 1 + ], + [ + 91, + 92, + 1 + ], + [ + 92, + 93, + 1 + ], + [ + 93, + 94, + 1 + ], + [ + 94, + 95, + 1 + ], + [ + 95, + 96, + 1 + ], + [ + 96, + 97, + 1 + ], + [ + 97, + 98, + 1 + ], + [ + 98, + 99, + 1 + ], + [ + 99, + 100, + 1 + ], + [ + 100, + 101, + 1 + ], + [ + 101, + 102, + 1 + ], + [ + 102, + 103, + 1 + ], + [ + 103, + 104, + 1 + ], + [ + 104, + 105, + 1 + ], + [ + 105, + 106, + 1 + ], + [ + 106, + 107, + 1 + ], + [ + 107, + 108, + 1 + ], + [ + 108, + 109, + 1 + ], + [ + 109, + 110, + 1 + ], + [ + 110, + 111, + 1 + ], + [ + 111, + 112, + 1 + ], + [ + 112, + 113, + 1 + ], + [ + 113, + 114, + 1 + ], + [ + 114, + 115, + 1 + ], + [ + 115, + 116, + 1 + ], + [ + 116, + 117, + 1 + ], + [ + 117, + 118, + 1 + ], + [ + 118, + 119, + 1 + ], + [ + 119, + 120, + 1 + ], + [ + 120, + 121, + 1 + ], + [ + 121, + 122, + 1 + ], + [ + 122, + 123, + 1 + ], + [ + 123, + 124, + 1 + ], + [ + 124, + 125, + 1 + ], + [ + 125, + 126, + 1 + ], + [ + 126, + 127, + 1 + ], + [ + 127, + 128, + 1 + ], + [ + 128, + 129, + 1 + ] + ], + "field": [ + 3, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "cost": 0 + }, + "field_bound": 4, + "start": "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "bound": 260, + "expect": { + "state": "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++", + "sweeps": 131, + "term": "fixed-point", + "accumulates": 33798, + "state_hash": "faf73d34848b59a5" + } + }, + { + "name": "path/n=300", + "landscape": { + "n": 300, + "couplings": [ + [ + 0, + 1, + 1 + ], + [ + 1, + 2, + 1 + ], + [ + 2, + 3, + 1 + ], + [ + 3, + 4, + 1 + ], + [ + 4, + 5, + 1 + ], + [ + 5, + 6, + 1 + ], + [ + 6, + 7, + 1 + ], + [ + 7, + 8, + 1 + ], + [ + 8, + 9, + 1 + ], + [ + 9, + 10, + 1 + ], + [ + 10, + 11, + 1 + ], + [ + 11, + 12, + 1 + ], + [ + 12, + 13, + 1 + ], + [ + 13, + 14, + 1 + ], + [ + 14, + 15, + 1 + ], + [ + 15, + 16, + 1 + ], + [ + 16, + 17, + 1 + ], + [ + 17, + 18, + 1 + ], + [ + 18, + 19, + 1 + ], + [ + 19, + 20, + 1 + ], + [ + 20, + 21, + 1 + ], + [ + 21, + 22, + 1 + ], + [ + 22, + 23, + 1 + ], + [ + 23, + 24, + 1 + ], + [ + 24, + 25, + 1 + ], + [ + 25, + 26, + 1 + ], + [ + 26, + 27, + 1 + ], + [ + 27, + 28, + 1 + ], + [ + 28, + 29, + 1 + ], + [ + 29, + 30, + 1 + ], + [ + 30, + 31, + 1 + ], + [ + 31, + 32, + 1 + ], + [ + 32, + 33, + 1 + ], + [ + 33, + 34, + 1 + ], + [ + 34, + 35, + 1 + ], + [ + 35, + 36, + 1 + ], + [ + 36, + 37, + 1 + ], + [ + 37, + 38, + 1 + ], + [ + 38, + 39, + 1 + ], + [ + 39, + 40, + 1 + ], + [ + 40, + 41, + 1 + ], + [ + 41, + 42, + 1 + ], + [ + 42, + 43, + 1 + ], + [ + 43, + 44, + 1 + ], + [ + 44, + 45, + 1 + ], + [ + 45, + 46, + 1 + ], + [ + 46, + 47, + 1 + ], + [ + 47, + 48, + 1 + ], + [ + 48, + 49, + 1 + ], + [ + 49, + 50, + 1 + ], + [ + 50, + 51, + 1 + ], + [ + 51, + 52, + 1 + ], + [ + 52, + 53, + 1 + ], + [ + 53, + 54, + 1 + ], + [ + 54, + 55, + 1 + ], + [ + 55, + 56, + 1 + ], + [ + 56, + 57, + 1 + ], + [ + 57, + 58, + 1 + ], + [ + 58, + 59, + 1 + ], + [ + 59, + 60, + 1 + ], + [ + 60, + 61, + 1 + ], + [ + 61, + 62, + 1 + ], + [ + 62, + 63, + 1 + ], + [ + 63, + 64, + 1 + ], + [ + 64, + 65, + 1 + ], + [ + 65, + 66, + 1 + ], + [ + 66, + 67, + 1 + ], + [ + 67, + 68, + 1 + ], + [ + 68, + 69, + 1 + ], + [ + 69, + 70, + 1 + ], + [ + 70, + 71, + 1 + ], + [ + 71, + 72, + 1 + ], + [ + 72, + 73, + 1 + ], + [ + 73, + 74, + 1 + ], + [ + 74, + 75, + 1 + ], + [ + 75, + 76, + 1 + ], + [ + 76, + 77, + 1 + ], + [ + 77, + 78, + 1 + ], + [ + 78, + 79, + 1 + ], + [ + 79, + 80, + 1 + ], + [ + 80, + 81, + 1 + ], + [ + 81, + 82, + 1 + ], + [ + 82, + 83, + 1 + ], + [ + 83, + 84, + 1 + ], + [ + 84, + 85, + 1 + ], + [ + 85, + 86, + 1 + ], + [ + 86, + 87, + 1 + ], + [ + 87, + 88, + 1 + ], + [ + 88, + 89, + 1 + ], + [ + 89, + 90, + 1 + ], + [ + 90, + 91, + 1 + ], + [ + 91, + 92, + 1 + ], + [ + 92, + 93, + 1 + ], + [ + 93, + 94, + 1 + ], + [ + 94, + 95, + 1 + ], + [ + 95, + 96, + 1 + ], + [ + 96, + 97, + 1 + ], + [ + 97, + 98, + 1 + ], + [ + 98, + 99, + 1 + ], + [ + 99, + 100, + 1 + ], + [ + 100, + 101, + 1 + ], + [ + 101, + 102, + 1 + ], + [ + 102, + 103, + 1 + ], + [ + 103, + 104, + 1 + ], + [ + 104, + 105, + 1 + ], + [ + 105, + 106, + 1 + ], + [ + 106, + 107, + 1 + ], + [ + 107, + 108, + 1 + ], + [ + 108, + 109, + 1 + ], + [ + 109, + 110, + 1 + ], + [ + 110, + 111, + 1 + ], + [ + 111, + 112, + 1 + ], + [ + 112, + 113, + 1 + ], + [ + 113, + 114, + 1 + ], + [ + 114, + 115, + 1 + ], + [ + 115, + 116, + 1 + ], + [ + 116, + 117, + 1 + ], + [ + 117, + 118, + 1 + ], + [ + 118, + 119, + 1 + ], + [ + 119, + 120, + 1 + ], + [ + 120, + 121, + 1 + ], + [ + 121, + 122, + 1 + ], + [ + 122, + 123, + 1 + ], + [ + 123, + 124, + 1 + ], + [ + 124, + 125, + 1 + ], + [ + 125, + 126, + 1 + ], + [ + 126, + 127, + 1 + ], + [ + 127, + 128, + 1 + ], + [ + 128, + 129, + 1 + ], + [ + 129, + 130, + 1 + ], + [ + 130, + 131, + 1 + ], + [ + 131, + 132, + 1 + ], + [ + 132, + 133, + 1 + ], + [ + 133, + 134, + 1 + ], + [ + 134, + 135, + 1 + ], + [ + 135, + 136, + 1 + ], + [ + 136, + 137, + 1 + ], + [ + 137, + 138, + 1 + ], + [ + 138, + 139, + 1 + ], + [ + 139, + 140, + 1 + ], + [ + 140, + 141, + 1 + ], + [ + 141, + 142, + 1 + ], + [ + 142, + 143, + 1 + ], + [ + 143, + 144, + 1 + ], + [ + 144, + 145, + 1 + ], + [ + 145, + 146, + 1 + ], + [ + 146, + 147, + 1 + ], + [ + 147, + 148, + 1 + ], + [ + 148, + 149, + 1 + ], + [ + 149, + 150, + 1 + ], + [ + 150, + 151, + 1 + ], + [ + 151, + 152, + 1 + ], + [ + 152, + 153, + 1 + ], + [ + 153, + 154, + 1 + ], + [ + 154, + 155, + 1 + ], + [ + 155, + 156, + 1 + ], + [ + 156, + 157, + 1 + ], + [ + 157, + 158, + 1 + ], + [ + 158, + 159, + 1 + ], + [ + 159, + 160, + 1 + ], + [ + 160, + 161, + 1 + ], + [ + 161, + 162, + 1 + ], + [ + 162, + 163, + 1 + ], + [ + 163, + 164, + 1 + ], + [ + 164, + 165, + 1 + ], + [ + 165, + 166, + 1 + ], + [ + 166, + 167, + 1 + ], + [ + 167, + 168, + 1 + ], + [ + 168, + 169, + 1 + ], + [ + 169, + 170, + 1 + ], + [ + 170, + 171, + 1 + ], + [ + 171, + 172, + 1 + ], + [ + 172, + 173, + 1 + ], + [ + 173, + 174, + 1 + ], + [ + 174, + 175, + 1 + ], + [ + 175, + 176, + 1 + ], + [ + 176, + 177, + 1 + ], + [ + 177, + 178, + 1 + ], + [ + 178, + 179, + 1 + ], + [ + 179, + 180, + 1 + ], + [ + 180, + 181, + 1 + ], + [ + 181, + 182, + 1 + ], + [ + 182, + 183, + 1 + ], + [ + 183, + 184, + 1 + ], + [ + 184, + 185, + 1 + ], + [ + 185, + 186, + 1 + ], + [ + 186, + 187, + 1 + ], + [ + 187, + 188, + 1 + ], + [ + 188, + 189, + 1 + ], + [ + 189, + 190, + 1 + ], + [ + 190, + 191, + 1 + ], + [ + 191, + 192, + 1 + ], + [ + 192, + 193, + 1 + ], + [ + 193, + 194, + 1 + ], + [ + 194, + 195, + 1 + ], + [ + 195, + 196, + 1 + ], + [ + 196, + 197, + 1 + ], + [ + 197, + 198, + 1 + ], + [ + 198, + 199, + 1 + ], + [ + 199, + 200, + 1 + ], + [ + 200, + 201, + 1 + ], + [ + 201, + 202, + 1 + ], + [ + 202, + 203, + 1 + ], + [ + 203, + 204, + 1 + ], + [ + 204, + 205, + 1 + ], + [ + 205, + 206, + 1 + ], + [ + 206, + 207, + 1 + ], + [ + 207, + 208, + 1 + ], + [ + 208, + 209, + 1 + ], + [ + 209, + 210, + 1 + ], + [ + 210, + 211, + 1 + ], + [ + 211, + 212, + 1 + ], + [ + 212, + 213, + 1 + ], + [ + 213, + 214, + 1 + ], + [ + 214, + 215, + 1 + ], + [ + 215, + 216, + 1 + ], + [ + 216, + 217, + 1 + ], + [ + 217, + 218, + 1 + ], + [ + 218, + 219, + 1 + ], + [ + 219, + 220, + 1 + ], + [ + 220, + 221, + 1 + ], + [ + 221, + 222, + 1 + ], + [ + 222, + 223, + 1 + ], + [ + 223, + 224, + 1 + ], + [ + 224, + 225, + 1 + ], + [ + 225, + 226, + 1 + ], + [ + 226, + 227, + 1 + ], + [ + 227, + 228, + 1 + ], + [ + 228, + 229, + 1 + ], + [ + 229, + 230, + 1 + ], + [ + 230, + 231, + 1 + ], + [ + 231, + 232, + 1 + ], + [ + 232, + 233, + 1 + ], + [ + 233, + 234, + 1 + ], + [ + 234, + 235, + 1 + ], + [ + 235, + 236, + 1 + ], + [ + 236, + 237, + 1 + ], + [ + 237, + 238, + 1 + ], + [ + 238, + 239, + 1 + ], + [ + 239, + 240, + 1 + ], + [ + 240, + 241, + 1 + ], + [ + 241, + 242, + 1 + ], + [ + 242, + 243, + 1 + ], + [ + 243, + 244, + 1 + ], + [ + 244, + 245, + 1 + ], + [ + 245, + 246, + 1 + ], + [ + 246, + 247, + 1 + ], + [ + 247, + 248, + 1 + ], + [ + 248, + 249, + 1 + ], + [ + 249, + 250, + 1 + ], + [ + 250, + 251, + 1 + ], + [ + 251, + 252, + 1 + ], + [ + 252, + 253, + 1 + ], + [ + 253, + 254, + 1 + ], + [ + 254, + 255, + 1 + ], + [ + 255, + 256, + 1 + ], + [ + 256, + 257, + 1 + ], + [ + 257, + 258, + 1 + ], + [ + 258, + 259, + 1 + ], + [ + 259, + 260, + 1 + ], + [ + 260, + 261, + 1 + ], + [ + 261, + 262, + 1 + ], + [ + 262, + 263, + 1 + ], + [ + 263, + 264, + 1 + ], + [ + 264, + 265, + 1 + ], + [ + 265, + 266, + 1 + ], + [ + 266, + 267, + 1 + ], + [ + 267, + 268, + 1 + ], + [ + 268, + 269, + 1 + ], + [ + 269, + 270, + 1 + ], + [ + 270, + 271, + 1 + ], + [ + 271, + 272, + 1 + ], + [ + 272, + 273, + 1 + ], + [ + 273, + 274, + 1 + ], + [ + 274, + 275, + 1 + ], + [ + 275, + 276, + 1 + ], + [ + 276, + 277, + 1 + ], + [ + 277, + 278, + 1 + ], + [ + 278, + 279, + 1 + ], + [ + 279, + 280, + 1 + ], + [ + 280, + 281, + 1 + ], + [ + 281, + 282, + 1 + ], + [ + 282, + 283, + 1 + ], + [ + 283, + 284, + 1 + ], + [ + 284, + 285, + 1 + ], + [ + 285, + 286, + 1 + ], + [ + 286, + 287, + 1 + ], + [ + 287, + 288, + 1 + ], + [ + 288, + 289, + 1 + ], + [ + 289, + 290, + 1 + ], + [ + 290, + 291, + 1 + ], + [ + 291, + 292, + 1 + ], + [ + 292, + 293, + 1 + ], + [ + 293, + 294, + 1 + ], + [ + 294, + 295, + 1 + ], + [ + 295, + 296, + 1 + ], + [ + 296, + 297, + 1 + ], + [ + 297, + 298, + 1 + ], + [ + 298, + 299, + 1 + ] + ], + "field": [ + 3, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "cost": 0 + }, + "field_bound": 4, + "start": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "bound": 600, + "expect": { + "state": "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++", + "sweeps": 301, + "term": "fixed-point", + "accumulates": 179998, + "state_hash": "3dacb19c179e6e25" + } + }, + { + "name": "path-stopped-in-a-later-batch", + "landscape": { + "n": 300, + "couplings": [ + [ + 0, + 1, + 1 + ], + [ + 1, + 2, + 1 + ], + [ + 2, + 3, + 1 + ], + [ + 3, + 4, + 1 + ], + [ + 4, + 5, + 1 + ], + [ + 5, + 6, + 1 + ], + [ + 6, + 7, + 1 + ], + [ + 7, + 8, + 1 + ], + [ + 8, + 9, + 1 + ], + [ + 9, + 10, + 1 + ], + [ + 10, + 11, + 1 + ], + [ + 11, + 12, + 1 + ], + [ + 12, + 13, + 1 + ], + [ + 13, + 14, + 1 + ], + [ + 14, + 15, + 1 + ], + [ + 15, + 16, + 1 + ], + [ + 16, + 17, + 1 + ], + [ + 17, + 18, + 1 + ], + [ + 18, + 19, + 1 + ], + [ + 19, + 20, + 1 + ], + [ + 20, + 21, + 1 + ], + [ + 21, + 22, + 1 + ], + [ + 22, + 23, + 1 + ], + [ + 23, + 24, + 1 + ], + [ + 24, + 25, + 1 + ], + [ + 25, + 26, + 1 + ], + [ + 26, + 27, + 1 + ], + [ + 27, + 28, + 1 + ], + [ + 28, + 29, + 1 + ], + [ + 29, + 30, + 1 + ], + [ + 30, + 31, + 1 + ], + [ + 31, + 32, + 1 + ], + [ + 32, + 33, + 1 + ], + [ + 33, + 34, + 1 + ], + [ + 34, + 35, + 1 + ], + [ + 35, + 36, + 1 + ], + [ + 36, + 37, + 1 + ], + [ + 37, + 38, + 1 + ], + [ + 38, + 39, + 1 + ], + [ + 39, + 40, + 1 + ], + [ + 40, + 41, + 1 + ], + [ + 41, + 42, + 1 + ], + [ + 42, + 43, + 1 + ], + [ + 43, + 44, + 1 + ], + [ + 44, + 45, + 1 + ], + [ + 45, + 46, + 1 + ], + [ + 46, + 47, + 1 + ], + [ + 47, + 48, + 1 + ], + [ + 48, + 49, + 1 + ], + [ + 49, + 50, + 1 + ], + [ + 50, + 51, + 1 + ], + [ + 51, + 52, + 1 + ], + [ + 52, + 53, + 1 + ], + [ + 53, + 54, + 1 + ], + [ + 54, + 55, + 1 + ], + [ + 55, + 56, + 1 + ], + [ + 56, + 57, + 1 + ], + [ + 57, + 58, + 1 + ], + [ + 58, + 59, + 1 + ], + [ + 59, + 60, + 1 + ], + [ + 60, + 61, + 1 + ], + [ + 61, + 62, + 1 + ], + [ + 62, + 63, + 1 + ], + [ + 63, + 64, + 1 + ], + [ + 64, + 65, + 1 + ], + [ + 65, + 66, + 1 + ], + [ + 66, + 67, + 1 + ], + [ + 67, + 68, + 1 + ], + [ + 68, + 69, + 1 + ], + [ + 69, + 70, + 1 + ], + [ + 70, + 71, + 1 + ], + [ + 71, + 72, + 1 + ], + [ + 72, + 73, + 1 + ], + [ + 73, + 74, + 1 + ], + [ + 74, + 75, + 1 + ], + [ + 75, + 76, + 1 + ], + [ + 76, + 77, + 1 + ], + [ + 77, + 78, + 1 + ], + [ + 78, + 79, + 1 + ], + [ + 79, + 80, + 1 + ], + [ + 80, + 81, + 1 + ], + [ + 81, + 82, + 1 + ], + [ + 82, + 83, + 1 + ], + [ + 83, + 84, + 1 + ], + [ + 84, + 85, + 1 + ], + [ + 85, + 86, + 1 + ], + [ + 86, + 87, + 1 + ], + [ + 87, + 88, + 1 + ], + [ + 88, + 89, + 1 + ], + [ + 89, + 90, + 1 + ], + [ + 90, + 91, + 1 + ], + [ + 91, + 92, + 1 + ], + [ + 92, + 93, + 1 + ], + [ + 93, + 94, + 1 + ], + [ + 94, + 95, + 1 + ], + [ + 95, + 96, + 1 + ], + [ + 96, + 97, + 1 + ], + [ + 97, + 98, + 1 + ], + [ + 98, + 99, + 1 + ], + [ + 99, + 100, + 1 + ], + [ + 100, + 101, + 1 + ], + [ + 101, + 102, + 1 + ], + [ + 102, + 103, + 1 + ], + [ + 103, + 104, + 1 + ], + [ + 104, + 105, + 1 + ], + [ + 105, + 106, + 1 + ], + [ + 106, + 107, + 1 + ], + [ + 107, + 108, + 1 + ], + [ + 108, + 109, + 1 + ], + [ + 109, + 110, + 1 + ], + [ + 110, + 111, + 1 + ], + [ + 111, + 112, + 1 + ], + [ + 112, + 113, + 1 + ], + [ + 113, + 114, + 1 + ], + [ + 114, + 115, + 1 + ], + [ + 115, + 116, + 1 + ], + [ + 116, + 117, + 1 + ], + [ + 117, + 118, + 1 + ], + [ + 118, + 119, + 1 + ], + [ + 119, + 120, + 1 + ], + [ + 120, + 121, + 1 + ], + [ + 121, + 122, + 1 + ], + [ + 122, + 123, + 1 + ], + [ + 123, + 124, + 1 + ], + [ + 124, + 125, + 1 + ], + [ + 125, + 126, + 1 + ], + [ + 126, + 127, + 1 + ], + [ + 127, + 128, + 1 + ], + [ + 128, + 129, + 1 + ], + [ + 129, + 130, + 1 + ], + [ + 130, + 131, + 1 + ], + [ + 131, + 132, + 1 + ], + [ + 132, + 133, + 1 + ], + [ + 133, + 134, + 1 + ], + [ + 134, + 135, + 1 + ], + [ + 135, + 136, + 1 + ], + [ + 136, + 137, + 1 + ], + [ + 137, + 138, + 1 + ], + [ + 138, + 139, + 1 + ], + [ + 139, + 140, + 1 + ], + [ + 140, + 141, + 1 + ], + [ + 141, + 142, + 1 + ], + [ + 142, + 143, + 1 + ], + [ + 143, + 144, + 1 + ], + [ + 144, + 145, + 1 + ], + [ + 145, + 146, + 1 + ], + [ + 146, + 147, + 1 + ], + [ + 147, + 148, + 1 + ], + [ + 148, + 149, + 1 + ], + [ + 149, + 150, + 1 + ], + [ + 150, + 151, + 1 + ], + [ + 151, + 152, + 1 + ], + [ + 152, + 153, + 1 + ], + [ + 153, + 154, + 1 + ], + [ + 154, + 155, + 1 + ], + [ + 155, + 156, + 1 + ], + [ + 156, + 157, + 1 + ], + [ + 157, + 158, + 1 + ], + [ + 158, + 159, + 1 + ], + [ + 159, + 160, + 1 + ], + [ + 160, + 161, + 1 + ], + [ + 161, + 162, + 1 + ], + [ + 162, + 163, + 1 + ], + [ + 163, + 164, + 1 + ], + [ + 164, + 165, + 1 + ], + [ + 165, + 166, + 1 + ], + [ + 166, + 167, + 1 + ], + [ + 167, + 168, + 1 + ], + [ + 168, + 169, + 1 + ], + [ + 169, + 170, + 1 + ], + [ + 170, + 171, + 1 + ], + [ + 171, + 172, + 1 + ], + [ + 172, + 173, + 1 + ], + [ + 173, + 174, + 1 + ], + [ + 174, + 175, + 1 + ], + [ + 175, + 176, + 1 + ], + [ + 176, + 177, + 1 + ], + [ + 177, + 178, + 1 + ], + [ + 178, + 179, + 1 + ], + [ + 179, + 180, + 1 + ], + [ + 180, + 181, + 1 + ], + [ + 181, + 182, + 1 + ], + [ + 182, + 183, + 1 + ], + [ + 183, + 184, + 1 + ], + [ + 184, + 185, + 1 + ], + [ + 185, + 186, + 1 + ], + [ + 186, + 187, + 1 + ], + [ + 187, + 188, + 1 + ], + [ + 188, + 189, + 1 + ], + [ + 189, + 190, + 1 + ], + [ + 190, + 191, + 1 + ], + [ + 191, + 192, + 1 + ], + [ + 192, + 193, + 1 + ], + [ + 193, + 194, + 1 + ], + [ + 194, + 195, + 1 + ], + [ + 195, + 196, + 1 + ], + [ + 196, + 197, + 1 + ], + [ + 197, + 198, + 1 + ], + [ + 198, + 199, + 1 + ], + [ + 199, + 200, + 1 + ], + [ + 200, + 201, + 1 + ], + [ + 201, + 202, + 1 + ], + [ + 202, + 203, + 1 + ], + [ + 203, + 204, + 1 + ], + [ + 204, + 205, + 1 + ], + [ + 205, + 206, + 1 + ], + [ + 206, + 207, + 1 + ], + [ + 207, + 208, + 1 + ], + [ + 208, + 209, + 1 + ], + [ + 209, + 210, + 1 + ], + [ + 210, + 211, + 1 + ], + [ + 211, + 212, + 1 + ], + [ + 212, + 213, + 1 + ], + [ + 213, + 214, + 1 + ], + [ + 214, + 215, + 1 + ], + [ + 215, + 216, + 1 + ], + [ + 216, + 217, + 1 + ], + [ + 217, + 218, + 1 + ], + [ + 218, + 219, + 1 + ], + [ + 219, + 220, + 1 + ], + [ + 220, + 221, + 1 + ], + [ + 221, + 222, + 1 + ], + [ + 222, + 223, + 1 + ], + [ + 223, + 224, + 1 + ], + [ + 224, + 225, + 1 + ], + [ + 225, + 226, + 1 + ], + [ + 226, + 227, + 1 + ], + [ + 227, + 228, + 1 + ], + [ + 228, + 229, + 1 + ], + [ + 229, + 230, + 1 + ], + [ + 230, + 231, + 1 + ], + [ + 231, + 232, + 1 + ], + [ + 232, + 233, + 1 + ], + [ + 233, + 234, + 1 + ], + [ + 234, + 235, + 1 + ], + [ + 235, + 236, + 1 + ], + [ + 236, + 237, + 1 + ], + [ + 237, + 238, + 1 + ], + [ + 238, + 239, + 1 + ], + [ + 239, + 240, + 1 + ], + [ + 240, + 241, + 1 + ], + [ + 241, + 242, + 1 + ], + [ + 242, + 243, + 1 + ], + [ + 243, + 244, + 1 + ], + [ + 244, + 245, + 1 + ], + [ + 245, + 246, + 1 + ], + [ + 246, + 247, + 1 + ], + [ + 247, + 248, + 1 + ], + [ + 248, + 249, + 1 + ], + [ + 249, + 250, + 1 + ], + [ + 250, + 251, + 1 + ], + [ + 251, + 252, + 1 + ], + [ + 252, + 253, + 1 + ], + [ + 253, + 254, + 1 + ], + [ + 254, + 255, + 1 + ], + [ + 255, + 256, + 1 + ], + [ + 256, + 257, + 1 + ], + [ + 257, + 258, + 1 + ], + [ + 258, + 259, + 1 + ], + [ + 259, + 260, + 1 + ], + [ + 260, + 261, + 1 + ], + [ + 261, + 262, + 1 + ], + [ + 262, + 263, + 1 + ], + [ + 263, + 264, + 1 + ], + [ + 264, + 265, + 1 + ], + [ + 265, + 266, + 1 + ], + [ + 266, + 267, + 1 + ], + [ + 267, + 268, + 1 + ], + [ + 268, + 269, + 1 + ], + [ + 269, + 270, + 1 + ], + [ + 270, + 271, + 1 + ], + [ + 271, + 272, + 1 + ], + [ + 272, + 273, + 1 + ], + [ + 273, + 274, + 1 + ], + [ + 274, + 275, + 1 + ], + [ + 275, + 276, + 1 + ], + [ + 276, + 277, + 1 + ], + [ + 277, + 278, + 1 + ], + [ + 278, + 279, + 1 + ], + [ + 279, + 280, + 1 + ], + [ + 280, + 281, + 1 + ], + [ + 281, + 282, + 1 + ], + [ + 282, + 283, + 1 + ], + [ + 283, + 284, + 1 + ], + [ + 284, + 285, + 1 + ], + [ + 285, + 286, + 1 + ], + [ + 286, + 287, + 1 + ], + [ + 287, + 288, + 1 + ], + [ + 288, + 289, + 1 + ], + [ + 289, + 290, + 1 + ], + [ + 290, + 291, + 1 + ], + [ + 291, + 292, + 1 + ], + [ + 292, + 293, + 1 + ], + [ + 293, + 294, + 1 + ], + [ + 294, + 295, + 1 + ], + [ + 295, + 296, + 1 + ], + [ + 296, + 297, + 1 + ], + [ + 297, + 298, + 1 + ], + [ + 298, + 299, + 1 + ] + ], + "field": [ + 3, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "cost": 0 + }, + "field_bound": 4, + "start": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "bound": 130, + "expect": { + "state": "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "sweeps": 130, + "term": "bound", + "accumulates": 77740, + "state_hash": "2a84c5a788afd57f" + } + }, + { + "name": "random/n=1000", + "landscape": { + "n": 1000, + "couplings": [ + [ + 0, + 21, + -1 + ], + [ + 0, + 92, + 1 + ], + [ + 0, + 126, + -1 + ], + [ + 0, + 519, + -1 + ], + [ + 0, + 548, + -1 + ], + [ + 0, + 939, + 1 + ], + [ + 1, + 35, + -1 + ], + [ + 1, + 386, + 1 + ], + [ + 1, + 428, + -1 + ], + [ + 1, + 634, + 1 + ], + [ + 1, + 779, + 1 + ], + [ + 1, + 838, + -1 + ], + [ + 1, + 930, + 1 + ], + [ + 2, + 227, + 1 + ], + [ + 2, + 470, + 1 + ], + [ + 2, + 555, + -1 + ], + [ + 2, + 585, + 1 + ], + [ + 2, + 631, + 1 + ], + [ + 3, + 102, + 1 + ], + [ + 3, + 186, + -1 + ], + [ + 3, + 331, + 1 + ], + [ + 3, + 344, + -1 + ], + [ + 4, + 69, + 1 + ], + [ + 4, + 177, + 1 + ], + [ + 4, + 336, + 1 + ], + [ + 4, + 437, + -1 + ], + [ + 4, + 593, + -1 + ], + [ + 5, + 125, + 1 + ], + [ + 5, + 561, + 1 + ], + [ + 5, + 623, + -1 + ], + [ + 6, + 70, + -1 + ], + [ + 6, + 403, + 1 + ], + [ + 6, + 549, + 1 + ], + [ + 6, + 818, + 1 + ], + [ + 6, + 987, + -1 + ], + [ + 6, + 988, + -1 + ], + [ + 7, + 24, + -1 + ], + [ + 7, + 86, + -1 + ], + [ + 7, + 229, + 1 + ], + [ + 7, + 396, + 1 + ], + [ + 7, + 537, + 1 + ], + [ + 7, + 640, + 1 + ], + [ + 7, + 691, + -1 + ], + [ + 8, + 29, + 1 + ], + [ + 8, + 213, + -1 + ], + [ + 8, + 409, + -1 + ], + [ + 8, + 481, + 1 + ], + [ + 8, + 639, + -1 + ], + [ + 8, + 697, + 1 + ], + [ + 8, + 809, + -1 + ], + [ + 9, + 172, + -1 + ], + [ + 9, + 562, + -1 + ], + [ + 9, + 578, + 1 + ], + [ + 9, + 745, + -1 + ], + [ + 10, + 119, + -1 + ], + [ + 10, + 243, + -1 + ], + [ + 10, + 397, + 1 + ], + [ + 10, + 706, + 1 + ], + [ + 11, + 317, + -1 + ], + [ + 11, + 820, + 1 + ], + [ + 11, + 849, + -1 + ], + [ + 11, + 926, + -1 + ], + [ + 12, + 66, + -1 + ], + [ + 12, + 452, + 1 + ], + [ + 12, + 516, + 1 + ], + [ + 12, + 561, + -1 + ], + [ + 12, + 573, + -1 + ], + [ + 12, + 640, + -1 + ], + [ + 12, + 933, + 1 + ], + [ + 12, + 967, + 1 + ], + [ + 13, + 142, + 1 + ], + [ + 13, + 316, + 1 + ], + [ + 13, + 411, + -1 + ], + [ + 13, + 454, + 1 + ], + [ + 13, + 574, + 1 + ], + [ + 13, + 600, + -1 + ], + [ + 14, + 177, + 1 + ], + [ + 14, + 187, + -1 + ], + [ + 14, + 414, + 1 + ], + [ + 14, + 712, + -1 + ], + [ + 14, + 787, + -1 + ], + [ + 14, + 886, + -1 + ], + [ + 14, + 901, + 1 + ], + [ + 14, + 906, + -1 + ], + [ + 14, + 961, + 1 + ], + [ + 15, + 164, + -1 + ], + [ + 15, + 213, + -1 + ], + [ + 15, + 230, + 1 + ], + [ + 15, + 268, + 1 + ], + [ + 15, + 351, + 1 + ], + [ + 15, + 582, + 1 + ], + [ + 15, + 818, + -1 + ], + [ + 15, + 899, + 1 + ], + [ + 16, + 75, + -1 + ], + [ + 16, + 303, + -1 + ], + [ + 16, + 344, + -1 + ], + [ + 16, + 507, + 1 + ], + [ + 16, + 684, + 1 + ], + [ + 16, + 803, + -1 + ], + [ + 16, + 856, + 1 + ], + [ + 17, + 42, + 1 + ], + [ + 17, + 101, + -1 + ], + [ + 17, + 187, + 1 + ], + [ + 17, + 386, + 1 + ], + [ + 17, + 466, + -1 + ], + [ + 17, + 486, + -1 + ], + [ + 17, + 525, + -1 + ], + [ + 17, + 902, + 1 + ], + [ + 18, + 528, + 1 + ], + [ + 18, + 557, + 1 + ], + [ + 18, + 638, + -1 + ], + [ + 18, + 781, + 1 + ], + [ + 18, + 902, + -1 + ], + [ + 19, + 461, + -1 + ], + [ + 19, + 480, + 1 + ], + [ + 19, + 660, + 1 + ], + [ + 19, + 841, + -1 + ], + [ + 19, + 855, + -1 + ], + [ + 20, + 73, + -1 + ], + [ + 20, + 118, + 1 + ], + [ + 20, + 293, + 1 + ], + [ + 20, + 440, + 1 + ], + [ + 20, + 492, + 1 + ], + [ + 20, + 833, + 1 + ], + [ + 20, + 860, + 1 + ], + [ + 21, + 49, + 1 + ], + [ + 21, + 190, + -1 + ], + [ + 21, + 220, + -1 + ], + [ + 21, + 591, + 1 + ], + [ + 21, + 967, + 1 + ], + [ + 21, + 972, + 1 + ], + [ + 22, + 28, + -1 + ], + [ + 22, + 77, + 1 + ], + [ + 22, + 612, + 1 + ], + [ + 22, + 745, + 1 + ], + [ + 22, + 783, + 1 + ], + [ + 22, + 856, + 1 + ], + [ + 23, + 448, + 1 + ], + [ + 23, + 492, + -1 + ], + [ + 23, + 635, + 1 + ], + [ + 23, + 636, + -1 + ], + [ + 23, + 712, + 1 + ], + [ + 23, + 867, + 1 + ], + [ + 24, + 179, + -1 + ], + [ + 24, + 197, + 1 + ], + [ + 24, + 406, + 1 + ], + [ + 24, + 527, + 1 + ], + [ + 24, + 739, + 1 + ], + [ + 24, + 778, + 1 + ], + [ + 24, + 857, + 1 + ], + [ + 25, + 76, + 1 + ], + [ + 25, + 126, + -1 + ], + [ + 25, + 302, + -1 + ], + [ + 25, + 332, + -1 + ], + [ + 25, + 416, + 1 + ], + [ + 25, + 531, + -1 + ], + [ + 25, + 593, + 1 + ], + [ + 26, + 153, + -1 + ], + [ + 26, + 181, + -1 + ], + [ + 26, + 198, + 1 + ], + [ + 26, + 199, + 1 + ], + [ + 26, + 213, + -1 + ], + [ + 26, + 234, + -1 + ], + [ + 26, + 877, + -1 + ], + [ + 26, + 887, + 1 + ], + [ + 27, + 53, + -1 + ], + [ + 27, + 276, + 1 + ], + [ + 27, + 310, + -1 + ], + [ + 27, + 313, + 1 + ], + [ + 27, + 371, + -1 + ], + [ + 27, + 634, + 1 + ], + [ + 27, + 660, + 1 + ], + [ + 27, + 903, + 1 + ], + [ + 28, + 217, + -1 + ], + [ + 28, + 344, + 1 + ], + [ + 28, + 383, + 1 + ], + [ + 28, + 426, + 1 + ], + [ + 28, + 434, + -1 + ], + [ + 28, + 663, + -1 + ], + [ + 28, + 759, + 1 + ], + [ + 28, + 779, + 1 + ], + [ + 29, + 100, + 1 + ], + [ + 29, + 571, + -1 + ], + [ + 29, + 669, + 1 + ], + [ + 30, + 131, + -1 + ], + [ + 30, + 182, + -1 + ], + [ + 30, + 295, + -1 + ], + [ + 30, + 527, + 1 + ], + [ + 30, + 667, + -1 + ], + [ + 31, + 158, + 1 + ], + [ + 31, + 271, + -1 + ], + [ + 31, + 416, + -1 + ], + [ + 31, + 548, + -1 + ], + [ + 31, + 709, + 1 + ], + [ + 31, + 771, + 1 + ], + [ + 31, + 800, + 1 + ], + [ + 32, + 62, + 1 + ], + [ + 32, + 63, + 1 + ], + [ + 32, + 282, + 1 + ], + [ + 32, + 296, + -1 + ], + [ + 32, + 390, + 1 + ], + [ + 32, + 625, + -1 + ], + [ + 32, + 788, + 1 + ], + [ + 33, + 38, + 1 + ], + [ + 33, + 46, + 1 + ], + [ + 33, + 648, + 1 + ], + [ + 33, + 978, + -1 + ], + [ + 34, + 108, + -1 + ], + [ + 34, + 156, + -1 + ], + [ + 34, + 345, + -1 + ], + [ + 34, + 536, + -1 + ], + [ + 34, + 575, + 1 + ], + [ + 34, + 690, + 1 + ], + [ + 34, + 708, + 1 + ], + [ + 34, + 726, + 1 + ], + [ + 34, + 757, + -1 + ], + [ + 35, + 60, + 1 + ], + [ + 35, + 303, + 1 + ], + [ + 35, + 510, + 1 + ], + [ + 35, + 679, + -1 + ], + [ + 35, + 943, + -1 + ], + [ + 36, + 92, + 1 + ], + [ + 36, + 105, + 1 + ], + [ + 36, + 197, + 1 + ], + [ + 36, + 615, + 1 + ], + [ + 36, + 628, + 1 + ], + [ + 36, + 692, + -1 + ], + [ + 36, + 725, + 1 + ], + [ + 36, + 821, + 1 + ], + [ + 37, + 107, + -1 + ], + [ + 37, + 152, + 1 + ], + [ + 37, + 323, + -1 + ], + [ + 37, + 484, + 1 + ], + [ + 37, + 508, + -1 + ], + [ + 37, + 516, + -1 + ], + [ + 37, + 558, + -1 + ], + [ + 37, + 727, + -1 + ], + [ + 37, + 878, + -1 + ], + [ + 37, + 906, + 1 + ], + [ + 37, + 994, + 1 + ], + [ + 38, + 292, + 1 + ], + [ + 38, + 314, + -1 + ], + [ + 38, + 695, + -1 + ], + [ + 38, + 741, + -1 + ], + [ + 38, + 818, + -1 + ], + [ + 38, + 939, + -1 + ], + [ + 39, + 676, + -1 + ], + [ + 40, + 135, + -1 + ], + [ + 40, + 213, + -1 + ], + [ + 40, + 224, + 1 + ], + [ + 40, + 312, + -1 + ], + [ + 40, + 356, + 1 + ], + [ + 40, + 396, + -1 + ], + [ + 40, + 402, + 1 + ], + [ + 40, + 640, + -1 + ], + [ + 40, + 699, + -1 + ], + [ + 40, + 874, + 1 + ], + [ + 40, + 875, + 1 + ], + [ + 40, + 992, + -1 + ], + [ + 41, + 68, + -1 + ], + [ + 41, + 330, + -1 + ], + [ + 41, + 400, + -1 + ], + [ + 41, + 429, + 1 + ], + [ + 41, + 880, + 1 + ], + [ + 42, + 205, + -1 + ], + [ + 42, + 287, + 1 + ], + [ + 42, + 361, + -1 + ], + [ + 42, + 382, + -1 + ], + [ + 42, + 558, + -1 + ], + [ + 42, + 808, + 1 + ], + [ + 42, + 914, + 1 + ], + [ + 43, + 47, + 1 + ], + [ + 43, + 127, + 1 + ], + [ + 43, + 250, + 1 + ], + [ + 43, + 300, + -1 + ], + [ + 43, + 522, + 1 + ], + [ + 43, + 525, + -1 + ], + [ + 43, + 526, + 1 + ], + [ + 44, + 59, + -1 + ], + [ + 44, + 88, + -1 + ], + [ + 44, + 642, + 1 + ], + [ + 44, + 821, + -1 + ], + [ + 44, + 825, + -1 + ], + [ + 44, + 915, + 1 + ], + [ + 44, + 925, + 1 + ], + [ + 44, + 964, + 1 + ], + [ + 45, + 231, + 1 + ], + [ + 45, + 328, + -1 + ], + [ + 45, + 405, + 1 + ], + [ + 45, + 458, + -1 + ], + [ + 45, + 477, + 1 + ], + [ + 45, + 585, + -1 + ], + [ + 45, + 756, + -1 + ], + [ + 46, + 193, + 1 + ], + [ + 46, + 347, + -1 + ], + [ + 46, + 710, + 1 + ], + [ + 46, + 907, + 1 + ], + [ + 46, + 920, + 1 + ], + [ + 46, + 992, + 1 + ], + [ + 47, + 131, + -1 + ], + [ + 47, + 600, + -1 + ], + [ + 47, + 676, + 1 + ], + [ + 47, + 687, + 1 + ], + [ + 47, + 939, + 1 + ], + [ + 48, + 225, + 1 + ], + [ + 48, + 275, + 1 + ], + [ + 48, + 464, + -1 + ], + [ + 48, + 466, + -1 + ], + [ + 48, + 484, + 1 + ], + [ + 48, + 595, + 1 + ], + [ + 48, + 670, + -1 + ], + [ + 49, + 288, + -1 + ], + [ + 49, + 291, + -1 + ], + [ + 49, + 308, + -1 + ], + [ + 49, + 345, + -1 + ], + [ + 49, + 766, + 1 + ], + [ + 49, + 933, + -1 + ], + [ + 50, + 61, + 1 + ], + [ + 50, + 404, + 1 + ], + [ + 50, + 631, + 1 + ], + [ + 50, + 639, + 1 + ], + [ + 50, + 640, + -1 + ], + [ + 50, + 813, + 1 + ], + [ + 50, + 837, + -1 + ], + [ + 51, + 355, + -1 + ], + [ + 51, + 412, + -1 + ], + [ + 51, + 462, + -1 + ], + [ + 51, + 740, + -1 + ], + [ + 51, + 753, + 1 + ], + [ + 52, + 101, + -1 + ], + [ + 52, + 967, + 1 + ], + [ + 53, + 259, + 1 + ], + [ + 53, + 362, + -1 + ], + [ + 53, + 542, + 1 + ], + [ + 53, + 671, + -1 + ], + [ + 53, + 864, + 1 + ], + [ + 53, + 924, + -1 + ], + [ + 54, + 254, + -1 + ], + [ + 54, + 294, + -1 + ], + [ + 54, + 424, + 1 + ], + [ + 54, + 730, + -1 + ], + [ + 54, + 872, + 1 + ], + [ + 54, + 895, + -1 + ], + [ + 54, + 952, + -1 + ], + [ + 54, + 992, + 1 + ], + [ + 55, + 82, + -1 + ], + [ + 55, + 177, + -1 + ], + [ + 55, + 210, + -1 + ], + [ + 55, + 386, + -1 + ], + [ + 55, + 401, + -1 + ], + [ + 55, + 438, + 1 + ], + [ + 55, + 453, + -1 + ], + [ + 55, + 624, + -1 + ], + [ + 55, + 690, + -1 + ], + [ + 55, + 785, + -1 + ], + [ + 55, + 796, + -1 + ], + [ + 55, + 800, + 1 + ], + [ + 56, + 65, + -1 + ], + [ + 56, + 400, + -1 + ], + [ + 56, + 559, + 1 + ], + [ + 56, + 595, + 1 + ], + [ + 56, + 732, + -1 + ], + [ + 56, + 807, + 1 + ], + [ + 56, + 929, + -1 + ], + [ + 57, + 495, + -1 + ], + [ + 57, + 537, + -1 + ], + [ + 57, + 618, + 1 + ], + [ + 57, + 709, + 1 + ], + [ + 57, + 860, + 1 + ], + [ + 58, + 370, + -1 + ], + [ + 58, + 403, + 1 + ], + [ + 58, + 564, + -1 + ], + [ + 58, + 569, + 1 + ], + [ + 58, + 677, + -1 + ], + [ + 58, + 692, + 1 + ], + [ + 58, + 987, + -1 + ], + [ + 59, + 214, + 1 + ], + [ + 59, + 383, + -1 + ], + [ + 59, + 741, + -1 + ], + [ + 59, + 822, + -1 + ], + [ + 59, + 850, + 1 + ], + [ + 59, + 868, + 1 + ], + [ + 59, + 870, + 1 + ], + [ + 60, + 95, + -1 + ], + [ + 60, + 376, + 1 + ], + [ + 61, + 239, + 1 + ], + [ + 61, + 995, + 1 + ], + [ + 62, + 99, + 1 + ], + [ + 62, + 196, + -1 + ], + [ + 62, + 432, + -1 + ], + [ + 62, + 808, + 1 + ], + [ + 62, + 926, + 1 + ], + [ + 63, + 361, + -1 + ], + [ + 63, + 671, + 1 + ], + [ + 63, + 827, + 1 + ], + [ + 64, + 432, + -1 + ], + [ + 64, + 529, + -1 + ], + [ + 64, + 570, + 1 + ], + [ + 64, + 574, + -1 + ], + [ + 64, + 821, + -1 + ], + [ + 65, + 698, + -1 + ], + [ + 66, + 100, + -1 + ], + [ + 66, + 112, + 1 + ], + [ + 66, + 116, + -1 + ], + [ + 66, + 191, + -1 + ], + [ + 66, + 380, + 1 + ], + [ + 66, + 593, + 1 + ], + [ + 66, + 761, + -1 + ], + [ + 66, + 809, + -1 + ], + [ + 66, + 814, + 1 + ], + [ + 67, + 369, + 1 + ], + [ + 67, + 578, + -1 + ], + [ + 67, + 705, + -1 + ], + [ + 67, + 817, + -1 + ], + [ + 68, + 151, + -1 + ], + [ + 68, + 388, + 1 + ], + [ + 68, + 426, + -1 + ], + [ + 68, + 436, + 1 + ], + [ + 68, + 451, + 1 + ], + [ + 68, + 705, + -1 + ], + [ + 68, + 723, + -1 + ], + [ + 69, + 209, + -1 + ], + [ + 69, + 214, + 1 + ], + [ + 69, + 552, + -1 + ], + [ + 69, + 685, + 1 + ], + [ + 69, + 839, + 1 + ], + [ + 70, + 119, + -1 + ], + [ + 70, + 344, + 1 + ], + [ + 70, + 402, + 1 + ], + [ + 70, + 437, + -1 + ], + [ + 70, + 510, + 1 + ], + [ + 70, + 728, + -1 + ], + [ + 70, + 771, + -1 + ], + [ + 71, + 227, + 1 + ], + [ + 71, + 283, + 1 + ], + [ + 71, + 464, + -1 + ], + [ + 71, + 533, + -1 + ], + [ + 71, + 659, + -1 + ], + [ + 71, + 811, + -1 + ], + [ + 71, + 893, + 1 + ], + [ + 71, + 929, + -1 + ], + [ + 72, + 604, + 1 + ], + [ + 72, + 680, + -1 + ], + [ + 72, + 690, + 1 + ], + [ + 72, + 754, + 1 + ], + [ + 73, + 300, + -1 + ], + [ + 73, + 535, + -1 + ], + [ + 73, + 634, + 1 + ], + [ + 73, + 635, + 1 + ], + [ + 73, + 790, + -1 + ], + [ + 74, + 144, + 1 + ], + [ + 74, + 177, + 1 + ], + [ + 74, + 258, + 1 + ], + [ + 74, + 592, + -1 + ], + [ + 75, + 256, + 1 + ], + [ + 75, + 415, + -1 + ], + [ + 75, + 569, + -1 + ], + [ + 76, + 381, + 1 + ], + [ + 76, + 426, + -1 + ], + [ + 76, + 577, + 1 + ], + [ + 77, + 135, + 1 + ], + [ + 78, + 233, + -1 + ], + [ + 78, + 311, + 1 + ], + [ + 78, + 770, + 1 + ], + [ + 78, + 879, + -1 + ], + [ + 79, + 610, + 1 + ], + [ + 79, + 989, + 1 + ], + [ + 80, + 84, + -1 + ], + [ + 80, + 215, + -1 + ], + [ + 80, + 309, + -1 + ], + [ + 81, + 86, + 1 + ], + [ + 81, + 104, + -1 + ], + [ + 81, + 192, + 1 + ], + [ + 81, + 325, + 1 + ], + [ + 81, + 376, + -1 + ], + [ + 81, + 407, + -1 + ], + [ + 81, + 414, + -1 + ], + [ + 81, + 801, + -1 + ], + [ + 81, + 902, + 1 + ], + [ + 82, + 540, + -1 + ], + [ + 82, + 550, + 1 + ], + [ + 82, + 560, + 1 + ], + [ + 82, + 987, + 1 + ], + [ + 82, + 994, + -1 + ], + [ + 83, + 190, + 1 + ], + [ + 83, + 302, + -1 + ], + [ + 83, + 425, + -1 + ], + [ + 83, + 491, + 1 + ], + [ + 83, + 528, + 1 + ], + [ + 83, + 598, + 1 + ], + [ + 83, + 766, + 1 + ], + [ + 83, + 805, + -1 + ], + [ + 83, + 942, + 1 + ], + [ + 84, + 508, + 1 + ], + [ + 84, + 705, + -1 + ], + [ + 84, + 784, + -1 + ], + [ + 84, + 959, + -1 + ], + [ + 85, + 156, + -1 + ], + [ + 85, + 790, + 1 + ], + [ + 85, + 805, + -1 + ], + [ + 85, + 986, + 1 + ], + [ + 86, + 108, + -1 + ], + [ + 86, + 181, + -1 + ], + [ + 86, + 332, + 1 + ], + [ + 86, + 456, + 1 + ], + [ + 86, + 990, + -1 + ], + [ + 87, + 598, + 1 + ], + [ + 87, + 663, + 1 + ], + [ + 87, + 856, + -1 + ], + [ + 88, + 290, + 1 + ], + [ + 88, + 362, + -1 + ], + [ + 88, + 383, + -1 + ], + [ + 88, + 441, + 1 + ], + [ + 88, + 515, + -1 + ], + [ + 88, + 825, + -1 + ], + [ + 89, + 133, + -1 + ], + [ + 89, + 296, + -1 + ], + [ + 89, + 343, + -1 + ], + [ + 89, + 709, + -1 + ], + [ + 89, + 817, + 1 + ], + [ + 89, + 862, + 1 + ], + [ + 90, + 113, + 1 + ], + [ + 90, + 201, + 1 + ], + [ + 90, + 504, + 1 + ], + [ + 90, + 787, + -1 + ], + [ + 90, + 821, + -1 + ], + [ + 90, + 841, + 1 + ], + [ + 90, + 893, + 1 + ], + [ + 91, + 307, + -1 + ], + [ + 91, + 455, + -1 + ], + [ + 91, + 480, + 1 + ], + [ + 91, + 516, + -1 + ], + [ + 91, + 562, + 1 + ], + [ + 91, + 630, + -1 + ], + [ + 91, + 740, + 1 + ], + [ + 91, + 775, + -1 + ], + [ + 91, + 797, + 1 + ], + [ + 91, + 982, + 1 + ], + [ + 92, + 123, + 1 + ], + [ + 92, + 141, + 1 + ], + [ + 92, + 483, + -1 + ], + [ + 92, + 789, + -1 + ], + [ + 92, + 800, + -1 + ], + [ + 92, + 987, + -1 + ], + [ + 92, + 993, + 1 + ], + [ + 93, + 285, + -1 + ], + [ + 93, + 490, + 1 + ], + [ + 93, + 553, + 1 + ], + [ + 93, + 607, + 1 + ], + [ + 93, + 838, + 1 + ], + [ + 93, + 899, + 1 + ], + [ + 94, + 337, + 1 + ], + [ + 94, + 410, + -1 + ], + [ + 94, + 473, + 1 + ], + [ + 94, + 726, + 1 + ], + [ + 94, + 754, + -1 + ], + [ + 95, + 152, + 1 + ], + [ + 95, + 306, + -1 + ], + [ + 95, + 414, + -1 + ], + [ + 95, + 494, + 1 + ], + [ + 95, + 637, + 1 + ], + [ + 96, + 151, + 1 + ], + [ + 96, + 262, + -1 + ], + [ + 96, + 283, + -1 + ], + [ + 96, + 308, + 1 + ], + [ + 96, + 496, + 1 + ], + [ + 96, + 566, + -1 + ], + [ + 96, + 611, + -1 + ], + [ + 97, + 193, + 1 + ], + [ + 97, + 276, + 1 + ], + [ + 97, + 343, + -1 + ], + [ + 97, + 479, + 1 + ], + [ + 97, + 709, + 1 + ], + [ + 97, + 785, + 1 + ], + [ + 97, + 837, + 1 + ], + [ + 98, + 351, + 1 + ], + [ + 98, + 454, + 1 + ], + [ + 98, + 592, + 1 + ], + [ + 98, + 651, + -1 + ], + [ + 98, + 775, + -1 + ], + [ + 98, + 872, + -1 + ], + [ + 98, + 910, + 1 + ], + [ + 98, + 999, + -1 + ], + [ + 99, + 236, + 1 + ], + [ + 99, + 447, + -1 + ], + [ + 99, + 785, + 1 + ], + [ + 100, + 178, + 1 + ], + [ + 100, + 645, + 1 + ], + [ + 100, + 741, + 1 + ], + [ + 100, + 761, + -1 + ], + [ + 101, + 310, + -1 + ], + [ + 101, + 323, + 1 + ], + [ + 101, + 472, + -1 + ], + [ + 101, + 917, + -1 + ], + [ + 102, + 591, + -1 + ], + [ + 102, + 604, + -1 + ], + [ + 103, + 155, + -1 + ], + [ + 103, + 169, + 1 + ], + [ + 103, + 244, + 1 + ], + [ + 103, + 641, + 1 + ], + [ + 104, + 336, + -1 + ], + [ + 104, + 381, + 1 + ], + [ + 104, + 425, + 1 + ], + [ + 104, + 600, + -1 + ], + [ + 104, + 773, + -1 + ], + [ + 104, + 915, + -1 + ], + [ + 105, + 112, + -1 + ], + [ + 105, + 130, + -1 + ], + [ + 105, + 423, + 1 + ], + [ + 105, + 571, + 1 + ], + [ + 105, + 658, + 1 + ], + [ + 105, + 937, + -1 + ], + [ + 105, + 951, + 1 + ], + [ + 105, + 993, + -1 + ], + [ + 106, + 335, + -1 + ], + [ + 106, + 389, + -1 + ], + [ + 106, + 627, + -1 + ], + [ + 106, + 742, + -1 + ], + [ + 107, + 154, + 1 + ], + [ + 107, + 187, + 1 + ], + [ + 107, + 771, + 1 + ], + [ + 108, + 358, + 1 + ], + [ + 108, + 448, + 1 + ], + [ + 109, + 121, + -1 + ], + [ + 109, + 126, + 1 + ], + [ + 109, + 210, + -1 + ], + [ + 109, + 524, + 1 + ], + [ + 110, + 252, + -1 + ], + [ + 110, + 584, + -1 + ], + [ + 110, + 647, + 1 + ], + [ + 110, + 686, + -1 + ], + [ + 110, + 936, + -1 + ], + [ + 111, + 161, + -1 + ], + [ + 111, + 274, + 1 + ], + [ + 111, + 313, + -1 + ], + [ + 111, + 586, + 1 + ], + [ + 111, + 607, + 1 + ], + [ + 111, + 655, + 1 + ], + [ + 111, + 708, + -1 + ], + [ + 112, + 166, + -1 + ], + [ + 112, + 190, + 1 + ], + [ + 112, + 756, + -1 + ], + [ + 112, + 859, + 1 + ], + [ + 112, + 868, + 1 + ], + [ + 113, + 339, + 1 + ], + [ + 113, + 406, + -1 + ], + [ + 113, + 603, + 1 + ], + [ + 113, + 808, + 1 + ], + [ + 113, + 853, + 1 + ], + [ + 113, + 856, + -1 + ], + [ + 113, + 990, + -1 + ], + [ + 114, + 155, + -1 + ], + [ + 114, + 221, + 1 + ], + [ + 114, + 239, + 1 + ], + [ + 114, + 841, + -1 + ], + [ + 115, + 483, + -1 + ], + [ + 115, + 856, + -1 + ], + [ + 115, + 934, + -1 + ], + [ + 115, + 949, + 1 + ], + [ + 115, + 999, + 1 + ], + [ + 116, + 186, + -1 + ], + [ + 116, + 201, + -1 + ], + [ + 116, + 349, + 1 + ], + [ + 116, + 720, + 1 + ], + [ + 116, + 772, + -1 + ], + [ + 116, + 976, + 1 + ], + [ + 117, + 184, + 1 + ], + [ + 117, + 377, + -1 + ], + [ + 117, + 574, + -1 + ], + [ + 117, + 722, + 1 + ], + [ + 117, + 808, + -1 + ], + [ + 117, + 864, + -1 + ], + [ + 118, + 262, + 1 + ], + [ + 118, + 342, + 1 + ], + [ + 118, + 564, + 1 + ], + [ + 118, + 626, + 1 + ], + [ + 118, + 716, + 1 + ], + [ + 118, + 718, + 1 + ], + [ + 118, + 850, + 1 + ], + [ + 118, + 936, + -1 + ], + [ + 119, + 166, + 1 + ], + [ + 119, + 246, + -1 + ], + [ + 119, + 427, + 1 + ], + [ + 119, + 448, + 1 + ], + [ + 119, + 554, + -1 + ], + [ + 119, + 677, + 1 + ], + [ + 119, + 792, + 1 + ], + [ + 119, + 887, + -1 + ], + [ + 120, + 143, + 1 + ], + [ + 120, + 472, + -1 + ], + [ + 120, + 487, + -1 + ], + [ + 120, + 593, + 1 + ], + [ + 120, + 732, + 1 + ], + [ + 120, + 849, + -1 + ], + [ + 120, + 866, + -1 + ], + [ + 120, + 958, + -1 + ], + [ + 120, + 983, + -1 + ], + [ + 121, + 279, + -1 + ], + [ + 121, + 396, + 1 + ], + [ + 121, + 489, + -1 + ], + [ + 121, + 526, + 1 + ], + [ + 121, + 581, + -1 + ], + [ + 121, + 822, + 1 + ], + [ + 121, + 886, + -1 + ], + [ + 122, + 124, + -1 + ], + [ + 122, + 198, + 1 + ], + [ + 123, + 212, + 1 + ], + [ + 123, + 255, + -1 + ], + [ + 123, + 361, + 1 + ], + [ + 123, + 539, + -1 + ], + [ + 123, + 540, + 1 + ], + [ + 123, + 725, + -1 + ], + [ + 124, + 929, + 1 + ], + [ + 125, + 309, + 1 + ], + [ + 125, + 605, + 1 + ], + [ + 125, + 631, + 1 + ], + [ + 125, + 646, + 1 + ], + [ + 125, + 648, + 1 + ], + [ + 125, + 666, + -1 + ], + [ + 125, + 942, + -1 + ], + [ + 125, + 953, + -1 + ], + [ + 125, + 992, + 1 + ], + [ + 126, + 261, + -1 + ], + [ + 126, + 657, + 1 + ], + [ + 126, + 860, + 1 + ], + [ + 126, + 914, + -1 + ], + [ + 127, + 191, + -1 + ], + [ + 127, + 222, + 1 + ], + [ + 127, + 319, + 1 + ], + [ + 127, + 762, + 1 + ], + [ + 128, + 698, + -1 + ], + [ + 128, + 965, + -1 + ], + [ + 129, + 251, + 1 + ], + [ + 129, + 580, + 1 + ], + [ + 129, + 644, + -1 + ], + [ + 129, + 933, + -1 + ], + [ + 129, + 941, + 1 + ], + [ + 130, + 150, + -1 + ], + [ + 130, + 214, + -1 + ], + [ + 130, + 363, + -1 + ], + [ + 130, + 565, + -1 + ], + [ + 130, + 601, + -1 + ], + [ + 130, + 632, + 1 + ], + [ + 130, + 677, + -1 + ], + [ + 130, + 703, + 1 + ], + [ + 130, + 752, + -1 + ], + [ + 130, + 825, + 1 + ], + [ + 131, + 263, + -1 + ], + [ + 131, + 440, + -1 + ], + [ + 131, + 560, + -1 + ], + [ + 131, + 895, + 1 + ], + [ + 131, + 974, + -1 + ], + [ + 132, + 282, + -1 + ], + [ + 132, + 340, + -1 + ], + [ + 132, + 356, + -1 + ], + [ + 132, + 359, + -1 + ], + [ + 132, + 474, + 1 + ], + [ + 132, + 620, + -1 + ], + [ + 132, + 916, + 1 + ], + [ + 132, + 938, + 1 + ], + [ + 133, + 565, + -1 + ], + [ + 133, + 678, + -1 + ], + [ + 134, + 282, + 1 + ], + [ + 134, + 423, + 1 + ], + [ + 134, + 710, + 1 + ], + [ + 134, + 745, + 1 + ], + [ + 134, + 754, + 1 + ], + [ + 135, + 274, + -1 + ], + [ + 135, + 447, + -1 + ], + [ + 135, + 640, + 1 + ], + [ + 135, + 679, + 1 + ], + [ + 135, + 793, + -1 + ], + [ + 136, + 455, + 1 + ], + [ + 136, + 598, + 1 + ], + [ + 136, + 639, + 1 + ], + [ + 137, + 187, + -1 + ], + [ + 137, + 268, + 1 + ], + [ + 137, + 328, + -1 + ], + [ + 137, + 433, + 1 + ], + [ + 137, + 673, + -1 + ], + [ + 137, + 781, + -1 + ], + [ + 138, + 352, + -1 + ], + [ + 138, + 482, + -1 + ], + [ + 138, + 544, + -1 + ], + [ + 138, + 587, + -1 + ], + [ + 139, + 219, + 1 + ], + [ + 140, + 243, + 1 + ], + [ + 140, + 312, + -1 + ], + [ + 140, + 388, + -1 + ], + [ + 140, + 545, + 1 + ], + [ + 140, + 855, + 1 + ], + [ + 141, + 161, + 1 + ], + [ + 141, + 301, + 1 + ], + [ + 141, + 369, + 1 + ], + [ + 141, + 645, + -1 + ], + [ + 141, + 783, + 1 + ], + [ + 141, + 942, + 1 + ], + [ + 141, + 981, + -1 + ], + [ + 142, + 186, + 1 + ], + [ + 142, + 282, + -1 + ], + [ + 142, + 290, + 1 + ], + [ + 142, + 528, + -1 + ], + [ + 142, + 779, + -1 + ], + [ + 142, + 905, + 1 + ], + [ + 143, + 160, + -1 + ], + [ + 143, + 429, + -1 + ], + [ + 143, + 521, + -1 + ], + [ + 143, + 692, + -1 + ], + [ + 143, + 742, + -1 + ], + [ + 144, + 426, + 1 + ], + [ + 144, + 673, + -1 + ], + [ + 144, + 901, + 1 + ], + [ + 145, + 197, + 1 + ], + [ + 145, + 465, + -1 + ], + [ + 145, + 475, + 1 + ], + [ + 145, + 542, + 1 + ], + [ + 145, + 593, + -1 + ], + [ + 145, + 930, + -1 + ], + [ + 145, + 985, + -1 + ], + [ + 146, + 156, + -1 + ], + [ + 146, + 232, + -1 + ], + [ + 146, + 283, + -1 + ], + [ + 146, + 331, + 1 + ], + [ + 146, + 485, + 1 + ], + [ + 146, + 579, + 1 + ], + [ + 146, + 697, + -1 + ], + [ + 146, + 735, + -1 + ], + [ + 146, + 813, + -1 + ], + [ + 147, + 241, + -1 + ], + [ + 147, + 378, + -1 + ], + [ + 147, + 380, + -1 + ], + [ + 147, + 663, + -1 + ], + [ + 148, + 335, + -1 + ], + [ + 148, + 481, + -1 + ], + [ + 148, + 772, + -1 + ], + [ + 148, + 824, + -1 + ], + [ + 148, + 949, + -1 + ], + [ + 149, + 170, + -1 + ], + [ + 149, + 415, + -1 + ], + [ + 149, + 425, + 1 + ], + [ + 149, + 435, + -1 + ], + [ + 149, + 453, + -1 + ], + [ + 149, + 489, + -1 + ], + [ + 149, + 635, + -1 + ], + [ + 149, + 975, + -1 + ], + [ + 150, + 627, + -1 + ], + [ + 151, + 158, + 1 + ], + [ + 151, + 265, + -1 + ], + [ + 151, + 549, + -1 + ], + [ + 151, + 940, + 1 + ], + [ + 152, + 214, + -1 + ], + [ + 152, + 252, + -1 + ], + [ + 152, + 585, + 1 + ], + [ + 152, + 849, + -1 + ], + [ + 152, + 991, + -1 + ], + [ + 153, + 190, + 1 + ], + [ + 153, + 452, + 1 + ], + [ + 153, + 577, + 1 + ], + [ + 154, + 231, + 1 + ], + [ + 154, + 260, + -1 + ], + [ + 154, + 328, + -1 + ], + [ + 154, + 458, + 1 + ], + [ + 154, + 481, + -1 + ], + [ + 155, + 164, + 1 + ], + [ + 155, + 201, + -1 + ], + [ + 155, + 357, + 1 + ], + [ + 155, + 363, + 1 + ], + [ + 155, + 843, + -1 + ], + [ + 155, + 939, + -1 + ], + [ + 156, + 515, + -1 + ], + [ + 156, + 626, + 1 + ], + [ + 157, + 302, + 1 + ], + [ + 157, + 509, + 1 + ], + [ + 157, + 696, + -1 + ], + [ + 157, + 762, + 1 + ], + [ + 157, + 949, + 1 + ], + [ + 158, + 198, + 1 + ], + [ + 158, + 200, + -1 + ], + [ + 158, + 205, + 1 + ], + [ + 158, + 378, + 1 + ], + [ + 158, + 380, + -1 + ], + [ + 158, + 514, + -1 + ], + [ + 159, + 286, + -1 + ], + [ + 159, + 358, + -1 + ], + [ + 159, + 660, + -1 + ], + [ + 159, + 751, + 1 + ], + [ + 159, + 938, + -1 + ], + [ + 160, + 286, + -1 + ], + [ + 160, + 303, + -1 + ], + [ + 160, + 450, + -1 + ], + [ + 160, + 569, + -1 + ], + [ + 160, + 614, + -1 + ], + [ + 161, + 589, + -1 + ], + [ + 162, + 258, + 1 + ], + [ + 162, + 294, + 1 + ], + [ + 162, + 385, + 1 + ], + [ + 162, + 825, + 1 + ], + [ + 162, + 963, + -1 + ], + [ + 163, + 308, + 1 + ], + [ + 163, + 409, + -1 + ], + [ + 163, + 424, + -1 + ], + [ + 163, + 788, + -1 + ], + [ + 164, + 535, + 1 + ], + [ + 164, + 563, + -1 + ], + [ + 164, + 568, + -1 + ], + [ + 164, + 919, + -1 + ], + [ + 165, + 389, + -1 + ], + [ + 165, + 452, + 1 + ], + [ + 166, + 405, + 1 + ], + [ + 166, + 428, + -1 + ], + [ + 166, + 512, + 1 + ], + [ + 166, + 564, + 1 + ], + [ + 166, + 574, + -1 + ], + [ + 166, + 686, + -1 + ], + [ + 166, + 800, + 1 + ], + [ + 166, + 828, + 1 + ], + [ + 166, + 889, + 1 + ], + [ + 166, + 926, + 1 + ], + [ + 167, + 200, + -1 + ], + [ + 167, + 556, + -1 + ], + [ + 167, + 629, + 1 + ], + [ + 167, + 941, + -1 + ], + [ + 168, + 183, + 1 + ], + [ + 168, + 184, + -1 + ], + [ + 168, + 209, + 1 + ], + [ + 168, + 365, + 1 + ], + [ + 168, + 708, + 1 + ], + [ + 169, + 205, + 1 + ], + [ + 169, + 317, + 1 + ], + [ + 169, + 353, + -1 + ], + [ + 169, + 633, + 1 + ], + [ + 170, + 186, + -1 + ], + [ + 170, + 202, + -1 + ], + [ + 170, + 263, + 1 + ], + [ + 170, + 538, + 1 + ], + [ + 171, + 268, + 1 + ], + [ + 171, + 316, + 1 + ], + [ + 171, + 322, + 1 + ], + [ + 171, + 348, + 1 + ], + [ + 171, + 398, + 1 + ], + [ + 171, + 408, + 1 + ], + [ + 171, + 769, + -1 + ], + [ + 171, + 903, + -1 + ], + [ + 171, + 973, + -1 + ], + [ + 172, + 224, + -1 + ], + [ + 172, + 681, + -1 + ], + [ + 172, + 963, + -1 + ], + [ + 172, + 981, + 1 + ], + [ + 172, + 999, + 1 + ], + [ + 173, + 233, + -1 + ], + [ + 173, + 521, + 1 + ], + [ + 173, + 523, + -1 + ], + [ + 173, + 726, + 1 + ], + [ + 174, + 766, + -1 + ], + [ + 175, + 517, + -1 + ], + [ + 175, + 614, + 1 + ], + [ + 175, + 995, + -1 + ], + [ + 176, + 371, + -1 + ], + [ + 176, + 447, + -1 + ], + [ + 176, + 647, + 1 + ], + [ + 176, + 842, + -1 + ], + [ + 177, + 294, + 1 + ], + [ + 177, + 494, + -1 + ], + [ + 177, + 536, + 1 + ], + [ + 177, + 595, + 1 + ], + [ + 177, + 949, + 1 + ], + [ + 177, + 969, + 1 + ], + [ + 178, + 253, + -1 + ], + [ + 178, + 423, + -1 + ], + [ + 178, + 449, + 1 + ], + [ + 178, + 691, + -1 + ], + [ + 179, + 393, + -1 + ], + [ + 179, + 599, + 1 + ], + [ + 180, + 552, + -1 + ], + [ + 180, + 688, + -1 + ], + [ + 180, + 890, + -1 + ], + [ + 181, + 498, + 1 + ], + [ + 181, + 846, + -1 + ], + [ + 182, + 412, + -1 + ], + [ + 182, + 448, + -1 + ], + [ + 182, + 559, + 1 + ], + [ + 182, + 955, + 1 + ], + [ + 183, + 212, + 1 + ], + [ + 183, + 300, + -1 + ], + [ + 183, + 652, + 1 + ], + [ + 183, + 807, + -1 + ], + [ + 183, + 826, + -1 + ], + [ + 184, + 240, + 1 + ], + [ + 184, + 262, + -1 + ], + [ + 184, + 808, + -1 + ], + [ + 185, + 516, + 1 + ], + [ + 185, + 905, + -1 + ], + [ + 186, + 212, + -1 + ], + [ + 186, + 531, + -1 + ], + [ + 186, + 585, + 1 + ], + [ + 186, + 746, + -1 + ], + [ + 186, + 873, + 1 + ], + [ + 187, + 192, + 1 + ], + [ + 187, + 202, + -1 + ], + [ + 187, + 596, + 1 + ], + [ + 188, + 206, + 1 + ], + [ + 188, + 284, + 1 + ], + [ + 188, + 416, + 1 + ], + [ + 188, + 542, + 1 + ], + [ + 188, + 692, + -1 + ], + [ + 189, + 282, + 1 + ], + [ + 189, + 386, + -1 + ], + [ + 189, + 967, + 1 + ], + [ + 189, + 983, + 1 + ], + [ + 190, + 235, + 1 + ], + [ + 190, + 456, + 1 + ], + [ + 190, + 512, + 1 + ], + [ + 190, + 597, + -1 + ], + [ + 190, + 815, + -1 + ], + [ + 190, + 909, + -1 + ], + [ + 190, + 925, + 1 + ], + [ + 191, + 235, + -1 + ], + [ + 191, + 309, + 1 + ], + [ + 191, + 336, + 1 + ], + [ + 191, + 382, + 1 + ], + [ + 191, + 451, + -1 + ], + [ + 191, + 458, + 1 + ], + [ + 191, + 668, + 1 + ], + [ + 192, + 204, + 1 + ], + [ + 192, + 492, + 1 + ], + [ + 192, + 664, + 1 + ], + [ + 192, + 935, + 1 + ], + [ + 194, + 226, + 1 + ], + [ + 194, + 358, + -1 + ], + [ + 194, + 488, + 1 + ], + [ + 194, + 530, + 1 + ], + [ + 194, + 555, + 1 + ], + [ + 194, + 581, + 1 + ], + [ + 194, + 693, + -1 + ], + [ + 194, + 774, + 1 + ], + [ + 195, + 487, + -1 + ], + [ + 195, + 490, + -1 + ], + [ + 195, + 535, + 1 + ], + [ + 195, + 613, + -1 + ], + [ + 195, + 651, + 1 + ], + [ + 195, + 769, + -1 + ], + [ + 195, + 803, + -1 + ], + [ + 195, + 944, + 1 + ], + [ + 196, + 394, + -1 + ], + [ + 196, + 522, + -1 + ], + [ + 196, + 569, + 1 + ], + [ + 197, + 523, + -1 + ], + [ + 197, + 865, + -1 + ], + [ + 197, + 972, + 1 + ], + [ + 198, + 333, + 1 + ], + [ + 198, + 796, + -1 + ], + [ + 198, + 943, + -1 + ], + [ + 198, + 969, + -1 + ], + [ + 199, + 431, + -1 + ], + [ + 199, + 510, + -1 + ], + [ + 199, + 560, + -1 + ], + [ + 199, + 766, + -1 + ], + [ + 199, + 839, + 1 + ], + [ + 199, + 914, + -1 + ], + [ + 200, + 308, + -1 + ], + [ + 200, + 783, + -1 + ], + [ + 201, + 300, + 1 + ], + [ + 201, + 372, + -1 + ], + [ + 202, + 525, + -1 + ], + [ + 202, + 708, + 1 + ], + [ + 202, + 853, + -1 + ], + [ + 203, + 384, + 1 + ], + [ + 203, + 449, + 1 + ], + [ + 203, + 813, + -1 + ], + [ + 203, + 883, + -1 + ], + [ + 203, + 938, + 1 + ], + [ + 204, + 276, + 1 + ], + [ + 204, + 486, + 1 + ], + [ + 204, + 678, + 1 + ], + [ + 204, + 932, + 1 + ], + [ + 205, + 287, + -1 + ], + [ + 205, + 329, + -1 + ], + [ + 205, + 586, + 1 + ], + [ + 205, + 846, + -1 + ], + [ + 205, + 976, + -1 + ], + [ + 206, + 346, + 1 + ], + [ + 206, + 441, + -1 + ], + [ + 206, + 482, + -1 + ], + [ + 206, + 682, + -1 + ], + [ + 206, + 999, + -1 + ], + [ + 207, + 237, + 1 + ], + [ + 207, + 555, + 1 + ], + [ + 207, + 647, + 1 + ], + [ + 208, + 552, + -1 + ], + [ + 208, + 815, + -1 + ], + [ + 208, + 935, + 1 + ], + [ + 208, + 957, + 1 + ], + [ + 209, + 363, + 1 + ], + [ + 209, + 907, + 1 + ], + [ + 209, + 946, + -1 + ], + [ + 210, + 345, + 1 + ], + [ + 210, + 400, + 1 + ], + [ + 210, + 532, + 1 + ], + [ + 210, + 656, + 1 + ], + [ + 210, + 837, + 1 + ], + [ + 211, + 217, + 1 + ], + [ + 211, + 385, + 1 + ], + [ + 211, + 402, + 1 + ], + [ + 211, + 465, + -1 + ], + [ + 211, + 630, + -1 + ], + [ + 211, + 933, + 1 + ], + [ + 212, + 255, + 1 + ], + [ + 212, + 340, + 1 + ], + [ + 212, + 488, + 1 + ], + [ + 212, + 837, + -1 + ], + [ + 213, + 443, + -1 + ], + [ + 213, + 546, + -1 + ], + [ + 213, + 628, + -1 + ], + [ + 213, + 634, + -1 + ], + [ + 213, + 722, + 1 + ], + [ + 213, + 848, + -1 + ], + [ + 213, + 973, + 1 + ], + [ + 214, + 376, + 1 + ], + [ + 214, + 406, + -1 + ], + [ + 214, + 455, + -1 + ], + [ + 214, + 787, + -1 + ], + [ + 214, + 827, + -1 + ], + [ + 215, + 331, + -1 + ], + [ + 215, + 802, + -1 + ], + [ + 215, + 870, + 1 + ], + [ + 216, + 300, + -1 + ], + [ + 216, + 652, + -1 + ], + [ + 216, + 767, + 1 + ], + [ + 216, + 853, + 1 + ], + [ + 216, + 902, + -1 + ], + [ + 217, + 310, + 1 + ], + [ + 217, + 387, + 1 + ], + [ + 217, + 615, + -1 + ], + [ + 217, + 695, + 1 + ], + [ + 217, + 835, + 1 + ], + [ + 217, + 871, + -1 + ], + [ + 217, + 949, + -1 + ], + [ + 217, + 992, + 1 + ], + [ + 218, + 247, + 1 + ], + [ + 218, + 405, + 1 + ], + [ + 218, + 428, + 1 + ], + [ + 218, + 500, + -1 + ], + [ + 218, + 864, + -1 + ], + [ + 219, + 271, + -1 + ], + [ + 219, + 307, + 1 + ], + [ + 219, + 731, + 1 + ], + [ + 219, + 858, + 1 + ], + [ + 220, + 226, + -1 + ], + [ + 220, + 401, + 1 + ], + [ + 220, + 595, + 1 + ], + [ + 220, + 759, + -1 + ], + [ + 220, + 781, + 1 + ], + [ + 221, + 358, + -1 + ], + [ + 221, + 373, + -1 + ], + [ + 221, + 384, + 1 + ], + [ + 221, + 503, + 1 + ], + [ + 221, + 545, + -1 + ], + [ + 221, + 628, + 1 + ], + [ + 221, + 790, + -1 + ], + [ + 221, + 909, + -1 + ], + [ + 222, + 343, + -1 + ], + [ + 222, + 502, + -1 + ], + [ + 222, + 602, + 1 + ], + [ + 222, + 640, + 1 + ], + [ + 222, + 776, + 1 + ], + [ + 222, + 897, + -1 + ], + [ + 223, + 229, + 1 + ], + [ + 223, + 314, + 1 + ], + [ + 223, + 744, + 1 + ], + [ + 223, + 890, + -1 + ], + [ + 223, + 900, + 1 + ], + [ + 223, + 984, + 1 + ], + [ + 224, + 247, + 1 + ], + [ + 224, + 521, + -1 + ], + [ + 224, + 778, + 1 + ], + [ + 224, + 820, + 1 + ], + [ + 224, + 982, + 1 + ], + [ + 225, + 295, + -1 + ], + [ + 225, + 474, + -1 + ], + [ + 225, + 691, + 1 + ], + [ + 225, + 883, + 1 + ], + [ + 225, + 937, + 1 + ], + [ + 226, + 550, + -1 + ], + [ + 226, + 631, + 1 + ], + [ + 226, + 961, + 1 + ], + [ + 227, + 400, + 1 + ], + [ + 227, + 886, + -1 + ], + [ + 227, + 970, + 1 + ], + [ + 228, + 571, + -1 + ], + [ + 228, + 735, + 1 + ], + [ + 228, + 852, + -1 + ], + [ + 228, + 948, + -1 + ], + [ + 229, + 313, + 1 + ], + [ + 229, + 451, + -1 + ], + [ + 229, + 593, + -1 + ], + [ + 230, + 349, + 1 + ], + [ + 230, + 584, + 1 + ], + [ + 230, + 977, + -1 + ], + [ + 231, + 237, + -1 + ], + [ + 231, + 302, + 1 + ], + [ + 231, + 306, + 1 + ], + [ + 231, + 333, + 1 + ], + [ + 231, + 334, + -1 + ], + [ + 231, + 539, + -1 + ], + [ + 231, + 561, + -1 + ], + [ + 231, + 632, + -1 + ], + [ + 231, + 673, + -1 + ], + [ + 231, + 682, + 1 + ], + [ + 231, + 729, + -1 + ], + [ + 231, + 749, + 1 + ], + [ + 231, + 877, + 1 + ], + [ + 231, + 930, + -1 + ], + [ + 232, + 629, + 1 + ], + [ + 232, + 777, + -1 + ], + [ + 232, + 792, + -1 + ], + [ + 232, + 890, + -1 + ], + [ + 233, + 237, + -1 + ], + [ + 234, + 353, + 1 + ], + [ + 234, + 376, + -1 + ], + [ + 234, + 485, + 1 + ], + [ + 234, + 657, + 1 + ], + [ + 234, + 927, + 1 + ], + [ + 235, + 436, + -1 + ], + [ + 235, + 547, + 1 + ], + [ + 235, + 858, + 1 + ], + [ + 236, + 261, + -1 + ], + [ + 236, + 433, + 1 + ], + [ + 236, + 604, + -1 + ], + [ + 236, + 628, + 1 + ], + [ + 236, + 674, + -1 + ], + [ + 236, + 849, + -1 + ], + [ + 237, + 280, + -1 + ], + [ + 237, + 333, + 1 + ], + [ + 237, + 542, + -1 + ], + [ + 237, + 717, + -1 + ], + [ + 237, + 719, + 1 + ], + [ + 237, + 919, + -1 + ], + [ + 238, + 421, + -1 + ], + [ + 238, + 840, + -1 + ], + [ + 238, + 859, + -1 + ], + [ + 239, + 478, + 1 + ], + [ + 239, + 531, + -1 + ], + [ + 239, + 780, + -1 + ], + [ + 240, + 367, + 1 + ], + [ + 240, + 377, + -1 + ], + [ + 240, + 398, + -1 + ], + [ + 240, + 540, + 1 + ], + [ + 240, + 745, + 1 + ], + [ + 240, + 811, + 1 + ], + [ + 241, + 375, + -1 + ], + [ + 241, + 521, + -1 + ], + [ + 241, + 575, + -1 + ], + [ + 241, + 634, + 1 + ], + [ + 241, + 970, + -1 + ], + [ + 242, + 264, + 1 + ], + [ + 242, + 392, + -1 + ], + [ + 242, + 462, + 1 + ], + [ + 242, + 560, + -1 + ], + [ + 243, + 315, + -1 + ], + [ + 243, + 611, + 1 + ], + [ + 243, + 688, + -1 + ], + [ + 243, + 810, + -1 + ], + [ + 243, + 963, + -1 + ], + [ + 244, + 286, + -1 + ], + [ + 244, + 418, + 1 + ], + [ + 244, + 421, + -1 + ], + [ + 244, + 785, + 1 + ], + [ + 244, + 900, + -1 + ], + [ + 245, + 386, + -1 + ], + [ + 245, + 404, + 1 + ], + [ + 245, + 413, + -1 + ], + [ + 245, + 476, + -1 + ], + [ + 245, + 519, + -1 + ], + [ + 245, + 797, + 1 + ], + [ + 245, + 839, + -1 + ], + [ + 246, + 637, + 1 + ], + [ + 246, + 676, + 1 + ], + [ + 246, + 680, + 1 + ], + [ + 246, + 724, + 1 + ], + [ + 246, + 748, + -1 + ], + [ + 246, + 759, + 1 + ], + [ + 246, + 884, + -1 + ], + [ + 246, + 916, + 1 + ], + [ + 247, + 412, + 1 + ], + [ + 247, + 480, + 1 + ], + [ + 247, + 537, + -1 + ], + [ + 247, + 906, + -1 + ], + [ + 247, + 938, + -1 + ], + [ + 247, + 960, + -1 + ], + [ + 247, + 961, + 1 + ], + [ + 248, + 587, + -1 + ], + [ + 248, + 667, + -1 + ], + [ + 248, + 685, + -1 + ], + [ + 248, + 757, + -1 + ], + [ + 248, + 793, + 1 + ], + [ + 248, + 798, + 1 + ], + [ + 248, + 910, + 1 + ], + [ + 248, + 967, + 1 + ], + [ + 249, + 335, + 1 + ], + [ + 249, + 423, + 1 + ], + [ + 249, + 509, + -1 + ], + [ + 249, + 512, + -1 + ], + [ + 250, + 315, + -1 + ], + [ + 250, + 329, + 1 + ], + [ + 250, + 516, + 1 + ], + [ + 250, + 681, + 1 + ], + [ + 250, + 876, + -1 + ], + [ + 251, + 466, + 1 + ], + [ + 251, + 573, + -1 + ], + [ + 251, + 911, + 1 + ], + [ + 251, + 969, + 1 + ], + [ + 251, + 977, + 1 + ], + [ + 252, + 440, + -1 + ], + [ + 252, + 885, + -1 + ], + [ + 252, + 925, + 1 + ], + [ + 253, + 364, + -1 + ], + [ + 253, + 951, + -1 + ], + [ + 254, + 386, + 1 + ], + [ + 254, + 526, + -1 + ], + [ + 254, + 665, + -1 + ], + [ + 254, + 690, + 1 + ], + [ + 254, + 719, + -1 + ], + [ + 254, + 946, + 1 + ], + [ + 255, + 517, + -1 + ], + [ + 255, + 572, + 1 + ], + [ + 255, + 581, + -1 + ], + [ + 255, + 873, + -1 + ], + [ + 256, + 382, + -1 + ], + [ + 256, + 887, + -1 + ], + [ + 257, + 290, + -1 + ], + [ + 257, + 488, + 1 + ], + [ + 257, + 553, + -1 + ], + [ + 257, + 586, + 1 + ], + [ + 257, + 623, + -1 + ], + [ + 257, + 695, + -1 + ], + [ + 257, + 731, + 1 + ], + [ + 258, + 296, + 1 + ], + [ + 258, + 303, + 1 + ], + [ + 258, + 396, + -1 + ], + [ + 258, + 886, + -1 + ], + [ + 258, + 889, + 1 + ], + [ + 259, + 362, + 1 + ], + [ + 259, + 568, + -1 + ], + [ + 259, + 713, + -1 + ], + [ + 260, + 444, + -1 + ], + [ + 261, + 554, + 1 + ], + [ + 261, + 697, + -1 + ], + [ + 261, + 716, + -1 + ], + [ + 261, + 751, + 1 + ], + [ + 262, + 301, + -1 + ], + [ + 262, + 926, + 1 + ], + [ + 263, + 384, + 1 + ], + [ + 263, + 398, + -1 + ], + [ + 263, + 447, + -1 + ], + [ + 263, + 512, + 1 + ], + [ + 263, + 567, + -1 + ], + [ + 263, + 626, + -1 + ], + [ + 263, + 796, + 1 + ], + [ + 263, + 877, + -1 + ], + [ + 264, + 480, + 1 + ], + [ + 264, + 810, + -1 + ], + [ + 264, + 985, + 1 + ], + [ + 265, + 709, + -1 + ], + [ + 266, + 403, + -1 + ], + [ + 266, + 780, + -1 + ], + [ + 267, + 495, + -1 + ], + [ + 267, + 632, + -1 + ], + [ + 267, + 752, + -1 + ], + [ + 268, + 286, + 1 + ], + [ + 268, + 290, + 1 + ], + [ + 268, + 357, + 1 + ], + [ + 268, + 469, + 1 + ], + [ + 268, + 541, + -1 + ], + [ + 268, + 702, + -1 + ], + [ + 268, + 789, + -1 + ], + [ + 268, + 808, + -1 + ], + [ + 268, + 892, + -1 + ], + [ + 268, + 995, + 1 + ], + [ + 269, + 808, + -1 + ], + [ + 269, + 894, + 1 + ], + [ + 270, + 353, + -1 + ], + [ + 270, + 533, + 1 + ], + [ + 270, + 579, + -1 + ], + [ + 270, + 658, + -1 + ], + [ + 270, + 745, + 1 + ], + [ + 270, + 760, + 1 + ], + [ + 270, + 769, + 1 + ], + [ + 270, + 946, + -1 + ], + [ + 271, + 504, + -1 + ], + [ + 272, + 340, + -1 + ], + [ + 272, + 563, + 1 + ], + [ + 273, + 584, + -1 + ], + [ + 273, + 730, + -1 + ], + [ + 273, + 787, + -1 + ], + [ + 273, + 866, + 1 + ], + [ + 274, + 370, + 1 + ], + [ + 274, + 402, + 1 + ], + [ + 274, + 497, + 1 + ], + [ + 275, + 553, + -1 + ], + [ + 275, + 555, + -1 + ], + [ + 275, + 638, + 1 + ], + [ + 275, + 699, + -1 + ], + [ + 275, + 754, + 1 + ], + [ + 277, + 314, + -1 + ], + [ + 277, + 726, + -1 + ], + [ + 277, + 732, + 1 + ], + [ + 277, + 823, + 1 + ], + [ + 278, + 610, + 1 + ], + [ + 278, + 871, + 1 + ], + [ + 279, + 377, + 1 + ], + [ + 279, + 438, + -1 + ], + [ + 279, + 455, + -1 + ], + [ + 279, + 780, + -1 + ], + [ + 279, + 809, + -1 + ], + [ + 280, + 287, + -1 + ], + [ + 280, + 358, + 1 + ], + [ + 280, + 379, + 1 + ], + [ + 280, + 768, + -1 + ], + [ + 280, + 886, + -1 + ], + [ + 280, + 980, + 1 + ], + [ + 280, + 982, + 1 + ], + [ + 281, + 807, + 1 + ], + [ + 281, + 810, + 1 + ], + [ + 282, + 557, + -1 + ], + [ + 282, + 588, + 1 + ], + [ + 282, + 614, + -1 + ], + [ + 282, + 867, + 1 + ], + [ + 283, + 436, + -1 + ], + [ + 283, + 775, + -1 + ], + [ + 283, + 839, + 1 + ], + [ + 283, + 847, + 1 + ], + [ + 283, + 884, + 1 + ], + [ + 284, + 293, + -1 + ], + [ + 284, + 443, + -1 + ], + [ + 284, + 529, + -1 + ], + [ + 285, + 883, + -1 + ], + [ + 285, + 884, + 1 + ], + [ + 285, + 915, + -1 + ], + [ + 286, + 541, + -1 + ], + [ + 286, + 552, + -1 + ], + [ + 286, + 827, + -1 + ], + [ + 286, + 839, + 1 + ], + [ + 287, + 334, + -1 + ], + [ + 287, + 441, + 1 + ], + [ + 287, + 532, + -1 + ], + [ + 287, + 693, + 1 + ], + [ + 287, + 720, + -1 + ], + [ + 287, + 830, + 1 + ], + [ + 287, + 862, + -1 + ], + [ + 288, + 636, + -1 + ], + [ + 288, + 707, + -1 + ], + [ + 288, + 889, + 1 + ], + [ + 289, + 648, + -1 + ], + [ + 289, + 801, + -1 + ], + [ + 289, + 818, + -1 + ], + [ + 289, + 902, + 1 + ], + [ + 290, + 464, + -1 + ], + [ + 290, + 612, + 1 + ], + [ + 290, + 747, + 1 + ], + [ + 291, + 397, + -1 + ], + [ + 291, + 579, + -1 + ], + [ + 291, + 841, + -1 + ], + [ + 291, + 844, + 1 + ], + [ + 292, + 432, + -1 + ], + [ + 292, + 481, + 1 + ], + [ + 292, + 486, + -1 + ], + [ + 292, + 905, + -1 + ], + [ + 293, + 304, + 1 + ], + [ + 293, + 372, + 1 + ], + [ + 293, + 472, + 1 + ], + [ + 293, + 573, + 1 + ], + [ + 293, + 606, + 1 + ], + [ + 294, + 385, + -1 + ], + [ + 294, + 516, + -1 + ], + [ + 294, + 577, + 1 + ], + [ + 294, + 625, + -1 + ], + [ + 294, + 793, + -1 + ], + [ + 295, + 616, + 1 + ], + [ + 295, + 843, + -1 + ], + [ + 296, + 302, + -1 + ], + [ + 296, + 492, + -1 + ], + [ + 296, + 599, + -1 + ], + [ + 296, + 949, + -1 + ], + [ + 297, + 446, + 1 + ], + [ + 297, + 606, + 1 + ], + [ + 297, + 713, + 1 + ], + [ + 297, + 723, + 1 + ], + [ + 298, + 600, + -1 + ], + [ + 298, + 798, + -1 + ], + [ + 298, + 963, + 1 + ], + [ + 299, + 407, + -1 + ], + [ + 299, + 561, + -1 + ], + [ + 299, + 953, + 1 + ], + [ + 300, + 474, + 1 + ], + [ + 300, + 593, + 1 + ], + [ + 300, + 658, + 1 + ], + [ + 300, + 910, + -1 + ], + [ + 300, + 977, + 1 + ], + [ + 301, + 857, + -1 + ], + [ + 302, + 440, + 1 + ], + [ + 302, + 495, + -1 + ], + [ + 303, + 389, + 1 + ], + [ + 303, + 804, + 1 + ], + [ + 303, + 903, + 1 + ], + [ + 303, + 916, + 1 + ], + [ + 304, + 390, + -1 + ], + [ + 304, + 391, + -1 + ], + [ + 304, + 447, + 1 + ], + [ + 304, + 554, + -1 + ], + [ + 304, + 568, + 1 + ], + [ + 305, + 329, + -1 + ], + [ + 305, + 391, + -1 + ], + [ + 305, + 432, + -1 + ], + [ + 305, + 680, + -1 + ], + [ + 305, + 726, + -1 + ], + [ + 305, + 738, + 1 + ], + [ + 305, + 746, + 1 + ], + [ + 305, + 912, + 1 + ], + [ + 306, + 552, + -1 + ], + [ + 306, + 595, + -1 + ], + [ + 307, + 457, + -1 + ], + [ + 307, + 859, + -1 + ], + [ + 307, + 878, + 1 + ], + [ + 307, + 934, + 1 + ], + [ + 307, + 967, + 1 + ], + [ + 308, + 758, + 1 + ], + [ + 308, + 800, + 1 + ], + [ + 309, + 379, + 1 + ], + [ + 309, + 964, + -1 + ], + [ + 310, + 347, + -1 + ], + [ + 310, + 725, + 1 + ], + [ + 310, + 799, + -1 + ], + [ + 311, + 435, + -1 + ], + [ + 311, + 599, + 1 + ], + [ + 311, + 720, + -1 + ], + [ + 311, + 932, + 1 + ], + [ + 312, + 387, + 1 + ], + [ + 312, + 706, + 1 + ], + [ + 312, + 857, + 1 + ], + [ + 313, + 392, + 1 + ], + [ + 313, + 444, + -1 + ], + [ + 313, + 459, + 1 + ], + [ + 313, + 515, + 1 + ], + [ + 313, + 557, + -1 + ], + [ + 313, + 710, + 1 + ], + [ + 313, + 870, + -1 + ], + [ + 314, + 710, + 1 + ], + [ + 314, + 733, + 1 + ], + [ + 314, + 746, + -1 + ], + [ + 314, + 758, + 1 + ], + [ + 315, + 645, + 1 + ], + [ + 315, + 686, + -1 + ], + [ + 315, + 750, + 1 + ], + [ + 316, + 395, + -1 + ], + [ + 316, + 720, + 1 + ], + [ + 316, + 753, + -1 + ], + [ + 316, + 757, + -1 + ], + [ + 316, + 938, + -1 + ], + [ + 317, + 516, + 1 + ], + [ + 317, + 632, + 1 + ], + [ + 318, + 328, + 1 + ], + [ + 318, + 600, + 1 + ], + [ + 318, + 842, + 1 + ], + [ + 318, + 881, + 1 + ], + [ + 319, + 971, + 1 + ], + [ + 320, + 339, + -1 + ], + [ + 321, + 431, + -1 + ], + [ + 321, + 579, + 1 + ], + [ + 321, + 660, + 1 + ], + [ + 321, + 741, + -1 + ], + [ + 321, + 906, + -1 + ], + [ + 322, + 411, + 1 + ], + [ + 322, + 726, + -1 + ], + [ + 323, + 438, + 1 + ], + [ + 323, + 475, + -1 + ], + [ + 323, + 639, + -1 + ], + [ + 324, + 374, + -1 + ], + [ + 324, + 487, + 1 + ], + [ + 324, + 508, + -1 + ], + [ + 324, + 607, + 1 + ], + [ + 324, + 885, + 1 + ], + [ + 324, + 894, + -1 + ], + [ + 325, + 346, + -1 + ], + [ + 325, + 452, + 1 + ], + [ + 325, + 461, + 1 + ], + [ + 325, + 477, + -1 + ], + [ + 325, + 491, + 1 + ], + [ + 325, + 503, + 1 + ], + [ + 325, + 511, + 1 + ], + [ + 325, + 607, + 1 + ], + [ + 325, + 697, + -1 + ], + [ + 325, + 768, + -1 + ], + [ + 325, + 770, + -1 + ], + [ + 325, + 787, + 1 + ], + [ + 326, + 540, + 1 + ], + [ + 326, + 928, + -1 + ], + [ + 327, + 486, + 1 + ], + [ + 327, + 624, + 1 + ], + [ + 327, + 670, + 1 + ], + [ + 328, + 352, + 1 + ], + [ + 328, + 382, + 1 + ], + [ + 328, + 495, + -1 + ], + [ + 328, + 543, + 1 + ], + [ + 328, + 768, + -1 + ], + [ + 328, + 930, + -1 + ], + [ + 328, + 969, + 1 + ], + [ + 329, + 567, + -1 + ], + [ + 329, + 779, + 1 + ], + [ + 330, + 353, + -1 + ], + [ + 330, + 579, + 1 + ], + [ + 330, + 728, + 1 + ], + [ + 330, + 826, + 1 + ], + [ + 331, + 493, + 1 + ], + [ + 331, + 879, + -1 + ], + [ + 331, + 941, + 1 + ], + [ + 331, + 980, + -1 + ], + [ + 332, + 394, + -1 + ], + [ + 332, + 459, + 1 + ], + [ + 332, + 799, + -1 + ], + [ + 332, + 855, + -1 + ], + [ + 333, + 359, + -1 + ], + [ + 333, + 425, + 1 + ], + [ + 333, + 533, + -1 + ], + [ + 333, + 558, + -1 + ], + [ + 333, + 718, + -1 + ], + [ + 333, + 751, + -1 + ], + [ + 333, + 982, + -1 + ], + [ + 334, + 381, + 1 + ], + [ + 334, + 588, + 1 + ], + [ + 335, + 350, + -1 + ], + [ + 335, + 405, + 1 + ], + [ + 335, + 567, + 1 + ], + [ + 335, + 646, + 1 + ], + [ + 335, + 852, + -1 + ], + [ + 336, + 433, + -1 + ], + [ + 336, + 474, + -1 + ], + [ + 336, + 509, + 1 + ], + [ + 336, + 520, + 1 + ], + [ + 336, + 549, + -1 + ], + [ + 336, + 796, + -1 + ], + [ + 336, + 983, + -1 + ], + [ + 337, + 418, + -1 + ], + [ + 337, + 419, + -1 + ], + [ + 337, + 580, + 1 + ], + [ + 338, + 582, + -1 + ], + [ + 338, + 706, + 1 + ], + [ + 338, + 983, + 1 + ], + [ + 339, + 448, + 1 + ], + [ + 339, + 512, + 1 + ], + [ + 339, + 532, + -1 + ], + [ + 339, + 749, + 1 + ], + [ + 339, + 755, + 1 + ], + [ + 340, + 384, + -1 + ], + [ + 340, + 555, + -1 + ], + [ + 340, + 691, + -1 + ], + [ + 341, + 429, + -1 + ], + [ + 341, + 636, + 1 + ], + [ + 341, + 670, + -1 + ], + [ + 341, + 801, + 1 + ], + [ + 342, + 715, + 1 + ], + [ + 342, + 791, + -1 + ], + [ + 342, + 798, + 1 + ], + [ + 342, + 801, + -1 + ], + [ + 342, + 866, + 1 + ], + [ + 343, + 407, + -1 + ], + [ + 343, + 487, + 1 + ], + [ + 343, + 544, + -1 + ], + [ + 343, + 651, + 1 + ], + [ + 343, + 952, + 1 + ], + [ + 344, + 581, + -1 + ], + [ + 344, + 586, + 1 + ], + [ + 344, + 769, + 1 + ], + [ + 345, + 785, + -1 + ], + [ + 345, + 796, + -1 + ], + [ + 345, + 868, + -1 + ], + [ + 345, + 978, + -1 + ], + [ + 346, + 405, + -1 + ], + [ + 346, + 520, + -1 + ], + [ + 346, + 918, + -1 + ], + [ + 347, + 414, + -1 + ], + [ + 347, + 533, + -1 + ], + [ + 347, + 825, + -1 + ], + [ + 347, + 897, + 1 + ], + [ + 347, + 960, + 1 + ], + [ + 348, + 530, + 1 + ], + [ + 348, + 696, + -1 + ], + [ + 350, + 872, + -1 + ], + [ + 350, + 963, + -1 + ], + [ + 351, + 448, + 1 + ], + [ + 351, + 534, + 1 + ], + [ + 351, + 613, + 1 + ], + [ + 351, + 720, + 1 + ], + [ + 351, + 918, + -1 + ], + [ + 352, + 359, + 1 + ], + [ + 352, + 651, + -1 + ], + [ + 352, + 654, + 1 + ], + [ + 352, + 665, + 1 + ], + [ + 352, + 803, + -1 + ], + [ + 352, + 989, + -1 + ], + [ + 353, + 487, + 1 + ], + [ + 353, + 543, + 1 + ], + [ + 353, + 584, + 1 + ], + [ + 353, + 830, + 1 + ], + [ + 353, + 947, + -1 + ], + [ + 354, + 505, + 1 + ], + [ + 354, + 611, + 1 + ], + [ + 354, + 751, + -1 + ], + [ + 354, + 899, + -1 + ], + [ + 354, + 967, + -1 + ], + [ + 355, + 420, + 1 + ], + [ + 355, + 450, + 1 + ], + [ + 355, + 687, + 1 + ], + [ + 355, + 691, + 1 + ], + [ + 355, + 819, + -1 + ], + [ + 356, + 400, + -1 + ], + [ + 356, + 539, + 1 + ], + [ + 356, + 690, + 1 + ], + [ + 356, + 925, + -1 + ], + [ + 356, + 957, + -1 + ], + [ + 357, + 706, + -1 + ], + [ + 357, + 708, + -1 + ], + [ + 357, + 800, + 1 + ], + [ + 358, + 514, + -1 + ], + [ + 358, + 521, + 1 + ], + [ + 358, + 686, + 1 + ], + [ + 358, + 788, + 1 + ], + [ + 359, + 424, + 1 + ], + [ + 359, + 539, + -1 + ], + [ + 359, + 754, + -1 + ], + [ + 359, + 827, + -1 + ], + [ + 359, + 895, + 1 + ], + [ + 359, + 947, + 1 + ], + [ + 361, + 546, + 1 + ], + [ + 361, + 593, + -1 + ], + [ + 361, + 782, + 1 + ], + [ + 362, + 391, + -1 + ], + [ + 362, + 497, + 1 + ], + [ + 362, + 589, + 1 + ], + [ + 362, + 607, + 1 + ], + [ + 362, + 978, + -1 + ], + [ + 363, + 436, + -1 + ], + [ + 363, + 573, + -1 + ], + [ + 363, + 666, + 1 + ], + [ + 363, + 683, + -1 + ], + [ + 363, + 744, + -1 + ], + [ + 363, + 754, + 1 + ], + [ + 363, + 835, + 1 + ], + [ + 364, + 579, + -1 + ], + [ + 364, + 612, + -1 + ], + [ + 364, + 705, + -1 + ], + [ + 364, + 869, + -1 + ], + [ + 365, + 557, + -1 + ], + [ + 365, + 674, + -1 + ], + [ + 365, + 765, + 1 + ], + [ + 365, + 956, + 1 + ], + [ + 366, + 557, + -1 + ], + [ + 366, + 648, + -1 + ], + [ + 367, + 400, + 1 + ], + [ + 368, + 536, + 1 + ], + [ + 368, + 968, + -1 + ], + [ + 369, + 528, + 1 + ], + [ + 369, + 604, + -1 + ], + [ + 369, + 768, + -1 + ], + [ + 369, + 791, + 1 + ], + [ + 369, + 879, + -1 + ], + [ + 369, + 965, + -1 + ], + [ + 370, + 418, + -1 + ], + [ + 370, + 488, + -1 + ], + [ + 370, + 492, + 1 + ], + [ + 370, + 701, + -1 + ], + [ + 370, + 730, + 1 + ], + [ + 370, + 815, + 1 + ], + [ + 370, + 833, + 1 + ], + [ + 370, + 848, + -1 + ], + [ + 370, + 902, + 1 + ], + [ + 371, + 620, + 1 + ], + [ + 371, + 629, + 1 + ], + [ + 371, + 787, + -1 + ], + [ + 371, + 899, + -1 + ], + [ + 371, + 911, + 1 + ], + [ + 371, + 934, + 1 + ], + [ + 372, + 593, + 1 + ], + [ + 372, + 827, + -1 + ], + [ + 372, + 884, + 1 + ], + [ + 372, + 910, + -1 + ], + [ + 373, + 554, + -1 + ], + [ + 373, + 555, + -1 + ], + [ + 373, + 572, + 1 + ], + [ + 373, + 603, + 1 + ], + [ + 373, + 851, + 1 + ], + [ + 374, + 434, + 1 + ], + [ + 374, + 468, + 1 + ], + [ + 375, + 409, + -1 + ], + [ + 375, + 652, + -1 + ], + [ + 375, + 802, + 1 + ], + [ + 375, + 876, + -1 + ], + [ + 376, + 383, + 1 + ], + [ + 376, + 538, + -1 + ], + [ + 376, + 634, + 1 + ], + [ + 377, + 740, + 1 + ], + [ + 377, + 824, + 1 + ], + [ + 377, + 891, + -1 + ], + [ + 378, + 688, + -1 + ], + [ + 378, + 790, + -1 + ], + [ + 378, + 853, + 1 + ], + [ + 378, + 983, + -1 + ], + [ + 378, + 994, + 1 + ], + [ + 379, + 503, + 1 + ], + [ + 379, + 644, + -1 + ], + [ + 379, + 933, + -1 + ], + [ + 379, + 972, + -1 + ], + [ + 380, + 611, + 1 + ], + [ + 380, + 721, + 1 + ], + [ + 380, + 828, + 1 + ], + [ + 381, + 462, + -1 + ], + [ + 381, + 505, + 1 + ], + [ + 382, + 523, + 1 + ], + [ + 382, + 605, + 1 + ], + [ + 382, + 664, + 1 + ], + [ + 382, + 709, + 1 + ], + [ + 382, + 857, + -1 + ], + [ + 382, + 882, + 1 + ], + [ + 383, + 975, + 1 + ], + [ + 384, + 523, + -1 + ], + [ + 385, + 444, + -1 + ], + [ + 385, + 637, + -1 + ], + [ + 385, + 786, + 1 + ], + [ + 385, + 912, + -1 + ], + [ + 386, + 563, + 1 + ], + [ + 386, + 594, + -1 + ], + [ + 386, + 928, + 1 + ], + [ + 386, + 944, + -1 + ], + [ + 386, + 961, + 1 + ], + [ + 387, + 693, + 1 + ], + [ + 387, + 769, + -1 + ], + [ + 388, + 429, + 1 + ], + [ + 388, + 569, + 1 + ], + [ + 388, + 631, + 1 + ], + [ + 388, + 652, + -1 + ], + [ + 389, + 474, + 1 + ], + [ + 389, + 712, + -1 + ], + [ + 390, + 685, + 1 + ], + [ + 390, + 938, + 1 + ], + [ + 391, + 727, + -1 + ], + [ + 391, + 776, + 1 + ], + [ + 391, + 941, + 1 + ], + [ + 392, + 431, + -1 + ], + [ + 392, + 468, + -1 + ], + [ + 392, + 724, + 1 + ], + [ + 392, + 835, + -1 + ], + [ + 392, + 936, + 1 + ], + [ + 393, + 631, + 1 + ], + [ + 394, + 420, + 1 + ], + [ + 394, + 435, + 1 + ], + [ + 394, + 855, + -1 + ], + [ + 394, + 925, + -1 + ], + [ + 394, + 929, + 1 + ], + [ + 394, + 983, + 1 + ], + [ + 395, + 571, + 1 + ], + [ + 395, + 654, + 1 + ], + [ + 395, + 690, + 1 + ], + [ + 395, + 801, + -1 + ], + [ + 395, + 981, + -1 + ], + [ + 396, + 437, + -1 + ], + [ + 396, + 817, + 1 + ], + [ + 397, + 407, + 1 + ], + [ + 397, + 445, + -1 + ], + [ + 397, + 522, + -1 + ], + [ + 397, + 571, + -1 + ], + [ + 397, + 578, + -1 + ], + [ + 397, + 633, + 1 + ], + [ + 397, + 784, + -1 + ], + [ + 397, + 934, + 1 + ], + [ + 397, + 943, + 1 + ], + [ + 398, + 457, + 1 + ], + [ + 398, + 630, + -1 + ], + [ + 398, + 999, + -1 + ], + [ + 399, + 429, + 1 + ], + [ + 399, + 525, + -1 + ], + [ + 399, + 585, + -1 + ], + [ + 399, + 962, + -1 + ], + [ + 400, + 725, + 1 + ], + [ + 400, + 820, + 1 + ], + [ + 400, + 861, + 1 + ], + [ + 401, + 446, + -1 + ], + [ + 401, + 478, + -1 + ], + [ + 401, + 648, + 1 + ], + [ + 401, + 664, + 1 + ], + [ + 401, + 718, + -1 + ], + [ + 401, + 738, + 1 + ], + [ + 401, + 865, + 1 + ], + [ + 402, + 796, + -1 + ], + [ + 402, + 842, + -1 + ], + [ + 402, + 880, + 1 + ], + [ + 403, + 571, + 1 + ], + [ + 403, + 673, + -1 + ], + [ + 403, + 832, + 1 + ], + [ + 404, + 542, + 1 + ], + [ + 404, + 653, + -1 + ], + [ + 404, + 821, + -1 + ], + [ + 404, + 885, + 1 + ], + [ + 404, + 914, + 1 + ], + [ + 404, + 989, + 1 + ], + [ + 405, + 418, + -1 + ], + [ + 405, + 652, + -1 + ], + [ + 405, + 690, + -1 + ], + [ + 406, + 775, + -1 + ], + [ + 407, + 455, + 1 + ], + [ + 407, + 690, + -1 + ], + [ + 407, + 888, + -1 + ], + [ + 407, + 937, + 1 + ], + [ + 407, + 994, + 1 + ], + [ + 408, + 704, + -1 + ], + [ + 408, + 775, + -1 + ], + [ + 409, + 491, + -1 + ], + [ + 409, + 583, + -1 + ], + [ + 409, + 589, + -1 + ], + [ + 409, + 632, + -1 + ], + [ + 409, + 634, + -1 + ], + [ + 409, + 721, + 1 + ], + [ + 409, + 825, + -1 + ], + [ + 409, + 894, + -1 + ], + [ + 409, + 970, + -1 + ], + [ + 410, + 762, + -1 + ], + [ + 411, + 828, + 1 + ], + [ + 411, + 853, + 1 + ], + [ + 412, + 783, + 1 + ], + [ + 412, + 836, + -1 + ], + [ + 413, + 423, + -1 + ], + [ + 413, + 622, + 1 + ], + [ + 414, + 493, + 1 + ], + [ + 415, + 653, + -1 + ], + [ + 415, + 735, + -1 + ], + [ + 415, + 836, + 1 + ], + [ + 416, + 724, + -1 + ], + [ + 416, + 781, + -1 + ], + [ + 416, + 854, + 1 + ], + [ + 417, + 434, + 1 + ], + [ + 417, + 460, + 1 + ], + [ + 417, + 617, + -1 + ], + [ + 417, + 639, + 1 + ], + [ + 417, + 659, + 1 + ], + [ + 417, + 698, + -1 + ], + [ + 417, + 868, + 1 + ], + [ + 418, + 522, + 1 + ], + [ + 418, + 661, + 1 + ], + [ + 418, + 725, + -1 + ], + [ + 419, + 573, + -1 + ], + [ + 419, + 601, + 1 + ], + [ + 419, + 626, + 1 + ], + [ + 419, + 874, + 1 + ], + [ + 419, + 876, + 1 + ], + [ + 419, + 972, + 1 + ], + [ + 420, + 536, + 1 + ], + [ + 420, + 981, + -1 + ], + [ + 421, + 479, + -1 + ], + [ + 421, + 502, + 1 + ], + [ + 421, + 504, + -1 + ], + [ + 421, + 734, + 1 + ], + [ + 421, + 785, + -1 + ], + [ + 421, + 949, + 1 + ], + [ + 422, + 697, + 1 + ], + [ + 422, + 717, + -1 + ], + [ + 422, + 821, + -1 + ], + [ + 422, + 837, + -1 + ], + [ + 422, + 843, + -1 + ], + [ + 423, + 508, + 1 + ], + [ + 423, + 534, + 1 + ], + [ + 423, + 604, + 1 + ], + [ + 423, + 639, + 1 + ], + [ + 423, + 691, + -1 + ], + [ + 423, + 693, + -1 + ], + [ + 423, + 710, + 1 + ], + [ + 424, + 959, + 1 + ], + [ + 424, + 991, + 1 + ], + [ + 425, + 554, + 1 + ], + [ + 425, + 759, + -1 + ], + [ + 425, + 909, + -1 + ], + [ + 425, + 937, + -1 + ], + [ + 427, + 595, + -1 + ], + [ + 427, + 811, + 1 + ], + [ + 428, + 680, + 1 + ], + [ + 428, + 750, + 1 + ], + [ + 429, + 434, + -1 + ], + [ + 429, + 702, + -1 + ], + [ + 429, + 709, + 1 + ], + [ + 430, + 570, + -1 + ], + [ + 430, + 623, + 1 + ], + [ + 430, + 657, + -1 + ], + [ + 430, + 947, + 1 + ], + [ + 432, + 474, + -1 + ], + [ + 432, + 521, + 1 + ], + [ + 432, + 570, + -1 + ], + [ + 432, + 622, + -1 + ], + [ + 432, + 753, + -1 + ], + [ + 433, + 552, + -1 + ], + [ + 434, + 510, + -1 + ], + [ + 434, + 511, + 1 + ], + [ + 434, + 634, + 1 + ], + [ + 434, + 656, + 1 + ], + [ + 434, + 767, + 1 + ], + [ + 435, + 573, + -1 + ], + [ + 435, + 737, + 1 + ], + [ + 435, + 752, + 1 + ], + [ + 435, + 777, + 1 + ], + [ + 435, + 782, + 1 + ], + [ + 435, + 888, + -1 + ], + [ + 435, + 930, + 1 + ], + [ + 436, + 471, + 1 + ], + [ + 436, + 686, + 1 + ], + [ + 437, + 450, + -1 + ], + [ + 437, + 571, + 1 + ], + [ + 437, + 717, + 1 + ], + [ + 437, + 998, + -1 + ], + [ + 438, + 497, + -1 + ], + [ + 438, + 624, + 1 + ], + [ + 438, + 671, + -1 + ], + [ + 438, + 949, + -1 + ], + [ + 439, + 766, + 1 + ], + [ + 440, + 777, + -1 + ], + [ + 440, + 840, + 1 + ], + [ + 440, + 959, + 1 + ], + [ + 441, + 538, + -1 + ], + [ + 441, + 630, + 1 + ], + [ + 441, + 823, + 1 + ], + [ + 441, + 901, + -1 + ], + [ + 442, + 596, + -1 + ], + [ + 442, + 626, + -1 + ], + [ + 442, + 660, + -1 + ], + [ + 443, + 475, + -1 + ], + [ + 443, + 577, + -1 + ], + [ + 443, + 650, + 1 + ], + [ + 443, + 719, + -1 + ], + [ + 443, + 781, + -1 + ], + [ + 444, + 646, + 1 + ], + [ + 444, + 728, + 1 + ], + [ + 444, + 810, + -1 + ], + [ + 444, + 897, + -1 + ], + [ + 444, + 902, + -1 + ], + [ + 444, + 970, + 1 + ], + [ + 445, + 487, + 1 + ], + [ + 445, + 488, + -1 + ], + [ + 445, + 639, + 1 + ], + [ + 445, + 981, + -1 + ], + [ + 446, + 586, + 1 + ], + [ + 446, + 779, + 1 + ], + [ + 446, + 827, + 1 + ], + [ + 446, + 933, + -1 + ], + [ + 447, + 828, + 1 + ], + [ + 447, + 992, + 1 + ], + [ + 449, + 604, + -1 + ], + [ + 449, + 606, + 1 + ], + [ + 449, + 613, + -1 + ], + [ + 449, + 644, + -1 + ], + [ + 449, + 676, + 1 + ], + [ + 449, + 685, + -1 + ], + [ + 449, + 742, + 1 + ], + [ + 450, + 508, + -1 + ], + [ + 450, + 610, + 1 + ], + [ + 450, + 697, + 1 + ], + [ + 450, + 705, + -1 + ], + [ + 450, + 796, + -1 + ], + [ + 450, + 838, + 1 + ], + [ + 451, + 491, + -1 + ], + [ + 451, + 591, + -1 + ], + [ + 451, + 869, + -1 + ], + [ + 452, + 633, + -1 + ], + [ + 452, + 751, + -1 + ], + [ + 452, + 912, + 1 + ], + [ + 452, + 939, + 1 + ], + [ + 452, + 990, + 1 + ], + [ + 453, + 508, + -1 + ], + [ + 453, + 670, + -1 + ], + [ + 453, + 963, + 1 + ], + [ + 454, + 490, + -1 + ], + [ + 454, + 544, + -1 + ], + [ + 454, + 617, + 1 + ], + [ + 454, + 633, + -1 + ], + [ + 454, + 642, + 1 + ], + [ + 454, + 655, + -1 + ], + [ + 454, + 778, + -1 + ], + [ + 454, + 802, + 1 + ], + [ + 455, + 468, + -1 + ], + [ + 455, + 663, + 1 + ], + [ + 455, + 686, + 1 + ], + [ + 455, + 892, + 1 + ], + [ + 455, + 944, + 1 + ], + [ + 456, + 529, + 1 + ], + [ + 456, + 570, + 1 + ], + [ + 456, + 654, + 1 + ], + [ + 456, + 722, + -1 + ], + [ + 456, + 845, + 1 + ], + [ + 457, + 503, + 1 + ], + [ + 457, + 776, + 1 + ], + [ + 458, + 474, + 1 + ], + [ + 458, + 519, + 1 + ], + [ + 459, + 675, + -1 + ], + [ + 459, + 700, + -1 + ], + [ + 459, + 738, + -1 + ], + [ + 459, + 845, + 1 + ], + [ + 460, + 540, + 1 + ], + [ + 461, + 534, + 1 + ], + [ + 461, + 637, + 1 + ], + [ + 461, + 784, + -1 + ], + [ + 461, + 966, + -1 + ], + [ + 461, + 974, + 1 + ], + [ + 462, + 563, + 1 + ], + [ + 462, + 577, + 1 + ], + [ + 462, + 716, + -1 + ], + [ + 463, + 557, + 1 + ], + [ + 463, + 786, + -1 + ], + [ + 464, + 616, + 1 + ], + [ + 464, + 632, + 1 + ], + [ + 464, + 939, + 1 + ], + [ + 465, + 536, + -1 + ], + [ + 465, + 870, + 1 + ], + [ + 466, + 478, + -1 + ], + [ + 466, + 638, + 1 + ], + [ + 467, + 481, + 1 + ], + [ + 467, + 487, + -1 + ], + [ + 467, + 517, + -1 + ], + [ + 467, + 538, + -1 + ], + [ + 467, + 579, + 1 + ], + [ + 467, + 590, + -1 + ], + [ + 467, + 797, + 1 + ], + [ + 467, + 832, + -1 + ], + [ + 467, + 939, + -1 + ], + [ + 467, + 948, + -1 + ], + [ + 468, + 559, + -1 + ], + [ + 469, + 556, + 1 + ], + [ + 469, + 758, + -1 + ], + [ + 469, + 907, + 1 + ], + [ + 470, + 569, + 1 + ], + [ + 470, + 646, + -1 + ], + [ + 470, + 677, + 1 + ], + [ + 470, + 800, + -1 + ], + [ + 470, + 821, + -1 + ], + [ + 470, + 844, + -1 + ], + [ + 470, + 906, + -1 + ], + [ + 470, + 982, + 1 + ], + [ + 471, + 482, + -1 + ], + [ + 471, + 508, + 1 + ], + [ + 472, + 546, + 1 + ], + [ + 472, + 582, + -1 + ], + [ + 472, + 620, + 1 + ], + [ + 473, + 897, + 1 + ], + [ + 474, + 492, + 1 + ], + [ + 474, + 584, + 1 + ], + [ + 474, + 793, + 1 + ], + [ + 474, + 899, + 1 + ], + [ + 475, + 538, + -1 + ], + [ + 475, + 586, + -1 + ], + [ + 475, + 599, + -1 + ], + [ + 475, + 666, + -1 + ], + [ + 475, + 737, + -1 + ], + [ + 475, + 976, + 1 + ], + [ + 476, + 491, + 1 + ], + [ + 476, + 509, + 1 + ], + [ + 476, + 683, + -1 + ], + [ + 476, + 740, + -1 + ], + [ + 476, + 763, + 1 + ], + [ + 478, + 594, + 1 + ], + [ + 478, + 864, + 1 + ], + [ + 478, + 987, + -1 + ], + [ + 479, + 487, + -1 + ], + [ + 479, + 489, + 1 + ], + [ + 479, + 979, + -1 + ], + [ + 480, + 628, + -1 + ], + [ + 480, + 653, + -1 + ], + [ + 480, + 701, + 1 + ], + [ + 480, + 877, + -1 + ], + [ + 480, + 997, + 1 + ], + [ + 481, + 775, + -1 + ], + [ + 481, + 801, + 1 + ], + [ + 481, + 852, + -1 + ], + [ + 481, + 888, + 1 + ], + [ + 482, + 730, + 1 + ], + [ + 482, + 829, + 1 + ], + [ + 482, + 900, + 1 + ], + [ + 483, + 513, + -1 + ], + [ + 483, + 799, + -1 + ], + [ + 483, + 803, + 1 + ], + [ + 483, + 916, + 1 + ], + [ + 484, + 721, + 1 + ], + [ + 484, + 955, + -1 + ], + [ + 484, + 972, + -1 + ], + [ + 485, + 950, + 1 + ], + [ + 485, + 957, + -1 + ], + [ + 486, + 516, + 1 + ], + [ + 486, + 998, + -1 + ], + [ + 487, + 504, + -1 + ], + [ + 488, + 508, + 1 + ], + [ + 488, + 555, + 1 + ], + [ + 488, + 576, + -1 + ], + [ + 488, + 582, + 1 + ], + [ + 488, + 700, + 1 + ], + [ + 488, + 771, + 1 + ], + [ + 488, + 881, + 1 + ], + [ + 489, + 573, + 1 + ], + [ + 489, + 576, + 1 + ], + [ + 489, + 727, + -1 + ], + [ + 489, + 967, + -1 + ], + [ + 490, + 721, + 1 + ], + [ + 490, + 845, + 1 + ], + [ + 491, + 653, + 1 + ], + [ + 491, + 696, + -1 + ], + [ + 491, + 738, + 1 + ], + [ + 491, + 874, + 1 + ], + [ + 492, + 631, + -1 + ], + [ + 492, + 875, + -1 + ], + [ + 493, + 658, + 1 + ], + [ + 493, + 872, + 1 + ], + [ + 494, + 515, + -1 + ], + [ + 494, + 535, + -1 + ], + [ + 494, + 901, + -1 + ], + [ + 496, + 510, + -1 + ], + [ + 496, + 673, + -1 + ], + [ + 496, + 884, + -1 + ], + [ + 497, + 534, + 1 + ], + [ + 497, + 554, + 1 + ], + [ + 497, + 586, + 1 + ], + [ + 497, + 890, + -1 + ], + [ + 498, + 555, + 1 + ], + [ + 498, + 716, + 1 + ], + [ + 498, + 849, + 1 + ], + [ + 498, + 850, + -1 + ], + [ + 499, + 520, + 1 + ], + [ + 499, + 593, + -1 + ], + [ + 499, + 732, + 1 + ], + [ + 499, + 793, + -1 + ], + [ + 499, + 928, + 1 + ], + [ + 500, + 509, + 1 + ], + [ + 500, + 675, + -1 + ], + [ + 500, + 729, + -1 + ], + [ + 500, + 798, + 1 + ], + [ + 501, + 686, + -1 + ], + [ + 501, + 714, + 1 + ], + [ + 501, + 741, + -1 + ], + [ + 501, + 799, + 1 + ], + [ + 501, + 863, + 1 + ], + [ + 501, + 948, + -1 + ], + [ + 502, + 534, + -1 + ], + [ + 502, + 554, + 1 + ], + [ + 502, + 560, + 1 + ], + [ + 502, + 574, + 1 + ], + [ + 502, + 738, + -1 + ], + [ + 502, + 756, + 1 + ], + [ + 502, + 759, + 1 + ], + [ + 503, + 555, + -1 + ], + [ + 503, + 575, + -1 + ], + [ + 503, + 909, + -1 + ], + [ + 504, + 684, + 1 + ], + [ + 504, + 761, + -1 + ], + [ + 504, + 991, + 1 + ], + [ + 504, + 999, + -1 + ], + [ + 505, + 616, + 1 + ], + [ + 506, + 554, + 1 + ], + [ + 506, + 815, + -1 + ], + [ + 507, + 710, + 1 + ], + [ + 507, + 725, + 1 + ], + [ + 507, + 799, + -1 + ], + [ + 507, + 881, + -1 + ], + [ + 508, + 604, + -1 + ], + [ + 508, + 701, + -1 + ], + [ + 508, + 751, + 1 + ], + [ + 508, + 830, + -1 + ], + [ + 509, + 582, + -1 + ], + [ + 509, + 732, + 1 + ], + [ + 510, + 555, + -1 + ], + [ + 510, + 799, + -1 + ], + [ + 511, + 727, + -1 + ], + [ + 511, + 741, + 1 + ], + [ + 512, + 649, + -1 + ], + [ + 512, + 749, + 1 + ], + [ + 512, + 959, + -1 + ], + [ + 513, + 520, + 1 + ], + [ + 513, + 819, + -1 + ], + [ + 514, + 932, + 1 + ], + [ + 515, + 685, + 1 + ], + [ + 515, + 698, + -1 + ], + [ + 515, + 830, + -1 + ], + [ + 515, + 864, + -1 + ], + [ + 515, + 870, + 1 + ], + [ + 515, + 938, + 1 + ], + [ + 516, + 715, + 1 + ], + [ + 516, + 735, + 1 + ], + [ + 518, + 535, + -1 + ], + [ + 519, + 853, + 1 + ], + [ + 519, + 933, + -1 + ], + [ + 520, + 620, + -1 + ], + [ + 520, + 800, + -1 + ], + [ + 521, + 582, + 1 + ], + [ + 521, + 636, + -1 + ], + [ + 521, + 643, + -1 + ], + [ + 521, + 706, + 1 + ], + [ + 522, + 652, + -1 + ], + [ + 522, + 695, + -1 + ], + [ + 522, + 898, + 1 + ], + [ + 523, + 707, + 1 + ], + [ + 523, + 820, + -1 + ], + [ + 523, + 842, + -1 + ], + [ + 524, + 698, + 1 + ], + [ + 524, + 772, + 1 + ], + [ + 524, + 792, + 1 + ], + [ + 524, + 864, + 1 + ], + [ + 524, + 996, + 1 + ], + [ + 524, + 999, + -1 + ], + [ + 525, + 695, + 1 + ], + [ + 525, + 819, + 1 + ], + [ + 526, + 579, + -1 + ], + [ + 526, + 736, + -1 + ], + [ + 526, + 856, + -1 + ], + [ + 526, + 983, + 1 + ], + [ + 527, + 617, + -1 + ], + [ + 527, + 690, + 1 + ], + [ + 528, + 583, + 1 + ], + [ + 528, + 597, + -1 + ], + [ + 528, + 707, + 1 + ], + [ + 528, + 747, + -1 + ], + [ + 528, + 775, + -1 + ], + [ + 528, + 843, + 1 + ], + [ + 528, + 912, + -1 + ], + [ + 528, + 993, + -1 + ], + [ + 529, + 719, + -1 + ], + [ + 529, + 907, + 1 + ], + [ + 529, + 994, + 1 + ], + [ + 530, + 531, + -1 + ], + [ + 530, + 962, + -1 + ], + [ + 530, + 985, + 1 + ], + [ + 531, + 620, + -1 + ], + [ + 531, + 800, + -1 + ], + [ + 531, + 911, + -1 + ], + [ + 532, + 573, + 1 + ], + [ + 532, + 597, + -1 + ], + [ + 532, + 660, + 1 + ], + [ + 532, + 989, + -1 + ], + [ + 533, + 588, + -1 + ], + [ + 533, + 886, + -1 + ], + [ + 533, + 947, + 1 + ], + [ + 534, + 598, + -1 + ], + [ + 534, + 729, + -1 + ], + [ + 534, + 828, + -1 + ], + [ + 534, + 976, + -1 + ], + [ + 535, + 556, + 1 + ], + [ + 535, + 600, + -1 + ], + [ + 535, + 603, + -1 + ], + [ + 535, + 838, + -1 + ], + [ + 536, + 675, + -1 + ], + [ + 536, + 713, + 1 + ], + [ + 536, + 897, + -1 + ], + [ + 537, + 927, + 1 + ], + [ + 538, + 727, + 1 + ], + [ + 538, + 843, + -1 + ], + [ + 539, + 720, + 1 + ], + [ + 540, + 545, + -1 + ], + [ + 540, + 581, + -1 + ], + [ + 540, + 728, + 1 + ], + [ + 540, + 745, + -1 + ], + [ + 540, + 941, + -1 + ], + [ + 541, + 673, + 1 + ], + [ + 541, + 788, + 1 + ], + [ + 541, + 837, + -1 + ], + [ + 541, + 843, + -1 + ], + [ + 541, + 935, + -1 + ], + [ + 541, + 941, + -1 + ], + [ + 542, + 627, + 1 + ], + [ + 543, + 545, + 1 + ], + [ + 543, + 792, + 1 + ], + [ + 543, + 937, + -1 + ], + [ + 544, + 552, + 1 + ], + [ + 544, + 622, + -1 + ], + [ + 544, + 675, + 1 + ], + [ + 544, + 805, + 1 + ], + [ + 545, + 635, + 1 + ], + [ + 545, + 669, + 1 + ], + [ + 545, + 715, + 1 + ], + [ + 547, + 835, + -1 + ], + [ + 547, + 930, + 1 + ], + [ + 548, + 624, + 1 + ], + [ + 549, + 618, + 1 + ], + [ + 549, + 833, + -1 + ], + [ + 549, + 841, + -1 + ], + [ + 549, + 997, + -1 + ], + [ + 551, + 687, + -1 + ], + [ + 551, + 991, + -1 + ], + [ + 552, + 618, + -1 + ], + [ + 552, + 637, + 1 + ], + [ + 553, + 785, + -1 + ], + [ + 555, + 653, + -1 + ], + [ + 555, + 746, + -1 + ], + [ + 556, + 761, + 1 + ], + [ + 557, + 663, + 1 + ], + [ + 557, + 687, + -1 + ], + [ + 557, + 826, + 1 + ], + [ + 557, + 864, + -1 + ], + [ + 558, + 998, + -1 + ], + [ + 559, + 574, + 1 + ], + [ + 559, + 676, + 1 + ], + [ + 559, + 832, + 1 + ], + [ + 559, + 952, + 1 + ], + [ + 560, + 873, + 1 + ], + [ + 560, + 902, + -1 + ], + [ + 561, + 624, + -1 + ], + [ + 561, + 681, + 1 + ], + [ + 561, + 859, + 1 + ], + [ + 562, + 753, + -1 + ], + [ + 562, + 974, + -1 + ], + [ + 562, + 983, + 1 + ], + [ + 562, + 989, + 1 + ], + [ + 563, + 796, + -1 + ], + [ + 564, + 883, + 1 + ], + [ + 564, + 940, + 1 + ], + [ + 564, + 972, + -1 + ], + [ + 565, + 609, + 1 + ], + [ + 565, + 939, + -1 + ], + [ + 565, + 944, + 1 + ], + [ + 565, + 999, + -1 + ], + [ + 567, + 623, + 1 + ], + [ + 567, + 707, + 1 + ], + [ + 567, + 800, + 1 + ], + [ + 568, + 746, + -1 + ], + [ + 568, + 919, + -1 + ], + [ + 569, + 690, + -1 + ], + [ + 569, + 723, + -1 + ], + [ + 569, + 733, + 1 + ], + [ + 569, + 863, + 1 + ], + [ + 569, + 919, + -1 + ], + [ + 570, + 757, + -1 + ], + [ + 570, + 789, + 1 + ], + [ + 570, + 856, + 1 + ], + [ + 570, + 911, + 1 + ], + [ + 571, + 654, + -1 + ], + [ + 571, + 788, + -1 + ], + [ + 571, + 843, + -1 + ], + [ + 571, + 910, + 1 + ], + [ + 572, + 653, + -1 + ], + [ + 572, + 732, + -1 + ], + [ + 572, + 743, + -1 + ], + [ + 572, + 895, + -1 + ], + [ + 572, + 973, + 1 + ], + [ + 573, + 596, + 1 + ], + [ + 573, + 693, + -1 + ], + [ + 574, + 631, + -1 + ], + [ + 574, + 693, + 1 + ], + [ + 574, + 808, + -1 + ], + [ + 575, + 623, + 1 + ], + [ + 575, + 636, + -1 + ], + [ + 575, + 638, + 1 + ], + [ + 576, + 682, + -1 + ], + [ + 576, + 793, + 1 + ], + [ + 576, + 896, + -1 + ], + [ + 577, + 643, + -1 + ], + [ + 577, + 665, + 1 + ], + [ + 579, + 631, + -1 + ], + [ + 579, + 647, + 1 + ], + [ + 579, + 689, + -1 + ], + [ + 579, + 916, + 1 + ], + [ + 580, + 682, + 1 + ], + [ + 580, + 741, + 1 + ], + [ + 580, + 898, + -1 + ], + [ + 581, + 763, + -1 + ], + [ + 581, + 879, + -1 + ], + [ + 581, + 997, + 1 + ], + [ + 582, + 663, + -1 + ], + [ + 582, + 737, + -1 + ], + [ + 582, + 860, + -1 + ], + [ + 582, + 915, + -1 + ], + [ + 582, + 938, + 1 + ], + [ + 583, + 664, + 1 + ], + [ + 583, + 924, + 1 + ], + [ + 584, + 821, + 1 + ], + [ + 585, + 625, + -1 + ], + [ + 585, + 686, + 1 + ], + [ + 585, + 815, + 1 + ], + [ + 585, + 863, + -1 + ], + [ + 585, + 864, + -1 + ], + [ + 585, + 870, + 1 + ], + [ + 586, + 782, + -1 + ], + [ + 586, + 858, + -1 + ], + [ + 586, + 937, + 1 + ], + [ + 587, + 705, + -1 + ], + [ + 587, + 846, + -1 + ], + [ + 587, + 899, + -1 + ], + [ + 587, + 951, + 1 + ], + [ + 588, + 655, + 1 + ], + [ + 588, + 794, + 1 + ], + [ + 591, + 641, + 1 + ], + [ + 591, + 732, + -1 + ], + [ + 591, + 829, + 1 + ], + [ + 591, + 832, + -1 + ], + [ + 591, + 892, + 1 + ], + [ + 591, + 915, + 1 + ], + [ + 592, + 596, + -1 + ], + [ + 592, + 645, + 1 + ], + [ + 593, + 857, + 1 + ], + [ + 593, + 932, + -1 + ], + [ + 593, + 994, + 1 + ], + [ + 594, + 645, + -1 + ], + [ + 594, + 648, + -1 + ], + [ + 594, + 727, + 1 + ], + [ + 594, + 746, + -1 + ], + [ + 594, + 882, + -1 + ], + [ + 595, + 770, + -1 + ], + [ + 595, + 779, + -1 + ], + [ + 595, + 873, + -1 + ], + [ + 596, + 726, + 1 + ], + [ + 596, + 739, + -1 + ], + [ + 596, + 766, + 1 + ], + [ + 596, + 879, + 1 + ], + [ + 596, + 956, + 1 + ], + [ + 596, + 972, + -1 + ], + [ + 597, + 795, + -1 + ], + [ + 597, + 828, + 1 + ], + [ + 597, + 872, + -1 + ], + [ + 597, + 929, + -1 + ], + [ + 597, + 970, + -1 + ], + [ + 597, + 987, + -1 + ], + [ + 598, + 848, + -1 + ], + [ + 599, + 738, + 1 + ], + [ + 599, + 939, + 1 + ], + [ + 599, + 951, + -1 + ], + [ + 601, + 729, + 1 + ], + [ + 601, + 816, + 1 + ], + [ + 601, + 821, + -1 + ], + [ + 601, + 923, + 1 + ], + [ + 602, + 796, + 1 + ], + [ + 602, + 969, + 1 + ], + [ + 603, + 642, + 1 + ], + [ + 603, + 686, + -1 + ], + [ + 603, + 705, + -1 + ], + [ + 603, + 748, + -1 + ], + [ + 604, + 740, + 1 + ], + [ + 604, + 896, + -1 + ], + [ + 605, + 802, + -1 + ], + [ + 605, + 901, + 1 + ], + [ + 605, + 966, + -1 + ], + [ + 606, + 818, + 1 + ], + [ + 606, + 835, + 1 + ], + [ + 606, + 936, + -1 + ], + [ + 607, + 756, + 1 + ], + [ + 607, + 875, + 1 + ], + [ + 607, + 953, + 1 + ], + [ + 608, + 642, + 1 + ], + [ + 608, + 815, + 1 + ], + [ + 609, + 932, + 1 + ], + [ + 610, + 765, + -1 + ], + [ + 610, + 862, + -1 + ], + [ + 610, + 988, + -1 + ], + [ + 611, + 797, + -1 + ], + [ + 612, + 671, + 1 + ], + [ + 612, + 701, + 1 + ], + [ + 612, + 776, + -1 + ], + [ + 613, + 743, + 1 + ], + [ + 613, + 869, + 1 + ], + [ + 613, + 964, + -1 + ], + [ + 614, + 654, + 1 + ], + [ + 614, + 761, + 1 + ], + [ + 614, + 777, + 1 + ], + [ + 614, + 856, + 1 + ], + [ + 614, + 964, + -1 + ], + [ + 614, + 980, + -1 + ], + [ + 615, + 937, + 1 + ], + [ + 615, + 984, + 1 + ], + [ + 616, + 652, + 1 + ], + [ + 616, + 667, + -1 + ], + [ + 616, + 701, + 1 + ], + [ + 616, + 984, + 1 + ], + [ + 617, + 853, + -1 + ], + [ + 617, + 931, + -1 + ], + [ + 617, + 937, + -1 + ], + [ + 617, + 996, + 1 + ], + [ + 617, + 997, + -1 + ], + [ + 619, + 773, + -1 + ], + [ + 619, + 913, + -1 + ], + [ + 620, + 701, + 1 + ], + [ + 621, + 954, + -1 + ], + [ + 622, + 788, + 1 + ], + [ + 622, + 895, + 1 + ], + [ + 623, + 644, + 1 + ], + [ + 623, + 723, + -1 + ], + [ + 623, + 931, + 1 + ], + [ + 624, + 766, + 1 + ], + [ + 624, + 784, + -1 + ], + [ + 624, + 842, + 1 + ], + [ + 624, + 897, + 1 + ], + [ + 624, + 992, + 1 + ], + [ + 625, + 685, + 1 + ], + [ + 625, + 725, + 1 + ], + [ + 625, + 997, + -1 + ], + [ + 626, + 741, + 1 + ], + [ + 628, + 653, + 1 + ], + [ + 628, + 884, + -1 + ], + [ + 630, + 795, + 1 + ], + [ + 631, + 632, + 1 + ], + [ + 631, + 649, + 1 + ], + [ + 631, + 674, + 1 + ], + [ + 631, + 884, + -1 + ], + [ + 632, + 735, + -1 + ], + [ + 632, + 798, + 1 + ], + [ + 632, + 951, + 1 + ], + [ + 633, + 786, + 1 + ], + [ + 633, + 926, + -1 + ], + [ + 634, + 734, + 1 + ], + [ + 634, + 772, + 1 + ], + [ + 634, + 931, + -1 + ], + [ + 635, + 673, + -1 + ], + [ + 637, + 899, + -1 + ], + [ + 637, + 927, + -1 + ], + [ + 638, + 748, + 1 + ], + [ + 638, + 835, + -1 + ], + [ + 638, + 931, + 1 + ], + [ + 638, + 982, + 1 + ], + [ + 639, + 718, + 1 + ], + [ + 639, + 734, + -1 + ], + [ + 639, + 757, + -1 + ], + [ + 640, + 754, + -1 + ], + [ + 640, + 884, + 1 + ], + [ + 641, + 750, + -1 + ], + [ + 641, + 945, + -1 + ], + [ + 642, + 966, + 1 + ], + [ + 643, + 774, + 1 + ], + [ + 644, + 743, + 1 + ], + [ + 644, + 875, + -1 + ], + [ + 644, + 987, + -1 + ], + [ + 645, + 670, + -1 + ], + [ + 645, + 756, + -1 + ], + [ + 645, + 836, + 1 + ], + [ + 645, + 859, + -1 + ], + [ + 645, + 879, + 1 + ], + [ + 645, + 989, + 1 + ], + [ + 646, + 751, + 1 + ], + [ + 646, + 948, + -1 + ], + [ + 647, + 657, + -1 + ], + [ + 647, + 741, + 1 + ], + [ + 647, + 814, + 1 + ], + [ + 647, + 818, + 1 + ], + [ + 647, + 852, + 1 + ], + [ + 647, + 995, + 1 + ], + [ + 648, + 676, + -1 + ], + [ + 649, + 685, + 1 + ], + [ + 649, + 905, + -1 + ], + [ + 649, + 909, + -1 + ], + [ + 649, + 974, + -1 + ], + [ + 649, + 993, + -1 + ], + [ + 651, + 706, + -1 + ], + [ + 652, + 660, + -1 + ], + [ + 652, + 749, + 1 + ], + [ + 652, + 956, + 1 + ], + [ + 653, + 732, + -1 + ], + [ + 653, + 817, + 1 + ], + [ + 653, + 892, + 1 + ], + [ + 653, + 897, + -1 + ], + [ + 653, + 949, + -1 + ], + [ + 654, + 887, + -1 + ], + [ + 654, + 891, + 1 + ], + [ + 656, + 797, + 1 + ], + [ + 656, + 814, + -1 + ], + [ + 657, + 766, + 1 + ], + [ + 657, + 913, + 1 + ], + [ + 657, + 998, + -1 + ], + [ + 659, + 672, + 1 + ], + [ + 659, + 723, + 1 + ], + [ + 660, + 791, + 1 + ], + [ + 660, + 865, + -1 + ], + [ + 661, + 779, + -1 + ], + [ + 661, + 795, + -1 + ], + [ + 662, + 938, + 1 + ], + [ + 663, + 713, + 1 + ], + [ + 663, + 870, + 1 + ], + [ + 663, + 980, + -1 + ], + [ + 664, + 692, + 1 + ], + [ + 664, + 781, + -1 + ], + [ + 664, + 797, + -1 + ], + [ + 664, + 896, + -1 + ], + [ + 664, + 938, + 1 + ], + [ + 665, + 783, + -1 + ], + [ + 666, + 797, + 1 + ], + [ + 666, + 872, + 1 + ], + [ + 667, + 863, + -1 + ], + [ + 669, + 997, + 1 + ], + [ + 670, + 710, + 1 + ], + [ + 670, + 778, + 1 + ], + [ + 670, + 842, + -1 + ], + [ + 670, + 999, + -1 + ], + [ + 673, + 850, + -1 + ], + [ + 674, + 783, + 1 + ], + [ + 674, + 952, + -1 + ], + [ + 674, + 969, + -1 + ], + [ + 675, + 917, + -1 + ], + [ + 675, + 988, + 1 + ], + [ + 676, + 839, + 1 + ], + [ + 676, + 995, + -1 + ], + [ + 677, + 706, + -1 + ], + [ + 678, + 756, + -1 + ], + [ + 678, + 917, + -1 + ], + [ + 679, + 730, + 1 + ], + [ + 680, + 850, + 1 + ], + [ + 680, + 991, + 1 + ], + [ + 680, + 995, + 1 + ], + [ + 681, + 848, + -1 + ], + [ + 682, + 821, + 1 + ], + [ + 682, + 901, + -1 + ], + [ + 683, + 802, + -1 + ], + [ + 683, + 846, + -1 + ], + [ + 683, + 930, + -1 + ], + [ + 685, + 694, + -1 + ], + [ + 685, + 944, + -1 + ], + [ + 686, + 726, + 1 + ], + [ + 686, + 821, + -1 + ], + [ + 686, + 832, + -1 + ], + [ + 686, + 971, + 1 + ], + [ + 687, + 910, + -1 + ], + [ + 687, + 968, + 1 + ], + [ + 689, + 906, + -1 + ], + [ + 689, + 939, + -1 + ], + [ + 690, + 715, + -1 + ], + [ + 690, + 895, + -1 + ], + [ + 690, + 953, + 1 + ], + [ + 691, + 732, + 1 + ], + [ + 691, + 846, + 1 + ], + [ + 691, + 953, + -1 + ], + [ + 693, + 778, + -1 + ], + [ + 693, + 917, + 1 + ], + [ + 694, + 784, + 1 + ], + [ + 694, + 878, + -1 + ], + [ + 695, + 700, + 1 + ], + [ + 695, + 929, + 1 + ], + [ + 696, + 781, + -1 + ], + [ + 696, + 792, + 1 + ], + [ + 696, + 839, + 1 + ], + [ + 696, + 871, + -1 + ], + [ + 696, + 907, + -1 + ], + [ + 696, + 981, + -1 + ], + [ + 697, + 740, + -1 + ], + [ + 697, + 787, + -1 + ], + [ + 697, + 900, + -1 + ], + [ + 697, + 921, + -1 + ], + [ + 698, + 712, + 1 + ], + [ + 699, + 827, + 1 + ], + [ + 699, + 954, + 1 + ], + [ + 700, + 938, + -1 + ], + [ + 701, + 830, + 1 + ], + [ + 701, + 949, + 1 + ], + [ + 702, + 858, + -1 + ], + [ + 703, + 872, + -1 + ], + [ + 703, + 953, + -1 + ], + [ + 704, + 981, + -1 + ], + [ + 706, + 710, + -1 + ], + [ + 706, + 936, + 1 + ], + [ + 707, + 716, + 1 + ], + [ + 707, + 725, + -1 + ], + [ + 708, + 714, + 1 + ], + [ + 708, + 874, + 1 + ], + [ + 708, + 939, + -1 + ], + [ + 709, + 829, + -1 + ], + [ + 710, + 938, + 1 + ], + [ + 710, + 962, + 1 + ], + [ + 711, + 787, + 1 + ], + [ + 711, + 882, + -1 + ], + [ + 711, + 984, + -1 + ], + [ + 711, + 999, + 1 + ], + [ + 713, + 804, + -1 + ], + [ + 714, + 842, + 1 + ], + [ + 716, + 772, + -1 + ], + [ + 716, + 773, + 1 + ], + [ + 716, + 869, + -1 + ], + [ + 717, + 791, + 1 + ], + [ + 717, + 893, + -1 + ], + [ + 717, + 994, + -1 + ], + [ + 718, + 807, + -1 + ], + [ + 718, + 935, + 1 + ], + [ + 719, + 802, + 1 + ], + [ + 719, + 874, + 1 + ], + [ + 719, + 967, + -1 + ], + [ + 720, + 828, + 1 + ], + [ + 720, + 858, + -1 + ], + [ + 720, + 999, + 1 + ], + [ + 722, + 905, + -1 + ], + [ + 723, + 729, + 1 + ], + [ + 724, + 780, + -1 + ], + [ + 724, + 893, + -1 + ], + [ + 726, + 892, + -1 + ], + [ + 728, + 801, + -1 + ], + [ + 728, + 914, + 1 + ], + [ + 729, + 998, + 1 + ], + [ + 730, + 805, + -1 + ], + [ + 730, + 875, + -1 + ], + [ + 731, + 871, + -1 + ], + [ + 733, + 987, + -1 + ], + [ + 734, + 904, + -1 + ], + [ + 735, + 788, + -1 + ], + [ + 736, + 805, + -1 + ], + [ + 736, + 876, + 1 + ], + [ + 737, + 870, + 1 + ], + [ + 738, + 945, + -1 + ], + [ + 739, + 782, + 1 + ], + [ + 739, + 898, + 1 + ], + [ + 740, + 988, + -1 + ], + [ + 741, + 926, + -1 + ], + [ + 741, + 984, + 1 + ], + [ + 742, + 990, + 1 + ], + [ + 743, + 970, + 1 + ], + [ + 744, + 766, + 1 + ], + [ + 744, + 934, + 1 + ], + [ + 746, + 947, + -1 + ], + [ + 747, + 801, + 1 + ], + [ + 748, + 750, + 1 + ], + [ + 748, + 972, + 1 + ], + [ + 749, + 946, + -1 + ], + [ + 749, + 971, + -1 + ], + [ + 750, + 868, + 1 + ], + [ + 750, + 916, + -1 + ], + [ + 750, + 947, + 1 + ], + [ + 751, + 805, + 1 + ], + [ + 751, + 910, + 1 + ], + [ + 752, + 760, + 1 + ], + [ + 752, + 820, + 1 + ], + [ + 752, + 890, + 1 + ], + [ + 754, + 774, + 1 + ], + [ + 754, + 957, + 1 + ], + [ + 755, + 850, + -1 + ], + [ + 755, + 952, + 1 + ], + [ + 755, + 960, + -1 + ], + [ + 756, + 802, + -1 + ], + [ + 758, + 784, + 1 + ], + [ + 758, + 867, + 1 + ], + [ + 760, + 834, + -1 + ], + [ + 760, + 849, + -1 + ], + [ + 760, + 864, + -1 + ], + [ + 761, + 956, + 1 + ], + [ + 765, + 870, + 1 + ], + [ + 765, + 937, + -1 + ], + [ + 766, + 861, + 1 + ], + [ + 766, + 947, + 1 + ], + [ + 767, + 851, + 1 + ], + [ + 767, + 900, + -1 + ], + [ + 767, + 915, + 1 + ], + [ + 767, + 948, + -1 + ], + [ + 767, + 967, + 1 + ], + [ + 768, + 920, + 1 + ], + [ + 769, + 945, + 1 + ], + [ + 770, + 843, + 1 + ], + [ + 770, + 911, + -1 + ], + [ + 771, + 958, + 1 + ], + [ + 772, + 896, + -1 + ], + [ + 773, + 797, + -1 + ], + [ + 774, + 932, + -1 + ], + [ + 776, + 905, + 1 + ], + [ + 776, + 965, + 1 + ], + [ + 777, + 851, + 1 + ], + [ + 779, + 825, + -1 + ], + [ + 779, + 920, + -1 + ], + [ + 779, + 940, + -1 + ], + [ + 780, + 820, + 1 + ], + [ + 780, + 989, + 1 + ], + [ + 781, + 962, + 1 + ], + [ + 783, + 866, + 1 + ], + [ + 783, + 914, + 1 + ], + [ + 785, + 792, + -1 + ], + [ + 785, + 819, + -1 + ], + [ + 785, + 881, + 1 + ], + [ + 786, + 817, + 1 + ], + [ + 788, + 805, + -1 + ], + [ + 789, + 841, + -1 + ], + [ + 789, + 852, + -1 + ], + [ + 790, + 920, + 1 + ], + [ + 794, + 976, + 1 + ], + [ + 794, + 995, + 1 + ], + [ + 795, + 893, + 1 + ], + [ + 795, + 969, + -1 + ], + [ + 796, + 834, + -1 + ], + [ + 798, + 803, + 1 + ], + [ + 799, + 836, + -1 + ], + [ + 799, + 904, + 1 + ], + [ + 800, + 839, + 1 + ], + [ + 801, + 845, + -1 + ], + [ + 801, + 894, + -1 + ], + [ + 804, + 825, + 1 + ], + [ + 804, + 839, + 1 + ], + [ + 805, + 944, + 1 + ], + [ + 805, + 994, + 1 + ], + [ + 806, + 822, + 1 + ], + [ + 807, + 837, + -1 + ], + [ + 807, + 932, + 1 + ], + [ + 807, + 957, + -1 + ], + [ + 808, + 974, + -1 + ], + [ + 809, + 920, + 1 + ], + [ + 809, + 974, + -1 + ], + [ + 810, + 849, + -1 + ], + [ + 812, + 845, + 1 + ], + [ + 812, + 890, + 1 + ], + [ + 814, + 815, + 1 + ], + [ + 814, + 869, + 1 + ], + [ + 814, + 983, + -1 + ], + [ + 816, + 854, + -1 + ], + [ + 817, + 944, + 1 + ], + [ + 818, + 834, + 1 + ], + [ + 819, + 948, + -1 + ], + [ + 819, + 978, + -1 + ], + [ + 822, + 936, + 1 + ], + [ + 822, + 952, + 1 + ], + [ + 823, + 956, + 1 + ], + [ + 824, + 987, + 1 + ], + [ + 824, + 992, + -1 + ], + [ + 829, + 993, + 1 + ], + [ + 831, + 847, + 1 + ], + [ + 831, + 921, + -1 + ], + [ + 834, + 842, + 1 + ], + [ + 834, + 867, + 1 + ], + [ + 836, + 932, + -1 + ], + [ + 837, + 998, + 1 + ], + [ + 838, + 896, + 1 + ], + [ + 839, + 926, + 1 + ], + [ + 841, + 872, + -1 + ], + [ + 841, + 945, + -1 + ], + [ + 842, + 847, + -1 + ], + [ + 843, + 952, + 1 + ], + [ + 845, + 856, + -1 + ], + [ + 845, + 895, + -1 + ], + [ + 845, + 998, + 1 + ], + [ + 846, + 854, + -1 + ], + [ + 846, + 932, + -1 + ], + [ + 846, + 961, + -1 + ], + [ + 847, + 893, + -1 + ], + [ + 847, + 975, + -1 + ], + [ + 848, + 983, + -1 + ], + [ + 849, + 873, + -1 + ], + [ + 849, + 919, + 1 + ], + [ + 850, + 918, + -1 + ], + [ + 851, + 941, + -1 + ], + [ + 852, + 887, + -1 + ], + [ + 852, + 958, + 1 + ], + [ + 853, + 905, + 1 + ], + [ + 853, + 948, + -1 + ], + [ + 854, + 930, + -1 + ], + [ + 855, + 998, + -1 + ], + [ + 856, + 982, + -1 + ], + [ + 857, + 946, + -1 + ], + [ + 857, + 968, + -1 + ], + [ + 859, + 979, + 1 + ], + [ + 861, + 872, + -1 + ], + [ + 861, + 992, + 1 + ], + [ + 867, + 954, + 1 + ], + [ + 867, + 971, + -1 + ], + [ + 869, + 934, + -1 + ], + [ + 871, + 911, + 1 + ], + [ + 871, + 963, + -1 + ], + [ + 872, + 991, + 1 + ], + [ + 874, + 895, + 1 + ], + [ + 875, + 972, + 1 + ], + [ + 877, + 994, + -1 + ], + [ + 879, + 979, + 1 + ], + [ + 881, + 920, + 1 + ], + [ + 885, + 900, + -1 + ], + [ + 885, + 935, + 1 + ], + [ + 885, + 949, + 1 + ], + [ + 886, + 911, + 1 + ], + [ + 891, + 933, + 1 + ], + [ + 893, + 993, + -1 + ], + [ + 894, + 988, + -1 + ], + [ + 898, + 921, + -1 + ], + [ + 903, + 931, + 1 + ], + [ + 904, + 926, + -1 + ], + [ + 909, + 919, + -1 + ], + [ + 909, + 948, + -1 + ], + [ + 915, + 961, + -1 + ], + [ + 916, + 924, + 1 + ], + [ + 916, + 928, + -1 + ], + [ + 916, + 943, + -1 + ], + [ + 917, + 928, + -1 + ], + [ + 919, + 970, + 1 + ], + [ + 922, + 959, + -1 + ], + [ + 929, + 987, + 1 + ], + [ + 931, + 944, + 1 + ], + [ + 932, + 941, + -1 + ], + [ + 932, + 957, + 1 + ], + [ + 934, + 958, + -1 + ], + [ + 938, + 991, + 1 + ], + [ + 943, + 966, + -1 + ], + [ + 944, + 954, + -1 + ], + [ + 954, + 966, + 1 + ], + [ + 954, + 973, + 1 + ], + [ + 956, + 995, + -1 + ], + [ + 964, + 966, + 1 + ], + [ + 966, + 974, + 1 + ], + [ + 975, + 996, + 1 + ], + [ + 979, + 997, + -1 + ], + [ + 980, + 985, + 1 + ], + [ + 987, + 994, + 1 + ] + ], + "field": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "cost": 0 + }, + "field_bound": 16, + "start": "-+-+-+-+-+-+-+-+-+-+-+-+-+-++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+--+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+--+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+--+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+--+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+--+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+--+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+--+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+--+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+--+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+--+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+--+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+--+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+--+-+-+-+-+-+-+-+-+-+-+-+", + "bound": 300, + "expect": { + "state": "------++-+0+0++++-++++--+-+0+-+-++-++---+---+---++++0++-+-++++-------0+-+-+---+-+-0-+-+-+-+-+---++-+---+--++-+-+--+---------0-+--+-++---+-+-+-++++-++-0---+0--0-+--0-0-+++++++--+-+++-+-+0-+++--++++-++++--+++-+---0+--+-+----+----++-++-+---++----++++++0--+++++---0-+-+---+0++++++++-0-0-+---++-++---+-+---+-0+-0+0-------+0+-+++-0--+-+-+--++-0--+0+++++-+++-0+-++-+-0+++--0+--++-+++----+-++++--0+0+-0+-------++-----++-++--++++---++-+-+-0-++-+++++---++-+-++-0--+--+++--+0-+-+----0-+-00++---+-++-+++--0--0+++-0+-0---0--0--+-+--+-++-+++-----+-+-++--++-++++-+---+-+0+-++--+---+-+-0+-+++--++-+++++--++-+-+++--+-------+0+++--0+0-++---+--0-++----++++--+-+-++-----++---++++-++++-0++++-++--0+---++-+0-+-++-++++-+-+0++++-+++--+++--0-+0-+-0--+++-+-+-+--+0--+++-++-+---+0-+0+-++---00+-++--+-----++-+-++++++-+++++++++--++++---++++---+-++-++++-0++--+-0------+0---++-+-+-+-+++----+-0++---+---++--+---+++----++--+-+-+--+0++-+-++++0+---0--+--++++++--+-++0-+----+-+-+-+++++--+-+-0++-++--++++++++++0-0+-+-+--+-0++++-+---++---", + "sweeps": 21, + "term": "two-cycle", + "accumulates": 125538, + "state_hash": "e06e33190adb03c1" + } + }, + { + "name": "a-two-cycle-past-a-workgroup", + "landscape": { + "n": 70, + "couplings": [ + [ + 0, + 1, + -1 + ], + [ + 0, + 69, + -1 + ], + [ + 1, + 2, + -1 + ], + [ + 2, + 3, + -1 + ], + [ + 3, + 4, + -1 + ], + [ + 4, + 5, + -1 + ], + [ + 5, + 6, + -1 + ], + [ + 6, + 7, + -1 + ], + [ + 7, + 8, + -1 + ], + [ + 8, + 9, + -1 + ], + [ + 9, + 10, + -1 + ], + [ + 10, + 11, + -1 + ], + [ + 11, + 12, + -1 + ], + [ + 12, + 13, + -1 + ], + [ + 13, + 14, + -1 + ], + [ + 14, + 15, + -1 + ], + [ + 15, + 16, + -1 + ], + [ + 16, + 17, + -1 + ], + [ + 17, + 18, + -1 + ], + [ + 18, + 19, + -1 + ], + [ + 19, + 20, + -1 + ], + [ + 20, + 21, + -1 + ], + [ + 21, + 22, + -1 + ], + [ + 22, + 23, + -1 + ], + [ + 23, + 24, + -1 + ], + [ + 24, + 25, + -1 + ], + [ + 25, + 26, + -1 + ], + [ + 26, + 27, + -1 + ], + [ + 27, + 28, + -1 + ], + [ + 28, + 29, + -1 + ], + [ + 29, + 30, + -1 + ], + [ + 30, + 31, + -1 + ], + [ + 31, + 32, + -1 + ], + [ + 32, + 33, + -1 + ], + [ + 33, + 34, + -1 + ], + [ + 34, + 35, + -1 + ], + [ + 35, + 36, + -1 + ], + [ + 36, + 37, + -1 + ], + [ + 37, + 38, + -1 + ], + [ + 38, + 39, + -1 + ], + [ + 39, + 40, + -1 + ], + [ + 40, + 41, + -1 + ], + [ + 41, + 42, + -1 + ], + [ + 42, + 43, + -1 + ], + [ + 43, + 44, + -1 + ], + [ + 44, + 45, + -1 + ], + [ + 45, + 46, + -1 + ], + [ + 46, + 47, + -1 + ], + [ + 47, + 48, + -1 + ], + [ + 48, + 49, + -1 + ], + [ + 49, + 50, + -1 + ], + [ + 50, + 51, + -1 + ], + [ + 51, + 52, + -1 + ], + [ + 52, + 53, + -1 + ], + [ + 53, + 54, + -1 + ], + [ + 54, + 55, + -1 + ], + [ + 55, + 56, + -1 + ], + [ + 56, + 57, + -1 + ], + [ + 57, + 58, + -1 + ], + [ + 58, + 59, + -1 + ], + [ + 59, + 60, + -1 + ], + [ + 60, + 61, + -1 + ], + [ + 61, + 62, + -1 + ], + [ + 62, + 63, + -1 + ], + [ + 63, + 64, + -1 + ], + [ + 64, + 65, + -1 + ], + [ + 65, + 66, + -1 + ], + [ + 66, + 67, + -1 + ], + [ + 67, + 68, + -1 + ], + [ + 68, + 69, + -1 + ] + ], + "field": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "cost": 0 + }, + "field_bound": 2, + "start": "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++", + "bound": 200, + "expect": { + "state": "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++", + "sweeps": 2, + "term": "two-cycle", + "accumulates": 280, + "state_hash": "0c2ca64dd43f22a5" + } + }, + { + "name": "fields-past-16-bits", + "landscape": { + "n": 4, + "couplings": [], + "field": [ + 65536, + 40000, + -70000, + 1 + ], + "cost": 5000 + }, + "field_bound": 70000, + "start": "0000", + "bound": 20, + "expect": { + "state": "++-0", + "sweeps": 2, + "term": "fixed-point", + "accumulates": 0, + "state_hash": "9e6dc390540988ba" + } + }, + { + "name": "fields-past-32-bits", + "landscape": { + "n": 4, + "couplings": [], + "field": [ + 8589934592, + 6442450944, + -8589934597, + 1 + ], + "cost": 7 + }, + "field_bound": 8589934597, + "start": "0000", + "bound": 20, + "expect": { + "state": "++-0", + "sweeps": 2, + "term": "fixed-point", + "accumulates": 0, + "state_hash": "9e6dc390540988ba" + } + }, + { + "name": "field-bound-at-the-line", + "landscape": { + "n": 3, + "couplings": [ + [ + 0, + 1, + 1 + ], + [ + 0, + 2, + 1 + ] + ], + "field": [ + 2147483645, + 1, + 1 + ], + "cost": 0 + }, + "field_bound": 2147483647, + "start": "000", + "bound": 20, + "expect": { + "state": "+++", + "sweeps": 2, + "term": "fixed-point", + "accumulates": 8, + "state_hash": "eaa6d61875e4f6c5" + } + }, + { + "name": "field-bound-at-the-line-below-zero", + "landscape": { + "n": 3, + "couplings": [ + [ + 0, + 1, + 1 + ], + [ + 0, + 2, + 1 + ] + ], + "field": [ + -2147483645, + -1, + -1 + ], + "cost": 0 + }, + "field_bound": 2147483647, + "start": "000", + "bound": 20, + "expect": { + "state": "---", + "sweeps": 2, + "term": "fixed-point", + "accumulates": 8, + "state_hash": "d94d12186c0f2fb7" + } + }, + { + "name": "field-bound-over-the-line-by-its-couplings", + "landscape": { + "n": 3, + "couplings": [ + [ + 0, + 1, + 1 + ], + [ + 0, + 2, + 1 + ] + ], + "field": [ + 2147483647, + 1, + 1 + ], + "cost": 0 + }, + "field_bound": 2147483649, + "start": "000", + "bound": 20, + "expect": { + "state": "+++", + "sweeps": 2, + "term": "fixed-point", + "accumulates": 8, + "state_hash": "eaa6d61875e4f6c5" + } + }, + { + "name": "field-bound-over-the-line-below-zero", + "landscape": { + "n": 3, + "couplings": [ + [ + 0, + 1, + 1 + ], + [ + 0, + 2, + 1 + ] + ], + "field": [ + -2147483647, + -1, + -1 + ], + "cost": 0 + }, + "field_bound": 2147483649, + "start": "000", + "bound": 20, + "expect": { + "state": "---", + "sweeps": 2, + "term": "fixed-point", + "accumulates": 8, + "state_hash": "d94d12186c0f2fb7" + } + }, + { + "name": "a-cost-past-the-lanes", + "landscape": { + "n": 3, + "couplings": [ + [ + 0, + 1, + 1 + ], + [ + 1, + 2, + -1 + ] + ], + "field": [ + 3, + 0, + 0 + ], + "cost": 8589934592 + }, + "field_bound": 4, + "start": "000", + "bound": 20, + "expect": { + "state": "000", + "sweeps": 1, + "term": "fixed-point", + "accumulates": 4, + "state_hash": "d0a6fc18672a1282" + } + }, + { + "name": "a-cost-just-past-the-lanes", + "landscape": { + "n": 3, + "couplings": [ + [ + 0, + 1, + 1 + ], + [ + 1, + 2, + -1 + ] + ], + "field": [ + 2147483646, + 0, + 0 + ], + "cost": 2147483648 + }, + "field_bound": 2147483647, + "start": "000", + "bound": 20, + "expect": { + "state": "000", + "sweeps": 1, + "term": "fixed-point", + "accumulates": 4, + "state_hash": "d0a6fc18672a1282" + } } ], "exp": [ @@ -12086,6 +33592,123 @@ "cut_b": 2177351939456059 } ], + "draws": [ + { + "seed": "0", + "j": 1, + "out": "16294208416658607535" + }, + { + "seed": "0", + "j": 2, + "out": "7960286522194355700" + }, + { + "seed": "0", + "j": 3, + "out": "487617019471545679" + }, + { + "seed": "0", + "j": 4, + "out": "17909611376780542444" + }, + { + "seed": "1", + "j": 1, + "out": "10451216379200822465" + }, + { + "seed": "1", + "j": 2, + "out": "13757245211066428519" + }, + { + "seed": "1", + "j": 3, + "out": "17911839290282890590" + }, + { + "seed": "1", + "j": 4, + "out": "8196980753821780235" + }, + { + "seed": "2", + "j": 1, + "out": "10905525725756348110" + }, + { + "seed": "2", + "j": 2, + "out": "13819372491320860226" + }, + { + "seed": "2", + "j": 3, + "out": "10987583248141275951" + }, + { + "seed": "2", + "j": 4, + "out": "14119491246550939236" + }, + { + "seed": "18446744073709551615", + "j": 1, + "out": "16490336266968443936" + }, + { + "seed": "18446744073709551615", + "j": 2, + "out": "16834447057089888969" + }, + { + "seed": "18446744073709551615", + "j": 3, + "out": "4048727598324417001" + }, + { + "seed": "18446744073709551615", + "j": 4, + "out": "7862637804313477842" + }, + { + "seed": "11400714819323198485", + "j": 1, + "out": "7960286522194355700" + }, + { + "seed": "11400714819323198485", + "j": 2, + "out": "487617019471545679" + }, + { + "seed": "11400714819323198485", + "j": 3, + "out": "17909611376780542444" + }, + { + "seed": "11400714819323198485", + "j": 4, + "out": "1961750202426094747" + }, + { + "seed": "12368052865198062608", + "j": 1, + "out": "1063768530571448245" + }, + { + "seed": "3930409837541395121", + "j": 1, + "out": "6148911451886787092" + }, + { + "seed": "14472243134433247250", + "j": 1, + "out": "12297827513363209411" + } + ], "chains": [ { "name": "sparse-with-a-cost/t0=2/sweeps=30", @@ -12150,10 +33773,8 @@ ], "cost": 1 }, + "field_bound": 3, "seed": "1", - "t0": 2.0, - "t0_bits": "4000000000000000", - "sweeps": 30, "chains": [ { "chain": "10451216379200822465", @@ -12166,7 +33787,10 @@ "state_hash": "72827d80810737a7" } } - ] + ], + "t0": 2.0, + "t0_bits": "4000000000000000", + "sweeps": 30 }, { "name": "sparse-with-a-cost/t0=0.5/sweeps=12", @@ -12231,10 +33855,8 @@ ], "cost": 1 }, + "field_bound": 3, "seed": "2", - "t0": 0.5, - "t0_bits": "3fe0000000000000", - "sweeps": 12, "chains": [ { "chain": "10905525725756348110", @@ -12247,7 +33869,10 @@ "state_hash": "87cde53c2909a4ff" } } - ] + ], + "t0": 0.5, + "t0_bits": "3fe0000000000000", + "sweeps": 12 }, { "name": "sparse-with-a-cost/t0=0/sweeps=7", @@ -12312,10 +33937,8 @@ ], "cost": 1 }, + "field_bound": 3, "seed": "3", - "t0": 0.0, - "t0_bits": "0000000000000000", - "sweeps": 7, "chains": [ { "chain": "2092789425003139053", @@ -12328,7 +33951,10 @@ "state_hash": "e7e395a2ad0bc74d" } } - ] + ], + "t0": 0.0, + "t0_bits": "0000000000000000", + "sweeps": 7 }, { "name": "sparse-with-a-cost/t0=1.8708286933869707/sweeps=41", @@ -12393,10 +34019,8 @@ ], "cost": 1 }, + "field_bound": 3, "seed": "4", - "t0": 1.8708286933869707, - "t0_bits": "3ffdeeea11683f49", - "sweeps": 41, "chains": [ { "chain": "7958955049054603978", @@ -12486,7 +34110,10 @@ "state_hash": "87cde53c2909a4ff" } } - ] + ], + "t0": 1.8708286933869707, + "t0_bits": "3ffdeeea11683f49", + "sweeps": 41 }, { "name": "sparse-with-a-cost/t0=13.7/sweeps=9", @@ -12551,10 +34178,8 @@ ], "cost": 1 }, + "field_bound": 3, "seed": "5", - "t0": 13.7, - "t0_bits": "402b666666666666", - "sweeps": 9, "chains": [ { "chain": "7134611160154358618", @@ -12589,7 +34214,10 @@ "state_hash": "127d2788d73cf0d2" } } - ] + ], + "t0": 13.7, + "t0_bits": "402b666666666666", + "sweeps": 9 }, { "name": "dense/t0=2/sweeps=30", @@ -13217,10 +34845,8 @@ ], "cost": 0 }, + "field_bound": 15, "seed": "1", - "t0": 2.0, - "t0_bits": "4000000000000000", - "sweeps": 30, "chains": [ { "chain": "10451216379200822465", @@ -13233,7 +34859,10 @@ "state_hash": "a12d60a06b2a6da5" } } - ] + ], + "t0": 2.0, + "t0_bits": "4000000000000000", + "sweeps": 30 }, { "name": "dense/t0=0.5/sweeps=12", @@ -13861,10 +35490,8 @@ ], "cost": 0 }, + "field_bound": 15, "seed": "2", - "t0": 0.5, - "t0_bits": "3fe0000000000000", - "sweeps": 12, "chains": [ { "chain": "10905525725756348110", @@ -13877,7 +35504,10 @@ "state_hash": "a12d60a06b2a6da5" } } - ] + ], + "t0": 0.5, + "t0_bits": "3fe0000000000000", + "sweeps": 12 }, { "name": "dense/t0=0/sweeps=7", @@ -14505,10 +36135,8 @@ ], "cost": 0 }, + "field_bound": 15, "seed": "3", - "t0": 0.0, - "t0_bits": "0000000000000000", - "sweeps": 7, "chains": [ { "chain": "2092789425003139053", @@ -14521,7 +36149,10 @@ "state_hash": "a700225ece6fb2a5" } } - ] + ], + "t0": 0.0, + "t0_bits": "0000000000000000", + "sweeps": 7 }, { "name": "dense/t0=1.8708286933869707/sweeps=41", @@ -15149,10 +36780,8 @@ ], "cost": 0 }, + "field_bound": 15, "seed": "4", - "t0": 1.8708286933869707, - "t0_bits": "3ffdeeea11683f49", - "sweeps": 41, "chains": [ { "chain": "7958955049054603978", @@ -15242,7 +36871,10 @@ "state_hash": "a700225ece6fb2a5" } } - ] + ], + "t0": 1.8708286933869707, + "t0_bits": "3ffdeeea11683f49", + "sweeps": 41 }, { "name": "dense/t0=13.7/sweeps=9", @@ -15870,10 +37502,8 @@ ], "cost": 0 }, + "field_bound": 15, "seed": "5", - "t0": 13.7, - "t0_bits": "402b666666666666", - "sweeps": 9, "chains": [ { "chain": "7134611160154358618", @@ -15908,7 +37538,10 @@ "state_hash": "a12d60a06b2a6da5" } } - ] + ], + "t0": 13.7, + "t0_bits": "402b666666666666", + "sweeps": 9 }, { "name": "wide-fields/t0=2/sweeps=30", @@ -15945,10 +37578,8 @@ ], "cost": 8 }, + "field_bound": 41, "seed": "1", - "t0": 2.0, - "t0_bits": "4000000000000000", - "sweeps": 30, "chains": [ { "chain": "10451216379200822465", @@ -15961,7 +37592,10 @@ "state_hash": "5e22ce25c997e147" } } - ] + ], + "t0": 2.0, + "t0_bits": "4000000000000000", + "sweeps": 30 }, { "name": "wide-fields/t0=0.5/sweeps=12", @@ -15998,10 +37632,8 @@ ], "cost": 8 }, + "field_bound": 41, "seed": "2", - "t0": 0.5, - "t0_bits": "3fe0000000000000", - "sweeps": 12, "chains": [ { "chain": "10905525725756348110", @@ -16014,7 +37646,10 @@ "state_hash": "5e22ce25c997e147" } } - ] + ], + "t0": 0.5, + "t0_bits": "3fe0000000000000", + "sweeps": 12 }, { "name": "wide-fields/t0=0/sweeps=7", @@ -16051,10 +37686,8 @@ ], "cost": 8 }, + "field_bound": 41, "seed": "3", - "t0": 0.0, - "t0_bits": "0000000000000000", - "sweeps": 7, "chains": [ { "chain": "2092789425003139053", @@ -16067,7 +37700,10 @@ "state_hash": "5e22ce25c997e147" } } - ] + ], + "t0": 0.0, + "t0_bits": "0000000000000000", + "sweeps": 7 }, { "name": "wide-fields/t0=1.8708286933869707/sweeps=41", @@ -16104,10 +37740,8 @@ ], "cost": 8 }, + "field_bound": 41, "seed": "4", - "t0": 1.8708286933869707, - "t0_bits": "3ffdeeea11683f49", - "sweeps": 41, "chains": [ { "chain": "7958955049054603978", @@ -16197,7 +37831,10 @@ "state_hash": "5e22ce25c997e147" } } - ] + ], + "t0": 1.8708286933869707, + "t0_bits": "3ffdeeea11683f49", + "sweeps": 41 }, { "name": "wide-fields/t0=13.7/sweeps=9", @@ -16234,10 +37871,8 @@ ], "cost": 8 }, + "field_bound": 41, "seed": "5", - "t0": 13.7, - "t0_bits": "402b666666666666", - "sweeps": 9, "chains": [ { "chain": "7134611160154358618", @@ -16272,6 +37907,16690 @@ "state_hash": "5e22cf25c997e2fa" } } + ], + "t0": 13.7, + "t0_bits": "402b666666666666", + "sweeps": 9 + }, + { + "name": "a-draw/the-carry-in-a-product", + "landscape": { + "n": 1, + "couplings": [], + "field": [ + 0 + ], + "cost": 0 + }, + "field_bound": 0, + "seed": "1982", + "chains": [ + { + "chain": "12368052865198062608", + "start": "+", + "expect": { + "state": "-", + "accumulates": 0, + "draws": 1, + "term": "bound", + "state_hash": "af63bd4c8601b7df" + } + } + ], + "held": [ + { + "temperature": 1.0, + "temperature_bits": "3ff0000000000000", + "sweeps": 1 + } + ] + }, + { + "name": "a-draw/under-the-first-cut-by-its-low-word", + "landscape": { + "n": 1, + "couplings": [], + "field": [ + 0 + ], + "cost": 0 + }, + "field_bound": 0, + "seed": "1174725", + "chains": [ + { + "chain": "3930409837541395121", + "start": "-", + "expect": { + "state": "-", + "accumulates": 0, + "draws": 1, + "term": "bound", + "state_hash": "af63bd4c8601b7df" + } + } + ], + "held": [ + { + "temperature": 1.0, + "temperature_bits": "3ff0000000000000", + "sweeps": 1 + } + ] + }, + { + "name": "a-draw/under-the-second-cut-by-its-low-word", + "landscape": { + "n": 1, + "couplings": [], + "field": [ + 0 + ], + "cost": 0 + }, + "field_bound": 0, + "seed": "6472161", + "chains": [ + { + "chain": "14472243134433247250", + "start": "+", + "expect": { + "state": "0", + "accumulates": 0, + "draws": 1, + "term": "fixed-point", + "state_hash": "af63bc4c8601b62c" + } + } + ], + "held": [ + { + "temperature": 1.0, + "temperature_bits": "3ff0000000000000", + "sweeps": 1 + } + ] + }, + { + "name": "one-sweep-of-16384-draws", + "landscape": { + "n": 128, + "couplings": [], + "field": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "cost": 0 + }, + "field_bound": 0, + "seed": "6", + "chains": [ + { + "chain": "13647215125184110592", + "start": "000+++-++-0--+0-+++-0-+----0-0+0-0++-+----+0-00+0-++0-+-0-0+0++0-000-0+0--+-+-00+-+-0+-00-00-0-0+0+0-+-0--+-0-+0-0+0--++00-0000+", + "expect": { + "state": "++++--0000-+--+00-0000+0-+++++-0+-0-0+-0-0000-00+0-++-0000+-0-+0++-00+0+0+++0-00-0--+0+-+0+-+++-00----00+0+-0-+0---00+0-+--+0+-0", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "b14b1d0887d13a2d" + } + }, + { + "chain": "8233034982601383833", + "start": "000--+--+-+-+0+---0++0+--0-0+--+00+-+-+-00+-+0-0--+-+++0++00+-0+++--0--0--+0-0++------+-0+--+--0+++000-000-0-++0++0-+--+0++-0+--", + "expect": { + "state": "-0000+0---0---+-0+++0-+-0+0--+0-+0-+00+-0+++-+-++0-+-00-+0-+-0--00+-0--0++0--+-++++++0-000+0-++0+00-+-+-0-+---0+-+0-00--00---+-+", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "0214c1bb042bdf6f" + } + }, + { + "chain": "1039343067777871686", + "start": "+0+++0-0-+++--+-00+000+0--000+++--+00-+00+-000-0000-0-00+-0-++0+-+-000+00000+-+-+000+-0---0-0-+0+0-00-++0++---+0--+-000--0-++0++", + "expect": { + "state": "+00+++-0-0-+--0+++0++++++0-00-0-0+-0++-0--+-++++0-+0-0+-0-0-0--++0-00-+0-++0+++---+000+00000-0+0+00+--+-++++-0+--0+0-++-++--+00+", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "275e98d138e5c78a" + } + }, + { + "chain": "1946848145997617808", + "start": "-+-+-+--0++--0-0+-+--00--+-+++--++-0++-0+++++--0--0+-+-++++--0+-+0+++-+-0-0+00-+-+--+00+0+00-+0-------0-00--++0+0-+0--+++0+0--0+", + "expect": { + "state": "0+0--0--+-00+-00-++--+-+-0-+-+0-00-++-+0-000-++00+0++0+00-0++00++0-++-000-+-0+---+++0-0--+--+++0-00-0++0+--+0--0-++---00-+-000--", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "6e51e4e044c7d4ea" + } + }, + { + "chain": "10159400350932864807", + "start": "--0-+-0+0-+0+0+-+-0--0000+0-++++0--+++++-+++--0-00-0++-+++00--+0++0-0+-0+0+--0+0--++-0+0+-+0--++000-+00-+0++-++++--0000-+-+0-00+", + "expect": { + "state": "0--+-+++-+000-00+00+--++0-0+0++00+0-0++0+-0000-++-0+--+---0+00-+---0-+-0-0+-+0-+0-0+0-++0++--+--+++00-000++-+-00000000--0+-00+0-", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "131896fd9c2679fe" + } + }, + { + "chain": "15194362148048808560", + "start": "-0---+-0--0+-++00-++00+0-00-00-++0--0-+--++-+-0--0+0+00--00+0+---00++--+0-+-0-+-0+---+++0++00-+--0-++-++00+00--0-+-0+00-0+-+--0+", + "expect": { + "state": "00-++0++-+-00++-00+-0---++-----+-0++--+0+0-++--++0--0--0-00++-0--0-0++00+0+++0-00++++---+0+-0+0+--0-+0+0++--+000-+++0+-+00+0+0++", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "dc7c0d1041b4dd9e" + } + }, + { + "chain": "3553216667817520020", + "start": "0+--000-0++00+00+--0-+0+--00++-+0+++0-000+-0+-+-+-0+0-++0++00+00+-0000-00+0+0+--+0+-++++++-+-++0++0-++0000--0+-0+0-----0--0000+0", + "expect": { + "state": "--+0-+--0---++0+----0000+++-+-+0--00+---+0-0++0--++00-00--+---+-0+-00+0--0--0-0--+-0-0-00+-0--+000+--0--++00+0++0-0+-+000--+-++-", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "da0d0b513f25aa82" + } + }, + { + "chain": "3738407306160466512", + "start": "0--0-0-+0-+0---0+++00-+++++0+++0+++++++000-++-0+0+0+0+++00+-++0+--00++-++-+---+-000--+-++-+-+--+-+--++-0++0---+-00--0++0-+-0-+0+", + "expect": { + "state": "---+0--+--00+0-+-0+-++0-0++++--00-+0-0-00+000+-0-----+-+--0-0000+-000-0--0-0+---+-+00-0--000-000-+++0---+0-00-+0+0+--++00++0-0+-", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "bb52f6f311811afc" + } + }, + { + "chain": "2174459033575661147", + "start": "----+0-00++0++-+--+--+--+-+-++---+0-00+00--0+-+++-+--++00+++--+++0-+0-0-+000---0-000+-0-0-+-+---0+0+000+----++-00-++-0-0+-++-00-", + "expect": { + "state": "--+++-0--+++0-++--00+0++++--0---------0+------++0000-00+0+00--0+00+-0--0++0-0-0+00+-0+0++0-0++0-00-++-0-+-0+00--0-0--+++--0+--00", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "8592e4d3daf40ab3" + } + }, + { + "chain": "16763386040539770622", + "start": "--+-000--+-+++000+0-++0++0-+-+-+00-0-00-+0--+0+0+0-0+0+++---0++---+-+++-+-+--+-0+0-000-+0--+-0+-0+-0+-+++-+0---+-000+00-0+0++00+", + "expect": { + "state": "0+0+-000+--0-00-+-0--+-00++-0+00+0-0--0--0-+-0++-0++----++-+++0+0+-0-00+0--0+++0++-++00+00+-00+-0-000-++0-+0+0--+000+-+00+00+-+-", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "d5c69ffa095e246b" + } + }, + { + "chain": "2295243371986251617", + "start": "0++-0-000-00-0++0+000-0+-+-+++0--+0+0+0++00000--00-+00--+++++-00000+0+00+-+0++00+--+--0-----+++++-+-0+--0-00-+-++++++0-+-+--++0+", + "expect": { + "state": "0+00-+++-++0-0--+++0-++-+--+--+--++-0000---++0--++00++00+-+00-0+-+0-+--++000++---+-+++0------0---+-0+--0--+----+-++++0+0+00+000+", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "13693d1ae6a5f904" + } + }, + { + "chain": "9187981958480619105", + "start": "00+-+-0+++0-0-+0-+0+-+-++++----0+-++0++0+--+++0-++0+++00-0+--++-+0--+0--00000--00+-++000-0+0-++0++++--0--0+---0+++-0-++--00+00-0", + "expect": { + "state": "0++0-+--00-0+000++-0+-+-+-0++-00-0---000+++00++-+-0-0+-+000+0--+0-000+0-++00--++--++00+-00+-0+-00-0000+-00-++0++0+0+-0+-0+00--00", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "149f95d7a0a82384" + } + }, + { + "chain": "3667511711285709012", + "start": "++0-+++-0--+0+-0++0++++---++0+--+-++-----++-+0+-0-+--0++--+--000-0-0---+000+-+--+0+---+-+++---+++--0+-++--0-0---+-00-0++--000+-0", + "expect": { + "state": "-00++00-+00--0--0-00+00+0++0+-00-+--+0-0+0++0+-0-0-0-++00++-0+-+-0-0+0-0++--0-+++0--+--+-00000+-++--0-+0--0-++0+00--++--0--+0-00", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "bcce8bb0b332bd74" + } + }, + { + "chain": "16370961070820843737", + "start": "-++0-+0+-+-++0+-0-00-+-+0+-++-+++++0--+-0+0+-++++0-+00---0+0+0-0++-0000+-00+-0++0++0-+0++-+00-+-000++000--+-0--00-+++00-+--++-0+", + "expect": { + "state": "0++00++0++---00++----0-0+++--++0-+-0--00+0+++00--0+-++++--++-+-00-0+-0+-00+0-++-000+---0+---0+-++--00+-+0--+0+-00-00-0++0+-000-0", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "d761bb2986b4c103" + } + }, + { + "chain": "7551476197616124343", + "start": "0+0+-0--++0-0++++-+-+-+0-00++0-0-+---+-+-+--+-0-++--00+0+-++++-+----+-000++0-00-+-0-+0+00---+00+0++00--+++0+00--0-++00-00--0-+0-", + "expect": { + "state": "00-++000-+-+00-000+0+-+-000+00+00-0--+-+0+--+++0+00++++-0++-0--0-0---0+--00++00++-0-++-++-++-0+00-+0+++0++-+-+-00-+0++---++00+++", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "8ecea517381e25ac" + } + }, + { + "chain": "625667783288752810", + "start": "+-+0000++-+---0+++--0++0+-0-00++-+-00000-+000--++-00+0+0-0-000----0+-0+-+-000-+-0--0-0-00+0++++0---+--0--0--+000+0+0+0--0+++0+-+", + "expect": { + "state": "+0+-+-0+--+---00+-0+-00-0-0+00+++000-00++-+00+0-++-+0--+0-+0--0000-+0-+-00000-0-----0-+-0-+0-0-++--0-+--0000--+-++-0---+-000++0-", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "42831eb6de425ea0" + } + }, + { + "chain": "11343257724504668539", + "start": "++-++--00+0-++++++0-++0-++-0-00----++-++-00+++0-0-0--+-+000+---+0+00-0+0--+++++-0+++++0-0-+-0-++++00---+-++0+0--++0-00-0-+00-+-0", + "expect": { + "state": "0+0++0--0+00+0-0-++0++000++++00+0+-00--00--+000+++0+0+-+0--++++-+00++++0-00-0000++0----00++0-+0--+-0+0000+-0+-++000++--++000+0-0", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "6c508e8a2af37036" + } + }, + { + "chain": "9447506989727615600", + "start": "+++-0+-++-+++0+--0-00--0-+00--+-000+-00-++++00+---0+-+0+-0+--00-0+--0+0+-0---00+0-0+0+0++-0++-00-0++-0-++---+0+0-++-0-+-+-+0++-+", + "expect": { + "state": "+-000+-++0++00-++0-0+0++++-+0-0-++0-+00-00+--+--0-----+0-+00+--0++0+0000+0--+-00-+0000--++-0---++++00+++0+-+-+0+----000+00----00", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "ef090f9f0d85fcf4" + } + }, + { + "chain": "16645558829571305038", + "start": "++-+-000++-0--0-+0---0+0-+++-0-+0-00--0+----+0-+-0+++++000--0+--++0+0+0-0+-0-+++0+0+00+-+0+0--00--++--+0+--------0+--+0----0+-00", + "expect": { + "state": "-000--0++++-00+0+--++--+----0++-+0++---+0-+000+00+-0++0+00+-0+-+---0+++00++----00+-++-+-0+-0+-0-0-0-0+00+-0+-++--0++0-000+-+0+-0", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "37be434f746f1e12" + } + }, + { + "chain": "4230692363683675924", + "start": "++-++000+-++0+-0++-0--0+0--++0--+-++0--+-0-+++--+-+---+--0+-+-0000000+-+-0+00--00+0-0---+00+0--+---00+0--+000+-++----+-+-0+-++-+", + "expect": { + "state": "---0-00-+00++0-+-+-0++00+-000++++00++-0++0-0-00-+000---++-0-++00----0--0+++--00+-0-+0+++-00-0++0+-0-0--+--+--+0+-++-0+--0+0+-0+0", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "4fcaa8f51b471fa6" + } + }, + { + "chain": "18373946458211564616", + "start": "+--++-0-+-0--0+000---++-0-00--+0+0+0+0000-0-0+00++0000+00+0-00-+0+00-0++-+--00000+0--+00+00+0-+0+00+-0-++-+000--+0-000+-++---0--", + "expect": { + "state": "--00-++--0++++0+0++----++0++-0-0-0-+++--0-++00-00--+0+--000-0--+000+0--++-0+000-00+-++0+0--0--+--0+-0--+0+--0+-0+0000+0-000--+-+", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "c62c2b42eb6de03c" + } + }, + { + "chain": "12263048759136497696", + "start": "0-+-0-0---0-+-00---++++-0+00+0+++00-0++-+00+0-++0++-00++-+-0+-+-++0++00+++---+---00--0+--0-+0-++-0+-+--++0-00+--00+++-0+-+-0++00", + "expect": { + "state": "+0+-++-00+000+00+++++-+--+00--+--+-+++0--0--+-0-0+0-+0+--+0+-00+--0-+--0----0000--+0++-+0++-0--++-++0+0+-+0-++0+000+---++-0-00--", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "bf3e100beff9cc23" + } + }, + { + "chain": "12379619890032322313", + "start": "0-+-+--+-+0+000++--+0++0----0---+-0+--000-++000-0-+-+000--++-0+-0--+00--+0+---0+-0+0-+0-000--+++-0-+-0+-+00+++00+-+0+++00--+--++", + "expect": { + "state": "+-+---+++--++-++-0++-00+-+00+++0+0+0-+-00-0+0+0-+-0+-+-+-0--++000-00-+0+++000+0-++0-++--0-0-+-+--+--+0---+0+-+0+-+0+-+0++--0-0+0", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "12bb41d2ce471ea7" + } + }, + { + "chain": "213719968000392126", + "start": "00+---0++0----++-0-+-00+--0+--0--0-0-+0--0000-+-0++---+00+0+0++-000+0+0-++-0+---+0-00+0-0+-000++----0----0-00-++-0+00-+0-0-+0-0+", + "expect": { + "state": "000-0-0-0-+00++--0+0+0++0--0---0-+000-+00-0+0+0++---+-++++-+++-000---+++++--00--+---++00++-+0+00-++++0++--+0-0--00-0-0+-+-++++++", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "9aa9d027508dc1f8" + } + }, + { + "chain": "5825476295528865317", + "start": "+-+0++-000+-00-++0-00-++00+-0-+-+-00++--+00-++000--0+-0-+-+---+++---++--00+-+0+-+-0++-0++-00+-0+++---++--0-0-0--+000-+0+0+0+0-0-", + "expect": { + "state": "000+0+---+-0-++0--++--+-0--+-++0++-0++--+00-0-+--0+--000+-+--+-00-0+++-+-+++++0-0--+--0+++0+0000--0-++0-0++--0-+0+0+-000+0-0-+--", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "01129b0db086b847" + } + }, + { + "chain": "7765247281217439551", + "start": "-0++---0-+0----0--+0---+0++-0--+00--0++++-00+-0++0-0-+-+--++--0000+0+-0--+-+0+0-0++-+-++-00-++-0++0--+-+000+0-0-+---+++-+----+--", + "expect": { + "state": "0----++00000+--0-++0-++0000++-+0+00-+-00++0+------00-+0+-+-+00+0+00-+-+-00--0--++0+00+++0++--+---0--0+-+-00+-+0+0---0000-+0++++0", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "6aa2c673a10ee0d4" + } + }, + { + "chain": "11225673450860287876", + "start": "0--+0+--00+0----+-0++0-0--+--00-----++-0+-+-0+-+-+-+-++0-+0-0+-++--+-0+-0+00+0+++-+-0000+0+--00-+0--+00--+-00+-0+-++-0--+0+-++00", + "expect": { + "state": "+++0-+++--+-----0-0+-0--+-+-+0+0---+-000++-+00-0++0-+-000---0+++++0+00+--0--+-++00++-000+0-0+0-000--++-0-+-++0+++++0+0--+00+000+", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "efb224ad9b8e3178" + } + }, + { + "chain": "13174592585459055636", + "start": "+-+0-+--0-0----++0++00+0-00+-0++0+-00++-0+-0-0+0-00-+00-++--+0---+-++--+0+-0+0+0+0+++-++0+00+-++-0-0-000+0---+-0-0-0-+0+-000--0+", + "expect": { + "state": "0000--00+++--+000+--++++-0--+++-000-0-0-0-000+-+0-00+-++---00++-+-+0+++-0--0+000---+0-++0-00-0+-++00++0-0++0---+-0++-+-00+--+-0-", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "9d142181ccf7cb85" + } + }, + { + "chain": "16863728637458840509", + "start": "-+-+--+-+++0++0+0-+-+++-++-0-+000-+0-+0+-0000+0++++0-0--+-+-00--0+00+-0+-+0++00-+0-00-000-0-00-++------0--00-+00+-+-0+00+-0-0-+0", + "expect": { + "state": "0+000++-+-0--0--0+-0++-00-0-0+0-00-0-0+-++000-+-+-+00-+++0-0+---00++-0++0++0++0-+-0-00+0+00+++0+0+---0+0++--++-+0+0-00-0+----++0", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "ef25ce3e75209991" + } + }, + { + "chain": "145400786984960477", + "start": "-+0-0--000++-00-0--+00----0+00000+0+00++++------0++0-0+00-0--++0-000++0++-000---+0-+++00--++0+0-+-+0+00000-0-00+0+0+++-+-0-000-0", + "expect": { + "state": "0-00++++-+0-+-000-0++--+0-+0+000--0--0--+++0-0+-0++-+-+--+-+00+++-+-+000-++00++++-00-+0+----+00---0-+0--+++00--++++-++0-++++00++", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "c642b6a87de053b8" + } + }, + { + "chain": "10148992964060510632", + "start": "-000------0+-+-+0-+-+-+000+++00-0-0-0+---0+-0-++--0---0+++--0+++-+00+-+0--+0---00+-0-0-++0+0+-----+-0-00-00-0--0++++--00--00-+-0", + "expect": { + "state": "0-0++0+00+00+++---0+00--+0000-+--0-0+---000--000++0++-000-+++-+-+--0--0+++++++0+-+0-+000-+------0000+0----+--+-0-+-+0+-++0-0+0++", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "366dcf85b2419ac8" + } + }, + { + "chain": "13947593610485266784", + "start": "-+000-0-+0-0-+-0+-+0-+0+0+++0--00---0--+0+0-0--0+0+0+0+--+0+-0000-+00-+++000+0-0-0++-++0-+0+----+-+0+0-000+-0--+0-+---0--+-++00+", + "expect": { + "state": "0+-+00-+-0+-++0++--00--+-+0-0+00-+++++0--++---+-+0-0++--++-000---+++0+---+000-0-00-++---0-++00-00000-0+-00+0--0-+-+-0+0-00+0-0+-", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "f346b9992be819b0" + } + }, + { + "chain": "17526469500722628381", + "start": "00-00-+0+0-00++0+++-+0++--0-+--00+0-00-0-00----00+++-0++0+000+-0-0----++0-+-0+--+00-+++--+++--00-0-+++-+0+-+00+-0+0++-+0-0--00-+", + "expect": { + "state": "++++000+-000+-+00-+--++0+-+-+---0---0-0-+0-00+-0-00---+---000+-0-+-+++0--00-+0-++-000++0----0+--00-++--0------0-+0+0000-+-00+0+0", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "eb3bdd6c1eb5caa3" + } + }, + { + "chain": "17844109393036198630", + "start": "-++--00+-++-0+00--+00-00++-00+0++++-++0++0++++--+-++++++00-++-++-0000+---0-0+00-0-+--+00-+00+-++0-0---0+++00++0++-00+0++0+-----0", + "expect": { + "state": "0+++0-00-+-0+-00+00-+0++-0--+000-+--0-+----00-+-0--+0+-+00--+0--+0--++++0++-+++++-+-+0-0-0-+-0+0--+-0--00+0-00000-0+00+0-++++-+0", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "7432cd57bda2c766" + } + }, + { + "chain": "8577291197220177247", + "start": "-+00--+0-+000++0+--0-++-0-+-0++0+000+--+-+0--+----++0-0++----+-00++-+0---+-++--0++0+0--0000+-0-00+++-+0+--0-+0--0---++-0-++00+--", + "expect": { + "state": "-+-+-+0--+-+++-+-+0++-0-++-0+++0+-0-++0-+-++++0+000+-+++-0++++0+0+00+000+++--0---0++--00-+-0++0+--+--0++++++-+++-0++00-++++-0+--", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "f4bbf78d074440e5" + } + }, + { + "chain": "2282376073247502527", + "start": "++--00000-00+++-+++0---00----+00+-0-+00+-0-+-+++0+0---+0-++00+0-00++0-0--+-++000-00--++-00-++00-+--00+++-00-++-0+00---+-+0--++0-", + "expect": { + "state": "0-++0-00---+0-+0+0+0--00+--+0+-000-++--0-++-+++00---0+-++++--+0+-0++---000-0+--+0+0+++--0++++----0+00-00-0000--0-0++00-++00-0-0+", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "f4bd8ac5206f0083" + } + }, + { + "chain": "1030956844657080501", + "start": "++0-++00+-0+-++--000++++00-0++0--0+0-0+00-+-00+0---+-0+000-++0--+++0-0-+-+--0--00-0-+----0--0--00--0--+++-0+0--+---00+-0++++-+--", + "expect": { + "state": "00+--0-00-+00-+--+0++--0-----++0+0----0++0+0+0++-0-+-0-+--0000000000---0-000+0-+0+00+++--+-0-+-0+----000---+++0-0++-++00+-00++++", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "88f85d5bc775529d" + } + }, + { + "chain": "11888868794304264352", + "start": "+0-++++++0++0++0--+-00+++++-+00--0+0000++0+-0-0+-00++--+-++-----000++0-++-00+00-+0---++0--+-++-0--0++0+-+0++0++++-+-++---+--+--+", + "expect": { + "state": "--0+-+0-000-+00-+00000-----0+0+-0--00---+---00+-+--0-0---0000+0++-00-0000--+-00-++-+00-0++-000++-+-00++00-0+--+00++0-0+0-+-0-++-", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "018cdfa538fd9ae6" + } + }, + { + "chain": "17179014020352687044", + "start": "--0--+0+--+++---0-0-+00---00-0+--++0-0--0+++++-0-+++0--++00+-+-----0--++++++++00-++-+-+00+-0-00+0+-000-+--000-00--+-+++-00-0+0+0", + "expect": { + "state": "+-+0-+++0--+--0-0-0---000+0-0-0-+-0+0+0-0+0-0-00-0+00--0000---0--++-+0000+0++0+--0--0+++0+0000-0+00----++--+--0+-0--+---+---+-+-", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "be062492325f7508" + } + }, + { + "chain": "9302598381462443377", + "start": "0+-+00-+0-0++0-0+-0-++-00++-00+++-++-0++00+-+++-+-+0--0-0+-00-----+0-++0++0--0-000++0+00+++++0-00+00-00-++00-+-+0-0-0-+-00+-+--+", + "expect": { + "state": "-+0+--0++0+-+-0-+-+0--+000++-0-0-00-+---+-+0-+0-++0++0+00+-0-0-+-0+000--0+0+-++0-----+-00--++-00++0+++++0---+0-0-0-0++++0+0000--", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "a2934c35ec5022df" + } + }, + { + "chain": "7381330920723315974", + "start": "-0---+00+---+-++-0---++-+0--000-00-+-+0-00++0---+++-0+00+--0-0-+-0+--+-+000--0+-+00--+-00--++0+0-----0+++0+0-0++0+++-+-+000+00--", + "expect": { + "state": "0+000++0++-+++0+0-0+00-0--+-000+0+0-00+++00--+++-++0-+00-00-++0-+-+-+++-0--++-+00+0000+-++0+0--0++000-0-00+-+-00000+++0+++-0-0--", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "b4ca580fca046857" + } + }, + { + "chain": "4158637692848040374", + "start": "+0-0-0-+++--0+0-+00+0--+000--++00++--+-0--0+-+--+-+-00+---+-0++++0+00-+-+-++0----+0+00-++-+0+00-+-0---+--+0++--+-0-0--+0+0+0-0++", + "expect": { + "state": "0++++---0-+++-00+++-0000-00-++--+--++-00-+00+0-+0----0-00-+0-0-0+-+-0+-++000+-+--0+0-+0++++0---+--++----+-000+0+-0-+0-+000+--00+", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "ca44e218af1634e8" + } + }, + { + "chain": "13595160513553128420", + "start": "+00+++--0-00+---+0--+00++++0+0-000-00+0+00+-+0+00+0-0++-+0+0+00-++-0+----+-0+----+0+0++++-00++-+++0+0---+--+----0+-+0+0+++++0-0-", + "expect": { + "state": "+0++-+--++----+-0--+0+00-0+++-0-0+0-00+00-0-+--0--+00--0-0+-+0+-++++0+++-0+-0-+0+---00---0++--0-000-+-+00-+0----0-+---++0+-0---+", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "fc68893156e2b8de" + } + }, + { + "chain": "14842253185942051145", + "start": "000000+0-+--0-0--0+--+-0+00+-0-+-++---0-++00-++0++0++0--++0+0++-00++-0-----00+0-0+---+0--0-+0-+0000-+++0++0+++0-+0+0-00+0+-0---+", + "expect": { + "state": "-00-0-----0000+0-00-0+0+--+-----++0-+-----0++-++0+00+++-+++-+--+-+0-+0+0+0+++0000++0-0---+0+-0+-0+--00+-0-0-0+-00+---00-0000--00", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "9dec7bd9c47d7a28" + } + }, + { + "chain": "3174023599467939491", + "start": "--000-+---++-0+++++----+++-+-0++-++0-0++0-000-0-+0-0+-++-0-000+-++-++00-+-+++++-00-+++-+0++---+000++-+++-+-00+++0+0+0-00-00+0--+", + "expect": { + "state": "+00--0--0+--0-+---++000-0+000++-0--+-++-+-0+---+-0++++0+-0-0-++-00-0000--0+++--0++++0-++++++++00+0++--0-+---+-+00+0-+++--0++-+++", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "66636e3c95082cee" + } + }, + { + "chain": "14941618853458826934", + "start": "-+0+0+++-++0--0--+-0++0+-+00++0++--0+0+00+-+-+++0---+00+--+-0+-+00++++0--+0+0++-++-++-0+0-0+--++000--0-+000+0++-+0+0+++000---+++", + "expect": { + "state": "0+-++0+-0-0-++0-++++0-+--++0-00-+-+++-0+--++0--++++++---+-0+-000+0+-00-++0----0+++--+-+++0++++-0+0++0--+++-+--0-0+++-00-++-+0+++", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "7fdcb3771f354152" + } + }, + { + "chain": "18223943856617632646", + "start": "-000000++00+-0+00+-+-+--0++0+0+--+-+-0+0-+-++0---++----+++-00-+00+-00-00++-0-0++0-+000-00++00+00++--+0+--000+0++-0+0+--0+++-++++", + "expect": { + "state": "00+-0-00+0-0--++++0-0--+-++-+++++0++0+--+0+--0-0-0+-00+--0++++0-+-0+0--+-++--++00-000+0-00--0+000++000-0+++-+++0+-000+0+0-0-0+--", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "67822887524aa76c" + } + }, + { + "chain": "3284395097888730860", + "start": "0-00-0+++0++00-+++-0+-++-+-+--+000+--00-0+-00--++0+00+--00++0--+0-00-0-0+0----0-+--00--0-+0+00-+0+--0000+-+0++--+0000-0++-0++-0-", + "expect": { + "state": "0000-0-0+-++-0-000+00-+++--0+0--+00+-00000-+0-+-++00-0-0--+-++-++-+-0+-00-000+0++00++000+0++--00++-00++-0---+0+-+00--0+--00-0000", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "281747a8adb3f3b1" + } + }, + { + "chain": "9356684688371128229", + "start": "000---++-+00+0-0+++00+0-+-0--+-0+-00+00+0000----000000000+++--000000+++000-0-00-0-0+--+-+-+0+-0+0+--0+0-+0-0+-+-++0000----0+++--", + "expect": { + "state": "-+00+-++0000+-+0+----++0-++-+000++--0+++++0++0--++-0++0-+0+000-00-+0-0+++-+00+---+000+0000-+00+-0+---++-+++-+0-00++-++0-++0+00--", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "fa98cdda1053e6f8" + } + }, + { + "chain": "8252271717858184212", + "start": "+0+-0-++-+-0++000-0++00+-00-+00+-++-++-++-+0--000+000-++--+--+0++++-+0-0-+-0-----+++00-000000----0-+0-+----++--++0-0+-+++---00-0", + "expect": { + "state": "----0++-00++-++-++00++-0+0-0-0+--++0-+-+0+000-0-00+-00++0-00+-+-+---00-+---+0-+-+-+0-00+--000-000-+-++--0-+00-++0---+++-++++++-0", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "cacd9d708a356b78" + } + }, + { + "chain": "6377398358189801317", + "start": "+000+-+0-+++0+0-+-+----00-0+++0++-++0++-+0-+-000000--++-0-----0+--+-0+0+0++0+--+0+0+-00-+0++++0-++00-0-0-0-0-0++-+0-++-0-0-+0+++", + "expect": { + "state": "0-0-0+-----0-00+0+00-0+--0-++-+0+--0++-++0+0000-+-+-0++-0+0++-+00-+0--0+-+++++-+++++---+-0--+-0+---+-+0-0+--+0+----+0-++0+-00+0-", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "60bf3e6bef4e5d22" + } + }, + { + "chain": "3172281893231203680", + "start": "+++-0+++000++0+0-+0--+-00-++----+---0-0+++-+00--+0+---0+-+0+++00-0++-++00++-+-+0+00++0---+++00+0-+-0-+-+-00000-0+00+-++-0+--++-+", + "expect": { + "state": "+++0+-0+--0+0000-++0+00-0+++0+0+0++0-0--+--00++000+--0++--+0++0+0+-++-0++--0+-++--0--0++0-++-00+0---0-0-++-+0+-+--00---++0+-+0--", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "3b0abe9476dcbda1" + } + }, + { + "chain": "17138131236253629236", + "start": "++00-++++++00+00-0+0--000+++-0-00+0+0+--+0++000+-++0-+0+0+-+0+0++0-0+00--000++0+---+---+0-000-0+-00++0--+0-++++-000+0--0+-0-+00+", + "expect": { + "state": "-00---+++-++-0-+0-----+--0--++0--++-000+0+-0-0--0+-0-0-+0-+0-00-0-00--+--0+0+0+0++--+--0++---0+-0+++0-0++----0++0-+0++++-+++0-++", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "b39c0a08eac0f7ac" + } + }, + { + "chain": "4316548875375740387", + "start": "0++++--+-0++-----+-+-0++-+--++000+-0+-+0-00++0+-0+--+0-0--0+-0++-0-+--+++00-++++++0-0-0000-+-+--0-++++00--+-+----+0-000++-0--0+-", + "expect": { + "state": "0+0+-00-0-000-++-0+-0+-00--00+-000000+--++-0+-+0-0-+0++0-0-+---+0-0+-0--0+++-0-0+0+000-+----+--000-+0--+0--0+-0+-+00-+0-+-++++0-", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "249af0768cad9ebe" + } + }, + { + "chain": "13420327571467654418", + "start": "0-0-0-0+0---+0+00+-0---+---0--++--+-+00-++-0+-+0---0++--+0+0-++--00++0-++-0-+-++000--+0-0-----+-000+00-+0-+++-+-000-00--00-0-++-", + "expect": { + "state": "+0-00+0+0+0-+++0+0--00--0-+00+--0+---0--0-+---0-+-+-+0++--+-+-00--00+-00+00++-0-++-000+-0-00--++-0+++-00-+00+-+--++-++00-00+---0", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "d5ac5cbc5d406ba0" + } + }, + { + "chain": "16310707294052292582", + "start": "--00---0++--0-+000+0++0-+----------++0-+-0000---+++00-0--++0+00+++++-+--0+---+++0--00+-00+-00+0-++++0-00+---000-00--0-++--++00+0", + "expect": { + "state": "+-0++++-0++-0+++-+-++0+0++000++00-0-0--+0-++-+0-----000+00-0-----+-+-+0+-00+--++-+--+-+++-0+0+0-+00+000+-+--++-0-+0+0-0+0-+--0-0", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "3d48e9f8ba376021" + } + }, + { + "chain": "2717654836736170984", + "start": "00+-00+-++--00--0-+++00-00-000-+++++-+--+0+-0++--+-00-00-0000--+00---+0+0+0+-+0-0+0000+0-+0+0+-++---0--+-0--0+0-----0-0+-++-0-0+", + "expect": { + "state": "+0+0+------0-+0-+--0-00+-000-+0--00-+--+0+---+-+--+--0+0000++++-+-+--+0+-++0-0+00+00-+0+0+0-0+-00-0--+0-++++0+0+--0+++++-0+0-++-", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "88d1392fef590096" + } + }, + { + "chain": "6369069754371979859", + "start": "0-00+0+00-0++0-0++++-0-+-0-+00++++--+0++0-0+0+00+00-+0000+-00--+0+---00-+0+0-+-0-----00+00----+00---+00+-00+-+++-00++-0--+++--+0", + "expect": { + "state": "-00-+0-0-0+0-+00+--+0+00++---00+00-00-----+00+--++--++-00+-0000+-0-00+0+++00+0+00++00+++0---0-++--0-+---000-00--++-0+0--++0++++-", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "15b94aa2e1d4c69c" + } + }, + { + "chain": "18132090003013912699", + "start": "00+0+0--+--00000+-0-+000-+0-000-+00++0-+0+-0+0-+000+++--0+00+0+-+00+++++00++0-+00000+----0+--+-++0++-0++00+-0-0++++-+0+++0+0000+", + "expect": { + "state": "-+-+0-0+-0++0-0+-+++00-0--0+---0+00++0+--0+0++-+-+++00---+000-0+--+++0-+---0+0-++000-0+++++-+-0+++000-++0++00++0-+00+++0-++-0-+-", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "44c6ef2b095064ab" + } + }, + { + "chain": "8309055160031582795", + "start": "+-++--+00-----+0+----0------+++-+0-0-0+-+-0+000--+00+0-0+--0--+000+0+-+-+-0++0--0-0+0-0-0-0-00+0++0-0+0--0000++00+-00+-+0++-0-++", + "expect": { + "state": "-0++++0--0+0-+00-+000++00-+0++0-0++00-0++000+00+-+000-+--+-00+--0-0+-00+0-+++0--++00+++---0-----0+0+00--++-00---00+0-+-000-++0+0", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "df3879847349d8d7" + } + }, + { + "chain": "297480325987471452", + "start": "00++-0+0-00+0++-00+++++0++---+++-0-0++-0++00--0+0-0+0000++--+++0-0+-+++-0+-0-++0-+++0+00-0-+-+---00-+000-+00-++00++-00+-+---0-0-", + "expect": { + "state": "+00-0++0-++0-+---++000+-0+0+0--0+-+-00000-00+00-0+0++0--+-++----+-0-+-0--------0+0++-+++0+++++0+0+00+0+0-+--+0---0+-0-0+--0-+++0", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "fa45b83028dae692" + } + }, + { + "chain": "9074855892878445053", + "start": "0-00-++-00+00000--+0+-+000+-+++--+++0+-0-0+++-0000+-+0-+-+0-0+0--+-+0++-0+0---++-+-+0-0--+00++0--+++-00-0--0-+0000++0--++-00+0++", + "expect": { + "state": "0-+-0-0--00---+0-+--0++++-0-0-+-0-0-0-0--0+0--++-+-0--++--+-0-00-+++++0+0+0-+0++-+0-+0--000+0++++-++00+0-+000--0+0++0+0+0-0--+00", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "73d8f89037ae7922" + } + }, + { + "chain": "4518874752789997835", + "start": "-+-+0++0++-++00++0+---0---000++-00+0++--0++-+00+0+++-0+++00+-++++-0-+00-+-00-+-0-+-0---0--+++0--+-000+++00++-0-00+0-00-0-+000000", + "expect": { + "state": "00++--0+000---0-0-+-00---0-0+---+00+--0---00+-+00--+----++000-++0+--0+---0+++-+0+-+-++-+--++++-+++0-++-0+++-+-+00+++0--0+0+++--0", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "15cf9dc378bcb755" + } + }, + { + "chain": "4548046243623530968", + "start": "+0-000-+-+0+0-0+00--++0+-0++0++0+--00++00-0--0-0-000+00+-++--00+-++-+0+000+0-00--000+0-+-+0-0-0-----0-0++--00+-0++0---0+0-0-0+00", + "expect": { + "state": "0+-0+-+---0+0-0-0+0-0-+++--+-0---+0-000+0+0+000+--00+0+0+00+00--0+-+-+00-+00+-----0--00-+0-00+++-++0-00+--0+-+-----+00+--+0++++0", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "f75df2e5ebdd7f2a" + } + }, + { + "chain": "7919297726193721499", + "start": "00+0+0++---+0000+--0+++0+0+0000+0-++0-0-++-0-+++-+-0+-+-00+++-00-00++--+++0+----0++0++--0-00+++0+0--+0++000--0---+-+0++0-+0+0-0-", + "expect": { + "state": "++0+---+-++-----+000+-0-0-0-00-++++00-0-+++--+-+----++-000+++0---0-00+0---0-0-0--+0++-000+-+-+++0--+0+++++0+0+00++----+--++00--0", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "76628b0363b1ced7" + } + }, + { + "chain": "2312587699001352252", + "start": "+00000--+-++00-+-0+-000+00+0--+0--+---00--++++0-0+-0--++---0+00+-+-+++0+0+0+++-+0+0+-0+++-+00-0---+---+000++-+-+++0-+--+++00+00-", + "expect": { + "state": "+0+0++0--0-0+0-++0++0-+--00+--0+--+0+000++-0-00000+++-0+0++-0---0-0+-----0-++-0+++000+0-++0000-0+0-0+00+++--+00--0+-0-------+--+", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "909a6fa3a5f679a0" + } + }, + { + "chain": "15905122837652555750", + "start": "++0+-+00+0----++0+++-++++00--+0++0+-0+0-00+++0-0--+-0+++00---0-++000-0--0-------00+-+0+000+-0+++-+0+++0+-+000-+00+000++00----0++", + "expect": { + "state": "0+0000---0++00++000++0+-0+0+++0-+0++-00+-00----00+-00-00-0+0--+0-+---+--00-+-+00+++-+0+0+-+0--0+0-+--0--0-++-+-00-+---00-0--000+", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "91edfb8902dc958d" + } + }, + { + "chain": "4480421947123355303", + "start": "00-0-00+00-0-++0+00--+0++0+000-+-+0-0+00-+0+++++0--+000-0--++-+00+-00+-0-+0--++-+0-++---+---+-0----+-+0--0-+-+---00++--000-0-+++", + "expect": { + "state": "-+---0+0-00--+-0++-+++---+-++-000---0++++0-+-0++-0--0++--0+++-0--+0-0+-00-+--+0+-+0-0-+-00--++00-++---0+++-00+0++0-0-000---0++-+", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "082aeefbdc822f07" + } + }, + { + "chain": "9253829006884688877", + "start": "+++00-0+00+---0--+++++0+-+0-+00+0-00++-0+-00-+-00+0-+----++-+00++--+0-0+-00+0++++++++-+0000+-++--0+00++++0+-00++-0---+++000-++++", + "expect": { + "state": "00--00++0-+0+-+-+0++-+0+0-0++00++0+0-0-00+-+0-++0+00++--0-00--00+0-000-+0+000-+0--+0+0+-+0+--+00-0-0+-+0+-00++-00------0-0+-+0++", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "c50cd378f92a8559" + } + }, + { + "chain": "3870334698468660564", + "start": "000-++-00+0+-0--++--0-00+-0++-+++-+0-00+-0-0++++--++0+00+0000-000+0+++--00000+00+0--+-+0++000+0-0++0+00+0+0-++++--+++00--+0+-+--", + "expect": { + "state": "00--00+00+0-0++-+---+++0-00-0+0+-++++0-+0-+-+0--+00+-0---0--+00+0-+0+++0+++000----00+000-++-0000+0--000-0-00-0+0+0+0+0-+00+++0--", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "d4eca7fa3eaed81d" + } + }, + { + "chain": "6791397401275289919", + "start": "00+++-0--+00-0000+++++-0--+---++0+0-+++0++0++-0-+----0---+---++-++-+++-+000+0--+-++000++--0+0+0-++-0-0+--++000+++00-00-0-+0+0-0+", + "expect": { + "state": "-0--0+0-++-++--+00---0-0---00++++-0-++0+--0+0++--+-++-0--0---0-0++00-0--+-0-00-+++-0+0-0--++-0--+000+00+++0+0+0--+++0-000-++-+--", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "5ba55e65c05f2a6c" + } + }, + { + "chain": "13661967056586217105", + "start": "-+++0-00--+-++00-----0+-00----+0+0-+0+-+0+00--++0000+--+0-0-+-+-+00+00-+0-0++-0++00++----+00++0---+-++0+00--+0-0---+---+--0-+0++", + "expect": { + "state": "-+0+0+++000--0-+--+-+0+++00---+000+-+--0+-0+-0----+0++++++000-0-+0++000+-+--00-+-0-0++-0++0+-0-0+--0+00-000-0++0++00+--0+0-0-++0", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "97e15004e1a53393" + } + }, + { + "chain": "15885330495164199198", + "start": "0-+-++++--00+-000++000--+0+-0--0+--000++00-0-++-0-0-+-0+++--0+0+++00+-+-+0++0+-0--+--+-0+0--0--+0+0++0+-0+-0-+0++00-+0+-0++-0++-", + "expect": { + "state": "+--000++-+0+0-+00-+-+0+0---+-++-+0-0+0---+000+-+0--0++0++-+0-0000+0++-0+-++-0+-+00+00+00+000--++-00-0-+---00----+-++-0-+00++---0", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "2dca4db6cc8a906b" + } + }, + { + "chain": "567086332819438549", + "start": "+-+-+0+-0+00++-+++-++--00+-+--0-+0--++0-++--0-+0-0-+0-0++-0-++-+0++++++-+-+-0+-0+0+0000-0+0+-+00+-0-0---+0-+--+-00++00000++0--0+", + "expect": { + "state": "-++0++0+0+000+0-----0-0---+-++0+--0++00-0--+000-0+0+0+0+-0----++-000-+00++-+00++0-++000++0000+0+0+--0-000-000000-----0-+0-0+0++0", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "f45c156dabb12ccf" + } + }, + { + "chain": "14345299641740084830", + "start": "0-++00-+-++-0+00+-+-+--+0--+0+0-+0++-0+-++----+++0++++0-++0-+00++0+--00-0++-+++-+00+0+--+-00+--0-0-0-0-+-+00+--+-+++0++++000+0-0", + "expect": { + "state": "000-0-+-++-+0+00--+0000++-0+---00-++0-0+0+-0+--+0--0---+++0+--000+0++0000++0+-+00++0+++0-0-+00--+00+0-00-+++++0-+000--+0++-0-+0+", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "648046af88be7566" + } + }, + { + "chain": "7544336281946245663", + "start": "++-+00-0+00000--0++---+-++--0-+000++0----0+0--00---0--+0+-0+++00+-+00+++--0++++00++++-0+++0-0----+0+0-+--++0-00-00+0+-00000-++++", + "expect": { + "state": "0++-00+00-0+0+00+-00+0---0+00-+++00+0-0-0-0++000++-++00+--++------+-++-+000-0+--+00+--0-+-+-++++---+0+---+00++++-+0++--0--00+0-0", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "8954dade8f3beaf3" + } + }, + { + "chain": "15460748506043742888", + "start": "+-0+-0-00++-0-0--0+-0+0-+-+---+00+-0-000+-0+---0-0-0+++0+0---+0++++-+0-0+0++-0-+-+00-00+0+0000++-+---+---++-+00-+000--++0+-+0+00", + "expect": { + "state": "-0-0-0+-0++-0++-+00-0+-00+--0++----++0-0+0-0+00+0---+0-+0--++++0--+-++00---0+--+00++0++++---++-000+++0-+++---0+0-+000++-+-0+-+0-", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "f9233740b0666ae1" + } + }, + { + "chain": "11699736244747357744", + "start": "000+00+-++++-00+-0-00+000+00+00-+++++---++0-++0+00-+00+0+--00-00-++++++0-+0-00-+000----++0-00+0-++++--+0+-++0+0-0+0+++00+-+000+0", + "expect": { + "state": "+-+----0+0-+--++-0--0+00+-+-+0-0+--0----0-0000++-0--+-+0+0-+0-00++-+-+-+-----+--0-+0-+-0-----0-+0++0+-+0+--+--+--0--++-00-+-0---", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "f2ac1e59828b0294" + } + }, + { + "chain": "14099037634565155202", + "start": "+++--000-+-+-+++00+0+-00++0++-+0+-+0-+-+-0---0-+-+000+-+00++++0+-0-+-0---+++00+0+0-0000+-+0+00---0-+--++00-+0-+00++0--0-++-0-+0+", + "expect": { + "state": "-0+-000-0----0-+-+0+--+++-+-+-+00-00-+++--+00-+0+000--++-0+0+++0-+0-++00-+0+0--00+000000---+---00+--00-++000-00----+0+00-+00+00+", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "be05d2af5deb5460" + } + }, + { + "chain": "4565449178863822757", + "start": "--0-00-0+00+-+--00-00+-0-+-0-0--+-+++00++0-+--+0++--++00+-0+00000--00+0-0-0+0-+-0+-0+---0+0-0+-000+--++0----+0+-0--0+-0---0+0+0-", + "expect": { + "state": "00-0--00+0+++-0+-+00--00-0-0+0-+++---+++0+++0+-+00--+00000+-++-00+0+0++-0--0++000-+-+0+--0-0-0+-+-++00+0-00-++----+--+++000-00-+", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "6fde977787d95eed" + } + }, + { + "chain": "1604993138320927234", + "start": "0+0++0-++0+-+0++-0+-+-++-+++--00--0+0++0--++0--00+-+-+--+0++-0-++-++-00-0-++---+---++-+-+00+0+---+++0+0-+-++-0+00+++0---++++000-", + "expect": { + "state": "--+000+0+---++--0-++0-0++0-0+--++0+---+000+0--+--+0---0+00-+00-++++++++00++-++-++++-0+--+++-0-00+0+--00-+0-0+0-++000+000-000+-00", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "52e01d88bfbfaae8" + } + }, + { + "chain": "15605313600197783348", + "start": "-----000++-+-+----0-0+00+-0-+++++-0---++-0-+000-+---+00++-++0-0-++-++--00-+0-+0+0-+++-00-00+++-0-+00-00+-++--0+---000+0+00--0-0+", + "expect": { + "state": "---+-0--+++++0+---++00+--++-+0--+0+-++--0-0-0--+--+--0+---0--+---++---++00-++0-0-0+--+0-0--+------+-0+0++-0++0--+0-++++-0-0--0++", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "2c2d41385d6c2755" + } + }, + { + "chain": "6714395774268796855", + "start": "0+-+00-+00-----+0+-0+--0--++000000+--+--+000--+-0--+0+-00++0---+++++-0+-0+-+-0--0++-0-+-+-0-00----0-+0++-0+-++--0--+0-0+0+0++-00", + "expect": { + "state": "+00++00--0-----00--+-+++0+00+00-+-+00+0+-+0+++--0+00-+00+0+0++-00000++-000-0+++-++-0+-00--+0+++0-0--00000+-+00-0--+00--0+-0+0--0", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "12c49a278fb53f83" + } + }, + { + "chain": "11802352134020272826", + "start": "+--++--0000-+-+-00-00+-00--+0-+00-00-+++0-+++++-00+0+----00+00+00-0-+++-0+++0+---+0+0-0+0+00-+00-+-+0+0+0-++-+--0+0---0----000-+", + "expect": { + "state": "++-+0-0++00+0++-+-0++0-++-000-+-+--+0-----00-0-+0-++-0-+--0++--+---+-0++++000+---0--+-++-0+--+0++-+-----+--00---00+-0-+0-+0++00-", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "2f8de054df11aa96" + } + }, + { + "chain": "10441739402075278175", + "start": "000+000-+0++0+-0+-+--0+-0--0-00-+-+++000--0++0+0+00--00-+-0--0-+00+0++0+000-0---0000---0-+++-0+0-+-++00-0-++0+0+0-++---++--++-0+", + "expect": { + "state": "+-00000--0++-+0+0--00+++00000++-0--+0-++0-+---0-0+-+-0--+0-++-++-+0----+-+++++--+0+000--00+---0---+0-00--+-00---0+-0--0++--0+0+-", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "b1c40f726de4ed61" + } + }, + { + "chain": "1828473540570693054", + "start": "-+0000+0+0-00-+0+0-0--+-0+-+00----0+0000---0++0--+0--+00+--+-000+-0-+-++-0+0--+-+000++-++++-0---0-++0+0+000+00--0-+00-+++0+0+-++", + "expect": { + "state": "000++++-+--+-++00+-+++0+0+++0+-0+00-+0---+++-+++0----+--+0+++000+-00+--0-++00+-++-0++--0-++-+++0+0+-0+-+00-+---++-+0+0--+0+++000", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "7dce1e85c62f625e" + } + }, + { + "chain": "13973319245849388596", + "start": "0+--+------+-++---0-++0+----+-0+-+++-0-0-++00+++--++--+0++--++0++00-+---+0--+-++00+-0-+0-+00-+----000+-00--++00++-+-+0000-0+-0+-", + "expect": { + "state": "-+00++--00-0--+++-0-0+++00-0--00+++++-+---0--+0+0-+-0+-0-00+-0-0--0+-00-0-00000-+0++---++0-+++-+00+0++-0--0++--0---+0-0-0-+0+-0-", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "c804b0487ac681b8" + } + }, + { + "chain": "14739371056909550813", + "start": "+000++--0-++++--+0-+00++0++-00-+-++---+-+0-+-0+++0-0--0+-+---0-0+00-+--0-00-+000--0++0+0+-+++++0-+-0+-+-0-++++-+--0-++-+0--+0++-", + "expect": { + "state": "0+++0++0+0000+-00-0-0-+-00+--0+0+-+-+--0---0++0+-+0-0+000---0+0--+-+--0+0-++-0---+00+0+-0-++++-0--+++-000+++--++-00--+-+---0++-+", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "7a6dc5305249ab96" + } + }, + { + "chain": "11606151890413472179", + "start": "-++00+--0+0+-++000---0++0-00+++-0-+--00++0--++-+--0++00++0+-++0-00++-+00++0--+0+-0-+0+-+0---++-0++-++0000+000+--0+-0--+00+--+-0-", + "expect": { + "state": "0++--00--+0--+00+++-0--0+0+-+++++--++0-0-+0+0+++------0000----0+0---0+-+-++00+-00-0-00++-++0--+0--0+0+00+0000+-0000-0-+0-0+-+0++", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "df977b92d4377cf0" + } + }, + { + "chain": "10429687466496053543", + "start": "++---0-0++-++-0000-+0+-+-++-0000-000-+0-0-+00+-+++00--0++-++-0+0+00+0+-+000-+-+----0+0--++0-0+0--------++-++0+0000+--0---+00--0+", + "expect": { + "state": "+----+-++--0-0--0-00-++0-0-++--0-+-+0++0+--000+----0++0-0-0+-+0-00-0-0--+0-+0--+00+++00+----++--+-++0-0-+-+--0+0+0+--00-0-+++00-", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "49f6b5b312b56e9b" + } + }, + { + "chain": "4372337763056922378", + "start": "+0++0-0--0000---0+0--0-+0-+--0-++00--+++--++0+--+++-+--+0++00+0+000--+0-000+-+-+0-0000-000+00000+00--+0+0-+------+----0-00++0-0-", + "expect": { + "state": "00++-+-0+-00+0-++--000-0+--0+--0+--0++0-0+--+-0--0-++--0-+0+00++-+--0-00+-+00-+000-0--0+00+--0-+0---++--++0+0++0--0+0-++-0-00+0+", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "90554d5deb259399" + } + }, + { + "chain": "2695553560857211975", + "start": "---00+-0++-+-+-0+-+-0----0-+++++0+-0+0-++--000+0+0+0+--0+00+0+-++0-0-+-++-0-+0-++00-----0++-0+0---0++-0--00+00++0-+0++0+-+++-000", + "expect": { + "state": "+0+00-+---+0+--00000+00+0++-0-+0000--++++++-++--00+-+-----+--0--0000--00-+----++-0-0++00-00-0+--+++0+0000-00+-0+0000+-0-+--+0-00", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "b2a1a8261f2b67ea" + } + }, + { + "chain": "14501810240104944965", + "start": "0--0-0-+0--+--0----+-++-0-0000+--+---++++0-00--+-+-+0000++-+00000+0+00+0+-+-++++++-00-0++-+0---0+0+-0++00+0++0-00000+0---+0+0-00", + "expect": { + "state": "-++00--+-0-++0--++++0-00-0-0-0000---++-++---+-0-+++---+-0++--0-+0+++0-00-+00-0+++0-+-0+--0+--+0+00-00--0++-0-+-0++0+00+--+0-+-0-", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "610f8215b266433a" + } + }, + { + "chain": "7196316884388886423", + "start": "0-+---0-0-+-00-00-0-0+---0+--+++0+00---+-00+-+0+0-00-0-+0-0-+0-+-++-00-0-++-+++0000----0--+-0-++-0++0+-00++--++--00+--+---+0+-0+", + "expect": { + "state": "-+---+----0-+-0000-0+00++00+-+--+--+-++++++-0-+++0-0+0---00--0++-0-+-0--+-0+-+00-0+0++000+00-+-0-000--00+000+-00++0++0+0++++---0", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "49e2acea34cabe47" + } + }, + { + "chain": "7886224337246313124", + "start": "+--00-00-+--0++0-0+---0+-0-++-0+-+-0000-++0+0--0+-+-++0000-+++++-0-+000+++00+0++-000-0-+0--+-+00-0-00--0+00--++-00-0-00-00000++0", + "expect": { + "state": "-++---00+++-0-0+0+0+00++0000+-+-00++0++0-0++0--++0+0-+-+++00+--++0000--0-+00++00-+00+++++-+--00-0+--0-+0------0-0-00+-0--0+--+0-", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "9b668627ccec986a" + } + }, + { + "chain": "2612879679738399117", + "start": "0++++0000000+00--++-+-+00-00-+++00+++-0+0+-++--0++++---0+--000++0+-00+-++-++0+--+0+-+0--00-+-0000+0-0+0-+000+0+0+0--0++0+-00+-+-", + "expect": { + "state": "0+-+++-----0--00-+0+00-++000-+00++-+0+0000+--00+--00--0++0--0+00-0000-++-+0++-0+-+--+---+-----+-+0++-0+0++000+0++0-+0++-+0-+-0+-", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "694653d9298011b4" + } + }, + { + "chain": "8470882600877910602", + "start": "00+-++0++0-++---0-++0+-0-+---++0+-00-000+++0+-0++00-+--00--0-+000+--+0--0-+-+++0+++--0--0+0+0-0-00-+000-+++-00++-+0-0+++-0-0+00-", + "expect": { + "state": "+-0+0--+0+-00+0-0+--0-00++00+00+-+--0-+-+0+--00-0--+0+-0-0-+--+-+-00+0-0-000-0++0--00+--00-++0-0-+-+++0+000++0++00---+++0+-+0+0+", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "f2ee232c57de01c4" + } + }, + { + "chain": "1657129842217738873", + "start": "--00+-0+-00--00+0++000-0-++00-0+--0+0--00+0+0+++++-+--0+--0-+0+00+00+-0+00+-0+-0-+--+-+0---00+-0+--+00---00+0-+-++0--+-00-+---0+", + "expect": { + "state": "0-++-+-0+-+-+0-+-0-+0-+-+++0+--0+0+----++++-0+0000++-++0-0+0+-000-0-++0++0+00+0-+000+--00000++0+--0-+0+00-0-00+0+00-+0-0-+-+-+00", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "ee41fc1b3272786d" + } + }, + { + "chain": "9672251419444184909", + "start": "--0-0+0++-0-0+-0-00+++-----+0+0++0+0++++--0+-+00-00-+--000-+--++00++-0+++-++00-+--+-000-00++--+++0++0+++0++-0++-0+-00-000-0+-0++", + "expect": { + "state": "+0+--000++0-+0+++0+-000+++0++-00++-0----0+0--+00-+00+-00-0----+00-0-+0-+0-0--++0+++--0+-++++-+000-+--+-0-0++++-++00-00-0+0+---0+", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "81642675d544198c" + } + }, + { + "chain": "8631556095947339704", + "start": "0-+00-+++++-+000+-0+00--+--+00---++--00+-000+0++-0000-0--0-0+0--+00-+-+--000--+00++-++-+00-000-00+0+0++0+-+-+0+---0--+00-0-00-++", + "expect": { + "state": "000+0+-+0+--+-0-0-0++-+-00---00-0+0-+0++++0+-000-+00-++++--0-+-+-0-+00---+0+0+----+-0+-0--+++0-+---0-0-++-+-0--000+0+--++0++-00-", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "c58ab7fd4a38c271" + } + }, + { + "chain": "4991571795517437031", + "start": "0-+++--0--+0-0++---000+000++000+00--+-++-000+-0+--+-+00-+++0+++-+00+00-0-------+-00-0++0++-+-0000-+0+-00-0++0--+00+--0++-0--0+-0", + "expect": { + "state": "---++0000-+00-0+-+0+0+--00-0--0+0+-+0++000---0--++0+0-+00++-++------+-+-00+++-0++0+-++-0++--0-+-0++--000--+00+-000-0+-+0-000----", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "3b8828a04c98d6d0" + } + }, + { + "chain": "10533949290791253762", + "start": "000--0--000-0+0+-00-++-+-0+--0++00++0------0+-0+000-+0--0-0++0+0--+0++------+-0000+0-0+0000+++-+0-00-0+--+-+0+0-0-0--+++---000-+", + "expect": { + "state": "+--000-0-00+-00-+++++---+-+++----+---0--++++-00-0++---+-00-0+-000+000--0-0-+000+-0++0--0+0+00--00+-+0-0-+00+-0++0-+-+---+-00+000", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "07094bf82d14d65e" + } + }, + { + "chain": "10237180400531992748", + "start": "+00-+------+---0+-+0--------++--+0++0+++-++++---++-+-000000++0-+0+++0--0+--0-0--+++0-00+++-+++00+-00--0--0-0-+--0-++000-++0-+0-+", + "expect": { + "state": "0-0-+-00-0+0-+0+0---+-0000-+-0+--+0+-0--+000--+--0-0-+---0++++0+--0-0++-00+-0+--+-+++--+0-++---0-+000+00+0+0-+00-+0-00----++---0", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "7cf2e40f3ab2b09b" + } + }, + { + "chain": "10625842631941868863", + "start": "000-0-00++0--+--0--+00+0--+0--0++0-0-0--0+0-++00-0+0+00+--+-0--0+++0++00------00+--0--0-++0+--00+++0+-0+0+-00-+-00-+00+++0+-+++0", + "expect": { + "state": "+0--0+--0-0+++-++-000---+-+00+0-++-+-0-++0++-+--+-0-000-+---0+-+-0-0-0-+++000--+++---0000-++0-+-++-+-+000+-+0-+-++-0+--+-+000--0", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "6aec8f237cc4373e" + } + }, + { + "chain": "4636517946019736097", + "start": "+-0---+-00++---0----00---+-+--0-+-+-0-+--00--00+++0+--+-000---0+-+++00++0+-+-0++-00--+------00+-0+-+-000++++++-+-+0+--+-0++0----", + "expect": { + "state": "0+0--++++++-000--0-+0++++++000-+0-0--+0-+0++0+0000+------00++0++00--++-+-0+-+0-0--00-0++0+-00---+-0+++-+0-0-++--+-+0-++--+++0++0", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "b384170a73cc89f6" + } + }, + { + "chain": "7887458806930542236", + "start": "--++-00+0-0--00--000-000++0-0-0+0+++-0+-0+--0---++0+000+++++0-0-+++--++-+00--++-00--0+++0+00-+++++---0++0+--+-000-0-000-0+000-+0", + "expect": { + "state": "+-+-+++-++0-+---0+0-+-++-++000---00+0+0-0000-0-+-++-++0+-0000++0--0-----+++-0+0++0-0+-+0-00-+---0--00--++00--0--+0+0-+0--00+000-", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "75ff18ba2d35366a" + } + }, + { + "chain": "18120779408919350118", + "start": "+0+---00+++0+++0000--0+000-++000--+0-+00+-+-0---0-0--0++++00-0+0-+000+0+-+---0+-+----+0+--+00--+-00+--+-----00+--0000++--0+++-+-", + "expect": { + "state": "-++-++--0+-+0+-+--0+0++0+-0+++00-00-0----+-0+-+++00-+0--+++0+--0---+-0-+-++++-+0000++000---+0-++0++++0+--+--0+0-0--0-----+000---", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "6bb9d349c41068f6" + } + }, + { + "chain": "4857331255992770783", + "start": "+-0+0-00-+0-++0+++---0+0+-+-0-0+00-0-0+-+-++0000-00-++++-+++00-000+000+0---0+-0-----00+0-+000+----0--000++0-+0--0000+-00++--+00-", + "expect": { + "state": "00+-0000+--+-++0+-+00-++0-0+0++-00--0+++++-+0+00++-+0+0+-00-0++000--+++00-00++0-+--+00-++0---0+-0--0++00000-+++++-+-0-0-++0--++0", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "0cc7385270539683" + } + }, + { + "chain": "8883775923643027385", + "start": "-++++-+-+0000+-0+--0+++-+0+0+-+--0-+-0+-0+++----0+-+-+--0+00-++++0-+-00-+-+-00++0-00-+-0--0-+0-+000+0-+--++00++--0-00+000-0000+-", + "expect": { + "state": "000++000-+0--0-00++0++00-+00-0--0-0-+-+0+0-0+-+0-0--++0+0-0--++--+0+0+-00-0-+-+00---+0--0-00+0-+0+0--+-0-0++-000-+00+-+++0+-+0-+", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "09446199bfc77a9c" + } + }, + { + "chain": "6288311659551586603", + "start": "+0+++0+++0-+----+00-0+0-0--0-0+-+0+-+000++--++000--+-000-+00+0+0-+-+--+-00++++0-0-0+++-+00+0-+++--0-0-+00+-0-+0+++++00+--+0++--+", + "expect": { + "state": "-0-----+-0--+--0--0+-0-0++0++0-0-++000++00+00--0++0+00-00--+000+0--+-++--000---0-0+-0+-+00+--000+--00+--000+++-0+--00-++0-0++-00", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "212e3698b8762232" + } + }, + { + "chain": "7232298338574214290", + "start": "0+-0++++00-++00+-+0++--0----000++0++000+0+00-0++++-+-0++0++0-+0+-+-0-+0-+0---0-0+--+-00-+0000-0-000+0+0-+-00-+-+---0-000-++-0+++", + "expect": { + "state": "0+-+-++00+000+00+0++0-+++-+--0-0-0--0-00--+-00--0++00-+--00--00-000----+++-+-----0++++++00+00+-0-+++---+-00-0--++0-00--0-+00+0-0", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "23fe9bceacb57ee6" + } + }, + { + "chain": "3956032837431463", + "start": "-0-+-0-+-++0++++-00++++00+00+-+0++-00+0++0+-000++0+00---+++-+-++000--0+0-+0-0-++0-0-00--00++--+000+0+-0+0+--0---00+-0++-0--00-00", + "expect": { + "state": "0-+++00-0++-++0--0-+0+++0---0-+-0+-+-000-0+--0-0+--++---000-++-00+0--++++-+-++0-+-00++0+----+-+0-+++-++0000--+0+-++-0-00+00--0--", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "e79f1088b6456f1d" + } + }, + { + "chain": "14441196853976124320", + "start": "0+0-0+0--+0+-0-++--000++++00+--00-0+00+0--+-+0++-0--0-0+0+-0+00-+-000-+-++-+-+--+0+000-+--0--0+----+-+-0-+00--0+00+0+--000-0+--0", + "expect": { + "state": "+0++0+-+++0++0--0+--0+--+0++-00+-++--+0+++0-0+0-++0-+++--+--++00+-00++++0+++--+--0-0+-+0-0-00----++-00+0++00+-++-0+0+00+-000-0-+", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "42dd8b5e20d135f3" + } + }, + { + "chain": "12784314400181199172", + "start": "++000+0-+0-++++-0+-0-+-+-0-+---00+0-+000--+-00++0++++0----0-+-0--+++0+++-+-0-+++----+--+++-0-0-00+++0--0-000---++++---0++0-+-0+0", + "expect": { + "state": "----0-0-00-----+-00+-00+0++0+0000+-0--0++-+-++--+-+++--+-0-+++-0-00+-+-++-++0+0--+0--+0+0++00+00--++00+-00--+-0+00-++0++0--0+-++", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "0530e284b22c2984" + } + }, + { + "chain": "6763848438858133413", + "start": "-+-+--+-00-+--0++0--0+0+--+00++++-++00-0-+++++0-+0+-++0----00---+-00+++0---0-000++0+-00+00+-0-00-00+00-0+0++0-0+-+0-+--+00++-00-", + "expect": { + "state": "+0+-000++--+-+0+--+++00+0-+000+--0++-0-0++++-++00---+-0+0-0+--+-0+0---+--+++---++0-+000+0-+-0++---+00-+---0++00+0+++00+-0---0+--", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "b00d490a0b584636" + } + }, + { + "chain": "13252844091207777551", + "start": "--000+000-----0+-+-0+-0+0----000+-0+++00+0+--0-+++---0--+0-0+--0-++0-++0+00+0-+0+0+++0+--0+0+0-+-++--+00--0+0-0++--+0-+-0--+0-00", + "expect": { + "state": "0-0000---++0-0--0-0++-++0-----+0-0-0+--++0-0+-000+00-++0--+-+00--+-00--+-0-0++-00+0-+--+-++0+++000++-000-+-+00---+00-0--00+-++-+", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "27b47eedcd6e8937" + } + }, + { + "chain": "44078524726763985", + "start": "+0-+0+---0++++00+00-0-0-0-00-0-000--+00+00+0+0-+0+0+++0--+0+00-++-+0-00+++00+-++00----0-++--++00+-0++0++---0-0+0-+--+---0+000-0+", + "expect": { + "state": "0-++-+-+--0000+00-+-0-++++0+-++0+--00+++0+0+-0+++0000-0-+00-+++0++00-+0-+0-------0+-00---0+0-----0+0--0++0---+-0+0-0++00-0---+++", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "99c4c4d3bbada2f5" + } + }, + { + "chain": "14317284154589993100", + "start": "--++-0-----++000-0+0+0++00+0+0++-+--+00-0++---+00-000++-0++00-000++0---------+-++-++0++0-+0-+-00-0++0++-+++-0------+00+0++-0000+", + "expect": { + "state": "---++-00++--0+000--0+-+-00----000++-0+0+0--0+--+---0+-00-00+++---+-+--00-++0+0-+--00++-00+----++-0-+++0+-0+++0---+++---+---++0++", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "f76fb4448db02d53" + } + }, + { + "chain": "4818709863824023668", + "start": "0000+00-0-++-++00-+--0+0+--++-++---+0+00+-0+-0++-0++00+0--0+--00++00+---+0--0-+--+0+-++0++0-0+++0+0-++-+++0++-0--00--+-+0+-+--++", + "expect": { + "state": "00-+-0-0--0+0--0--++-0--00-0+00-0+0+--+0--+-++-0+0++-0+0-++++-0-0-00-00++00+000-+0-000+0--0-+-+-+0--+0----++-+0-00---+00--++0-00", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "016ca73f67b5dbc4" + } + }, + { + "chain": "17017293576556084989", + "start": "0-00-0+---0+--00++-+0-0-0-0-++-++++---0-0++00-+00+0-0+--0-0--0--000--+0+-+++++++++-00+-+0+0-0---+++0++00-+0+-+-00---00+---0+--+0", + "expect": { + "state": "+-+0-+0+00+0-0+---+++-+-++00-+--0+0-+0-+-++-+00+---0++-+---0+-0-++-+-+-+0++---00-0-+---0-+-++++-+--+0+-0+00+0000++-+0000+-0+0--+", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "2cc361d9118e8464" + } + }, + { + "chain": "12681349783656822995", + "start": "-0+0--0---0+--0+0++0--0000+--00+-+00-0-0-+0+---+0-+00++00-+000---0--+--+-+---0-++0++000+--+---0--+--00-----++-0-++-0+0++--++--00", + "expect": { + "state": "-00--0---+0-00+000+-0+--++0+0+---0+--++---0-+00+++---00---++0--+000-0+--++++0-0+-+-00+-0-+-+-0-++-0000++-00+-++-0+-0++++-0+-00+-", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "a5fc9bf78de3a321" + } + }, + { + "chain": "15602640267756040389", + "start": "0+00---0+0+-+0-0000++-0+0---++-+-+0-+-+00+000-0-0---00++-++0000+--000++00---0000-0++0+0+0++0-+0++00+0+-+00-++0+000++00-0-+00+00+", + "expect": { + "state": "-+0++---0+-+++-0-+-0+---+-0-0-+00+0-00-++0-0-++-+0+--+00-++000+00+0000-00----00+0++000-+--0+0--0+-0+-0++0---0+++0+-+0+0-0-0+-000", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "7d22161db1418b21" + } + }, + { + "chain": "3783348553075578567", + "start": "++0++0+0+0++++-0++-+-0-+++0-+--+00--000+---+-0++-+-0--+--0+--+00--+000-0+-0-00-++0+-00-+0000+00+-+-++0-++0+0--0-0-++0+000+-0--+-", + "expect": { + "state": "-++0-+--+++-+0-+-00++--00-+--++0+--++-+0--00+--+0-+0-00---+--00-0-0-0-+-+0+-+0+000--+++0-+0--0-++-+-0++00+-++-0-00--+0-0++---+0+", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "918fcb6a48f8df4f" + } + }, + { + "chain": "17943367732143867884", + "start": "00++0-0-0+0+0-00-000+0-+0+-0-+00-0-+0+-0--0--++-00+-+-+0-0+0+0+-+++--0+---0++0-0++0++-00+-++---+0+0+0-+++0-+0+++00-00--++-+-00-+", + "expect": { + "state": "-++++0--+-0++--0++-0-+-+0++0+-0+0000++--+-++0-0+-0+00+-0++++---+00--+++-0+-0+-+-+++-0++0+00+-0+-+-0+---0++0-+++-++--0+-0+++0--00", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "09e31c519317ab3f" + } + }, + { + "chain": "9521134827581757141", + "start": "-+-+0-0-+0-+-0++-0+-0+000+++00-+-0-+0+-0+0++0+0-+----+--+-++0---0---++-+--00-+0-00000-0-00000-+-0+0--+0-+000-+++-++-0++--+++++--", + "expect": { + "state": "0-+--+0-0+-0--0+000000-00+++0+000-0--0----000-+0+++0+0+-++0--0--0--+00+--0+-+++--+000000--+0+-+-0-0----++0-+00-0--0-+00+000---+0", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "946472afe6d7d450" + } + }, + { + "chain": "2868902296044425396", + "start": "++---++-0++000--0-0---0+0-0-0000++-+-0+++-00-0+--0+0++0-0-0+00--+-0000-+0-000+0+0------++-++----0-+00+-+0-0++00-0+0--0-0++0+0+-0", + "expect": { + "state": "++--0++0+0000--0-00+---00--+0-0+00000-0++0+-0-00-+0+0000--0-0+-0-+00-+---0+0-++0+0-00--+--00--0000+0+--+++-0-+-0+++000-+----0+++", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "af98befb54f4da13" + } + }, + { + "chain": "10685194766232772244", + "start": "+0-++++-0+0-00++++-0-+-++0+-++-+--0----00+-+0+00-0++0-0+000-+--+0+--+++0-0+-+0-00++00-+++0++-00+0-----+0-+0-00-00-+0++000+-+0+--", + "expect": { + "state": "00+-+0--0+-0++--00+++000+0-+0++-0+++00--+++++00+00-0000-+0+-----0++0--00++---------0-0+--++++-00+++000+0+++++0++-00+0+0++-------", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "c95b132979247dd4" + } + }, + { + "chain": "15881697996574989635", + "start": "+0+++0++00++00-++++0+0-00-00-0++0--0+000-0------+-000---+0+-+-00--0-0-+0--++00+000--+++++++-00-0-00++0+000--0+00++---+--+-++0--+", + "expect": { + "state": "000--0+--++-+-0---+++++++---++0--++-+++0++0-00+00--000000+-00+-0-00-+0+000++-0-+0+++0000+-00000--0-0++0++----++---0-+-+0-+--++++", + "accumulates": 0, + "draws": 128, + "term": "bound", + "state_hash": "3487c7e16685162e" + } + } + ], + "held": [ + { + "temperature": 1.0, + "temperature_bits": "3ff0000000000000", + "sweeps": 1 + } + ] + }, + { + "name": "more-chains-than-a-workgroup", + "landscape": { + "n": 5, + "couplings": [ + [ + 0, + 1, + 1 + ], + [ + 1, + 2, + -1 + ], + [ + 2, + 3, + 1 + ], + [ + 3, + 4, + -1 + ] + ], + "field": [ + 40, + 0, + -17, + 0, + 9 + ], + "cost": 8 + }, + "field_bound": 41, + "seed": "7", + "chains": [ + { + "chain": "7191089600892374487", + "start": "++0+-", + "expect": { + "state": "+0--+", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "5e1f6c25c99504ea" + } + }, + { + "chain": "309689372594955804", + "start": "-+0-0", + "expect": { + "state": "++-+0", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "3e08a13ecc306f32" + } + }, + { + "chain": "16616101746815609346", + "start": "+00+-", + "expect": { + "state": "++-+0", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "3e08a13ecc306f32" + } + }, + { + "chain": "10753165928301472203", + "start": "0+++0", + "expect": { + "state": "+00+-", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "66c21925ce7739dd" + } + }, + { + "chain": "8346079845500723674", + "start": "++-0+", + "expect": { + "state": "++0-+", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "3565f53ec74e3bf2" + } + }, + { + "chain": "4601199455465548305", + "start": "0+-+-", + "expect": { + "state": "+0--0", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "5e1f6b25c9950337" + } + }, + { + "chain": "8632209307422871798", + "start": "+-00+", + "expect": { + "state": "++000", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "3569583ec7511a02" + } + }, + { + "chain": "6051947643683389182", + "start": "+0+-0", + "expect": { + "state": "++-+0", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "3e08a13ecc306f32" + } + }, + { + "chain": "2476628477891077985", + "start": "---00", + "expect": { + "state": "++-00", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "3e0c093ecc3355c1" + } + }, + { + "chain": "7621113624420504425", + "start": "++0-0", + "expect": { + "state": "++--+", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "3e0f703ecc363a9d" + } + }, + { + "chain": "1910343844960271083", + "start": "-++0-", + "expect": { + "state": "++-00", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "3e0c093ecc3355c1" + } + }, + { + "chain": "17706551433532105516", + "start": "0+0-0", + "expect": { + "state": "++--+", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "3e0f703ecc363a9d" + } + }, + { + "chain": "16934472341843718990", + "start": "--0++", + "expect": { + "state": "000+-", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "6f57d1b6f9247680" + } + }, + { + "chain": "16073233977741239344", + "start": "0-0-0", + "expect": { + "state": "+0-++", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "5e263425c99ac470" + } + }, + { + "chain": "15938128224054089190", + "start": "+0+-0", + "expect": { + "state": "++--+", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "3e0f703ecc363a9d" + } + }, + { + "chain": "10114117652854834680", + "start": "0--0+", + "expect": { + "state": "+-00+", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "f574032e1ae5a5f9" + } + }, + { + "chain": "16226008763869681327", + "start": "00000", + "expect": { + "state": "+0++0", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "6f6b9525d35f3a3b" + } + }, + { + "chain": "6020303405324641991", + "start": "-0-00", + "expect": { + "state": "+-0++", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "f569d12e1adcfc7e" + } + }, + { + "chain": "11420759280519519797", + "start": "+-+0+", + "expect": { + "state": "0+000", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "20a8dabf592d1543" + } + }, + { + "chain": "13970124788236171000", + "start": "0+00-", + "expect": { + "state": "0000+", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "6f6205b6f92d2361" + } + }, + { + "chain": "12443559672962333743", + "start": "0-0++", + "expect": { + "state": "++-0+", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "3e0c063ecc3350a8" + } + }, + { + "chain": "1968162948761465549", + "start": "-0+-0", + "expect": { + "state": "+000+", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "66c57d25ce7a19a0" + } + }, + { + "chain": "6353848000608806813", + "start": "0+--0", + "expect": { + "state": "+0-+0", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "5e263725c99ac989" + } + }, + { + "chain": "7817223230648258815", + "start": "0+0--", + "expect": { + "state": "0--++", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "d81436aea7e241d8" + } + }, + { + "chain": "16648918177313050160", + "start": "-+++0", + "expect": { + "state": "+-+-+", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "ecc7222e15fac425" + } + }, + { + "chain": "17714943439140796905", + "start": "++0++", + "expect": { + "state": "+0-++", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "5e263425c99ac470" + } + }, + { + "chain": "1414519515228510906", + "start": "-0000", + "expect": { + "state": "+0-0+", + "accumulates": 24, + "draws": 15, + "term": "fixed-point", + "state_hash": "5e22ce25c997e147" + } + }, + { + "chain": "7508637138623878239", + "start": "++0+-", + "expect": { + "state": "+++0+", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "4f5f003ed60354ca" + } + }, + { + "chain": "16636106807449963335", + "start": "+--00", + "expect": { + "state": "++-++", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "3e08a03ecc306d7f" + } + }, + { + "chain": "7655978328340571165", + "start": "0--0+", + "expect": { + "state": "00-0+", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "7804b4b6fe0f5bba" + } + }, + { + "chain": "17914293243756186232", + "start": "++-+-", + "expect": { + "state": "++---", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "3e0f6e3ecc363737" + } + }, + { + "chain": "1000556317519771820", + "start": "0+-0+", + "expect": { + "state": "00-++", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "780ee6b6fe180535" + } + }, + { + "chain": "7068487416905635272", + "start": "++-+0", + "expect": { + "state": "+000+", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "66c57d25ce7a19a0" + } + }, + { + "chain": "5212851896957457618", + "start": "----+", + "expect": { + "state": "+0---", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "5e1f6a25c9950184" + } + }, + { + "chain": "10283542806791360001", + "start": "0-+00", + "expect": { + "state": "++00+", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "3569573ec751184f" + } + }, + { + "chain": "11192945628045636651", + "start": "0++--", + "expect": { + "state": "+--00", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "fe16b12e1fc7dc9f" + } + }, + { + "chain": "1397226789297177968", + "start": "0--0+", + "expect": { + "state": "++000", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "3569583ec7511a02" + } + }, + { + "chain": "17251110125614244469", + "start": "-00+0", + "expect": { + "state": "+0-00", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "5e22cf25c997e2fa" + } + }, + { + "chain": "3882525348760934407", + "start": "+00--", + "expect": { + "state": "++-00", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "3e0c093ecc3355c1" + } + }, + { + "chain": "3246376524827696731", + "start": "0++--", + "expect": { + "state": "00-0+", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "7804b4b6fe0f5bba" + } + }, + { + "chain": "11877382311623396833", + "start": "+0--+", + "expect": { + "state": "+00--", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "66c8e525ce7d002f" + } + }, + { + "chain": "16967882976242524105", + "start": "00--+", + "expect": { + "state": "+---+", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "fe1a142e1fcabaaf" + } + }, + { + "chain": "7047080995384433728", + "start": "-+00-", + "expect": { + "state": "++00+", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "3569573ec751184f" + } + }, + { + "chain": "1078412720614623980", + "start": "+++0+", + "expect": { + "state": "+0--+", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "5e1f6c25c99504ea" + } + }, + { + "chain": "740264374230832002", + "start": "+-+++", + "expect": { + "state": "+0-0+", + "accumulates": 24, + "draws": 15, + "term": "fixed-point", + "state_hash": "5e22ce25c997e147" + } + }, + { + "chain": "5894705141355004150", + "start": "++00-", + "expect": { + "state": "0+-00", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "180629bf544ad984" + } + }, + { + "chain": "9803848125589857807", + "start": "-0-++", + "expect": { + "state": "+0++0", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "6f6b9525d35f3a3b" + } + }, + { + "chain": "15272132712961327230", + "start": "+0--0", + "expect": { + "state": "+0000", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "66c58025ce7a1eb9" + } + }, + { + "chain": "15593953646079023958", + "start": "-00--", + "expect": { + "state": "+0-++", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "5e263425c99ac470" + } + }, + { + "chain": "11876575118127461096", + "start": "0000-", + "expect": { + "state": "+0-+0", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "5e263725c99ac989" + } + }, + { + "chain": "12369902262100240266", + "start": "--+++", + "expect": { + "state": "+0--0", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "5e1f6b25c9950337" + } + }, + { + "chain": "6669996092984356130", + "start": "-0--+", + "expect": { + "state": "+0-0-", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "5e22d025c997e4ad" + } + }, + { + "chain": "1195840149651824978", + "start": "+0-0+", + "expect": { + "state": "+-000", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "f574002e1ae5a0e0" + } + }, + { + "chain": "4658812758431757542", + "start": "-++00", + "expect": { + "state": "++-0+", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "3e0c063ecc3350a8" + } + }, + { + "chain": "10579607602651382059", + "start": "0+0++", + "expect": { + "state": "+-0+-", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "f569cf2e1adcf918" + } + }, + { + "chain": "4097608408350706963", + "start": "0-0+0", + "expect": { + "state": "+0-++", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "5e263425c99ac470" + } + }, + { + "chain": "9197939099258079246", + "start": "-+++-", + "expect": { + "state": "++-0+", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "3e0c063ecc3350a8" + } + }, + { + "chain": "6192541766920947367", + "start": "---0-", + "expect": { + "state": "++-0-", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "3e0c083ecc33540e" + } + }, + { + "chain": "10525442303063760554", + "start": "+++0-", + "expect": { + "state": "+-000", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "f574002e1ae5a0e0" + } + }, + { + "chain": "11368285850004288834", + "start": "0-0++", + "expect": { + "state": "++-0-", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "3e0c083ecc33540e" + } + }, + { + "chain": "6552326866483609062", + "start": "+0+++", + "expect": { + "state": "+0-0+", + "accumulates": 24, + "draws": 15, + "term": "fixed-point", + "state_hash": "5e22ce25c997e147" + } + }, + { + "chain": "6836129103706797786", + "start": "00+0-", + "expect": { + "state": "+0000", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "66c58025ce7a1eb9" + } + }, + { + "chain": "16126564460731059850", + "start": "-+++-", + "expect": { + "state": "+0-0-", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "5e22d025c997e4ad" + } + }, + { + "chain": "7407618199125714360", + "start": "00-+0", + "expect": { + "state": "++-++", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "3e08a03ecc306d7f" + } + }, + { + "chain": "8548921452456689817", + "start": "-+++-", + "expect": { + "state": "0+--+", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "1802c2bf5447f4a8" + } + }, + { + "chain": "6609238898917001067", + "start": "00+--", + "expect": { + "state": "++--+", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "3e0f703ecc363a9d" + } + }, + { + "chain": "14696359123380530193", + "start": "+-++-", + "expect": { + "state": "++0+-", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "356cbf3ec753fede" + } + }, + { + "chain": "14349261631190404931", + "start": "0+-0-", + "expect": { + "state": "+-++0", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "ecc0532e15f4f8ba" + } + }, + { + "chain": "18169484529148634047", + "start": "-0--+", + "expect": { + "state": "+--0+", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "fe16b22e1fc7de52" + } + }, + { + "chain": "17599774830844088246", + "start": "0+++-", + "expect": { + "state": "+0-0+", + "accumulates": 24, + "draws": 15, + "term": "fixed-point", + "state_hash": "5e22ce25c997e147" + } + }, + { + "chain": "6717592762715772622", + "start": "0+-00", + "expect": { + "state": "+-000", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "f574002e1ae5a0e0" + } + }, + { + "chain": "462069237369365477", + "start": "++-+0", + "expect": { + "state": "+0000", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "66c58025ce7a1eb9" + } + }, + { + "chain": "9722696506342463596", + "start": "-00-+", + "expect": { + "state": "+00++", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "66c21725ce773677" + } + }, + { + "chain": "8802026256938777957", + "start": "++-0-", + "expect": { + "state": "+--++", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "fe20e42e1fd087cd" + } + }, + { + "chain": "12090244088508166212", + "start": "++0+0", + "expect": { + "state": "+--0+", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "fe16b22e1fc7de52" + } + }, + { + "chain": "17344683474179107697", + "start": "-00-+", + "expect": { + "state": "+0-0-", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "5e22d025c997e4ad" + } + }, + { + "chain": "4072649400181965378", + "start": "++0-0", + "expect": { + "state": "+-0++", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "f569d12e1adcfc7e" + } + }, + { + "chain": "6599697964279942024", + "start": "+0--+", + "expect": { + "state": "+00-0", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "66c8e425ce7cfe7c" + } + }, + { + "chain": "9747999593140079188", + "start": "-+0+-", + "expect": { + "state": "+0--+", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "5e1f6c25c99504ea" + } + }, + { + "chain": "13067365067347293291", + "start": "--+-0", + "expect": { + "state": "+0-0+", + "accumulates": 24, + "draws": 15, + "term": "fixed-point", + "state_hash": "5e22ce25c997e147" + } + }, + { + "chain": "11469559047602927891", + "start": "-0-0-", + "expect": { + "state": "+---+", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "fe1a142e1fcabaaf" + } + }, + { + "chain": "5800357193373371334", + "start": "-+-00", + "expect": { + "state": "+--+-", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "fe20e22e1fd08467" + } + }, + { + "chain": "14188302174690699311", + "start": "+----", + "expect": { + "state": "++--0", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "3e0f6d3ecc363584" + } + }, + { + "chain": "6018638183136409814", + "start": "-++00", + "expect": { + "state": "+0-0+", + "accumulates": 24, + "draws": 15, + "term": "fixed-point", + "state_hash": "5e22ce25c997e147" + } + }, + { + "chain": "265820088835577910", + "start": "+-++-", + "expect": { + "state": "+-0++", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "f569d12e1adcfc7e" + } + }, + { + "chain": "2944505714425472303", + "start": "+++++", + "expect": { + "state": "+++0+", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "4f5f003ed60354ca" + } + }, + { + "chain": "11801280095000948982", + "start": "++-+-", + "expect": { + "state": "+-+0-", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "ecc3ba2e15f7dd96" + } + }, + { + "chain": "5422033091938086637", + "start": "+0000", + "expect": { + "state": "+0--+", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "5e1f6c25c99504ea" + } + }, + { + "chain": "15353075157857049030", + "start": "+----", + "expect": { + "state": "++00+", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "3569573ec751184f" + } + }, + { + "chain": "4677395499658674701", + "start": "-000+", + "expect": { + "state": "+0--+", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "5e1f6c25c99504ea" + } + }, + { + "chain": "17609116314125081960", + "start": "0-+00", + "expect": { + "state": "+00-+", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "66c8e725ce7d0395" + } + }, + { + "chain": "1234638223158467977", + "start": "-++00", + "expect": { + "state": "++-00", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "3e0c093ecc3355c1" + } + }, + { + "chain": "8145197266383741513", + "start": "-00+-", + "expect": { + "state": "+----", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "fe1a162e1fcabe15" + } + }, + { + "chain": "17658512780620837010", + "start": "0-0+-", + "expect": { + "state": "++-0+", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "3e0c063ecc3350a8" + } + }, + { + "chain": "12831733888665064643", + "start": "++++0", + "expect": { + "state": "++-++", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "3e08a03ecc306d7f" + } + }, + { + "chain": "10527209363850832854", + "start": "-0---", + "expect": { + "state": "+00-+", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "66c8e725ce7d0395" + } + }, + { + "chain": "1756148621030821548", + "start": "00-00", + "expect": { + "state": "+000-", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "66c57f25ce7a1d06" + } + }, + { + "chain": "16418894510351553834", + "start": "0-+-0", + "expect": { + "state": "+0-00", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "5e22cf25c997e2fa" + } + }, + { + "chain": "18436886863753955888", + "start": "-0+-+", + "expect": { + "state": "+000+", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "66c57d25ce7a19a0" + } + }, + { + "chain": "17507770786031350393", + "start": "0-+--", + "expect": { + "state": "+0-++", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "5e263425c99ac470" + } + }, + { + "chain": "14658102248709227083", + "start": "0--+0", + "expect": { + "state": "+0---", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "5e1f6a25c9950184" + } + }, + { + "chain": "1029357966365906655", + "start": "0-0+0", + "expect": { + "state": "+--++", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "fe20e42e1fd087cd" + } + }, + { + "chain": "2439607561507303284", + "start": "--0+-", + "expect": { + "state": "++---", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "3e0f6e3ecc363737" + } + }, + { + "chain": "6121728340198765717", + "start": "+-0+0", + "expect": { + "state": "+--0+", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "fe16b22e1fc7de52" + } + }, + { + "chain": "17791449276630106578", + "start": "0--+0", + "expect": { + "state": "+0--0", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "5e1f6b25c9950337" + } + }, + { + "chain": "13092322672875254760", + "start": "++0--", + "expect": { + "state": "+-00+", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "f574032e1ae5a5f9" + } + }, + { + "chain": "11892696917623250872", + "start": "0+--+", + "expect": { + "state": "+0-0+", + "accumulates": 24, + "draws": 15, + "term": "fixed-point", + "state_hash": "5e22ce25c997e147" + } + }, + { + "chain": "15274026912512802149", + "start": "0++--", + "expect": { + "state": "++-++", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "3e08a03ecc306d7f" + } + }, + { + "chain": "13779241446633394306", + "start": "-+000", + "expect": { + "state": "+--+0", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "fe20e12e1fd082b4" + } + }, + { + "chain": "18063519880123341728", + "start": "-0-00", + "expect": { + "state": "++--0", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "3e0f6d3ecc363584" + } + }, + { + "chain": "12205045038081506662", + "start": "+0+0+", + "expect": { + "state": "++00-", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "3569593ec7511bb5" + } + }, + { + "chain": "3866426887687620450", + "start": "000++", + "expect": { + "state": "+00-+", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "66c8e725ce7d0395" + } + }, + { + "chain": "9447970065020527112", + "start": "++00+", + "expect": { + "state": "+--++", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "fe20e42e1fd087cd" + } + }, + { + "chain": "11867102854153806985", + "start": "00-+0", + "expect": { + "state": "+00++", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "66c21725ce773677" + } + }, + { + "chain": "17226879785997612789", + "start": "+-00-", + "expect": { + "state": "+000+", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "66c57d25ce7a19a0" + } + }, + { + "chain": "17607962638738138599", + "start": "-000-", + "expect": { + "state": "0+---", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "1802c4bf5447f80e" + } + }, + { + "chain": "7312513595315071943", + "start": "0+-+0", + "expect": { + "state": "+00++", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "66c21725ce773677" + } + }, + { + "chain": "18108705507020998984", + "start": "-0000", + "expect": { + "state": "+---+", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "fe1a142e1fcabaaf" + } + }, + { + "chain": "6737799053170374271", + "start": "--000", + "expect": { + "state": "0-0+-", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "e0b01baeacbeb745" + } + }, + { + "chain": "6270771758972338627", + "start": "--0+0", + "expect": { + "state": "++--+", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "3e0f703ecc363a9d" + } + }, + { + "chain": "10085279469469406620", + "start": "-0++-", + "expect": { + "state": "+---+", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "fe1a142e1fcabaaf" + } + }, + { + "chain": "11321943175626252643", + "start": "-+-00", + "expect": { + "state": "+0--0", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "5e1f6b25c9950337" + } + }, + { + "chain": "3844052134576708850", + "start": "00+0-", + "expect": { + "state": "+00+0", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "66c21825ce77382a" + } + }, + { + "chain": "11643247792660730917", + "start": "+--++", + "expect": { + "state": "+--00", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "fe16b12e1fc7dc9f" + } + }, + { + "chain": "11388625514558433436", + "start": "-+0++", + "expect": { + "state": "+0-0+", + "accumulates": 24, + "draws": 15, + "term": "fixed-point", + "state_hash": "5e22ce25c997e147" + } + }, + { + "chain": "3142648068194189331", + "start": "+-+0-", + "expect": { + "state": "+0--+", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "5e1f6c25c99504ea" + } + }, + { + "chain": "16801926558693758238", + "start": "00-+0", + "expect": { + "state": "+000+", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "66c57d25ce7a19a0" + } + }, + { + "chain": "11697068266323246350", + "start": "--+00", + "expect": { + "state": "+0--0", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "5e1f6b25c9950337" + } + }, + { + "chain": "10767612947977357250", + "start": "-00+-", + "expect": { + "state": "+0--+", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "5e1f6c25c99504ea" + } + }, + { + "chain": "7213761060293429927", + "start": "0-0-+", + "expect": { + "state": "+0-00", + "accumulates": 24, + "draws": 15, + "term": "bound", + "state_hash": "5e22cf25c997e2fa" + } + } + ], + "held": [ + { + "temperature": 13.7, + "temperature_bits": "402b666666666666", + "sweeps": 3 + } + ] + }, + { + "name": "the-order-of-the-cells", + "landscape": { + "n": 8, + "couplings": [ + [ + 0, + 1, + -1 + ], + [ + 0, + 2, + -1 + ], + [ + 1, + 2, + -1 + ], + [ + 3, + 4, + 1 + ], + [ + 3, + 5, + 1 + ], + [ + 3, + 6, + -1 + ], + [ + 4, + 5, + 1 + ], + [ + 5, + 6, + -1 + ], + [ + 6, + 7, + 1 + ] + ], + "field": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "cost": 1 + }, + "field_bound": 3, + "seed": "8", + "chains": [ + { + "chain": "11409396526365357622", + "start": "0----0+-", + "expect": { + "state": "-0+00000", + "accumulates": 36, + "draws": 16, + "term": "bound", + "state_hash": "9fa301642661a57f" + } + }, + { + "chain": "11288449918072354817", + "start": "00+---++", + "expect": { + "state": "-00-0-+0", + "accumulates": 36, + "draws": 16, + "term": "bound", + "state_hash": "200a69d26dfcabf3" + } + }, + { + "chain": "12710348155395669505", + "start": "00-++-++", + "expect": { + "state": "000---00", + "accumulates": 36, + "draws": 16, + "term": "bound", + "state_hash": "728cad80810fddbc" + } + }, + { + "chain": "9889543983210544564", + "start": "-+++--0+", + "expect": { + "state": "+00---+0", + "accumulates": 36, + "draws": 16, + "term": "bound", + "state_hash": "fe0839ecda1c8604" + } + }, + { + "chain": "1177231695481881802", + "start": "+0+0++0-", + "expect": { + "state": "+-0+++--", + "accumulates": 36, + "draws": 16, + "term": "bound", + "state_hash": "1f6bbdccf803aed4" + } + }, + { + "chain": "6915105739825135632", + "start": "-+0+-+-0", + "expect": { + "state": "0000--+0", + "accumulates": 36, + "draws": 16, + "term": "bound", + "state_hash": "7f3ef6aafe622b48" + } + }, + { + "chain": "17600687008453613099", + "start": "0-+0+-++", + "expect": { + "state": "-0+---00", + "accumulates": 36, + "draws": 16, + "term": "bound", + "state_hash": "2a4c1941fa65bbee" + } + }, + { + "chain": "6584036627333980314", + "start": "+--000-+", + "expect": { + "state": "+0++++-0", + "accumulates": 36, + "draws": 16, + "term": "bound", + "state_hash": "efdc6fdb70053057" + } + }, + { + "chain": "1477866036430354435", + "start": "++0--00+", + "expect": { + "state": "-+0-0++0", + "accumulates": 36, + "draws": 16, + "term": "bound", + "state_hash": "f2e109f2f68c4c5c" + } + }, + { + "chain": "8057604280536758380", + "start": "0-0+--0+", + "expect": { + "state": "--0--0+0", + "accumulates": 36, + "draws": 16, + "term": "bound", + "state_hash": "d4d441ff3f0aaa32" + } + }, + { + "chain": "11549040031240728435", + "start": "-0-0+-++", + "expect": { + "state": "-++0+0+0", + "accumulates": 36, + "draws": 16, + "term": "bound", + "state_hash": "20f4ab7c83b65abe" + } + }, + { + "chain": "13634498620288515940", + "start": "0-+-0-0-", + "expect": { + "state": "-0+-0-++", + "accumulates": 36, + "draws": 16, + "term": "bound", + "state_hash": "ca4a2e4a509e60c1" + } + }, + { + "chain": "8137595979192764205", + "start": "000-++0-", + "expect": { + "state": "0-0000--", + "accumulates": 36, + "draws": 16, + "term": "bound", + "state_hash": "f105e339df1b0960" + } + }, + { + "chain": "3600118290926418534", + "start": "0--+-0-+", + "expect": { + "state": "+00+++--", + "accumulates": 36, + "draws": 16, + "term": "bound", + "state_hash": "935b5a87293c7627" + } + }, + { + "chain": "12618063654782812134", + "start": "0---0+0-", + "expect": { + "state": "+0-0000-", + "accumulates": 36, + "draws": 16, + "term": "bound", + "state_hash": "96c556dc1823c480" + } + }, + { + "chain": "10800321000548654100", + "start": "0+000-+-", + "expect": { + "state": "0---0-0+", + "accumulates": 36, + "draws": 16, + "term": "bound", + "state_hash": "29c47931735dcc72" + } + } + ], + "held": [ + { + "temperature": 1.5, + "temperature_bits": "3ff8000000000000", + "sweeps": 2 + } + ] + }, + { + "name": "windows-by-their-table", + "landscape": { + "n": 64, + "couplings": [ + [ + 0, + 1, + 1 + ], + [ + 0, + 63, + 1 + ], + [ + 1, + 2, + 1 + ], + [ + 2, + 3, + 1 + ], + [ + 3, + 4, + 1 + ], + [ + 4, + 5, + 1 + ], + [ + 5, + 6, + 1 + ], + [ + 6, + 7, + 1 + ], + [ + 7, + 8, + 1 + ], + [ + 8, + 9, + 1 + ], + [ + 9, + 10, + 1 + ], + [ + 10, + 11, + 1 + ], + [ + 11, + 12, + 1 + ], + [ + 12, + 13, + 1 + ], + [ + 13, + 14, + 1 + ], + [ + 14, + 15, + 1 + ], + [ + 15, + 16, + 1 + ], + [ + 16, + 17, + 1 + ], + [ + 17, + 18, + 1 + ], + [ + 18, + 19, + 1 + ], + [ + 19, + 20, + 1 + ], + [ + 20, + 21, + 1 + ], + [ + 21, + 22, + 1 + ], + [ + 22, + 23, + 1 + ], + [ + 23, + 24, + 1 + ], + [ + 24, + 25, + 1 + ], + [ + 25, + 26, + 1 + ], + [ + 26, + 27, + 1 + ], + [ + 27, + 28, + 1 + ], + [ + 28, + 29, + 1 + ], + [ + 29, + 30, + 1 + ], + [ + 30, + 31, + 1 + ], + [ + 31, + 32, + 1 + ], + [ + 32, + 33, + 1 + ], + [ + 33, + 34, + 1 + ], + [ + 34, + 35, + 1 + ], + [ + 35, + 36, + 1 + ], + [ + 36, + 37, + 1 + ], + [ + 37, + 38, + 1 + ], + [ + 38, + 39, + 1 + ], + [ + 39, + 40, + 1 + ], + [ + 40, + 41, + 1 + ], + [ + 41, + 42, + 1 + ], + [ + 42, + 43, + 1 + ], + [ + 43, + 44, + 1 + ], + [ + 44, + 45, + 1 + ], + [ + 45, + 46, + 1 + ], + [ + 46, + 47, + 1 + ], + [ + 47, + 48, + 1 + ], + [ + 48, + 49, + 1 + ], + [ + 49, + 50, + 1 + ], + [ + 50, + 51, + 1 + ], + [ + 51, + 52, + 1 + ], + [ + 52, + 53, + 1 + ], + [ + 53, + 54, + 1 + ], + [ + 54, + 55, + 1 + ], + [ + 55, + 56, + 1 + ], + [ + 56, + 57, + 1 + ], + [ + 57, + 58, + 1 + ], + [ + 58, + 59, + 1 + ], + [ + 59, + 60, + 1 + ], + [ + 60, + 61, + 1 + ], + [ + 61, + 62, + 1 + ], + [ + 62, + 63, + 1 + ] + ], + "field": [ + -224, + -217, + -210, + -203, + -196, + -189, + -182, + -175, + -168, + -161, + -154, + -147, + -140, + -133, + -126, + -119, + -112, + -105, + -98, + -91, + -84, + -77, + -70, + -63, + -56, + -49, + -42, + -35, + -28, + -21, + -14, + -7, + 0, + 7, + 14, + 21, + 28, + 35, + 42, + 49, + 56, + 63, + 70, + 77, + 84, + 91, + 98, + 105, + 112, + 119, + 126, + 133, + 140, + 147, + 154, + 161, + 168, + 175, + 182, + 189, + 196, + 203, + 210, + 217 + ], + "cost": 0 + }, + "field_bound": 226, + "seed": "9", + "chains": [ + { + "chain": "12587370737594032228", + "start": "0+000-000+--0+00---00+0-++--+00++--00--0--0000-++0++000-0+++---+", + "expect": { + "state": "0-------000---0---+-+--00+-0-++0++00++0++++00-0++0-++0+0++0+++-0", + "accumulates": 25856, + "draws": 12800, + "term": "bound", + "state_hash": "eef1bcbaea2cc3db" + } + }, + { + "chain": "13847876567842155106", + "start": "++0--00+-0++0-0-+++0-0-+--++0-+-+---+-++---00+-0+0-++-+-+-00+--+", + "expect": { + "state": "-0------0000-0-00++--00-00+-0+-+--++-+++00000+000000-+++++++++++", + "accumulates": 25856, + "draws": 12800, + "term": "bound", + "state_hash": "cc5b5e96f1a735c1" + } + }, + { + "chain": "4894335158745139638", + "start": "00+000++++--+++-0-++-+00-0++-+0++-++0-+0--0+000-0--++-0++-+00+++", + "expect": { + "state": "000-0+-0-----0--+---0+-000--00--000-+0++-0+-++++++++0-++++0+++++", + "accumulates": 25856, + "draws": 12800, + "term": "bound", + "state_hash": "5581962942d884a6" + } + }, + { + "chain": "14477257330446655584", + "start": "0+++0+0+-+0+-0---0+0-+-00+++00-++0+00+--++-++-00-++--++--00+0-00", + "expect": { + "state": "------0--0+-00--+-+0---0+-0+0-0+00---0-+0-++-0-0++-++-0++++0+++0", + "accumulates": 25856, + "draws": 12800, + "term": "bound", + "state_hash": "cb1752aa3e30c0d9" + } + } + ], + "held": [ + { + "temperature": 400.0, + "temperature_bits": "4079000000000000", + "sweeps": 70 + }, + { + "temperature": 0.0, + "temperature_bits": "0000000000000000", + "sweeps": 2 + }, + { + "temperature": 250.0, + "temperature_bits": "406f400000000000", + "sweeps": 70 + }, + { + "temperature": 150.0, + "temperature_bits": "4062c00000000000", + "sweeps": 60 + } + ] + }, + { + "name": "windows-by-their-work", + "landscape": { + "n": 1024, + "couplings": [ + [ + 0, + 19, + 1 + ], + [ + 0, + 440, + 1 + ], + [ + 0, + 1007, + -1 + ], + [ + 1, + 433, + 1 + ], + [ + 1, + 535, + 1 + ], + [ + 1, + 699, + 1 + ], + [ + 1, + 740, + 1 + ], + [ + 1, + 984, + -1 + ], + [ + 2, + 7, + -1 + ], + [ + 2, + 96, + -1 + ], + [ + 2, + 216, + 1 + ], + [ + 2, + 397, + -1 + ], + [ + 2, + 566, + -1 + ], + [ + 2, + 610, + -1 + ], + [ + 2, + 717, + 1 + ], + [ + 3, + 50, + 1 + ], + [ + 3, + 53, + -1 + ], + [ + 3, + 99, + 1 + ], + [ + 3, + 437, + 1 + ], + [ + 4, + 88, + 1 + ], + [ + 4, + 224, + 1 + ], + [ + 4, + 620, + -1 + ], + [ + 5, + 573, + -1 + ], + [ + 5, + 857, + -1 + ], + [ + 5, + 953, + -1 + ], + [ + 6, + 559, + 1 + ], + [ + 6, + 903, + 1 + ], + [ + 6, + 920, + 1 + ], + [ + 6, + 1020, + -1 + ], + [ + 7, + 76, + -1 + ], + [ + 7, + 267, + -1 + ], + [ + 7, + 492, + -1 + ], + [ + 7, + 831, + -1 + ], + [ + 8, + 382, + -1 + ], + [ + 8, + 655, + -1 + ], + [ + 8, + 764, + -1 + ], + [ + 9, + 115, + -1 + ], + [ + 9, + 179, + 1 + ], + [ + 9, + 459, + 1 + ], + [ + 9, + 537, + -1 + ], + [ + 9, + 571, + 1 + ], + [ + 9, + 885, + 1 + ], + [ + 10, + 44, + 1 + ], + [ + 10, + 316, + -1 + ], + [ + 10, + 773, + 1 + ], + [ + 10, + 812, + -1 + ], + [ + 10, + 918, + 1 + ], + [ + 10, + 972, + 1 + ], + [ + 11, + 227, + 1 + ], + [ + 11, + 811, + -1 + ], + [ + 12, + 223, + 1 + ], + [ + 12, + 450, + -1 + ], + [ + 13, + 64, + -1 + ], + [ + 13, + 67, + -1 + ], + [ + 13, + 82, + -1 + ], + [ + 13, + 224, + -1 + ], + [ + 13, + 463, + 1 + ], + [ + 13, + 471, + 1 + ], + [ + 13, + 820, + -1 + ], + [ + 13, + 840, + 1 + ], + [ + 15, + 179, + -1 + ], + [ + 15, + 772, + 1 + ], + [ + 15, + 810, + -1 + ], + [ + 16, + 243, + 1 + ], + [ + 16, + 383, + 1 + ], + [ + 16, + 397, + 1 + ], + [ + 16, + 702, + -1 + ], + [ + 16, + 717, + -1 + ], + [ + 16, + 991, + -1 + ], + [ + 17, + 111, + -1 + ], + [ + 18, + 101, + -1 + ], + [ + 18, + 480, + -1 + ], + [ + 18, + 768, + -1 + ], + [ + 19, + 372, + -1 + ], + [ + 19, + 726, + -1 + ], + [ + 19, + 736, + -1 + ], + [ + 19, + 986, + 1 + ], + [ + 20, + 322, + 1 + ], + [ + 20, + 697, + 1 + ], + [ + 20, + 859, + 1 + ], + [ + 21, + 124, + -1 + ], + [ + 21, + 191, + 1 + ], + [ + 21, + 374, + 1 + ], + [ + 21, + 782, + 1 + ], + [ + 22, + 476, + 1 + ], + [ + 22, + 481, + 1 + ], + [ + 22, + 507, + 1 + ], + [ + 22, + 613, + -1 + ], + [ + 22, + 881, + -1 + ], + [ + 22, + 912, + -1 + ], + [ + 22, + 919, + 1 + ], + [ + 22, + 956, + -1 + ], + [ + 23, + 31, + 1 + ], + [ + 23, + 219, + 1 + ], + [ + 23, + 764, + -1 + ], + [ + 24, + 92, + -1 + ], + [ + 24, + 144, + -1 + ], + [ + 24, + 241, + -1 + ], + [ + 25, + 781, + 1 + ], + [ + 25, + 947, + -1 + ], + [ + 26, + 30, + -1 + ], + [ + 26, + 434, + 1 + ], + [ + 26, + 618, + -1 + ], + [ + 26, + 985, + 1 + ], + [ + 27, + 132, + 1 + ], + [ + 27, + 693, + -1 + ], + [ + 27, + 697, + 1 + ], + [ + 27, + 920, + -1 + ], + [ + 27, + 946, + -1 + ], + [ + 27, + 953, + 1 + ], + [ + 28, + 112, + -1 + ], + [ + 28, + 128, + 1 + ], + [ + 28, + 160, + -1 + ], + [ + 28, + 439, + 1 + ], + [ + 28, + 513, + 1 + ], + [ + 29, + 69, + 1 + ], + [ + 29, + 967, + 1 + ], + [ + 30, + 198, + -1 + ], + [ + 30, + 661, + 1 + ], + [ + 30, + 817, + 1 + ], + [ + 30, + 895, + -1 + ], + [ + 30, + 1021, + -1 + ], + [ + 31, + 209, + -1 + ], + [ + 31, + 281, + -1 + ], + [ + 31, + 741, + 1 + ], + [ + 31, + 861, + -1 + ], + [ + 31, + 1019, + 1 + ], + [ + 32, + 382, + -1 + ], + [ + 32, + 443, + -1 + ], + [ + 32, + 445, + 1 + ], + [ + 32, + 497, + -1 + ], + [ + 32, + 611, + 1 + ], + [ + 32, + 648, + 1 + ], + [ + 32, + 862, + 1 + ], + [ + 32, + 876, + -1 + ], + [ + 32, + 1007, + 1 + ], + [ + 33, + 749, + 1 + ], + [ + 33, + 872, + 1 + ], + [ + 33, + 983, + 1 + ], + [ + 34, + 315, + 1 + ], + [ + 34, + 978, + 1 + ], + [ + 35, + 92, + 1 + ], + [ + 35, + 247, + -1 + ], + [ + 35, + 293, + 1 + ], + [ + 35, + 355, + 1 + ], + [ + 35, + 420, + -1 + ], + [ + 35, + 887, + -1 + ], + [ + 36, + 67, + -1 + ], + [ + 36, + 354, + -1 + ], + [ + 36, + 778, + -1 + ], + [ + 36, + 968, + 1 + ], + [ + 37, + 999, + 1 + ], + [ + 38, + 240, + -1 + ], + [ + 38, + 677, + 1 + ], + [ + 38, + 947, + 1 + ], + [ + 38, + 1000, + -1 + ], + [ + 39, + 129, + -1 + ], + [ + 39, + 291, + -1 + ], + [ + 39, + 809, + 1 + ], + [ + 40, + 237, + -1 + ], + [ + 40, + 349, + 1 + ], + [ + 40, + 375, + 1 + ], + [ + 40, + 467, + -1 + ], + [ + 40, + 697, + -1 + ], + [ + 40, + 788, + -1 + ], + [ + 41, + 250, + 1 + ], + [ + 41, + 481, + -1 + ], + [ + 41, + 569, + -1 + ], + [ + 41, + 667, + 1 + ], + [ + 42, + 534, + 1 + ], + [ + 42, + 1015, + 1 + ], + [ + 43, + 241, + -1 + ], + [ + 43, + 280, + 1 + ], + [ + 43, + 633, + -1 + ], + [ + 44, + 151, + -1 + ], + [ + 44, + 342, + -1 + ], + [ + 44, + 370, + 1 + ], + [ + 44, + 528, + -1 + ], + [ + 44, + 652, + -1 + ], + [ + 45, + 295, + 1 + ], + [ + 45, + 565, + -1 + ], + [ + 45, + 666, + -1 + ], + [ + 45, + 1006, + -1 + ], + [ + 46, + 259, + 1 + ], + [ + 47, + 395, + 1 + ], + [ + 47, + 605, + 1 + ], + [ + 47, + 887, + -1 + ], + [ + 47, + 900, + 1 + ], + [ + 48, + 496, + -1 + ], + [ + 49, + 55, + -1 + ], + [ + 49, + 381, + 1 + ], + [ + 49, + 472, + -1 + ], + [ + 49, + 589, + -1 + ], + [ + 49, + 880, + 1 + ], + [ + 49, + 903, + 1 + ], + [ + 50, + 398, + -1 + ], + [ + 50, + 537, + -1 + ], + [ + 50, + 692, + 1 + ], + [ + 50, + 999, + -1 + ], + [ + 51, + 494, + -1 + ], + [ + 51, + 699, + -1 + ], + [ + 51, + 901, + -1 + ], + [ + 52, + 370, + -1 + ], + [ + 52, + 482, + 1 + ], + [ + 52, + 884, + -1 + ], + [ + 52, + 942, + 1 + ], + [ + 53, + 111, + 1 + ], + [ + 53, + 120, + -1 + ], + [ + 53, + 366, + 1 + ], + [ + 53, + 376, + 1 + ], + [ + 53, + 396, + 1 + ], + [ + 53, + 654, + 1 + ], + [ + 53, + 1005, + 1 + ], + [ + 54, + 399, + -1 + ], + [ + 54, + 684, + 1 + ], + [ + 54, + 718, + -1 + ], + [ + 55, + 59, + 1 + ], + [ + 55, + 94, + -1 + ], + [ + 55, + 450, + -1 + ], + [ + 55, + 888, + -1 + ], + [ + 56, + 60, + -1 + ], + [ + 56, + 189, + -1 + ], + [ + 56, + 551, + 1 + ], + [ + 56, + 666, + -1 + ], + [ + 57, + 678, + 1 + ], + [ + 58, + 954, + -1 + ], + [ + 58, + 997, + 1 + ], + [ + 59, + 201, + -1 + ], + [ + 59, + 982, + 1 + ], + [ + 60, + 578, + -1 + ], + [ + 60, + 645, + -1 + ], + [ + 60, + 761, + -1 + ], + [ + 60, + 861, + 1 + ], + [ + 61, + 676, + -1 + ], + [ + 61, + 937, + 1 + ], + [ + 62, + 147, + 1 + ], + [ + 62, + 422, + 1 + ], + [ + 63, + 84, + 1 + ], + [ + 63, + 182, + -1 + ], + [ + 63, + 315, + 1 + ], + [ + 63, + 456, + 1 + ], + [ + 63, + 669, + -1 + ], + [ + 63, + 770, + -1 + ], + [ + 64, + 280, + -1 + ], + [ + 65, + 128, + -1 + ], + [ + 65, + 224, + 1 + ], + [ + 65, + 873, + 1 + ], + [ + 65, + 944, + 1 + ], + [ + 65, + 1013, + 1 + ], + [ + 66, + 547, + -1 + ], + [ + 67, + 331, + -1 + ], + [ + 67, + 485, + -1 + ], + [ + 67, + 540, + 1 + ], + [ + 67, + 732, + -1 + ], + [ + 68, + 333, + -1 + ], + [ + 68, + 354, + -1 + ], + [ + 68, + 450, + 1 + ], + [ + 68, + 736, + 1 + ], + [ + 68, + 817, + -1 + ], + [ + 69, + 255, + 1 + ], + [ + 69, + 289, + 1 + ], + [ + 69, + 606, + -1 + ], + [ + 70, + 475, + -1 + ], + [ + 70, + 595, + 1 + ], + [ + 70, + 703, + -1 + ], + [ + 71, + 126, + -1 + ], + [ + 71, + 429, + 1 + ], + [ + 71, + 594, + 1 + ], + [ + 71, + 793, + -1 + ], + [ + 72, + 280, + -1 + ], + [ + 72, + 700, + -1 + ], + [ + 73, + 505, + 1 + ], + [ + 73, + 672, + 1 + ], + [ + 73, + 721, + 1 + ], + [ + 74, + 581, + -1 + ], + [ + 74, + 839, + -1 + ], + [ + 74, + 862, + -1 + ], + [ + 75, + 478, + 1 + ], + [ + 75, + 558, + 1 + ], + [ + 75, + 686, + -1 + ], + [ + 75, + 708, + 1 + ], + [ + 75, + 991, + -1 + ], + [ + 76, + 254, + 1 + ], + [ + 76, + 839, + -1 + ], + [ + 77, + 242, + -1 + ], + [ + 77, + 440, + 1 + ], + [ + 77, + 618, + -1 + ], + [ + 77, + 742, + 1 + ], + [ + 77, + 967, + -1 + ], + [ + 78, + 262, + -1 + ], + [ + 78, + 305, + -1 + ], + [ + 78, + 433, + 1 + ], + [ + 78, + 561, + 1 + ], + [ + 78, + 588, + 1 + ], + [ + 78, + 622, + -1 + ], + [ + 78, + 970, + 1 + ], + [ + 79, + 244, + 1 + ], + [ + 79, + 478, + -1 + ], + [ + 79, + 488, + 1 + ], + [ + 80, + 138, + 1 + ], + [ + 80, + 877, + -1 + ], + [ + 81, + 237, + -1 + ], + [ + 81, + 396, + 1 + ], + [ + 81, + 579, + 1 + ], + [ + 82, + 403, + -1 + ], + [ + 83, + 93, + 1 + ], + [ + 83, + 182, + -1 + ], + [ + 83, + 250, + 1 + ], + [ + 83, + 390, + 1 + ], + [ + 83, + 449, + -1 + ], + [ + 83, + 564, + 1 + ], + [ + 83, + 768, + 1 + ], + [ + 83, + 817, + 1 + ], + [ + 84, + 230, + -1 + ], + [ + 84, + 257, + 1 + ], + [ + 85, + 565, + -1 + ], + [ + 85, + 748, + -1 + ], + [ + 86, + 238, + 1 + ], + [ + 86, + 312, + -1 + ], + [ + 86, + 334, + 1 + ], + [ + 86, + 405, + -1 + ], + [ + 87, + 334, + -1 + ], + [ + 87, + 410, + -1 + ], + [ + 87, + 418, + 1 + ], + [ + 87, + 849, + 1 + ], + [ + 87, + 863, + -1 + ], + [ + 87, + 944, + -1 + ], + [ + 88, + 268, + -1 + ], + [ + 88, + 769, + 1 + ], + [ + 88, + 843, + -1 + ], + [ + 88, + 927, + 1 + ], + [ + 88, + 967, + -1 + ], + [ + 89, + 216, + 1 + ], + [ + 89, + 271, + 1 + ], + [ + 89, + 342, + 1 + ], + [ + 89, + 702, + 1 + ], + [ + 89, + 921, + -1 + ], + [ + 90, + 223, + -1 + ], + [ + 90, + 407, + -1 + ], + [ + 90, + 438, + -1 + ], + [ + 90, + 497, + -1 + ], + [ + 90, + 908, + 1 + ], + [ + 91, + 270, + 1 + ], + [ + 91, + 272, + -1 + ], + [ + 91, + 302, + -1 + ], + [ + 91, + 433, + 1 + ], + [ + 91, + 621, + -1 + ], + [ + 91, + 857, + -1 + ], + [ + 92, + 423, + 1 + ], + [ + 92, + 431, + 1 + ], + [ + 92, + 775, + 1 + ], + [ + 92, + 869, + -1 + ], + [ + 93, + 108, + -1 + ], + [ + 93, + 574, + 1 + ], + [ + 93, + 582, + -1 + ], + [ + 93, + 898, + 1 + ], + [ + 94, + 899, + 1 + ], + [ + 95, + 228, + -1 + ], + [ + 95, + 819, + -1 + ], + [ + 96, + 162, + -1 + ], + [ + 96, + 397, + 1 + ], + [ + 96, + 901, + 1 + ], + [ + 96, + 976, + 1 + ], + [ + 97, + 111, + -1 + ], + [ + 97, + 831, + 1 + ], + [ + 98, + 103, + 1 + ], + [ + 98, + 133, + 1 + ], + [ + 98, + 461, + -1 + ], + [ + 98, + 472, + -1 + ], + [ + 98, + 523, + 1 + ], + [ + 98, + 574, + 1 + ], + [ + 98, + 749, + 1 + ], + [ + 98, + 835, + -1 + ], + [ + 99, + 785, + 1 + ], + [ + 99, + 803, + -1 + ], + [ + 100, + 233, + 1 + ], + [ + 100, + 580, + -1 + ], + [ + 100, + 612, + 1 + ], + [ + 101, + 253, + 1 + ], + [ + 101, + 324, + -1 + ], + [ + 101, + 379, + 1 + ], + [ + 101, + 758, + 1 + ], + [ + 102, + 403, + -1 + ], + [ + 102, + 496, + -1 + ], + [ + 103, + 225, + -1 + ], + [ + 103, + 384, + 1 + ], + [ + 103, + 555, + -1 + ], + [ + 103, + 617, + -1 + ], + [ + 103, + 825, + 1 + ], + [ + 103, + 842, + 1 + ], + [ + 103, + 938, + 1 + ], + [ + 104, + 965, + -1 + ], + [ + 105, + 112, + 1 + ], + [ + 105, + 474, + 1 + ], + [ + 106, + 179, + 1 + ], + [ + 106, + 565, + 1 + ], + [ + 107, + 688, + -1 + ], + [ + 107, + 958, + -1 + ], + [ + 108, + 198, + 1 + ], + [ + 108, + 457, + -1 + ], + [ + 108, + 688, + -1 + ], + [ + 108, + 712, + -1 + ], + [ + 108, + 740, + 1 + ], + [ + 109, + 154, + 1 + ], + [ + 109, + 626, + -1 + ], + [ + 109, + 791, + -1 + ], + [ + 109, + 901, + 1 + ], + [ + 109, + 931, + 1 + ], + [ + 109, + 981, + -1 + ], + [ + 110, + 433, + 1 + ], + [ + 110, + 435, + 1 + ], + [ + 110, + 472, + 1 + ], + [ + 110, + 521, + -1 + ], + [ + 110, + 948, + 1 + ], + [ + 110, + 961, + -1 + ], + [ + 110, + 1004, + -1 + ], + [ + 111, + 712, + -1 + ], + [ + 111, + 831, + -1 + ], + [ + 112, + 436, + -1 + ], + [ + 112, + 451, + -1 + ], + [ + 112, + 592, + 1 + ], + [ + 112, + 635, + 1 + ], + [ + 112, + 966, + -1 + ], + [ + 112, + 988, + -1 + ], + [ + 113, + 155, + 1 + ], + [ + 113, + 216, + -1 + ], + [ + 114, + 499, + -1 + ], + [ + 114, + 754, + 1 + ], + [ + 114, + 993, + -1 + ], + [ + 115, + 542, + 1 + ], + [ + 115, + 600, + 1 + ], + [ + 115, + 651, + -1 + ], + [ + 115, + 738, + 1 + ], + [ + 116, + 118, + -1 + ], + [ + 116, + 174, + -1 + ], + [ + 116, + 211, + 1 + ], + [ + 116, + 366, + -1 + ], + [ + 116, + 437, + 1 + ], + [ + 116, + 521, + 1 + ], + [ + 117, + 820, + -1 + ], + [ + 118, + 120, + -1 + ], + [ + 118, + 443, + 1 + ], + [ + 118, + 589, + -1 + ], + [ + 119, + 300, + 1 + ], + [ + 119, + 392, + 1 + ], + [ + 119, + 446, + -1 + ], + [ + 119, + 815, + -1 + ], + [ + 119, + 950, + 1 + ], + [ + 119, + 973, + 1 + ], + [ + 120, + 665, + 1 + ], + [ + 120, + 782, + -1 + ], + [ + 122, + 164, + 1 + ], + [ + 122, + 238, + -1 + ], + [ + 122, + 261, + -1 + ], + [ + 122, + 338, + 1 + ], + [ + 122, + 454, + -1 + ], + [ + 122, + 472, + 1 + ], + [ + 123, + 151, + -1 + ], + [ + 123, + 194, + -1 + ], + [ + 124, + 148, + 1 + ], + [ + 124, + 384, + 1 + ], + [ + 124, + 422, + 1 + ], + [ + 125, + 192, + 1 + ], + [ + 125, + 301, + -1 + ], + [ + 125, + 951, + 1 + ], + [ + 126, + 139, + 1 + ], + [ + 126, + 518, + 1 + ], + [ + 126, + 954, + -1 + ], + [ + 127, + 866, + 1 + ], + [ + 127, + 942, + 1 + ], + [ + 128, + 329, + -1 + ], + [ + 128, + 524, + 1 + ], + [ + 128, + 679, + 1 + ], + [ + 129, + 389, + 1 + ], + [ + 129, + 414, + 1 + ], + [ + 130, + 133, + -1 + ], + [ + 130, + 417, + -1 + ], + [ + 130, + 711, + 1 + ], + [ + 130, + 988, + 1 + ], + [ + 131, + 574, + -1 + ], + [ + 132, + 811, + 1 + ], + [ + 132, + 890, + 1 + ], + [ + 133, + 477, + -1 + ], + [ + 133, + 931, + -1 + ], + [ + 134, + 241, + -1 + ], + [ + 134, + 279, + -1 + ], + [ + 134, + 482, + 1 + ], + [ + 134, + 487, + 1 + ], + [ + 134, + 771, + 1 + ], + [ + 135, + 203, + -1 + ], + [ + 135, + 523, + 1 + ], + [ + 135, + 940, + 1 + ], + [ + 136, + 574, + 1 + ], + [ + 136, + 625, + 1 + ], + [ + 136, + 689, + 1 + ], + [ + 136, + 773, + -1 + ], + [ + 136, + 929, + -1 + ], + [ + 137, + 292, + -1 + ], + [ + 137, + 295, + 1 + ], + [ + 137, + 757, + 1 + ], + [ + 137, + 902, + -1 + ], + [ + 137, + 983, + 1 + ], + [ + 138, + 578, + 1 + ], + [ + 138, + 746, + -1 + ], + [ + 138, + 777, + -1 + ], + [ + 138, + 786, + 1 + ], + [ + 138, + 862, + 1 + ], + [ + 139, + 175, + -1 + ], + [ + 139, + 252, + -1 + ], + [ + 139, + 559, + 1 + ], + [ + 139, + 649, + 1 + ], + [ + 139, + 946, + -1 + ], + [ + 140, + 208, + 1 + ], + [ + 140, + 409, + 1 + ], + [ + 140, + 680, + -1 + ], + [ + 140, + 762, + -1 + ], + [ + 140, + 765, + 1 + ], + [ + 141, + 405, + 1 + ], + [ + 141, + 452, + -1 + ], + [ + 141, + 529, + -1 + ], + [ + 141, + 536, + -1 + ], + [ + 141, + 575, + -1 + ], + [ + 141, + 625, + -1 + ], + [ + 141, + 995, + -1 + ], + [ + 142, + 211, + -1 + ], + [ + 142, + 605, + 1 + ], + [ + 142, + 705, + -1 + ], + [ + 142, + 746, + -1 + ], + [ + 142, + 799, + -1 + ], + [ + 142, + 869, + -1 + ], + [ + 142, + 1008, + 1 + ], + [ + 142, + 1021, + 1 + ], + [ + 143, + 227, + 1 + ], + [ + 143, + 369, + -1 + ], + [ + 143, + 394, + -1 + ], + [ + 143, + 536, + -1 + ], + [ + 143, + 832, + 1 + ], + [ + 143, + 919, + -1 + ], + [ + 144, + 213, + 1 + ], + [ + 144, + 722, + 1 + ], + [ + 144, + 827, + -1 + ], + [ + 145, + 322, + 1 + ], + [ + 145, + 443, + 1 + ], + [ + 145, + 617, + -1 + ], + [ + 145, + 942, + -1 + ], + [ + 146, + 294, + -1 + ], + [ + 146, + 400, + 1 + ], + [ + 146, + 546, + -1 + ], + [ + 146, + 682, + -1 + ], + [ + 146, + 925, + 1 + ], + [ + 146, + 1022, + 1 + ], + [ + 147, + 203, + 1 + ], + [ + 147, + 827, + 1 + ], + [ + 148, + 372, + 1 + ], + [ + 148, + 408, + -1 + ], + [ + 148, + 858, + -1 + ], + [ + 149, + 465, + 1 + ], + [ + 149, + 638, + -1 + ], + [ + 149, + 867, + 1 + ], + [ + 149, + 871, + 1 + ], + [ + 150, + 168, + -1 + ], + [ + 150, + 707, + -1 + ], + [ + 150, + 756, + -1 + ], + [ + 150, + 810, + -1 + ], + [ + 150, + 832, + -1 + ], + [ + 150, + 866, + 1 + ], + [ + 151, + 404, + -1 + ], + [ + 151, + 554, + 1 + ], + [ + 151, + 721, + 1 + ], + [ + 151, + 771, + 1 + ], + [ + 151, + 956, + -1 + ], + [ + 152, + 191, + 1 + ], + [ + 152, + 405, + -1 + ], + [ + 153, + 285, + -1 + ], + [ + 153, + 356, + -1 + ], + [ + 153, + 457, + 1 + ], + [ + 153, + 776, + -1 + ], + [ + 153, + 921, + 1 + ], + [ + 153, + 987, + 1 + ], + [ + 154, + 513, + -1 + ], + [ + 154, + 727, + -1 + ], + [ + 154, + 915, + -1 + ], + [ + 155, + 167, + 1 + ], + [ + 155, + 467, + -1 + ], + [ + 155, + 547, + -1 + ], + [ + 155, + 965, + -1 + ], + [ + 156, + 204, + 1 + ], + [ + 156, + 675, + -1 + ], + [ + 156, + 777, + -1 + ], + [ + 157, + 648, + -1 + ], + [ + 157, + 904, + -1 + ], + [ + 157, + 908, + 1 + ], + [ + 157, + 936, + 1 + ], + [ + 158, + 186, + 1 + ], + [ + 158, + 316, + -1 + ], + [ + 158, + 580, + 1 + ], + [ + 158, + 668, + 1 + ], + [ + 159, + 866, + 1 + ], + [ + 159, + 920, + 1 + ], + [ + 160, + 206, + -1 + ], + [ + 160, + 243, + 1 + ], + [ + 160, + 364, + 1 + ], + [ + 161, + 187, + -1 + ], + [ + 161, + 325, + 1 + ], + [ + 161, + 502, + -1 + ], + [ + 161, + 1000, + -1 + ], + [ + 162, + 452, + 1 + ], + [ + 163, + 342, + -1 + ], + [ + 163, + 523, + -1 + ], + [ + 163, + 810, + -1 + ], + [ + 163, + 985, + 1 + ], + [ + 164, + 437, + -1 + ], + [ + 164, + 580, + 1 + ], + [ + 164, + 696, + 1 + ], + [ + 164, + 798, + 1 + ], + [ + 164, + 854, + -1 + ], + [ + 164, + 1000, + 1 + ], + [ + 165, + 290, + 1 + ], + [ + 165, + 389, + 1 + ], + [ + 165, + 404, + 1 + ], + [ + 165, + 408, + 1 + ], + [ + 165, + 487, + -1 + ], + [ + 165, + 923, + -1 + ], + [ + 166, + 216, + -1 + ], + [ + 166, + 280, + -1 + ], + [ + 166, + 601, + 1 + ], + [ + 166, + 810, + 1 + ], + [ + 166, + 886, + 1 + ], + [ + 167, + 186, + -1 + ], + [ + 167, + 325, + 1 + ], + [ + 167, + 777, + -1 + ], + [ + 168, + 232, + 1 + ], + [ + 168, + 493, + -1 + ], + [ + 168, + 674, + 1 + ], + [ + 168, + 675, + -1 + ], + [ + 168, + 953, + -1 + ], + [ + 168, + 1022, + 1 + ], + [ + 169, + 224, + 1 + ], + [ + 169, + 470, + -1 + ], + [ + 169, + 628, + 1 + ], + [ + 169, + 721, + -1 + ], + [ + 169, + 766, + -1 + ], + [ + 169, + 931, + -1 + ], + [ + 169, + 940, + -1 + ], + [ + 170, + 288, + 1 + ], + [ + 170, + 424, + -1 + ], + [ + 170, + 445, + 1 + ], + [ + 170, + 448, + -1 + ], + [ + 170, + 471, + 1 + ], + [ + 170, + 711, + -1 + ], + [ + 170, + 983, + -1 + ], + [ + 171, + 410, + -1 + ], + [ + 171, + 552, + -1 + ], + [ + 171, + 683, + 1 + ], + [ + 171, + 899, + 1 + ], + [ + 172, + 293, + -1 + ], + [ + 172, + 838, + -1 + ], + [ + 173, + 287, + -1 + ], + [ + 173, + 679, + -1 + ], + [ + 173, + 746, + 1 + ], + [ + 173, + 1016, + 1 + ], + [ + 174, + 285, + -1 + ], + [ + 174, + 580, + 1 + ], + [ + 174, + 862, + 1 + ], + [ + 174, + 956, + -1 + ], + [ + 174, + 1020, + 1 + ], + [ + 175, + 203, + -1 + ], + [ + 175, + 605, + -1 + ], + [ + 175, + 626, + 1 + ], + [ + 175, + 692, + -1 + ], + [ + 175, + 962, + 1 + ], + [ + 176, + 209, + -1 + ], + [ + 176, + 393, + 1 + ], + [ + 176, + 648, + 1 + ], + [ + 177, + 217, + -1 + ], + [ + 177, + 400, + -1 + ], + [ + 177, + 576, + 1 + ], + [ + 177, + 670, + -1 + ], + [ + 178, + 432, + -1 + ], + [ + 178, + 433, + -1 + ], + [ + 178, + 482, + -1 + ], + [ + 178, + 518, + 1 + ], + [ + 179, + 449, + -1 + ], + [ + 179, + 468, + -1 + ], + [ + 179, + 471, + 1 + ], + [ + 179, + 525, + 1 + ], + [ + 179, + 610, + -1 + ], + [ + 180, + 738, + 1 + ], + [ + 180, + 941, + 1 + ], + [ + 181, + 191, + 1 + ], + [ + 181, + 256, + -1 + ], + [ + 181, + 397, + 1 + ], + [ + 182, + 540, + 1 + ], + [ + 182, + 574, + 1 + ], + [ + 182, + 651, + -1 + ], + [ + 182, + 991, + 1 + ], + [ + 183, + 232, + -1 + ], + [ + 183, + 324, + -1 + ], + [ + 183, + 575, + 1 + ], + [ + 183, + 629, + -1 + ], + [ + 183, + 961, + 1 + ], + [ + 184, + 396, + 1 + ], + [ + 184, + 549, + -1 + ], + [ + 184, + 609, + 1 + ], + [ + 185, + 635, + -1 + ], + [ + 185, + 812, + 1 + ], + [ + 185, + 896, + -1 + ], + [ + 186, + 353, + -1 + ], + [ + 186, + 880, + 1 + ], + [ + 186, + 944, + 1 + ], + [ + 186, + 1005, + 1 + ], + [ + 187, + 278, + 1 + ], + [ + 188, + 202, + -1 + ], + [ + 188, + 853, + -1 + ], + [ + 188, + 988, + 1 + ], + [ + 189, + 812, + -1 + ], + [ + 190, + 350, + 1 + ], + [ + 190, + 370, + -1 + ], + [ + 190, + 653, + -1 + ], + [ + 190, + 680, + 1 + ], + [ + 190, + 730, + 1 + ], + [ + 190, + 742, + -1 + ], + [ + 190, + 753, + -1 + ], + [ + 190, + 908, + -1 + ], + [ + 191, + 307, + 1 + ], + [ + 191, + 378, + 1 + ], + [ + 191, + 722, + -1 + ], + [ + 191, + 989, + -1 + ], + [ + 193, + 203, + 1 + ], + [ + 193, + 296, + 1 + ], + [ + 193, + 302, + -1 + ], + [ + 193, + 789, + -1 + ], + [ + 193, + 914, + 1 + ], + [ + 194, + 195, + 1 + ], + [ + 194, + 661, + -1 + ], + [ + 194, + 775, + -1 + ], + [ + 194, + 853, + -1 + ], + [ + 195, + 287, + -1 + ], + [ + 195, + 344, + -1 + ], + [ + 195, + 455, + -1 + ], + [ + 195, + 494, + -1 + ], + [ + 195, + 681, + -1 + ], + [ + 195, + 695, + -1 + ], + [ + 195, + 905, + -1 + ], + [ + 196, + 246, + 1 + ], + [ + 196, + 505, + -1 + ], + [ + 196, + 571, + -1 + ], + [ + 196, + 773, + -1 + ], + [ + 196, + 841, + 1 + ], + [ + 196, + 964, + -1 + ], + [ + 197, + 525, + -1 + ], + [ + 197, + 650, + 1 + ], + [ + 197, + 900, + -1 + ], + [ + 198, + 427, + -1 + ], + [ + 198, + 527, + -1 + ], + [ + 198, + 556, + 1 + ], + [ + 198, + 941, + -1 + ], + [ + 198, + 964, + -1 + ], + [ + 199, + 595, + 1 + ], + [ + 199, + 663, + 1 + ], + [ + 200, + 304, + 1 + ], + [ + 200, + 745, + 1 + ], + [ + 200, + 840, + -1 + ], + [ + 200, + 997, + 1 + ], + [ + 201, + 397, + -1 + ], + [ + 201, + 469, + 1 + ], + [ + 203, + 222, + -1 + ], + [ + 203, + 234, + -1 + ], + [ + 203, + 543, + -1 + ], + [ + 203, + 1017, + 1 + ], + [ + 204, + 257, + 1 + ], + [ + 204, + 459, + 1 + ], + [ + 204, + 468, + -1 + ], + [ + 204, + 500, + -1 + ], + [ + 204, + 502, + 1 + ], + [ + 204, + 681, + -1 + ], + [ + 204, + 781, + -1 + ], + [ + 204, + 884, + 1 + ], + [ + 205, + 302, + 1 + ], + [ + 205, + 500, + 1 + ], + [ + 205, + 837, + 1 + ], + [ + 207, + 466, + -1 + ], + [ + 207, + 534, + -1 + ], + [ + 207, + 628, + -1 + ], + [ + 208, + 281, + 1 + ], + [ + 209, + 274, + 1 + ], + [ + 209, + 609, + 1 + ], + [ + 209, + 618, + 1 + ], + [ + 209, + 822, + 1 + ], + [ + 209, + 899, + 1 + ], + [ + 210, + 287, + -1 + ], + [ + 210, + 345, + 1 + ], + [ + 210, + 543, + 1 + ], + [ + 211, + 804, + 1 + ], + [ + 211, + 837, + 1 + ], + [ + 211, + 958, + -1 + ], + [ + 212, + 306, + 1 + ], + [ + 212, + 337, + 1 + ], + [ + 212, + 412, + 1 + ], + [ + 212, + 576, + -1 + ], + [ + 212, + 685, + 1 + ], + [ + 213, + 445, + -1 + ], + [ + 213, + 607, + 1 + ], + [ + 213, + 633, + -1 + ], + [ + 214, + 580, + 1 + ], + [ + 215, + 403, + 1 + ], + [ + 215, + 461, + 1 + ], + [ + 215, + 479, + 1 + ], + [ + 215, + 666, + -1 + ], + [ + 215, + 916, + -1 + ], + [ + 216, + 394, + -1 + ], + [ + 216, + 404, + 1 + ], + [ + 216, + 582, + 1 + ], + [ + 217, + 304, + -1 + ], + [ + 217, + 642, + -1 + ], + [ + 217, + 744, + -1 + ], + [ + 217, + 955, + 1 + ], + [ + 217, + 1005, + 1 + ], + [ + 218, + 402, + 1 + ], + [ + 218, + 404, + 1 + ], + [ + 219, + 849, + 1 + ], + [ + 219, + 860, + 1 + ], + [ + 220, + 389, + 1 + ], + [ + 220, + 701, + 1 + ], + [ + 220, + 786, + 1 + ], + [ + 221, + 232, + -1 + ], + [ + 221, + 376, + -1 + ], + [ + 221, + 386, + -1 + ], + [ + 221, + 736, + 1 + ], + [ + 222, + 948, + 1 + ], + [ + 223, + 354, + -1 + ], + [ + 223, + 441, + 1 + ], + [ + 223, + 939, + -1 + ], + [ + 225, + 260, + 1 + ], + [ + 225, + 369, + -1 + ], + [ + 225, + 573, + -1 + ], + [ + 225, + 919, + 1 + ], + [ + 226, + 462, + -1 + ], + [ + 226, + 714, + 1 + ], + [ + 226, + 883, + 1 + ], + [ + 226, + 901, + 1 + ], + [ + 227, + 378, + -1 + ], + [ + 227, + 438, + -1 + ], + [ + 227, + 883, + 1 + ], + [ + 228, + 331, + -1 + ], + [ + 228, + 364, + 1 + ], + [ + 228, + 458, + 1 + ], + [ + 228, + 984, + 1 + ], + [ + 229, + 343, + 1 + ], + [ + 229, + 437, + -1 + ], + [ + 229, + 439, + -1 + ], + [ + 229, + 457, + 1 + ], + [ + 229, + 465, + -1 + ], + [ + 229, + 518, + 1 + ], + [ + 229, + 537, + -1 + ], + [ + 230, + 974, + 1 + ], + [ + 231, + 636, + -1 + ], + [ + 231, + 669, + -1 + ], + [ + 232, + 574, + 1 + ], + [ + 232, + 608, + -1 + ], + [ + 233, + 587, + 1 + ], + [ + 233, + 920, + 1 + ], + [ + 234, + 292, + 1 + ], + [ + 234, + 834, + 1 + ], + [ + 235, + 652, + -1 + ], + [ + 236, + 275, + -1 + ], + [ + 236, + 443, + -1 + ], + [ + 236, + 538, + 1 + ], + [ + 236, + 595, + 1 + ], + [ + 236, + 709, + -1 + ], + [ + 236, + 768, + 1 + ], + [ + 237, + 279, + 1 + ], + [ + 237, + 372, + 1 + ], + [ + 237, + 394, + 1 + ], + [ + 237, + 396, + -1 + ], + [ + 237, + 450, + -1 + ], + [ + 237, + 486, + -1 + ], + [ + 238, + 374, + 1 + ], + [ + 238, + 589, + -1 + ], + [ + 238, + 622, + 1 + ], + [ + 238, + 682, + 1 + ], + [ + 239, + 398, + 1 + ], + [ + 239, + 644, + -1 + ], + [ + 239, + 733, + 1 + ], + [ + 239, + 734, + 1 + ], + [ + 239, + 770, + 1 + ], + [ + 240, + 716, + 1 + ], + [ + 240, + 830, + 1 + ], + [ + 240, + 941, + -1 + ], + [ + 241, + 541, + -1 + ], + [ + 241, + 875, + 1 + ], + [ + 242, + 506, + 1 + ], + [ + 242, + 599, + -1 + ], + [ + 242, + 753, + 1 + ], + [ + 242, + 860, + -1 + ], + [ + 242, + 965, + 1 + ], + [ + 242, + 1019, + -1 + ], + [ + 243, + 731, + -1 + ], + [ + 243, + 912, + -1 + ], + [ + 244, + 264, + -1 + ], + [ + 244, + 799, + 1 + ], + [ + 245, + 948, + 1 + ], + [ + 246, + 966, + -1 + ], + [ + 247, + 746, + -1 + ], + [ + 247, + 780, + 1 + ], + [ + 248, + 686, + -1 + ], + [ + 248, + 900, + 1 + ], + [ + 249, + 543, + -1 + ], + [ + 250, + 964, + 1 + ], + [ + 251, + 256, + -1 + ], + [ + 251, + 283, + -1 + ], + [ + 251, + 297, + -1 + ], + [ + 251, + 326, + 1 + ], + [ + 251, + 617, + -1 + ], + [ + 252, + 478, + 1 + ], + [ + 253, + 486, + 1 + ], + [ + 253, + 524, + -1 + ], + [ + 253, + 580, + 1 + ], + [ + 253, + 869, + 1 + ], + [ + 254, + 688, + -1 + ], + [ + 254, + 743, + 1 + ], + [ + 254, + 932, + -1 + ], + [ + 255, + 381, + -1 + ], + [ + 255, + 875, + -1 + ], + [ + 255, + 955, + 1 + ], + [ + 257, + 369, + -1 + ], + [ + 257, + 481, + -1 + ], + [ + 258, + 421, + 1 + ], + [ + 258, + 740, + -1 + ], + [ + 258, + 887, + -1 + ], + [ + 259, + 299, + -1 + ], + [ + 259, + 645, + -1 + ], + [ + 259, + 868, + -1 + ], + [ + 259, + 870, + -1 + ], + [ + 259, + 923, + -1 + ], + [ + 260, + 549, + -1 + ], + [ + 260, + 565, + -1 + ], + [ + 260, + 903, + -1 + ], + [ + 261, + 378, + -1 + ], + [ + 262, + 702, + -1 + ], + [ + 263, + 611, + 1 + ], + [ + 263, + 768, + 1 + ], + [ + 264, + 493, + 1 + ], + [ + 264, + 981, + 1 + ], + [ + 265, + 796, + -1 + ], + [ + 265, + 877, + -1 + ], + [ + 266, + 369, + 1 + ], + [ + 266, + 473, + 1 + ], + [ + 266, + 553, + 1 + ], + [ + 266, + 646, + -1 + ], + [ + 266, + 773, + 1 + ], + [ + 267, + 605, + -1 + ], + [ + 267, + 724, + 1 + ], + [ + 268, + 389, + -1 + ], + [ + 268, + 583, + 1 + ], + [ + 268, + 719, + 1 + ], + [ + 268, + 985, + 1 + ], + [ + 269, + 664, + 1 + ], + [ + 269, + 998, + 1 + ], + [ + 270, + 334, + 1 + ], + [ + 270, + 336, + 1 + ], + [ + 270, + 662, + 1 + ], + [ + 270, + 686, + -1 + ], + [ + 271, + 652, + -1 + ], + [ + 272, + 508, + -1 + ], + [ + 272, + 560, + -1 + ], + [ + 272, + 698, + 1 + ], + [ + 273, + 872, + 1 + ], + [ + 273, + 962, + -1 + ], + [ + 274, + 405, + 1 + ], + [ + 274, + 483, + 1 + ], + [ + 274, + 539, + -1 + ], + [ + 275, + 582, + -1 + ], + [ + 275, + 1001, + -1 + ], + [ + 276, + 428, + -1 + ], + [ + 276, + 519, + 1 + ], + [ + 276, + 1021, + 1 + ], + [ + 277, + 777, + -1 + ], + [ + 278, + 666, + -1 + ], + [ + 278, + 679, + -1 + ], + [ + 279, + 280, + -1 + ], + [ + 279, + 416, + -1 + ], + [ + 279, + 437, + 1 + ], + [ + 279, + 549, + 1 + ], + [ + 279, + 879, + -1 + ], + [ + 280, + 356, + -1 + ], + [ + 280, + 679, + -1 + ], + [ + 280, + 927, + 1 + ], + [ + 282, + 362, + 1 + ], + [ + 282, + 777, + -1 + ], + [ + 283, + 300, + 1 + ], + [ + 283, + 426, + 1 + ], + [ + 283, + 673, + -1 + ], + [ + 284, + 475, + -1 + ], + [ + 284, + 870, + 1 + ], + [ + 284, + 979, + 1 + ], + [ + 285, + 336, + -1 + ], + [ + 285, + 892, + 1 + ], + [ + 285, + 911, + -1 + ], + [ + 286, + 814, + 1 + ], + [ + 286, + 845, + 1 + ], + [ + 286, + 882, + 1 + ], + [ + 287, + 336, + -1 + ], + [ + 287, + 350, + 1 + ], + [ + 287, + 728, + 1 + ], + [ + 287, + 1009, + 1 + ], + [ + 287, + 1017, + -1 + ], + [ + 288, + 447, + 1 + ], + [ + 289, + 501, + 1 + ], + [ + 289, + 819, + -1 + ], + [ + 291, + 330, + 1 + ], + [ + 291, + 997, + -1 + ], + [ + 292, + 390, + 1 + ], + [ + 292, + 462, + 1 + ], + [ + 293, + 424, + -1 + ], + [ + 293, + 451, + 1 + ], + [ + 293, + 525, + 1 + ], + [ + 293, + 676, + -1 + ], + [ + 293, + 858, + -1 + ], + [ + 294, + 341, + -1 + ], + [ + 294, + 456, + -1 + ], + [ + 294, + 510, + 1 + ], + [ + 294, + 714, + 1 + ], + [ + 294, + 753, + -1 + ], + [ + 294, + 813, + 1 + ], + [ + 294, + 951, + 1 + ], + [ + 295, + 500, + 1 + ], + [ + 295, + 719, + 1 + ], + [ + 295, + 1000, + 1 + ], + [ + 296, + 312, + 1 + ], + [ + 296, + 651, + 1 + ], + [ + 296, + 889, + -1 + ], + [ + 297, + 332, + 1 + ], + [ + 297, + 380, + -1 + ], + [ + 297, + 413, + -1 + ], + [ + 297, + 555, + 1 + ], + [ + 297, + 823, + -1 + ], + [ + 297, + 967, + 1 + ], + [ + 298, + 556, + 1 + ], + [ + 298, + 719, + 1 + ], + [ + 298, + 747, + 1 + ], + [ + 299, + 500, + -1 + ], + [ + 299, + 649, + 1 + ], + [ + 299, + 953, + 1 + ], + [ + 299, + 974, + 1 + ], + [ + 300, + 494, + 1 + ], + [ + 300, + 722, + 1 + ], + [ + 300, + 758, + -1 + ], + [ + 300, + 967, + 1 + ], + [ + 300, + 992, + 1 + ], + [ + 300, + 1020, + 1 + ], + [ + 301, + 450, + -1 + ], + [ + 301, + 474, + 1 + ], + [ + 302, + 652, + 1 + ], + [ + 302, + 759, + -1 + ], + [ + 302, + 810, + -1 + ], + [ + 302, + 860, + -1 + ], + [ + 302, + 883, + 1 + ], + [ + 303, + 465, + -1 + ], + [ + 303, + 573, + -1 + ], + [ + 304, + 380, + 1 + ], + [ + 304, + 418, + 1 + ], + [ + 304, + 429, + 1 + ], + [ + 305, + 433, + 1 + ], + [ + 305, + 674, + 1 + ], + [ + 305, + 710, + 1 + ], + [ + 305, + 908, + -1 + ], + [ + 306, + 334, + 1 + ], + [ + 306, + 612, + -1 + ], + [ + 307, + 359, + 1 + ], + [ + 307, + 499, + -1 + ], + [ + 307, + 542, + -1 + ], + [ + 308, + 316, + 1 + ], + [ + 308, + 522, + 1 + ], + [ + 308, + 721, + 1 + ], + [ + 308, + 942, + -1 + ], + [ + 309, + 590, + 1 + ], + [ + 309, + 959, + 1 + ], + [ + 310, + 403, + -1 + ], + [ + 310, + 541, + -1 + ], + [ + 311, + 433, + 1 + ], + [ + 311, + 527, + 1 + ], + [ + 311, + 771, + -1 + ], + [ + 311, + 795, + -1 + ], + [ + 311, + 979, + -1 + ], + [ + 312, + 574, + -1 + ], + [ + 312, + 776, + -1 + ], + [ + 313, + 901, + 1 + ], + [ + 313, + 932, + -1 + ], + [ + 314, + 409, + -1 + ], + [ + 314, + 415, + -1 + ], + [ + 314, + 426, + -1 + ], + [ + 314, + 541, + 1 + ], + [ + 314, + 725, + -1 + ], + [ + 315, + 889, + -1 + ], + [ + 315, + 909, + -1 + ], + [ + 316, + 505, + -1 + ], + [ + 316, + 517, + -1 + ], + [ + 317, + 571, + 1 + ], + [ + 317, + 889, + 1 + ], + [ + 318, + 415, + -1 + ], + [ + 319, + 375, + 1 + ], + [ + 319, + 446, + 1 + ], + [ + 319, + 597, + -1 + ], + [ + 319, + 778, + 1 + ], + [ + 320, + 387, + -1 + ], + [ + 320, + 440, + -1 + ], + [ + 321, + 442, + 1 + ], + [ + 321, + 521, + 1 + ], + [ + 321, + 691, + 1 + ], + [ + 321, + 724, + -1 + ], + [ + 321, + 943, + 1 + ], + [ + 322, + 509, + 1 + ], + [ + 322, + 563, + 1 + ], + [ + 322, + 689, + 1 + ], + [ + 322, + 876, + 1 + ], + [ + 323, + 673, + 1 + ], + [ + 323, + 844, + 1 + ], + [ + 323, + 849, + -1 + ], + [ + 324, + 424, + -1 + ], + [ + 324, + 535, + -1 + ], + [ + 325, + 445, + 1 + ], + [ + 325, + 587, + -1 + ], + [ + 326, + 649, + 1 + ], + [ + 327, + 330, + 1 + ], + [ + 327, + 767, + -1 + ], + [ + 328, + 385, + -1 + ], + [ + 328, + 659, + 1 + ], + [ + 328, + 843, + 1 + ], + [ + 328, + 876, + -1 + ], + [ + 328, + 901, + 1 + ], + [ + 329, + 767, + 1 + ], + [ + 329, + 779, + 1 + ], + [ + 329, + 934, + -1 + ], + [ + 330, + 495, + -1 + ], + [ + 330, + 604, + -1 + ], + [ + 332, + 705, + -1 + ], + [ + 332, + 871, + -1 + ], + [ + 332, + 940, + -1 + ], + [ + 333, + 352, + -1 + ], + [ + 333, + 678, + -1 + ], + [ + 333, + 857, + 1 + ], + [ + 334, + 722, + -1 + ], + [ + 335, + 370, + -1 + ], + [ + 335, + 392, + -1 + ], + [ + 335, + 591, + 1 + ], + [ + 335, + 614, + -1 + ], + [ + 335, + 842, + 1 + ], + [ + 336, + 852, + -1 + ], + [ + 337, + 600, + -1 + ], + [ + 338, + 537, + -1 + ], + [ + 338, + 719, + -1 + ], + [ + 339, + 1002, + 1 + ], + [ + 340, + 343, + 1 + ], + [ + 340, + 442, + 1 + ], + [ + 340, + 538, + 1 + ], + [ + 341, + 347, + 1 + ], + [ + 341, + 773, + -1 + ], + [ + 341, + 788, + 1 + ], + [ + 342, + 734, + -1 + ], + [ + 342, + 787, + 1 + ], + [ + 343, + 1011, + 1 + ], + [ + 344, + 370, + 1 + ], + [ + 344, + 458, + 1 + ], + [ + 344, + 574, + 1 + ], + [ + 344, + 1006, + -1 + ], + [ + 345, + 614, + -1 + ], + [ + 345, + 856, + -1 + ], + [ + 346, + 790, + 1 + ], + [ + 346, + 938, + 1 + ], + [ + 347, + 762, + 1 + ], + [ + 347, + 960, + -1 + ], + [ + 348, + 452, + -1 + ], + [ + 348, + 628, + -1 + ], + [ + 348, + 644, + -1 + ], + [ + 349, + 449, + 1 + ], + [ + 349, + 651, + 1 + ], + [ + 349, + 694, + -1 + ], + [ + 349, + 999, + 1 + ], + [ + 350, + 518, + -1 + ], + [ + 350, + 595, + 1 + ], + [ + 350, + 747, + -1 + ], + [ + 350, + 857, + 1 + ], + [ + 350, + 935, + 1 + ], + [ + 350, + 973, + 1 + ], + [ + 351, + 552, + -1 + ], + [ + 351, + 557, + 1 + ], + [ + 351, + 558, + -1 + ], + [ + 351, + 645, + 1 + ], + [ + 351, + 711, + 1 + ], + [ + 351, + 789, + -1 + ], + [ + 351, + 925, + -1 + ], + [ + 352, + 666, + -1 + ], + [ + 352, + 838, + -1 + ], + [ + 353, + 638, + -1 + ], + [ + 353, + 880, + 1 + ], + [ + 354, + 537, + -1 + ], + [ + 355, + 585, + -1 + ], + [ + 355, + 907, + 1 + ], + [ + 356, + 856, + -1 + ], + [ + 357, + 677, + 1 + ], + [ + 357, + 942, + -1 + ], + [ + 358, + 573, + -1 + ], + [ + 358, + 769, + -1 + ], + [ + 360, + 451, + -1 + ], + [ + 360, + 500, + 1 + ], + [ + 360, + 580, + 1 + ], + [ + 360, + 638, + 1 + ], + [ + 360, + 737, + -1 + ], + [ + 360, + 863, + 1 + ], + [ + 362, + 666, + -1 + ], + [ + 362, + 676, + -1 + ], + [ + 362, + 975, + -1 + ], + [ + 363, + 397, + -1 + ], + [ + 363, + 452, + -1 + ], + [ + 363, + 485, + 1 + ], + [ + 363, + 566, + 1 + ], + [ + 363, + 700, + -1 + ], + [ + 364, + 529, + -1 + ], + [ + 364, + 740, + -1 + ], + [ + 365, + 847, + -1 + ], + [ + 365, + 954, + -1 + ], + [ + 366, + 519, + 1 + ], + [ + 367, + 464, + 1 + ], + [ + 367, + 777, + -1 + ], + [ + 367, + 1003, + 1 + ], + [ + 369, + 962, + 1 + ], + [ + 369, + 981, + 1 + ], + [ + 369, + 1005, + -1 + ], + [ + 370, + 477, + 1 + ], + [ + 370, + 962, + -1 + ], + [ + 371, + 393, + -1 + ], + [ + 371, + 406, + 1 + ], + [ + 371, + 724, + -1 + ], + [ + 371, + 745, + 1 + ], + [ + 371, + 774, + -1 + ], + [ + 372, + 402, + -1 + ], + [ + 372, + 459, + -1 + ], + [ + 372, + 654, + -1 + ], + [ + 372, + 894, + -1 + ], + [ + 373, + 566, + 1 + ], + [ + 373, + 889, + -1 + ], + [ + 373, + 955, + 1 + ], + [ + 374, + 729, + 1 + ], + [ + 375, + 628, + 1 + ], + [ + 375, + 740, + 1 + ], + [ + 376, + 403, + -1 + ], + [ + 376, + 443, + -1 + ], + [ + 376, + 544, + -1 + ], + [ + 377, + 568, + 1 + ], + [ + 377, + 836, + 1 + ], + [ + 378, + 706, + -1 + ], + [ + 378, + 1007, + 1 + ], + [ + 379, + 462, + 1 + ], + [ + 379, + 500, + -1 + ], + [ + 379, + 753, + -1 + ], + [ + 379, + 989, + 1 + ], + [ + 382, + 385, + 1 + ], + [ + 382, + 561, + 1 + ], + [ + 382, + 913, + 1 + ], + [ + 383, + 545, + -1 + ], + [ + 383, + 708, + 1 + ], + [ + 384, + 601, + -1 + ], + [ + 385, + 826, + 1 + ], + [ + 385, + 871, + 1 + ], + [ + 385, + 972, + 1 + ], + [ + 386, + 497, + -1 + ], + [ + 386, + 520, + 1 + ], + [ + 386, + 987, + -1 + ], + [ + 387, + 399, + 1 + ], + [ + 387, + 461, + 1 + ], + [ + 387, + 540, + 1 + ], + [ + 387, + 749, + 1 + ], + [ + 388, + 501, + 1 + ], + [ + 388, + 672, + 1 + ], + [ + 388, + 890, + 1 + ], + [ + 391, + 586, + -1 + ], + [ + 392, + 909, + -1 + ], + [ + 393, + 638, + 1 + ], + [ + 393, + 641, + 1 + ], + [ + 394, + 395, + 1 + ], + [ + 394, + 837, + -1 + ], + [ + 396, + 604, + 1 + ], + [ + 397, + 614, + 1 + ], + [ + 397, + 679, + 1 + ], + [ + 397, + 819, + -1 + ], + [ + 397, + 847, + -1 + ], + [ + 397, + 882, + 1 + ], + [ + 398, + 785, + -1 + ], + [ + 398, + 817, + -1 + ], + [ + 400, + 827, + -1 + ], + [ + 400, + 941, + 1 + ], + [ + 401, + 520, + -1 + ], + [ + 402, + 641, + 1 + ], + [ + 402, + 675, + -1 + ], + [ + 402, + 764, + -1 + ], + [ + 402, + 846, + 1 + ], + [ + 402, + 932, + 1 + ], + [ + 403, + 569, + -1 + ], + [ + 403, + 680, + -1 + ], + [ + 404, + 591, + -1 + ], + [ + 404, + 649, + 1 + ], + [ + 404, + 743, + -1 + ], + [ + 404, + 912, + -1 + ], + [ + 405, + 477, + -1 + ], + [ + 405, + 657, + 1 + ], + [ + 405, + 714, + -1 + ], + [ + 406, + 456, + -1 + ], + [ + 406, + 845, + 1 + ], + [ + 407, + 488, + 1 + ], + [ + 407, + 910, + 1 + ], + [ + 408, + 468, + 1 + ], + [ + 408, + 525, + 1 + ], + [ + 410, + 579, + 1 + ], + [ + 410, + 684, + -1 + ], + [ + 410, + 688, + 1 + ], + [ + 410, + 698, + 1 + ], + [ + 410, + 791, + -1 + ], + [ + 410, + 889, + 1 + ], + [ + 411, + 616, + -1 + ], + [ + 412, + 826, + -1 + ], + [ + 412, + 1019, + -1 + ], + [ + 413, + 480, + 1 + ], + [ + 413, + 613, + -1 + ], + [ + 414, + 805, + -1 + ], + [ + 414, + 835, + -1 + ], + [ + 414, + 920, + 1 + ], + [ + 415, + 822, + -1 + ], + [ + 415, + 878, + -1 + ], + [ + 415, + 879, + -1 + ], + [ + 415, + 1011, + -1 + ], + [ + 416, + 674, + -1 + ], + [ + 416, + 887, + -1 + ], + [ + 416, + 935, + -1 + ], + [ + 417, + 664, + -1 + ], + [ + 417, + 675, + 1 + ], + [ + 417, + 960, + 1 + ], + [ + 418, + 1009, + -1 + ], + [ + 418, + 1011, + 1 + ], + [ + 419, + 881, + 1 + ], + [ + 419, + 975, + -1 + ], + [ + 420, + 574, + 1 + ], + [ + 420, + 663, + 1 + ], + [ + 420, + 911, + -1 + ], + [ + 421, + 477, + -1 + ], + [ + 421, + 766, + -1 + ], + [ + 421, + 827, + 1 + ], + [ + 421, + 903, + 1 + ], + [ + 422, + 583, + 1 + ], + [ + 422, + 983, + 1 + ], + [ + 423, + 707, + -1 + ], + [ + 423, + 736, + -1 + ], + [ + 423, + 744, + 1 + ], + [ + 424, + 842, + -1 + ], + [ + 425, + 698, + -1 + ], + [ + 426, + 714, + 1 + ], + [ + 428, + 450, + -1 + ], + [ + 428, + 662, + -1 + ], + [ + 428, + 666, + 1 + ], + [ + 428, + 724, + -1 + ], + [ + 428, + 902, + -1 + ], + [ + 428, + 955, + 1 + ], + [ + 428, + 959, + -1 + ], + [ + 429, + 468, + 1 + ], + [ + 429, + 553, + -1 + ], + [ + 429, + 733, + 1 + ], + [ + 429, + 960, + 1 + ], + [ + 430, + 453, + 1 + ], + [ + 431, + 645, + -1 + ], + [ + 431, + 918, + -1 + ], + [ + 432, + 668, + 1 + ], + [ + 432, + 953, + -1 + ], + [ + 433, + 555, + 1 + ], + [ + 433, + 691, + 1 + ], + [ + 434, + 470, + -1 + ], + [ + 434, + 571, + -1 + ], + [ + 434, + 610, + 1 + ], + [ + 434, + 662, + 1 + ], + [ + 435, + 921, + -1 + ], + [ + 436, + 651, + -1 + ], + [ + 437, + 764, + 1 + ], + [ + 437, + 957, + 1 + ], + [ + 438, + 498, + -1 + ], + [ + 439, + 508, + -1 + ], + [ + 439, + 671, + -1 + ], + [ + 439, + 894, + 1 + ], + [ + 440, + 797, + 1 + ], + [ + 440, + 807, + 1 + ], + [ + 440, + 855, + 1 + ], + [ + 440, + 996, + 1 + ], + [ + 442, + 637, + 1 + ], + [ + 442, + 1012, + -1 + ], + [ + 442, + 1013, + 1 + ], + [ + 443, + 814, + 1 + ], + [ + 443, + 824, + -1 + ], + [ + 444, + 698, + -1 + ], + [ + 444, + 727, + -1 + ], + [ + 444, + 973, + 1 + ], + [ + 445, + 562, + 1 + ], + [ + 445, + 1011, + 1 + ], + [ + 446, + 463, + -1 + ], + [ + 446, + 681, + 1 + ], + [ + 446, + 756, + -1 + ], + [ + 447, + 777, + -1 + ], + [ + 448, + 541, + 1 + ], + [ + 448, + 613, + -1 + ], + [ + 448, + 819, + -1 + ], + [ + 448, + 997, + -1 + ], + [ + 449, + 586, + 1 + ], + [ + 449, + 655, + -1 + ], + [ + 449, + 910, + 1 + ], + [ + 449, + 967, + -1 + ], + [ + 449, + 1001, + -1 + ], + [ + 450, + 739, + 1 + ], + [ + 450, + 792, + 1 + ], + [ + 450, + 920, + -1 + ], + [ + 451, + 576, + -1 + ], + [ + 451, + 594, + 1 + ], + [ + 451, + 611, + 1 + ], + [ + 451, + 638, + 1 + ], + [ + 452, + 625, + -1 + ], + [ + 452, + 1007, + 1 + ], + [ + 453, + 889, + 1 + ], + [ + 453, + 1004, + -1 + ], + [ + 455, + 559, + -1 + ], + [ + 455, + 581, + -1 + ], + [ + 455, + 735, + -1 + ], + [ + 455, + 794, + 1 + ], + [ + 456, + 736, + 1 + ], + [ + 456, + 835, + -1 + ], + [ + 457, + 545, + 1 + ], + [ + 458, + 768, + -1 + ], + [ + 458, + 783, + 1 + ], + [ + 459, + 906, + -1 + ], + [ + 459, + 953, + 1 + ], + [ + 460, + 493, + -1 + ], + [ + 460, + 666, + 1 + ], + [ + 460, + 712, + -1 + ], + [ + 460, + 811, + -1 + ], + [ + 461, + 814, + -1 + ], + [ + 461, + 867, + -1 + ], + [ + 461, + 1005, + -1 + ], + [ + 462, + 935, + -1 + ], + [ + 463, + 632, + 1 + ], + [ + 463, + 777, + 1 + ], + [ + 464, + 513, + 1 + ], + [ + 464, + 750, + 1 + ], + [ + 466, + 497, + -1 + ], + [ + 466, + 719, + -1 + ], + [ + 467, + 715, + -1 + ], + [ + 467, + 950, + 1 + ], + [ + 469, + 559, + -1 + ], + [ + 469, + 678, + -1 + ], + [ + 469, + 738, + -1 + ], + [ + 470, + 531, + 1 + ], + [ + 470, + 584, + -1 + ], + [ + 471, + 539, + -1 + ], + [ + 471, + 867, + 1 + ], + [ + 471, + 1012, + -1 + ], + [ + 472, + 847, + -1 + ], + [ + 472, + 929, + 1 + ], + [ + 473, + 671, + -1 + ], + [ + 473, + 844, + -1 + ], + [ + 475, + 515, + -1 + ], + [ + 475, + 682, + -1 + ], + [ + 475, + 978, + 1 + ], + [ + 477, + 604, + 1 + ], + [ + 477, + 660, + 1 + ], + [ + 477, + 685, + -1 + ], + [ + 477, + 822, + -1 + ], + [ + 478, + 827, + 1 + ], + [ + 478, + 832, + -1 + ], + [ + 479, + 649, + -1 + ], + [ + 479, + 894, + -1 + ], + [ + 480, + 615, + 1 + ], + [ + 480, + 807, + -1 + ], + [ + 480, + 827, + 1 + ], + [ + 481, + 827, + -1 + ], + [ + 482, + 588, + 1 + ], + [ + 484, + 590, + 1 + ], + [ + 484, + 792, + 1 + ], + [ + 484, + 977, + 1 + ], + [ + 484, + 996, + -1 + ], + [ + 485, + 489, + 1 + ], + [ + 485, + 491, + -1 + ], + [ + 486, + 756, + -1 + ], + [ + 486, + 843, + 1 + ], + [ + 486, + 944, + 1 + ], + [ + 487, + 832, + 1 + ], + [ + 487, + 857, + 1 + ], + [ + 488, + 1021, + -1 + ], + [ + 489, + 706, + -1 + ], + [ + 489, + 791, + 1 + ], + [ + 489, + 968, + -1 + ], + [ + 490, + 637, + -1 + ], + [ + 490, + 993, + 1 + ], + [ + 491, + 531, + -1 + ], + [ + 491, + 582, + -1 + ], + [ + 491, + 776, + 1 + ], + [ + 492, + 613, + -1 + ], + [ + 492, + 655, + 1 + ], + [ + 492, + 982, + -1 + ], + [ + 492, + 987, + -1 + ], + [ + 492, + 1003, + 1 + ], + [ + 492, + 1023, + -1 + ], + [ + 493, + 630, + -1 + ], + [ + 493, + 1018, + -1 + ], + [ + 495, + 970, + -1 + ], + [ + 495, + 979, + -1 + ], + [ + 496, + 586, + 1 + ], + [ + 496, + 875, + 1 + ], + [ + 497, + 947, + 1 + ], + [ + 498, + 565, + -1 + ], + [ + 498, + 848, + 1 + ], + [ + 498, + 877, + 1 + ], + [ + 499, + 580, + -1 + ], + [ + 499, + 612, + 1 + ], + [ + 499, + 757, + -1 + ], + [ + 499, + 811, + -1 + ], + [ + 499, + 835, + 1 + ], + [ + 500, + 517, + -1 + ], + [ + 500, + 853, + 1 + ], + [ + 501, + 774, + -1 + ], + [ + 502, + 545, + 1 + ], + [ + 502, + 600, + -1 + ], + [ + 502, + 757, + 1 + ], + [ + 502, + 907, + -1 + ], + [ + 502, + 980, + -1 + ], + [ + 504, + 683, + 1 + ], + [ + 504, + 849, + 1 + ], + [ + 505, + 527, + 1 + ], + [ + 505, + 886, + -1 + ], + [ + 506, + 924, + 1 + ], + [ + 506, + 1004, + 1 + ], + [ + 507, + 600, + 1 + ], + [ + 507, + 685, + -1 + ], + [ + 507, + 720, + 1 + ], + [ + 507, + 955, + 1 + ], + [ + 508, + 546, + -1 + ], + [ + 508, + 976, + 1 + ], + [ + 509, + 811, + 1 + ], + [ + 509, + 918, + 1 + ], + [ + 510, + 521, + 1 + ], + [ + 511, + 887, + -1 + ], + [ + 511, + 892, + -1 + ], + [ + 511, + 980, + -1 + ], + [ + 512, + 678, + 1 + ], + [ + 512, + 1003, + -1 + ], + [ + 513, + 707, + -1 + ], + [ + 513, + 908, + 1 + ], + [ + 514, + 634, + 1 + ], + [ + 515, + 704, + -1 + ], + [ + 515, + 834, + -1 + ], + [ + 516, + 653, + 1 + ], + [ + 516, + 693, + -1 + ], + [ + 518, + 639, + 1 + ], + [ + 518, + 801, + 1 + ], + [ + 518, + 992, + 1 + ], + [ + 518, + 996, + -1 + ], + [ + 518, + 1002, + 1 + ], + [ + 519, + 664, + -1 + ], + [ + 519, + 712, + 1 + ], + [ + 520, + 644, + -1 + ], + [ + 520, + 800, + -1 + ], + [ + 521, + 777, + -1 + ], + [ + 522, + 539, + 1 + ], + [ + 522, + 596, + -1 + ], + [ + 522, + 767, + -1 + ], + [ + 523, + 664, + 1 + ], + [ + 523, + 854, + -1 + ], + [ + 523, + 974, + 1 + ], + [ + 524, + 614, + -1 + ], + [ + 524, + 789, + -1 + ], + [ + 526, + 685, + 1 + ], + [ + 527, + 851, + 1 + ], + [ + 527, + 954, + 1 + ], + [ + 527, + 1019, + 1 + ], + [ + 528, + 709, + 1 + ], + [ + 528, + 760, + 1 + ], + [ + 528, + 925, + -1 + ], + [ + 528, + 1006, + 1 + ], + [ + 529, + 693, + 1 + ], + [ + 529, + 1012, + -1 + ], + [ + 531, + 708, + -1 + ], + [ + 531, + 1007, + -1 + ], + [ + 532, + 691, + 1 + ], + [ + 533, + 570, + 1 + ], + [ + 533, + 688, + 1 + ], + [ + 534, + 998, + -1 + ], + [ + 534, + 1016, + -1 + ], + [ + 535, + 712, + -1 + ], + [ + 535, + 875, + 1 + ], + [ + 536, + 1014, + -1 + ], + [ + 538, + 706, + -1 + ], + [ + 538, + 899, + -1 + ], + [ + 539, + 795, + 1 + ], + [ + 540, + 782, + -1 + ], + [ + 541, + 752, + 1 + ], + [ + 541, + 964, + -1 + ], + [ + 542, + 588, + -1 + ], + [ + 542, + 609, + -1 + ], + [ + 542, + 674, + 1 + ], + [ + 542, + 750, + -1 + ], + [ + 543, + 928, + 1 + ], + [ + 544, + 695, + 1 + ], + [ + 545, + 688, + 1 + ], + [ + 545, + 990, + -1 + ], + [ + 545, + 1001, + 1 + ], + [ + 546, + 834, + 1 + ], + [ + 547, + 662, + 1 + ], + [ + 547, + 689, + 1 + ], + [ + 547, + 792, + 1 + ], + [ + 548, + 606, + 1 + ], + [ + 548, + 698, + 1 + ], + [ + 549, + 599, + 1 + ], + [ + 549, + 908, + 1 + ], + [ + 549, + 1020, + -1 + ], + [ + 550, + 708, + 1 + ], + [ + 550, + 948, + -1 + ], + [ + 551, + 602, + 1 + ], + [ + 551, + 883, + -1 + ], + [ + 552, + 788, + -1 + ], + [ + 553, + 978, + 1 + ], + [ + 553, + 1010, + 1 + ], + [ + 554, + 810, + 1 + ], + [ + 554, + 844, + 1 + ], + [ + 555, + 556, + 1 + ], + [ + 555, + 750, + -1 + ], + [ + 555, + 793, + -1 + ], + [ + 555, + 837, + 1 + ], + [ + 555, + 947, + 1 + ], + [ + 555, + 1009, + 1 + ], + [ + 556, + 710, + 1 + ], + [ + 556, + 738, + 1 + ], + [ + 556, + 933, + 1 + ], + [ + 557, + 663, + -1 + ], + [ + 558, + 625, + 1 + ], + [ + 559, + 629, + 1 + ], + [ + 559, + 823, + 1 + ], + [ + 560, + 1001, + -1 + ], + [ + 560, + 1015, + -1 + ], + [ + 561, + 826, + 1 + ], + [ + 561, + 882, + -1 + ], + [ + 563, + 975, + 1 + ], + [ + 565, + 568, + -1 + ], + [ + 565, + 767, + 1 + ], + [ + 565, + 805, + -1 + ], + [ + 565, + 888, + -1 + ], + [ + 566, + 588, + -1 + ], + [ + 568, + 801, + 1 + ], + [ + 568, + 802, + 1 + ], + [ + 568, + 925, + -1 + ], + [ + 568, + 967, + 1 + ], + [ + 569, + 770, + -1 + ], + [ + 569, + 905, + -1 + ], + [ + 569, + 927, + 1 + ], + [ + 569, + 936, + 1 + ], + [ + 570, + 749, + -1 + ], + [ + 570, + 796, + -1 + ], + [ + 570, + 968, + -1 + ], + [ + 571, + 614, + -1 + ], + [ + 571, + 924, + -1 + ], + [ + 572, + 573, + 1 + ], + [ + 572, + 947, + -1 + ], + [ + 574, + 648, + 1 + ], + [ + 574, + 947, + 1 + ], + [ + 575, + 898, + -1 + ], + [ + 576, + 728, + 1 + ], + [ + 576, + 777, + -1 + ], + [ + 577, + 655, + -1 + ], + [ + 578, + 690, + -1 + ], + [ + 578, + 804, + 1 + ], + [ + 578, + 848, + 1 + ], + [ + 578, + 1007, + -1 + ], + [ + 579, + 760, + -1 + ], + [ + 579, + 838, + -1 + ], + [ + 581, + 585, + -1 + ], + [ + 581, + 685, + -1 + ], + [ + 581, + 891, + -1 + ], + [ + 581, + 916, + 1 + ], + [ + 581, + 929, + 1 + ], + [ + 582, + 593, + -1 + ], + [ + 582, + 749, + 1 + ], + [ + 582, + 972, + -1 + ], + [ + 583, + 631, + -1 + ], + [ + 583, + 959, + 1 + ], + [ + 584, + 649, + -1 + ], + [ + 584, + 724, + 1 + ], + [ + 584, + 905, + -1 + ], + [ + 585, + 611, + 1 + ], + [ + 585, + 663, + -1 + ], + [ + 586, + 866, + -1 + ], + [ + 586, + 940, + 1 + ], + [ + 587, + 737, + -1 + ], + [ + 587, + 854, + -1 + ], + [ + 588, + 796, + 1 + ], + [ + 588, + 838, + -1 + ], + [ + 589, + 799, + 1 + ], + [ + 589, + 939, + -1 + ], + [ + 590, + 996, + -1 + ], + [ + 591, + 922, + -1 + ], + [ + 591, + 969, + 1 + ], + [ + 593, + 688, + 1 + ], + [ + 593, + 754, + -1 + ], + [ + 593, + 974, + 1 + ], + [ + 594, + 968, + -1 + ], + [ + 595, + 645, + -1 + ], + [ + 595, + 696, + -1 + ], + [ + 595, + 768, + 1 + ], + [ + 596, + 653, + 1 + ], + [ + 596, + 820, + 1 + ], + [ + 597, + 631, + 1 + ], + [ + 597, + 907, + -1 + ], + [ + 597, + 960, + -1 + ], + [ + 598, + 709, + 1 + ], + [ + 599, + 628, + -1 + ], + [ + 599, + 747, + 1 + ], + [ + 599, + 1007, + -1 + ], + [ + 600, + 618, + -1 + ], + [ + 600, + 917, + -1 + ], + [ + 601, + 751, + 1 + ], + [ + 601, + 759, + -1 + ], + [ + 601, + 877, + 1 + ], + [ + 602, + 855, + -1 + ], + [ + 602, + 919, + 1 + ], + [ + 603, + 626, + -1 + ], + [ + 603, + 785, + -1 + ], + [ + 604, + 732, + -1 + ], + [ + 605, + 749, + -1 + ], + [ + 605, + 811, + 1 + ], + [ + 605, + 867, + -1 + ], + [ + 605, + 911, + 1 + ], + [ + 605, + 912, + -1 + ], + [ + 605, + 982, + -1 + ], + [ + 606, + 613, + -1 + ], + [ + 607, + 747, + -1 + ], + [ + 607, + 790, + -1 + ], + [ + 607, + 810, + 1 + ], + [ + 608, + 728, + 1 + ], + [ + 608, + 977, + -1 + ], + [ + 609, + 626, + -1 + ], + [ + 610, + 752, + 1 + ], + [ + 610, + 916, + 1 + ], + [ + 610, + 921, + 1 + ], + [ + 610, + 942, + -1 + ], + [ + 610, + 976, + -1 + ], + [ + 611, + 798, + 1 + ], + [ + 611, + 1014, + 1 + ], + [ + 612, + 649, + 1 + ], + [ + 612, + 696, + -1 + ], + [ + 612, + 770, + -1 + ], + [ + 612, + 940, + -1 + ], + [ + 615, + 1004, + -1 + ], + [ + 616, + 710, + -1 + ], + [ + 616, + 752, + 1 + ], + [ + 618, + 902, + -1 + ], + [ + 619, + 695, + 1 + ], + [ + 619, + 775, + -1 + ], + [ + 619, + 895, + -1 + ], + [ + 620, + 844, + 1 + ], + [ + 621, + 921, + -1 + ], + [ + 621, + 954, + -1 + ], + [ + 623, + 718, + -1 + ], + [ + 624, + 692, + 1 + ], + [ + 627, + 632, + -1 + ], + [ + 627, + 696, + -1 + ], + [ + 627, + 703, + 1 + ], + [ + 627, + 778, + -1 + ], + [ + 627, + 967, + 1 + ], + [ + 628, + 726, + 1 + ], + [ + 629, + 795, + 1 + ], + [ + 629, + 1010, + -1 + ], + [ + 630, + 709, + 1 + ], + [ + 630, + 779, + 1 + ], + [ + 631, + 834, + -1 + ], + [ + 632, + 646, + 1 + ], + [ + 632, + 782, + -1 + ], + [ + 632, + 848, + -1 + ], + [ + 633, + 711, + -1 + ], + [ + 633, + 809, + 1 + ], + [ + 633, + 873, + -1 + ], + [ + 633, + 944, + -1 + ], + [ + 633, + 974, + -1 + ], + [ + 635, + 936, + 1 + ], + [ + 636, + 795, + 1 + ], + [ + 636, + 966, + 1 + ], + [ + 637, + 978, + -1 + ], + [ + 639, + 688, + 1 + ], + [ + 639, + 864, + -1 + ], + [ + 640, + 951, + 1 + ], + [ + 642, + 738, + 1 + ], + [ + 642, + 903, + -1 + ], + [ + 642, + 924, + 1 + ], + [ + 643, + 932, + -1 + ], + [ + 645, + 721, + 1 + ], + [ + 648, + 782, + 1 + ], + [ + 648, + 788, + 1 + ], + [ + 648, + 1001, + 1 + ], + [ + 649, + 986, + -1 + ], + [ + 651, + 930, + -1 + ], + [ + 652, + 697, + 1 + ], + [ + 653, + 656, + 1 + ], + [ + 653, + 818, + -1 + ], + [ + 653, + 874, + 1 + ], + [ + 654, + 733, + -1 + ], + [ + 655, + 665, + 1 + ], + [ + 655, + 876, + 1 + ], + [ + 655, + 969, + 1 + ], + [ + 655, + 999, + -1 + ], + [ + 656, + 702, + 1 + ], + [ + 656, + 717, + -1 + ], + [ + 658, + 787, + 1 + ], + [ + 661, + 663, + -1 + ], + [ + 661, + 825, + -1 + ], + [ + 661, + 956, + -1 + ], + [ + 662, + 690, + -1 + ], + [ + 662, + 828, + 1 + ], + [ + 663, + 698, + -1 + ], + [ + 663, + 919, + 1 + ], + [ + 663, + 958, + -1 + ], + [ + 663, + 965, + -1 + ], + [ + 664, + 717, + -1 + ], + [ + 664, + 726, + -1 + ], + [ + 664, + 862, + 1 + ], + [ + 664, + 952, + -1 + ], + [ + 664, + 1005, + 1 + ], + [ + 665, + 976, + -1 + ], + [ + 667, + 697, + -1 + ], + [ + 667, + 791, + 1 + ], + [ + 667, + 897, + 1 + ], + [ + 668, + 825, + 1 + ], + [ + 671, + 841, + 1 + ], + [ + 671, + 1008, + -1 + ], + [ + 672, + 695, + -1 + ], + [ + 672, + 984, + 1 + ], + [ + 673, + 817, + 1 + ], + [ + 673, + 1003, + -1 + ], + [ + 674, + 758, + 1 + ], + [ + 675, + 774, + 1 + ], + [ + 676, + 937, + -1 + ], + [ + 676, + 977, + -1 + ], + [ + 677, + 900, + -1 + ], + [ + 677, + 1008, + 1 + ], + [ + 678, + 736, + -1 + ], + [ + 679, + 708, + -1 + ], + [ + 680, + 950, + -1 + ], + [ + 680, + 1023, + 1 + ], + [ + 683, + 858, + -1 + ], + [ + 684, + 687, + -1 + ], + [ + 685, + 713, + -1 + ], + [ + 685, + 755, + 1 + ], + [ + 687, + 984, + 1 + ], + [ + 688, + 930, + 1 + ], + [ + 691, + 743, + -1 + ], + [ + 691, + 797, + -1 + ], + [ + 692, + 784, + 1 + ], + [ + 693, + 702, + 1 + ], + [ + 693, + 730, + -1 + ], + [ + 694, + 886, + 1 + ], + [ + 694, + 968, + 1 + ], + [ + 695, + 725, + 1 + ], + [ + 695, + 1018, + 1 + ], + [ + 696, + 819, + -1 + ], + [ + 697, + 730, + -1 + ], + [ + 698, + 957, + 1 + ], + [ + 699, + 711, + -1 + ], + [ + 700, + 1023, + -1 + ], + [ + 701, + 912, + 1 + ], + [ + 702, + 821, + -1 + ], + [ + 702, + 828, + -1 + ], + [ + 703, + 798, + 1 + ], + [ + 703, + 1013, + -1 + ], + [ + 705, + 879, + -1 + ], + [ + 706, + 806, + 1 + ], + [ + 708, + 801, + -1 + ], + [ + 708, + 940, + 1 + ], + [ + 708, + 1018, + 1 + ], + [ + 711, + 917, + -1 + ], + [ + 713, + 881, + 1 + ], + [ + 714, + 855, + 1 + ], + [ + 714, + 892, + 1 + ], + [ + 717, + 773, + 1 + ], + [ + 721, + 948, + -1 + ], + [ + 723, + 765, + 1 + ], + [ + 723, + 853, + -1 + ], + [ + 724, + 899, + 1 + ], + [ + 728, + 770, + 1 + ], + [ + 729, + 757, + 1 + ], + [ + 731, + 741, + 1 + ], + [ + 731, + 798, + -1 + ], + [ + 731, + 939, + -1 + ], + [ + 732, + 801, + 1 + ], + [ + 733, + 737, + 1 + ], + [ + 733, + 839, + -1 + ], + [ + 733, + 881, + -1 + ], + [ + 733, + 968, + 1 + ], + [ + 737, + 793, + 1 + ], + [ + 737, + 814, + -1 + ], + [ + 738, + 741, + 1 + ], + [ + 738, + 962, + 1 + ], + [ + 738, + 1003, + -1 + ], + [ + 740, + 780, + 1 + ], + [ + 741, + 853, + -1 + ], + [ + 741, + 1014, + 1 + ], + [ + 742, + 792, + 1 + ], + [ + 743, + 765, + -1 + ], + [ + 743, + 936, + -1 + ], + [ + 744, + 852, + 1 + ], + [ + 744, + 871, + 1 + ], + [ + 744, + 992, + -1 + ], + [ + 745, + 951, + 1 + ], + [ + 748, + 763, + -1 + ], + [ + 751, + 895, + -1 + ], + [ + 751, + 905, + -1 + ], + [ + 751, + 955, + 1 + ], + [ + 752, + 762, + -1 + ], + [ + 752, + 964, + -1 + ], + [ + 753, + 763, + -1 + ], + [ + 753, + 1010, + -1 + ], + [ + 755, + 879, + 1 + ], + [ + 756, + 968, + 1 + ], + [ + 759, + 964, + -1 + ], + [ + 760, + 1010, + -1 + ], + [ + 763, + 946, + 1 + ], + [ + 764, + 873, + -1 + ], + [ + 764, + 908, + -1 + ], + [ + 765, + 1004, + -1 + ], + [ + 766, + 912, + -1 + ], + [ + 767, + 876, + 1 + ], + [ + 768, + 776, + 1 + ], + [ + 768, + 1002, + -1 + ], + [ + 769, + 813, + -1 + ], + [ + 769, + 821, + 1 + ], + [ + 769, + 840, + -1 + ], + [ + 770, + 848, + 1 + ], + [ + 773, + 957, + 1 + ], + [ + 775, + 804, + -1 + ], + [ + 775, + 854, + 1 + ], + [ + 778, + 780, + 1 + ], + [ + 779, + 946, + -1 + ], + [ + 780, + 793, + 1 + ], + [ + 780, + 818, + 1 + ], + [ + 780, + 953, + 1 + ], + [ + 780, + 959, + 1 + ], + [ + 783, + 953, + 1 + ], + [ + 784, + 857, + 1 + ], + [ + 786, + 849, + -1 + ], + [ + 787, + 833, + 1 + ], + [ + 788, + 906, + 1 + ], + [ + 789, + 855, + -1 + ], + [ + 790, + 826, + -1 + ], + [ + 791, + 852, + -1 + ], + [ + 791, + 1000, + 1 + ], + [ + 792, + 910, + 1 + ], + [ + 792, + 914, + 1 + ], + [ + 792, + 970, + -1 + ], + [ + 794, + 839, + 1 + ], + [ + 795, + 985, + -1 + ], + [ + 796, + 872, + 1 + ], + [ + 796, + 946, + 1 + ], + [ + 797, + 993, + -1 + ], + [ + 798, + 881, + -1 + ], + [ + 798, + 928, + -1 + ], + [ + 800, + 804, + -1 + ], + [ + 802, + 990, + -1 + ], + [ + 803, + 992, + -1 + ], + [ + 805, + 871, + -1 + ], + [ + 805, + 990, + -1 + ], + [ + 813, + 901, + -1 + ], + [ + 813, + 998, + -1 + ], + [ + 815, + 916, + -1 + ], + [ + 816, + 882, + 1 + ], + [ + 816, + 979, + 1 + ], + [ + 817, + 990, + 1 + ], + [ + 823, + 927, + 1 + ], + [ + 827, + 961, + 1 + ], + [ + 829, + 922, + -1 + ], + [ + 830, + 921, + -1 + ], + [ + 831, + 952, + 1 + ], + [ + 834, + 1008, + -1 + ], + [ + 836, + 1014, + -1 + ], + [ + 837, + 880, + 1 + ], + [ + 839, + 899, + 1 + ], + [ + 842, + 993, + 1 + ], + [ + 845, + 853, + 1 + ], + [ + 854, + 982, + 1 + ], + [ + 856, + 938, + 1 + ], + [ + 856, + 989, + 1 + ], + [ + 857, + 1004, + 1 + ], + [ + 859, + 908, + 1 + ], + [ + 860, + 948, + -1 + ], + [ + 861, + 903, + 1 + ], + [ + 862, + 902, + 1 + ], + [ + 863, + 1008, + -1 + ], + [ + 864, + 890, + 1 + ], + [ + 871, + 888, + 1 + ], + [ + 872, + 910, + -1 + ], + [ + 873, + 954, + -1 + ], + [ + 873, + 1010, + 1 + ], + [ + 874, + 971, + -1 + ], + [ + 876, + 1019, + -1 + ], + [ + 878, + 978, + 1 + ], + [ + 880, + 994, + 1 + ], + [ + 884, + 1000, + 1 + ], + [ + 885, + 903, + 1 + ], + [ + 886, + 900, + -1 + ], + [ + 889, + 996, + 1 + ], + [ + 892, + 903, + 1 + ], + [ + 892, + 997, + -1 + ], + [ + 895, + 979, + -1 + ], + [ + 896, + 1000, + 1 + ], + [ + 898, + 995, + 1 + ], + [ + 901, + 947, + -1 + ], + [ + 902, + 938, + -1 + ], + [ + 903, + 925, + 1 + ], + [ + 904, + 926, + -1 + ], + [ + 906, + 931, + -1 + ], + [ + 906, + 936, + 1 + ], + [ + 913, + 1021, + 1 + ], + [ + 914, + 928, + -1 + ], + [ + 915, + 956, + 1 + ], + [ + 915, + 1013, + -1 + ], + [ + 921, + 970, + -1 + ], + [ + 922, + 966, + 1 + ], + [ + 923, + 934, + -1 + ], + [ + 925, + 1016, + -1 + ], + [ + 927, + 955, + -1 + ], + [ + 928, + 975, + -1 + ], + [ + 928, + 997, + -1 + ], + [ + 932, + 971, + 1 + ], + [ + 939, + 1010, + 1 + ], + [ + 941, + 985, + -1 + ], + [ + 948, + 967, + 1 + ], + [ + 959, + 1003, + 1 + ], + [ + 959, + 1019, + 1 + ], + [ + 979, + 1008, + -1 + ], + [ + 987, + 1016, + 1 + ], + [ + 992, + 1011, + 1 + ], + [ + 993, + 1013, + 1 + ], + [ + 993, + 1021, + -1 + ], + [ + 993, + 1023, + -1 + ], + [ + 998, + 1014, + -1 + ], + [ + 1009, + 1022, + 1 + ] + ], + "field": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "cost": 0 + }, + "field_bound": 11, + "seed": "10", + "chains": [ + { + "chain": "614480483733483466", + "start": "++---+--00+-+++++00+0-+---0++-+0+00--++++0+-0+0+0+0+++0++-00-++0000-++++0+00--0+-+0+++00+0-++000+---0-0---0+-++-++0-00++++000-00+-+0-0-0+-+--00-00--++++++-000+00000-0++0-0++-0++00-+00-00-00+00-0++---0--0++0-+--000-----++-+--0+0+0+0-0-0-+--+-+-+-0--0++0+0--------+0-0+-+000----+-+0+------+-00+0++-00+0+0+--00+-000++0+00+-+00++0--+-00++---0-+-++-0+++++0-0-0-+-+-+-------0+00-+0++-+0+0+++0+0+00+-+-0+-0+--+000+++-0-++-+++-0++-0+-00--0-0+--+--+-+0---+----+00+-++++0--+0-0-++-+++0-0---0++-0++000--+----0+++00-+-00+-0--00++++0-0-----0--0-0+-0++---+--++--0-0-+-0++-00-+--0+--+-+0-000-+00-0-++0-0++0+-+-+-+--+-+000+0++-++0++0----+00000-+0-+0+--+-----00+++-+--000++++0-00++0++00+-+--0-0-0-00+--00+000+-0+--0+-+0--000+-0+0++0-++0+0-++0+-00+---+-+0+---0-+---0++-000-+0---0--0+++0++0+-0++-+0+0-+-00+0++0-0--+00+00-00++-+++00+0+--0--++-+++00+0-0+00-+0-0+-00-0--+++-0+++00++0000-+0-0-0++-++0-00--0-+++00--00--0+-00+0-+++0-0+-+++00+-+---000+00-++00-0-0-0-----0-+++-+---0--0+000+0-0-0+--+--+0--++++----+0+0+0+00-+0-+0-000+-0+-+00-0-+-000++-", + "expect": { + "state": "+-+++00-----+-0--00-+0+--+--0++--+--0-+-0--+----0++++-0--0--0---++-++--+0-++0-+--+++-++-++--0+++--+++--+-+-0--+00--0-0+0+-++-0-+-+-0-+-+00--+-00+++0-00---0----+0-+---0----0+-++---++-++--++-++00----0--0++--+-+++0-+++-+---++0++--------++-+-++--+---00-0-00+-0+-0----+0+0+-+-++-+-0-++++00++-+0--++---0+-++++0+-00+-++-0+-++00-0++0-0+-0+0+0-+0++-+0+0+0----+-+0+--+-++-----0--+-+---+-0+000+----+++++---++--0+-0-+-0-+++++0+--+-0++--00-0++0-+0-+-+++00++0-0++---+++0++---+0--0-++++--00-+-+--+0+--0---+-0+-00+-+++000-00-+--000----+0-++-+++-+++----+-00++-00+0000-+++++--+--+-+-0--0+--0+++00-+-0+0-++-+--0-0-0--0-000-+-+0++-++00+0+++--0-+-+00---0-0+-+-+--+-0+0-0++0+-0+--0-0+---++-+++-0++0++--++0--00++--+++00-++00+++0-0+0-00+-0--+00-++--0+0+0+--+0-+-0-0---++--0+-0-00++--0--+0++--++--0+-+++0--0--00+0-0+0---0+-+-0---------+----+-0+--++--+----000++00+0--00-++-+--0+0++-++++-+0+--0-0++++--0++0++----0++++-0+0+---++---++++--+--0-0--+0-+-++0+-+--+0+0+0+-++++00+++++++-000--++-+-++++++++++-0++-0--000++----+++-++00-+--+-0---++++--+-+---++-++", + "accumulates": 1634400, + "draws": 409600, + "term": "bound", + "state_hash": "7a338b3d8c298957" + } + }, + { + "chain": "13546682927695711814", + "start": "--00000+00--0-0+0+--++--++++0-+++0++++0---+-00++0+--0---++---+--+++0+00000+0++++000+0+---+0+0--++00+0+++-0000-00++-000-0-00-++000-+0+++0+0-0+00+-+--0+----+-++0-+00--0+--+-+000++-00---0-0-0--000---+-000+0-+-0++0+0000-+---00-0++--0+--+0+-0-+--+++-++-+0++00+++--0--0-+0--0-0+-++0+++-0+0--0+0-0+00--++0----++++0000-0-++0-00---0-0-0+-+0-0-00++++-0000++0+0--0-+--00---000--0-0+0++-+0++-00-+-+--+--00-00-+-+0++0--+---+0+-0-0----+-0++-0000+00+-0-0++++000--0-++-0-+++--00++00+--++-+0+0-+-++0+000--00+-0+-0-00+00++0---0--+-0+-0+-+-000--0-00-0+--00++-0++0++0+-+0000+++-+-+++-00+0---+++0-0+++++0++0-+0-0--+--+--00+00-+0-++0---+++0+++000--0++--000--+-0--+--0+0-0----0-00+-0--000000++--0-+++-0-0-0-+0-0000+0+0-0+-0--+----+0-0+++---00--+0+-0--00-0-+0-00--00-0+0000000+--0-------00-+0++-+-+0--+-0-+-+--++++++---0+0-00000+0---+0-----+-+-0-+++++0-0++++--0-+--0+000--0+-+00-0++-+--+---0----+-0---++00+-00+0-0+0000++--0+0+00+0+-+-00+00++--0-0+--000-0-0000+--+-+++++0000+++00-+0000---0+-00+--000-+00+000-++0++-+000+000-+--+0+0-00+++-+++00-0+0++-", + "expect": { + "state": "+--0++-+++---+-+++++++0000-----+-0-++--0+--+-+-+0-++-+0++0+0---+--+00+++0--+-+--++0-+++-+--+-0--+0++----++---++++++---++-++---0-0+0++-00+00-0++-0+0-+0-++++-00++0-0-+0+-+---0++-++-++0--++++---+-++++++0-00++00+----+++0-00+-0---++0+++-++-0--+--0-++-+-++-+++-+-0+-+-+--+00-++-0+-+--0-+0--00---++--+-++0-+0+---+++0++--0+++-000--++-+--00-+-+-0-+-++++++0-++-++-++---++0--++0-+0+--++-+++--000-+--+0--++++++0++0+----0--++-0+--++00----++---+-00----+-+--+0---+++--+-0++++---+-+-+--+-+-+--+++++-0+-+---+0-+-+++---++000--++-++-0++++0+--+0++0+-++0+--0-0+-+-0++-+--0++--+++++-0+-0+++-++000+-+-+0++-++-+0--+0++-0++0+-+0+00-+-0+--0+0----0-+-0+-0-+--+-0+0-+++++0-+0++--++++-+-+0+++-+----+0-+0+-+-++0-++-+-++++0+-00+++-+--0---+-00+0-0---00++--+--0-------+--++-----0+0---0++0+++0+00-+-++0-+-++----++00-0+--+-+-0-+-0+-000000-++-++0+++-+-----++0+-0+0++---0---++++++-+-+-+-++0+-+0-00+-++-++000+-0-00-0++00--++----00---++-+++++-000---+---0-0++-++-+000+0+++0+-++0-+++-+++0+-+00-0-0--+++00--0--+--0+-+0++00-000+--+0--0+--0-0+-+0+--+--+--+00--++0+++++", + "accumulates": 1634400, + "draws": 409600, + "term": "bound", + "state_hash": "e917d0ac02f4e8c5" + } + } + ], + "held": [ + { + "temperature": 3.0, + "temperature_bits": "4008000000000000", + "sweeps": 200 + }, + { + "temperature": 1.5, + "temperature_bits": "3ff8000000000000", + "sweeps": 200 + } + ] + }, + { + "name": "fields-past-16-bits/at-0", + "landscape": { + "n": 4, + "couplings": [], + "field": [ + 65536, + 40000, + -70000, + 1 + ], + "cost": 5000 + }, + "field_bound": 70000, + "seed": "11", + "chains": [ + { + "chain": "5833679380957638813", + "start": "0+00", + "expect": { + "state": "++-0", + "accumulates": 0, + "draws": 0, + "term": "fixed-point", + "state_hash": "9e6dc390540988ba" + } + }, + { + "chain": "4839782808629744545", + "start": "-00-", + "expect": { + "state": "++-0", + "accumulates": 0, + "draws": 0, + "term": "fixed-point", + "state_hash": "9e6dc390540988ba" + } + }, + { + "chain": "11769803791402734189", + "start": "00+-", + "expect": { + "state": "++-0", + "accumulates": 0, + "draws": 0, + "term": "fixed-point", + "state_hash": "9e6dc390540988ba" + } + }, + { + "chain": "9308485889748266480", + "start": "-+-+", + "expect": { + "state": "++-0", + "accumulates": 0, + "draws": 0, + "term": "fixed-point", + "state_hash": "9e6dc390540988ba" + } + } + ], + "t0": 0.0, + "t0_bits": "0000000000000000", + "sweeps": 5 + }, + { + "name": "fields-past-16-bits/hot", + "landscape": { + "n": 4, + "couplings": [], + "field": [ + 65536, + 40000, + -70000, + 1 + ], + "cost": 5000 + }, + "field_bound": 70000, + "seed": "12", + "chains": [ + { + "chain": "10682531704454680323", + "start": "+00+", + "expect": { + "state": "++0-", + "accumulates": 0, + "draws": 16, + "term": "bound", + "state_hash": "9e6a5e905406a744" + } + }, + { + "chain": "17330038448952222807", + "start": "-++0", + "expect": { + "state": "-+-0", + "accumulates": 0, + "draws": 16, + "term": "bound", + "state_hash": "3bd27f7f93fe14ec" + } + }, + { + "chain": "4330166885954844398", + "start": "-00-", + "expect": { + "state": "+0-+", + "accumulates": 0, + "draws": 16, + "term": "bound", + "state_hash": "84715590455191d2" + } + }, + { + "chain": "16691334188567521737", + "start": "-+-0", + "expect": { + "state": "0+--", + "accumulates": 0, + "draws": 16, + "term": "bound", + "state_hash": "be7dc077516854ba" + } + }, + { + "chain": "15719682454639411054", + "start": "----", + "expect": { + "state": "++-0", + "accumulates": 0, + "draws": 16, + "term": "fixed-point", + "state_hash": "9e6dc390540988ba" + } + }, + { + "chain": "5425630273831533980", + "start": "+-00", + "expect": { + "state": "++++", + "accumulates": 0, + "draws": 16, + "term": "bound", + "state_hash": "9e749290540f5425" + } + }, + { + "chain": "2570129635822222897", + "start": "-0-0", + "expect": { + "state": "+0-0", + "accumulates": 0, + "draws": 16, + "term": "bound", + "state_hash": "847154904551901f" + } + }, + { + "chain": "13844750118391845279", + "start": "0-0+", + "expect": { + "state": "++-+", + "accumulates": 0, + "draws": 16, + "term": "bound", + "state_hash": "9e6dc29054098707" + } + } + ], + "held": [ + { + "temperature": 70000.0, + "temperature_bits": "40f1170000000000", + "sweeps": 4 + } + ] + }, + { + "name": "fields-past-32-bits/at-0", + "landscape": { + "n": 4, + "couplings": [], + "field": [ + 8589934592, + 6442450944, + -8589934597, + 1 + ], + "cost": 7 + }, + "field_bound": 8589934597, + "seed": "11", + "chains": [ + { + "chain": "5833679380957638813", + "start": "0+00", + "expect": { + "state": "++-0", + "accumulates": 0, + "draws": 0, + "term": "fixed-point", + "state_hash": "9e6dc390540988ba" + } + }, + { + "chain": "4839782808629744545", + "start": "-00-", + "expect": { + "state": "++-0", + "accumulates": 0, + "draws": 0, + "term": "fixed-point", + "state_hash": "9e6dc390540988ba" + } + }, + { + "chain": "11769803791402734189", + "start": "00+-", + "expect": { + "state": "++-0", + "accumulates": 0, + "draws": 0, + "term": "fixed-point", + "state_hash": "9e6dc390540988ba" + } + }, + { + "chain": "9308485889748266480", + "start": "-+-+", + "expect": { + "state": "++-0", + "accumulates": 0, + "draws": 0, + "term": "fixed-point", + "state_hash": "9e6dc390540988ba" + } + } + ], + "t0": 0.0, + "t0_bits": "0000000000000000", + "sweeps": 5 + }, + { + "name": "fields-past-32-bits/hot", + "landscape": { + "n": 4, + "couplings": [], + "field": [ + 8589934592, + 6442450944, + -8589934597, + 1 + ], + "cost": 7 + }, + "field_bound": 8589934597, + "seed": "12", + "chains": [ + { + "chain": "10682531704454680323", + "start": "+00+", + "expect": { + "state": "++0-", + "accumulates": 0, + "draws": 16, + "term": "bound", + "state_hash": "9e6a5e905406a744" + } + }, + { + "chain": "17330038448952222807", + "start": "-++0", + "expect": { + "state": "-+-0", + "accumulates": 0, + "draws": 16, + "term": "bound", + "state_hash": "3bd27f7f93fe14ec" + } + }, + { + "chain": "4330166885954844398", + "start": "-00-", + "expect": { + "state": "++-+", + "accumulates": 0, + "draws": 16, + "term": "bound", + "state_hash": "9e6dc29054098707" + } + }, + { + "chain": "16691334188567521737", + "start": "-+-0", + "expect": { + "state": "0+--", + "accumulates": 0, + "draws": 16, + "term": "bound", + "state_hash": "be7dc077516854ba" + } + }, + { + "chain": "15719682454639411054", + "start": "----", + "expect": { + "state": "++-0", + "accumulates": 0, + "draws": 16, + "term": "fixed-point", + "state_hash": "9e6dc390540988ba" + } + }, + { + "chain": "5425630273831533980", + "start": "+-00", + "expect": { + "state": "++++", + "accumulates": 0, + "draws": 16, + "term": "bound", + "state_hash": "9e749290540f5425" + } + }, + { + "chain": "2570129635822222897", + "start": "-0-0", + "expect": { + "state": "++-+", + "accumulates": 0, + "draws": 16, + "term": "bound", + "state_hash": "9e6dc29054098707" + } + }, + { + "chain": "13844750118391845279", + "start": "0-0+", + "expect": { + "state": "++-+", + "accumulates": 0, + "draws": 16, + "term": "bound", + "state_hash": "9e6dc29054098707" + } + } + ], + "held": [ + { + "temperature": 8589934597.0, + "temperature_bits": "4200000000280000", + "sweeps": 4 + } + ] + }, + { + "name": "field-bound-at-the-line/at-0", + "landscape": { + "n": 3, + "couplings": [ + [ + 0, + 1, + 1 + ], + [ + 0, + 2, + 1 + ] + ], + "field": [ + 2147483645, + 1, + 1 + ], + "cost": 0 + }, + "field_bound": 2147483647, + "seed": "11", + "chains": [ + { + "chain": "5833679380957638813", + "start": "0+0", + "expect": { + "state": "+++", + "accumulates": 20, + "draws": 0, + "term": "fixed-point", + "state_hash": "eaa6d61875e4f6c5" + } + }, + { + "chain": "4839782808629744545", + "start": "-00", + "expect": { + "state": "+++", + "accumulates": 20, + "draws": 0, + "term": "fixed-point", + "state_hash": "eaa6d61875e4f6c5" + } + }, + { + "chain": "11769803791402734189", + "start": "00+", + "expect": { + "state": "+++", + "accumulates": 20, + "draws": 0, + "term": "fixed-point", + "state_hash": "eaa6d61875e4f6c5" + } + }, + { + "chain": "9308485889748266480", + "start": "-+-", + "expect": { + "state": "+++", + "accumulates": 20, + "draws": 0, + "term": "fixed-point", + "state_hash": "eaa6d61875e4f6c5" + } + } + ], + "t0": 0.0, + "t0_bits": "0000000000000000", + "sweeps": 5 + }, + { + "name": "field-bound-at-the-line/hot", + "landscape": { + "n": 3, + "couplings": [ + [ + 0, + 1, + 1 + ], + [ + 0, + 2, + 1 + ] + ], + "field": [ + 2147483645, + 1, + 1 + ], + "cost": 0 + }, + "field_bound": 2147483647, + "seed": "12", + "chains": [ + { + "chain": "10682531704454680323", + "start": "+00", + "expect": { + "state": "0-+", + "accumulates": 16, + "draws": 12, + "term": "bound", + "state_hash": "d0a3991867273472" + } + }, + { + "chain": "17330038448952222807", + "start": "-++", + "expect": { + "state": "++0", + "accumulates": 16, + "draws": 12, + "term": "bound", + "state_hash": "eaa6d31875e4f1ac" + } + }, + { + "chain": "4330166885954844398", + "start": "-00", + "expect": { + "state": "0-0", + "accumulates": 16, + "draws": 12, + "term": "bound", + "state_hash": "d0a39818672732bf" + } + }, + { + "chain": "16691334188567521737", + "start": "-+-", + "expect": { + "state": "+--", + "accumulates": 16, + "draws": 12, + "term": "bound", + "state_hash": "eaa0081875df2d0d" + } + }, + { + "chain": "15719682454639411054", + "start": "---", + "expect": { + "state": "+00", + "accumulates": 16, + "draws": 12, + "term": "bound", + "state_hash": "ea9ca31875dc4b97" + } + }, + { + "chain": "5425630273831533980", + "start": "+-0", + "expect": { + "state": "-0+", + "accumulates": 16, + "draws": 12, + "term": "bound", + "state_hash": "d949aa186c0c4928" + } + }, + { + "chain": "2570129635822222897", + "start": "-0-", + "expect": { + "state": "+00", + "accumulates": 16, + "draws": 12, + "term": "bound", + "state_hash": "ea9ca31875dc4b97" + } + }, + { + "chain": "13844750118391845279", + "start": "0-0", + "expect": { + "state": "+0+", + "accumulates": 16, + "draws": 12, + "term": "bound", + "state_hash": "ea9ca41875dc4d4a" + } + } + ], + "held": [ + { + "temperature": 2147483645.0, + "temperature_bits": "41dfffffff400000", + "sweeps": 4 + } + ] + }, + { + "name": "field-bound-at-the-line-below-zero/at-0", + "landscape": { + "n": 3, + "couplings": [ + [ + 0, + 1, + 1 + ], + [ + 0, + 2, + 1 + ] + ], + "field": [ + -2147483645, + -1, + -1 + ], + "cost": 0 + }, + "field_bound": 2147483647, + "seed": "11", + "chains": [ + { + "chain": "5833679380957638813", + "start": "0+0", + "expect": { + "state": "---", + "accumulates": 20, + "draws": 0, + "term": "fixed-point", + "state_hash": "d94d12186c0f2fb7" + } + }, + { + "chain": "4839782808629744545", + "start": "-00", + "expect": { + "state": "---", + "accumulates": 20, + "draws": 0, + "term": "fixed-point", + "state_hash": "d94d12186c0f2fb7" + } + }, + { + "chain": "11769803791402734189", + "start": "00+", + "expect": { + "state": "---", + "accumulates": 20, + "draws": 0, + "term": "fixed-point", + "state_hash": "d94d12186c0f2fb7" + } + }, + { + "chain": "9308485889748266480", + "start": "-+-", + "expect": { + "state": "---", + "accumulates": 20, + "draws": 0, + "term": "fixed-point", + "state_hash": "d94d12186c0f2fb7" + } + } + ], + "t0": 0.0, + "t0_bits": "0000000000000000", + "sweeps": 5 + }, + { + "name": "field-bound-at-the-line-below-zero/hot", + "landscape": { + "n": 3, + "couplings": [ + [ + 0, + 1, + 1 + ], + [ + 0, + 2, + 1 + ] + ], + "field": [ + -2147483645, + -1, + -1 + ], + "cost": 0 + }, + "field_bound": 2147483647, + "seed": "12", + "chains": [ + { + "chain": "10682531704454680323", + "start": "+00", + "expect": { + "state": "--+", + "accumulates": 16, + "draws": 12, + "term": "bound", + "state_hash": "d94d14186c0f331d" + } + }, + { + "chain": "17330038448952222807", + "start": "-++", + "expect": { + "state": "-+0", + "accumulates": 16, + "draws": 12, + "term": "bound", + "state_hash": "d94645186c0967b2" + } + }, + { + "chain": "4330166885954844398", + "start": "-00", + "expect": { + "state": "--0", + "accumulates": 16, + "draws": 12, + "term": "bound", + "state_hash": "d94d11186c0f2e04" + } + }, + { + "chain": "16691334188567521737", + "start": "-+-", + "expect": { + "state": "---", + "accumulates": 16, + "draws": 12, + "term": "fixed-point", + "state_hash": "d94d12186c0f2fb7" + } + }, + { + "chain": "15719682454639411054", + "start": "---", + "expect": { + "state": "000", + "accumulates": 16, + "draws": 12, + "term": "bound", + "state_hash": "d0a6fc18672a1282" + } + }, + { + "chain": "5425630273831533980", + "start": "+-0", + "expect": { + "state": "-0+", + "accumulates": 16, + "draws": 12, + "term": "bound", + "state_hash": "d949aa186c0c4928" + } + }, + { + "chain": "2570129635822222897", + "start": "-0-", + "expect": { + "state": "000", + "accumulates": 16, + "draws": 12, + "term": "bound", + "state_hash": "d0a6fc18672a1282" + } + }, + { + "chain": "13844750118391845279", + "start": "0-0", + "expect": { + "state": "-0+", + "accumulates": 16, + "draws": 12, + "term": "bound", + "state_hash": "d949aa186c0c4928" + } + } + ], + "held": [ + { + "temperature": 2147483645.0, + "temperature_bits": "41dfffffff400000", + "sweeps": 4 + } + ] + }, + { + "name": "field-bound-over-the-line-by-its-couplings/at-0", + "landscape": { + "n": 3, + "couplings": [ + [ + 0, + 1, + 1 + ], + [ + 0, + 2, + 1 + ] + ], + "field": [ + 2147483647, + 1, + 1 + ], + "cost": 0 + }, + "field_bound": 2147483649, + "seed": "11", + "chains": [ + { + "chain": "5833679380957638813", + "start": "0+0", + "expect": { + "state": "+++", + "accumulates": 20, + "draws": 0, + "term": "fixed-point", + "state_hash": "eaa6d61875e4f6c5" + } + }, + { + "chain": "4839782808629744545", + "start": "-00", + "expect": { + "state": "+++", + "accumulates": 20, + "draws": 0, + "term": "fixed-point", + "state_hash": "eaa6d61875e4f6c5" + } + }, + { + "chain": "11769803791402734189", + "start": "00+", + "expect": { + "state": "+++", + "accumulates": 20, + "draws": 0, + "term": "fixed-point", + "state_hash": "eaa6d61875e4f6c5" + } + }, + { + "chain": "9308485889748266480", + "start": "-+-", + "expect": { + "state": "+++", + "accumulates": 20, + "draws": 0, + "term": "fixed-point", + "state_hash": "eaa6d61875e4f6c5" + } + } + ], + "t0": 0.0, + "t0_bits": "0000000000000000", + "sweeps": 5 + }, + { + "name": "field-bound-over-the-line-by-its-couplings/hot", + "landscape": { + "n": 3, + "couplings": [ + [ + 0, + 1, + 1 + ], + [ + 0, + 2, + 1 + ] + ], + "field": [ + 2147483647, + 1, + 1 + ], + "cost": 0 + }, + "field_bound": 2147483649, + "seed": "12", + "chains": [ + { + "chain": "10682531704454680323", + "start": "+00", + "expect": { + "state": "0-+", + "accumulates": 16, + "draws": 12, + "term": "bound", + "state_hash": "d0a3991867273472" + } + }, + { + "chain": "17330038448952222807", + "start": "-++", + "expect": { + "state": "++0", + "accumulates": 16, + "draws": 12, + "term": "bound", + "state_hash": "eaa6d31875e4f1ac" + } + }, + { + "chain": "4330166885954844398", + "start": "-00", + "expect": { + "state": "0-0", + "accumulates": 16, + "draws": 12, + "term": "bound", + "state_hash": "d0a39818672732bf" + } + }, + { + "chain": "16691334188567521737", + "start": "-+-", + "expect": { + "state": "+--", + "accumulates": 16, + "draws": 12, + "term": "bound", + "state_hash": "eaa0081875df2d0d" + } + }, + { + "chain": "15719682454639411054", + "start": "---", + "expect": { + "state": "+00", + "accumulates": 16, + "draws": 12, + "term": "bound", + "state_hash": "ea9ca31875dc4b97" + } + }, + { + "chain": "5425630273831533980", + "start": "+-0", + "expect": { + "state": "-0+", + "accumulates": 16, + "draws": 12, + "term": "bound", + "state_hash": "d949aa186c0c4928" + } + }, + { + "chain": "2570129635822222897", + "start": "-0-", + "expect": { + "state": "+00", + "accumulates": 16, + "draws": 12, + "term": "bound", + "state_hash": "ea9ca31875dc4b97" + } + }, + { + "chain": "13844750118391845279", + "start": "0-0", + "expect": { + "state": "+0+", + "accumulates": 16, + "draws": 12, + "term": "bound", + "state_hash": "ea9ca41875dc4d4a" + } + } + ], + "held": [ + { + "temperature": 2147483647.0, + "temperature_bits": "41dfffffffc00000", + "sweeps": 4 + } + ] + }, + { + "name": "field-bound-over-the-line-below-zero/at-0", + "landscape": { + "n": 3, + "couplings": [ + [ + 0, + 1, + 1 + ], + [ + 0, + 2, + 1 + ] + ], + "field": [ + -2147483647, + -1, + -1 + ], + "cost": 0 + }, + "field_bound": 2147483649, + "seed": "11", + "chains": [ + { + "chain": "5833679380957638813", + "start": "0+0", + "expect": { + "state": "---", + "accumulates": 20, + "draws": 0, + "term": "fixed-point", + "state_hash": "d94d12186c0f2fb7" + } + }, + { + "chain": "4839782808629744545", + "start": "-00", + "expect": { + "state": "---", + "accumulates": 20, + "draws": 0, + "term": "fixed-point", + "state_hash": "d94d12186c0f2fb7" + } + }, + { + "chain": "11769803791402734189", + "start": "00+", + "expect": { + "state": "---", + "accumulates": 20, + "draws": 0, + "term": "fixed-point", + "state_hash": "d94d12186c0f2fb7" + } + }, + { + "chain": "9308485889748266480", + "start": "-+-", + "expect": { + "state": "---", + "accumulates": 20, + "draws": 0, + "term": "fixed-point", + "state_hash": "d94d12186c0f2fb7" + } + } + ], + "t0": 0.0, + "t0_bits": "0000000000000000", + "sweeps": 5 + }, + { + "name": "field-bound-over-the-line-below-zero/hot", + "landscape": { + "n": 3, + "couplings": [ + [ + 0, + 1, + 1 + ], + [ + 0, + 2, + 1 + ] + ], + "field": [ + -2147483647, + -1, + -1 + ], + "cost": 0 + }, + "field_bound": 2147483649, + "seed": "12", + "chains": [ + { + "chain": "10682531704454680323", + "start": "+00", + "expect": { + "state": "--+", + "accumulates": 16, + "draws": 12, + "term": "bound", + "state_hash": "d94d14186c0f331d" + } + }, + { + "chain": "17330038448952222807", + "start": "-++", + "expect": { + "state": "-+0", + "accumulates": 16, + "draws": 12, + "term": "bound", + "state_hash": "d94645186c0967b2" + } + }, + { + "chain": "4330166885954844398", + "start": "-00", + "expect": { + "state": "--0", + "accumulates": 16, + "draws": 12, + "term": "bound", + "state_hash": "d94d11186c0f2e04" + } + }, + { + "chain": "16691334188567521737", + "start": "-+-", + "expect": { + "state": "---", + "accumulates": 16, + "draws": 12, + "term": "fixed-point", + "state_hash": "d94d12186c0f2fb7" + } + }, + { + "chain": "15719682454639411054", + "start": "---", + "expect": { + "state": "000", + "accumulates": 16, + "draws": 12, + "term": "bound", + "state_hash": "d0a6fc18672a1282" + } + }, + { + "chain": "5425630273831533980", + "start": "+-0", + "expect": { + "state": "-0+", + "accumulates": 16, + "draws": 12, + "term": "bound", + "state_hash": "d949aa186c0c4928" + } + }, + { + "chain": "2570129635822222897", + "start": "-0-", + "expect": { + "state": "000", + "accumulates": 16, + "draws": 12, + "term": "bound", + "state_hash": "d0a6fc18672a1282" + } + }, + { + "chain": "13844750118391845279", + "start": "0-0", + "expect": { + "state": "-0+", + "accumulates": 16, + "draws": 12, + "term": "bound", + "state_hash": "d949aa186c0c4928" + } + } + ], + "held": [ + { + "temperature": 2147483647.0, + "temperature_bits": "41dfffffffc00000", + "sweeps": 4 + } + ] + }, + { + "name": "a-cost-past-the-lanes/at-0", + "landscape": { + "n": 3, + "couplings": [ + [ + 0, + 1, + 1 + ], + [ + 1, + 2, + -1 + ] + ], + "field": [ + 3, + 0, + 0 + ], + "cost": 8589934592 + }, + "field_bound": 4, + "seed": "11", + "chains": [ + { + "chain": "5833679380957638813", + "start": "0+0", + "expect": { + "state": "000", + "accumulates": 20, + "draws": 0, + "term": "fixed-point", + "state_hash": "d0a6fc18672a1282" + } + }, + { + "chain": "4839782808629744545", + "start": "-00", + "expect": { + "state": "000", + "accumulates": 20, + "draws": 0, + "term": "fixed-point", + "state_hash": "d0a6fc18672a1282" + } + }, + { + "chain": "11769803791402734189", + "start": "00+", + "expect": { + "state": "000", + "accumulates": 20, + "draws": 0, + "term": "fixed-point", + "state_hash": "d0a6fc18672a1282" + } + }, + { + "chain": "9308485889748266480", + "start": "-+-", + "expect": { + "state": "000", + "accumulates": 20, + "draws": 0, + "term": "fixed-point", + "state_hash": "d0a6fc18672a1282" + } + } + ], + "t0": 0.0, + "t0_bits": "0000000000000000", + "sweeps": 5 + }, + { + "name": "a-cost-past-the-lanes/hot", + "landscape": { + "n": 3, + "couplings": [ + [ + 0, + 1, + 1 + ], + [ + 1, + 2, + -1 + ] + ], + "field": [ + 3, + 0, + 0 + ], + "cost": 8589934592 + }, + "field_bound": 4, + "seed": "12", + "chains": [ + { + "chain": "10682531704454680323", + "start": "+00", + "expect": { + "state": "000", + "accumulates": 16, + "draws": 12, + "term": "fixed-point", + "state_hash": "d0a6fc18672a1282" + } + }, + { + "chain": "17330038448952222807", + "start": "-++", + "expect": { + "state": "000", + "accumulates": 16, + "draws": 12, + "term": "fixed-point", + "state_hash": "d0a6fc18672a1282" + } + }, + { + "chain": "4330166885954844398", + "start": "-00", + "expect": { + "state": "000", + "accumulates": 16, + "draws": 12, + "term": "fixed-point", + "state_hash": "d0a6fc18672a1282" + } + }, + { + "chain": "16691334188567521737", + "start": "-+-", + "expect": { + "state": "000", + "accumulates": 16, + "draws": 12, + "term": "fixed-point", + "state_hash": "d0a6fc18672a1282" + } + }, + { + "chain": "15719682454639411054", + "start": "---", + "expect": { + "state": "000", + "accumulates": 16, + "draws": 12, + "term": "fixed-point", + "state_hash": "d0a6fc18672a1282" + } + }, + { + "chain": "5425630273831533980", + "start": "+-0", + "expect": { + "state": "000", + "accumulates": 16, + "draws": 12, + "term": "fixed-point", + "state_hash": "d0a6fc18672a1282" + } + }, + { + "chain": "2570129635822222897", + "start": "-0-", + "expect": { + "state": "000", + "accumulates": 16, + "draws": 12, + "term": "fixed-point", + "state_hash": "d0a6fc18672a1282" + } + }, + { + "chain": "13844750118391845279", + "start": "0-0", + "expect": { + "state": "000", + "accumulates": 16, + "draws": 12, + "term": "fixed-point", + "state_hash": "d0a6fc18672a1282" + } + } + ], + "held": [ + { + "temperature": 3.0, + "temperature_bits": "4008000000000000", + "sweeps": 4 + } + ] + }, + { + "name": "a-cost-just-past-the-lanes/at-0", + "landscape": { + "n": 3, + "couplings": [ + [ + 0, + 1, + 1 + ], + [ + 1, + 2, + -1 + ] + ], + "field": [ + 2147483646, + 0, + 0 + ], + "cost": 2147483648 + }, + "field_bound": 2147483647, + "seed": "11", + "chains": [ + { + "chain": "5833679380957638813", + "start": "0+0", + "expect": { + "state": "000", + "accumulates": 20, + "draws": 0, + "term": "fixed-point", + "state_hash": "d0a6fc18672a1282" + } + }, + { + "chain": "4839782808629744545", + "start": "-00", + "expect": { + "state": "000", + "accumulates": 20, + "draws": 0, + "term": "fixed-point", + "state_hash": "d0a6fc18672a1282" + } + }, + { + "chain": "11769803791402734189", + "start": "00+", + "expect": { + "state": "000", + "accumulates": 20, + "draws": 0, + "term": "fixed-point", + "state_hash": "d0a6fc18672a1282" + } + }, + { + "chain": "9308485889748266480", + "start": "-+-", + "expect": { + "state": "000", + "accumulates": 20, + "draws": 0, + "term": "fixed-point", + "state_hash": "d0a6fc18672a1282" + } + } + ], + "t0": 0.0, + "t0_bits": "0000000000000000", + "sweeps": 5 + }, + { + "name": "a-cost-just-past-the-lanes/hot", + "landscape": { + "n": 3, + "couplings": [ + [ + 0, + 1, + 1 + ], + [ + 1, + 2, + -1 + ] + ], + "field": [ + 2147483646, + 0, + 0 + ], + "cost": 2147483648 + }, + "field_bound": 2147483647, + "seed": "12", + "chains": [ + { + "chain": "10682531704454680323", + "start": "+00", + "expect": { + "state": "0-+", + "accumulates": 16, + "draws": 12, + "term": "bound", + "state_hash": "d0a3991867273472" + } + }, + { + "chain": "17330038448952222807", + "start": "-++", + "expect": { + "state": "0+0", + "accumulates": 16, + "draws": 12, + "term": "bound", + "state_hash": "d0aa6418672cf911" + } + }, + { + "chain": "4330166885954844398", + "start": "-00", + "expect": { + "state": "000", + "accumulates": 16, + "draws": 12, + "term": "fixed-point", + "state_hash": "d0a6fc18672a1282" + } + }, + { + "chain": "16691334188567521737", + "start": "-+-", + "expect": { + "state": "0--", + "accumulates": 16, + "draws": 12, + "term": "bound", + "state_hash": "d0a397186727310c" + } + }, + { + "chain": "15719682454639411054", + "start": "---", + "expect": { + "state": "+00", + "accumulates": 16, + "draws": 12, + "term": "bound", + "state_hash": "ea9ca31875dc4b97" + } + }, + { + "chain": "5425630273831533980", + "start": "+-0", + "expect": { + "state": "-00", + "accumulates": 16, + "draws": 12, + "term": "bound", + "state_hash": "d949ad186c0c4e41" + } + }, + { + "chain": "2570129635822222897", + "start": "-0-", + "expect": { + "state": "+00", + "accumulates": 16, + "draws": 12, + "term": "bound", + "state_hash": "ea9ca31875dc4b97" + } + }, + { + "chain": "13844750118391845279", + "start": "0-0", + "expect": { + "state": "+0+", + "accumulates": 16, + "draws": 12, + "term": "bound", + "state_hash": "ea9ca41875dc4d4a" + } + } + ], + "held": [ + { + "temperature": 2147483646.0, + "temperature_bits": "41dfffffff800000", + "sweeps": 4 + } ] } ], @@ -16332,6 +54651,8 @@ 1.0, 1.0 ], + "law": "settled", + "block": 2, "state_order": "the sum of (s_i + 1) · 3^i", "exact": [ 0.24330499042671336, @@ -16397,6 +54718,8 @@ 1.0, 1.0 ], + "law": "settled", + "block": 3, "state_order": "the sum of (s_i + 1) · 3^i", "exact": [ 0.021904887743330933, @@ -16486,6 +54809,8 @@ 0.8, 0.8 ], + "law": "settled", + "block": 4, "state_order": "the sum of (s_i + 1) · 3^i", "exact": [ 0.013213605239596046, @@ -16633,6 +54958,8 @@ 1.2, 1.2 ], + "law": "settled", + "block": 3, "state_order": "the sum of (s_i + 1) · 3^i", "exact": [ 0.11208585385919495, @@ -16721,6 +55048,8 @@ 13.7, 13.7 ], + "law": "settled", + "block": 3, "state_order": "the sum of (s_i + 1) · 3^i", "exact": [ 0.0000622655414178924, @@ -16803,6 +55132,8 @@ 0.45, 0.45 ], + "law": "settled", + "block": 2, "state_order": "the sum of (s_i + 1) · 3^i", "exact": [ 9.447832090137123e-9, @@ -16879,6 +55210,8 @@ 1.3, 0.0 ], + "law": "settled", + "block": 4, "state_order": "the sum of (s_i + 1) · 3^i", "exact": [ 0.0, @@ -17026,6 +55359,8 @@ 0.7, 0.7 ], + "law": "settled", + "block": 3, "state_order": "the sum of (s_i + 1) · 3^i", "exact": [ 0.0005926415389637267, @@ -17056,6 +55391,1307 @@ 0.00003403695586947855, 1.954831527480454e-6 ] + }, + { + "name": "a-ring-of-six", + "landscape": { + "n": 6, + "couplings": [ + [ + 0, + 1, + -1 + ], + [ + 0, + 5, + 1 + ], + [ + 1, + 2, + 1 + ], + [ + 2, + 3, + 1 + ], + [ + 3, + 4, + -1 + ], + [ + 4, + 5, + 1 + ] + ], + "field": [ + 1, + 0, + 0, + -1, + 0, + 0 + ], + "cost": 1 + }, + "temperatures": [ + 1.1, + 1.1, + 1.1, + 1.1, + 1.1, + 1.1, + 1.1, + 1.1, + 1.1, + 1.1, + 1.1, + 1.1, + 1.1, + 1.1, + 1.1, + 1.1, + 1.1, + 1.1, + 1.1, + 1.1, + 1.1, + 1.1, + 1.1, + 1.1, + 1.1, + 1.1, + 1.1, + 1.1, + 1.1, + 1.1, + 1.1, + 1.1 + ], + "law": "settled", + "block": 6, + "state_order": "the sum of (s_i + 1) · 3^i", + "exact": [ + 0.00026442755762927096, + 0.0016290448619185648, + 0.0016290448619185648, + 0.0006563264082037529, + 0.0016290448619185648, + 0.0006563264082037529, + 0.00026442755762927096, + 0.00026442755762927096, + 0.000042922042767706626, + 0.00010653530371442031, + 0.0006563264082037529, + 0.0006563264082037529, + 0.0006563264082037529, + 0.0016290448619185648, + 0.0006563264082037529, + 0.0006563264082037529, + 0.0006563264082037529, + 0.00010653530371442031, + 6.967132215227572e-6, + 0.000042922042767706626, + 0.000042922042767706626, + 0.00010653530371442031, + 0.00026442755762927096, + 0.00010653530371442031, + 0.00026442755762927096, + 0.00026442755762927096, + 0.000042922042767706626, + 0.00026442755762927096, + 0.0016290448619185648, + 0.0016290448619185648, + 0.0006563264082037529, + 0.0016290448619185648, + 0.0006563264082037529, + 0.00026442755762927096, + 0.00026442755762927096, + 0.000042922042767706626, + 0.00026442755762927096, + 0.0016290448619185648, + 0.0016290448619185648, + 0.0016290448619185648, + 0.004043395373052585, + 0.0016290448619185648, + 0.0016290448619185648, + 0.0016290448619185648, + 0.00026442755762927096, + 0.000042922042767706626, + 0.00026442755762927096, + 0.00026442755762927096, + 0.0006563264082037529, + 0.0016290448619185648, + 0.0006563264082037529, + 0.0016290448619185648, + 0.0016290448619185648, + 0.00026442755762927096, + 0.000042922042767706626, + 0.00026442755762927096, + 0.00026442755762927096, + 0.00010653530371442031, + 0.00026442755762927096, + 0.00010653530371442031, + 0.000042922042767706626, + 0.000042922042767706626, + 6.967132215227572e-6, + 0.00010653530371442031, + 0.0006563264082037529, + 0.0006563264082037529, + 0.0006563264082037529, + 0.0016290448619185648, + 0.0006563264082037529, + 0.0006563264082037529, + 0.0006563264082037529, + 0.00010653530371442031, + 0.000042922042767706626, + 0.00026442755762927096, + 0.00026442755762927096, + 0.0006563264082037529, + 0.0016290448619185648, + 0.0006563264082037529, + 0.0016290448619185648, + 0.0016290448619185648, + 0.00026442755762927096, + 0.0006563264082037529, + 0.004043395373052585, + 0.004043395373052585, + 0.0016290448619185648, + 0.004043395373052585, + 0.0016290448619185648, + 0.0006563264082037529, + 0.0006563264082037529, + 0.00010653530371442031, + 0.00026442755762927096, + 0.0016290448619185648, + 0.0016290448619185648, + 0.0016290448619185648, + 0.004043395373052585, + 0.0016290448619185648, + 0.0016290448619185648, + 0.0016290448619185648, + 0.00026442755762927096, + 0.00001729287561136852, + 0.00010653530371442031, + 0.00010653530371442031, + 0.00026442755762927096, + 0.0006563264082037529, + 0.00026442755762927096, + 0.0006563264082037529, + 0.0006563264082037529, + 0.00010653530371442031, + 0.00026442755762927096, + 0.0016290448619185648, + 0.0016290448619185648, + 0.0006563264082037529, + 0.0016290448619185648, + 0.0006563264082037529, + 0.00026442755762927096, + 0.00026442755762927096, + 0.000042922042767706626, + 0.00026442755762927096, + 0.0016290448619185648, + 0.0016290448619185648, + 0.0016290448619185648, + 0.004043395373052585, + 0.0016290448619185648, + 0.0016290448619185648, + 0.0016290448619185648, + 0.00026442755762927096, + 0.000042922042767706626, + 0.00026442755762927096, + 0.00026442755762927096, + 0.0006563264082037529, + 0.0016290448619185648, + 0.0006563264082037529, + 0.0016290448619185648, + 0.0016290448619185648, + 0.00026442755762927096, + 0.00001729287561136852, + 0.00010653530371442031, + 0.00010653530371442031, + 0.000042922042767706626, + 0.00010653530371442031, + 0.000042922042767706626, + 0.00001729287561136852, + 0.00001729287561136852, + 2.806990138329014e-6, + 0.000042922042767706626, + 0.00026442755762927096, + 0.00026442755762927096, + 0.00026442755762927096, + 0.0006563264082037529, + 0.00026442755762927096, + 0.00026442755762927096, + 0.00026442755762927096, + 0.000042922042767706626, + 0.00001729287561136852, + 0.00010653530371442031, + 0.00010653530371442031, + 0.00026442755762927096, + 0.0006563264082037529, + 0.00026442755762927096, + 0.0006563264082037529, + 0.0006563264082037529, + 0.00010653530371442031, + 0.00026442755762927096, + 0.0016290448619185648, + 0.0016290448619185648, + 0.0006563264082037529, + 0.0016290448619185648, + 0.0006563264082037529, + 0.00026442755762927096, + 0.00026442755762927096, + 0.000042922042767706626, + 0.00010653530371442031, + 0.0006563264082037529, + 0.0006563264082037529, + 0.0006563264082037529, + 0.0016290448619185648, + 0.0006563264082037529, + 0.0006563264082037529, + 0.0006563264082037529, + 0.00010653530371442031, + 6.967132215227572e-6, + 0.000042922042767706626, + 0.000042922042767706626, + 0.00010653530371442031, + 0.00026442755762927096, + 0.00010653530371442031, + 0.00026442755762927096, + 0.00026442755762927096, + 0.000042922042767706626, + 0.000042922042767706626, + 0.00026442755762927096, + 0.00026442755762927096, + 0.00010653530371442031, + 0.00026442755762927096, + 0.00010653530371442031, + 0.000042922042767706626, + 0.000042922042767706626, + 6.967132215227572e-6, + 0.000042922042767706626, + 0.00026442755762927096, + 0.00026442755762927096, + 0.00026442755762927096, + 0.0006563264082037529, + 0.00026442755762927096, + 0.00026442755762927096, + 0.00026442755762927096, + 0.000042922042767706626, + 6.967132215227572e-6, + 0.000042922042767706626, + 0.000042922042767706626, + 0.00010653530371442031, + 0.00026442755762927096, + 0.00010653530371442031, + 0.00026442755762927096, + 0.00026442755762927096, + 0.000042922042767706626, + 1.130909159360483e-6, + 6.967132215227572e-6, + 6.967132215227572e-6, + 2.806990138329014e-6, + 6.967132215227572e-6, + 2.806990138329014e-6, + 1.130909159360483e-6, + 1.130909159360483e-6, + 1.835698659385437e-7, + 2.806990138329014e-6, + 0.00001729287561136852, + 0.00001729287561136852, + 0.00001729287561136852, + 0.000042922042767706626, + 0.00001729287561136852, + 0.00001729287561136852, + 0.00001729287561136852, + 2.806990138329014e-6, + 1.130909159360483e-6, + 6.967132215227572e-6, + 6.967132215227572e-6, + 0.00001729287561136852, + 0.000042922042767706626, + 0.00001729287561136852, + 0.000042922042767706626, + 0.000042922042767706626, + 6.967132215227572e-6, + 0.00010653530371442031, + 0.0016290448619185648, + 0.004043395373052585, + 0.00026442755762927096, + 0.0016290448619185648, + 0.0016290448619185648, + 0.00010653530371442031, + 0.00026442755762927096, + 0.00010653530371442031, + 0.000042922042767706626, + 0.0006563264082037529, + 0.0016290448619185648, + 0.00026442755762927096, + 0.0016290448619185648, + 0.0016290448619185648, + 0.00026442755762927096, + 0.0006563264082037529, + 0.00026442755762927096, + 2.806990138329014e-6, + 0.000042922042767706626, + 0.00010653530371442031, + 0.000042922042767706626, + 0.00026442755762927096, + 0.00026442755762927096, + 0.00010653530371442031, + 0.00026442755762927096, + 0.00010653530371442031, + 0.00010653530371442031, + 0.0016290448619185648, + 0.004043395373052585, + 0.00026442755762927096, + 0.0016290448619185648, + 0.0016290448619185648, + 0.00010653530371442031, + 0.00026442755762927096, + 0.00010653530371442031, + 0.00010653530371442031, + 0.0016290448619185648, + 0.004043395373052585, + 0.0006563264082037529, + 0.004043395373052585, + 0.004043395373052585, + 0.0006563264082037529, + 0.0016290448619185648, + 0.0006563264082037529, + 0.00001729287561136852, + 0.00026442755762927096, + 0.0006563264082037529, + 0.00026442755762927096, + 0.0016290448619185648, + 0.0016290448619185648, + 0.0006563264082037529, + 0.0016290448619185648, + 0.0006563264082037529, + 0.00001729287561136852, + 0.00026442755762927096, + 0.0006563264082037529, + 0.000042922042767706626, + 0.00026442755762927096, + 0.00026442755762927096, + 0.00001729287561136852, + 0.000042922042767706626, + 0.00001729287561136852, + 0.000042922042767706626, + 0.0006563264082037529, + 0.0016290448619185648, + 0.00026442755762927096, + 0.0016290448619185648, + 0.0016290448619185648, + 0.00026442755762927096, + 0.0006563264082037529, + 0.00026442755762927096, + 0.00001729287561136852, + 0.00026442755762927096, + 0.0006563264082037529, + 0.00026442755762927096, + 0.0016290448619185648, + 0.0016290448619185648, + 0.0006563264082037529, + 0.0016290448619185648, + 0.0006563264082037529, + 0.0006563264082037529, + 0.010035970478780059, + 0.024909931915687276, + 0.0016290448619185648, + 0.010035970478780059, + 0.010035970478780059, + 0.0006563264082037529, + 0.0016290448619185648, + 0.0006563264082037529, + 0.00026442755762927096, + 0.004043395373052585, + 0.010035970478780059, + 0.0016290448619185648, + 0.010035970478780059, + 0.010035970478780059, + 0.0016290448619185648, + 0.004043395373052585, + 0.0016290448619185648, + 0.00001729287561136852, + 0.00026442755762927096, + 0.0006563264082037529, + 0.00026442755762927096, + 0.0016290448619185648, + 0.0016290448619185648, + 0.0006563264082037529, + 0.0016290448619185648, + 0.0006563264082037529, + 0.00026442755762927096, + 0.004043395373052585, + 0.010035970478780059, + 0.0006563264082037529, + 0.004043395373052585, + 0.004043395373052585, + 0.00026442755762927096, + 0.0006563264082037529, + 0.00026442755762927096, + 0.00026442755762927096, + 0.004043395373052585, + 0.010035970478780059, + 0.0016290448619185648, + 0.010035970478780059, + 0.010035970478780059, + 0.0016290448619185648, + 0.004043395373052585, + 0.0016290448619185648, + 0.000042922042767706626, + 0.0006563264082037529, + 0.0016290448619185648, + 0.0006563264082037529, + 0.004043395373052585, + 0.004043395373052585, + 0.0016290448619185648, + 0.004043395373052585, + 0.0016290448619185648, + 0.00001729287561136852, + 0.00026442755762927096, + 0.0006563264082037529, + 0.000042922042767706626, + 0.00026442755762927096, + 0.00026442755762927096, + 0.00001729287561136852, + 0.000042922042767706626, + 0.00001729287561136852, + 0.000042922042767706626, + 0.0006563264082037529, + 0.0016290448619185648, + 0.00026442755762927096, + 0.0016290448619185648, + 0.0016290448619185648, + 0.00026442755762927096, + 0.0006563264082037529, + 0.00026442755762927096, + 0.00001729287561136852, + 0.00026442755762927096, + 0.0006563264082037529, + 0.00026442755762927096, + 0.0016290448619185648, + 0.0016290448619185648, + 0.0006563264082037529, + 0.0016290448619185648, + 0.0006563264082037529, + 0.0006563264082037529, + 0.010035970478780059, + 0.024909931915687276, + 0.0016290448619185648, + 0.010035970478780059, + 0.010035970478780059, + 0.0006563264082037529, + 0.0016290448619185648, + 0.0006563264082037529, + 0.00026442755762927096, + 0.004043395373052585, + 0.010035970478780059, + 0.0016290448619185648, + 0.010035970478780059, + 0.010035970478780059, + 0.0016290448619185648, + 0.004043395373052585, + 0.0016290448619185648, + 0.00001729287561136852, + 0.00026442755762927096, + 0.0006563264082037529, + 0.00026442755762927096, + 0.0016290448619185648, + 0.0016290448619185648, + 0.0006563264082037529, + 0.0016290448619185648, + 0.0006563264082037529, + 0.00010653530371442031, + 0.0016290448619185648, + 0.004043395373052585, + 0.00026442755762927096, + 0.0016290448619185648, + 0.0016290448619185648, + 0.00010653530371442031, + 0.00026442755762927096, + 0.00010653530371442031, + 0.00010653530371442031, + 0.0016290448619185648, + 0.004043395373052585, + 0.0006563264082037529, + 0.004043395373052585, + 0.004043395373052585, + 0.0006563264082037529, + 0.0016290448619185648, + 0.0006563264082037529, + 0.00001729287561136852, + 0.00026442755762927096, + 0.0006563264082037529, + 0.00026442755762927096, + 0.0016290448619185648, + 0.0016290448619185648, + 0.0006563264082037529, + 0.0016290448619185648, + 0.0006563264082037529, + 2.806990138329014e-6, + 0.000042922042767706626, + 0.00010653530371442031, + 6.967132215227572e-6, + 0.000042922042767706626, + 0.000042922042767706626, + 2.806990138329014e-6, + 6.967132215227572e-6, + 2.806990138329014e-6, + 6.967132215227572e-6, + 0.00010653530371442031, + 0.00026442755762927096, + 0.000042922042767706626, + 0.00026442755762927096, + 0.00026442755762927096, + 0.000042922042767706626, + 0.00010653530371442031, + 0.000042922042767706626, + 2.806990138329014e-6, + 0.000042922042767706626, + 0.00010653530371442031, + 0.000042922042767706626, + 0.00026442755762927096, + 0.00026442755762927096, + 0.00010653530371442031, + 0.00026442755762927096, + 0.00010653530371442031, + 6.967132215227572e-6, + 0.00026442755762927096, + 0.0016290448619185648, + 0.00001729287561136852, + 0.00026442755762927096, + 0.0006563264082037529, + 6.967132215227572e-6, + 0.000042922042767706626, + 0.000042922042767706626, + 2.806990138329014e-6, + 0.00010653530371442031, + 0.0006563264082037529, + 0.00001729287561136852, + 0.00026442755762927096, + 0.0006563264082037529, + 0.00001729287561136852, + 0.00010653530371442031, + 0.00010653530371442031, + 1.835698659385437e-7, + 6.967132215227572e-6, + 0.000042922042767706626, + 2.806990138329014e-6, + 0.000042922042767706626, + 0.00010653530371442031, + 6.967132215227572e-6, + 0.000042922042767706626, + 0.000042922042767706626, + 6.967132215227572e-6, + 0.00026442755762927096, + 0.0016290448619185648, + 0.00001729287561136852, + 0.00026442755762927096, + 0.0006563264082037529, + 6.967132215227572e-6, + 0.000042922042767706626, + 0.000042922042767706626, + 6.967132215227572e-6, + 0.00026442755762927096, + 0.0016290448619185648, + 0.000042922042767706626, + 0.0006563264082037529, + 0.0016290448619185648, + 0.000042922042767706626, + 0.00026442755762927096, + 0.00026442755762927096, + 1.130909159360483e-6, + 0.000042922042767706626, + 0.00026442755762927096, + 0.00001729287561136852, + 0.00026442755762927096, + 0.0006563264082037529, + 0.000042922042767706626, + 0.00026442755762927096, + 0.00026442755762927096, + 1.130909159360483e-6, + 0.000042922042767706626, + 0.00026442755762927096, + 2.806990138329014e-6, + 0.000042922042767706626, + 0.00010653530371442031, + 1.130909159360483e-6, + 6.967132215227572e-6, + 6.967132215227572e-6, + 2.806990138329014e-6, + 0.00010653530371442031, + 0.0006563264082037529, + 0.00001729287561136852, + 0.00026442755762927096, + 0.0006563264082037529, + 0.00001729287561136852, + 0.00010653530371442031, + 0.00010653530371442031, + 1.130909159360483e-6, + 0.000042922042767706626, + 0.00026442755762927096, + 0.00001729287561136852, + 0.00026442755762927096, + 0.0006563264082037529, + 0.000042922042767706626, + 0.00026442755762927096, + 0.00026442755762927096, + 0.00010653530371442031, + 0.004043395373052585, + 0.024909931915687276, + 0.00026442755762927096, + 0.004043395373052585, + 0.010035970478780059, + 0.00010653530371442031, + 0.0006563264082037529, + 0.0006563264082037529, + 0.000042922042767706626, + 0.0016290448619185648, + 0.010035970478780059, + 0.00026442755762927096, + 0.004043395373052585, + 0.010035970478780059, + 0.00026442755762927096, + 0.0016290448619185648, + 0.0016290448619185648, + 2.806990138329014e-6, + 0.00010653530371442031, + 0.0006563264082037529, + 0.000042922042767706626, + 0.0006563264082037529, + 0.0016290448619185648, + 0.00010653530371442031, + 0.0006563264082037529, + 0.0006563264082037529, + 0.000042922042767706626, + 0.0016290448619185648, + 0.010035970478780059, + 0.00010653530371442031, + 0.0016290448619185648, + 0.004043395373052585, + 0.000042922042767706626, + 0.00026442755762927096, + 0.00026442755762927096, + 0.000042922042767706626, + 0.0016290448619185648, + 0.010035970478780059, + 0.00026442755762927096, + 0.004043395373052585, + 0.010035970478780059, + 0.00026442755762927096, + 0.0016290448619185648, + 0.0016290448619185648, + 6.967132215227572e-6, + 0.00026442755762927096, + 0.0016290448619185648, + 0.00010653530371442031, + 0.0016290448619185648, + 0.004043395373052585, + 0.00026442755762927096, + 0.0016290448619185648, + 0.0016290448619185648, + 2.806990138329014e-6, + 0.00010653530371442031, + 0.0006563264082037529, + 6.967132215227572e-6, + 0.00010653530371442031, + 0.00026442755762927096, + 2.806990138329014e-6, + 0.00001729287561136852, + 0.00001729287561136852, + 6.967132215227572e-6, + 0.00026442755762927096, + 0.0016290448619185648, + 0.000042922042767706626, + 0.0006563264082037529, + 0.0016290448619185648, + 0.000042922042767706626, + 0.00026442755762927096, + 0.00026442755762927096, + 2.806990138329014e-6, + 0.00010653530371442031, + 0.0006563264082037529, + 0.000042922042767706626, + 0.0006563264082037529, + 0.0016290448619185648, + 0.00010653530371442031, + 0.0006563264082037529, + 0.0006563264082037529, + 0.00026442755762927096, + 0.010035970478780059, + 0.061828072268263805, + 0.0006563264082037529, + 0.010035970478780059, + 0.024909931915687276, + 0.00026442755762927096, + 0.0016290448619185648, + 0.0016290448619185648, + 0.00010653530371442031, + 0.004043395373052585, + 0.024909931915687276, + 0.0006563264082037529, + 0.010035970478780059, + 0.024909931915687276, + 0.0006563264082037529, + 0.004043395373052585, + 0.004043395373052585, + 6.967132215227572e-6, + 0.00026442755762927096, + 0.0016290448619185648, + 0.00010653530371442031, + 0.0016290448619185648, + 0.004043395373052585, + 0.00026442755762927096, + 0.0016290448619185648, + 0.0016290448619185648, + 0.000042922042767706626, + 0.0016290448619185648, + 0.010035970478780059, + 0.00010653530371442031, + 0.0016290448619185648, + 0.004043395373052585, + 0.000042922042767706626, + 0.00026442755762927096, + 0.00026442755762927096, + 0.000042922042767706626, + 0.0016290448619185648, + 0.010035970478780059, + 0.00026442755762927096, + 0.004043395373052585, + 0.010035970478780059, + 0.00026442755762927096, + 0.0016290448619185648, + 0.0016290448619185648, + 6.967132215227572e-6, + 0.00026442755762927096, + 0.0016290448619185648, + 0.00010653530371442031, + 0.0016290448619185648, + 0.004043395373052585, + 0.00026442755762927096, + 0.0016290448619185648, + 0.0016290448619185648, + 1.130909159360483e-6, + 0.000042922042767706626, + 0.00026442755762927096, + 2.806990138329014e-6, + 0.000042922042767706626, + 0.00010653530371442031, + 1.130909159360483e-6, + 6.967132215227572e-6, + 6.967132215227572e-6, + 2.806990138329014e-6, + 0.00010653530371442031, + 0.0006563264082037529, + 0.00001729287561136852, + 0.00026442755762927096, + 0.0006563264082037529, + 0.00001729287561136852, + 0.00010653530371442031, + 0.00010653530371442031, + 1.130909159360483e-6, + 0.000042922042767706626, + 0.00026442755762927096, + 0.00001729287561136852, + 0.00026442755762927096, + 0.0006563264082037529, + 0.000042922042767706626, + 0.00026442755762927096, + 0.00026442755762927096 + ] + }, + { + "name": "triangles-side-by-side", + "landscape": { + "n": 24, + "couplings": [ + [ + 0, + 1, + 1 + ], + [ + 0, + 2, + 1 + ], + [ + 1, + 2, + 1 + ], + [ + 3, + 4, + 1 + ], + [ + 3, + 5, + 1 + ], + [ + 4, + 5, + 1 + ], + [ + 6, + 7, + 1 + ], + [ + 6, + 8, + 1 + ], + [ + 7, + 8, + 1 + ], + [ + 9, + 10, + 1 + ], + [ + 9, + 11, + 1 + ], + [ + 10, + 11, + 1 + ], + [ + 12, + 13, + 1 + ], + [ + 12, + 14, + 1 + ], + [ + 13, + 14, + 1 + ], + [ + 15, + 16, + 1 + ], + [ + 15, + 17, + 1 + ], + [ + 16, + 17, + 1 + ], + [ + 18, + 19, + 1 + ], + [ + 18, + 20, + 1 + ], + [ + 19, + 20, + 1 + ], + [ + 21, + 22, + 1 + ], + [ + 21, + 23, + 1 + ], + [ + 22, + 23, + 1 + ] + ], + "field": [ + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0 + ], + "cost": 1 + }, + "temperatures": [ + 1.2, + 1.2, + 1.2, + 1.2, + 1.2, + 1.2, + 1.2, + 1.2, + 1.2, + 1.2, + 1.2, + 1.2, + 1.2, + 1.2, + 1.2, + 1.2, + 1.2, + 1.2, + 1.2, + 1.2, + 1.2, + 1.2, + 1.2, + 1.2, + 1.2, + 1.2, + 1.2, + 1.2, + 1.2, + 1.2, + 1.2, + 1.2 + ], + "law": "settled", + "block": 3, + "state_order": "the sum of (s_i + 1) · 3^i", + "exact": [ + 0.040181694150280056, + 0.040181694150280056, + 0.0075893417056686775, + 0.01746289229249106, + 0.040181694150280056, + 0.01746289229249106, + 0.0014334414897984214, + 0.0075893417056686775, + 0.0075893417056686775, + 0.01746289229249106, + 0.040181694150280056, + 0.01746289229249106, + 0.01746289229249106, + 0.09245710949502366, + 0.09245710949502366, + 0.0032983143090316597, + 0.040181694150280056, + 0.09245710949502366, + 0.0014334414897984214, + 0.0075893417056686775, + 0.0075893417056686775, + 0.0032983143090316597, + 0.040181694150280056, + 0.09245710949502366, + 0.0014334414897984214, + 0.040181694150280056, + 0.21274157988968753 + ] + }, + { + "name": "a-schedule-that-matters", + "landscape": { + "n": 3, + "couplings": [ + [ + 0, + 1, + 1 + ], + [ + 0, + 2, + 1 + ], + [ + 1, + 2, + 1 + ] + ], + "field": [ + 1, + 1, + 1 + ], + "cost": 0 + }, + "temperatures": [ + 1.2, + 1.2, + 1.2, + 1.2, + 1.2, + 1.2, + 1.2, + 1.2, + 1.2, + 1.2, + 1.2, + 1.2, + 1.2, + 1.2, + 1.2, + 1.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ], + "law": "walked", + "block": 3, + "state_order": "the sum of (s_i + 1) · 3^i", + "exact": [ + 0.010100279572722531, + 8.621260035254652e-6, + 1.8693135432689412e-14, + 0.000025710243105338204, + 1.1741545296723077e-9, + 7.555704616465967e-16, + 4.118913783795507e-10, + 1.0768077447731804e-13, + 9.260679166255838e-14, + 0.00006805514843695003, + 8.621260035254652e-6, + 2.7743072830977458e-12, + 0.000025710243105338204, + 1.7425998302326725e-7, + 1.6642546929025083e-11, + 6.113010067242384e-8, + 2.3718268957829413e-9, + 3.027332107538087e-7, + 4.5855198318306344e-7, + 8.621260035254652e-6, + 4.1174370820582276e-10, + 0.000025710243105338204, + 0.00002586247458564446, + 3.6657649067063655e-7, + 9.072511357052966e-6, + 0.000052242963991165564, + 0.9896401251784616 + ] + }, + { + "name": "fields-past-16-bits", + "landscape": { + "n": 3, + "couplings": [ + [ + 0, + 1, + 1 + ], + [ + 1, + 2, + -1 + ] + ], + "field": [ + 70000, + -65536, + 40000 + ], + "cost": 30000 + }, + "temperatures": [ + 40000.0, + 40000.0, + 40000.0, + 40000.0, + 40000.0, + 40000.0, + 40000.0, + 40000.0, + 40000.0, + 40000.0, + 40000.0, + 40000.0, + 40000.0, + 40000.0, + 40000.0, + 40000.0, + 40000.0, + 40000.0, + 40000.0, + 40000.0, + 40000.0, + 40000.0, + 40000.0, + 40000.0, + 40000.0, + 40000.0, + 40000.0, + 40000.0, + 40000.0, + 40000.0, + 40000.0, + 40000.0 + ], + "law": "settled", + "block": 3, + "state_order": "the sum of (s_i + 1) · 3^i", + "exact": [ + 0.001053880155382102, + 0.012838567660044112, + 0.03489797271351633, + 0.00043347495389328493, + 0.005280806007921159, + 0.014354719010949441, + 0.00003978277453851542, + 0.00048466552704221137, + 0.001317490431888454, + 0.006064813180785462, + 0.07388270285705374, + 0.20082898782631314, + 0.002494476129655758, + 0.030388940384650245, + 0.08260570443371996, + 0.00022892833753829434, + 0.0027889878133179383, + 0.007581444426477357, + 0.007787568958381723, + 0.09486964003339934, + 0.2578759715953634, + 0.003202970751798859, + 0.039020171840099455, + 0.10606782405629171, + 0.0002939424553459989, + 0.003581041711970715, + 0.009734523972661162 + ] + }, + { + "name": "fields-past-32-bits", + "landscape": { + "n": 3, + "couplings": [ + [ + 0, + 1, + 1 + ], + [ + 1, + 2, + -1 + ] + ], + "field": [ + 6442450944, + -8589934592, + 4294967301 + ], + "cost": 4294967296 + }, + "temperatures": [ + 4294967296.0, + 4294967296.0, + 4294967296.0, + 4294967296.0, + 4294967296.0, + 4294967296.0, + 4294967296.0, + 4294967296.0, + 4294967296.0, + 4294967296.0, + 4294967296.0, + 4294967296.0, + 4294967296.0, + 4294967296.0, + 4294967296.0, + 4294967296.0, + 4294967296.0, + 4294967296.0, + 4294967296.0, + 4294967296.0, + 4294967296.0, + 4294967296.0, + 4294967296.0, + 4294967296.0, + 4294967296.0, + 4294967296.0, + 4294967296.0, + 4294967296.0, + 4294967296.0, + 4294967296.0, + 4294967296.0, + 4294967296.0 + ], + "law": "settled", + "block": 3, + "state_order": "the sum of (s_i + 1) · 3^i", + "exact": [ + 0.0013743378590596019, + 0.01674286266406149, + 0.02760431380022206, + 0.0005055906435716028, + 0.006159354961899234, + 0.010155059539475644, + 0.000025171875937651717, + 0.0003066562266614181, + 0.0005055906438070368, + 0.010155059553662098, + 0.12371395165426821, + 0.2039698235272689, + 0.0037358376327941115, + 0.045511819399683025, + 0.07503630471252014, + 0.00018599640359185377, + 0.0022659000639978955, + 0.003735837633663929, + 0.010155059567848552, + 0.12371395182709462, + 0.20396982381221143, + 0.0037358376371431985, + 0.045511819452665754, + 0.0750363047998739, + 0.00018599640376507643, + 0.002265900066108179, + 0.0037358376371431985 + ] } ] }, diff --git a/wai/wai-quantum/README.md b/wai/wai-quantum/README.md index c2e1a085e..a5ba9a786 100644 --- a/wai/wai-quantum/README.md +++ b/wai/wai-quantum/README.md @@ -80,7 +80,7 @@ never creates information. | `quantum_resource_receipt` | a signed, energy-accounted receipt over an estimate, re-checkable bit for bit on any machine | | `quantum_qio` | **quantum-inspired optimisation**: Ising and Max-Cut problems (G-set reader, spin-glass generator), simulated bifurcation in its ballistic, discrete and heated forms (batched eight trials per pass, bit-identical to one at a time), simulated annealing, single-flip descent, an exact Gray-code referee up to 34 spins, and step-to-solution. Reaches the best-known cut on seven G-set instances | | `quantum_landscape` | **energy-landscape jobs**: a program in epuML states an energy over variables of three states (spin-1, with a single-ion cost), run on a classical emulator of the settling model: a deterministic kernel in whole numbers and a sampling kernel of one draw between two cut points, both the same bits on every platform; every arrangement checked up to 16 variables, each lowest one graded against the problem's own check; the joules priced at rates whose sources are named, never measured here; the same instance on simulated annealing, tabu search and greedy descent at the same budget, with the published table; and the seam a GPU or an FPGA board runs behind (`extensions/quantum-landscape.md`). `quantum_engine` and `full` both turn it on | -| `quantum_landscape_webgpu`, `quantum_landscape_cuda`, `quantum_landscape_metal` | **energy-landscape jobs on a GPU**: the settle and sampling kernels through WebGPU (32-bit lanes, and a coloured kernel that can be statistical at most), CUDA and Metal (64-bit fields), each member certified by the host on the conformance set and given only the workloads it takes (`job::call_placed`) | +| `quantum_landscape_webgpu`, `quantum_landscape_cuda`, `quantum_landscape_metal` | **energy-landscape jobs on a GPU**: the settle and sampling kernels through WebGPU (32-bit lanes, and a coloured kernel that can be statistical at most), CUDA and Metal (64-bit fields), each member certified by the host on the conformance set and given only the workloads it takes, a job passing over an uncertified kernel or a member that fails it (`job::call_placed`) | | `quantum_uf` | **union-find decoding in near-linear time** over the same matching graph: event-driven cluster growth on integer weights (a radix heap orders the fill events), merges, and peeling. Measured against exact matching on the same shots: within 0.92–1.18× the failures, up to 77× faster at `d = 11` | | `quantum_dmrg` | **ground states of spin chains by DMRG**: matrix-product operators for nearest-neighbour chains (XXZ Heisenberg in a field, transverse-field Ising), two-site sweeps with Lanczos and density-matrix truncation, entanglement entropies, a symmetric eigensolver, and two exact referees: full diagonalisation up to 22 sites, and the free-fermion solution of the Ising chain at any length. The critical Ising chain is reproduced to about `10⁻¹¹` per site at 200 sites | | `quantum_tdvp` | **time evolution of spin chains by TDVP**: two-site sweeps forward and back, Lanczos exponentials that halve a step they cannot converge, bonds split by singular value decomposition, and every basis completed with null vectors so a product state's small bonds do not hold the evolution back. Measures site and bond observables, entanglement at every bond, energy and the Loschmidt echo. Held to two exact referees: Krylov evolution of all amplitudes up to 20 sites, and the free-fermion solution of an Ising quench at any length. On a 100-site critical quench at bond 64 it stays within `10⁻⁹` of the exact answer until the entanglement fills the bond, and past that its error follows the discarded weight it reports | diff --git a/wai/wai-quantum/README.zh-CN.md b/wai/wai-quantum/README.zh-CN.md index 6de0769f1..bd19d29ba 100644 --- a/wai/wai-quantum/README.zh-CN.md +++ b/wai/wai-quantum/README.zh-CN.md @@ -63,7 +63,7 @@ wai-quantum lab qutrit # d = 3,不只是量子比特 | `quantum_resource_receipt` | 针对一次估算的、带能耗核算的签名回执,可在任何机器上逐位重新校验 | | `quantum_qio` | **量子启发式优化**:Ising 与 Max-Cut 问题(G-set 读取器、自旋玻璃生成器),弹道式、离散式和加热式的模拟分岔(每遍批量运行八次试验,与逐次运行的结果逐位一致),模拟退火,单翻转下降,最多 34 个自旋的精确 Gray 码参照,以及求解所需步数(step-to-solution)。在七个 G-set 实例上达到已知最优割 | | `quantum_landscape` | **能量景观作业**:epuML 程序对三态变量(自旋 1,带单离子代价)给出能量,在沉降模型的经典模拟器上运行:整数运算的确定性核,以及在两个切点之间做一次抽样的采样核,两者在每个平台上比特一致;最多 16 个变量时检查每一种排列,并按问题自身的检查为每个最低能量状态评分;焦耳按注明来源的费率定价,这里从不测量;同一实例上以相同预算运行模拟退火、禁忌搜索和贪心下降,并发布结果表;以及 GPU 或 FPGA 板运行所经由的接缝(`extensions/quantum-landscape.md`)。`quantum_engine` 和 `full` 都会启用它 | -| `quantum_landscape_webgpu`、`quantum_landscape_cuda`、`quantum_landscape_metal` | **GPU 上的能量景观作业**:沉降核与采样核分别通过 WebGPU(32 位通道,另有至多只能达到统计一致的着色核)、CUDA 与 Metal(64 位场)运行,每个成员都由主机在一致性集合上认证,且只接收它能承接的工作负载(`job::call_placed`) | +| `quantum_landscape_webgpu`、`quantum_landscape_cuda`、`quantum_landscape_metal` | **GPU 上的能量景观作业**:沉降核与采样核分别通过 WebGPU(32 位通道,另有至多只能达到统计一致的着色核)、CUDA 与 Metal(64 位场)运行,每个成员都由主机在一致性集合上认证,且只接收它能承接的工作负载;作业会跳过未经认证的核或运行失败的成员(`job::call_placed`) | | `quantum_uf` | **近线性时间的并查集(union-find)解码**,基于同一个匹配图:在整数权重上由事件驱动的簇生长(用基数堆为填充事件排序)、合并以及剥离(peeling)。在相同的采样上与精确匹配对比:失败次数为其 0.92–1.18 倍,在 `d = 11` 时最多快 77 倍 | | `quantum_dmrg` | **用 DMRG 求自旋链的基态**:最近邻链的矩阵乘积算符(外场中的 XXZ Heisenberg 链、横场 Ising 链),带 Lanczos 和密度矩阵截断的双格点扫描,纠缠熵,一个对称矩阵本征求解器,以及两个精确参照:最多 22 个格点的完全对角化,以及任意长度 Ising 链的自由费米子解。在 200 个格点上,临界 Ising 链的复现精度约为每格点 `10⁻¹¹` | | `quantum_tdvp` | **用 TDVP 计算自旋链的时间演化**:前后往返的双格点扫描,遇到无法收敛的步长就将其减半的 Lanczos 指数运算,用奇异值分解拆分键,并用零空间向量补全每个基,使乘积态的小键不会阻碍演化。测量格点和键上的可观测量、每个键上的纠缠、能量以及 Loschmidt 回波。以两个精确参照为准:最多 20 个格点的全振幅 Krylov 演化,以及任意长度 Ising 淬火的自由费米子解。在键维数 64、100 个格点的临界淬火中,直到纠缠填满键之前,它与精确解的偏差都保持在 `10⁻⁹` 以内;超过之后,其误差随它所报告的舍弃权重变化 | diff --git a/wai/wai-quantum/docs/landscape.md b/wai/wai-quantum/docs/landscape.md index 83fa73824..606b9eeb6 100644 --- a/wai/wai-quantum/docs/landscape.md +++ b/wai/wai-quantum/docs/landscape.md @@ -176,16 +176,25 @@ The kernels run on a GPU too, as members the host certifies and checks like any | feature | member | lanes | parity it can earn | |---|---|---|---| -| `quantum_landscape_webgpu` | `quantum_landscape::webgpu`: Metal, Vulkan or DX12 through WebGPU | 32-bit: it declines a workload whose fields do not fit | bit-exact for both kernels; its coloured kernel, a colour class of cells at once, statistical at most | -| `quantum_landscape_cuda` | `quantum_landscape::cuda`: an NVIDIA GPU, compiled at run time | 64-bit | bit-exact for both kernels | -| `quantum_landscape_metal` | `quantum_landscape::metal`: an Apple GPU, macOS | 64-bit | bit-exact for both kernels | +| `quantum_landscape_webgpu` | `quantum_landscape::webgpu`: Metal, Vulkan or DX12 through WebGPU | 32-bit: it declines a workload whose field bound is above 2³¹ − 1 | bit-exact for both kernels; its coloured kernel, a colour class of cells at once, statistical at most | +| `quantum_landscape_cuda` | `quantum_landscape::cuda`: an NVIDIA GPU, compiled at run time | 64-bit: it takes every workload | bit-exact for both kernels | +| `quantum_landscape_metal` | `quantum_landscape::metal`: an Apple GPU, macOS | 64-bit: it takes every workload | bit-exact for both kernels | + +A parity in this table is what a member can earn, not what any one device has: the host finds it +on each device, every time it certifies one, and states it in every answer. The settle kernel runs one thread a cell and one dispatch a sweep. The sampling kernel runs one thread a chain, its cells in the reference's order, with the reference's draws and the cut points the -host works out for every field the workload can give: so its bits are the reference's, and many -chains run side by side, the starts of a run and the seeds of a sweep, a comparison or a replay. -A member declines, before anything runs, a sampling run whose cut points for one sweep would be more -than 4,194,304 entries. +host works out for the fields the cells can reach (at most `4P + n` a sweep, counted in the job's +operations and memory): so its bits are the reference's, and many chains run side by side, the +starts of a run and the seeds of a sweep, a comparison or a replay. A member runs a window of sweeps +a dispatch and waits for each before it sends the next. + +A workload's field bound is the largest field any state can give a cell, `max_i (|b_i| + deg_i)`. +A member may decline a workload only where that is above 2³¹ − 1, and the host never runs it on one +it declines. Placing a job, the host passes over a member whose kernel for the job is not certified, +one that declines the workload, and one that fails the job or whose answer its checks refuse, and +names each under `refusals`; offered last, the reference answers what the others did not. ```rust norun=account use wai_quantum::quantum_landscape::{job, member, webgpu, MAX_JOB_BYTES}; @@ -194,14 +203,21 @@ use serde_json::json; let gpu = webgpu::discover().into_iter().find_map(Result::ok).expect("a GPU"); let placed = [member::certify(&gpu).unwrap(), member::Certified::reference()]; let answer = job::call_placed(&placed, "run", &json!({ "program": "vars 2\noppose 0 1\nsolve\n" }), MAX_JOB_BYTES).unwrap(); -// answer["member"] names the GPU and the parity the host found; answer["refusals"], if any, -// names each member that declined the job, with why. +// answer["member"] names what ran the job and the parity the host found for the kernel it used; +// answer["refusals"], if any, names each member passed over, with why. ``` A member can carry a meter: `with_meter` takes the engine's run meter, and `wai::sink_meter::RunCounter` -makes one of a machine's own on-chip counter (the GPU rail through IOReport on macOS, the board through -NVML on Linux), labelled `OnChipCounter`. Its reading stands beside the priced figures in `joules.metered`, -for a run whose calls each covered that run alone and lasted the counter's minimum window. +makes one of a machine's own on-chip counter: the graphics rail through IOReport on macOS, and the +board through NVML on Linux once NVML has shown itself plausible (`RunCounter::nvml`). It reads the +counter through each call and gives a figure only from a window in which the counter was live and +which lasted the profile's minimum, labelled `OnChipCounter` with the uncertainty the operator declares +and the meter's own terms beside it. **The figure is the whole rail or board over the call's window: +the run's work, the device's idle draw and any other work on the device then.** No share is +attributed to the run, and the answer says so: `joules.metered.scope` states it, and the `label` +says a reading is there, and what it covers, only in an answer that has one. Over a run of several +calls the figures and the windows are added up. A reading stands for a run only where every call +carried that run alone; elsewhere `metered` is null. ## A member of your own diff --git a/wai/wai-quantum/schemas/landscape.v1.json b/wai/wai-quantum/schemas/landscape.v1.json index 88feb4ba3..b2f6ee230 100644 --- a/wai/wai-quantum/schemas/landscape.v1.json +++ b/wai/wai-quantum/schemas/landscape.v1.json @@ -19,7 +19,7 @@ }, "$defs": { "result": { - "description": "A run, a temperature sweep or a comparison, with its receipt.", + "description": "A run, a temperature sweep, a comparison or a replay, with its receipt where it has one.", "anyOf": [ { "$ref": "#/$defs/run" @@ -29,6 +29,9 @@ }, { "$ref": "#/$defs/compare" + }, + { + "$ref": "#/$defs/replay" } ] }, @@ -598,19 +601,39 @@ } }, "metered": { - "description": "A reading from a meter on the member (extensions/energy-measurement.md); null where there is none.", + "description": "A reading from a meter on the member (extensions/energy-measurement.md; extensions/quantum-landscape.md §4.4); null where there is none or the run has none.", "anyOf": [ { "type": "object", "required": [ "joules_micro", - "class" + "window_us", + "class", + "scope" ], "properties": { "joules_micro": { - "type": "integer" + "type": "integer", + "description": "The run's calls' figures added up." }, - "class": {} + "window_us": { + "type": "integer", + "description": "The meter's windows over the run's calls, added up." + }, + "class": { + "description": "The acquisition class and the evidence energy-measurement asks of it." + }, + "scope": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "What the figure covers where that is more than this run: a whole rail or board, its idle draw and any other work on it in the windows. Null is this run alone." + } } }, { @@ -630,7 +653,7 @@ }, "refusals": { "type": "array", - "description": "Members that declined this job before anything ran, each with why, in the order they were offered; absent when the first member took it (extensions/quantum-landscape.md §5).", + "description": "Members a placing host passed over before one answered, each with why (an uncertified kernel, a declined workload, a failed run or an answer the host's checks refused), in the order they were offered; absent where there was none (extensions/quantum-landscape.md §5).", "items": { "type": "object", "required": [ @@ -694,7 +717,8 @@ "type": "object", "required": [ "id", - "class" + "class", + "kernels" ], "properties": { "id": { @@ -702,6 +726,34 @@ }, "class": { "type": "string" + }, + "kernels": { + "type": "array", + "description": "Each kernel the job used, in the order first used, with the parity the host found for it.", + "items": { + "type": "object", + "required": [ + "kernel", + "parity" + ], + "properties": { + "kernel": { + "enum": [ + "wai.quantum.landscape/settle", + "wai.quantum.landscape/sample" + ] + }, + "parity": { + "enum": [ + "reference", + "bit-exact", + "statistical", + "uncertified" + ], + "description": "What the member has earned for this kernel." + } + } + } } } }, @@ -733,7 +785,9 @@ "mean_energy", "accumulates_per_run", "priced_per_run", - "left_out_per_run" + "left_out_per_run", + "kernel", + "parity" ], "properties": { "temperature": { @@ -784,13 +838,28 @@ "left_out_per_run": { "type": "string", "description": "The board's own draw over the fewest clocks one run's sweeps take, which priced_per_run leaves out." + }, + "kernel": { + "enum": [ + "wai.quantum.landscape/settle", + "wai.quantum.landscape/sample" + ] + }, + "parity": { + "enum": [ + "reference", + "bit-exact", + "statistical", + "uncertified" + ], + "description": "What the member has earned for this kernel." } } } }, "refusals": { "type": "array", - "description": "Members that declined this job before anything ran, each with why, in the order they were offered; absent when the first member took it (extensions/quantum-landscape.md §5).", + "description": "Members a placing host passed over before one answered, each with why (an uncertified kernel, a declined workload, a failed run or an answer the host's checks refused), in the order they were offered; absent where there was none (extensions/quantum-landscape.md §5).", "items": { "type": "object", "required": [ @@ -851,7 +920,8 @@ "type": "object", "required": [ "id", - "class" + "class", + "kernels" ], "properties": { "id": { @@ -859,6 +929,34 @@ }, "class": { "type": "string" + }, + "kernels": { + "type": "array", + "description": "Each kernel the job used, in the order first used, with the parity the host found for it.", + "items": { + "type": "object", + "required": [ + "kernel", + "parity" + ], + "properties": { + "kernel": { + "enum": [ + "wai.quantum.landscape/settle", + "wai.quantum.landscape/sample" + ] + }, + "parity": { + "enum": [ + "reference", + "bit-exact", + "statistical", + "uncertified" + ], + "description": "What the member has earned for this kernel." + } + } + } } } }, @@ -894,7 +992,8 @@ "passed_checks", "best_energy", "operations_per_run", - "accumulates_per_run" + "accumulates_per_run", + "parity" ], "properties": { "solver": { @@ -927,13 +1026,108 @@ }, "accumulates_per_run": { "type": "integer" + }, + "parity": { + "anyOf": [ + { + "enum": [ + "reference", + "bit-exact", + "statistical", + "uncertified" + ], + "description": "What the member has earned for this kernel." + }, + { + "type": "null" + } + ], + "description": "The member's parity for the sampling kernel; null for a classical method, which ran on no member." } } } }, "refusals": { "type": "array", - "description": "Members that declined this job before anything ran, each with why, in the order they were offered; absent when the first member took it (extensions/quantum-landscape.md §5).", + "description": "Members a placing host passed over before one answered, each with why (an uncertified kernel, a declined workload, a failed run or an answer the host's checks refused), in the order they were offered; absent where there was none (extensions/quantum-landscape.md §5).", + "items": { + "type": "object", + "required": [ + "member", + "why" + ], + "properties": { + "member": { + "type": "string" + }, + "why": { + "type": "string" + } + } + } + } + } + }, + "replay": { + "type": "object", + "description": "Many seeds, one line each: the seed, a digest of that run's whole answer, the state, its energy, the verdict, the accumulates and the replay digest.", + "required": [ + "protocol", + "label", + "member", + "seeds", + "lines" + ], + "properties": { + "protocol": { + "const": "wai.quantum.landscape/1" + }, + "label": { + "type": "string" + }, + "member": { + "type": "object", + "required": [ + "id", + "class", + "kernel", + "parity" + ], + "properties": { + "id": { + "type": "string", + "description": "The member that ran it: cpu:reference, gpu:..., fpga:..." + }, + "class": { + "type": "string", + "description": "A class of the fabric: cpu, gpu, fpga." + }, + "kernel": { + "enum": [ + "wai.quantum.landscape/settle", + "wai.quantum.landscape/sample" + ] + }, + "parity": { + "enum": [ + "reference", + "bit-exact", + "statistical", + "uncertified" + ], + "description": "What the member has earned for this kernel." + } + } + }, + "seeds": { + "type": "integer" + }, + "lines": { + "type": "string" + }, + "refusals": { + "type": "array", + "description": "Members a placing host passed over before one answered, each with why (an uncertified kernel, a declined workload, a failed run or an answer the host's checks refused), in the order they were offered; absent where there was none (extensions/quantum-landscape.md §5).", "items": { "type": "object", "required": [ diff --git a/wai/wai-quantum/src/quantum_fabric.rs b/wai/wai-quantum/src/quantum_fabric.rs index 40129abdf..cd7d47955 100644 --- a/wai/wai-quantum/src/quantum_fabric.rs +++ b/wai/wai-quantum/src/quantum_fabric.rs @@ -624,8 +624,15 @@ pub trait RunMeter { /// After the last readback: the microjoules in the window and how they /// were acquired, or `None` when the window cannot be attributed to this - /// run alone. + /// run alone, unless [`RunMeter::scope`] says what else the figure holds. fn stop(&mut self) -> Result, String>; + + /// What a figure covers, where that is more than this run: an answer says + /// it beside the figure. A meter that attributes its window to the run + /// alone has nothing to add. + fn scope(&self) -> Option { + None + } } impl RunMeter for &mut M { @@ -636,6 +643,10 @@ impl RunMeter for &mut M { fn stop(&mut self) -> Result, String> { (**self).stop() } + + fn scope(&self) -> Option { + (**self).scope() + } } /// The meter of an unmetered run: it reads nothing. diff --git a/wai/wai-quantum/src/quantum_landscape.rs b/wai/wai-quantum/src/quantum_landscape.rs index 92354ddcf..60cbf154b 100644 --- a/wai/wai-quantum/src/quantum_landscape.rs +++ b/wai/wai-quantum/src/quantum_landscape.rs @@ -89,6 +89,7 @@ pub mod job; pub mod member; pub mod price; pub mod program; +pub mod reach; pub mod run; pub mod sample; pub mod settle; diff --git a/wai/wai-quantum/src/quantum_landscape/conformance.rs b/wai/wai-quantum/src/quantum_landscape/conformance.rs index 8a03c75bb..237aab132 100644 --- a/wai/wai-quantum/src/quantum_landscape/conformance.rs +++ b/wai/wai-quantum/src/quantum_landscape/conformance.rs @@ -3,18 +3,30 @@ //! //! - `settle`: whole workloads, the state, the sweeps, the ending and the //! accumulates a settle kernel gives from a start. A member's settle kernel -//! is `bit-exact` when it gives exactly these. +//! is `bit-exact` when it gives exactly every one it takes, and it takes +//! every one it must ([`MUST_TAKE_FIELD_BOUND`]). They reach past one +//! workgroup and past one batch of sweeps, pin the width of a field at 16, 32 +//! and 40 bits, and sit on both sides of the line a member may decline at. //! - `exp`, `schedule`, `cells`: the reference's exponential, its cooling //! schedule and its cut points, bit for bit, so that a text-only //! implementation that computes any of them another way is caught here. +//! - `draws`: the generator itself, at the seeds where a port of it in 32-bit +//! words first goes wrong. //! - `chains`: whole chains, the reference's bits. A sampling kernel is -//! `bit-exact` when it gives exactly these. -//! - `statistical`: a battery of small workloads, each with a schedule and -//! the exact distribution of the final states; a sampling kernel with draws -//! of its own is `statistical` when it passes every one. They are built to -//! catch a kernel that moves two coupled cells at once in any order of -//! cells, one that ignores its schedule or mishandles the last sweep at 0, -//! and one whose table is wrong for large fields or costs. +//! `bit-exact` when it gives exactly these, as the settle kernel does. Some +//! stop hot, so that the last sweep's draws are in the answer; some are long +//! enough to cross a device's windows, by work and by table, with every +//! window hot; some hold more chains than a workgroup; some have fields that +//! only their full width tells apart, above temperature 0 and at it. +//! - `statistical`: a battery of workloads, each with a schedule and the exact +//! distribution of the final states; a sampling kernel with draws of its own +//! is `statistical` when it passes every one. They are built to catch a kernel +//! that moves two coupled cells at once in any order of cells, one that +//! ignores its schedule, keeps only the end of it or mishandles the last sweep +//! at 0, one whose table is wrong for large fields or costs, one that is +//! wrong past the first four cells of a workload, and one whose fields are +//! held in 16 or 32 bits. A member may decline a workload of it only where +//! it may decline a vector. //! - `jobs`: whole requests, what their answers hold, refusals included: the //! worked examples and the rules a reader must follow. @@ -23,7 +35,7 @@ use super::energy::Landscape; use super::job; use super::member::{Chain, Member, check_chain, check_settle}; use super::run::state_text; -use super::sample::{anneal_with, chain_seed, cuts, schedule, start}; +use super::sample::{anneal_with, chain_seed, cuts, out, schedule, start}; use super::settle::{Outcome, state_hash}; use serde_json::{Value, json}; @@ -116,10 +128,73 @@ pub fn settle_cases() -> Vec { case("fields-at-the-bound", &wide, &[1, 0, -1, 1], 20); let cycle = landscape(2, &[(0, 1, 1)], &[(0, 1), (1, -1)], 0); case("two-cycle-with-fields", &cycle, &[0, 0], 60); + // Past one workgroup of cells and past one batch of sweeps: a wave down a path moves one + // cell a sweep, so every cell's move depends on the one before it. + for n in [65usize, 130, 300] { + case(&format!("path/n={n}"), &path(n), &vec![0; n], 2 * n as u32); + } + case("path-stopped-in-a-later-batch", &path(300), &[0; 300], 130); + case("random/n=1000", &random(1000, 6, 0x5E77), &pattern(0x51DE, 1000), 300); + let mut ring = Landscape::new(70); + for i in 0..70 { + ring.couple(i, (i + 1) % 70, -1); + } + case("a-two-cycle-past-a-workgroup", &ring, &vec![1; 70], 200); + for (name, l) in width_cases() { + let start = vec![0; l.len()]; + case(&name, &l, &start, 20); + } out } -/// The widest field bound a member must take: every workload whose fields fit 32 bits. +/// A wave along a path: a field on the first cell, every cell agreeing with the next. +fn path(n: usize) -> Landscape { + let mut l = Landscape::new(n); + for i in 0..n - 1 { + l.couple(i, i + 1, 1); + } + l.set_field(0, 3); + l +} + +/// `n` cells with about `degree` couplings each, of both signs. +fn random(n: usize, degree: usize, seed: u64) -> Landscape { + let mut l = Landscape::new(n); + for k in 0..(n * degree / 2) as u64 { + let (i, j) = ((out(seed, 2 * k + 1) % n as u64) as usize, (out(seed, 2 * k + 2) % n as u64) as usize); + if i != j { + l.couple(i, j, if out(seed ^ 1, k + 1) & 1 == 0 { 1 } else { -1 }); + } + } + l +} + +/// Workloads whose fields only their full width tells apart, on both sides of +/// the line a member may decline at: `(name, landscape)`. +fn width_cases() -> Vec<(String, Landscape)> { + const LANE: i64 = i32::MAX as i64; + let star = |b: i64, cost: i64| landscape(3, &[(0, 1, 1), (0, 2, 1)], &[(0, b), (1, b.signum()), (2, b.signum())], cost); + vec![ + // 65,536 is 0 in 16 bits, 40,000 negative, and −70,000 is inside the cost. + ("fields-past-16-bits".into(), landscape(4, &[], &[(0, 65_536), (1, 40_000), (2, -70_000), (3, 1)], 5_000)), + // 2³³ is 0 in 32 bits, 3·2³¹ negative, and −(2³³ + 5) is inside the cost. + ("fields-past-32-bits".into(), landscape(4, &[], &[(0, 1 << 33), (1, 3 << 31), (2, -((1 << 33) + 5)), (3, 1)], 7)), + // At the line: a field bound of exactly 2³¹ − 1 is one a member must take. + ("field-bound-at-the-line".into(), star(LANE - 2, 0)), + ("field-bound-at-the-line-below-zero".into(), star(-(LANE - 2), 0)), + // Its largest field is 2³¹ − 1 and its field bound 2³¹ + 1: a member that checks + // the fields and forgets the couplings overflows here. + ("field-bound-over-the-line-by-its-couplings".into(), star(LANE, 0)), + ("field-bound-over-the-line-below-zero".into(), star(-LANE, 0)), + // A cost past any lane on small fields: the cost holds every cell, at any width. + ("a-cost-past-the-lanes".into(), landscape(3, &[(0, 1, 1), (1, 2, -1)], &[(0, 3)], 1 << 33)), + ("a-cost-just-past-the-lanes".into(), landscape(3, &[(0, 1, 1), (1, 2, -1)], &[(0, LANE - 1)], LANE + 1)), + ] +} + +/// The widest field bound a member must take: every vector at or below it. The +/// bound is [`super::settle::Workload::field_bound`], the largest field any +/// state can give a cell, couplings included. pub const MUST_TAKE_FIELD_BOUND: i64 = i32::MAX as i64; /// Whether a member declines a vector it may decline, before running it: one @@ -130,7 +205,7 @@ fn declines(m: &dyn Member, w: &super::settle::Workload, sampling: bool) -> Opti } /// Whether a member's settle kernel gives every `settle` vector it takes -/// exactly, and takes every one whose fields fit 32 bits. +/// exactly, and takes every one it must. pub fn settle_vectors_hold(m: &dyn Member) -> bool { settle_cases().iter().all(|c| { let w = c.landscape.lower(); @@ -150,6 +225,7 @@ fn settle_json() -> Vec { json!({ "name": c.name, "landscape": landscape_json(&c.landscape), + "field_bound": c.landscape.lower().field_bound(), "start": state_text(&c.start), "bound": c.bound, "expect": { "state": state_text(&o.state), "sweeps": o.sweeps, "term": o.term.label(), "accumulates": o.accumulates, "state_hash": format!("{:016x}", state_hash(&o.state)) }, @@ -203,12 +279,31 @@ fn schedule_json() -> Vec { // ── chains ──────────────────────────────────────────────────────────────────────────────── +/// The temperatures of a chain's sweeps. +#[derive(Clone, Debug, PartialEq)] +pub enum Temperatures { + /// The schedule of a run: cooling in a line from `t0` to 0. + Cooling { t0: f64, sweeps: u32 }, + /// Runs of sweeps at one temperature, in order, which need not end at 0: + /// a member is given every sweep's temperature, so a vector can hold a + /// chain hot to its last sweep, where its draws are in the final state. + Held(Vec<(f64, u32)>), +} + +impl Temperatures { + pub fn each(&self) -> Vec { + match self { + Temperatures::Cooling { t0, sweeps } => schedule(*t0, *sweeps), + Temperatures::Held(runs) => runs.iter().flat_map(|&(t, k)| std::iter::repeat_n(t, k as usize)).collect(), + } + } +} + /// Chains the reference runs: every start of a run with this seed. pub struct ChainCase { pub name: String, pub landscape: Landscape, - pub t0: f64, - pub sweeps: u32, + pub temperatures: Temperatures, pub seed: u64, pub restarts: u64, } @@ -224,6 +319,13 @@ impl ChainCase { } } +/// Seeds whose first draw, for one cell at field 0 and cost 0, a port of the +/// generator in 32-bit words gets wrong: found by search, and held by a test. +/// The first needs the carry out of the middle of a 32 by 32 product; the other +/// two fall in the same high word as a cut point, so that only a compare of all +/// 53 bits puts them on the right side of it. +pub const DRAW_EDGES: [(&str, u64); 3] = [("the-carry-in-a-product", 1982), ("under-the-first-cut-by-its-low-word", 1_174_725), ("under-the-second-cut-by-its-low-word", 6_472_161)]; + pub fn chain_cases() -> Vec { let mixed = landscape(8, &[(0, 1, -1), (1, 2, -1), (0, 2, -1), (3, 4, 1), (4, 5, 1), (3, 5, 1), (5, 6, -1), (3, 6, -1), (6, 7, 1)], &[], 1); let dense = stored(&pattern(0xBEEF, 16)); @@ -231,17 +333,46 @@ pub fn chain_cases() -> Vec { let mut out = Vec::new(); for (name, l) in [("sparse-with-a-cost", &mixed), ("dense", &dense), ("wide-fields", &wide)] { for (t0, sweeps, seed, restarts) in [(2.0f64, 30u32, 1u64, 1u64), (0.5, 12, 2, 1), (0.0, 7, 3, 1), (1.8708286933869707, 41, 4, 8), (13.7, 9, 5, 3)] { - out.push(ChainCase { name: format!("{name}/t0={t0}/sweeps={sweeps}"), landscape: l.clone(), t0, sweeps, seed, restarts }); + out.push(ChainCase { name: format!("{name}/t0={t0}/sweeps={sweeps}"), landscape: l.clone(), temperatures: Temperatures::Cooling { t0, sweeps }, seed, restarts }); } } + let held = |runs: &[(f64, u32)]| Temperatures::Held(runs.to_vec()); + // The draws, held to the last sweep: at temperature 1 with no field a cell takes each + // state a third of the time, so a wrong draw shows in the state at once. + for (name, seed) in DRAW_EDGES { + out.push(ChainCase { name: format!("a-draw/{name}"), landscape: Landscape::new(1), temperatures: held(&[(1.0, 1)]), seed, restarts: 1 }); + } + out.push(ChainCase { name: "one-sweep-of-16384-draws".into(), landscape: Landscape::new(128), temperatures: held(&[(1.0, 1)]), seed: 6, restarts: 128 }); + // More chains than a workgroup holds, and hot to the end. + out.push(ChainCase { name: "more-chains-than-a-workgroup".into(), landscape: wide.clone(), temperatures: held(&[(13.7, 3)]), seed: 7, restarts: 130 }); + // In the reference's order or not: a chain that stops hot shows the order of its cells. + out.push(ChainCase { name: "the-order-of-the-cells".into(), landscape: mixed.clone(), temperatures: held(&[(1.5, 2)]), seed: 8, restarts: 16 }); + // Windows of sweeps, by table: every cell's reach is its own, so 320 entries a sweep + // and about fifty sweeps a window. Hot from start to end, with sweeps at 0 inside. + let mut spread = Landscape::new(64); + for i in 0..64 { + spread.couple(i, (i + 1) % 64, 1); + spread.set_field(i, 7 * i as i64 - 224); + } + out.push(ChainCase { name: "windows-by-their-table".into(), landscape: spread, temperatures: held(&[(400.0, 70), (0.0, 2), (250.0, 70), (150.0, 60)]), seed: 9, restarts: 4 }); + // Windows of sweeps, by work: about 170 sweeps a window, couplings of both signs. + out.push(ChainCase { name: "windows-by-their-work".into(), landscape: random(1024, 4, 0x71D0), temperatures: held(&[(3.0, 200), (1.5, 200)]), seed: 10, restarts: 2 }); + // Fields only their full width tells apart: at temperature 0, and at a temperature + // of their own size, so that every cell's field is in its draw. + for (name, l) in width_cases() { + let t = l.field().iter().map(|b| b.unsigned_abs()).max().unwrap_or(1).max(1) as f64; + out.push(ChainCase { name: format!("{name}/at-0"), landscape: l.clone(), temperatures: Temperatures::Cooling { t0: 0.0, sweeps: 5 }, seed: 11, restarts: 4 }); + out.push(ChainCase { name: format!("{name}/hot"), landscape: l, temperatures: held(&[(t, 4)]), seed: 12, restarts: 8 }); + } out } -/// Whether a member's sampling kernel gives every `chains` vector exactly, every case in one call. +/// Whether a member's sampling kernel gives every `chains` vector it takes +/// exactly, every case in one call, and takes every one it must. pub fn chain_vectors_hold(m: &dyn Member) -> bool { chain_cases().iter().all(|c| { let w = c.landscape.lower(); - let temperatures = schedule(c.t0, c.sweeps); + let temperatures = c.temperatures.each(); if let Some(may) = declines(m, &w, temperatures.iter().any(|&t| t > 0.0)) { return may; } @@ -256,23 +387,38 @@ fn chains_json() -> Vec { .iter() .map(|c| { let w = c.landscape.lower(); - let temperatures = schedule(c.t0, c.sweeps); - json!({ + let temperatures = c.temperatures.each(); + let mut v = json!({ "name": c.name, "landscape": landscape_json(&c.landscape), + "field_bound": w.field_bound(), "seed": c.seed.to_string(), - "t0": c.t0, - "t0_bits": bits(c.t0), - "sweeps": c.sweeps, "chains": c.chains().iter().map(|ch| { let o = anneal_with(&w, &ch.start, &temperatures, ch.seed); json!({ "chain": ch.seed.to_string(), "start": state_text(&ch.start), "expect": { "state": state_text(&o.state), "accumulates": o.accumulates, "draws": o.draws, "term": o.term.label(), "state_hash": format!("{:016x}", state_hash(&o.state)) } }) }).collect::>(), - }) + }); + match &c.temperatures { + Temperatures::Cooling { t0, sweeps } => { + v["t0"] = json!(t0); + v["t0_bits"] = json!(bits(*t0)); + v["sweeps"] = json!(sweeps); + } + Temperatures::Held(runs) => v["held"] = runs.iter().map(|&(t, k)| json!({ "temperature": t, "temperature_bits": bits(t), "sweeps": k })).collect(), + } + v }) .collect() } +/// The generator's outputs: the first few of several seeds, and every seed of +/// [`DRAW_EDGES`] with the draw its case turns on. +fn draws_json() -> Vec { + let mut at: Vec<(u64, u64)> = [0u64, 1, 2, u64::MAX, 0x9E37_79B9_7F4A_7C15].iter().flat_map(|&s| (1..=4).map(move |j| (s, j))).collect(); + at.extend(DRAW_EDGES.iter().map(|&(_, seed)| (chain_seed(seed, 0), 1))); + at.iter().map(|&(s, j)| json!({ "seed": s.to_string(), "j": j, "out": out(s, j).to_string() })).collect() +} + // ── the statistical battery ─────────────────────────────────────────────────────────────── /// How many chains each battery workload runs, and the test its final states are held to. @@ -282,25 +428,66 @@ pub const BATTERY_POOL_BELOW: f64 = 5.0; /// The seed battery workload `k` draws its chains from: `chain_seed(BATTERY_SEED + k, r)`. pub const BATTERY_SEED: u64 = 0xBA77_E124; -/// One workload of the battery: a landscape, a schedule, and the temperature -/// the schedule holds before its end, at which the chains have mixed. +/// What a battery workload's final states are held to. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Law { + /// Boltzmann at the schedule's last temperature above 0, reached on a + /// plateau; carried through one deterministic sweep where the schedule ends at 0. + Settled, + /// The law of the walk itself, from the start's uniform law through every + /// sweep of the schedule, on a block whose cells are all alike. Which order a + /// sweep takes the cells in only renames them, so the law is read over how + /// many cells hold each state, and any order of cells passes. + Walked, +} + +/// One workload of the battery: a landscape, a schedule, what its final states +/// are held to, and how many cells a block of it has. A landscape of several +/// blocks holds copies of one block, none coupled to another, each held to the +/// block's law by itself. pub struct BatteryCase { pub name: &'static str, pub landscape: Landscape, pub temperatures: Vec, + pub law: Law, + pub block: usize, } impl BatteryCase { + fn of(name: &'static str, landscape: Landscape, temperatures: Vec) -> BatteryCase { + let block = landscape.len(); + BatteryCase { name, landscape, temperatures, law: Law::Settled, block } + } + /// The temperature the schedule settles at: its last sweep above 0. pub fn equilibrium(&self) -> f64 { self.temperatures.iter().rev().copied().find(|&t| t > 0.0).unwrap_or(0.0) } - /// The exact distribution of the final state, over the `3^n` states in the - /// order `Σ (s_i + 1) · 3^i`: Boltzmann at [`Self::equilibrium`], and if the - /// schedule ends at 0, that distribution carried through one deterministic sweep. + /// The first block of the landscape by itself. + fn unit(&self) -> Landscape { + let mut l = Landscape::new(self.block); + for i in 0..self.block { + for j in (i + 1)..self.block { + l.couple(i, j, self.landscape.coupling(i, j)); + } + l.set_field(i, self.landscape.field()[i]); + } + l.set_cost(self.landscape.cost()); + l + } + + /// The exact distribution of a block's final state, over its `3^block` + /// states in the order `Σ (s_i + 1) · 3^i`. pub fn exact(&self) -> Vec { - let l = &self.landscape; + match self.law { + Law::Settled => self.settled(), + Law::Walked => self.walked(), + } + } + + fn settled(&self) -> Vec { + let l = self.unit(); let n = l.len(); let all: Vec> = (0..3usize.pow(n as u32)).map(|k| state_of(k, n)).collect(); let energies: Vec = all.iter().map(|s| l.energy(s)).collect(); @@ -321,6 +508,34 @@ impl BatteryCase { } } + /// Every sweep of the schedule, cell by cell in order, on the whole law at once. + fn walked(&self) -> Vec { + let l = self.unit(); + let (n, w) = (l.len(), l.lower()); + let states = 3usize.pow(n as u32); + let mut p = vec![1.0 / states as f64; states]; + for &t in &self.temperatures { + for i in 0..n { + let mut q = vec![0.0; states]; + for (k, &pk) in p.iter().enumerate() { + let mut s = state_of(k, n); + let h = w.field(&s, i) as f64; + let c = w.cost() as f64; + let weights = [(-h - c) / t, 0.0, (h - c) / t]; + let top = weights.iter().copied().fold(f64::MIN, f64::max); + let e = weights.map(|x| crate::repro::exp(x - top)); + let z = e[0] + e[1] + e[2]; + for (v, ev) in [-1i8, 0, 1].into_iter().zip(e) { + s[i] = v; + q[index_of(&s)] += pk * ev / z; + } + } + p = q; + } + } + p + } + pub fn chains(&self, k: usize) -> Vec { (0..u64::from(BATTERY_CHAINS)) .map(|r| { @@ -351,19 +566,46 @@ pub fn battery() -> Vec { cooling.extend(vec![0.7; 24]); let mut quench = vec![1.3; 31]; quench.push(0.0); + // A triangle pulled one way: warm, it lies mostly that way; cold, a chain cannot leave + // the side it is on in the sweeps it has. So the end of the schedule alone gives another law. + let alike = landscape(3, &[(0, 1, 1), (1, 2, 1), (0, 2, 1)], &[(0, 1), (1, 1), (2, 1)], 0); + let mut two_stages = vec![1.2; 16]; + two_stages.extend(vec![0.2; 6]); + // Past the first cells of a workload: eight triangles side by side, each held to its law. + let (blocks, side) = (8usize, 3usize); + let mut triangles = Landscape::new(blocks * side); + for b in 0..blocks { + let at = b * side; + triangles.couple(at, at + 1, 1).couple(at + 1, at + 2, 1).couple(at, at + 2, 1); + triangles.set_field(at, 1); + } + triangles.set_cost(1); + let mut ring = Landscape::new(6); + for i in 0..6 { + ring.couple(i, (i + 1) % 6, if i % 3 == 0 { -1 } else { 1 }); + } + ring.set_field(0, 1).set_field(3, -1).set_cost(1); vec![ - BatteryCase { name: "one-pair", landscape: landscape(2, &[(0, 1, 1)], &[], 0), temperatures: constant(1.0, 32) }, - BatteryCase { name: "a-pair-apart", landscape: landscape(3, &[(0, 2, 1)], &[(1, 1)], 0), temperatures: constant(1.0, 32) }, - BatteryCase { name: "a-pair-far-apart", landscape: landscape(4, &[(0, 3, -1), (1, 2, 1)], &[(1, -1)], 0), temperatures: constant(0.8, 32) }, - BatteryCase { name: "a-triangle", landscape: landscape(3, &[(0, 1, 1), (1, 2, 1), (0, 2, 1)], &[], 1), temperatures: constant(1.2, 32) }, - BatteryCase { name: "wide-fields-and-cost", landscape: landscape(3, &[(0, 1, 1), (1, 2, -1)], &[(0, 17), (1, -9), (2, 40)], 8), temperatures: constant(13.7, 32) }, - BatteryCase { name: "a-small-field-cold", landscape: landscape(2, &[(0, 1, 1)], &[(0, 5), (1, -3)], 2), temperatures: constant(0.45, 32) }, - BatteryCase { name: "ending-at-zero", landscape: landscape(4, &[(0, 1, 1), (1, 2, 1), (2, 3, -1)], &[(0, 1)], 1), temperatures: quench }, - BatteryCase { name: "cooling", landscape: landscape(3, &[(0, 1, -1), (1, 2, -1), (0, 2, -1)], &[(2, -2)], 0), temperatures: cooling }, + BatteryCase::of("one-pair", landscape(2, &[(0, 1, 1)], &[], 0), constant(1.0, 32)), + BatteryCase::of("a-pair-apart", landscape(3, &[(0, 2, 1)], &[(1, 1)], 0), constant(1.0, 32)), + BatteryCase::of("a-pair-far-apart", landscape(4, &[(0, 3, -1), (1, 2, 1)], &[(1, -1)], 0), constant(0.8, 32)), + BatteryCase::of("a-triangle", landscape(3, &[(0, 1, 1), (1, 2, 1), (0, 2, 1)], &[], 1), constant(1.2, 32)), + BatteryCase::of("wide-fields-and-cost", landscape(3, &[(0, 1, 1), (1, 2, -1)], &[(0, 17), (1, -9), (2, 40)], 8), constant(13.7, 32)), + BatteryCase::of("a-small-field-cold", landscape(2, &[(0, 1, 1)], &[(0, 5), (1, -3)], 2), constant(0.45, 32)), + BatteryCase::of("ending-at-zero", landscape(4, &[(0, 1, 1), (1, 2, 1), (2, 3, -1)], &[(0, 1)], 1), quench), + BatteryCase::of("cooling", landscape(3, &[(0, 1, -1), (1, 2, -1), (0, 2, -1)], &[(2, -2)], 0), cooling), + BatteryCase::of("a-ring-of-six", ring, constant(1.1, 32)), + BatteryCase { name: "triangles-side-by-side", landscape: triangles, temperatures: constant(1.2, 32), law: Law::Settled, block: side }, + BatteryCase { name: "a-schedule-that-matters", landscape: alike, temperatures: two_stages, law: Law::Walked, block: 3 }, + // Fields that only their full width tells apart, at a temperature of their own size: + // past 16 bits, which every member takes, and past 32, which a member may decline. + BatteryCase::of("fields-past-16-bits", landscape(3, &[(0, 1, 1), (1, 2, -1)], &[(0, 70_000), (1, -65_536), (2, 40_000)], 30_000), constant(40_000.0, 32)), + BatteryCase::of("fields-past-32-bits", landscape(3, &[(0, 1, 1), (1, 2, -1)], &[(0, 3 << 31), (1, -(1 << 33)), (2, (1 << 32) + 5)], 1 << 32), constant(4_294_967_296.0, 32)), ] } -/// What one workload of the battery found. +/// What one workload of the battery found: over its blocks, the one furthest +/// from its law. #[derive(Clone, Debug, PartialEq)] pub struct BatteryResult { pub name: &'static str, @@ -371,6 +613,9 @@ pub struct BatteryResult { pub degrees: usize, pub critical: f64, pub passed: bool, + /// The member declined the workload, which it may where the field bound is above + /// [`MUST_TAKE_FIELD_BOUND`], as for a vector; it then passes without running. + pub declined: bool, } /// Pearson's statistic of `counts` against `expected` (probabilities) over @@ -399,31 +644,60 @@ pub fn chi_square(counts: &[u64], expected: &[f64], total: u64) -> (f64, usize, (stat, df, df as f64 + 5.0 * (2.0 * df as f64).sqrt()) } -/// Run the battery on a member: every workload's chains, their final states -/// against the exact distribution. A member that fails to answer, or whose -/// answers fail [`check_chain`], fails that workload. +/// Bins as a block's law reads them: every state, or for [`Law::Walked`] each +/// count of cells at −1, 0 and +1. +fn binned(law: Law, n: usize, by_state: &[f64]) -> Vec { + if law == Law::Settled { + return by_state.to_vec(); + } + let mut bins = std::collections::BTreeMap::new(); + for (k, &v) in by_state.iter().enumerate() { + let mut s = state_of(k, n); + s.sort_unstable(); + *bins.entry(s).or_insert(0.0) += v; + } + bins.into_values().collect() +} + +/// Run the battery on a member: every workload's chains, the final states of +/// each block against the exact distribution. A member that fails to answer, or +/// whose answers fail [`check_chain`], fails that workload. pub fn run_battery(m: &dyn Member) -> Vec { battery() .iter() .enumerate() .map(|(k, case)| { let w = case.landscape.lower(); - let expected = case.exact(); - let mut counts = vec![0u64; expected.len()]; - // Every workload of the battery fits 32 bits: a member that declines one fails it. - let mut ok = m.takes(&w, true).is_ok(); + if let Some(may) = declines(m, &w, true) { + return BatteryResult { name: case.name, chi_square: 0.0, degrees: 0, critical: 0.0, passed: may, declined: true }; + } + let exact = case.exact(); + let blocks = case.landscape.len() / case.block; + let mut counts = vec![vec![0u64; exact.len()]; blocks]; + let mut ok = true; for chunk in case.chains(k).chunks(4096) { match m.chains(&w, chunk, &case.temperatures) { Ok(got) if got.len() == chunk.len() && got.iter().all(|o| check_chain(o, &w, &case.temperatures).is_ok()) => { for o in got { - counts[index_of(&o.state)] += 1; + for (b, c) in counts.iter_mut().enumerate() { + c[index_of(&o.state[b * case.block..(b + 1) * case.block])] += 1; + } } } _ => ok = false, } } - let (chi, df, critical) = chi_square(&counts, &expected, u64::from(BATTERY_CHAINS)); - BatteryResult { name: case.name, chi_square: chi, degrees: df, critical, passed: ok && chi <= critical } + let expected = binned(case.law, case.block, &exact); + let worst = counts + .iter() + .map(|c| { + let as_real: Vec = c.iter().map(|&v| v as f64).collect(); + let got: Vec = binned(case.law, case.block, &as_real).iter().map(|&v| v as u64).collect(); + chi_square(&got, &expected, u64::from(BATTERY_CHAINS)) + }) + .fold((0.0f64, 1usize, 0.0f64), |w, r| if r.0 - r.2 > w.0 - w.2 || w.2 == 0.0 { r } else { w }); + let (chi, df, critical) = worst; + BatteryResult { name: case.name, chi_square: chi, degrees: df, critical, passed: ok && chi <= critical, declined: false } }) .collect() } @@ -438,6 +712,8 @@ fn battery_json() -> Value { "name": c.name, "landscape": landscape_json(&c.landscape), "temperatures": c.temperatures, + "law": if c.law == Law::Walked { "walked" } else { "settled" }, + "block": c.block, "state_order": "the sum of (s_i + 1) · 3^i", "exact": c.exact(), })).collect::>(), @@ -494,6 +770,7 @@ pub fn vectors() -> Value { "exp": exp_arguments().iter().map(|&x| json!([bits(x), bits(crate::repro::exp(x))])).collect::>(), "schedule": schedule_json(), "cells": cells_json(), + "draws": draws_json(), "chains": chains_json(), "statistical": battery_json(), "jobs": jobs_json(), diff --git a/wai/wai-quantum/src/quantum_landscape/cuda.rs b/wai/wai-quantum/src/quantum_landscape/cuda.rs index 4fdd0eea4..78ad755e9 100644 --- a/wai/wai-quantum/src/quantum_landscape/cuda.rs +++ b/wai/wai-quantum/src/quantum_landscape/cuda.rs @@ -4,11 +4,9 @@ //! The kernels are those of `landscape.wgsl` with native 64-bit words: the //! settle kernel one thread a cell, one launch a sweep and one to advance; the //! sampling kernel one thread a chain, its cells in order, the reference's -//! draws and the host's cut points. Fields are 64-bit, so a settle of any -//! workload the extension allows is taken; a sampling run is declined only -//! when one sweep's cut points would be more than -//! [`device::MAX_TABLE_ENTRIES`]. Every launch is on one stream, so each sees -//! what the one before it wrote. +//! draws and the host's cut points. Fields are 64-bit, so it takes every +//! workload a job can give. Every launch is on one stream, so each sees what +//! the one before it wrote. //! //! The driver and the runtime compiler are loaded when first asked for, so a //! build with this feature runs on a host with no CUDA and finds no member. @@ -69,7 +67,7 @@ __device__ __forceinline__ u64 splitmix(u64 s, u64 j) { return x ^ (x >> 31); } -extern "C" __global__ void chains_in_order(const unsigned* rows, const unsigned* cols, const int* vals, const i64* fields, i64 cost, unsigned n, unsigned chains, i64 bound, unsigned k0, unsigned k1, const u64* seeds, const unsigned* sweeps, const u64* table, int* cells) { +extern "C" __global__ void chains_in_order(const unsigned* rows, const unsigned* cols, const int* vals, const i64* fields, i64 cost, unsigned n, unsigned chains, const i64* offsets, unsigned k0, unsigned k1, const u64* seeds, const unsigned* sweeps, const u64* table, int* cells) { unsigned c = blockIdx.x * blockDim.x + threadIdx.x; if (c >= chains) return; int* s = cells + (u64)c * n; @@ -84,7 +82,7 @@ extern "C" __global__ void chains_in_order(const unsigned* rows, const unsigned* for (unsigned i = 0; i < n; ++i) { i64 h = field_of(rows, cols, vals, fields, s, i); u64 u = splitmix(seed, (u64)k * n + i + 1) >> 11; - u64 e = (u64)(kind - 1u) + (u64)(h + bound); + u64 e = (u64)(kind - 1u) + (u64)h + (u64)offsets[i]; s[i] = u < table[2 * e] ? -1 : (u < table[2 * e + 1] ? 0 : 1); } } @@ -128,16 +126,23 @@ fn driver(e: cudarc::driver::DriverError) -> String { /// Every device the driver lists, opened and compiled for, each a member or /// the reason it is not one. Nothing, with the reason, where there is no CUDA. pub fn discover() -> Vec> { + discover_with(SOURCE) +} + +/// [`discover`] with kernels other than [`SOURCE`]: how a test shows that a +/// fault in the kernels loses the grade it would otherwise earn on this device. +#[doc(hidden)] +pub fn discover_with(source: &str) -> Vec> { if let Err(why) = crate::quantum_fabric_cuda::available() { return vec![Err(why)]; } match CudaContext::device_count() { - Ok(count) => (0..usize::try_from(count).unwrap_or(0)).map(open).collect(), + Ok(count) => (0..usize::try_from(count).unwrap_or(0)).map(|k| open(k, source)).collect(), Err(e) => vec![Err(driver(e))], } } -fn open(ordinal: usize) -> Result { +fn open(ordinal: usize, source: &str) -> Result { let ctx = CudaContext::new(ordinal).map_err(driver)?; let name = ctx.name().map_err(driver)?; let (major, minor) = ctx.compute_capability().map_err(driver)?; @@ -147,7 +152,7 @@ fn open(ordinal: usize) -> Result { }); let arch: &'static str = Box::leak(format!("compute_{major}{minor}").into_boxed_str()); let opts = CompileOptions { arch: Some(arch), options: vec!["--std=c++17".into(), "--fmad=false".into()], ..Default::default() }; - let ptx = compile_ptx_with_opts(SOURCE, opts).map_err(|e| format!("{name}: the kernels did not compile: {e}"))?; + let ptx = compile_ptx_with_opts(source, opts).map_err(|e| format!("{name}: the kernels did not compile: {e}"))?; let module = ctx.load_module(ptx).map_err(driver)?; let kernels = Kernels { settle_sweep: module.load_function("settle_sweep").map_err(driver)?, @@ -204,8 +209,9 @@ impl CudaLandscape { fn chains_on_device(&self, w: &Workload, chains: &[Chain], temperatures: &[f64]) -> Result, String> { let sampling = temperatures.iter().any(|&t| t > 0.0); + let reach = device::reach(w); if sampling { - device::table_fits(w)?; + device::table_fits(&reach)?; } let (n, count) = (w.n(), chains.len()); if count == 0 { @@ -226,19 +232,22 @@ impl CudaLandscape { let mut cells = s.clone_htod(&init).map_err(driver)?; drop(init); let seeds = s.clone_htod(&chains.iter().map(|c| c.seed).collect::>()).map_err(driver)?; - let (cost, n32, count32, bound) = (w.cost(), n as u32, count as u32, w.field_bound()); - let window = device::window(w, sampling); + let offsets = s.clone_htod(&reach.offsets).map_err(driver)?; + let (cost, n32, count32) = (w.cost(), n as u32, count as u32); + let window = device::window(w, &reach, sampling); let mut k0 = 0usize; while k0 < temperatures.len() { let k1 = (k0 + window).min(temperatures.len()); - let (sweeps, table) = device::window_sweeps(w, &temperatures[k0..k1]); + let (sweeps, table) = device::window_sweeps(w, &reach, &temperatures[k0..k1]); let (sweeps, table) = (s.clone_htod(&sweeps).map_err(driver)?, s.clone_htod(&nonempty(&table)).map_err(driver)?); let (a0, a1) = (k0 as u32, k1 as u32); let mut a = s.launch_builder(&self.kernels.chains); - a.arg(&rows).arg(&cols).arg(&vals).arg(&fields).arg(&cost).arg(&n32).arg(&count32).arg(&bound).arg(&a0).arg(&a1).arg(&seeds).arg(&sweeps).arg(&table).arg(&mut cells); - // SAFETY: 2·count·n cells, a seed a chain, a word a sweep of the window, and the - // window's table of (a, b) pairs for every field from −bound to bound. + a.arg(&rows).arg(&cols).arg(&vals).arg(&fields).arg(&cost).arg(&n32).arg(&count32).arg(&offsets).arg(&a0).arg(&a1).arg(&seeds).arg(&sweeps).arg(&table).arg(&mut cells); + // SAFETY: 2·count·n cells, an offset a cell, a seed a chain, a word a sweep of the + // window, and the window's table of (a, b) pairs for every field a cell can reach. unsafe { a.launch(launch(count as u64)) }.map_err(driver)?; + // One window at a time: only its table is held, and a failure leaves no more queued. + s.synchronize().map_err(driver)?; k0 = k1; } let got = s.clone_dtoh(&cells).map_err(driver)?; @@ -261,11 +270,11 @@ impl Member for CudaLandscape { } fn label(&self) -> String { - device::label(&self.name, "CUDA", "its cells in the reference's order", self.meter.is_metered()) + device::label("gpu", &self.name, "CUDA", "its cells in the reference's order") } fn takes(&self, w: &Workload, sampling: bool) -> Result<(), String> { - if sampling { device::table_fits(w) } else { Ok(()) } + if sampling { device::table_fits(&device::reach(w)) } else { Ok(()) } } fn settle(&self, w: &Workload, start: &[i8], bound: u32) -> Result { @@ -280,3 +289,23 @@ impl Member for CudaLandscape { self.meter.last() } } + +#[cfg(test)] +mod tests { + use super::*; + + /// The kernels compile, where the runtime compiler is, with no device to run them on: a + /// build host whose card is off its bus still catches a kernel that no longer compiles. + /// With `WAI_LANDSCAPE_NVRTC` set, a host with no runtime compiler fails. + #[test] + fn the_kernels_compile_where_the_runtime_compiler_is() { + // SAFETY: only tries to open the runtime compiler's library by its known names. + if !unsafe { cudarc::nvrtc::sys::is_culib_present() } { + assert!(std::env::var_os("WAI_LANDSCAPE_NVRTC").is_none(), "WAI_LANDSCAPE_NVRTC is set, and this host has no runtime compiler"); + eprintln!("no runtime compiler on this host: nothing compiled"); + return; + } + let opts = CompileOptions { arch: Some("compute_80"), options: vec!["--std=c++17".into(), "--fmad=false".into()], ..Default::default() }; + compile_ptx_with_opts(SOURCE, opts).unwrap(); + } +} diff --git a/wai/wai-quantum/src/quantum_landscape/device.rs b/wai/wai-quantum/src/quantum_landscape/device.rs index 7f30140f8..ccea4465b 100644 --- a/wai/wai-quantum/src/quantum_landscape/device.rs +++ b/wai/wai-quantum/src/quantum_landscape/device.rs @@ -1,20 +1,19 @@ //! What the GPU members of the landscape fabric share: the workload in rows, -//! the cut-point tables the host works out, the windows of sweeps a dispatch -//! runs, the outcomes a member returns, its id, its label and its meter. +//! the cut-point tables the host works out and the windows of sweeps a +//! dispatch runs ([`super::reach`]), the outcomes a member returns, its id, its +//! label and its meter. //! The kernels themselves are each member's ([`super::webgpu`], [`super::cuda`], //! [`super::metal`]). use super::member::Metered; -use super::sample::cuts; +pub use super::reach::{MAX_TABLE_ENTRIES, Reach, WINDOW_ENTRIES, WINDOW_WORK, cut_pairs, reach, table_fits, window, window_sweeps}; use super::settle::{Outcome, Term, Workload}; +use crate::quantum_energy::check_label; use crate::quantum_fabric::RunMeter; +use std::collections::HashMap; use std::sync::Mutex; - -/// The most cut-point entries one sweep's table may hold: a field bound of about two million. -pub const MAX_TABLE_ENTRIES: u64 = 1 << 22; - -/// The work one chain does in one dispatch, at most: cells × (couplings + 1) × sweeps. -pub const WINDOW_WORK: u64 = 1 << 22; +use std::thread::{self, ThreadId}; +use std::time::Instant; /// Sweeps a settle submits before it reads where it got to. pub const SETTLE_BATCH: u32 = 64; @@ -43,53 +42,6 @@ pub fn rows(w: &Workload) -> Rows { r } -/// One sweep's cut points at temperature `t`, `(a, b)` for every field from -/// `−bound` to `bound`: the reference's own ([`cuts`]). -pub fn cut_pairs(cost: i64, t: f64, bound: i64, out: &mut Vec) { - let beta = 1.0 / t; - for h in -bound..=bound { - let (a, b) = cuts(h, cost, beta); - out.extend([a, b]); - } -} - -/// Whether a sampling run's table fits: one sweep's cut points within [`MAX_TABLE_ENTRIES`]. -pub fn table_fits(w: &Workload) -> Result<(), String> { - let bound = w.field_bound(); - if (2 * bound as u64).saturating_add(1) > MAX_TABLE_ENTRIES { - return Err(format!("its fields reach {bound}, and a sweep's cut points for every field would be more than {MAX_TABLE_ENTRIES} entries")); - } - Ok(()) -} - -/// How many sweeps one dispatch runs: within [`WINDOW_WORK`] for each chain, -/// and within [`MAX_TABLE_ENTRIES`] of cut points. -pub fn window(w: &Workload, sampling: bool) -> usize { - let n = w.n() as u64; - let per_sweep = n * (w.nonzeros() / n.max(1) + 2); - let mut window = (WINDOW_WORK / per_sweep.max(1)).max(1); - if sampling { - window = window.min((MAX_TABLE_ENTRIES / (2 * w.field_bound() as u64 + 1)).max(1)); - } - window as usize -} - -/// The sweeps of a window as a device reads them, and their tables: 0 for a -/// sweep at temperature 0, else 1 + where its table starts, in entries. -pub fn window_sweeps(w: &Workload, temperatures: &[f64]) -> (Vec, Vec) { - let mut sweeps = Vec::with_capacity(temperatures.len()); - let mut table = Vec::new(); - for &t in temperatures { - if t > 0.0 { - sweeps.push(1 + (table.len() / 2) as u32); - cut_pairs(w.cost(), t, w.field_bound(), &mut table); - } else { - sweeps.push(0); - } - } - (sweeps, table) -} - /// The outcomes of `count` chains from their cells: the state, every sweep /// run, the work and the draws the schedule comes to, and whether it rests. pub fn outcomes(w: &Workload, cells: &[i32], count: usize, temperatures: &[f64]) -> Vec { @@ -136,48 +88,54 @@ pub fn id_of(class: &str, backend: &str, name: &str, hardware: &str) -> String { format!("{class}:{backend}.{}.{:04x}", slug.trim_end_matches('-'), h & 0xffff) } -/// What a GPU member's answers say of themselves. -pub fn label(device: &str, through: &str, order: &str, metered: bool) -> String { - let joules = if metered { - "The joule line is this run's operation count priced at rates measured on an FPGA, with a published figure for one integer add beside it for scale, and a reading of this GPU's own energy counter beside those." - } else { - "The joule line is this run's operation count priced at rates measured on an FPGA, with a published figure for one integer add beside it for scale." - }; +/// What a device member's answers say of themselves. `class` is the member's: +/// a software driver is `cpu`, and is not called a GPU. +pub fn label(class: &str, device: &str, through: &str, order: &str) -> String { + let on = if class == "gpu" { format!("this machine's GPU, {device}") } else { format!("a software driver on this machine's CPU, {device}") }; format!( - "A classical emulator of the settling model of a planned energy-based processor, running on this machine's GPU, {device}, through {through}, {order}. Energies are computed exactly. {joules} No photonic hardware, custom chip or quantum effect is involved in this run." + "A classical emulator of the settling model of a planned energy-based processor, running on {on}, through {through}, {order}. Energies are computed exactly. The joule line is this run's operation count priced at rates measured on an FPGA, with a published figure for one integer add beside it for scale. No photonic hardware, custom chip or quantum effect is involved in this run." ) } -/// A member's meter, read around each call, and the last call's reading. +/// A member's meter, read around each call. A reading goes back to the thread +/// that made the call, so that runs sharing a member never carry each other's. #[derive(Default)] pub struct MeterSlot { meter: Option>>, - reading: Mutex>, + readings: Mutex>, } impl MeterSlot { pub fn new(meter: Box) -> MeterSlot { - MeterSlot { meter: Some(Mutex::new(meter)), reading: Mutex::new(None) } + MeterSlot { meter: Some(Mutex::new(meter)), readings: Mutex::new(HashMap::new()) } } - pub fn is_metered(&self) -> bool { - self.meter.is_some() - } - - /// Run `call` inside the meter's window, keeping its reading. + /// Run `call` inside the meter's window, keeping its reading for this + /// thread. A meter that fails leaves the call unmetered and the call whole, + /// and a figure its label does not hold for gives no reading. pub fn around(&self, call: impl FnOnce() -> Result) -> Result { let Some(meter) = &self.meter else { return call() }; let mut meter = meter.lock().unwrap_or_else(|p| p.into_inner()); - meter.start()?; + let me = thread::current().id(); + let started = meter.start().is_ok(); + let from = Instant::now(); let out = call(); - let reading = meter.stop()?; - *self.reading.lock().unwrap_or_else(|p| p.into_inner()) = - reading.map(|(joules_micro, class)| Metered { joules_micro, class: serde_json::Value::Object(class.json_fields().into_iter().map(|(k, v)| (k.to_string(), v)).collect()) }); + let window_us = from.elapsed().as_micros() as u64; + let reading = if started { meter.stop().ok().flatten() } else { None }; + let scope = meter.scope(); + drop(meter); + let mut readings = self.readings.lock().unwrap_or_else(|p| p.into_inner()); + readings.remove(&me); + if let Some((joules_micro, class)) = reading.filter(|(j, c)| check_label(*j, c).is_ok()) { + let class = serde_json::Value::Object(class.json_fields().into_iter().map(|(k, v)| (k.to_string(), v)).collect()); + readings.insert(me, Metered { joules_micro, window_us, class, scope }); + } out } + /// The reading of this thread's last call, once. pub fn last(&self) -> Option { - self.reading.lock().unwrap_or_else(|p| p.into_inner()).clone() + self.readings.lock().unwrap_or_else(|p| p.into_inner()).remove(&thread::current().id()) } } @@ -193,11 +151,66 @@ mod tests { assert!(id_of("gpu", "vulkan", &"x".repeat(200), "").len() <= 64); } + /// Two threads on one meter each get the reading of their own call. #[test] - fn the_table_is_the_reference_s_cut_points() { - let mut t = Vec::new(); - cut_pairs(1, 0.7, 3, &mut t); - assert_eq!(t.len(), 14); - assert_eq!((t[6], t[7]), cuts(0, 1, 1.0 / 0.7)); + fn a_reading_goes_back_to_the_thread_that_made_the_call() { + use crate::quantum_energy::EnergyClass; + struct Ticks(u64); + impl RunMeter for Ticks { + fn start(&mut self) -> Result<(), String> { + Ok(()) + } + fn stop(&mut self) -> Result, String> { + self.0 += 1; + Ok(Some((self.0, EnergyClass::Estimator))) + } + } + let slot = MeterSlot::new(Box::new(Ticks(0))); + let (first, second) = (std::sync::Barrier::new(2), std::sync::Barrier::new(2)); + thread::scope(|s| { + s.spawn(|| { + slot.around(|| Ok(())).unwrap(); + first.wait(); + // The other thread's whole call falls here, before this one reads its own. + second.wait(); + assert_eq!(slot.last().unwrap().joules_micro, 1); + assert!(slot.last().is_none(), "a reading is read once"); + }); + s.spawn(|| { + first.wait(); + slot.around(|| Ok(())).unwrap(); + second.wait(); + assert_eq!(slot.last().unwrap().joules_micro, 2); + }); + }); + } + + /// A figure its class does not hold for gives no reading; a meter that fails + /// leaves the call whole and unmetered. + #[test] + fn a_meter_never_costs_a_call_its_answer() { + use crate::quantum_energy::EnergyClass; + struct Broken(bool); + impl RunMeter for Broken { + fn start(&mut self) -> Result<(), String> { + if self.0 { Err("no counter".into()) } else { Ok(()) } + } + fn stop(&mut self) -> Result, String> { + Ok(Some((0, EnergyClass::Estimator))) + } + } + for broken in [true, false] { + let slot = MeterSlot::new(Box::new(Broken(broken))); + assert_eq!(slot.around(|| Ok(7)), Ok(7)); + assert!(slot.last().is_none()); + } + } + + /// A software driver is not called a GPU. + #[test] + fn the_label_says_what_ran() { + assert!(label("gpu", "Radeon", "Vulkan", "in order").contains("this machine's GPU, Radeon")); + let soft = label("cpu", "llvmpipe", "WebGPU (vulkan)", "in order"); + assert!(soft.contains("a software driver on this machine's CPU, llvmpipe") && !soft.contains("GPU, llvmpipe"), "{soft}"); } } diff --git a/wai/wai-quantum/src/quantum_landscape/job.rs b/wai/wai-quantum/src/quantum_landscape/job.rs index 9cb56db13..ad5c2d8a4 100644 --- a/wai/wai-quantum/src/quantum_landscape/job.rs +++ b/wai/wai-quantum/src/quantum_landscape/job.rs @@ -20,7 +20,7 @@ //! [`call`] answers on the CPU reference, and [`call_certified`] on a member the //! host has certified ([`super::member::certify`]). -use super::member::Certified; +use super::member::{Certified, Parity}; use super::program::{Program, Refusal, decimal, read}; use super::run::{self, DEFAULT_SEED, Job, Plan}; use super::{EXAMPLES, MAX_JOB_BYTES, MAX_OPERATIONS, PROTOCOL}; @@ -103,6 +103,8 @@ pub struct Prepared { temperatures: Vec, /// Whether any sweep of the job is above temperature 0. pub sampling: bool, + /// The kernels the job runs. + pub kernels: Vec<&'static str>, } /// Read a request and check every bound of its job before any of it runs: @@ -160,7 +162,10 @@ pub fn prepare(op: &str, request: &Value, max_bytes: u64) -> Result = runs.iter().map(|(plan, _)| plan.kernel()).collect(); + kernels.sort_unstable(); + kernels.dedup(); + Ok(Some(Prepared { job: Job::new(p), op: op.into(), seed, temperature, seeds, temperatures, sampling, kernels })) } /// Answer a prepared job on a certified member. @@ -193,7 +198,7 @@ pub fn answer(c: &Certified<'_>, prepared: &Prepared) -> Result )); } } - Ok(json!({ "protocol": PROTOCOL, "seeds": seeds, "lines": lines })) + Ok(json!({ "protocol": PROTOCOL, "label": c.member().label(), "member": super::member::describe(c, plan.kernel()), "seeds": seeds, "lines": lines })) } } } @@ -208,26 +213,36 @@ pub fn call_certified(c: &Certified<'_>, op: &str, request: &Value, max_bytes: u } } -/// Answer a request on the first of `members` that takes its workload -/// ([`super::member::Member::takes`]): each that does not is named in the -/// answer's `refusals` with why, and the next is asked. Put the CPU reference -/// last, and every job has a member. +/// Answer a request on the first of `members` that can: one certified for +/// every kernel the job runs (an uncertified kernel is never placed; a member +/// asked for by name goes through [`call_certified`]), that takes the workload +/// ([`super::member::Member::takes`]), and that gives an answer. Each member +/// passed over, for any of those, is named in the answer's `refusals` with why, +/// and the next is asked. Put the CPU reference last, and a job that a member +/// could not finish is still answered. pub fn call_placed(members: &[Certified<'_>], op: &str, request: &Value, max_bytes: u64) -> Result { let Some(p) = prepare(op, request, max_bytes)? else { return Ok(examples()) }; let mut refusals = Vec::new(); for c in members { - match c.member().takes(&p.job.workload, p.sampling) { - Ok(()) => { - let mut a = answer(c, &p)?; - if !refusals.is_empty() { - a["refusals"] = Value::Array(refusals); + let m = c.member(); + let why = if let Some(k) = p.kernels.iter().find(|k| c.parity(k) == Parity::Uncertified) { + format!("its kernel {k} is not certified") + } else if let Err(why) = m.takes(&p.job.workload, p.sampling) { + why + } else { + match answer(c, &p) { + Ok(mut a) => { + if !refusals.is_empty() { + a["refusals"] = Value::Array(refusals); + } + return Ok(a); } - return Ok(a); + Err(r) => r.plain(), } - Err(why) => refusals.push(json!({ "member": c.member().id(), "why": why })), - } + }; + refusals.push(json!({ "member": m.id(), "why": why })); } - Err(Refusal::general(format!("No member offered takes this job: {}.", refusals.iter().map(|r| format!("{}: {}", r["member"].as_str().unwrap_or(""), r["why"].as_str().unwrap_or(""))).collect::>().join("; ")))) + Err(Refusal::general(format!("No member offered answered this job: {}.", refusals.iter().map(|r| format!("{}: {}", r["member"].as_str().unwrap_or(""), r["why"].as_str().unwrap_or(""))).collect::>().join("; ")))) } /// A digest of what a member gives for every worked example at its own @@ -567,7 +582,7 @@ mod tests { assert!(narrow.get("refusals").is_none()); assert_eq!(narrow["replay"], call("run", &json!({ "program": example("talks"), "seed": "3" })).unwrap()["replay"]); let none = call_placed(&placed[..1], "run", &json!({ "program": "vars 2\nfield 0 1099511627776\nminimize\n" }), MAX_JOB_BYTES).unwrap_err(); - assert!(none.message.starts_with("No member offered takes this job"), "{}", none.message); + assert!(none.message.starts_with("No member offered answered this job"), "{}", none.message); } /// A member may decline only the vectors whose fields do not fit 32 bits: @@ -583,6 +598,118 @@ mod tests { assert_eq!((c.parity(KERNEL_SETTLE), c.parity(KERNEL_SAMPLE)), (Parity::Uncertified, Parity::Uncertified), "it declines vectors whose fields fit 32 bits"); } + /// A member whose chains come back where they started: its settle kernel is the + /// reference's, its sampling kernel wrong. It fails at run time once told to. + struct Inert(std::cell::Cell); + + impl super::super::member::Member for Inert { + fn id(&self) -> String { + "gpu:inert".into() + } + fn class(&self) -> &'static str { + "gpu" + } + fn label(&self) -> String { + "A board under test.".into() + } + fn settle(&self, w: &super::super::settle::Workload, start: &[i8], bound: u32) -> Result { + if self.0.get() { Err("the device was lost".into()) } else { Ok(w.settle(start, bound)) } + } + fn chains(&self, w: &super::super::settle::Workload, chains: &[super::super::member::Chain], temperatures: &[f64]) -> Result, String> { + if self.0.get() { + return Err("the device was lost".into()); + } + let draws = w.n() as u64 * temperatures.iter().filter(|&&t| t > 0.0).count() as u64; + Ok(chains + .iter() + .map(|c| { + let term = if w.rests(&c.start) { super::super::settle::Term::FixedPoint } else { super::super::settle::Term::Bound }; + super::super::settle::Outcome { state: c.start.clone(), sweeps: temperatures.len() as u32, term, accumulates: w.nonzeros() * temperatures.len() as u64, draws } + }) + .collect()) + } + } + + /// An uncertified kernel is never placed: a job that runs it goes to the next + /// member, and the answer says why. A job that runs only a certified kernel of + /// the same member stays on it. A member asked for by name is not placed at all. + #[test] + fn an_uncertified_kernel_is_never_placed() { + use super::super::member::{KERNEL_SAMPLE, KERNEL_SETTLE, Parity, certify, named}; + let board = Inert(std::cell::Cell::new(false)); + let c = certify(&board).unwrap(); + assert_eq!((c.parity(KERNEL_SETTLE), c.parity(KERNEL_SAMPLE)), (Parity::BitExact, Parity::Uncertified)); + let placed = [c, Certified::reference()]; + let why = json!([{ "member": "gpu:inert", "why": "its kernel wai.quantum.landscape/sample is not certified" }]); + for program in [example("talks"), "vars 3\nagree 0 1\nagree 1 2\nsolve t0=0 restarts=4\n"] { + for op in ["run", "replay", "sweep", "compare"] { + let a = call_placed(&placed, op, &json!({ "program": program, "seed": "3", "seeds": 2 }), MAX_JOB_BYTES).unwrap(); + assert_eq!(a["member"]["id"], "cpu:reference", "{op}"); + assert_eq!(a["refusals"], why, "{op}"); + } + } + let settle = call_placed(&placed, "run", &json!({ "program": "vars 3\nagree 0 1\nfield 0 1\nminimize\n" }), MAX_JOB_BYTES).unwrap(); + assert_eq!((settle["member"]["id"].as_str(), settle["member"]["parity"].as_str()), (Some("gpu:inert"), Some("bit-exact"))); + let by_name = [named(&board).unwrap()]; + let refused = call_placed(&by_name, "run", &json!({ "program": "vars 3\nagree 0 1\nminimize\n" }), MAX_JOB_BYTES).unwrap_err(); + assert!(refused.message.contains("is not certified"), "{}", refused.message); + assert_eq!(call_certified(&by_name[0], "run", &json!({ "program": "vars 3\nagree 0 1\nminimize\n" }), MAX_JOB_BYTES).unwrap()["member"]["parity"], "uncertified"); + } + + /// A member that takes a job and fails it passes the job on: the next member + /// answers, and every member passed over is named, in order, with why. + #[test] + fn a_member_that_fails_a_job_passes_it_on() { + use super::super::member::certify; + let (lanes32, board) = (Narrow(i64::from(i32::MAX)), Inert(std::cell::Cell::new(false))); + let placed = [certify(&lanes32).unwrap(), certify(&board).unwrap(), Certified::reference()]; + board.0.set(true); + let a = call_placed(&placed, "run", &json!({ "program": "vars 3\nagree 0 1\nfield 0 4294967295\nminimize\n" }), MAX_JOB_BYTES).unwrap(); + assert_eq!(a["member"]["id"], "cpu:reference"); + let refusals = a["refusals"].as_array().unwrap(); + assert_eq!(refusals.iter().map(|r| r["member"].as_str().unwrap()).collect::>(), ["fpga:narrow-2147483647", "gpu:inert"]); + assert!(refusals[1]["why"].as_str().unwrap().contains("the device was lost"), "{a}"); + let none = call_placed(&placed[1..2], "run", &json!({ "program": "vars 3\nagree 0 1\nminimize\n" }), MAX_JOB_BYTES).unwrap_err(); + assert!(none.message.contains("the device was lost"), "{}", none.message); + } + + /// However a member is reached, it is never run on a workload it declines. + #[test] + fn a_member_never_runs_what_it_declines() { + use super::super::member::certify; + let lanes32 = Narrow(i64::from(i32::MAX)); + let c = certify(&lanes32).unwrap(); + for op in ["run", "replay", "sweep", "compare"] { + for program in ["vars 3\nfield 0 4294967295\nagree 0 1\nagree 0 2\nminimize steps=1\n", "vars 3\nfield 0 4294967295\nagree 0 1\nsolve\n"] { + let r = call_certified(&c, op, &json!({ "program": program, "seeds": 2 }), MAX_JOB_BYTES).unwrap_err(); + assert!(r.message.contains("fpga:narrow-2147483647") && r.message.contains("beyond 2147483647"), "{op}: {}", r.message); + } + } + } + + /// Every answer says who ran it and at what parity: a replay names its member + /// and kernel, a sweep each kernel it used, and a comparison the emulator's. + #[test] + fn every_answer_says_who_ran_it_at_what_parity() { + let c = Certified::reference(); + let replay = call_certified(&c, "replay", &json!({ "program": example("talks"), "seeds": 2 }), MAX_JOB_BYTES).unwrap(); + assert_eq!(replay["member"], json!({ "id": "cpu:reference", "class": "cpu", "kernel": "wai.quantum.landscape/sample", "parity": "reference" })); + assert_eq!(replay["label"], super::super::LABEL); + let sweep = call_certified(&c, "sweep", &json!({ "program": example("talks"), "temperatures": [0, 1.5], "seeds": 2 }), MAX_JOB_BYTES).unwrap(); + assert_eq!( + sweep["member"]["kernels"], + json!([{ "kernel": "wai.quantum.landscape/sample", "parity": "reference" }]) + ); + assert_eq!(sweep["rows"][0]["kernel"], "wai.quantum.landscape/sample"); + let minimize = call_certified(&c, "sweep", &json!({ "program": "vars 2\nagree 0 1\nminimize\n", "temperatures": [0, 1.5] }), MAX_JOB_BYTES).unwrap(); + assert_eq!(minimize["member"]["kernels"].as_array().unwrap().len(), 2, "a settle at 0 and a sampling run above it"); + assert_eq!(minimize["rows"][0]["parity"], "reference"); + let compare = call_certified(&c, "compare", &json!({ "program": example("talks"), "seeds": 2 }), MAX_JOB_BYTES).unwrap(); + assert_eq!(compare["member"]["kernels"][0]["parity"], "reference"); + assert_eq!(compare["rows"][0]["parity"], "reference"); + assert_eq!(compare["rows"][2]["parity"], Value::Null, "a classical row ran on no member"); + } + #[test] fn a_seed_keeps_all_64_bits() { let a = call("run", &json!({ "program": example("chain"), "seed": "18446744073709551615" })).unwrap(); diff --git a/wai/wai-quantum/src/quantum_landscape/landscape.wgsl b/wai/wai-quantum/src/quantum_landscape/landscape.wgsl index e1cb557a2..9ff9086f7 100644 --- a/wai/wai-quantum/src/quantum_landscape/landscape.wgsl +++ b/wai/wai-quantum/src/quantum_landscape/landscape.wgsl @@ -1,7 +1,8 @@ // The two kernels of wai.quantum.landscape/1 in WGSL (extensions/quantum-landscape.md §4). // // Every value a cell's field can take fits a 32-bit lane: the host gives a workload only when -// its field bound and cost do (quantum_landscape::webgpu). Fields are whole numbers, so a sum +// its field bound does (quantum_landscape::webgpu), and a cost above every field is held at the +// lane's top, where it holds every cell at 0 as it would at its own size. Fields are whole numbers, so a sum // is the same whatever order a device adds it in, and the settle kernel gives the reference's // bits. The sampling kernel gives them too: its draws are the reference's (SplitMix64 by // counter, in pairs of 32-bit words), its cut points come from the host, and each chain runs @@ -16,12 +17,13 @@ struct Shape { n: u32, cost: i32, chains: u32, - // A settle: the most sweeps. Chains: the field bound B of the cut-point table. + // A settle: the most sweeps. bound: u32, // Chains: the first sweep of this window, and one past its last. k0: u32, k1: u32, - // Where each part of `graph` starts: rows at 0, then columns, values and fields. + // Where each part of `graph` starts: rows at 0, then columns, values, fields and, last, + // each cell's table offset (`offsets`, below). cols: u32, vals: u32, fields: u32, @@ -33,7 +35,7 @@ struct Shape { class_count: u32, class_k: u32, class_kind: u32, - pad: u32, + offsets: u32, } @group(0) @binding(0) var graph: array; @@ -176,6 +178,11 @@ fn seed_of(c: u32) -> vec2 { return vec2(draws[c * 2u], draws[c * 2u + 1u]); } +// Cell i's entry for field h in a sweep's table: its offset, modulo 2³², lands inside the table. +fn entry_of(kind: u32, i: u32, h: i32) -> u32 { + return kind - 1u + bitcast(h) + graph[shape.offsets + i]; +} + @compute @workgroup_size(64) fn chains_in_order(@builtin(global_invocation_id) id: vec3, @builtin(num_workgroups) groups: vec3) { let c = id.x + id.y * groups.x * 64u; @@ -184,7 +191,6 @@ fn chains_in_order(@builtin(global_invocation_id) id: vec3, @builtin(num_wo let base = c * n; let spare = shape.chains * n + base; let seed = seed_of(c); - let b = i32(shape.bound); for (var k = shape.k0; k < shape.k1; k = k + 1u) { let kind = draws[shape.sweeps + k - shape.k0]; if (kind == 0u) { @@ -197,7 +203,7 @@ fn chains_in_order(@builtin(global_invocation_id) id: vec3, @builtin(num_wo } else { for (var i = 0u; i < n; i = i + 1u) { let h = cell_field(base, i); - cells[base + i] = pick(draw(seed, k, i), kind - 1u + u32(h + b)); + cells[base + i] = pick(draw(seed, k, i), entry_of(kind, i, h)); } } } @@ -222,7 +228,7 @@ fn chains_coloured(@builtin(global_invocation_id) id: vec3, @builtin(num_wo let i = draws[shape.class_first + t % shape.class_count]; let base = c * n; let h = cell_field(base, i); - cells[base + i] = pick(draw(seed_of(c), shape.class_k, i), shape.class_kind - 1u + u32(h + i32(shape.bound))); + cells[base + i] = pick(draw(seed_of(c), shape.class_k, i), entry_of(shape.class_kind, i, h)); } @compute @workgroup_size(64) diff --git a/wai/wai-quantum/src/quantum_landscape/member.rs b/wai/wai-quantum/src/quantum_landscape/member.rs index 1e6ae4944..a83ba3690 100644 --- a/wai/wai-quantum/src/quantum_landscape/member.rs +++ b/wai/wai-quantum/src/quantum_landscape/member.rs @@ -17,8 +17,9 @@ //! conformance set and grants what they pass: //! - [`Parity::Reference`]: only this crate's own CPU kernels, through //! [`Certified::reference`]; no member can be granted it. -//! - [`Parity::BitExact`]: its settle kernel gives every `settle` vector -//! bit for bit, or its sampling kernel every `chains` vector. +//! - [`Parity::BitExact`]: its settle kernel gives every `settle` vector it +//! takes bit for bit, or its sampling kernel every `chains` vector it takes, +//! and it takes every one it must ([`conformance::MUST_TAKE_FIELD_BOUND`]). //! - [`Parity::Statistical`]: a sampling kernel with draws of its own whose //! final states, over the battery of `statistical` workloads, follow the //! exact distribution by the test that battery states. @@ -58,13 +59,44 @@ impl Parity { } } -/// A reading of what a run spent, from a meter on the member. `class` is the -/// acquisition class of `extensions/energy-measurement.md` §2 as a member -/// states it (`OnChipCounter`, `CalibratedInstrument`), with its uncertainty. +/// A reading from a meter on the member, over the calls of a run. `class` is +/// the acquisition class of `extensions/energy-measurement.md` §2 as a member +/// states it, with what that class asks. `window_us` is how long the meter's +/// windows ran, added up over the calls. `scope` is what the figure covers where +/// that is more than the run, as the meter says it; `None` is the run alone. #[derive(Clone, Debug, PartialEq)] pub struct Metered { pub joules_micro: u64, + pub window_us: u64, pub class: Value, + pub scope: Option, +} + +impl Metered { + /// Two calls' readings as one, if they are one reading: the same class and + /// scope. The figure is the two together and so is the window, and an + /// uncertainty's window with it, its relative part the larger of the two. + pub fn and(self, other: Metered) -> Option { + if self.scope != other.scope { + return None; + } + let without = |c: &Value| { + let mut c = c.clone(); + if let Some(o) = c.as_object_mut() { + o.remove("energy_uncertainty"); + } + c + }; + if without(&self.class) != without(&other.class) { + return None; + } + let mut class = self.class; + if let (Some(a), Some(b)) = (class.get("energy_uncertainty").cloned(), other.class.get("energy_uncertainty")) { + let of = |v: &Value, k: &str| v[k].as_u64().unwrap_or(0); + class["energy_uncertainty"] = json!({ "relative_ppm": of(&a, "relative_ppm").max(of(b, "relative_ppm")), "window_us": of(&a, "window_us").saturating_add(of(b, "window_us")) }); + } + Some(Metered { joules_micro: self.joules_micro.saturating_add(other.joules_micro), window_us: self.window_us.saturating_add(other.window_us), class, scope: self.scope }) + } } /// One chain to run: where it starts and the seed its draws come from. @@ -90,14 +122,16 @@ pub trait Member { /// ([`super::sample::anneal_with`]): a sweep at 0 is the deterministic one. /// A device may run the chains side by side. fn chains(&self, w: &Workload, chains: &[Chain], temperatures: &[f64]) -> Result, String>; - /// A reading of what its last call spent, if it has a meter. + /// The reading of the last call this thread made, if it has a meter and + /// the meter gave one. fn meter(&self) -> Option { None } /// Whether it takes a workload, said before anything runs: the reason - /// where it does not (a field too wide for its lanes, a table too large), - /// and the host then gives the job to the next member it was offered. - /// `sampling` is whether any sweep is above temperature 0. + /// where it does not, and the host then gives the job to the next member it + /// was offered. `sampling` is whether any sweep is above temperature 0. + /// A member declines only what `extensions/quantum-landscape.md` §5 lets it, + /// and the host never runs it on what it declines. fn takes(&self, _w: &Workload, _sampling: bool) -> Result<(), String> { Ok(()) } @@ -262,6 +296,19 @@ pub(super) fn describe(c: &Certified<'_>, kernel: &str) -> Value { json!({ "id": m.id(), "class": m.class(), "kernel": kernel, "parity": c.parity(kernel).label() }) } +/// What an answer that may run both kernels says of the member that ran it: +/// each kernel it used, in the order first used, at the parity the host found. +pub(super) fn describe_all(c: &Certified<'_>, kernels: &[&str]) -> Value { + let m = c.member(); + let mut used: Vec<&str> = Vec::new(); + for k in kernels { + if !used.contains(k) { + used.push(k); + } + } + json!({ "id": m.id(), "class": m.class(), "kernels": used.iter().map(|k| json!({ "kernel": k, "parity": c.parity(k).label() })).collect::>() }) +} + #[cfg(test)] mod tests { use super::*; diff --git a/wai/wai-quantum/src/quantum_landscape/metal.rs b/wai/wai-quantum/src/quantum_landscape/metal.rs index 3321e8747..cab9a608f 100644 --- a/wai/wai-quantum/src/quantum_landscape/metal.rs +++ b/wai/wai-quantum/src/quantum_landscape/metal.rs @@ -4,11 +4,9 @@ //! The kernels are those of `landscape.wgsl` with native 64-bit words, as the //! CUDA member's: the settle kernel one thread a cell, one dispatch a sweep and //! one to advance; the sampling kernel one thread a chain, its cells in order, -//! the reference's draws and the host's cut points. Fields are 64-bit, so a -//! settle of any workload the extension allows is taken; a sampling run is -//! declined only when one sweep's cut points would be more than -//! [`device::MAX_TABLE_ENTRIES`]. The dispatches of a command buffer run one -//! after another, each seeing what the one before it wrote. +//! the reference's draws and the host's cut points. Fields are 64-bit, so it +//! takes every workload a job can give. The dispatches of a command buffer run +//! one after another, each seeing what the one before it wrote. //! //! The member says nothing of its own parity: the host certifies it. On a //! platform other than macOS there is no device, and [`discover`] says so. @@ -31,7 +29,6 @@ struct Shape { uint k1; uint pad; long cost; - long bound; }; inline int threshold(long h, long c) { @@ -84,7 +81,7 @@ inline ulong splitmix(ulong s, ulong j) { kernel void chains_in_order(device const uint* rows [[buffer(0)]], device const uint* cols [[buffer(1)]], device const int* vals [[buffer(2)]], device const long* fields [[buffer(3)]], constant Shape& sh [[buffer(4)]], device const ulong* seeds [[buffer(7)]], device const uint* sweeps [[buffer(8)]], device const ulong* table [[buffer(9)]], device int* cells [[buffer(10)]], - uint c [[thread_position_in_grid]]) { + device const long* offsets [[buffer(11)]], uint c [[thread_position_in_grid]]) { if (c >= sh.chains) return; uint n = sh.n; device int* s = cells + ulong(c) * n; @@ -99,7 +96,7 @@ kernel void chains_in_order(device const uint* rows [[buffer(0)]], device const for (uint i = 0; i < n; ++i) { long h = field_of(rows, cols, vals, fields, s, i); ulong u = splitmix(seed, ulong(k) * n + i + 1) >> 11; - ulong e = ulong(kind - 1u) + ulong(h + sh.bound); + ulong e = ulong(kind - 1u) + ulong(h) + ulong(offsets[i]); s[i] = u < table[2 * e] ? -1 : (u < table[2 * e + 1] ? 0 : 1); } } @@ -108,6 +105,7 @@ kernel void chains_in_order(device const uint* rows [[buffer(0)]], device const "#; /// The uniform the kernels read, as the shading language lays it out. +#[cfg_attr(not(target_os = "macos"), allow(dead_code))] #[repr(C)] #[derive(Clone, Copy)] struct Shape { @@ -118,7 +116,6 @@ struct Shape { k1: u32, pad: u32, cost: i64, - bound: i64, } /// One Metal device as a landscape member. @@ -137,7 +134,14 @@ impl std::fmt::Debug for MetalLandscape { /// Every Metal device, opened and compiled for, each a member or the reason it is not one. pub fn discover() -> Vec> { - sys::discover() + sys::discover(SOURCE) +} + +/// [`discover`] with kernels other than [`SOURCE`]: how a test shows that a +/// fault in the kernels loses the grade it would otherwise earn on this device. +#[doc(hidden)] +pub fn discover_with(source: &str) -> Vec> { + sys::discover(source) } impl MetalLandscape { @@ -154,8 +158,9 @@ impl MetalLandscape { fn chains_on_device(&self, w: &Workload, chains: &[Chain], temperatures: &[f64]) -> Result, String> { let sampling = temperatures.iter().any(|&t| t > 0.0); + let reach = device::reach(w); if sampling { - device::table_fits(w)?; + device::table_fits(&reach)?; } if chains.is_empty() { return Ok(Vec::new()); @@ -163,7 +168,7 @@ impl MetalLandscape { if chains.iter().any(|c| c.start.len() != w.n()) { return Err("a chain's start is not one cell a variable".into()); } - sys::chains(&self.gpu, w, chains, temperatures, device::window(w, sampling)) + sys::chains(&self.gpu, w, &reach, chains, temperatures, device::window(w, &reach, sampling)) } } @@ -177,11 +182,11 @@ impl Member for MetalLandscape { } fn label(&self) -> String { - device::label(&self.name, "Metal", "its cells in the reference's order", self.meter.is_metered()) + device::label("gpu", &self.name, "Metal", "its cells in the reference's order") } fn takes(&self, w: &Workload, sampling: bool) -> Result<(), String> { - if sampling { device::table_fits(w) } else { Ok(()) } + if sampling { device::table_fits(&device::reach(w)) } else { Ok(()) } } fn settle(&self, w: &Workload, start: &[i8], bound: u32) -> Result { @@ -199,8 +204,8 @@ impl Member for MetalLandscape { #[cfg(target_os = "macos")] mod sys { - use super::{MetalLandscape, SOURCE, Shape}; - use crate::quantum_landscape::device::{self, MeterSlot, SETTLE_BATCH}; + use super::{MetalLandscape, Shape}; + use crate::quantum_landscape::device::{self, MeterSlot, Reach, SETTLE_BATCH}; use crate::quantum_landscape::member::Chain; use crate::quantum_landscape::settle::{Outcome, Workload}; use objc2::rc::{Retained, autoreleasepool}; @@ -228,23 +233,23 @@ mod sys { e.localizedDescription().to_string() } - pub fn discover() -> Vec> { + pub fn discover(source: &str) -> Vec> { autoreleasepool(|_| { let devices = MTLCopyAllDevices(); if devices.count() == 0 { return vec![Err("the platform lists no Metal device".into())]; } - (0..devices.count()).map(|k| open(devices.objectAtIndex(k))).collect() + (0..devices.count()).map(|k| open(devices.objectAtIndex(k), source)).collect() }) } - fn open(device: Retained>) -> Result { + fn open(device: Retained>, source: &str) -> Result { let name = device.name().to_string(); let queue = device.newCommandQueue().ok_or_else(|| format!("{name}: the device gave no command queue"))?; let options = MTLCompileOptions::new(); options.setLanguageVersion(MTLLanguageVersion((3 << 16) as usize)); options.setMathMode(MTLMathMode::Safe); - let library = device.newLibraryWithSource_options_error(&NSString::from_str(SOURCE), Some(&options)).map_err(|e| format!("{name}: the kernels did not compile: {}", ns_error(&e)))?; + let library = device.newLibraryWithSource_options_error(&NSString::from_str(source), Some(&options)).map_err(|e| format!("{name}: the kernels did not compile: {}", ns_error(&e)))?; let pipeline = |entry: &str| -> Result { let f = library.newFunctionWithName(&NSString::from_str(entry)).ok_or_else(|| format!("{name}: no entry point {entry}"))?; device.newComputePipelineStateWithFunction_error(&f).map_err(|e| format!("{name}: {entry}: {}", ns_error(&e))) @@ -316,7 +321,7 @@ mod sys { } let ring = upload(gpu, &init)?; let status = upload(gpu, &[0u32; 4])?; - let s = Shape { n: n as u32, chains: 0, bound_sweeps: bound, k0: 0, k1: 0, pad: 0, cost: w.cost(), bound: 0 }; + let s = Shape { n: n as u32, chains: 0, bound_sweeps: bound, k0: 0, k1: 0, pad: 0, cost: w.cost() }; let (mut done, mut ended) = (0u32, 0u32); while ended == 0 && done < bound { submit(gpu, |e| { @@ -340,10 +345,11 @@ mod sys { }) } - pub fn chains(gpu: &Gpu, w: &Workload, chains: &[Chain], temperatures: &[f64], window: usize) -> Result, String> { + pub fn chains(gpu: &Gpu, w: &Workload, reach: &Reach, chains: &[Chain], temperatures: &[f64], window: usize) -> Result, String> { autoreleasepool(|_| { let (n, count) = (w.n(), chains.len()); let g = graph(gpu, w)?; + let offsets = upload(gpu, &reach.offsets)?; let mut init = vec![0i32; 2 * count * n]; for (c, chain) in chains.iter().enumerate() { for (i, &v) in chain.start.iter().enumerate() { @@ -356,9 +362,9 @@ mod sys { let mut k0 = 0usize; while k0 < temperatures.len() { let k1 = (k0 + window).min(temperatures.len()); - let (sweeps, table) = device::window_sweeps(w, &temperatures[k0..k1]); + let (sweeps, table) = device::window_sweeps(w, reach, &temperatures[k0..k1]); let (sweeps, table) = (upload(gpu, &sweeps)?, upload(gpu, &table)?); - let s = Shape { n: n as u32, chains: count as u32, bound_sweeps: 0, k0: k0 as u32, k1: k1 as u32, pad: 0, cost: w.cost(), bound: w.field_bound() }; + let s = Shape { n: n as u32, chains: count as u32, bound_sweeps: 0, k0: k0 as u32, k1: k1 as u32, pad: 0, cost: w.cost() }; submit(gpu, |e| { for (k, b) in g.iter().enumerate() { bind(e, b, k); @@ -368,6 +374,7 @@ mod sys { bind(e, &sweeps, 8); bind(e, &table, 9); bind(e, &cells, 10); + bind(e, &offsets, 11); dispatch(e, &gpu.chains, count as u64); })?; k0 = k1; @@ -380,12 +387,13 @@ mod sys { #[cfg(not(target_os = "macos"))] mod sys { use super::MetalLandscape; + use crate::quantum_landscape::device::Reach; use crate::quantum_landscape::member::Chain; use crate::quantum_landscape::settle::{Outcome, Workload}; pub enum Gpu {} - pub fn discover() -> Vec> { + pub fn discover(_: &str) -> Vec> { vec![Err("this platform has no Metal; the member runs on macOS".into())] } @@ -393,7 +401,7 @@ mod sys { match *gpu {} } - pub fn chains(gpu: &Gpu, _: &Workload, _: &[Chain], _: &[f64], _: usize) -> Result, String> { + pub fn chains(gpu: &Gpu, _: &Workload, _: &Reach, _: &[Chain], _: &[f64], _: usize) -> Result, String> { match *gpu {} } } diff --git a/wai/wai-quantum/src/quantum_landscape/reach.rs b/wai/wai-quantum/src/quantum_landscape/reach.rs new file mode 100644 index 000000000..202b46e0c --- /dev/null +++ b/wai/wai-quantum/src/quantum_landscape/reach.rs @@ -0,0 +1,207 @@ +//! The cut points a device's sampling kernel reads, and how many sweeps of them +//! one dispatch holds: what any member that takes its table from the host +//! (`extensions/quantum-landscape.md` §4.2) needs, whatever it runs on. +//! +//! A table holds the fields a workload's cells can reach and no others +//! ([`reach`]): at most `2·nonzeros + n` entries a sweep, however wide the +//! fields. A job counts the work of building one for each sampled sweep of +//! each call in its operations ([`super::run::Plan::tables_bound`]), and the +//! memory of holding one in its bytes ([`super::run::job_bytes`]). + +use super::sample::cuts; +use super::settle::Workload; + +/// The most cut-point entries one sweep's table may hold. No job comes near it: +/// a table holds at most `2·nonzeros + n` entries ([`reach`]), and a program of +/// [`super::MAX_PROGRAM_BYTES`] has room for under 1.7 million. Only a workload +/// made by hand can be declined for this. +pub const MAX_TABLE_ENTRIES: u64 = 1 << 22; + +/// The work one chain does in one dispatch, at most: cells × (couplings + 2) × sweeps. +pub const WINDOW_WORK: u64 = 1 << 20; + +/// The cut-point entries one dispatch's tables hold, at most, unless one sweep's alone is more. +pub const WINDOW_ENTRIES: u64 = 1 << 14; + +/// The fields a workload's cells can reach, which are all a sweep's table needs: +/// cell `i` only ever sees `b_i − degree_i` to `b_i + degree_i`. Where those +/// intervals overlap they are one span, so a table is no larger than `2B + 1` +/// entries for a field bound `B`, nor than `2·nonzeros + n`, however wide the +/// fields are. The work of building it is therefore never more than twice the +/// sweep it serves, and a sampling kernel's table cannot outgrow its job. +pub struct Reach { + /// What added to a cell's field, wrapping, gives its entry in a sweep's table. + pub offsets: Vec, + /// The spans, lowest first: first field and last field. + spans: Vec<(i64, i64)>, + /// The entries of one sweep's table. + pub entries: u64, +} + +/// What the cells of a workload can reach. +pub fn reach(w: &Workload) -> Reach { + let n = w.n(); + let zero = vec![0i8; n]; + let mut cells: Vec<(i64, i64, usize)> = (0..n) + .map(|i| { + let (b, d) = (w.field(&zero, i), i64::try_from(w.degree(i)).unwrap_or(i64::MAX)); + (b.saturating_sub(d), b.saturating_add(d), i) + }) + .collect(); + cells.sort_unstable(); + let (mut offsets, mut spans, mut entries) = (vec![0i64; n], Vec::<(i64, i64)>::new(), 0u64); + let mut first = 0u64; + for (lo, hi, i) in cells { + match spans.last_mut() { + Some(last) if lo <= last.1.saturating_add(1) => { + entries += hi.saturating_sub(last.1).max(0) as u64; + last.1 = last.1.max(hi); + } + _ => { + first = entries; + entries += hi.saturating_sub(lo) as u64 + 1; + spans.push((lo, hi)); + } + } + let span = spans[spans.len() - 1]; + offsets[i] = (first as i64).wrapping_sub(span.0); + } + Reach { offsets, spans, entries } +} + +/// One sweep's cut points at temperature `t`, `(a, b)` for every field the +/// cells can reach: the reference's own ([`cuts`]). +pub fn cut_pairs(cost: i64, t: f64, reach: &Reach, out: &mut Vec) { + let beta = 1.0 / t; + for &(lo, hi) in &reach.spans { + for h in lo..=hi { + let (a, b) = cuts(h, cost, beta); + out.extend([a, b]); + } + } +} + +/// Whether a sampling run's table fits: one sweep's cut points within [`MAX_TABLE_ENTRIES`]. +pub fn table_fits(reach: &Reach) -> Result<(), String> { + if reach.entries > MAX_TABLE_ENTRIES { + return Err(format!("its cells reach {} different fields, and a sweep's cut points are at most {MAX_TABLE_ENTRIES} entries here", reach.entries)); + } + Ok(()) +} + +/// How many sweeps one dispatch runs: within [`WINDOW_WORK`] for each chain, +/// and within [`WINDOW_ENTRIES`] of cut points, and at least one. +pub fn window(w: &Workload, reach: &Reach, sampling: bool) -> usize { + let n = w.n() as u64; + let per_sweep = n * (w.nonzeros() / n.max(1) + 2); + let mut window = (WINDOW_WORK / per_sweep.max(1)).max(1); + if sampling { + window = window.min((WINDOW_ENTRIES / reach.entries.max(1)).max(1)); + } + window as usize +} + +/// The sweeps of a window as a device reads them, and their tables: 0 for a +/// sweep at temperature 0, else 1 + where its table starts, in entries. +pub fn window_sweeps(w: &Workload, reach: &Reach, temperatures: &[f64]) -> (Vec, Vec) { + let mut sweeps = Vec::with_capacity(temperatures.len()); + let mut table = Vec::new(); + for &t in temperatures { + if t > 0.0 { + sweeps.push(1 + (table.len() / 2) as u32); + cut_pairs(w.cost(), t, reach, &mut table); + } else { + sweeps.push(0); + } + } + (sweeps, table) +} + +#[cfg(test)] +mod tests { + use super::super::energy::Landscape; + use super::*; + + #[test] + fn the_table_is_the_reference_s_cut_points() { + let mut l = Landscape::new(2); + l.couple(0, 1, 1).set_field(0, 2).set_cost(1); + let r = reach(&l.lower()); + let mut t = Vec::new(); + cut_pairs(1, 0.7, &r, &mut t); + // −1 to 3 are one span: cell 1 reaches −1 to 1, cell 0 reaches 1 to 3. + assert_eq!(r.entries, 5); + assert_eq!(t.len(), 10); + assert_eq!((t[2], t[3]), cuts(0, 1, 1.0 / 0.7)); + assert_eq!(r.offsets, vec![1, 1]); + } + + /// A table holds the fields the cells can reach, whatever the field bound: + /// the job that once asked for four million entries a sweep has six. + #[test] + fn a_table_holds_only_what_the_cells_can_reach() { + let mut l = Landscape::new(2); + l.couple(0, 1, 1).set_field(0, 2_097_149); + let w = l.lower(); + assert_eq!(w.field_bound(), 2_097_150); + let r = reach(&w); + assert_eq!(r.entries, 6); + // Every field a cell can reach has its own entry, and nothing else does. + let mut seen = std::collections::BTreeSet::new(); + for s0 in -1i8..=1 { + for s1 in -1i8..=1 { + for i in 0..2 { + let e = w.field(&[s0, s1], i).wrapping_add(r.offsets[i]); + assert!((0..r.entries as i64).contains(&e)); + seen.insert((e, w.field(&[s0, s1], i))); + } + } + } + assert_eq!(seen.len(), 6); + assert_eq!(seen.iter().map(|p| p.0).collect::>().len(), 6); + } + + /// `2·nonzeros + n` bounds a table, and `2B + 1`: whichever is smaller. + #[test] + fn a_table_is_bounded_by_the_smaller_of_the_two_forms() { + let (n, mut l) = (64, Landscape::new(64)); + for i in 0..n { + l.couple(i, (i + 1) % n, 1); + } + let dense = l.clone(); + for i in 0..n { + l.set_field(i, 7 * i as i64); + } + let (spread, flat) = (l.lower(), dense.lower()); + assert_eq!(reach(&spread).entries, 2 * spread.nonzeros() + n as u64); + assert_eq!(reach(&flat).entries, 2 * flat.field_bound() as u64 + 1); + } + + /// The largest program a job can read cannot ask for more than a table holds. + #[test] + fn no_job_can_outgrow_a_table() { + // The shortest line that couples two cells is ten bytes, `agree 0 1` and its end. + let pairs = (crate::quantum_landscape::MAX_PROGRAM_BYTES / 10) as u64; + assert!(4 * pairs + crate::quantum_landscape::MAX_VARS as u64 <= MAX_TABLE_ENTRIES); + } + + /// A window ends at the work a chain may do in a dispatch, or at the cut + /// points it may hold, whichever comes first, and a sweep at 0 needs no table. + #[test] + fn a_window_ends_at_its_work_or_at_its_table() { + let (n, mut l) = (64, Landscape::new(64)); + for i in 0..n { + l.couple(i, (i + 1) % n, 1); + l.set_field(i, 7 * i as i64); + } + let w = l.lower(); + let r = reach(&w); + assert_eq!(r.entries, 320); + assert_eq!(window(&w, &r, true), (WINDOW_ENTRIES / 320) as usize); + assert_eq!(window(&w, &r, false), (WINDOW_WORK / (64 * 4)) as usize); + let (sweeps, table) = window_sweeps(&w, &r, &[0.5, 0.0, 0.25]); + assert_eq!(sweeps, vec![1, 0, 321]); + assert_eq!(table.len(), 2 * 2 * 320); + } + +} diff --git a/wai/wai-quantum/src/quantum_landscape/run.rs b/wai/wai-quantum/src/quantum_landscape/run.rs index 982e59b6b..488739659 100644 --- a/wai/wai-quantum/src/quantum_landscape/run.rs +++ b/wai/wai-quantum/src/quantum_landscape/run.rs @@ -103,7 +103,7 @@ impl Plan { /// The kernel a plan runs: a settle runs the settle kernel, and a quench /// and a sampling run run chains, a quench's every sweep at 0. - fn kernel(self) -> &'static str { + pub(crate) fn kernel(self) -> &'static str { match self { Plan::Settle { .. } => KERNEL_SETTLE, _ => KERNEL_SAMPLE, @@ -143,15 +143,28 @@ impl Plan { per_sweep.saturating_mul(self.restarts()).saturating_mul(u64::from(self.sweeps())) } - /// The most operations one run of this plan can take: its sweeps, the - /// energy of every start's state on the lowered program, the energy of the - /// answer on the program as declared (and on its weights), and its checks. + /// The most operations a device's cut points for this plan can take: for + /// each call of one batch of starts, one sweep's table for every sweep above + /// 0, at most `4P + n` entries ([`super::reach`]). Nothing for a plan with + /// no sweep above 0. + pub fn tables_bound(self, p: &Program) -> u64 { + let Plan::Anneal { restarts, sweeps, .. } = self else { return 0 }; + let n = p.landscape.len() as u64; + let batch = (CHUNK_CELLS as u64 / n.max(1)).clamp(1, CHUNK_CHAINS as u64); + let calls = u64::from(restarts).div_ceil(batch); + (4 * p.landscape.pairs() + n).saturating_mul(u64::from(sweeps)).saturating_mul(calls) + } + + /// The most operations one run of this plan can take: its sweeps, a + /// device's cut points for them, the energy of every start's state on the + /// lowered program, the energy of the answer on the program as declared + /// (and on its weights), and its checks. pub fn run_bound(self, p: &Program) -> u64 { let n = p.landscape.len() as u64; let energies = (2 * p.landscape.pairs() + n).saturating_mul(self.restarts()); let declared = if p.declared.is_some() { 2 * n * n } else { n * n }; let checks: u64 = p.notes.checks.iter().map(|c| check_items(c) as u64).sum(); - self.operations_bound(p).saturating_add(energies).saturating_add(declared).saturating_add(checks) + self.operations_bound(p).saturating_add(self.tables_bound(p)).saturating_add(energies).saturating_add(declared).saturating_add(checks) } } @@ -198,8 +211,9 @@ pub fn job_operations(p: &Program, runs: &[(Plan, u64)]) -> u64 { /// The most memory a job can hold, known before any of it runs: the program's /// text, its couplings as declared and as rows, its weights where it gives -/// them, one batch of chains, `lines` lines of a replay, and a margin for -/// everything of fixed size. +/// them, one sweep's cut points as a device member holds them (at most +/// `2·nonzeros + n` of 16 bytes: `device::reach`), one batch of chains, `lines` +/// lines of a replay, and a margin for everything of fixed size. pub fn job_bytes(p: &Program, lines: u64) -> u64 { let n = p.landscape.len() as u64; let nonzeros = 2 * p.landscape.pairs(); @@ -209,6 +223,7 @@ pub fn job_bytes(p: &Program, lines: u64) -> u64 { .saturating_add(n * n) .saturating_add(weights) .saturating_add(16 * nonzeros + 64 * n) + .saturating_add(16 * (2 * nonzeros + n)) .saturating_add(batch) .saturating_add(lines.saturating_mul(256)) .saturating_add(1 << 20) @@ -292,11 +307,12 @@ pub(crate) struct Found { metered: Option, } -/// Sum the readings of a run's calls: none if any call gave none. +/// Sum the readings of a run's calls, figures and windows: none if any call +/// gave none, or two calls' readings are not one reading. fn add_reading(total: &mut Option>, reading: Option) { *total = match (total.take(), reading) { (None, r) => Some(r), - (Some(Some(a)), Some(b)) => Some(Some(member::Metered { joules_micro: a.joules_micro + b.joules_micro, class: a.class })), + (Some(Some(a)), Some(b)) => Some(a.and(b)), _ => Some(None), }; } @@ -308,6 +324,8 @@ fn execute(job: &Job, plan: Plan, seed: u64, c: &Certified<'_>) -> Result { + // The host gives a member only what it takes, however it was reached. + m.takes(w, false).map_err(refused)?; let o = m.settle(w, &vec![0i8; n], sweeps).map_err(refused)?; member::check_settle(&o, w, sweeps).map_err(refused)?; let energy = job.program.landscape.energy(&o.state); @@ -335,6 +353,7 @@ fn chains_for_seeds(job: &Job, temperatures: &[f64], restarts: u32, seeds: &[u64 let n = w.n(); let m = c.member(); let refused = |e: String| Refusal::general(format!("The member {} refused or failed: {e}.", m.id())); + m.takes(w, temperatures.iter().any(|&t| t > 0.0)).map_err(refused)?; let batch = (CHUNK_CELLS / n.max(1)).clamp(1, CHUNK_CHAINS); let r = u64::from(restarts); // Every (seed, start) in order: seed after seed, start after start. @@ -385,7 +404,11 @@ fn chains_for_seeds(job: &Job, temperatures: &[f64], restarts: u32, seeds: &[u64 pub(crate) fn execute_seeds(job: &Job, plan: Plan, seeds: &[u64], c: &Certified<'_>) -> Result, Refusal> { match plan { Plan::Settle { .. } => { - let f = execute(job, plan, seeds.first().copied().unwrap_or(DEFAULT_SEED), c)?; + let mut f = execute(job, plan, seeds.first().copied().unwrap_or(DEFAULT_SEED), c)?; + // One call answered every seed, so its reading stands for none of them alone. + if seeds.len() > 1 { + f.metered = None; + } Ok(vec![f; seeds.len()]) } Plan::Quench { restarts, .. } | Plan::Anneal { restarts, .. } => chains_for_seeds(job, &plan.temperatures(), restarts, seeds, c, seeds.len() == 1), @@ -541,12 +564,17 @@ pub(crate) fn answer_of(c: &Certified<'_>, job: &Job, plan: Plan, seed: u64, fou let rests = job.workload.rests(&found.state); let mut joules_statement = price::priced(found.accumulates, plan.mode(), found.sweeps); joules_statement["metered"] = match &found.metered { - Some(r) => json!({ "joules_micro": r.joules_micro, "class": r.class }), + Some(r) => json!({ "joules_micro": r.joules_micro, "window_us": r.window_us, "class": r.class, "scope": r.scope }), None => Value::Null, }; + // The label claims a reading only for a run that has one, and says what it covers. + let label = match &found.metered { + Some(r) => format!("{} Beside the priced figures is a reading of {}.", m.label(), r.scope.as_deref().unwrap_or("this run alone")), + None => m.label(), + }; json!({ "protocol": PROTOCOL, - "label": m.label(), + "label": label, "member": member::describe(c, plan.kernel()), "program_digest": blake3::hash(p.source.as_bytes()).to_hex().to_string(), "title": p.notes.title, @@ -620,6 +648,7 @@ pub fn sweep_on(c: &Certified<'_>, job: &Job, temperatures: &[f64], seeds: u32, let plans = sweep_plans(p, temperatures, seeds)?; let default = p.landscape.default_temperature(); let mut rows = Vec::with_capacity(plans.len()); + let mut kernels = Vec::new(); for (t, plan, runs) in plans { let (mut best, mut sum, mut reached, mut passed, mut accumulates, mut sweeps) = (i64::MAX, 0i128, 0u32, 0u32, 0u64, 0u64); let seeds: Vec = (0..runs).map(|k| first_seed.wrapping_add(u64::from(k))).collect(); @@ -637,10 +666,13 @@ pub fn sweep_on(c: &Certified<'_>, job: &Job, temperatures: &[f64], seeds: u32, } let figures = price::priced(accumulates, plan.mode(), sweeps); let left_out = figures["left_out"]["pj"].as_f64().unwrap_or(0.0); + kernels.push(plan.kernel()); rows.push(json!({ "temperature": t, "is_default": t == default && t > 0.0, "mode": plan.name(), + "kernel": plan.kernel(), + "parity": c.parity(plan.kernel()).label(), "runs": runs, "reached_lowest": exact.truth.as_ref().map(|_| reached), "passed_checks": (!p.notes.checks.is_empty()).then_some(passed), @@ -654,7 +686,7 @@ pub fn sweep_on(c: &Certified<'_>, job: &Job, temperatures: &[f64], seeds: u32, Ok(json!({ "protocol": PROTOCOL, "label": m.label(), - "member": { "id": m.id(), "class": m.class() }, + "member": member::describe_all(c, &kernels), "lowest": exact.truth.as_ref().map(|t| t.lowest), "default_temperature": default, "rows": rows, @@ -714,6 +746,7 @@ pub fn compare_on(c: &Certified<'_>, job: &Job, seeds: u32, first_seed: u64, tem json!({ "solver": t.0, "classical": t.1, + "parity": (!t.1).then(|| c.parity(member::KERNEL_SAMPLE).label()), "runs": seeds, "reached_target": t.2.iter().filter(|&&v| v == target).count(), "passed_checks": (!p.notes.checks.is_empty()).then_some(t.3), @@ -726,7 +759,7 @@ pub fn compare_on(c: &Certified<'_>, job: &Job, seeds: u32, first_seed: u64, tem Ok(json!({ "protocol": PROTOCOL, "label": c.member().label(), - "member": { "id": c.member().id(), "class": c.member().class() }, + "member": member::describe_all(c, &[member::KERNEL_SAMPLE]), "target": target, "target_is_exact": exact_target, "arrangements": exact.truth.as_ref().map(|t| t.arrangements), @@ -810,7 +843,7 @@ mod tests { CpuReference.chains(w, chains, temperatures) } fn meter(&self) -> Option { - self.meter.then(|| member::Metered { joules_micro: 12, class: json!({ "class": "CalibratedInstrument", "calibration_ref": "a test" }) }) + self.meter.then(|| member::Metered { joules_micro: 12, window_us: 5, class: json!({ "class": "CalibratedInstrument", "calibration_ref": "a test" }), scope: None }) } } @@ -825,8 +858,9 @@ mod tests { assert_eq!(board["member"]["id"], "fpga:test-board"); assert_eq!(board["member"]["class"], "fpga"); assert_eq!(board["member"]["parity"], "bit-exact", "its kernels give the reference's bits, and the host found so"); - assert_eq!(board["label"], "A run on a test board."); + assert_eq!(board["label"], "A run on a test board. Beside the priced figures is a reading of this run alone.", "the label claims a reading where the run has one"); assert_eq!(board["joules"]["metered"]["joules_micro"], 12); + assert_eq!(board["joules"]["metered"]["window_us"], 5); assert_eq!(board["joules"]["kind"], "priced", "a reading stands beside the price"); assert_eq!(board["replay"], reference["replay"]); assert_eq!(board["state"], reference["state"]); @@ -834,6 +868,36 @@ mod tests { let unmetered = Board { meter: false }; let cold = run_on(&named(&unmetered).unwrap(), &j, 5, Some(0.0)).unwrap(); assert_eq!(cold["member"]["parity"], "uncertified", "asked for by name, it is not certified"); + assert_eq!(cold["label"], "A run on a test board.", "and with no reading, the label claims none"); + } + + /// A run of several calls adds up their figures and their windows; calls + /// whose readings are not one reading give the run none; and a settle that + /// answered several seeds from one call gives none of them a reading. + #[test] + fn a_run_s_reading_is_its_calls_added_up() { + use crate::quantum_landscape::member::Metered; + let class = |ppm: u64, window: u64| json!({ "energy_provenance": "OnChipCounter", "energy_uncertainty": { "relative_ppm": ppm, "window_us": window }, "energy_counter_filtering": "Unknown" }); + let reading = |uj: u64, window: u64, ppm: u64| Some(Metered { joules_micro: uj, window_us: window, class: class(ppm, window), scope: Some("a rail".into()) }); + let mut total = None; + for (uj, window, ppm) in [(2_000_000, 1_000_000, 50_000), (3_000_000, 1_500_000, 70_000), (1_000_000, 800_000, 60_000)] { + add_reading(&mut total, reading(uj, window, ppm)); + } + let r = total.flatten().unwrap(); + assert_eq!((r.joules_micro, r.window_us), (6_000_000, 3_300_000)); + assert_eq!(r.class["energy_uncertainty"], json!({ "relative_ppm": 70_000, "window_us": 3_300_000 })); + let mut other = None; + add_reading(&mut other, reading(1, 1, 1)); + add_reading(&mut other, Some(Metered { scope: None, ..reading(1, 1, 1).unwrap() })); + assert_eq!(other.flatten(), None, "two scopes are not one reading"); + // A settle replayed over seeds: one call, so no seed's run has a reading of its own. + let j = job("vars 3\nagree 0 1\nfield 0 2\nminimize\n"); + let metered = Board { meter: true }; + let c = certify(&metered).unwrap(); + let plan = Plan::of(&j.program, None).unwrap(); + let found = execute_seeds(&j, plan, &[1, 2, 3], &c).unwrap(); + assert!(found.iter().all(|f| f.metered.is_none())); + assert!(execute_seeds(&j, plan, &[1], &c).unwrap()[0].metered.is_some()); } /// A member cannot pass off a broken answer: the host checks the state and works out its energy. diff --git a/wai/wai-quantum/src/quantum_landscape/webgpu.rs b/wai/wai-quantum/src/quantum_landscape/webgpu.rs index e2bf7f0ef..ec401dd52 100644 --- a/wai/wai-quantum/src/quantum_landscape/webgpu.rs +++ b/wai/wai-quantum/src/quantum_landscape/webgpu.rs @@ -7,8 +7,9 @@ //! whole numbers in 32-bit lanes, so it gives the reference's bits. //! - **The sampling kernel** runs one invocation a chain, its cells in order, //! the reference's draws (SplitMix64 by counter, in pairs of 32-bit words) -//! and the reference's cut points, which the host works out for every field -//! the workload can give and ships as a table a window of sweeps at a time. +//! and the reference's cut points, which the host works out for the fields +//! the cells can reach ([`device::reach`]) and ships as a table a window of +//! sweeps at a time, waiting for each window before it sends the next. //! So it gives the reference's bits too, and many chains run side by side: //! the starts of a run, and the seeds of a sweep, a comparison or a replay. //! - **The coloured kernel** ([`WebGpuLandscape::coloured`]) moves a colour @@ -16,31 +17,42 @@ //! class are coupled, but the order is not the reference's, so its parity //! can only be statistical, and it has to pass the battery to get that. //! -//! A workload whose field bound or cost does not fit a 32-bit lane, or whose -//! cut-point table for one sweep is larger than [`MAX_TABLE_ENTRIES`], is -//! refused before anything runs, with the reason ([`Member::takes`]): the host -//! then runs it on the next member, the CPU reference, and the answer says so. +//! A workload whose field bound does not fit a 32-bit lane is declined before +//! anything runs, with the reason ([`Member::takes`]): the host then runs it on +//! the next member it was offered, and the answer says so. A cost above every +//! field is held at the lane's top, where it does what it does at its own size. //! //! The member says nothing of its own parity: the host certifies it //! ([`super::member::certify`]). It is native only: a page's WebGPU is //! asynchronous, and the member's calls block. -use super::device::{self, MAX_TABLE_ENTRIES, MeterSlot, SETTLE_BATCH}; +use super::device::{self, MeterSlot, Reach, SETTLE_BATCH}; use super::member::{Chain, Member, Metered}; -use super::sample::cuts; use super::settle::{Outcome, Term, Workload}; use crate::quantum_fabric::RunMeter; use crate::quantum_fabric_webgpu::blocking::resolve; use std::borrow::Cow; +use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::{Arc, Mutex}; use std::time::Duration; /// The kernels. pub const SHADER: &str = include_str!("landscape.wgsl"); -/// How long one wait on the device may take. +/// How long one wait on the device may take: one window of sweeps, or one +/// batch of a settle, never a whole run. const POLL_TIMEOUT: Duration = Duration::from_secs(300); +/// The largest storage binding this member uses, whatever an adapter offers: +/// its cell indices are 32-bit words, and at 4 GiB a binding's size no longer +/// fits the word a shader's bounds check is made from. +const MAX_BINDING: u64 = 1 << 31; + +/// The binding limit of a device that offers `offered`. +fn binding_limit(offered: u64) -> u64 { + offered.min(MAX_BINDING) +} + /// How the sampling kernel orders its cells. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Order { @@ -76,6 +88,8 @@ pub struct WebGpuLandscape { backend: &'static str, order: Order, max_binding: u64, + /// How long one wait may take, in nanoseconds. + wait_at_most: AtomicU64, sink: Arc>, meter: MeterSlot, } @@ -108,6 +122,13 @@ fn member_id(class: &str, backend: &str, info: &wgpu::AdapterInfo) -> String { /// Every adapter of the platform's primary GPU interfaces, opened, each a /// member or the reason it is not one. pub fn discover() -> Vec> { + discover_with(SHADER) +} + +/// [`discover`] with kernels other than [`SHADER`]: how a test shows that a +/// fault in the kernels loses the grade it would otherwise earn on this device. +#[doc(hidden)] +pub fn discover_with(source: &str) -> Vec> { if wgpu::Instance::enabled_backend_features().is_empty() { return Vec::new(); } @@ -116,10 +137,10 @@ pub fn discover() -> Vec> { d.flags = wgpu::InstanceFlags::empty(); let instance = wgpu::Instance::new(d); let Ok(adapters) = resolve(instance.enumerate_adapters(wgpu::Backends::PRIMARY)) else { return Vec::new() }; - adapters.into_iter().map(open).collect() + adapters.into_iter().map(|a| open(a, source)).collect() } -fn open(adapter: wgpu::Adapter) -> Result { +fn open(adapter: wgpu::Adapter, source: &str) -> Result { let info = adapter.get_info(); let backend = backend_label(info.backend).ok_or_else(|| format!("{}: backend {:?} is not one this member runs on", info.name, info.backend))?; if !adapter.get_downlevel_capabilities().flags.contains(wgpu::DownlevelFlags::COMPUTE_SHADERS) { @@ -147,7 +168,7 @@ fn open(adapter: wgpu::Adapter) -> Result { } }); let scope = device.push_error_scope(wgpu::ErrorFilter::Validation); - let module = device.create_shader_module(wgpu::ShaderModuleDescriptor { label: Some("wai-quantum landscape"), source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(SHADER)) }); + let module = device.create_shader_module(wgpu::ShaderModuleDescriptor { label: Some("wai-quantum landscape"), source: wgpu::ShaderSource::Wgsl(Cow::Owned(source.to_string())) }); let pipeline = |entry: &str| { device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { label: Some(entry), layout: None, module: &module, entry_point: Some(entry), compilation_options: Default::default(), cache: None }) }; @@ -162,7 +183,7 @@ fn open(adapter: wgpu::Adapter) -> Result { return Err(format!("{}: the kernels did not compile: {e}", info.name)); } let class = if info.device_type == wgpu::DeviceType::Cpu { "cpu" } else { "gpu" }; - let max_binding = device.limits().max_storage_buffer_binding_size; + let max_binding = binding_limit(device.limits().max_storage_buffer_binding_size); Ok(WebGpuLandscape { id: member_id(class, backend, &info), class, @@ -173,6 +194,7 @@ fn open(adapter: wgpu::Adapter) -> Result { pipelines, order: Order::InOrder, max_binding, + wait_at_most: AtomicU64::new(POLL_TIMEOUT.as_nanos() as u64), sink, meter: MeterSlot::default(), }) @@ -182,39 +204,20 @@ fn words(v: &[u32]) -> Vec { v.iter().flat_map(|w| w.to_le_bytes()).collect() } -/// The workload in one buffer of words: rows, columns, values, fields; and -/// where each part starts. -fn graph(w: &Workload) -> (Vec, [u32; 3]) { - let n = w.n(); - let zero = vec![0i8; n]; - let mut rows = Vec::with_capacity(n + 1); - let (mut cols, mut vals) = (Vec::new(), Vec::new()); - rows.push(0u32); - for i in 0..n { - for (j, c) in w.row(i) { - cols.push(j as u32); - vals.push(c as i32 as u32); - } - rows.push(cols.len() as u32); - } - let fields: Vec = (0..n).map(|i| w.field(&zero, i) as i32 as u32).collect(); - let at_cols = rows.len() as u32; - let at_vals = at_cols + cols.len() as u32; - let at_fields = at_vals + vals.len() as u32; - let mut out = rows; - out.extend(cols); - out.extend(vals); - out.extend(fields); - (out, [at_cols, at_vals, at_fields]) -} - -/// The cut points of one sweep, for every field from `−bound` to `bound`. -fn table(cost: i64, t: f64, bound: i64, out: &mut Vec) { - let beta = 1.0 / t; - for h in -bound..=bound { - let (a, b) = cuts(h, cost, beta); - out.extend([a as u32, (a >> 32) as u32, b as u32, (b >> 32) as u32]); - } +/// The workload in one buffer of words: rows, columns, values, fields and each +/// cell's table offset; and where each part after the first starts. +fn graph(w: &Workload, reach: &Reach) -> (Vec, [u32; 4]) { + let r = device::rows(w); + let at_cols = r.rows.len() as u32; + let at_vals = at_cols + r.cols.len() as u32; + let at_fields = at_vals + r.vals.len() as u32; + let at_offsets = at_fields + r.fields.len() as u32; + let mut out = r.rows; + out.extend(r.cols); + out.extend(r.vals.iter().map(|&v| v as u32)); + out.extend(r.fields.iter().map(|&f| f as i32 as u32)); + out.extend(reach.offsets.iter().map(|&o| o as u32)); + (out, [at_cols, at_vals, at_fields, at_offsets]) } /// A greedy colouring of the coupling graph, in order of cell: each cell the @@ -235,10 +238,21 @@ fn colouring(w: &Workload) -> Vec> { classes } +/// The coloured kernel's id: the member's own with `-coloured` after it, its name +/// shortened where it must be so that the id stays within 64 characters and keeps +/// the hex that tells two devices of one name apart. +fn coloured_id(id: &str) -> String { + const SUFFIX: &str = "-coloured"; + let (stem, hex) = id.rsplit_once('.').unwrap_or((id, "")); + let room = 64usize.saturating_sub(SUFFIX.len() + hex.len() + 1); + let stem: String = stem.chars().take(room).collect(); + format!("{}.{hex}{SUFFIX}", stem.trim_end_matches(['-', '.'])) +} + impl WebGpuLandscape { /// The same device running the coloured kernel: a member of its own. pub fn coloured(&self) -> WebGpuLandscape { - let id = format!("{}-coloured", &self.id[..self.id.len().min(55)]); + let id = coloured_id(&self.id); WebGpuLandscape { device: self.device.clone(), queue: self.queue.clone(), @@ -249,6 +263,7 @@ impl WebGpuLandscape { backend: self.backend, order: Order::Coloured, max_binding: self.max_binding, + wait_at_most: AtomicU64::new(self.wait_at_most.load(Ordering::Relaxed)), sink: Arc::clone(&self.sink), meter: MeterSlot::default(), } @@ -260,6 +275,13 @@ impl WebGpuLandscape { self } + /// Another limit on one wait: for a test of what a wait covers, and of + /// what the host does when one runs out. + #[doc(hidden)] + pub fn set_wait(&self, wait_at_most: Duration) { + self.wait_at_most.store(wait_at_most.as_nanos().max(1) as u64, Ordering::Relaxed); + } + /// The adapter's name, as the platform reports it. pub fn adapter(&self) -> &str { &self.adapter @@ -283,7 +305,7 @@ impl WebGpuLandscape { } fn wait(&self, index: wgpu::SubmissionIndex) -> Result<(), String> { - let poll = || self.device.poll(wgpu::PollType::Wait { submission_index: Some(index), timeout: Some(POLL_TIMEOUT) }); + let poll = || self.device.poll(wgpu::PollType::Wait { submission_index: Some(index), timeout: Some(Duration::from_nanos(self.wait_at_most.load(Ordering::Relaxed))) }); match std::panic::catch_unwind(std::panic::AssertUnwindSafe(poll)) { Ok(Ok(_)) => Ok(()), Ok(Err(e)) => Err(format!("the device did not finish: {e}")), @@ -343,37 +365,41 @@ impl WebGpuLandscape { pass.dispatch_workgroups(x, y, 1); } - fn shape(w: &Workload, at: [u32; 3], chains: u32, bound: u32, extra: [u32; 9]) -> Vec { - let mut s = vec![w.n() as u32, w.cost() as i32 as u32, chains, bound]; + fn shape(w: &Workload, at: [u32; 4], chains: u32, bound: u32, extra: [u32; 8]) -> Vec { + // A cost above the lane holds every cell at 0, as the lane's top does: + // every field is at most 2³¹ − 1 here, or the workload was declined. + let cost = w.cost().min(i64::from(i32::MAX)) as i32 as u32; + let mut s = vec![w.n() as u32, cost, chains, bound]; s.extend(extra[..2].iter()); - s.extend(at); + s.extend(&at[..3]); s.extend(extra[2..].iter()); + s.push(at[3]); words(&s) } - /// Whether the workload fits the kernels: every field and the cost in a - /// 32-bit lane, and one sweep's cut points within [`MAX_TABLE_ENTRIES`]. + /// Whether the workload fits the kernels: every field in a 32-bit lane, + /// the workload within a binding, and, for sampling, a table of one sweep's + /// cut points within [`device::MAX_TABLE_ENTRIES`]. Only the first can + /// decline a workload a job gives (`extensions/quantum-landscape.md` §2.0). fn fits(&self, w: &Workload, sampling: bool) -> Result<(), String> { let bound = w.field_bound(); - if bound > i64::from(i32::MAX) || w.cost() > i64::from(i32::MAX) { - return Err(format!("its fields reach {bound}, beyond the 32-bit lanes this member runs in")); - } - if sampling && (2 * bound as u64 + 1) > MAX_TABLE_ENTRIES { - return Err(format!("its fields reach {bound}, and a sweep's cut points for every field would be more than {MAX_TABLE_ENTRIES} entries")); + if bound > i64::from(i32::MAX) { + return Err(format!("its field bound is {bound}, and this member's 32-bit lanes hold at most {}", i32::MAX)); } let n = w.n() as u64; - if 3 * n * 4 > self.max_binding || (w.nonzeros() * 2 + 2 * n + 1) * 4 > self.max_binding { + if 3 * n * 4 > self.max_binding || (w.nonzeros() * 2 + 3 * n + 1) * 4 > self.max_binding { return Err("the workload is larger than a storage binding on this device".into()); } - Ok(()) + if sampling { device::table_fits(&device::reach(w)) } else { Ok(()) } } fn settle_on_device(&self, w: &Workload, start: &[i8], bound: u32) -> Result { + self.settled()?; self.fits(w, false)?; let n = w.n(); - let (g, at) = graph(w); + let (g, at) = graph(w, &device::reach(w)); let graph = self.buffer("graph", &words(&g), wgpu::BufferUsages::STORAGE); - let shape = self.buffer("shape", &Self::shape(w, at, 0, bound, [0; 9]), wgpu::BufferUsages::UNIFORM); + let shape = self.buffer("shape", &Self::shape(w, at, 0, bound, [0; 8]), wgpu::BufferUsages::UNIFORM); let mut ring_words = vec![0u32; 3 * n]; for (i, &s) in start.iter().enumerate() { ring_words[i] = i32::from(s) as u32; @@ -406,6 +432,7 @@ impl WebGpuLandscape { fn chains_on_device(&self, w: &Workload, chains: &[Chain], temperatures: &[f64]) -> Result, String> { let sampling = temperatures.iter().any(|&t| t > 0.0); + self.settled()?; self.fits(w, sampling)?; let n = w.n(); let count = chains.len(); @@ -415,9 +442,9 @@ impl WebGpuLandscape { if (2 * count * n * 4) as u64 > self.max_binding { return Err("these chains are larger than a storage binding on this device".into()); } - let bound = w.field_bound(); - let window = device::window(w, sampling) as u64; - let (g, at) = graph(w); + let reach = device::reach(w); + let window = device::window(w, &reach, sampling) as u64; + let (g, at) = graph(w, &reach); let graph = self.buffer("graph", &words(&g), wgpu::BufferUsages::STORAGE); let mut cell_words = vec![0u32; 2 * count * n]; for (c, chain) in chains.iter().enumerate() { @@ -437,36 +464,31 @@ impl WebGpuLandscape { while k0 < temperatures.len() { let k1 = (k0 + window as usize).min(temperatures.len()); // The window's draws: seeds, the sweeps, the colour classes' cells, the table. + let (sweeps, table) = device::window_sweeps(w, &reach, &temperatures[k0..k1]); let mut d = seeds.clone(); let at_sweeps = d.len() as u32; - d.resize(d.len() + (k1 - k0), 0); + d.extend(&sweeps); let at_members = d.len() as u32; d.extend(&members); let at_table = d.len() as u32; - let mut entries = 0u32; - for k in k0..k1 { - if temperatures[k] > 0.0 { - d[at_sweeps as usize + k - k0] = 1 + entries; - let mut t = Vec::new(); - table(w.cost(), temperatures[k], bound, &mut t); - entries += (t.len() / 4) as u32; - d.extend(t); - } - } + d.extend(table.iter().flat_map(|&v| [v as u32, (v >> 32) as u32])); if (d.len() as u64) * 4 > self.max_binding { return Err("a window's cut points are larger than a storage binding on this device".into()); } let draws = self.buffer("draws", &words(&d), wgpu::BufferUsages::STORAGE); - let b = u32::try_from(bound).unwrap_or(0); - match self.order { + // Each window is waited for before the next is sent, so that one wait + // covers one window, what a failed wait leaves queued is one window, + // and only one window's table is held at a time. + let last = match self.order { Order::InOrder => { - let shape = self.buffer("shape", &Self::shape(w, at, count as u32, b, [k0 as u32, k1 as u32, at_sweeps, at_table, 0, 0, 0, 0, 0]), wgpu::BufferUsages::UNIFORM); + let shape = self.buffer("shape", &Self::shape(w, at, count as u32, 0, [k0 as u32, k1 as u32, at_sweeps, at_table, 0, 0, 0, 0]), wgpu::BufferUsages::UNIFORM); let bind = self.bind(&self.pipelines.in_order, &[(0, &graph), (1, &shape), (4, &cells), (5, &draws)]); let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("chains") }); Self::dispatch(&mut encoder, &self.pipelines.in_order, &bind, count as u64); - self.queue.submit([encoder.finish()]); + Some(self.queue.submit([encoder.finish()])) } Order::Coloured => { + let mut last = None; for k in k0..k1 { let kind = d[at_sweeps as usize + k - k0]; let passes: Vec<(u32, u32)> = if kind == 0 { @@ -483,8 +505,8 @@ impl WebGpuLandscape { .collect() }; for (first, len) in passes { - let extra = [k0 as u32, k1 as u32, at_sweeps, at_table, first, len, k as u32, kind, 0]; - let shape = self.buffer("shape", &Self::shape(w, at, count as u32, b, extra), wgpu::BufferUsages::UNIFORM); + let extra = [k0 as u32, k1 as u32, at_sweeps, at_table, first, len, k as u32, kind]; + let shape = self.buffer("shape", &Self::shape(w, at, count as u32, 0, extra), wgpu::BufferUsages::UNIFORM); let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("coloured") }); let bind = self.bind(&self.pipelines.coloured, &[(0, &graph), (1, &shape), (4, &cells), (5, &draws)]); let invocations = if kind == 0 { (count * n) as u64 } else { count as u64 * u64::from(len) }; @@ -493,10 +515,14 @@ impl WebGpuLandscape { let copy = self.bind(&self.pipelines.copy, &[(1, &shape), (4, &cells)]); Self::dispatch(&mut encoder, &self.pipelines.copy, ©, (count * n) as u64); } - self.queue.submit([encoder.finish()]); + last = Some(self.queue.submit([encoder.finish()])); } } + last } + }; + if let Some(index) = last { + self.wait(index)?; } self.settled()?; k0 = k1; @@ -529,7 +555,7 @@ impl Member for WebGpuLandscape { Order::InOrder => "its cells in the reference's order", Order::Coloured => "a colour class of cells at a time", }; - device::label(&self.adapter, &format!("WebGPU ({})", self.backend), order, self.meter.is_metered()) + device::label(self.class, &self.adapter, &format!("WebGPU ({})", self.backend), order) } fn takes(&self, w: &Workload, sampling: bool) -> Result<(), String> { @@ -566,4 +592,43 @@ mod tests { } } } + + /// The coloured kernel's id is a member id that keeps its device's hex. + #[test] + fn a_coloured_id_keeps_its_device_s_hex() { + let long = device::id_of("gpu", "wgpu-vulkan", "Radeon 8060S Graphics (RADV STRIX_HALO)", "4098/5510/radv/Mesa 25"); + let c = coloured_id(&long); + assert!(c.len() <= 64, "{c}"); + assert!(c.ends_with(&format!(".{}-coloured", long.rsplit_once('.').unwrap().1)), "{c}"); + assert!(c.starts_with("gpu:wgpu-vulkan.radeon-8060s"), "{c}"); + assert_eq!(coloured_id("gpu:wgpu-metal.apple-m3-ultra.3cc6"), "gpu:wgpu-metal.apple-m3-ultra.3cc6-coloured"); + } + + /// A binding this member uses stays under the size a 32-bit bounds check + /// can hold, whatever the adapter offers. + #[test] + fn a_binding_stays_below_four_gibibytes() { + assert_eq!(binding_limit(179_357_827_072), 1 << 31); + assert_eq!(binding_limit(128 << 20), 128 << 20); + } + + /// A table offset is the same word whatever a field's size: every field at + /// the lanes' edge lands inside the table. + #[test] + fn a_cell_s_entry_fits_its_word_at_the_lanes_edge() { + let mut l = super::super::energy::Landscape::new(2); + l.couple(0, 1, 1).set_field(0, i64::from(i32::MAX) - 1).set_field(1, -i64::from(i32::MAX) + 1); + let w = l.lower(); + assert_eq!(w.field_bound(), i64::from(i32::MAX)); + let r = device::reach(&w); + let (g, at) = graph(&w, &r); + for i in 0..2 { + for s in [-1i8, 0, 1] { + let state = if i == 0 { [0, s] } else { [s, 0] }; + let h = w.field(&state, i) as i32; + let e = (h as u32).wrapping_add(g[at[3] as usize + i]); + assert!(u64::from(e) < r.entries, "cell {i} field {h} entry {e}"); + } + } + } } diff --git a/wai/wai-quantum/tests/landscape_conformance.rs b/wai/wai-quantum/tests/landscape_conformance.rs index 96ee12177..738a63b38 100644 --- a/wai/wai-quantum/tests/landscape_conformance.rs +++ b/wai/wai-quantum/tests/landscape_conformance.rs @@ -5,9 +5,10 @@ use serde_json::Value; use wai_quantum::quantum_landscape::conformance::{chi_square, run_battery, vectors}; use wai_quantum::quantum_landscape::energy::Landscape; use wai_quantum::quantum_landscape::job; -use wai_quantum::quantum_landscape::member::{Chain, CpuReference, Member, Parity, certify, KERNEL_SAMPLE}; +use wai_quantum::quantum_landscape::member::{Chain, CpuReference, Member, Parity, certify, KERNEL_SAMPLE, KERNEL_SETTLE}; +use wai_quantum::quantum_landscape::reach; use wai_quantum::quantum_landscape::run::state_text; -use wai_quantum::quantum_landscape::sample::{anneal_with, cuts, schedule}; +use wai_quantum::quantum_landscape::sample::{anneal_with, cuts, out, pick, schedule}; use wai_quantum::quantum_landscape::settle::{Outcome, Term, Workload}; fn text() -> String { @@ -88,12 +89,38 @@ fn the_exponential_schedule_and_cut_points_hold() { } } +/// A chain's sweeps: cooling from `t0`, or runs of sweeps held at a temperature. +fn temperatures_of(c: &Value) -> Vec { + match c["held"].as_array() { + Some(runs) => runs.iter().flat_map(|r| std::iter::repeat_n(bits(&r["temperature_bits"]), r["sweeps"].as_u64().unwrap() as usize)).collect(), + None => schedule(bits(&c["t0_bits"]), c["sweeps"].as_u64().unwrap() as u32), + } +} + +/// The generator's outputs, worked out here in the text's own words. +#[test] +fn the_draw_vectors_hold() { + let f = file(); + let cases = f["draws"].as_array().unwrap(); + assert!(cases.len() >= 20); + for c in cases { + let (s, j): (u64, u64) = (c["seed"].as_str().unwrap().parse().unwrap(), c["j"].as_u64().unwrap()); + let mut x = s.wrapping_add(j.wrapping_mul(0x9E37_79B9_7F4A_7C15)); + x ^= x >> 30; + x = x.wrapping_mul(0xBF58_476D_1CE4_E5B9); + x ^= x >> 27; + x = x.wrapping_mul(0x94D0_49BB_1331_11EB); + x ^= x >> 31; + assert_eq!(x.to_string(), c["out"].as_str().unwrap()); + } +} + #[test] fn the_chain_vectors_hold() { let f = file(); for c in f["chains"].as_array().unwrap() { let l = landscape(&c["landscape"]); - let temperatures = schedule(bits(&c["t0_bits"]), c["sweeps"].as_u64().unwrap() as u32); + let temperatures = temperatures_of(c); for ch in c["chains"].as_array().unwrap() { let chain: u64 = ch["chain"].as_str().unwrap().parse().unwrap(); let o = anneal_with(&l.lower(), &cells(ch["start"].as_str().unwrap()), &temperatures, chain); @@ -127,28 +154,71 @@ fn the_job_vectors_hold() { } /// The battery's distributions are the formula, worked out here with the -/// platform's own exponential, from the file alone. +/// platform's own exponential, from the file alone: a block's law, in a +/// landscape of one block or of several. #[test] fn the_battery_s_distributions_are_the_formula() { let f = file(); for c in f["statistical"]["workloads"].as_array().unwrap() { - let l = landscape(&c["landscape"]); - let n = l.len(); + let whole = landscape(&c["landscape"]); + let n = c["block"].as_u64().unwrap() as usize; + let mut l = Landscape::new(n); + for i in 0..n { + for j in (i + 1)..n { + l.couple(i, j, whole.coupling(i, j)); + } + l.set_field(i, whole.field()[i]); + } + l.set_cost(whole.cost()); + for b in 1..whole.len() / n { + for i in 0..n { + assert_eq!(whole.field()[b * n + i], whole.field()[i], "{}: blocks are copies", c["name"]); + for j in 0..whole.len() { + let same = if j / n == b { whole.coupling(i, j % n) } else { 0 }; + if j != b * n + i { + assert_eq!(whole.coupling(b * n + i, j), same, "{}: blocks are copies, none coupled to another", c["name"]); + } + } + } + } let temperatures: Vec = c["temperatures"].as_array().unwrap().iter().map(|t| t.as_f64().unwrap()).collect(); let t = temperatures.iter().rev().copied().find(|&t| t > 0.0).unwrap(); - let states: Vec> = (0..3usize.pow(n as u32)).map(|mut k| (0..n).map(|_| { let s = (k % 3) as i8 - 1; k /= 3; s }).collect()).collect(); - let e: Vec = states.iter().map(|s| (-(l.energy(s) as f64) / t).exp()).collect(); - let z: f64 = e.iter().sum(); - let mut p: Vec = e.iter().map(|w| w / z).collect(); - if *temperatures.last().unwrap() == 0.0 { - let w = l.lower(); - let mut q = vec![0.0; p.len()]; - for (k, s) in states.iter().enumerate() { - let next = w.sweep(s); - q[next.iter().rev().fold(0, |acc, &v| acc * 3 + (v + 1) as usize)] += p[k]; + let state = |mut k: usize| -> Vec { (0..n).map(|_| { let s = (k % 3) as i8 - 1; k /= 3; s }).collect() }; + let index = |s: &[i8]| s.iter().rev().fold(0, |acc, &v| acc * 3 + (v + 1) as usize); + let states: Vec> = (0..3usize.pow(n as u32)).map(state).collect(); + let w = l.lower(); + let p = if c["law"] == "walked" { + let mut p = vec![1.0 / states.len() as f64; states.len()]; + for &t in &temperatures { + for i in 0..n { + let mut q = vec![0.0; states.len()]; + for (k, s) in states.iter().enumerate() { + let h = w.field(s, i) as f64; + let e = [((-h - w.cost() as f64) / t).exp(), 1.0, ((h - w.cost() as f64) / t).exp()]; + let z: f64 = e.iter().sum(); + for (v, ev) in [-1i8, 0, 1].into_iter().zip(e) { + let mut u = s.clone(); + u[i] = v; + q[index(&u)] += p[k] * ev / z; + } + } + p = q; + } } - p = q; - } + p + } else { + let e: Vec = states.iter().map(|s| (-(l.energy(s) as f64) / t).exp()).collect(); + let z: f64 = e.iter().sum(); + let mut p: Vec = e.iter().map(|w| w / z).collect(); + if *temperatures.last().unwrap() == 0.0 { + let mut q = vec![0.0; p.len()]; + for (k, s) in states.iter().enumerate() { + q[index(&w.sweep(s))] += p[k]; + } + p = q; + } + p + }; for (want, got) in p.iter().zip(c["exact"].as_array().unwrap()) { assert!((want - got.as_f64().unwrap()).abs() < 1e-12, "{}", c["name"]); } @@ -172,6 +242,16 @@ enum Mutant { NoFinalZero, /// A proper colouring of the coupling graph: correct, so it is granted `statistical`. ProperColouring, + /// Cells from the fourth on left where they started. + FrozenFromFour, + /// Every sweep above 0 at the schedule's last temperature above 0. + OnlyTheEndOfTheSchedule, + /// Fields held in 16 bits. + Fields16, + /// Fields held in 32 bits, and nothing declined. + Fields32, + /// Cells taken last to first: correct, so it is granted `statistical`. + LastToFirst, } impl Member for Mutant { @@ -206,6 +286,7 @@ impl Mutant { let colour: Vec = match self { Mutant::AllAtOnce => vec![0; n], Mutant::EvenThenOdd => (0..n).map(|i| i % 2).collect(), + Mutant::LastToFirst => (0..n).rev().collect(), Mutant::ProperColouring => { let mut colour = vec![usize::MAX; n]; for i in 0..n { @@ -222,6 +303,7 @@ impl Mutant { let t = match self { Mutant::IgnoresSchedule => temperatures[0], Mutant::NoFinalZero if k == last => temperatures[last.saturating_sub(1)], + Mutant::OnlyTheEndOfTheSchedule if t > 0.0 => temperatures.iter().rev().copied().find(|&t| t > 0.0).unwrap(), _ => t, }; if t <= 0.0 { @@ -231,9 +313,15 @@ impl Mutant { for g in 0..groups { let old = s.clone(); for i in (0..n).filter(|&i| colour[i] == g) { + if matches!(self, Mutant::FrozenFromFour) && i >= 4 { + continue; + } let mut h = w.field(&old, i); - if matches!(self, Mutant::ClampedTable) { - h = h.clamp(-5, 5); + match self { + Mutant::ClampedTable => h = h.clamp(-5, 5), + Mutant::Fields16 => h = i64::from(h as i16), + Mutant::Fields32 => h = i64::from(h as i32), + _ => {} } let (a, b) = cuts(h, w.cost(), 1.0 / t); let u = (draw() * (1u64 << 53) as f64) as u64; @@ -260,17 +348,243 @@ fn the_battery_tells_a_sampler_from_a_wrong_one() { let reference = run_battery(&CpuReference); show("reference", &reference); assert!(reference.iter().all(|r| r.passed), "the reference's own kernel passes"); - for (name, m) in [("all at once", Mutant::AllAtOnce), ("even, then odd", Mutant::EvenThenOdd), ("ignores schedule", Mutant::IgnoresSchedule), ("clamped table", Mutant::ClampedTable), ("no final zero", Mutant::NoFinalZero)] { + for (name, m) in [("all at once", Mutant::AllAtOnce), ("even, then odd", Mutant::EvenThenOdd), ("ignores schedule", Mutant::IgnoresSchedule), ("clamped table", Mutant::ClampedTable), ("no final zero", Mutant::NoFinalZero), ("frozen from four", Mutant::FrozenFromFour), ("only the end", Mutant::OnlyTheEndOfTheSchedule), ("fields in 16 bits", Mutant::Fields16), ("fields in 32 bits", Mutant::Fields32)] { let results = run_battery(&m); show(name, &results); assert!(results.iter().any(|r| !r.passed), "{name}: a wrong kernel passed every workload"); } assert_eq!(certify(&Mutant::EvenThenOdd).unwrap().parity(KERNEL_SAMPLE), Parity::Uncertified); - let good = Mutant::ProperColouring; - let results = run_battery(&good); - show("proper colouring", &results); - assert!(results.iter().all(|r| r.passed), "a proper colouring passes"); - assert_eq!(certify(&good).unwrap().parity(KERNEL_SAMPLE), Parity::Statistical); + for m in [Mutant::FrozenFromFour, Mutant::OnlyTheEndOfTheSchedule, Mutant::Fields16, Mutant::Fields32] { + assert_eq!(certify(&m).unwrap().parity(KERNEL_SAMPLE), Parity::Uncertified); + } + for (name, good) in [("proper colouring", Mutant::ProperColouring), ("last to first", Mutant::LastToFirst)] { + let results = run_battery(&good); + show(name, &results); + assert!(results.iter().all(|r| r.passed), "{name} passes"); + assert_eq!(certify(&good).unwrap().parity(KERNEL_SAMPLE), Parity::Statistical); + } +} + +// ── kernels that the vectors must refuse ─────────────────────────────────────────────────── + +/// A kernel that gives the reference's bits but for one fault of the kind a +/// port of it to a device can have: what the certificate exists to find. +#[derive(Clone, Copy, Debug, PartialEq)] +enum Fault { + /// A 32 by 32 product that loses the carry out of its middle. + DroppedCarry, + /// A draw compared with a cut point by its high word alone. + HighWordCompare, + /// Fields held in 16 bits. + Fields16, + /// Fields held in 32 bits, and nothing declined. + Fields32, + /// Fields held in 32 bits, and a check of `|b_i|` alone that forgets the couplings. + Fields32CheckedWithoutCouplings, + /// Each chain's cells last to first. + CellsBackwards, + /// The draw counter starting again at each window of this many sweeps. + CounterRestartsEachWindow(usize), + /// The sweep index stopping after this many sweeps. + StopsAfterOneWindow(usize), + /// Only this many chains run. + OnlyTheFirstChains(usize), + /// Only this many cells of a settle move. + OnlyTheFirstCells(usize), + /// A settle that stops looking for its end after this many sweeps. + SettleEndsLate(u32), + /// Declines everything wider than this. + DeclinesAbove(i64), +} + +fn mul32(a: u32, b: u32) -> (u32, u32) { + let (a0, a1, b0, b1) = (a & 0xffff, a >> 16, b & 0xffff, b >> 16); + let (p00, p01, p10, p11) = (a0 * b0, a0 * b1, a1 * b0, a1 * b1); + let mid = p01.wrapping_add(p10); + let lo = p00.wrapping_add(mid << 16); + (lo, p11.wrapping_add(mid >> 16).wrapping_add(u32::from(lo < p00))) +} + +/// The low 64 bits of a product in 32-bit words, with no carry out of the middle. +fn mul64_no_carry(a: u64, b: u64) -> u64 { + let (ax, ay, bx, by) = (a as u32, (a >> 32) as u32, b as u32, (b >> 32) as u32); + let (lo, hi) = mul32(ax, bx); + u64::from(lo) | (u64::from(hi.wrapping_add(ax.wrapping_mul(by)).wrapping_add(ay.wrapping_mul(bx))) << 32) +} + +fn out_no_carry(s: u64, j: u64) -> u64 { + let mut x = s.wrapping_add(j.wrapping_mul(0x9E37_79B9_7F4A_7C15)); + x ^= x >> 30; + x = mul64_no_carry(x, 0xBF58_476D_1CE4_E5B9); + x ^= x >> 27; + x = mul64_no_carry(x, 0x94D0_49BB_1331_11EB); + x ^ (x >> 31) +} + +impl Fault { + fn field(self, w: &Workload, s: &[i8], i: usize) -> i64 { + let h = w.field(s, i); + match self { + Fault::Fields16 => i64::from(h as i16), + Fault::Fields32 | Fault::Fields32CheckedWithoutCouplings => i64::from(h as i32), + _ => h, + } + } + + fn sweep(self, w: &Workload, s: &[i8]) -> Vec { + let c = w.cost(); + (0..w.n()).map(|i| { + let h = self.field(w, s, i); + if h > c { 1 } else if h < -c { -1 } else { 0 } + }).collect() + } + + fn chain(self, w: &Workload, c: &Chain, temperatures: &[f64]) -> Outcome { + let n = w.n(); + let mut s = c.start.clone(); + for (k, &t) in temperatures.iter().enumerate() { + if matches!(self, Fault::StopsAfterOneWindow(at) if k >= at) { + break; + } + if t <= 0.0 { + s = self.sweep(w, &s); + continue; + } + let cells: Vec = if self == Fault::CellsBackwards { (0..n).rev().collect() } else { (0..n).collect() }; + for i in cells { + let counter = if let Fault::CounterRestartsEachWindow(at) = self { k % at } else { k }; + let j = (counter * n + i + 1) as u64; + let u = if self == Fault::DroppedCarry { out_no_carry(c.seed, j) } else { out(c.seed, j) } >> 11; + let (a, b) = cuts(self.field(w, &s, i), w.cost(), 1.0 / t); + s[i] = if self == Fault::HighWordCompare { + if u >> 32 < a >> 32 { -1 } else if u >> 32 < b >> 32 { 0 } else { 1 } + } else { + pick(u, a, b) + }; + } + } + let draws = (n as u64) * temperatures.iter().filter(|&&t| t > 0.0).count() as u64; + let term = if w.rests(&s) { Term::FixedPoint } else { Term::Bound }; + Outcome { state: s, sweeps: temperatures.len() as u32, term, accumulates: w.nonzeros() * temperatures.len() as u64, draws } + } +} + +impl Member for Fault { + fn id(&self) -> String { + "gpu:fault".into() + } + fn class(&self) -> &'static str { + "gpu" + } + fn label(&self) -> String { + "A kernel under test.".into() + } + fn settle(&self, w: &Workload, start: &[i8], bound: u32) -> Result { + if let Fault::SettleEndsLate(at) = *self { + let o = w.settle(start, bound); + return Ok(if o.sweeps > at { Outcome { sweeps: o.sweeps - 1, accumulates: o.accumulates - w.nonzeros(), ..o } } else { o }); + } + let mut s = start.to_vec(); + let mut back: Option> = None; + for k in 1..=bound { + let mut next = self.sweep(w, &s); + if let Fault::OnlyTheFirstCells(at) = *self { + next[at.min(s.len())..].copy_from_slice(&s[at.min(s.len())..]); + } + let term = if next == s { Some(Term::FixedPoint) } else if back.as_ref() == Some(&next) { Some(Term::TwoCycle) } else if k == bound { Some(Term::Bound) } else { None }; + back = Some(std::mem::replace(&mut s, next)); + if let Some(term) = term { + return Ok(Outcome { state: s, sweeps: k, term, accumulates: w.nonzeros() * u64::from(k), draws: 0 }); + } + } + Err("no sweeps".into()) + } + fn chains(&self, w: &Workload, chains: &[Chain], temperatures: &[f64]) -> Result, String> { + Ok(chains.iter().enumerate().map(|(k, c)| match *self { + Fault::OnlyTheFirstChains(at) if k >= at => { + let term = if w.rests(&c.start) { Term::FixedPoint } else { Term::Bound }; + Outcome { state: c.start.clone(), sweeps: temperatures.len() as u32, term, accumulates: w.nonzeros() * temperatures.len() as u64, draws: (w.n() as u64) * temperatures.iter().filter(|&&t| t > 0.0).count() as u64 } + } + f => f.chain(w, c, temperatures), + }).collect()) + } + fn takes(&self, w: &Workload, _sampling: bool) -> Result<(), String> { + let zero = vec![0i8; w.n()]; + match *self { + Fault::DeclinesAbove(most) if w.field_bound() > most => Err(format!("its field bound is {}, above {most}", w.field_bound())), + Fault::Fields32CheckedWithoutCouplings if (0..w.n()).any(|i| w.field(&zero, i).abs() > i64::from(i32::MAX)) => Err("its fields are beyond 32 bits".into()), + _ => Ok(()), + } + } +} + +/// With no fault the stand-in is the reference, so each fault below is the only difference. +#[test] +fn every_fault_loses_the_grade_of_the_kernel_it_is_in() { + let clean = Fault::DeclinesAbove(i64::MAX); + let c = certify(&clean).unwrap(); + assert_eq!((c.parity(KERNEL_SETTLE), c.parity(KERNEL_SAMPLE)), (Parity::BitExact, Parity::BitExact)); + let settle = [Fault::Fields16, Fault::Fields32, Fault::Fields32CheckedWithoutCouplings, Fault::OnlyTheFirstCells(64), Fault::OnlyTheFirstCells(256), Fault::SettleEndsLate(64), Fault::SettleEndsLate(256)]; + let sample = [ + Fault::DroppedCarry, + Fault::HighWordCompare, + Fault::Fields16, + Fault::Fields32, + Fault::Fields32CheckedWithoutCouplings, + Fault::CellsBackwards, + Fault::OnlyTheFirstChains(64), + ]; + for f in settle { + assert_ne!(certify(&f).unwrap().parity(KERNEL_SETTLE), Parity::BitExact, "{f:?}"); + } + // A window's faults, at every window a vector crosses on a device that takes its table from the host. + let mut windows = Vec::new(); + for c in wai_quantum::quantum_landscape::conformance::chain_cases() { + let w = c.landscape.lower(); + let at = reach::window(&w, &reach::reach(&w), true); + if at < c.temperatures.each().len() { + windows.push(at); + } + } + windows.sort_unstable(); + windows.dedup(); + assert!(windows.len() >= 2, "windows by table and by work: {windows:?}"); + let sample = sample.into_iter().chain(windows.iter().flat_map(|&at| [Fault::CounterRestartsEachWindow(at), Fault::StopsAfterOneWindow(at)])); + for f in sample { + assert!(!wai_quantum::quantum_landscape::conformance::chain_vectors_hold(&f), "{f:?}"); + } +} + +/// A member takes every vector at or under the line, and only there may it decline. +#[test] +fn a_member_that_declines_below_the_line_earns_nothing_and_one_at_it_keeps_its_grade() { + for most in [41, 70_000, i64::from(i32::MAX) - 1] { + let narrow = Fault::DeclinesAbove(most); + let c = certify(&narrow).unwrap(); + assert_eq!(c.parity(KERNEL_SETTLE), Parity::Uncertified, "declines above {most}"); + assert_ne!(c.parity(KERNEL_SAMPLE), Parity::BitExact, "declines above {most}"); + } + let lanes32 = Fault::DeclinesAbove(i64::from(i32::MAX)); + let c = certify(&lanes32).unwrap(); + assert_eq!((c.parity(KERNEL_SETTLE), c.parity(KERNEL_SAMPLE)), (Parity::BitExact, Parity::BitExact)); +} + +/// The set reaches what large jobs reach: a settle past a workgroup and past a +/// batch, cells and chains past a workgroup, and hot chains at every width. +#[test] +fn the_set_reaches_what_large_jobs_reach() { + use wai_quantum::quantum_landscape::conformance::{chain_cases, settle_cases}; + let settles = settle_cases(); + let chains = chain_cases(); + assert!(settles.iter().any(|c| c.landscape.len() > 256 && c.landscape.lower().settle(&c.start, c.bound).sweeps > 256)); + let lane = i64::from(i32::MAX); + for bound in [lane, lane + 2] { + assert!(settles.iter().any(|c| c.landscape.lower().field_bound() == bound), "a settle vector at {bound}"); + assert!(chains.iter().any(|c| c.landscape.lower().field_bound() == bound && c.temperatures.each().iter().all(|&t| t > 0.0)), "a hot chain at {bound}"); + assert!(chains.iter().any(|c| c.landscape.lower().field_bound() == bound && c.temperatures.each().iter().all(|&t| t == 0.0)), "a chain at 0 at {bound}"); + } + assert!(chains.iter().any(|c| c.restarts > 64)); + assert!(chains.iter().filter(|c| c.temperatures.each().last().is_some_and(|&t| t > 0.0)).count() >= 8, "chains that stop hot"); } #[test] diff --git a/wai/wai-quantum/tests/landscape_devices.rs b/wai/wai-quantum/tests/landscape_devices.rs index da849fff8..67d617821 100644 --- a/wai/wai-quantum/tests/landscape_devices.rs +++ b/wai/wai-quantum/tests/landscape_devices.rs @@ -1,49 +1,117 @@ //! The landscape kernels on every GPU this machine has, through each backend //! this build holds (`quantum_landscape::{webgpu, cuda, metal}`), held to the //! reference as a host holds a member: certified on the conformance set, then -//! every worked example over many seeds, bit for bit. With `WAI_REQUIRE_GPU` -//! set, a machine where no backend finds a device fails. +//! every worked example over many seeds and jobs past every window and +//! workgroup, bit for bit; and each fault a port of the kernels can have, put +//! into the kernels on this device, shown to cost them their grade. +//! +//! - `WAI_REQUIRE_GPU`: a machine where no backend finds a device fails. +//! - `WAI_LANDSCAPE_EXPECT`: the members this machine must have, as id +//! prefixes separated by commas (`gpu:metal.,gpu:wgpu-metal.`). A prefix no +//! member has fails, and so does a backend that names one and found none, +//! with why: a card off its bus, or kernels that no longer compile. +//! - `WAI_LANDSCAPE_REPORT`: a file each test that certifies a member appends +//! its certificates to, one line each, for a workflow to read. #![cfg(not(target_arch = "wasm32"))] -use serde_json::json; -use std::time::Instant; +use serde_json::{Value, json}; +use std::io::Write; +use std::time::{Duration, Instant}; use wai_quantum::quantum_landscape::MAX_JOB_BYTES; use wai_quantum::quantum_landscape::energy::Landscape; use wai_quantum::quantum_landscape::job; use wai_quantum::quantum_landscape::member::{self, Certified, Chain, CpuReference, KERNEL_SAMPLE, KERNEL_SETTLE, Member, Parity}; -use wai_quantum::quantum_landscape::sample::{chain_seed, schedule, start}; +use wai_quantum::quantum_landscape::sample::{chain_seed, out, schedule, start}; -/// Every member this build's backends find: each with its backend's name. -#[allow(unused_mut)] -fn members() -> Vec> { - let mut out: Vec> = Vec::new(); - let mut take = |backend: &str, found: Vec, String>>| { - for r in found { - match r { - Ok(m) => out.push(m), - Err(why) => eprintln!("{backend}: not a member: {why}"), +type Found = Vec, String>>; + +fn boxed(found: Vec>) -> Found { + found.into_iter().map(|r| r.map(|m| Box::new(m) as Box)).collect() +} + +/// Every backend this build holds, its name, and what it finds with these kernels. +#[allow(unused_mut, unused_variables)] +fn backends(faults: &[(&str, &str, &str)]) -> Vec<(&'static str, Found)> { + let apply = |source: &str, backend: &str| { + let mut s = source.to_string(); + for (b, from, to) in faults { + if *b == backend { + assert!(s.contains(from), "{backend}: the kernel text no longer holds {from:?}"); + s = s.replace(from, to); } } + s }; + let mut out: Vec<(&'static str, Found)> = Vec::new(); #[cfg(feature = "quantum_landscape_webgpu")] - take("webgpu", wai_quantum::quantum_landscape::webgpu::discover().into_iter().map(|r| r.map(|m| Box::new(m) as Box)).collect()); + out.push(("webgpu", boxed(wai_quantum::quantum_landscape::webgpu::discover_with(&apply(wai_quantum::quantum_landscape::webgpu::SHADER, "webgpu"))))); #[cfg(feature = "quantum_landscape_cuda")] - take("cuda", wai_quantum::quantum_landscape::cuda::discover().into_iter().map(|r| r.map(|m| Box::new(m) as Box)).collect()); + out.push(("cuda", boxed(wai_quantum::quantum_landscape::cuda::discover_with(&apply(wai_quantum::quantum_landscape::cuda::SOURCE, "cuda"))))); #[cfg(feature = "quantum_landscape_metal")] - take("metal", wai_quantum::quantum_landscape::metal::discover().into_iter().map(|r| r.map(|m| Box::new(m) as Box)).collect()); - let _ = &mut take; - if out.is_empty() { + out.push(("metal", boxed(wai_quantum::quantum_landscape::metal::discover_with(&apply(wai_quantum::quantum_landscape::metal::SOURCE, "metal"))))); + out +} + +/// The prefixes `WAI_LANDSCAPE_EXPECT` names. +fn expected() -> Vec { + std::env::var("WAI_LANDSCAPE_EXPECT").map(|v| v.split(',').map(|p| p.trim().to_string()).filter(|p| !p.is_empty()).collect()).unwrap_or_default() +} + +/// The backends this build holds. +fn built() -> Vec<&'static str> { + [cfg!(feature = "quantum_landscape_webgpu").then_some("webgpu"), cfg!(feature = "quantum_landscape_cuda").then_some("cuda"), cfg!(feature = "quantum_landscape_metal").then_some("metal")].into_iter().flatten().collect() +} + +/// The backend a member's id names. +fn backend_of(id: &str) -> &'static str { + if id.contains(":wgpu-") { + "webgpu" + } else if id.contains(":cuda.") { + "cuda" + } else { + "metal" + } +} + +/// Every member this build's backends find, held to what the machine expects. +fn members() -> Vec> { + let expect = expected(); + let mut found: Vec> = Vec::new(); + let mut not = Vec::new(); + for (backend, results) in backends(&[]) { + for r in results { + match r { + Ok(m) => found.push(m), + Err(why) => { + eprintln!("{backend}: not a member: {why}"); + not.push(format!("{backend}: {why}")); + } + } + } + } + let missing: Vec<&String> = expect.iter().filter(|p| !found.iter().any(|m| m.id().starts_with(p.as_str()))).collect(); + assert!(missing.is_empty(), "WAI_LANDSCAPE_EXPECT is not met: no member's id starts with {missing:?}; found {:?}; not members: {not:?}", found.iter().map(|m| m.id()).collect::>()); + if found.is_empty() { assert!(std::env::var_os("WAI_REQUIRE_GPU").is_none(), "no device here, and WAI_REQUIRE_GPU is set"); eprintln!("no device here: nothing to hold"); } - out + found +} + +/// A line of the report a workflow reads. +fn report(line: &str) { + eprintln!("{line}"); + if let Some(path) = std::env::var_os("WAI_LANDSCAPE_REPORT") { + let mut f = std::fs::OpenOptions::new().create(true).append(true).open(path).expect("the report file opens"); + writeln!(f, "{line}").expect("the report file takes a line"); + } } #[test] fn every_device_is_certified_bit_exact_for_both_kernels() { for m in members() { let c = member::certify(m.as_ref()).unwrap(); - eprintln!("{}: settle {}, sample {}", m.id(), c.parity(KERNEL_SETTLE).label(), c.parity(KERNEL_SAMPLE).label()); + report(&format!("certified {}: settle {}, sample {}", m.id(), c.parity(KERNEL_SETTLE).label(), c.parity(KERNEL_SAMPLE).label())); assert_eq!(c.parity(KERNEL_SETTLE), Parity::BitExact, "{}", m.id()); assert_eq!(c.parity(KERNEL_SAMPLE), Parity::BitExact, "{}", m.id()); } @@ -77,25 +145,34 @@ fn the_coloured_kernel_earns_statistical_and_no_more() { let coloured = m.coloured(); let c = member::certify(&coloured).unwrap(); for r in wai_quantum::quantum_landscape::conformance::run_battery(&coloured) { - eprintln!("{} {:<22} chi-square {:>8.1} against {:>6.1}: {}", coloured.id(), r.name, r.chi_square, r.critical, if r.passed { "pass" } else { "FAIL" }); + eprintln!("{} {:<28} chi-square {:>8.1} against {:>6.1}: {}", coloured.id(), r.name, r.chi_square, r.critical, if r.passed { "pass" } else { "FAIL" }); } + report(&format!("certified {}: settle {}, sample {}", coloured.id(), c.parity(KERNEL_SETTLE).label(), c.parity(KERNEL_SAMPLE).label())); assert_eq!(c.parity(KERNEL_SETTLE), Parity::BitExact, "{}", coloured.id()); assert_eq!(c.parity(KERNEL_SAMPLE), Parity::Statistical, "{}", coloured.id()); } } -/// A sampling run whose cut points would not fit a table is declined before -/// anything runs, and the job runs on the reference, which the answer names, -/// with each refusal beside it. A narrow one runs on the device. +/// A workload whose field bound is past 32 bits: the WebGPU member declines it +/// and the job runs on the reference, which the answer names with the refusal +/// beside it; the CUDA and Metal members take it and give the reference's bits. +/// A narrow one runs on every device. #[test] fn a_workload_a_device_declines_goes_to_the_reference_and_the_answer_says_so() { for m in members() { let placed = [member::certify(m.as_ref()).unwrap(), Certified::reference()]; - let wide = json!({ "program": "vars 2\nfield 0 1099511627776\nagree 0 1\nsolve\n" }); + let wide = json!({ "program": "vars 2\nfield 0 1099511627776\nagree 0 1\nsolve t0=0 restarts=4\n" }); let a = job::call_placed(&placed, "run", &wide, MAX_JOB_BYTES).unwrap(); - assert_eq!(a["member"]["id"], "cpu:reference"); - assert_eq!(a["refusals"][0]["member"], m.id()); - assert!(!a["refusals"][0]["why"].as_str().unwrap().is_empty(), "{a}"); + let reference = job::call("run", &wide).unwrap(); + assert_eq!((&a["state"], &a["replay"]), (&reference["state"], &reference["replay"]), "{}", m.id()); + if m.id().contains(":wgpu-") { + assert_eq!(a["member"]["id"], "cpu:reference"); + assert_eq!(a["refusals"][0]["member"], m.id()); + assert!(a["refusals"][0]["why"].as_str().unwrap().contains("field bound is 1099511627777"), "{a}"); + } else { + assert_eq!(a["member"]["id"], m.id(), "a 64-bit member takes it"); + assert!(a.get("refusals").is_none()); + } let narrow = json!({ "program": "vars 2\nfield 0 3\nagree 0 1\nsolve\n" }); let a = job::call_placed(&placed, "run", &narrow, MAX_JOB_BYTES).unwrap(); assert_eq!(a["member"]["id"], m.id()); @@ -103,13 +180,14 @@ fn a_workload_a_device_declines_goes_to_the_reference_and_the_answer_says_so() { } } +/// `n` cells with about `degree` couplings each, of both signs. fn sparse(n: usize, degree: usize, seed: u64) -> Landscape { let mut l = Landscape::new(n); let mut k = 0u64; for i in 0..n { for _ in 0..degree / 2 { k += 1; - let j = (wai_quantum::quantum_landscape::sample::out(seed, k) % n as u64) as usize; + let j = (out(seed, k) % n as u64) as usize; if j != i { l.couple(i, j, if k % 3 == 0 { -1 } else { 1 }); } @@ -119,6 +197,191 @@ fn sparse(n: usize, degree: usize, seed: u64) -> Landscape { l } +/// A program's text for a landscape and a run line. +fn program(l: &Landscape, run: &str) -> String { + let n = l.len(); + let mut text = format!("vars {n}\ncost {}\n", l.cost()); + for i in 0..n { + for j in (i + 1)..n { + match l.coupling(i, j) { + 1 => text.push_str(&format!("agree {i} {j}\n")), + -1 => text.push_str(&format!("oppose {i} {j}\n")), + _ => {} + } + } + if l.field()[i] != 0 { + text.push_str(&format!("field {i} {}\n", l.field()[i])); + } + } + text.push_str(run); + text.push('\n'); + text +} + +/// Jobs past every window and workgroup a device splits them into, through the +/// host as a placed job: the reference's bits, from the device. +#[test] +fn jobs_past_every_window_and_workgroup_give_the_reference_s_bits() { + let mut wide = sparse(300, 6, 0x51DE); + for i in 0..300 { + wide.set_field(i, (out(0xF1E1D, i as u64 + 1) % 200_001) as i64 - 100_000); + } + let jobs: Vec<(&str, String)> = vec![ + ("a settle of 4096 cells", program(&sparse(4096, 8, 0x5E77), "minimize steps=200")), + ("a sampling run of many sweeps", program(&sparse(1024, 6, 0x7177), "solve t0=3 restarts=40 steps=600")), + ("wide fields, every window hot", program(&wide, "solve t0=150000 restarts=40 steps=120")), + ("more chains than a batch", program(&sparse(64, 6, 0xC4A1), "solve restarts=5000 steps=20")), + ]; + for m in members() { + let placed = [member::certify(m.as_ref()).unwrap(), Certified::reference()]; + for (name, text) in &jobs { + let req = json!({ "program": text, "seed": "11" }); + let cpu = job::call("run", &req).unwrap(); + let gpu = job::call_placed(&placed, "run", &req, MAX_JOB_BYTES).unwrap(); + assert_eq!(gpu["member"]["id"], m.id(), "{}: {name}", m.id()); + for key in ["state", "energy", "verdict", "work", "replay"] { + assert_eq!(cpu[key], gpu[key], "{}: {name}: {key}", m.id()); + } + } + } +} + +/// The faults a port of the kernels can have, as text put into each backend's +/// kernels: `(name, kernel it is in, [(backend, from, to)])`. +fn faults() -> Vec<(&'static str, &'static str, Vec<(&'static str, &'static str, &'static str)>)> { + vec![ + ("a dropped carry in a 32 by 32 product", KERNEL_SAMPLE, vec![("webgpu", "let mid_carry = select(0u, 0x10000u, mid < p01);", "let mid_carry = 0u;")]), + ("a draw compared with a cut point by its high word", KERNEL_SAMPLE, vec![("webgpu", "return u.y < a.y || (u.y == a.y && u.x < a.x);", "return u.y < a.y;")]), + ( + "fields held in 16 bits", + KERNEL_SETTLE, + vec![("webgpu", " h = h + bitcast(graph[shape.vals + k]) * ring[base + graph[shape.cols + k]];\n }\n return h;", " h = h + bitcast(graph[shape.vals + k]) * ring[base + graph[shape.cols + k]];\n }\n return (h << 16u) >> 16u;")], + ), + ( + "sampled fields held in 16 bits", + KERNEL_SAMPLE, + vec![("webgpu", " h = h + bitcast(graph[shape.vals + k]) * cells[base + graph[shape.cols + k]];\n }\n return h;", " h = h + bitcast(graph[shape.vals + k]) * cells[base + graph[shape.cols + k]];\n }\n return (h << 16u) >> 16u;")], + ), + ( + "fields held in 32 bits", + KERNEL_SETTLE, + vec![ + ("metal", " h += long(vals[k]) * long(s[cols[k]]);\n }\n return h;", " h += long(vals[k]) * long(s[cols[k]]);\n }\n return long(int(h));"), + ("cuda", " h += (i64)vals[k] * (i64)s[cols[k]];\n }\n return h;", " h += (i64)vals[k] * (i64)s[cols[k]];\n }\n return (i64)(int)h;"), + ], + ), + ( + "a draw counter that starts again at each window", + KERNEL_SAMPLE, + vec![ + ("webgpu", "pick(draw(seed, k, i), entry_of(kind, i, h))", "pick(draw(seed, k - shape.k0, i), entry_of(kind, i, h))"), + ("metal", "splitmix(seed, ulong(k) * n + i + 1)", "splitmix(seed, ulong(k - sh.k0) * n + i + 1)"), + ("cuda", "splitmix(seed, (u64)k * n + i + 1)", "splitmix(seed, (u64)(k - k0) * n + i + 1)"), + ], + ), + ( + "cells taken last to first", + KERNEL_SAMPLE, + vec![ + ("webgpu", " for (var i = 0u; i < n; i = i + 1u) {\n let h = cell_field(base, i);", " for (var r = 0u; r < n; r = r + 1u) {\n let i = n - 1u - r;\n let h = cell_field(base, i);"), + ("metal", " for (uint i = 0; i < n; ++i) {\n long h = field_of(", " for (uint r = 0; r < n; ++r) {\n uint i = n - 1u - r;\n long h = field_of("), + ("cuda", " for (unsigned i = 0; i < n; ++i) {\n i64 h = field_of(", " for (unsigned r = 0; r < n; ++r) {\n unsigned i = n - 1u - r;\n i64 h = field_of("), + ], + ), + ( + "a settle whose cells past one workgroup never move", + KERNEL_SETTLE, + vec![ + ("webgpu", "if (i >= shape.n || atomicLoad(&status[1]) != 0u) { return; }", "if (i >= min(shape.n, 64u) || atomicLoad(&status[1]) != 0u) { return; }"), + ("metal", "if (i >= sh.n || atomic_load_explicit(&status[1], memory_order_relaxed) != 0u) return;", "if (i >= min(sh.n, 64u) || atomic_load_explicit(&status[1], memory_order_relaxed) != 0u) return;"), + ("cuda", "if (i >= n || st[1] != 0u) return;", "if (i >= (n < 64u ? n : 64u) || st[1] != 0u) return;"), + ], + ), + ] +} + +/// Each fault, put into the kernels on this device, costs the kernel it is in +/// its grade: the certificate the host finds for it is not bit-exact. Where the +/// fault leaves a correct sampler in another order, the battery may still +/// grant it `statistical`, which is what it is. +#[test] +fn every_fault_in_a_device_s_kernels_loses_its_grade() { + let present: Vec = members().iter().map(|m| m.id()).collect(); + let have = built(); + for (name, kernel, edits) in faults() { + let edits: Vec<(&str, &str, &str)> = edits.into_iter().filter(|(b, _, _)| have.contains(b)).collect(); + if edits.is_empty() { + continue; + } + for (backend, found) in backends(&edits) { + if !edits.iter().any(|(b, _, _)| *b == backend) { + continue; + } + let faulted: Vec> = found.into_iter().filter_map(Result::ok).collect(); + let clean: Vec<&String> = present.iter().filter(|id| backend_of(id) == backend).collect(); + assert_eq!(faulted.len(), clean.len(), "{backend}, {name}: the faulted kernels opened on {} devices, the clean ones on {}", faulted.len(), clean.len()); + for m in faulted { + let c = member::certify(m.as_ref()).unwrap(); + report(&format!("fault {name} in {}: settle {}, sample {}", m.id(), c.parity(KERNEL_SETTLE).label(), c.parity(KERNEL_SAMPLE).label())); + assert!(present.contains(&m.id()), "{} is not a member without the fault", m.id()); + assert_ne!(c.parity(kernel), Parity::BitExact, "{}: {name} kept {kernel} bit-exact", m.id()); + } + } + } +} + +/// A WebGPU member waits once a window, so a run longer than one wait but made +/// of windows shorter than it finishes; and a job a member cannot finish in its +/// wait goes to the next member, the answer naming the one that failed. +/// What one wait may take while the host certifies a member here: the member's own limit. +#[cfg(feature = "quantum_landscape_webgpu")] +const POLL_FOR_CERTIFYING: Duration = Duration::from_secs(300); + +#[cfg(feature = "quantum_landscape_webgpu")] +#[test] +fn a_wait_covers_one_window_and_a_job_a_member_cannot_finish_goes_on() { + use wai_quantum::quantum_landscape::reach; + let l = sparse(2048, 64, 0xA17); + let w = l.lower(); + let per_window = reach::window(&w, &reach::reach(&w), true); + let chains: Vec = (0..4u64).map(|r| Chain { start: start(chain_seed(5, r), w.n()), seed: chain_seed(5, r) }).collect(); + for r in wai_quantum::quantum_landscape::webgpu::discover() { + let Ok(m) = r else { continue }; + let one = vec![1.5; per_window]; + let _ = m.chains(&w, &chains, &one); + let mut took: Vec = (0..3) + .map(|_| { + let t = Instant::now(); + m.chains(&w, &chains, &one).unwrap(); + t.elapsed().as_secs_f64() + }) + .collect(); + took.sort_by(f64::total_cmp); + let wait = Duration::from_secs_f64((10.0 * took[1]).max(0.05)); + m.set_wait(wait); + let long = vec![1.5; 40 * per_window]; + let t = Instant::now(); + let got = m.chains(&w, &chains, &long).unwrap_or_else(|e| panic!("{}: a run of 40 windows with a wait of {wait:?} each: {e}", m.id())); + let ran = t.elapsed(); + report(&format!("waits {}: 40 windows in {:.2} s, one wait at most {:.3} s", m.id(), ran.as_secs_f64(), wait.as_secs_f64())); + assert!(ran > wait, "{}: the run should be longer than one wait to show anything ({ran:?} against {wait:?})", m.id()); + assert_eq!(got, CpuReference.chains(&w, &chains, &long).unwrap(), "{}", m.id()); + m.set_wait(POLL_FOR_CERTIFYING); + let placed = [member::certify(&m).unwrap(), Certified::reference()]; + assert_eq!(placed[0].parity(KERNEL_SAMPLE), Parity::BitExact, "{}", m.id()); + m.set_wait(Duration::from_nanos(1)); + let text = program(&sparse(2048, 64, 0xA17), "solve t0=1.5 restarts=4 steps=400"); + let a = job::call_placed(&placed, "run", &json!({ "program": text, "seed": "5" }), MAX_JOB_BYTES).unwrap(); + assert_eq!(a["member"]["id"], "cpu:reference"); + assert_eq!(a["replay"], job::call("run", &json!({ "program": text, "seed": "5" })).unwrap()["replay"]); + let refusals = a["refusals"].as_array().unwrap(); + assert_eq!(refusals.len(), 1, "{a}"); + assert_eq!(refusals[0]["member"], m.id()); + let why = refusals[0]["why"].as_str().unwrap(); + assert!(why.contains("did not finish"), "{why}"); + } +} + fn time(f: impl FnOnce() -> T) -> (T, f64) { let t = Instant::now(); let v = f(); @@ -126,11 +389,20 @@ fn time(f: impl FnOnce() -> T) -> (T, f64) { } /// Wall-clock seconds on this machine, the CPU reference on one thread against -/// each device, for the same work, with whether the bits are the same. Run -/// with `--ignored --nocapture`. +/// each device, for the same work, with whether the bits are the same, each the +/// middle of three runs. The bits are asserted; the times are printed, with the +/// host's load beside them where the platform gives it. Run with +/// `--ignored --nocapture`. #[test] #[ignore] fn timings() { + let load = std::fs::read_to_string("/proc/loadavg").ok().or_else(|| std::process::Command::new("sysctl").args(["-n", "vm.loadavg"]).output().ok().map(|o| String::from_utf8_lossy(&o.stdout).into_owned())); + println!("host load: {}", load.as_deref().map(str::trim).unwrap_or("not given here")); + let middle = |f: &mut dyn FnMut() -> f64| { + let mut t = [f(), f(), f()]; + t.sort_by(f64::total_cmp); + t[1] + }; let ms = members(); for (n, degree, chains, sweeps) in [(256usize, 6usize, 4096usize, 40u32), (1024, 6, 4096, 40), (4096, 8, 1024, 40)] { let l = sparse(n, degree, 0x7177); @@ -142,33 +414,41 @@ fn timings() { Chain { start: start(c, n), seed: c } }) .collect(); - let (cpu, t_cpu) = time(|| CpuReference.chains(&w, &cs, &temperatures).unwrap()); - println!("chains n={n} degree≈{degree} chains={chains} sweeps={sweeps}: cpu:reference (one thread) {t_cpu:.3} s"); + let cpu = CpuReference.chains(&w, &cs, &temperatures).unwrap(); + let t_cpu = middle(&mut || time(|| CpuReference.chains(&w, &cs, &temperatures).unwrap()).1); + println!("chains n={n} degree≈{degree} chains={chains} sweeps={sweeps}: cpu:reference, one thread, {t_cpu:.3} s"); for m in &ms { let _ = m.chains(&w, &cs[..64.min(cs.len())], &temperatures); - let (gpu, t) = time(|| m.chains(&w, &cs, &temperatures).unwrap()); - println!(" {} {t:.3} s, the same bits: {}", m.id(), gpu == cpu); + let gpu = m.chains(&w, &cs, &temperatures).unwrap(); + assert_eq!(gpu, cpu, "{}", m.id()); + let t = middle(&mut || time(|| m.chains(&w, &cs, &temperatures).unwrap()).1); + println!(" {} {t:.3} s, the same bits", m.id()); } } for n in [1024usize, 4096] { let l = sparse(n, 32, 0x5e77); let w = l.lower(); let from: Vec = start(3, n); - let (cpu, t_cpu) = time(|| CpuReference.settle(&w, &from, 200).unwrap()); - println!("settle n={n} degree≈32: cpu:reference {t_cpu:.3} s ({} sweeps, {:?})", cpu.sweeps, cpu.term); + let cpu = CpuReference.settle(&w, &from, 200).unwrap(); + let t_cpu = middle(&mut || time(|| CpuReference.settle(&w, &from, 200).unwrap()).1); + println!("settle n={n} degree≈32: cpu:reference, one thread, {t_cpu:.3} s ({} sweeps, {:?})", cpu.sweeps, cpu.term); for m in &ms { - let (gpu, t) = time(|| m.settle(&w, &from, 200).unwrap()); - println!(" {} {t:.3} s, the same bits: {}", m.id(), gpu == cpu); + assert_eq!(m.settle(&w, &from, 200).unwrap(), cpu, "{}", m.id()); + let t = middle(&mut || time(|| m.settle(&w, &from, 200).unwrap()).1); + println!(" {} {t:.3} s, the same bits", m.id()); } } let talks = wai_quantum::quantum_landscape::EXAMPLES[0].1; let req = json!({ "program": talks, "seed": "1", "seeds": 2000 }); - let (cpu, t_cpu) = time(|| job::call("replay", &req).unwrap()); - println!("replay of the talks, 2,000 seeds: cpu:reference {t_cpu:.3} s"); + let cpu = job::call("replay", &req).unwrap(); + let t_cpu = middle(&mut || time(|| job::call("replay", &req).unwrap()).1); + println!("replay of the talks, 2,000 seeds: cpu:reference, one thread, {t_cpu:.3} s"); + let columns = |a: &Value| a["lines"].as_str().unwrap().lines().map(|l| l.split(' ').skip(2).collect::>().join(" ")).collect::>(); for m in &ms { let c = member::certify(m.as_ref()).unwrap(); - let (gpu, t) = time(|| job::call_certified(&c, "replay", &req, MAX_JOB_BYTES).unwrap()); - let same = cpu["lines"].as_str().unwrap().lines().zip(gpu["lines"].as_str().unwrap().lines()).all(|(a, b)| a.split(' ').skip(2).eq(b.split(' ').skip(2))); - println!(" {} {t:.3} s, the same states, energies, verdicts, work and replay digests: {same}", m.id()); + let gpu = job::call_certified(&c, "replay", &req, MAX_JOB_BYTES).unwrap(); + assert_eq!(columns(&gpu), columns(&cpu), "{}", m.id()); + let t = middle(&mut || time(|| job::call_certified(&c, "replay", &req, MAX_JOB_BYTES).unwrap()).1); + println!(" {} {t:.3} s, the same states, energies, verdicts, work and replay digests", m.id()); } } diff --git a/wai/wai-rs/Cargo.toml b/wai/wai-rs/Cargo.toml index 238b31683..0509da052 100644 --- a/wai/wai-rs/Cargo.toml +++ b/wai/wai-rs/Cargo.toml @@ -1028,9 +1028,10 @@ quantum_fabric = ["quantum", "quantum_ops", "wai-quantum/quantum_fabric"] quantum_fabric_webgpu = ["quantum_fabric", "wai-quantum/quantum_fabric_webgpu"] # The engine's energy-landscape kernels on a GPU (wai_quantum::quantum_landscape::{webgpu, cuda, # metal}), which sink_meter::RunCounter meters with an on-chip counter (with sink_energy). -quantum_landscape_webgpu = ["quantum_fabric_webgpu", "wai-quantum/quantum_landscape_webgpu"] -quantum_landscape_cuda = ["quantum_fabric", "wai-quantum/quantum_landscape_cuda"] -quantum_landscape_metal = ["quantum_fabric", "wai-quantum/quantum_landscape_metal"] +quantum_landscape = ["quantum_fabric", "wai-quantum/quantum_landscape"] +quantum_landscape_webgpu = ["quantum_landscape", "quantum_fabric_webgpu", "wai-quantum/quantum_landscape_webgpu"] +quantum_landscape_cuda = ["quantum_landscape", "wai-quantum/quantum_landscape_cuda"] +quantum_landscape_metal = ["quantum_landscape", "wai-quantum/quantum_landscape_metal"] # Variational quantum machine learning (extensions/quantum-sim § VML, # `wai.quantum.vml`): the interference-based ML the thermodynamic-computing pivot # left behind — a Quantum Circuit Born Machine (P_θ(x)=|ψ_θ(x)|²) trained by the @@ -2338,7 +2339,7 @@ required-features = ["sink_energy"] [[test]] name = "landscape_meter" path = "tests/landscape_meter.rs" -required-features = ["sink_energy", "quantum_fabric"] +required-features = ["sink_energy", "quantum_landscape"] [[test]] name = "browser_sink" diff --git a/wai/wai-rs/src/sink_meter.rs b/wai/wai-rs/src/sink_meter.rs index 8599fbaf2..a61b58547 100644 --- a/wai/wai-rs/src/sink_meter.rs +++ b/wai/wai-rs/src/sink_meter.rs @@ -59,6 +59,10 @@ pub trait Counter: Send { fn wrap_nj(&self) -> Option { None } + /// The step its register counts in, in nanojoules: a read is a whole number of these. + fn step_nj(&self) -> u64 { + 1 + } } /// One interval of a bracket (sink-energy §4.3): the counter's advance in nanojoules across @@ -540,6 +544,9 @@ impl Counter for Nvml { let mut mj = 0u64; (unsafe { (self.energy)(self.dev, &mut mj) } == 0).then_some(mj.saturating_mul(1_000_000)) } + fn step_nj(&self) -> u64 { + 1_000_000 + } } // --------------------------------------------------------------------------------------- @@ -622,63 +629,192 @@ pub fn survey(children: &Children, every: Duration, intervals: u32) -> (Vec, + counter: Arc>>, + profile: &'static str, + step_nj: u64, + wrap_nj: Option, relative_ppm: u32, minimum: Duration, - start: Option<(u64, Instant)>, + every: Duration, + refresh: Duration, + ceiling_mw: Option, + span: Option, +} + +#[cfg(feature = "quantum_fabric")] +struct RunSpan { + reads: Arc>>, + done: Arc, + sampler: std::thread::JoinHandle<()>, +} + +/// How long a counter takes to refresh: the longest gap between its changes while it is +/// polled every millisecond, up to `within`. `None` when it changed fewer than three times. +#[cfg(feature = "quantum_fabric")] +fn refresh_of(c: &mut dyn Counter, within: Duration) -> Option { + let start = Instant::now(); + let mut last = c.read_nj()?; + let mut changes: Vec = Vec::new(); + while start.elapsed() < within && changes.len() < 6 { + std::thread::sleep(Duration::from_millis(1)); + let now = c.read_nj()?; + if now != last { + changes.push(Instant::now()); + last = now; + } + } + (changes.len() >= 3).then(|| changes.windows(2).map(|w| w[1] - w[0]).max().unwrap_or_default()) } #[cfg(feature = "quantum_fabric")] impl RunCounter { /// A meter over `counter`, whose figures carry the relative uncertainty `relative_ppm`, in - /// parts per million, as the caller evaluates it. Refused for an uncertainty of 0 and for a - /// counter whose profile energy-measurement §9 does not give. + /// parts per million, that the operator declares for the counter at the windows it will be + /// read over, with the meter's own terms beside it. Refused for an uncertainty of 0, for a + /// counter whose profile energy-measurement §9 does not give, for an NVML counter (see + /// [`RunCounter::nvml`]), and for a counter that does not refresh often enough to be shown + /// live in the profile's minimum window. pub fn new(counter: Box, relative_ppm: u32) -> Result { + if counter.profile() == "linux-nvml" { + return Err("an NVML counter is metered through RunCounter::nvml, which holds it to §9.3's plausibility first".into()); + } + RunCounter::open(counter, relative_ppm, None) + } + + /// A meter over NVML, once the counter has shown itself plausible (energy-measurement + /// §9.3): over five one-second intervals its rate stayed within 1.5 times the enforced + /// power limit and twice the mean power reading plus 5 W. Every window after is held to + /// the first of those, and gives no figure above it. + pub fn nvml(mut counter: Nvml, relative_ppm: u32) -> Result { + let (ok, seen) = counter.plausible(5, Duration::from_secs(1)); + if !ok { + let shown: Vec = seen.iter().map(|(rate, power)| format!("{rate} mW against {power} mW")).collect(); + return Err(format!("NVML is implausible here (energy-measurement §9.3), so it gives no figure: its counter's rate against its power reading, each second: {}", shown.join(", "))); + } + let ceiling = counter.limit_mw().map(|l| u64::from(l) * 3 / 2); + RunCounter::open(Box::new(counter), relative_ppm, ceiling) + } + + fn open(mut counter: Box, relative_ppm: u32, ceiling_mw: Option) -> Result { if relative_ppm == 0 { return Err("an on-chip counter's figure declares its uncertainty".into()); } - let (_, minimum_us) = crate::sink_energy::profile(counter.profile()).ok_or_else(|| format!("{}: no profile gives this counter's minimum window", counter.profile()))?; - Ok(RunCounter { counter, relative_ppm, minimum: Duration::from_micros(minimum_us), start: None }) + let profile = counter.profile(); + let (_, minimum_us) = crate::sink_energy::profile(profile).ok_or_else(|| format!("{profile}: no profile gives this counter's minimum window"))?; + let minimum = Duration::from_micros(minimum_us); + let refresh = refresh_of(counter.as_mut(), Duration::from_secs(2)).ok_or_else(|| format!("{profile}: the counter did not change three times in 2 s, so it cannot be shown live"))?; + let every = (2 * refresh).max(minimum / 8); + if every > minimum / 5 { + return Err(format!("{profile}: the counter refreshes every {} ms, too seldom to be shown live in a {} ms window", refresh.as_millis(), minimum.as_millis())); + } + let (step_nj, wrap_nj) = (counter.step_nj().max(1), counter.wrap_nj()); + Ok(RunCounter { counter: Arc::new(Mutex::new(counter)), profile, step_nj, wrap_nj, relative_ppm, minimum, every, refresh, ceiling_mw, span: None }) } - /// The same meter with another minimum window, for tests. + /// The same meter with another minimum window and sampling interval, for tests. #[doc(hidden)] - pub fn with_minimum(mut self, minimum: Duration) -> RunCounter { + pub fn with_timing(mut self, minimum: Duration, every: Duration) -> RunCounter { self.minimum = minimum; + self.every = every; self } + + /// The same meter with a ceiling on a window's rate, in milliwatts, for tests. + #[doc(hidden)] + pub fn with_ceiling_mw(mut self, ceiling_mw: u64) -> RunCounter { + self.ceiling_mw = Some(ceiling_mw); + self + } + + /// How long the counter took to refresh when the meter was made. + pub fn refresh(&self) -> Duration { + self.refresh + } } #[cfg(feature = "quantum_fabric")] impl wai_quantum::quantum_fabric::RunMeter for RunCounter { fn start(&mut self) -> Result<(), String> { - let c = self.counter.read_nj().ok_or_else(|| format!("{}: the counter did not read", self.counter.profile()))?; - self.start = Some((c, Instant::now())); + let c = self.counter.lock().unwrap_or_else(|p| p.into_inner()).read_nj().ok_or_else(|| format!("{}: the counter did not read", self.profile))?; + let reads = Arc::new(Mutex::new(vec![(c, Instant::now())])); + let done = Arc::new(std::sync::atomic::AtomicBool::new(false)); + let (r, d, counter, every) = (Arc::clone(&reads), Arc::clone(&done), Arc::clone(&self.counter), self.every); + let sampler = std::thread::spawn(move || { + while !d.load(std::sync::atomic::Ordering::Acquire) { + std::thread::park_timeout(every); + if d.load(std::sync::atomic::Ordering::Acquire) { + break; + } + let read = counter.lock().unwrap_or_else(|p| p.into_inner()).read_nj(); + match read { + Some(v) => r.lock().unwrap_or_else(|p| p.into_inner()).push((v, Instant::now())), + // A read that fails ends the samples, and the window cannot be shown live. + None => break, + } + } + }); + self.span = Some(RunSpan { reads, done, sampler }); Ok(()) } fn stop(&mut self) -> Result, String> { use wai_quantum::quantum_energy::{CounterFiltering, EnergyClass, Uncertainty}; - let Some((c0, t0)) = self.start.take() else { return Err("the meter was stopped without being started".into()) }; - let span = t0.elapsed(); - let Some(c1) = self.counter.read_nj() else { return Ok(None) }; - let nj = advance(c0, c1, self.counter.wrap_nj()); - if span < self.minimum || nj < 1000 { + let Some(span) = self.span.take() else { return Err("the meter was stopped without being started".into()) }; + span.done.store(true, std::sync::atomic::Ordering::Release); + span.sampler.thread().unpark(); + let _ = span.sampler.join(); + let last = self.counter.lock().unwrap_or_else(|p| p.into_inner()).read_nj(); + let Some(last) = last else { return Ok(None) }; + let mut reads = std::mem::take(&mut *span.reads.lock().unwrap_or_else(|p| p.into_inner())); + reads.push((last, Instant::now())); + let window = reads[reads.len() - 1].1 - reads[0].1; + let window_us = window.as_micros() as u64; + let deltas: Vec = reads.windows(2).map(|w| advance(w[0].0, w[1].0, self.wrap_nj)).collect(); + if window < self.minimum || !crate::energy_report::Liveness::from_advances(deltas.iter().map(|&d| d > 0)).is_live_over(window_us) { return Ok(None); } - let uncertainty = Uncertainty { relative_ppm: self.relative_ppm, window_us: span.as_micros() as u64 }; + let nj: u64 = deltas.iter().sum(); + if nj < 1000 || self.ceiling_mw.is_some_and(|c| nj / window_us.max(1) > c) { + return Ok(None); + } + // The operator's term, the register's step at each end, and the counter's refresh at + // each edge of the window: each an error the reads cannot see, combined as independent. + let e_uj = nj as f64 / 1000.0; + let step = self.step_nj as f64 / 1000.0 / 6f64.sqrt(); + let edge = e_uj * self.refresh.as_secs_f64() / window.as_secs_f64() / 6f64.sqrt(); + let rel = ((f64::from(self.relative_ppm) / 1e6).powi(2) + (step / e_uj).powi(2) + (edge / e_uj).powi(2)).sqrt(); + let relative_ppm = u32::try_from((rel * 1e6).ceil() as u64).unwrap_or(u32::MAX); + let uncertainty = Uncertainty { relative_ppm, window_us }; Ok(Some((nj / 1000, EnergyClass::OnChipCounter { uncertainty, filtering: CounterFiltering::Unknown }))) } + + fn scope(&self) -> Option { + Some(match self.profile { + "macos-gpu" => "the graphics processor's whole rail as IOReport's GPU Energy channel reports it, over the windows of this run's calls: this run's work, the rail's idle draw and any other work on the graphics processor in those windows, with no share attributed to this run".into(), + "linux-nvml" => "the whole board as NVML reports it, over the windows of this run's calls: this run's work, the board's idle draw and any other work on the board in those windows, with no share attributed to this run".into(), + p => format!("what the {p} profile covers (energy-measurement §9), over the windows of this run's calls, with no share attributed to this run"), + }) + } } #[cfg(test)] @@ -718,39 +854,72 @@ mod tests { assert_eq!(liveness(&mut Scripted(failing, 0), 10, d), CounterStatus::Frozen, "a read that fails is no reading"); } - /// A counter as a run's meter: the advance in microjoules, labelled as an on-chip reading - /// with the declared uncertainty and the window; none for a window below the profile's - /// minimum; refused with no uncertainty or no profile. + /// A counter as a run's meter: a figure only from a window in which the counter was live, + /// at least the profile's minimum and under any ceiling; its uncertainty never below what + /// it declares, with the meter's own terms; and what it covers, said. Two reads alone, a + /// counter that moves in one lump, one that never changes, and an NVML counter offered as + /// any other, give nothing. #[cfg(feature = "quantum_fabric")] #[test] - fn a_counter_meters_a_run_and_says_what_it_read() { + fn a_counter_meters_a_run_only_where_it_was_live_and_says_what_it_covers() { use wai_quantum::quantum_energy::{CounterFiltering, EnergyClass}; use wai_quantum::quantum_fabric::RunMeter; - struct Steps(u64, &'static str); + /// A counter that rises `.1` nJ a read once it has been read `.3` times; `.4` counts reads. + struct Steps(u64, u64, &'static str, u64, u64); impl Counter for Steps { fn profile(&self) -> &'static str { - self.1 + self.2 } fn detail(&self) -> String { "steps".into() } fn read_nj(&mut self) -> Option { - self.0 += 2_500_000; + self.4 += 1; + if self.4 >= self.3 { + self.0 += self.1; + } Some(self.0) } + fn step_nj(&self) -> u64 { + 1000 + } } - assert!(RunCounter::new(Box::new(Steps(0, "macos-gpu")), 0).is_err()); - assert!(RunCounter::new(Box::new(Steps(0, "no-such-profile")), 10_000).is_err()); - let mut m = RunCounter::new(Box::new(Steps(0, "macos-gpu")), 50_000).unwrap().with_minimum(Duration::ZERO); + let rising = |profile| Box::new(Steps(0, 2_500_000, profile, 0, 0)) as Box; + assert!(RunCounter::new(rising("macos-gpu"), 0).is_err()); + assert!(RunCounter::new(rising("no-such-profile"), 10_000).is_err()); + assert!(RunCounter::new(rising("linux-nvml"), 10_000).err().unwrap().contains("RunCounter::nvml")); + assert!(RunCounter::new(Box::new(Steps(7, 0, "macos-gpu", 0, 0)), 50_000).err().unwrap().contains("cannot be shown live")); + let fast = |m: RunCounter| m.with_timing(Duration::ZERO, Duration::from_millis(20)); + let mut m = fast(RunCounter::new(rising("macos-gpu"), 50_000).unwrap()); assert!(m.stop().is_err(), "stopped before it started"); + // Two reads, start and stop: one interval, never live. m.start().unwrap(); + assert_eq!(m.stop().unwrap(), None); + // A live window of about 400 ms. + m.start().unwrap(); + std::thread::sleep(Duration::from_millis(400)); let (uj, class) = m.stop().unwrap().expect("a reading"); - assert_eq!(uj, 2_500); + assert!(uj >= 2_500 * 10, "{uj}"); let EnergyClass::OnChipCounter { uncertainty, filtering } = class else { panic!("{class:?}") }; - assert_eq!((uncertainty.relative_ppm, filtering), (50_000, CounterFiltering::Unknown)); - let mut short = RunCounter::new(Box::new(Steps(0, "linux-nvml")), 50_000).unwrap(); + assert_eq!(filtering, CounterFiltering::Unknown); + assert!(uncertainty.relative_ppm > 50_000, "the meter's own terms are added: {}", uncertainty.relative_ppm); + assert!(uncertainty.window_us >= 400_000, "{}", uncertainty.window_us); + assert!(m.scope().unwrap().contains("idle draw and any other work on the graphics processor")); + // Above the ceiling: no figure. + let mut hot = fast(RunCounter::new(rising("macos-gpu"), 50_000).unwrap()).with_ceiling_mw(1); + hot.start().unwrap(); + std::thread::sleep(Duration::from_millis(400)); + assert_eq!(hot.stop().unwrap(), None, "a window above the ceiling gives nothing"); + // Still until late in the window, then one lump: not live. + let mut lump = fast(RunCounter::new(rising("macos-gpu"), 50_000).unwrap()); + *lump.counter.lock().unwrap() = Box::new(Steps(0, 2_500_000_000, "macos-gpu", 18, 0)); + lump.start().unwrap(); + std::thread::sleep(Duration::from_millis(400)); + assert_eq!(lump.stop().unwrap(), None, "one lump is one advance"); + // A window under the profile's minimum gives none. + let mut short = RunCounter::new(rising("macos-gpu"), 50_000).unwrap(); short.start().unwrap(); - assert_eq!(short.stop().unwrap(), None, "a window under the profile's 5 s gives no figure"); + assert_eq!(short.stop().unwrap(), None, "a window under the profile's 1 s gives no figure"); } /// An advance across a counter's wrap, and none where a counter with no wrap goes back. diff --git a/wai/wai-rs/tests/landscape_meter.rs b/wai/wai-rs/tests/landscape_meter.rs index 19065e006..cc554f923 100644 --- a/wai/wai-rs/tests/landscape_meter.rs +++ b/wai/wai-rs/tests/landscape_meter.rs @@ -2,9 +2,10 @@ //! counter (`sink_meter::RunCounter`): the GPU rail through IOReport on macOS, the board //! through NVML on Linux. Hardware only, run by hand with `--ignored --nocapture`. //! -//! The uncertainty these tests declare, 100,000 ppm, is the caller's coarse choice for a -//! hand run, not an evaluation under energy-measurement §2.3. The figure is the whole rail -//! or board over the call's window, idle draw included. +//! The operator's term these tests declare for the counter, 100,000 ppm, is a coarse choice +//! for a hand run, not an evaluation of the counter's own error; the meter adds its own terms +//! beside it, and nothing here seals a figure. The figure is the whole rail or board over the +//! call's window, idle draw and any other work on the device included, as the answer says. #![allow(dead_code, unused_imports)] use std::time::Instant; @@ -45,11 +46,12 @@ fn metered(m: &dyn Member, w: &Workload, cs: &[Chain], temperatures: &[f64]) { let reading = m.meter(); println!("{}: {} chains, {} cells, {} sweeps in {wall:.2} s wall-clock", m.id(), cs.len(), w.n(), temperatures.len()); match &reading { - Some(r) => println!(" read: {} µJ, class {}", r.joules_micro, r.class), + Some(r) => println!(" read: {} µJ over {} µs, class {}\n covering {}", r.joules_micro, r.window_us, r.class, r.scope.as_deref().unwrap_or("this run alone")), None => println!(" no reading"), } - let r = reading.expect("a call longer than the profile's minimum window gives a reading"); + let r = reading.expect("a call longer than the profile's minimum window, on a live counter, gives a reading"); assert_eq!(r.class["energy_provenance"], "OnChipCounter", "{}", r.class); + assert!(r.scope.as_deref().is_some_and(|s| s.contains("idle draw")), "{:?}", r.scope); // The same bits as the reference, on a slice of the chains. assert_eq!(got[..4], CpuReference.chains(w, &cs[..4], temperatures).unwrap()[..]); } @@ -69,16 +71,18 @@ fn a_metal_member_reads_the_gpu_rail() { #[test] #[ignore] fn a_cuda_member_reads_the_board() { - let mut counter = wai::sink_meter::Nvml::open().expect("NVML"); - let (ok, seen) = counter.plausible(5, std::time::Duration::from_secs(1)); - println!("NVML plausible: {ok} ({seen:?}, counter rate and power reading in mW)"); - if !ok { - // energy-measurement §9.3: an implausible counter gives no figure, so none is asked of it. - println!("NVML is implausible here: no figure, as §9.3 requires"); - return; - } + let counter = wai::sink_meter::Nvml::open().expect("NVML"); + // energy-measurement §9.3: the bridge itself refuses an implausible counter, so no figure + // can come from one. This hand run then has nothing to read, and says so. + let meter = match RunCounter::nvml(counter, DECLARED_PPM) { + Ok(m) => m, + Err(why) => { + println!("no meter: {why}"); + return; + } + }; let m = wai_quantum::quantum_landscape::cuda::discover().into_iter().find_map(Result::ok).expect("a CUDA device"); - let m = m.with_meter(Box::new(RunCounter::new(Box::new(counter), DECLARED_PPM).unwrap())); + let m = m.with_meter(Box::new(meter)); let (w, cs, t) = work(4096, 1024, 1200); metered(&m, &w, &cs, &t); } -- 2.54.0 (Apple Git-157) From 44b284c9f1a9b305035c01562415baea8fc6d946 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Sat, 7 Nov 2026 01:26:34 -0500 Subject: [PATCH 4/4] wai-quantum: engine::job's seal test needs quantum_qec, and says so Its test of a host's seals calls qec, so the library's tests did not compile for quantum_engine,quantum_receipt, the landscape wasip2 job's feature set. Co-Authored-By: Claude Opus 5.5 --- wai/wai-quantum/src/engine/job.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/wai/wai-quantum/src/engine/job.rs b/wai/wai-quantum/src/engine/job.rs index 3d020880e..a2b28bdb2 100644 --- a/wai/wai-quantum/src/engine/job.rs +++ b/wai/wai-quantum/src/engine/job.rs @@ -791,6 +791,7 @@ pub(crate) mod tests { /// What a host seals, its own verifier accepts: a request holding a number /// no double holds is refused where the request is read, on every path to /// a seal, with the sentence a run gives. + #[cfg(feature = "quantum_qec")] #[test] fn a_host_never_seals_what_its_verifier_refuses() { const BIG: u64 = 1793907034945123456; -- 2.54.0 (Apple Git-157)