diff --git a/.github/workflows/wai-quantum-landscape.yml b/.github/workflows/wai-quantum-landscape.yml new file mode 100644 index 000000000..7a7fc3bb8 --- /dev/null +++ b/.github/workflows/wai-quantum-landscape.yml @@ -0,0 +1,358 @@ +name: wai-quantum energy-landscape jobs + +# A job that states an energy over variables of three states (extensions/quantum-landscape.md): +# the program reader, the two kernels, the exhaustive truth, the answer and its statement of joules, the +# classical methods and the table (wai_quantum::quantum_landscape), and the command line's verb. +# +# The kernels and every worked example give the same bits on every platform: a test pins a digest over them, +# and this workflow runs it on x86_64 Linux, arm64 Linux, arm64 macOS and wasm32-wasip2 (wasmtime), and compares +# the browser build's answers with the command line's, byte for byte, over many seeds. The conformance set is held to the +# code, and the stated result (quantum-conformance/landscape/stated-result.md) is checked by the script a reader runs. +# The browser build also runs its own digest in wai-web-wasm.yml. + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +on: + push: + branches: [main] + paths: + - 'wai/wai-quantum/src/quantum_landscape/**' + - 'wai/wai-quantum/src/quantum_landscape.rs' + - 'wai/wai-quantum/src/quantum_qio.rs' + - 'wai/wai-quantum/src/repro.rs' + - 'wai/wai-quantum/src/repro/**' + - 'wai/wai-quantum/src/engine/receipt.rs' + - 'wai/wai-quantum/src/engine/schema.rs' + - 'wai/wai-quantum/schemas/landscape.v1.json' + - 'wai/wai-quantum/tests/landscape_conformance.rs' + - 'wai/wai-quantum/tests/landscape_float_read.rs' + - 'wai/wai-quantum/Cargo.toml' + - 'wai/wai-quantum/Cargo.lock' + - 'wai/quantum-conformance/landscape/**' + - 'wai/wai-quantum-cli/src/landscape.rs' + - 'wai/wai-quantum-cli/src/main.rs' + - 'wai/wai-quantum-cli/Cargo.toml' + - 'wai/wai-quantum-cli/Cargo.lock' + - 'wai/wai-web/src/quantum_engine.rs' + - 'wai/wai-web/Cargo.toml' + - 'wai/wai-web/Cargo.lock' + - 'wai/wai-web/demo/verify_landscape_wasm.mjs' + - 'rust-toolchain.toml' + - '.github/workflows/wai-quantum-landscape.yml' + pull_request: + paths: + - 'wai/wai-quantum/src/quantum_landscape/**' + - 'wai/wai-quantum/src/quantum_landscape.rs' + - 'wai/wai-quantum/src/quantum_qio.rs' + - 'wai/wai-quantum/src/repro.rs' + - 'wai/wai-quantum/src/repro/**' + - 'wai/wai-quantum/src/engine/receipt.rs' + - 'wai/wai-quantum/src/engine/schema.rs' + - 'wai/wai-quantum/schemas/landscape.v1.json' + - 'wai/wai-quantum/tests/landscape_conformance.rs' + - 'wai/wai-quantum/tests/landscape_float_read.rs' + - 'wai/wai-quantum/Cargo.toml' + - 'wai/wai-quantum/Cargo.lock' + - 'wai/quantum-conformance/landscape/**' + - 'wai/wai-quantum-cli/src/landscape.rs' + - 'wai/wai-quantum-cli/src/main.rs' + - 'wai/wai-quantum-cli/Cargo.toml' + - 'wai/wai-quantum-cli/Cargo.lock' + - 'wai/wai-web/src/quantum_engine.rs' + - 'wai/wai-web/Cargo.toml' + - 'wai/wai-web/Cargo.lock' + - 'wai/wai-web/demo/verify_landscape_wasm.mjs' + - 'rust-toolchain.toml' + - '.github/workflows/wai-quantum-landscape.yml' + +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.yml \ + "${{ github.event.pull_request.base.sha }}" \ + "${{ github.event.pull_request.head.sha }}") + echo "this change touches energy-landscape jobs: $r" + echo "run=$r" >> "$GITHUB_OUTPUT" + + native: + name: reader, kernels, answers and the stated result · linux, release + needs: relevant + if: needs.relevant.outputs.run == 'true' + runs-on: linux + timeout-minutes: 60 + steps: + - uses: actions/checkout@v5 + - name: Install Rust 1.99.1 + shell: bash + run: | + export PATH="$HOME/.cargo/bin:$PATH" + command -v rustup >/dev/null 2>&1 || \ + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain 1.99.1 + rustup toolchain install 1.99.1 --profile minimal + rustup default 1.99.1 + echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" + # A module filter is satisfied by any one test of the module, so the + # count is pinned and each test is required by name to have run and passed. + - name: The module's tests + shell: bash + working-directory: wai/wai-quantum + 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; } + 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 \ + quantum_landscape::conformance::tests::a_state_s_index_is_its_digits \ + quantum_landscape::conformance::tests::the_reference_kernel_samples_every_workload_s_exact_law \ + quantum_landscape::energy::tests::a_resting_state_is_one_no_single_change_improves \ + quantum_landscape::energy::tests::quantising_keeps_signs_drops_the_small_and_says_so \ + quantum_landscape::energy::tests::the_default_temperature_is_the_root_of_the_mean_couplings \ + quantum_landscape::energy::tests::the_energy_is_the_formula \ + 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_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 \ + quantum_landscape::job::tests::a_temperature_string_follows_the_grammar \ + quantum_landscape::job::tests::an_answer_s_energy_is_recomputed_from_its_program_alone \ + quantum_landscape::job::tests::an_answer_says_what_it_is \ + quantum_landscape::job::tests::checks_are_bounded \ + quantum_landscape::job::tests::each_failure_fails_for_its_stated_reason \ + quantum_landscape::job::tests::every_example_says_what_it_is_there_to_show \ + quantum_landscape::job::tests::every_platform_gives_the_same_bits \ + quantum_landscape::job::tests::junk_never_panics \ + quantum_landscape::job::tests::quantised_weights_stay_within_the_field_bound \ + quantum_landscape::job::tests::steps_and_restarts_are_bounded_before_anything_runs \ + quantum_landscape::job::tests::the_examples_that_fail_fail_at_every_seed_tried \ + quantum_landscape::job::tests::the_examples_that_work_solve_at_every_seed_tried \ + quantum_landscape::job::tests::the_grammar_is_ascii_and_strict \ + quantum_landscape::job::tests::the_most_negative_whole_number_is_refused \ + quantum_landscape::job::tests::the_sweep_mean_does_not_overflow \ + quantum_landscape::job::tests::the_talks_are_solved_against_every_arrangement \ + quantum_landscape::member::tests::a_member_earns_its_parity_and_names_itself_properly \ + quantum_landscape::member::tests::the_classes_are_the_fabric_s \ + quantum_landscape::member::tests::the_reference_says_what_it_is \ + quantum_landscape::price::tests::a_small_job_says_it_prices_low_and_by_how_much \ + quantum_landscape::price::tests::above_zero_has_its_own_rate_about_two_and_a_half_times_the_other \ + quantum_landscape::price::tests::numbers_are_written_to_the_figures_they_have \ + quantum_landscape::price::tests::the_line_says_priced_and_never_calls_a_run_measured \ + quantum_landscape::program::tests::a_pin_outvotes_everything \ + quantum_landscape::program::tests::a_program_means_the_landscape_its_builder_calls_stand_for \ + quantum_landscape::program::tests::a_typo_gets_a_plain_message_with_its_line_and_a_suggestion \ + quantum_landscape::program::tests::each_refusal_names_its_line_where_one_means_anything \ + quantum_landscape::program::tests::notes_are_read_and_checks_grade_both_ways \ + 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::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_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 \ + quantum_landscape::run::tests::the_temperature_picks_the_mode_and_the_rate \ + quantum_landscape::sample::tests::a_cell_samples_its_boltzmann_weights \ + quantum_landscape::sample::tests::a_chain_at_temperature_zero_is_the_deterministic_sweep \ + quantum_landscape::sample::tests::a_cold_cell_at_a_tie_is_uniform_over_the_tied_states \ + quantum_landscape::sample::tests::a_cold_cell_away_from_a_tie_is_the_threshold \ + quantum_landscape::sample::tests::starts_use_all_three_states \ + quantum_landscape::sample::tests::the_cut_points_are_the_spec_s_formula \ + quantum_landscape::sample::tests::the_cut_points_are_the_weights \ + quantum_landscape::sample::tests::the_generator_is_splitmix64 \ + quantum_landscape::sample::tests::the_schedule_ends_at_zero_and_a_chain_repeats_itself \ + quantum_landscape::sample::tests::the_schedule_is_the_spec_s_formula \ + quantum_landscape::sample::tests::two_coupled_cells_sample_their_joint_distribution \ + quantum_landscape::sample::tests::without_zero_the_cell_is_the_two_state_cell \ + quantum_landscape::settle::tests::a_tie_goes_to_zero_and_the_cost_widens_it \ + quantum_landscape::settle::tests::four_recall_vectors_are_pinned \ + quantum_landscape::settle::tests::the_digest_tells_every_state_of_three_cells_apart \ + quantum_landscape::settle::tests::the_move_is_the_cell_s_lowest_energy \ + quantum_landscape::settle::tests::the_three_endings_are_told_apart \ + quantum_landscape::table::tests::a_hidden_group_really_is_the_lowest_state \ + quantum_landscape::table::tests::a_printed_instance_is_the_same_landscape \ + quantum_landscape::table::tests::the_table_is_the_same_on_one_thread_and_many \ + quantum_landscape::table::tests::the_tally_counts_what_it_says \ + quantum_landscape::trial::tests::the_grid_s_lowest_states_are_as_the_text_says \ + quantum_landscape::trial::tests::the_two_value_cell_picks_the_lower_energy_when_cold \ + quantum_landscape::truth::tests::a_known_answer_two_cells_that_must_oppose \ + quantum_landscape::truth::tests::the_declared_weights_are_checked_too \ + quantum_landscape::truth::tests::the_fast_walk_agrees_with_the_plain_one \ + quantum_landscape::truth::tests::the_lowest_states_are_graded_against_the_checks \ + quantum_landscape::truth::tests::too_large_is_none_and_the_largest_is_walked_whole; do + grep -q "^test $t \.\.\. ok$" "$RUNNER_TEMP/landscape.log" || { echo "::error::$t did not run and pass"; exit 1; } + done + - name: The conformance set, read as a member's checker reads it + shell: bash + working-directory: wai/wai-quantum + 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; } + # 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 + working-directory: wai/wai-quantum + run: | + 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; } + - name: The command line's verb + shell: bash + working-directory: wai/wai-quantum-cli + run: | + set -euo pipefail + cargo test --locked --release --bin wai-quantum -- landscape:: 2>&1 | tee "$RUNNER_TEMP/verb.log" + grep -qE '^test result: ok\. 2 passed; 0 failed' "$RUNNER_TEMP/verb.log" || { echo "::error::the verb's tests did not all pass (2 expected)"; exit 1; } + # The script a reader runs. It exits 0 only if every stated value is what this machine gets. + - name: The stated result + shell: bash + run: sh wai/quantum-conformance/landscape/reproduce.sh + # Without warnings: a feature gate that leaves code unreachable or a + # value unused in one feature set shows here. + - name: Each feature set that holds the module builds, without warnings + shell: bash + working-directory: wai/wai-quantum + env: + RUSTFLAGS: -D warnings + run: | + set -euo pipefail + cargo check --locked --no-default-features --features quantum_landscape --lib + cargo check --locked --no-default-features --features quantum_engine --lib + cargo check --locked --no-default-features --features quantum_engine,quantum_receipt --lib + + # The same bits on other processors and operating systems. + bits: + name: the same bits · ${{ matrix.label }} + needs: relevant + if: needs.relevant.outputs.run == 'true' + strategy: + fail-fast: false + matrix: + label: [linux-arm64, macos] + runs-on: ${{ matrix.label }} + timeout-minutes: 60 + steps: + - uses: actions/checkout@v5 + # launchd's PATH omits cargo on the macOS host runner. + - name: Install Rust 1.99.1 + shell: bash + run: | + export PATH="$HOME/.cargo/bin:$PATH" + command -v rustup >/dev/null 2>&1 || \ + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain 1.99.1 + rustup toolchain install 1.99.1 --profile minimal + rustup default 1.99.1 + echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" + - name: The pinned digest and the conformance set, in a debug build and a release build + shell: bash + working-directory: wai/wai-quantum + run: | + set -euo pipefail + export PATH="$HOME/.cargo/bin:$PATH" + for profile in "" "--release"; do + nice -n 10 cargo test --locked $profile --no-default-features --features quantum_landscape --lib -- \ + quantum_landscape::job::tests::every_platform_gives_the_same_bits \ + 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; } + done + + wasip2: + name: the module under wasm32-wasip2 (wasmtime) + needs: relevant + if: needs.relevant.outputs.run == 'true' + runs-on: linux + timeout-minutes: 60 + steps: + - uses: actions/checkout@v5 + - name: Install Rust 1.99.1 with the wasm32-wasip2 target + shell: bash + run: | + export PATH="$HOME/.cargo/bin:$PATH" + command -v rustup >/dev/null 2>&1 || \ + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain 1.99.1 + rustup toolchain install 1.99.1 --profile minimal + rustup default 1.99.1 + rustup target add --toolchain 1.99.1 wasm32-wasip2 + echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" + # The release binary, pinned by version and SHA-256, as every WAI workflow that runs wasmtime pins it + # (scripts/check-wasmtime-pin.py), and every run passes the two settings that fix its floats. + - name: Install wasmtime 50.0.1 + shell: bash + run: | + set -euo pipefail + [ "$(uname -m)" = x86_64 ] || { echo "::error::the pinned wasmtime archive is x86_64; this host is $(uname -m)"; exit 1; } + if ! command -v xz >/dev/null 2>&1; then + SUDO=""; [ "$(id -u)" -ne 0 ] && SUDO="sudo" + $SUDO apt-get update -qq + $SUDO apt-get install -y -qq xz-utils + fi + v=50.0.1 + f="wasmtime-v$v-x86_64-linux.tar.xz" + curl --proto '=https' --tlsv1.2 -sSfL -o "$RUNNER_TEMP/$f" \ + "https://github.com/bytecodealliance/wasmtime/releases/download/v$v/$f" + echo "685466455dd42a14774a86c36c8f2e45cbb9bd6d92a850139315df60a75498a5 $RUNNER_TEMP/$f" | sha256sum -c - + tar -xJf "$RUNNER_TEMP/$f" -C "$RUNNER_TEMP" + echo "$RUNNER_TEMP/wasmtime-v$v-x86_64-linux" >> "$GITHUB_PATH" + # A debug build, so integer overflow panics instead of wrapping. + - name: The module's tests under wasmtime, to the pinned bits + shell: bash + working-directory: wai/wai-quantum + env: + CARGO_TARGET_WASM32_WASIP2_RUNNER: wasmtime run -W nan-canonicalization=y -W relaxed-simd-deterministic=y + 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; } + + # The browser build's answers against the command line's, byte for byte, in Node. + parity: + name: native against WebAssembly, over many seeds + needs: relevant + if: needs.relevant.outputs.run == 'true' + runs-on: linux + timeout-minutes: 60 + steps: + - uses: actions/checkout@v5 + - name: Install Rust 1.99.1 with the wasm32 target + shell: bash + run: | + export PATH="$HOME/.cargo/bin:$PATH" + command -v rustup >/dev/null 2>&1 || \ + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain 1.99.1 + rustup toolchain install 1.99.1 --profile minimal + rustup default 1.99.1 + rustup target add wasm32-unknown-unknown + echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" + # Pinned to the wasm-bindgen version in wai-web/Cargo.lock: the CLI and the crate must match exactly. + - name: wasm-bindgen CLI 0.2.127 + shell: bash + run: cargo install --locked wasm-bindgen-cli --version 0.2.127 + - uses: actions/setup-node@v4 + with: + node-version: '22' + - name: The same answers, byte for byte + shell: bash + run: | + set -euo pipefail + (cd wai/wai-web && cargo build --locked --release --target wasm32-unknown-unknown --features quantum_engine \ + && wasm-bindgen --target nodejs --out-dir demo/pkg-node --out-name wai_web target/wasm32-unknown-unknown/release/wai_web.wasm) + (cd wai/wai-quantum-cli && cargo build --locked --release) + node wai/wai-web/demo/verify_landscape_wasm.mjs wai/wai-web/demo/pkg-node/wai_web.js wai/wai-quantum-cli/target/release/wai-quantum 200 diff --git a/.github/workflows/wai-web-wasm.yml b/.github/workflows/wai-web-wasm.yml index 5ca9bb67d..8f3e5b965 100644 --- a/.github/workflows/wai-web-wasm.yml +++ b/.github/workflows/wai-web-wasm.yml @@ -227,9 +227,10 @@ jobs: # course check whose bytes match the native build's pinned digest, a pulse # run whose final density matrix is the native build's bits (its pinned # digest), a program converted between formats to the QIR bytes the - # conformance set pins, and the variational library's gradient to the - # engine's pinned bytes beside a program built and checked. The count is the - # four tests above and these seventeen. + # conformance set pins, the variational library's gradient to the engine's + # pinned bytes beside a program built and checked, and every worked example of + # an energy-landscape job to the native build's pinned digest. The count is the + # four tests above and these eighteen. - name: The job engine in a page (wasm32 tests under Node) shell: bash working-directory: wai/wai-web @@ -237,7 +238,7 @@ jobs: set -euo pipefail CARGO_TARGET_WASM32_UNKNOWN_UNKNOWN_RUNNER=wasm-bindgen-test-runner \ cargo test --locked --target wasm32-unknown-unknown --features quantum_engine 2>&1 | tee "$RUNNER_TEMP/wai-web-engine-wasm.log" - grep -q "test result: ok. 21 passed; 0 failed" "$RUNNER_TEMP/wai-web-engine-wasm.log" + grep -q "test result: ok. 22 passed; 0 failed" "$RUNNER_TEMP/wai-web-engine-wasm.log" # Its native half: the service's form and receipt, any angle on the state # vector, what does not run saying why, plan, lint, draw and methods as JSON, # critique, equivalence and variants as JSON, the classical shortcuts, the @@ -256,8 +257,9 @@ jobs: # there, the circuits course read and checked there, a pulse program's # schedule drawn, its run as a job to the pinned bits and its check there, # a program carried between OpenQASM, QIR and jeff there, each conversion - # checked, and the variational library's gradient, optimisation, built - # program and refusal there. + # checked, the variational library's gradient, optimisation, built + # program and refusal there, and an energy-landscape job run and sealed there, + # its refusal with its line, and its examples to the pinned digest. - name: The job engine in a page (native unit tests) shell: bash working-directory: wai/wai-web diff --git a/wai/.gitignore b/wai/.gitignore index 5cdb0cdb6..c2b5b17ea 100644 --- a/wai/.gitignore +++ b/wai/.gitignore @@ -51,6 +51,7 @@ wai-web/demo/scpkg/ # wasm-pack build outputs (rebuilt by wasm-pack / demo/verify_int_synth.sh). wai-web/demo/pkg/ wai-web/demo/pkg-node/ +wai-web/demo/pkg-engine/ # The WebGPU member's bundle (demo/quantum_webgpu.html; built with --features quantum_webgpu). wai-web/demo/pkg-webgpu/ # The platform decode bundle (demo/platform_decode.html; built with --features platform_decode). diff --git a/wai/changes/2026-11-05-energy-landscape-jobs.md b/wai/changes/2026-11-05-energy-landscape-jobs.md new file mode 100644 index 000000000..7ab10ce49 --- /dev/null +++ b/wai/changes/2026-11-05-energy-landscape-jobs.md @@ -0,0 +1,11 @@ +### wai-quantum: energy-landscape jobs, a classical emulator of the settling model + +- **`quantum_landscape`** (`wai_quantum::quantum_landscape`, in `quantum_engine` and `full`) runs a program in epuML, an energy over variables of three states, on a classical emulator: a deterministic kernel in whole numbers and a sampling kernel of one draw between two cut points, both the same bits on every platform. An answer gives the state, its exact energy, every arrangement checked up to 16 variables, each lowest one graded against the problem's own check, the work counted, the joules priced at rates whose sources are named and never called measured, and a replay digest. +- **`wai-quantum landscape`** has `run`, `sweep`, `compare`, `replay`, `examples`, `table`, `instance`, `call`, `corpus`, `vectors` and `trial-assignment`. In a page, `QuantumEngine.landscape` and `landscape_answer` answer the same requests, to the same bytes, and `wai-web/demo/landscape.html` is the page. +- **The extension `wai.quantum.landscape/1`** ([quantum-landscape](extensions/quantum-landscape.md)) states the job, the answer, how joules are stated, and the seam a GPU member or an FPGA board runs behind, with the answer naming what ran it and how far that member is held to the reference. It states every rule a reader needs exactly: the ASCII grammar, a pin's size, how weights are quantised, the plans, the schedule, the cut points and the exponential. The CPU reference is the only member here. `wai.quantum.landscape/1` joins the schemas (`schemas/landscape.v1.json`). +- **Every quantity a job can ask for is bounded before it runs**, the same on every host: the program's length, its numbers, its starts, sweeps and checks, the operations of the whole job (the exhaustive truth and every seed included) and the memory it can hold, which the page also holds to its own limit. +- **The host checks what a member claims.** Every answer a member returns is re-checked (a fixed point rests, a two-cycle has period 2, the work and the draws are what the schedule comes to), and a member's parity is found by running its kernels on the conformance set, never declared: `bit-exact` on the `settle` and `chains` vectors, `statistical` on a battery of workloads built to catch a kernel that moves coupled cells at once, ignores its schedule, mishandles the last sweep or has a wrong table. +- **`quantum-conformance/landscape/`** holds the vectors a member is held to, the table of the emulator against simulated annealing, tabu search and greedy descent at the same budget, published as it came out, in which simulated annealing leads on the larger instances, and a stated result with the script that checks it. +- The joule line gives, for scale only, a published 2014 figure for one 8-bit integer add, which models no processor. +- The code came from a private crate declared `BSL-1.1`, and was relicensed under Apache-2.0 by its copyright holder, Open Interface Engineering, Inc. (`wai-quantum/src/quantum_landscape/PROVENANCE.md`). +- **Breaking:** `api:rust:wai_quantum::engine::schema::FILES`: the array gains `landscape.v1.json`, so its length goes from 24 to 25. Code that names the array's length should read `FILES.len()`; code that iterates it needs no change. diff --git a/wai/extensions/quantum-landscape.md b/wai/extensions/quantum-landscape.md new file mode 100644 index 000000000..d7dd2007d --- /dev/null +++ b/wai/extensions/quantum-landscape.md @@ -0,0 +1,540 @@ +# WAI Extension: Energy-Landscape Jobs (`wai.quantum.landscape/1`) + +Status: **Draft.** Reference: the `quantum_landscape` feature of `wai-quantum` +(`wai_quantum::quantum_landscape`), the command `wai-quantum landscape`, and +`QuantumEngine.landscape` in the browser build. Answer schema: +`wai-quantum/schemas/landscape.v1.json`. Conformance set: +`quantum-conformance/landscape/vectors.json`. Results: +`quantum-conformance/landscape/`. + +Keywords **MUST**, **MUST NOT**, **SHOULD** and **MAY** are RFC 2119/8174. + +## 0. What this extension adds + +A job of this kind gives no circuit. It states an energy over variables of +three states and asks for a state of low energy. A machine that takes such a +job settles: each cell adds up the pull of the cells it is coupled to and moves +to the state that pull favours, with or without randomness, until the whole +comes to rest. + +This text is the job, the answer and the seam between them and the device that +runs them. The same job can be run by the reference in software, by a GPU or by +an FPGA board, and its answer names what ran it, says how far that device is +held to the reference, and states its joules so that a priced figure can never +be read as a measured one. + +The reference is a classical emulation. Its cells are three-state values +sampled with classical random draws. Nothing here is a quantum effect, and no +run of the reference involves any device but the machine it runs on. + +## 1. The energy + +```text +E(s) = − Σ_{i i`, as doubles; 1 if there are none. +2. A weight with `|w| > m/2` becomes the coupling of its sign; any other weight + becomes 0, and is dropped. +3. Each field `b_i` becomes `round(b_i / m)` and the cost `round(c / m)` (at least + 0), dividing as doubles and rounding half away from zero. A result above 2⁴⁰ in + size is refused: the weights are too small beside the fields and the cost. + +The answer MUST say what was lost (`declared.quantised`: the scale, how many were +declared, kept and dropped, and the relative error `√(Σ(w − m·t)² / Σw²)` with `t` +each weight's coupling), give the energy of its state on the weights as declared, +and where every arrangement can be checked say whether that state is the lowest on +them (`declared`, §3). Two energies on declared weights are equal within 10⁻⁹. A program whose sizes matter does +not run on this model, and its answer says so rather than reaching a low energy +on signs that mean something else. + +## 3. The answer + +A `run` answers with these fields (the schema has them all): + +| field | | +|---|---| +| `protocol` | `wai.quantum.landscape/1` | +| `label` | what kind of run this is, in the member's own words (§5.3) | +| `member` | what ran it: `id`, `class`, `kernel` and `parity` (§5) | +| `program_digest` | BLAKE3 of the program's bytes | +| `seed`, `mode`, `temperature`, `temperature_from`, `restarts`, `sweeps` | how it ran; `seed` is a decimal string; `mode` is `settle`, `quench` or `sample` | +| `state`, `energy` | the state of lowest energy found, and its energy from the program | +| `truth` | every arrangement checked (up to 16 variables), else null: `arrangements`, `lowest`, `at_lowest`, `at_lowest_passing` and `example` | +| `reached` | whether `energy` is `truth.lowest`; null where there is no truth | +| `declared` | for a program with `weight`s: §2.2; else null | +| `warnings` | what the reader found in the program: `code`, `line`, `text` | +| `checks` | each `#! check`, graded on `state` | +| `verdict`, `summary` | `solved`, `missed`, `failed` or `unchecked`, and the sentence that says why | +| `work` | `sweeps`, `accumulates`, `cell_updates`, `draws`, `ended`, `rests` | +| `joules` | §4.4 | +| `state_hash`, `replay` | §3.2 | + +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 +pair twice, whatever device does it. + +### 3.1. The verdict + +`failed` means the answer does not solve what the program states. A host MUST +report it when: the deterministic mode never came to rest; it ran out of sweeps; +the answer fails a `#! check`; the lowest states of the landscape fail the +program's check, in which case the landscape itself does not express the problem, +whatever a run returns; or the answer misses the lowest energy on declared weights. +A host MUST grade every lowest state, not just the one it found: an answer can +pass by luck where the landscape is wrong. `at_lowest_passing` holds that count. + +`missed` means the truth is known and the run did not reach it. `solved` means it +did, or that a check passed where the truth is out of reach, in which case the +summary says nothing shows the energy is the lowest. `unchecked` means neither a +truth nor a check exists. A job that ran exits as success on a command line, +whatever its verdict. + +### 3.2. Replay + +The digest's restarts are 1 for a settle, and its sweeps are the plan's sweeps, +the `steps` asked, not the sweeps a settle ran. + +`state_hash` is FNV-1a over the state's cells, each the byte `s + 1`, printed as 16 +hex digits. `replay` is BLAKE3 over, in order: the protocol's name and a zero byte; +the program's length (u64, little-endian) and its bytes; the seed (u64, little-endian); +the mode's name and a zero byte; above temperature 0, the bits of the starting +temperature (IEEE 754 binary64, as a u64, little-endian); the restarts (u64, +little-endian); the sweeps (u32, little-endian); the state's cells, each the byte +`s + 1`; the energy (i64, little-endian). + +The digest leaves out the member and the work. Two members that give the same +bits give the same digest, and anyone can re-run a job on any member held to bits +and compare. For a member with draws of its own (§5.1) the digest identifies the +answer and cannot be reproduced from the seed. The energy can always be recomputed +from the program. + +## 4. The kernels + +A host's plan is one of three, from the run line and the request's temperature `T`: + +| run line | `T` | plan | starts | sweeps | +|---|---|---|---|---| +| `minimize steps=S` | none, or 0 | `settle`: from every cell at 0, stopping when it comes to rest | 1 | at most `S` | +| `minimize steps=S` | above 0 | `sample`, cooling from `T` | 8 | `S` each | +| `solve restarts=R steps=S` | 0 | `quench`: chains at temperature 0, every sweep run | `R` | `S` each | +| `solve restarts=R steps=S` | above 0 | `sample`, cooling from `T` | `R` | `S` each | +| `solve t0=T₀ …` | none | `quench` if `T₀` is 0, else `sample`, cooling from `T₀` | `R` | `S` each | +| `solve …` without `t0` | none | `sample`, cooling from the default start (§4.3) | `R` | `S` each | + +A `settle` runs the settle kernel; a `quench` and a `sample` run chains through the +sampling kernel, a quench's every sweep at 0. Of many starts the host keeps the first +state of lowest energy, in order of start. + +### 4.1. `wai.quantum.landscape/settle` + +Each cell adds up its field, `h_i = b_i + Σ_j W_ij s_j`, and moves to +1 when +`h_i > c`, to −1 when `h_i < −c`, and to 0 otherwise. A sweep reads all the fields +from the same state, then moves all the cells. A settle ends at a fixed point (the +state did not change), a two-cycle (the state returned to the one two sweeps back; +the state reported is the one the last sweep computed) or the bound. Everything here is whole-number arithmetic: the result cannot depend +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 `quench` runs chains through the kernel of §4.2 at temperature 0, whose every +sweep is this one. + +### 4.2. `wai.quantum.landscape/sample` + +Cell by cell, in order, each reading the states the cells before it have just +taken, a cell draws its next state with the Boltzmann weights of its field: + +```text +P(s) ∝ exp((h·s − c·|s|) / T), s ∈ {−1, 0, +1} +``` + +Updating every cell at once from one common old state does not sample this model's +joint distribution: two coupled cells would each follow the other's old value. A +kernel that does so fails the battery of §7.2 and is not granted `statistical`. A +kernel that colours its cells properly, so that no two coupled cells move at once, +can pass it. + +**The schedule.** The host gives a member every sweep's temperature. A chain of `K` +sweeps cooling from `T₀` has, at sweep `k` (from 0), `q = k / (K − 1)` and +`T_k = T₀ − T₀·q`, each operation rounded to the nearest double in that order; a +chain of one sweep is at 0. So the last sweep is at 0, where a sweep is §4.1's. The +conformance set pins the schedule's bits (§7.1). + +**One draw, two cut points.** For a field `h` at a temperature `T` above 0, with +`β = 1 / T`, `l₋ = −h − c`, `l₊ = h − c` (whole numbers, exact as doubles) and `m` +the largest of `l₋`, 0 and `l₊`: + +```text +w₋ = exp(β · (l₋ − m)), w₀ = exp(β · (0 − m)), w₊ = exp(β · (l₊ − m)) +z = (w₋ + w₀) + w₊ +a = ⌊ (w₋ / z) · 2⁵³ ⌋, b = ⌊ ((w₋ + w₀) / z) · 2⁵³ ⌋ +``` + +every operation a double rounded to the nearest, in the order the parentheses give, +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. + +**The reference's draws.** They come from a counter, not from a state carried along: + +```text +mix(x): x ^= x >> 30; x *= 0xBF58476D1CE4E5B9; + x ^= x >> 27; x *= 0x94D049BB133111EB; x ^= x >> 31 +out(s, j) = mix(s + j · 0x9E3779B97F4A7C15) (64-bit, wrapping; j ≥ 1) +``` + +For a run's seed `S`, start `r` (from 0) has the chain seed `C = out(S, r + 1)`; its +cell `i` starts at `out(!C, i + 1) mod 3 − 1`; and the draw of cell `i` in sweep `k` +is `out(C, k·n + i + 1) >> 11`. Any draw can be computed alone, so chains may run +side by side and still give the reference's bits. The exponential of Appendix A uses +`+ − × ÷` alone, so it gives the same bits on every IEEE 754 platform; the conformance +set pins its values and the cut points it gives (§7.1). A device takes the table from +the host, or computes its own and is held to it. + +A member MAY use draws of its own, and then its parity is `statistical` at most. + +### 4.3. The default start + +A `solve` with no `t0` starts at `max(1, √(2·P/n))`, where `P` is how many pairs are +coupled: the square root of the mean couplings a variable has. The answer's +`temperature_from` is `landscape`, and its words say this is the default. A starting +temperature set from the largest row of couplings is hotter than most instances +need, and at the same budget does worse on the larger instances of the published +table; the table's `start_temperature` section has the numbers. + +### 4.4. How the joules are stated + +`joules` is an object with `kind: "priced"`, always. It is this run's accumulates +multiplied by a rate measured elsewhere, with that rate's source named. A host: + +- MUST state the figure as priced, and MUST NOT call it measured, read, or metered, + in `line` or anywhere an answer's reader sees it. `rate_source` may say the *rate* + was measured, and on what. +- MUST state its class in the terms of [energy-measurement](energy-measurement.md): + `ModelBased` for the figure, which describes the board the rate was calibrated on + and says nothing of what the machine that ran the job spent (`describes`), and + `Estimator` for the published add's figure (`published_add`). +- MUST price temperature 0 and sampling at separate rates. Sampling costs more. +- MUST say what the rate leaves out (`left_out`): a rate that is a difference + cancels the board's own draw, so a small job prices low, by an amount the answer + gives from the fewest clocks the work could take. +- 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. + +The reference's rates: + +| | pJ per accumulate | from | +|---|---|---| +| temperature 0 | about 0.16 | one FPGA board, a dense 64-cell test design at 100 MHz, the slope of power against clock speed | +| sampling | about 0.41 | the same board: about 0.25 more, from paired readings at one temperature, 0.25, on a design that updates all its cells at once | +| for scale | about 0.03 | a published 2014 figure for one 8-bit integer add in 45 nm logic: a reference point, not a model of any processor, with no term for sampling | +| left out | about 3.1 W at every clock | the same board's own draw, at 100 MHz | + +These are the numbers of one board and one meter. They are not part of the protocol: +another host MAY price at rates of its own, with their sources named. + +## 5. Members + +A member runs the two kernels and nothing else. The reader, the truth, the energy of the +state it returns, the checks, the verdict, the replay digest and the seal stay on the +host, and do not trust the member. + +What the host gives a member: the lowered workload (`n`, the couplings as compressed +rows, the fields, the cost); for a settle, a start and a bound; for chains, each chain's +start and chain seed, and every sweep's temperature (§4.2). A host gives at most one +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. + +A host MUST check what comes back, and refuse the answer with one plain sentence if +any of these does not hold: + +- the state has the right length and every cell is −1, 0 or +1; +- for a settle: 1 to `bound` sweeps; accumulates `2P` a sweep and no draws; a fixed + point that rests (no cell would move); a two-cycle of period exactly 2 (one sweep + moves the state, a second returns it), after at least 2 sweeps; a bound reached + only after `bound` sweeps; +- for a chain: every sweep of the schedule run; accumulates `2P` a sweep and `n` + draws a sweep above 0; and the ending a fixed point exactly when the state rests. + +It MUST recompute the energy from the program. So a member can be wrong about a state +only by returning a worse one, and it cannot price itself low. How many sweeps a settle +took before it came to rest can be checked only by running it again: a `bit-exact` +certificate is what vouches for it. + +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. + +### 5.1. Parity + +A member does not state its parity: the host finds it, by running the member's kernels +on the conformance set (§7), and an answer states what the host found for the kernel the +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 | +| `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 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. + +### 5.2. The receipt + +A host seals an answer in the form of every engine result: the request, and the result +with `receipt` (`wai-quantum/schemas/receipt.v2.json`), a digest over both and an Ed25519 +signature. `member` is inside what is signed. The receipt says which key vouched for the +answer and the answer says what ran it, so a board's answer sealed by the host that drove +it names the board and names the host's key. `wai-quantum verify` checks it. + +### 5.3. The label + +Every answer carries `label`, a sentence that says what kind of run this is. The +reference's, word for word: + +> A classical emulator of the settling model of a planned energy-based processor, running on your machine. +> 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 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. + +## 6. The comparison + +`compare` runs the program on the member and on three classical methods on the same +three-state energy: simulated annealing, tabu search and greedy descent. Each has the +same budget of operations a run: the sampling run's own count, where one operation is +one coupling read, one cell updated or one move considered, whoever does it. A host +gives each row's count of runs at the target, and its best energy. A table MUST be +published as it comes out, including where a classical method leads. + +The operation count is not a machine's time. It says nothing of cells that settle side +by side, and the comparison is not a claim about any device. + +## 7. Conformance + +`quantum-conformance/landscape/vectors.json` is rebuilt by `wai-quantum landscape +vectors`, and a test holds the file to the code. Each part pins a rule of this text, so +that an implementation written from the text alone, which computes any of these another +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. +- `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. +- `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. +- `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 + reports, a settle's digest, leading zeros, and refusals of a `+` sign, an exponent in a + temperature, a byte-order mark, a no-break space, no steps, too many starts, the most + negative whole number, a weight too small and a pin too large). A host that claims this + protocol on the reference's kernels MUST give exactly these. + +### 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)`. + +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. + +## 8. What does not run on this model + +The worked examples include each of these, and they fail there, on purpose: problems of three +interchangeable options (colouring a map), where the state 0 is not a third colour; problems +whose sizes matter (two equal piles), where a coupling keeps only a sign; assignment beyond a +2×2 grid written as sums; and a constraint at temperature 0, which flips between two states +and never rests. A host MUST NOT hide these, and a program marked `#! expect fails` MUST fail. + +## 9. Versioning + +The protocol's name carries its version. Within `/1`: fields may be added to an answer, never +removed or given another meaning; the kernels' bits, the replay digest and the conformance +set's `settle`, `chains` and `jobs` do not change. A change to any of those is `/2`, with a +new kernel name. The words of a sentence, the rates of §4.4 and the published table are not part of +the protocol, and a host's `label` is its own. + +## Appendix A. The exponential + +The exponential of §4.2, as the reference computes it, with every operation a double +rounded to the nearest and no operation fused. With `ln2_hi = 6.93147180369123816490e-1`, +`ln2_lo = 1.90821492927058770002e-10`, `inv_ln2 = 1.44269504088896338700`, and +`P1 … P5 = 1.66666666666666019037e-1, −2.77777777770155933842e-3, +6.61375632143793436117e-5, −1.65339022054652515390e-6, 4.13813679705723846039e-8`: + +1. A NaN is returned as it is; `x > 709.782712893383973096` gives +∞, and + `x < −745.133219101941108420` gives 0. +2. If `|x| > ½·ln 2` (`ln 2` the double nearest it): `k = ±1` when `|x| < 1.5·ln 2`, with + the sign of `x`, and otherwise `k = trunc(inv_ln2·x + (x < 0 ? −0.5 : 0.5))`; then + `hi = x − k·ln2_hi`, `lo = k·ln2_lo`. If `|x| < 2⁻²⁸`, the result is `1 + x`. + Otherwise `hi = x`, `lo = 0`, `k = 0`. +3. `r = hi − lo`, `t = r·r`, `c = r − t·(P1 + t·(P2 + t·(P3 + t·(P4 + t·P5))))`. +4. If `k = 0`, the result is `1 − ((r·c)/(c − 2) − r)`. Otherwise + `y = 1 − ((lo − (r·c)/(2 − c)) − hi)`, and the result is `y·2ᵏ`: computed as + `y·2¹⁰²³·2^(k−1023)` when `k > 1023`, as `y·2^(k+1000)·2⁻¹⁰⁰⁰` when `k < −1021`, and as + `y·2ᵏ` otherwise, each power of two exact. + +The conformance set's `exp` part holds its values. + diff --git a/wai/quantum-conformance/README.md b/wai/quantum-conformance/README.md index a6f8f8533..98e352ef3 100644 --- a/wai/quantum-conformance/README.md +++ b/wai/quantum-conformance/README.md @@ -25,6 +25,10 @@ and jeff), each with the program it must read to, its round trip and its refusals, and OpenQASM written to each, are `formats/`, with a README of its own. +Energy-landscape jobs (`../extensions/quantum-landscape.md`) are `landscape/`: the +vectors a member is held to, the published table, the stated result and the script +that checks it, with a README of its own. + Job receipts, as the provider protocol's §8 states them (the seal, its strict reader and signature, and the rules of a provider's result), are `receipts/`, with a README of its own: receipts the command line, the job service and every built-in diff --git a/wai/quantum-conformance/landscape/README.md b/wai/quantum-conformance/landscape/README.md new file mode 100644 index 000000000..261ac579e --- /dev/null +++ b/wai/quantum-conformance/landscape/README.md @@ -0,0 +1,139 @@ +# Energy-landscape jobs: conformance and results + +For the extension [`quantum-landscape`](../../extensions/quantum-landscape.md). Reference: `wai_quantum::quantum_landscape` +and `wai-quantum landscape`. Everything here is the output of a command named below, on the code at the commit that +holds it. Every count is deterministic: the same command gives the same bytes on any machine. Wall-clock time is not, +and it is kept apart. + +| file | what | made by | +|---|---|---| +| [`vectors.json`](vectors.json) | the conformance set a member is held to, §7 of the extension | `wai-quantum landscape vectors` | +| [`table.json`](table.json), [`table.md`](table.md) | the scale table, below | `wai-quantum landscape table --out DIR` | +| [`timing.json`](timing.json) | that table's wall-clock seconds a run, on one machine | the same command | +| [`trial-assignment.json`](trial-assignment.json) | a trial outside the job form, below | `wai-quantum landscape trial-assignment` | +| [`native-wasm.txt`](native-wasm.txt) | the browser build against the command line | `wai-web/demo/verify_landscape_wasm.mjs`, `verify_landscape_page.mjs` | +| [`stated-result.md`](stated-result.md), [`reproduce.sh`](reproduce.sh) | a stated result, and the script that checks it | | + +These are classical runs on a classical emulator, and nothing in them is a quantum effect. + +## The worked examples + +Fourteen programs, in `wai-quantum/src/quantum_landscape/programs/`. At seeds 1 to 20 each one marked as working is +solved every time, and each one marked as failing fails every time, for its stated reason (`job::tests`). + +| example | variables | what it shows | result | +|---|---|---|---| +| `talks` | 12 | three states: room A, room B or left out | lowest energy −10, reached at all 1,000 seeds tried | +| `clusters` | 8 | signed clustering with a quarrel no split can settle | lowest energy −1, reached | +| `selection` | 14 | six of fourteen that belong together | lowest energy −9, and the planted six | +| `chain` | 8 | the deterministic mode with one end held | settles to the one lowest arrangement | +| `assign-2x2` | 4 | assignment written as sums, 2×2 | both lowest states are assignments | +| `blocks` | 64 | eight blocks of eight: too many arrangements to check | the planted pattern, with 200 sweeps a start | +| `assign-3x3` | 9 | the same construction, 3×3 | **fails**: 21 lowest states, only 6 of them assignments | +| `colour-six` | 6 | three colours, six regions | **fails**: none of the 6 lowest states is a colouring | +| `colour-four` | 4 | three colours, four regions | **fails**: neither of the 2 lowest states is a colouring | +| `piles` | 8 | two equal piles | **fails**: quantising to signs loses the sizes, and the answer has energy −7 on the weights as declared, against −130 | +| `cold-constraint` | 4 | 2×2 assignment at temperature 0 | **fails**: never rests, flipping between two states | +| `cold-start` | 12 | the talks at temperature 0, from all 0 | **misses**: stays at 0, energy 0 against −10 | +| `too-costly` | 14 | the six of fourteen at too high a cost | **fails**: leaving everything out is the one lowest state | +| `typo` | 3 | a misspelt statement | refused: `Line 6: `oppos` is not a statement epuML knows. Did you mean `oppose`?` | + +Where the truth exists and a program has its own check, every lowest arrangement is graded against it. That is how +`assign-3x3` fails even when a run's answer happens to be an assignment: the landscape does not express the problem. + +## Native and WebAssembly + +`wai-web/demo/verify_landscape_wasm.mjs` sends the same request to `wai-quantum landscape call` and to the browser build, and +compares the bytes: every example at its own temperature, at 0, at 0.5 and at a temperature given as text, a replay of 2,000 seeds +each, whole answers for 11 seeds, a sweep and a comparison. **700 answers the same bytes, 0 different, 104,000 seeded runs.** In Chrome, +`verify_landscape_page.mjs` repeats the replays in the browser's own engine: **84 of 84 answers the same bytes, 19,500 seeded runs** +([`native-wasm.txt`](native-wasm.txt)). The command line gives the same bytes on x86-64 Linux, arm64 Linux and arm64 macOS +([`stated-result.md`](stated-result.md)). + +Two pinned digests hold this in the repository's checks. `job::tests::every_platform_gives_the_same_bits` covers every worked +example at three temperatures over 20 seeds, on Linux x86-64 and arm64, macOS arm64 and wasm32-wasip2 under wasmtime, in debug and +release builds. `landscape_jobs_are_the_native_bits_in_wasm` runs the browser build's own, over 3 seeds, in `wai-web-wasm.yml`. + +## The scale table + +Four families, 8 to 128 variables (dense to 64), 5 instances each, 20 runs an instance and method, at 8 starts of 40 sweeps and +of 320. Every method in a row has the budget of the sampling mode's run, where one operation is one coupling read, one cell updated +or one move considered, whoever does it. The target is the exact lowest energy up to 16 variables, then a planted answer, or the best +that long runs of simulated annealing and tabu search found, which proves nothing. + +**Who reached the target more often, row by row, against the best of the three classical methods:** + +| the sampling mode | ahead | tied | behind | of | +|---|---|---|---|---| +| at the default start | 2 | 45 | 21 | 68 | +| at the hot start, the largest row of couplings | 0 | 38 | 30 | 68 | + +Simulated annealing is the strongest method here: it is the best of the three, or level with the best, in 64 of the 68 rows, and it +is ahead of the default start in 20 where the default start is ahead of it in 2. The two rows where the sampling mode is ahead of all three +are ahead by one run of a hundred (clusters at 64 variables and 320 sweeps, sparse-glass at 32 and 40), which chance gives. Simulated +annealing leads where it matters, at the larger sizes. At the default budget, at the largest size of each family: + +| family | variables | default start | hot start | deterministic | simulated annealing | tabu search | greedy descent | +|---|---|---|---|---|---|---|---| +| clusters | 128 | 46 of 100 | 0 | 0 | 79 | 7 | 0 | +| sparse-glass | 128 | 36 | 0 | 0 | 71 | 5 | 0 | +| dense-glass | 64 | 81 | 4 | 0 | 97 | 54 | 91 | +| selection | 128 | 100 | 100 | 100 | 100 | 100 | 100 | + +The deterministic mode, at temperature 0 from random starts, is far behind on all but the selection family, which is easy for everyone. + +`timing.json` has wall-clock seconds a run on one shared machine. They depend on how each method here is written, and say nothing about a +device, so they are not a result. + +**What the table does not say.** The operation count says nothing about what an operation costs in hardware, or about running cells side by +side, which a machine would do and these methods do not. + +## The starting temperature + +A `solve` with no `t0` starts at the square root of the mean couplings a variable has, and at least 1. On instance 99 of three families at 32 and 64 +variables, which the table's rows do not use, 40 runs at each start: + +| held-out instance | lowest found | default start: runs at the lowest | hot start: runs at the lowest | start 1: runs at the lowest | +|---|---|---|---|---| +| clusters, 32 | −16 | 2: 40 of 40 | 9: 28 | 40 | +| clusters, 64 | −26 | 2: 28 | 8: 1 | 40 | +| sparse-glass, 32 | −19 | 2.449: 33 | 13: 15 | 38 | +| sparse-glass, 64 | −51 | 2.449: 40 | 12: 4 | 40 | +| dense-glass, 32 | −128 | 5.568: 39 | 31: 23 | 39 | +| dense-glass, 64 | −362 | 7.937: 38 | 63: 0 | 40 | + +The hot start, the largest row of couplings, is far too hot at 64 variables, and worse at every size here. The default was chosen with this section in +view, and it is not the best start: a start of 1 does at least as well on all six, better on three of them (by 2 to 12 runs of 40), and on clusters at +64 by a wide margin. It stays where it is because it depends on the instance, and a fixed start of 1 would be too cold for a landscape of larger fields. +No instance here shows that; it is a judgement. `wai-quantum landscape sweep` tries the temperatures on a program of your own. + +## Assignment with a {0, +1} domain + +`wai-quantum landscape trial-assignment`, outside the job form: the same assignment landscape, with each cell limited to 0 or +1. 40 runs a size, 8 starts of 60 sweeps. + +| grid | lowest states (assignments among them) | three states | {0, +1} | +|---|---|---|---| +| 2×2 | 2 (2) | 40 of 40 | 40 of 40 | +| 3×3 | 21 (6) | 15 | 40 | +| 4×4 | 1,344 (24) | 1 | 40 | +| 5×5 | too many to check | 0 | 40 | +| 6×6 | | 0 | 40 | +| 8×8 | | 0 | 40 | + +So a {0, +1} domain fixes **feasibility**: the lowest states become exactly the assignments. It does not fix **costs**. With costs of 0 or 1 a cell, on 80 random instances, it found the +cheapest assignment every time the cheapest cost 0, and never found a valid assignment when the cheapest cost more: the couplings are fixed at 1, so the constraint's weight is 1, and a cost of 1 ties +with leaving a row empty. Costed assignment would need couplings above 1 or fractional fields, and this model has neither. The domain itself is a change to the cell, so it is a trial and not a kernel. + +## The joule line + +On the twelve talks: 13,440 accumulates × about 0.41 pJ = about 5.5 nJ, priced. The same run's 320 sweeps need at least 320 clocks on that board, which draws about 3.1 W whatever it does: about 9.9 µJ +of board power the priced line leaves out. A run's joules are not measured here, because no meter is attached to a run 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`. diff --git a/wai/quantum-conformance/landscape/native-wasm.txt b/wai/quantum-conformance/landscape/native-wasm.txt new file mode 100644 index 000000000..9bd96bb88 --- /dev/null +++ b/wai/quantum-conformance/landscape/native-wasm.txt @@ -0,0 +1,18 @@ +The browser build against the command line, byte for byte, on 2026-11-06. + +wai-web/demo/verify_landscape_wasm.mjs, seeds 2000, Node, macOS arm64: every worked example, at its own +temperature, at 0, at 0.5 and at a temperature given as text with every digit of a double. For each, a replay of +2,000 seeds (one line a seed, carrying a digest of that run's whole answer), whole answers for 11 seeds including +the largest, and at its own temperature a sweep and a comparison with classical methods. + + native and wasm: 700 answers the same bytes, 0 different; 104000 seeded runs inside the replays. + +wai-web/demo/verify_landscape_page.mjs, seeds 500, Chrome, macOS arm64: every worked example at its own temperature, +at 0 and at 0.5, a replay and a whole answer, from the module the page loads. + + in the browser, 84 of 84 answers are the command line's bytes (19500 seeded runs) + +The same script pressed every button at 1280×800 and 390×844, in the light and dark schemes, with no WCAG 2.2 A or +AA violation found by axe, no sideways overflow and no console error; a refusal through Sweep and Compare reached the +live status, the sweep showed the board draw each priced figure leaves out, and the count under the published table was +of the rows shown: 77 checks passed, 0 failed. diff --git a/wai/quantum-conformance/landscape/reproduce.sh b/wai/quantum-conformance/landscape/reproduce.sh new file mode 100755 index 000000000..5826e019b --- /dev/null +++ b/wai/quantum-conformance/landscape/reproduce.sh @@ -0,0 +1,89 @@ +#!/bin/sh +# Check the stated result (stated-result.md), from a clean checkout. Needs Rust through rustup, which +# reads rust-toolchain.toml. Node, wasm-bindgen 0.2.127 and the wasm32-unknown-unknown target are needed +# only for the last, optional check. +# quantum-conformance/landscape/reproduce.sh each stated value, and what this machine got +# quantum-conformance/landscape/reproduce.sh --print this machine's values only, to state them +set -eu +HERE=$(cd "$(dirname "$0")" && pwd) +WAI=$(cd "$HERE/../.." && pwd) +PRINT=0; [ "${1:-}" = "--print" ] && PRINT=1 +cd "$WAI/wai-quantum-cli" +cargo build --release --locked >/dev/null 2>&1 || cargo build --release --locked +T=$(cargo metadata --format-version 1 --no-deps | sed -n 's/.*"target_directory":"\([^"]*\)".*/\1/p') +B=$T/release/wai-quantum +TMP=$(mktemp -d) +sha() { if command -v sha256sum >/dev/null; then sha256sum "$1" | cut -d' ' -f1; else shasum -a 256 "$1" | cut -d' ' -f1; fi; } +fail=0 +state() { + if [ $PRINT = 1 ]; then echo "$1: $2"; return; fi + if [ "$2" = "$3" ]; then echo "PASS $1: $2"; else echo "FAIL $1: this machine got $2, the stated value is $3"; fail=1; fi +} + +# 1. The twelve talks, a thousand seeds. +"$B" landscape replay --example talks --seed 1 --seeds 1000 > "$TMP/talks.txt" +"$B" landscape run --example talks --seed 1 --json --demo-key > "$TMP/talks1.json" +state "talks: arrangements checked" "$(sed -n 's/.*"arrangements":\([0-9]*\).*/\1/p' "$TMP/talks1.json")" "531441" +state "talks: lowest energy" "$(sed -n 's/.*"lowest":\(-*[0-9]*\).*/\1/p' "$TMP/talks1.json")" "-10" +state "talks: arrangements at the lowest energy" "$(sed -n 's/.*"at_lowest":\([0-9]*\).*/\1/p' "$TMP/talks1.json")" "2" +state "talks: runs of 1000 that reach it" "$(awk '$4 == "-10"' "$TMP/talks.txt" | wc -l | tr -d ' ')" "1000" +state "talks: accumulates a run" "$(awk '{print $6}' "$TMP/talks.txt" | sort -u | tr '\n' ' ' | sed 's/ $//')" "13440" +state "talks: replay digest at seed 1" "$(sed -n 's/.*"replay":"\([0-9a-f]*\)".*/\1/p' "$TMP/talks1.json")" "6c40700b29e6193abe0a54d8424f2c65f6658a55849176b21f2e2cce4bca05fc" +state "talks: the lines of seeds 1 to 1000, SHA-256" "$(sha "$TMP/talks.txt")" "9ad4367cf31a607c3edfc3732d740824e4b7d56ae141c5bf772fd087347209dd" + +# 2. Every worked example, at its own temperature, at 0 and at 0.5, two hundred seeds each. +: > "$TMP/examples.txt" +for e in $("$B" landscape examples | awk '{print $1}'); do + for t in "" 0 0.5; do + echo "== $e ${t:-own}" >> "$TMP/examples.txt" + if [ -n "$t" ]; then "$B" landscape replay --example "$e" --seed 0 --seeds 200 --temperature "$t" >> "$TMP/examples.txt" 2>&1 || true + else "$B" landscape replay --example "$e" --seed 0 --seeds 200 >> "$TMP/examples.txt" 2>&1 || true; fi + done +done +state "examples: runs that solved, of those that ran" "$(grep -c ' solved ' "$TMP/examples.txt") of $(awk 'NF == 7' "$TMP/examples.txt" | wc -l | tr -d ' ')" "3637 of 7800" +state "examples: every line, SHA-256" "$(sha "$TMP/examples.txt")" "d9db5db3fc24f35a8d8c389e720ccbe36e7c3c26bcb16672db064cddc892e95b" +state "examples: the digest of every answer at 20 seeds" "$("$B" landscape corpus --seeds 20)" "a84fb1e13592511ce7f9ebd2dde1af711bcc16786fc2f08d79032cab2e93158a" + +# 3. The scale table, which is this directory's. +mkdir -p "$TMP/table" +"$B" landscape table --out "$TMP/table" > /dev/null +state "table: table.json, SHA-256" "$(sha "$TMP/table/table.json")" "aea3a97c927fbce24197d0065f63e1f36a2bda6c359fa67f956c4ab752c32dd6" +state "table: table.json the same as this directory's" "$(cmp -s "$TMP/table/table.json" "$HERE/table.json" && echo same || echo different)" "same" +state "table: table.md the same as this directory's" "$(cmp -s "$TMP/table/table.md" "$HERE/table.md" && echo same || echo different)" "same" +tally() { sed -n "s/.*$1 reached the target more often than every classical solver in \([0-9]*\), tied the best of them in \([0-9]*\), and was behind the best of them in \([0-9]*\)\..*/\1 \2 \3/p" "$TMP/table/table.md"; } +state "table: at the default start, ahead, tied and behind the best classical method" "$(tally 'at the default start')" "2 45 21" +state "table: at the hot start, ahead, tied and behind the best classical method" "$(tally 'at the hot start, the largest row of couplings')" "0 38 30" + +# 4. The conformance set and the trial, which are this directory's. +"$B" landscape vectors > "$TMP/vectors.json" +state "vectors: the same as this directory's" "$(cmp -s "$TMP/vectors.json" "$HERE/vectors.json" && echo same || echo different)" "same" +"$B" landscape trial-assignment > "$TMP/trial-assignment.json" +state "trial: the same as this directory's" "$(cmp -s "$TMP/trial-assignment.json" "$HERE/trial-assignment.json" && echo same || echo different)" "same" + +# 5. Optional: the same answers from the browser build, if Node and its tools are here. The +# comparison's own exit status decides: POSIX sh has no pipefail, so nothing is piped. +if command -v node >/dev/null && command -v wasm-bindgen >/dev/null && rustup target list --installed 2>/dev/null | grep -q wasm32-unknown-unknown; then + WB=$(wasm-bindgen --version | awk '{print $2}') + if [ "$WB" != 0.2.127 ]; then + echo "SKIP native against WebAssembly: wasm-bindgen $WB is here, and wai-web's lockfile takes 0.2.127" + elif ! (cd "$WAI/wai-web" && cargo build --locked --release --target wasm32-unknown-unknown --features quantum_engine >/dev/null 2>&1); then + echo "FAIL native against WebAssembly: the browser build did not build" + fail=1 + else + W=$(cd "$WAI/wai-web" && cargo metadata --format-version 1 --no-deps | sed -n 's/.*"target_directory":"\([^"]*\)".*/\1/p') + wasm-bindgen --target nodejs --out-dir "$TMP/pkg" --out-name wai_web "$W/wasm32-unknown-unknown/release/wai_web.wasm" + cp "$WAI/wai-web/demo/verify_landscape_wasm.mjs" "$TMP/verify.mjs" + if out=$(node "$TMP/verify.mjs" "$TMP/pkg/wai_web.js" "$B" 200 2>&1); then + echo "PASS $(printf '%s\n' "$out" | tail -1)" + else + echo "FAIL $(printf '%s\n' "$out" | tail -1)" + fail=1 + fi + fi +else + echo "SKIP native against WebAssembly: needs Node, wasm-bindgen and the wasm32-unknown-unknown target" +fi + +[ $PRINT = 1 ] && exit 0 +[ $fail = 0 ] && echo "Every stated value reproduced on this machine." || echo "At least one stated value did not reproduce." +exit $fail diff --git a/wai/quantum-conformance/landscape/stated-result.md b/wai/quantum-conformance/landscape/stated-result.md new file mode 100644 index 000000000..42d643b50 --- /dev/null +++ b/wai/quantum-conformance/landscape/stated-result.md @@ -0,0 +1,55 @@ +# The stated result + +This is a result anyone can reproduce from the scripts here, and the script that checks it. + +## The statement + +On the commit that holds this file, built with Rust 1.99.1 (`rust-toolchain.toml`), on any machine: + +1. The twelve-talk program (`wai-quantum landscape examples talks`) has 531,441 arrangements. Its lowest + energy is −10, held by 2 of them. The sampling mode, at the default start, reaches −10 at **all 1,000 + seeds from 1 to 1,000**, spending exactly **13,440 accumulates** a run. Seed 1's replay digest is + `6c40700b29e6193abe0a54d8424f2c65f6658a55849176b21f2e2cce4bca05fc`, and the 1,000 lines + of `wai-quantum landscape replay` hash, with SHA-256, to + `9ad4367cf31a607c3edfc3732d740824e4b7d56ae141c5bf772fd087347209dd`. +2. Across every worked example, at its own temperature, at 0 and at 0.5, 200 seeds each, **3,637 of the + 7,800 runs** that run are solved, and the lines hash to + `d9db5db3fc24f35a8d8c389e720ccbe36e7c3c26bcb16672db064cddc892e95b`. The examples marked as failing fail. +3. The scale table, [`table.json`](table.json), rebuilds to SHA-256 + `aea3a97c927fbce24197d0065f63e1f36a2bda6c359fa67f956c4ab752c32dd6`. In it, against the best of three + classical methods at the same budget, the sampling mode at the default start is **ahead in 2 rows, tied + in 45 and behind in 21** of 68. Starting at the largest row of couplings, it is **ahead in 0, tied in 38 + and behind in 30**. + +## The check + +```sh norun=usage +git clone https://git.transaction.science/Transaction-Science/open-standards +cd open-standards/wai/quantum-conformance/landscape +./reproduce.sh +``` + +It prints each stated value beside what the machine got, and exits 0 only if every one matches. It needs +Rust through rustup and nothing else. With Node, wasm-bindgen and the `wasm32-unknown-unknown` target, it +also compares the browser build's answers with the native ones. It takes about a minute and a half on a +machine of 32 cores. + +## Who has reproduced it + +Each row ran `reproduce.sh` on 2026-11-06, on the code at the commit that holds this file. + +| who | machine | result | +|---|---|---| +| the author | Linux, x86-64 | every value | +| the author | Linux, arm64 | every value, with the same bytes | +| the author | macOS, Apple M3 Ultra (arm64) | every value, with the same bytes, and the browser build's answers the same as the native ones | + +No one outside the project has run it. Until someone does, this is a result that can be reproduced, not one +that has been. The machines above show that the bytes do not depend on the processor or the operating system. + +## What it shows, and what it does not + +It shows that the emulator's answers, counts and digests are exact and portable, that its failures are +reported as failures, and that a classical method on the same instance leads it on most of the larger rows. +It does not show anything about a machine's speed or energy. The joule line is priced from one FPGA board's +rates, and nothing here ran on any device but the machine that ran the script. diff --git a/wai/quantum-conformance/landscape/table.json b/wai/quantum-conformance/landscape/table.json new file mode 100644 index 000000000..d49959ddb --- /dev/null +++ b/wai/quantum-conformance/landscape/table.json @@ -0,0 +1,3584 @@ +{ + "protocol": "wai.quantum.landscape/1", + "options": { + "sizes": [ + 8, + 12, + 16, + 24, + 32, + 48, + 64, + 96, + 128 + ], + "dense_max": 64, + "instances": 5, + "seeds": 20, + "budgets": [ + 1, + 8 + ] + }, + "families": [ + { + "name": "clusters", + "about": "sparse signed clustering, about degree 4, one link in eight contradicting the rest, cost 1" + }, + { + "name": "sparse-glass", + "about": "random signs on a sparse graph, about degree 6, cost 1" + }, + { + "name": "dense-glass", + "about": "random signs on every pair, cost 0" + }, + { + "name": "selection", + "about": "a hidden group of a quarter of the variables, all linked by sign, cost 1, with a known answer" + } + ], + "solvers": [ + "emulator, sampling, default start", + "emulator, sampling, hot start", + "emulator, deterministic", + "simulated annealing", + "tabu search", + "greedy descent" + ], + "rows": [ + { + "family": "clusters", + "n": 8, + "pairs": 16, + "sweeps_per_start": 40, + "target": "exact", + "solvers": [ + { + "hits": 100, + "runs": 100, + "operations_per_run": 12800, + "operations_for_99_in_100": 12800 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 12800, + "operations_for_99_in_100": 12800 + }, + { + "hits": 60, + "runs": 100, + "operations_per_run": 12800, + "operations_for_99_in_100": 64331 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 12801, + "operations_for_99_in_100": 12801 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 12809, + "operations_for_99_in_100": 12809 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 12812, + "operations_for_99_in_100": 12812 + } + ] + }, + { + "family": "clusters", + "n": 8, + "pairs": 16, + "sweeps_per_start": 320, + "target": "exact", + "solvers": [ + { + "hits": 100, + "runs": 100, + "operations_per_run": 102400, + "operations_for_99_in_100": 102400 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 102400, + "operations_for_99_in_100": 102400 + }, + { + "hits": 60, + "runs": 100, + "operations_per_run": 102400, + "operations_for_99_in_100": 514650 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 102401, + "operations_for_99_in_100": 102401 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 102411, + "operations_for_99_in_100": 102411 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 102413, + "operations_for_99_in_100": 102413 + } + ] + }, + { + "family": "clusters", + "n": 12, + "pairs": 24, + "sweeps_per_start": 40, + "target": "exact", + "solvers": [ + { + "hits": 100, + "runs": 100, + "operations_per_run": 19200, + "operations_for_99_in_100": 19200 + }, + { + "hits": 97, + "runs": 100, + "operations_per_run": 19200, + "operations_for_99_in_100": 25215 + }, + { + "hits": 55, + "runs": 100, + "operations_per_run": 19200, + "operations_for_99_in_100": 110730 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 19200, + "operations_for_99_in_100": 19200 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 19212, + "operations_for_99_in_100": 19212 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 19219, + "operations_for_99_in_100": 19219 + } + ] + }, + { + "family": "clusters", + "n": 12, + "pairs": 24, + "sweeps_per_start": 320, + "target": "exact", + "solvers": [ + { + "hits": 100, + "runs": 100, + "operations_per_run": 153600, + "operations_for_99_in_100": 153600 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 153600, + "operations_for_99_in_100": 153600 + }, + { + "hits": 55, + "runs": 100, + "operations_per_run": 153600, + "operations_for_99_in_100": 885845 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 153601, + "operations_for_99_in_100": 153601 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 153613, + "operations_for_99_in_100": 153613 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 153620, + "operations_for_99_in_100": 153620 + } + ] + }, + { + "family": "clusters", + "n": 16, + "pairs": 32, + "sweeps_per_start": 40, + "target": "exact", + "solvers": [ + { + "hits": 100, + "runs": 100, + "operations_per_run": 25600, + "operations_for_99_in_100": 25600 + }, + { + "hits": 97, + "runs": 100, + "operations_per_run": 25600, + "operations_for_99_in_100": 33620 + }, + { + "hits": 4, + "runs": 100, + "operations_per_run": 25600, + "operations_for_99_in_100": 2887961 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 25602, + "operations_for_99_in_100": 25602 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 25618, + "operations_for_99_in_100": 25618 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 25625, + "operations_for_99_in_100": 25625 + } + ] + }, + { + "family": "clusters", + "n": 16, + "pairs": 32, + "sweeps_per_start": 320, + "target": "exact", + "solvers": [ + { + "hits": 100, + "runs": 100, + "operations_per_run": 204800, + "operations_for_99_in_100": 204800 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 204800, + "operations_for_99_in_100": 204800 + }, + { + "hits": 4, + "runs": 100, + "operations_per_run": 204800, + "operations_for_99_in_100": 23103693 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 204801, + "operations_for_99_in_100": 204801 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 204818, + "operations_for_99_in_100": 204818 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 204829, + "operations_for_99_in_100": 204829 + } + ] + }, + { + "family": "clusters", + "n": 24, + "pairs": 48, + "sweeps_per_start": 40, + "target": "best known", + "solvers": [ + { + "hits": 100, + "runs": 100, + "operations_per_run": 38400, + "operations_for_99_in_100": 38400 + }, + { + "hits": 94, + "runs": 100, + "operations_per_run": 38400, + "operations_for_99_in_100": 62855 + }, + { + "hits": 20, + "runs": 100, + "operations_per_run": 38400, + "operations_for_99_in_100": 792487 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 38401, + "operations_for_99_in_100": 38401 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 38423, + "operations_for_99_in_100": 38423 + }, + { + "hits": 99, + "runs": 100, + "operations_per_run": 38434, + "operations_for_99_in_100": 38434 + } + ] + }, + { + "family": "clusters", + "n": 24, + "pairs": 48, + "sweeps_per_start": 320, + "target": "best known", + "solvers": [ + { + "hits": 100, + "runs": 100, + "operations_per_run": 307200, + "operations_for_99_in_100": 307200 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 307200, + "operations_for_99_in_100": 307200 + }, + { + "hits": 20, + "runs": 100, + "operations_per_run": 307200, + "operations_for_99_in_100": 6339902 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 307202, + "operations_for_99_in_100": 307202 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 307224, + "operations_for_99_in_100": 307224 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 307237, + "operations_for_99_in_100": 307237 + } + ] + }, + { + "family": "clusters", + "n": 32, + "pairs": 64, + "sweeps_per_start": 40, + "target": "best known", + "solvers": [ + { + "hits": 97, + "runs": 100, + "operations_per_run": 51200, + "operations_for_99_in_100": 67241 + }, + { + "hits": 73, + "runs": 100, + "operations_per_run": 51200, + "operations_for_99_in_100": 180079 + }, + { + "hits": 19, + "runs": 100, + "operations_per_run": 51200, + "operations_for_99_in_100": 1118942 + }, + { + "hits": 99, + "runs": 100, + "operations_per_run": 51201, + "operations_for_99_in_100": 51201 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 51232, + "operations_for_99_in_100": 51232 + }, + { + "hits": 82, + "runs": 100, + "operations_per_run": 51245, + "operations_for_99_in_100": 137622 + } + ] + }, + { + "family": "clusters", + "n": 32, + "pairs": 64, + "sweeps_per_start": 320, + "target": "best known", + "solvers": [ + { + "hits": 100, + "runs": 100, + "operations_per_run": 409600, + "operations_for_99_in_100": 409600 + }, + { + "hits": 96, + "runs": 100, + "operations_per_run": 409600, + "operations_for_99_in_100": 586005 + }, + { + "hits": 19, + "runs": 100, + "operations_per_run": 409600, + "operations_for_99_in_100": 8951539 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 409601, + "operations_for_99_in_100": 409601 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 409633, + "operations_for_99_in_100": 409633 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 409650, + "operations_for_99_in_100": 409650 + } + ] + }, + { + "family": "clusters", + "n": 48, + "pairs": 96, + "sweeps_per_start": 40, + "target": "best known", + "solvers": [ + { + "hits": 96, + "runs": 100, + "operations_per_run": 76800, + "operations_for_99_in_100": 109875 + }, + { + "hits": 46, + "runs": 100, + "operations_per_run": 76800, + "operations_for_99_in_100": 573977 + }, + { + "hits": 0, + "runs": 100, + "operations_per_run": 76800, + "operations_for_99_in_100": null + }, + { + "hits": 98, + "runs": 100, + "operations_per_run": 76801, + "operations_for_99_in_100": 90409 + }, + { + "hits": 90, + "runs": 100, + "operations_per_run": 76839, + "operations_for_99_in_100": 153679 + }, + { + "hits": 62, + "runs": 100, + "operations_per_run": 76871, + "operations_for_99_in_100": 365865 + } + ] + }, + { + "family": "clusters", + "n": 48, + "pairs": 96, + "sweeps_per_start": 320, + "target": "best known", + "solvers": [ + { + "hits": 99, + "runs": 100, + "operations_per_run": 614400, + "operations_for_99_in_100": 614400 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 614400, + "operations_for_99_in_100": 614400 + }, + { + "hits": 0, + "runs": 100, + "operations_per_run": 614400, + "operations_for_99_in_100": null + }, + { + "hits": 99, + "runs": 100, + "operations_per_run": 614401, + "operations_for_99_in_100": 614401 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 614448, + "operations_for_99_in_100": 614448 + }, + { + "hits": 98, + "runs": 100, + "operations_per_run": 614472, + "operations_for_99_in_100": 723347 + } + ] + }, + { + "family": "clusters", + "n": 64, + "pairs": 128, + "sweeps_per_start": 40, + "target": "best known", + "solvers": [ + { + "hits": 94, + "runs": 100, + "operations_per_run": 102400, + "operations_for_99_in_100": 167614 + }, + { + "hits": 35, + "runs": 100, + "operations_per_run": 102400, + "operations_for_99_in_100": 1094679 + }, + { + "hits": 0, + "runs": 100, + "operations_per_run": 102400, + "operations_for_99_in_100": null + }, + { + "hits": 97, + "runs": 100, + "operations_per_run": 102401, + "operations_for_99_in_100": 134484 + }, + { + "hits": 60, + "runs": 100, + "operations_per_run": 102462, + "operations_for_99_in_100": 514966 + }, + { + "hits": 22, + "runs": 100, + "operations_per_run": 102496, + "operations_for_99_in_100": 1899749 + } + ] + }, + { + "family": "clusters", + "n": 64, + "pairs": 128, + "sweeps_per_start": 320, + "target": "best known", + "solvers": [ + { + "hits": 100, + "runs": 100, + "operations_per_run": 819200, + "operations_for_99_in_100": 819200 + }, + { + "hits": 93, + "runs": 100, + "operations_per_run": 819200, + "operations_for_99_in_100": 1418648 + }, + { + "hits": 0, + "runs": 100, + "operations_per_run": 819200, + "operations_for_99_in_100": null + }, + { + "hits": 99, + "runs": 100, + "operations_per_run": 819201, + "operations_for_99_in_100": 819201 + }, + { + "hits": 94, + "runs": 100, + "operations_per_run": 819266, + "operations_for_99_in_100": 1341027 + }, + { + "hits": 40, + "runs": 100, + "operations_per_run": 819302, + "operations_for_99_in_100": 7386135 + } + ] + }, + { + "family": "clusters", + "n": 96, + "pairs": 192, + "sweeps_per_start": 40, + "target": "best known", + "solvers": [ + { + "hits": 56, + "runs": 100, + "operations_per_run": 153600, + "operations_for_99_in_100": 861596 + }, + { + "hits": 4, + "runs": 100, + "operations_per_run": 153600, + "operations_for_99_in_100": 17327770 + }, + { + "hits": 0, + "runs": 100, + "operations_per_run": 153600, + "operations_for_99_in_100": null + }, + { + "hits": 86, + "runs": 100, + "operations_per_run": 153601, + "operations_for_99_in_100": 359777 + }, + { + "hits": 18, + "runs": 100, + "operations_per_run": 153701, + "operations_for_99_in_100": 3566727 + }, + { + "hits": 3, + "runs": 100, + "operations_per_run": 153748, + "operations_for_99_in_100": 23245467 + } + ] + }, + { + "family": "clusters", + "n": 96, + "pairs": 192, + "sweeps_per_start": 320, + "target": "best known", + "solvers": [ + { + "hits": 98, + "runs": 100, + "operations_per_run": 1228800, + "operations_for_99_in_100": 1446523 + }, + { + "hits": 80, + "runs": 100, + "operations_per_run": 1228800, + "operations_for_99_in_100": 3516030 + }, + { + "hits": 0, + "runs": 100, + "operations_per_run": 1228800, + "operations_for_99_in_100": null + }, + { + "hits": 99, + "runs": 100, + "operations_per_run": 1228802, + "operations_for_99_in_100": 1228802 + }, + { + "hits": 68, + "runs": 100, + "operations_per_run": 1228899, + "operations_for_99_in_100": 4966754 + }, + { + "hits": 8, + "runs": 100, + "operations_per_run": 1228941, + "operations_for_99_in_100": 67874513 + } + ] + }, + { + "family": "clusters", + "n": 128, + "pairs": 256, + "sweeps_per_start": 40, + "target": "best known", + "solvers": [ + { + "hits": 46, + "runs": 100, + "operations_per_run": 204800, + "operations_for_99_in_100": 1530607 + }, + { + "hits": 0, + "runs": 100, + "operations_per_run": 204800, + "operations_for_99_in_100": null + }, + { + "hits": 0, + "runs": 100, + "operations_per_run": 204800, + "operations_for_99_in_100": null + }, + { + "hits": 79, + "runs": 100, + "operations_per_run": 204801, + "operations_for_99_in_100": 604330 + }, + { + "hits": 7, + "runs": 100, + "operations_per_run": 204944, + "operations_for_99_in_100": 13005283 + }, + { + "hits": 0, + "runs": 100, + "operations_per_run": 204995, + "operations_for_99_in_100": null + } + ] + }, + { + "family": "clusters", + "n": 128, + "pairs": 256, + "sweeps_per_start": 320, + "target": "best known", + "solvers": [ + { + "hits": 91, + "runs": 100, + "operations_per_run": 1638400, + "operations_for_99_in_100": 3133422 + }, + { + "hits": 67, + "runs": 100, + "operations_per_run": 1638400, + "operations_for_99_in_100": 6805596 + }, + { + "hits": 0, + "runs": 100, + "operations_per_run": 1638400, + "operations_for_99_in_100": null + }, + { + "hits": 94, + "runs": 100, + "operations_per_run": 1638401, + "operations_for_99_in_100": 2681840 + }, + { + "hits": 26, + "runs": 100, + "operations_per_run": 1638543, + "operations_for_99_in_100": 25060253 + }, + { + "hits": 0, + "runs": 100, + "operations_per_run": 1638583, + "operations_for_99_in_100": null + } + ] + }, + { + "family": "sparse-glass", + "n": 8, + "pairs": 24, + "sweeps_per_start": 40, + "target": "exact", + "solvers": [ + { + "hits": 100, + "runs": 100, + "operations_per_run": 17920, + "operations_for_99_in_100": 17920 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 17920, + "operations_for_99_in_100": 17920 + }, + { + "hits": 21, + "runs": 100, + "operations_per_run": 17920, + "operations_for_99_in_100": 350092 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 17922, + "operations_for_99_in_100": 17922 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 17929, + "operations_for_99_in_100": 17929 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 17937, + "operations_for_99_in_100": 17937 + } + ] + }, + { + "family": "sparse-glass", + "n": 8, + "pairs": 24, + "sweeps_per_start": 320, + "target": "exact", + "solvers": [ + { + "hits": 100, + "runs": 100, + "operations_per_run": 143360, + "operations_for_99_in_100": 143360 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 143360, + "operations_for_99_in_100": 143360 + }, + { + "hits": 21, + "runs": 100, + "operations_per_run": 143360, + "operations_for_99_in_100": 2800740 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 143362, + "operations_for_99_in_100": 143362 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 143371, + "operations_for_99_in_100": 143371 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 143379, + "operations_for_99_in_100": 143379 + } + ] + }, + { + "family": "sparse-glass", + "n": 12, + "pairs": 36, + "sweeps_per_start": 40, + "target": "exact", + "solvers": [ + { + "hits": 100, + "runs": 100, + "operations_per_run": 26880, + "operations_for_99_in_100": 26880 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 26880, + "operations_for_99_in_100": 26880 + }, + { + "hits": 27, + "runs": 100, + "operations_per_run": 26880, + "operations_for_99_in_100": 393335 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 26882, + "operations_for_99_in_100": 26882 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 26894, + "operations_for_99_in_100": 26894 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 26905, + "operations_for_99_in_100": 26905 + } + ] + }, + { + "family": "sparse-glass", + "n": 12, + "pairs": 36, + "sweeps_per_start": 320, + "target": "exact", + "solvers": [ + { + "hits": 100, + "runs": 100, + "operations_per_run": 215040, + "operations_for_99_in_100": 215040 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 215040, + "operations_for_99_in_100": 215040 + }, + { + "hits": 27, + "runs": 100, + "operations_per_run": 215040, + "operations_for_99_in_100": 3146685 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 215042, + "operations_for_99_in_100": 215042 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 215053, + "operations_for_99_in_100": 215053 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 215066, + "operations_for_99_in_100": 215066 + } + ] + }, + { + "family": "sparse-glass", + "n": 16, + "pairs": 48, + "sweeps_per_start": 40, + "target": "exact", + "solvers": [ + { + "hits": 100, + "runs": 100, + "operations_per_run": 35840, + "operations_for_99_in_100": 35840 + }, + { + "hits": 90, + "runs": 100, + "operations_per_run": 35840, + "operations_for_99_in_100": 71680 + }, + { + "hits": 27, + "runs": 100, + "operations_per_run": 35840, + "operations_for_99_in_100": 524447 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 35842, + "operations_for_99_in_100": 35842 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 35859, + "operations_for_99_in_100": 35859 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 35872, + "operations_for_99_in_100": 35872 + } + ] + }, + { + "family": "sparse-glass", + "n": 16, + "pairs": 48, + "sweeps_per_start": 320, + "target": "exact", + "solvers": [ + { + "hits": 100, + "runs": 100, + "operations_per_run": 286720, + "operations_for_99_in_100": 286720 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 286720, + "operations_for_99_in_100": 286720 + }, + { + "hits": 27, + "runs": 100, + "operations_per_run": 286720, + "operations_for_99_in_100": 4195580 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 286722, + "operations_for_99_in_100": 286722 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 286737, + "operations_for_99_in_100": 286737 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 286756, + "operations_for_99_in_100": 286756 + } + ] + }, + { + "family": "sparse-glass", + "n": 24, + "pairs": 72, + "sweeps_per_start": 40, + "target": "best known", + "solvers": [ + { + "hits": 100, + "runs": 100, + "operations_per_run": 53760, + "operations_for_99_in_100": 53760 + }, + { + "hits": 68, + "runs": 100, + "operations_per_run": 53760, + "operations_for_99_in_100": 217277 + }, + { + "hits": 0, + "runs": 100, + "operations_per_run": 53760, + "operations_for_99_in_100": null + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 53762, + "operations_for_99_in_100": 53762 + }, + { + "hits": 97, + "runs": 100, + "operations_per_run": 53788, + "operations_for_99_in_100": 70640 + }, + { + "hits": 90, + "runs": 100, + "operations_per_run": 53817, + "operations_for_99_in_100": 107634 + } + ] + }, + { + "family": "sparse-glass", + "n": 24, + "pairs": 72, + "sweeps_per_start": 320, + "target": "best known", + "solvers": [ + { + "hits": 100, + "runs": 100, + "operations_per_run": 430080, + "operations_for_99_in_100": 430080 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 430080, + "operations_for_99_in_100": 430080 + }, + { + "hits": 0, + "runs": 100, + "operations_per_run": 430080, + "operations_for_99_in_100": null + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 430082, + "operations_for_99_in_100": 430082 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 430107, + "operations_for_99_in_100": 430107 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 430128, + "operations_for_99_in_100": 430128 + } + ] + }, + { + "family": "sparse-glass", + "n": 32, + "pairs": 96, + "sweeps_per_start": 40, + "target": "best known", + "solvers": [ + { + "hits": 100, + "runs": 100, + "operations_per_run": 71680, + "operations_for_99_in_100": 71680 + }, + { + "hits": 74, + "runs": 100, + "operations_per_run": 71680, + "operations_for_99_in_100": 245048 + }, + { + "hits": 3, + "runs": 100, + "operations_per_run": 71680, + "operations_for_99_in_100": 10837399 + }, + { + "hits": 99, + "runs": 100, + "operations_per_run": 71682, + "operations_for_99_in_100": 71682 + }, + { + "hits": 98, + "runs": 100, + "operations_per_run": 71716, + "operations_for_99_in_100": 84423 + }, + { + "hits": 96, + "runs": 100, + "operations_per_run": 71754, + "operations_for_99_in_100": 102657 + } + ] + }, + { + "family": "sparse-glass", + "n": 32, + "pairs": 96, + "sweeps_per_start": 320, + "target": "best known", + "solvers": [ + { + "hits": 100, + "runs": 100, + "operations_per_run": 573440, + "operations_for_99_in_100": 573440 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 573440, + "operations_for_99_in_100": 573440 + }, + { + "hits": 3, + "runs": 100, + "operations_per_run": 573440, + "operations_for_99_in_100": 86699195 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 573442, + "operations_for_99_in_100": 573442 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 573473, + "operations_for_99_in_100": 573473 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 573513, + "operations_for_99_in_100": 573513 + } + ] + }, + { + "family": "sparse-glass", + "n": 48, + "pairs": 144, + "sweeps_per_start": 40, + "target": "best known", + "solvers": [ + { + "hits": 77, + "runs": 100, + "operations_per_run": 107520, + "operations_for_99_in_100": 336909 + }, + { + "hits": 28, + "runs": 100, + "operations_per_run": 107520, + "operations_for_99_in_100": 1507280 + }, + { + "hits": 2, + "runs": 100, + "operations_per_run": 107520, + "operations_for_99_in_100": 24508987 + }, + { + "hits": 82, + "runs": 100, + "operations_per_run": 107522, + "operations_for_99_in_100": 288757 + }, + { + "hits": 58, + "runs": 100, + "operations_per_run": 107576, + "operations_for_99_in_100": 571075 + }, + { + "hits": 38, + "runs": 100, + "operations_per_run": 107616, + "operations_for_99_in_100": 1036727 + } + ] + }, + { + "family": "sparse-glass", + "n": 48, + "pairs": 144, + "sweeps_per_start": 320, + "target": "best known", + "solvers": [ + { + "hits": 85, + "runs": 100, + "operations_per_run": 860160, + "operations_for_99_in_100": 2087998 + }, + { + "hits": 83, + "runs": 100, + "operations_per_run": 860160, + "operations_for_99_in_100": 2235485 + }, + { + "hits": 2, + "runs": 100, + "operations_per_run": 860160, + "operations_for_99_in_100": 196071898 + }, + { + "hits": 87, + "runs": 100, + "operations_per_run": 860162, + "operations_for_99_in_100": 1941551 + }, + { + "hits": 79, + "runs": 100, + "operations_per_run": 860212, + "operations_for_99_in_100": 2538320 + }, + { + "hits": 91, + "runs": 100, + "operations_per_run": 860260, + "operations_for_99_in_100": 1645239 + } + ] + }, + { + "family": "sparse-glass", + "n": 64, + "pairs": 192, + "sweeps_per_start": 40, + "target": "best known", + "solvers": [ + { + "hits": 68, + "runs": 100, + "operations_per_run": 143360, + "operations_for_99_in_100": 579407 + }, + { + "hits": 2, + "runs": 100, + "operations_per_run": 143360, + "operations_for_99_in_100": 32678649 + }, + { + "hits": 0, + "runs": 100, + "operations_per_run": 143360, + "operations_for_99_in_100": null + }, + { + "hits": 90, + "runs": 100, + "operations_per_run": 143362, + "operations_for_99_in_100": 286725 + }, + { + "hits": 28, + "runs": 100, + "operations_per_run": 143424, + "operations_for_99_in_100": 2010616 + }, + { + "hits": 12, + "runs": 100, + "operations_per_run": 143497, + "operations_for_99_in_100": 5169468 + } + ] + }, + { + "family": "sparse-glass", + "n": 64, + "pairs": 192, + "sweeps_per_start": 320, + "target": "best known", + "solvers": [ + { + "hits": 94, + "runs": 100, + "operations_per_run": 1146880, + "operations_for_99_in_100": 1877286 + }, + { + "hits": 87, + "runs": 100, + "operations_per_run": 1146880, + "operations_for_99_in_100": 2588728 + }, + { + "hits": 0, + "runs": 100, + "operations_per_run": 1146880, + "operations_for_99_in_100": null + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 1146882, + "operations_for_99_in_100": 1146882 + }, + { + "hits": 65, + "runs": 100, + "operations_per_run": 1146952, + "operations_for_99_in_100": 5031245 + }, + { + "hits": 42, + "runs": 100, + "operations_per_run": 1146994, + "operations_for_99_in_100": 9696787 + } + ] + }, + { + "family": "sparse-glass", + "n": 96, + "pairs": 288, + "sweeps_per_start": 40, + "target": "best known", + "solvers": [ + { + "hits": 37, + "runs": 100, + "operations_per_run": 215040, + "operations_for_99_in_100": 2143332 + }, + { + "hits": 0, + "runs": 100, + "operations_per_run": 215040, + "operations_for_99_in_100": null + }, + { + "hits": 0, + "runs": 100, + "operations_per_run": 215040, + "operations_for_99_in_100": null + }, + { + "hits": 76, + "runs": 100, + "operations_per_run": 215042, + "operations_for_99_in_100": 693922 + }, + { + "hits": 4, + "runs": 100, + "operations_per_run": 215144, + "operations_for_99_in_100": 24270642 + }, + { + "hits": 1, + "runs": 100, + "operations_per_run": 215220, + "operations_for_99_in_100": 98616355 + } + ] + }, + { + "family": "sparse-glass", + "n": 96, + "pairs": 288, + "sweeps_per_start": 320, + "target": "best known", + "solvers": [ + { + "hits": 87, + "runs": 100, + "operations_per_run": 1720320, + "operations_for_99_in_100": 3883092 + }, + { + "hits": 69, + "runs": 100, + "operations_per_run": 1720320, + "operations_for_99_in_100": 6764413 + }, + { + "hits": 0, + "runs": 100, + "operations_per_run": 1720320, + "operations_for_99_in_100": null + }, + { + "hits": 93, + "runs": 100, + "operations_per_run": 1720322, + "operations_for_99_in_100": 2979166 + }, + { + "hits": 9, + "runs": 100, + "operations_per_run": 1720423, + "operations_for_99_in_100": 84007918 + }, + { + "hits": 7, + "runs": 100, + "operations_per_run": 1720500, + "operations_for_99_in_100": 109179042 + } + ] + }, + { + "family": "sparse-glass", + "n": 128, + "pairs": 384, + "sweeps_per_start": 40, + "target": "best known", + "solvers": [ + { + "hits": 36, + "runs": 100, + "operations_per_run": 286720, + "operations_for_99_in_100": 2958621 + }, + { + "hits": 0, + "runs": 100, + "operations_per_run": 286720, + "operations_for_99_in_100": null + }, + { + "hits": 0, + "runs": 100, + "operations_per_run": 286720, + "operations_for_99_in_100": null + }, + { + "hits": 71, + "runs": 100, + "operations_per_run": 286723, + "operations_for_99_in_100": 1066673 + }, + { + "hits": 5, + "runs": 100, + "operations_per_run": 286852, + "operations_for_99_in_100": 25753943 + }, + { + "hits": 0, + "runs": 100, + "operations_per_run": 286931, + "operations_for_99_in_100": null + } + ] + }, + { + "family": "sparse-glass", + "n": 128, + "pairs": 384, + "sweeps_per_start": 320, + "target": "best known", + "solvers": [ + { + "hits": 70, + "runs": 100, + "operations_per_run": 2293760, + "operations_for_99_in_100": 8773582 + }, + { + "hits": 45, + "runs": 100, + "operations_per_run": 2293760, + "operations_for_99_in_100": 17668955 + }, + { + "hits": 0, + "runs": 100, + "operations_per_run": 2293760, + "operations_for_99_in_100": null + }, + { + "hits": 84, + "runs": 100, + "operations_per_run": 2293762, + "operations_for_99_in_100": 5764091 + }, + { + "hits": 14, + "runs": 100, + "operations_per_run": 2293875, + "operations_for_99_in_100": 70040338 + }, + { + "hits": 0, + "runs": 100, + "operations_per_run": 2294008, + "operations_for_99_in_100": null + } + ] + }, + { + "family": "dense-glass", + "n": 8, + "pairs": 28, + "sweeps_per_start": 40, + "target": "exact", + "solvers": [ + { + "hits": 100, + "runs": 100, + "operations_per_run": 20480, + "operations_for_99_in_100": 20480 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 20480, + "operations_for_99_in_100": 20480 + }, + { + "hits": 88, + "runs": 100, + "operations_per_run": 20480, + "operations_for_99_in_100": 44482 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 20482, + "operations_for_99_in_100": 20482 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 20484, + "operations_for_99_in_100": 20484 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 20506, + "operations_for_99_in_100": 20506 + } + ] + }, + { + "family": "dense-glass", + "n": 8, + "pairs": 28, + "sweeps_per_start": 320, + "target": "exact", + "solvers": [ + { + "hits": 100, + "runs": 100, + "operations_per_run": 163840, + "operations_for_99_in_100": 163840 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 163840, + "operations_for_99_in_100": 163840 + }, + { + "hits": 88, + "runs": 100, + "operations_per_run": 163840, + "operations_for_99_in_100": 355857 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 163842, + "operations_for_99_in_100": 163842 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 163848, + "operations_for_99_in_100": 163848 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 163862, + "operations_for_99_in_100": 163862 + } + ] + }, + { + "family": "dense-glass", + "n": 12, + "pairs": 66, + "sweeps_per_start": 40, + "target": "exact", + "solvers": [ + { + "hits": 100, + "runs": 100, + "operations_per_run": 46080, + "operations_for_99_in_100": 46080 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 46080, + "operations_for_99_in_100": 46080 + }, + { + "hits": 78, + "runs": 100, + "operations_per_run": 46080, + "operations_for_99_in_100": 140150 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 46083, + "operations_for_99_in_100": 46083 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 46086, + "operations_for_99_in_100": 46086 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 46126, + "operations_for_99_in_100": 46126 + } + ] + }, + { + "family": "dense-glass", + "n": 12, + "pairs": 66, + "sweeps_per_start": 320, + "target": "exact", + "solvers": [ + { + "hits": 100, + "runs": 100, + "operations_per_run": 368640, + "operations_for_99_in_100": 368640 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 368640, + "operations_for_99_in_100": 368640 + }, + { + "hits": 78, + "runs": 100, + "operations_per_run": 368640, + "operations_for_99_in_100": 1121206 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 368643, + "operations_for_99_in_100": 368643 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 368660, + "operations_for_99_in_100": 368660 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 368687, + "operations_for_99_in_100": 368687 + } + ] + }, + { + "family": "dense-glass", + "n": 16, + "pairs": 120, + "sweeps_per_start": 40, + "target": "exact", + "solvers": [ + { + "hits": 100, + "runs": 100, + "operations_per_run": 81920, + "operations_for_99_in_100": 81920 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 81920, + "operations_for_99_in_100": 81920 + }, + { + "hits": 39, + "runs": 100, + "operations_per_run": 81920, + "operations_for_99_in_100": 763217 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 81927, + "operations_for_99_in_100": 81927 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 81947, + "operations_for_99_in_100": 81947 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 82007, + "operations_for_99_in_100": 82007 + } + ] + }, + { + "family": "dense-glass", + "n": 16, + "pairs": 120, + "sweeps_per_start": 320, + "target": "exact", + "solvers": [ + { + "hits": 100, + "runs": 100, + "operations_per_run": 655360, + "operations_for_99_in_100": 655360 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 655360, + "operations_for_99_in_100": 655360 + }, + { + "hits": 39, + "runs": 100, + "operations_per_run": 655360, + "operations_for_99_in_100": 6105739 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 655367, + "operations_for_99_in_100": 655367 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 655385, + "operations_for_99_in_100": 655385 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 655447, + "operations_for_99_in_100": 655447 + } + ] + }, + { + "family": "dense-glass", + "n": 24, + "pairs": 276, + "sweeps_per_start": 40, + "target": "best known", + "solvers": [ + { + "hits": 100, + "runs": 100, + "operations_per_run": 184320, + "operations_for_99_in_100": 184320 + }, + { + "hits": 95, + "runs": 100, + "operations_per_run": 184320, + "operations_for_99_in_100": 283344 + }, + { + "hits": 25, + "runs": 100, + "operations_per_run": 184320, + "operations_for_99_in_100": 2950566 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 184332, + "operations_for_99_in_100": 184332 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 184359, + "operations_for_99_in_100": 184359 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 184502, + "operations_for_99_in_100": 184502 + } + ] + }, + { + "family": "dense-glass", + "n": 24, + "pairs": 276, + "sweeps_per_start": 320, + "target": "best known", + "solvers": [ + { + "hits": 100, + "runs": 100, + "operations_per_run": 1474560, + "operations_for_99_in_100": 1474560 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 1474560, + "operations_for_99_in_100": 1474560 + }, + { + "hits": 25, + "runs": 100, + "operations_per_run": 1474560, + "operations_for_99_in_100": 23604528 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 1474569, + "operations_for_99_in_100": 1474569 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 1474593, + "operations_for_99_in_100": 1474593 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 1474770, + "operations_for_99_in_100": 1474770 + } + ] + }, + { + "family": "dense-glass", + "n": 32, + "pairs": 496, + "sweeps_per_start": 40, + "target": "best known", + "solvers": [ + { + "hits": 94, + "runs": 100, + "operations_per_run": 327680, + "operations_for_99_in_100": 536367 + }, + { + "hits": 53, + "runs": 100, + "operations_per_run": 327680, + "operations_for_99_in_100": 1998645 + }, + { + "hits": 4, + "runs": 100, + "operations_per_run": 327680, + "operations_for_99_in_100": 36965909 + }, + { + "hits": 99, + "runs": 100, + "operations_per_run": 327697, + "operations_for_99_in_100": 327697 + }, + { + "hits": 95, + "runs": 100, + "operations_per_run": 327736, + "operations_for_99_in_100": 503811 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 328039, + "operations_for_99_in_100": 328039 + } + ] + }, + { + "family": "dense-glass", + "n": 32, + "pairs": 496, + "sweeps_per_start": 320, + "target": "best known", + "solvers": [ + { + "hits": 98, + "runs": 100, + "operations_per_run": 2621440, + "operations_for_99_in_100": 3085916 + }, + { + "hits": 94, + "runs": 100, + "operations_per_run": 2621440, + "operations_for_99_in_100": 4290940 + }, + { + "hits": 4, + "runs": 100, + "operations_per_run": 2621440, + "operations_for_99_in_100": 295727278 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 2621451, + "operations_for_99_in_100": 2621451 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 2621481, + "operations_for_99_in_100": 2621481 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 2621820, + "operations_for_99_in_100": 2621820 + } + ] + }, + { + "family": "dense-glass", + "n": 48, + "pairs": 1128, + "sweeps_per_start": 40, + "target": "best known", + "solvers": [ + { + "hits": 94, + "runs": 100, + "operations_per_run": 737280, + "operations_for_99_in_100": 1206826 + }, + { + "hits": 22, + "runs": 100, + "operations_per_run": 737280, + "operations_for_99_in_100": 13665303 + }, + { + "hits": 3, + "runs": 100, + "operations_per_run": 737280, + "operations_for_99_in_100": 111470394 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 737340, + "operations_for_99_in_100": 737340 + }, + { + "hits": 92, + "runs": 100, + "operations_per_run": 737321, + "operations_for_99_in_100": 1344361 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 738037, + "operations_for_99_in_100": 738037 + } + ] + }, + { + "family": "dense-glass", + "n": 48, + "pairs": 1128, + "sweeps_per_start": 320, + "target": "best known", + "solvers": [ + { + "hits": 100, + "runs": 100, + "operations_per_run": 5898240, + "operations_for_99_in_100": 5898240 + }, + { + "hits": 96, + "runs": 100, + "operations_per_run": 5898240, + "operations_for_99_in_100": 8438473 + }, + { + "hits": 3, + "runs": 100, + "operations_per_run": 5898240, + "operations_for_99_in_100": 891763156 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 5898264, + "operations_for_99_in_100": 5898264 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 5898330, + "operations_for_99_in_100": 5898330 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 5899028, + "operations_for_99_in_100": 5899028 + } + ] + }, + { + "family": "dense-glass", + "n": 64, + "pairs": 2016, + "sweeps_per_start": 40, + "target": "best known", + "solvers": [ + { + "hits": 81, + "runs": 100, + "operations_per_run": 1310720, + "operations_for_99_in_100": 3634597 + }, + { + "hits": 4, + "runs": 100, + "operations_per_run": 1310720, + "operations_for_99_in_100": 147863639 + }, + { + "hits": 0, + "runs": 100, + "operations_per_run": 1310720, + "operations_for_99_in_100": null + }, + { + "hits": 97, + "runs": 100, + "operations_per_run": 1310864, + "operations_for_99_in_100": 1721561 + }, + { + "hits": 54, + "runs": 100, + "operations_per_run": 1310756, + "operations_for_99_in_100": 7773386 + }, + { + "hits": 91, + "runs": 100, + "operations_per_run": 1312018, + "operations_for_99_in_100": 2509220 + } + ] + }, + { + "family": "dense-glass", + "n": 64, + "pairs": 2016, + "sweeps_per_start": 320, + "target": "best known", + "solvers": [ + { + "hits": 96, + "runs": 100, + "operations_per_run": 10485760, + "operations_for_99_in_100": 15001731 + }, + { + "hits": 90, + "runs": 100, + "operations_per_run": 10485760, + "operations_for_99_in_100": 20971520 + }, + { + "hits": 0, + "runs": 100, + "operations_per_run": 10485760, + "operations_for_99_in_100": null + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 10485791, + "operations_for_99_in_100": 10485791 + }, + { + "hits": 87, + "runs": 100, + "operations_per_run": 10485895, + "operations_for_99_in_100": 23668679 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 10487106, + "operations_for_99_in_100": 10487106 + } + ] + }, + { + "family": "selection", + "n": 8, + "pairs": 10, + "sweeps_per_start": 40, + "target": "exact", + "solvers": [ + { + "hits": 100, + "runs": 100, + "operations_per_run": 8960, + "operations_for_99_in_100": 8960 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 8960, + "operations_for_99_in_100": 8960 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 8960, + "operations_for_99_in_100": 8960 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 8960, + "operations_for_99_in_100": 8960 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 8967, + "operations_for_99_in_100": 8967 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 8969, + "operations_for_99_in_100": 8969 + } + ] + }, + { + "family": "selection", + "n": 8, + "pairs": 10, + "sweeps_per_start": 320, + "target": "exact", + "solvers": [ + { + "hits": 100, + "runs": 100, + "operations_per_run": 71680, + "operations_for_99_in_100": 71680 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 71680, + "operations_for_99_in_100": 71680 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 71680, + "operations_for_99_in_100": 71680 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 71680, + "operations_for_99_in_100": 71680 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 71686, + "operations_for_99_in_100": 71686 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 71687, + "operations_for_99_in_100": 71687 + } + ] + }, + { + "family": "selection", + "n": 12, + "pairs": 10, + "sweeps_per_start": 40, + "target": "exact", + "solvers": [ + { + "hits": 100, + "runs": 100, + "operations_per_run": 10240, + "operations_for_99_in_100": 10240 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 10240, + "operations_for_99_in_100": 10240 + }, + { + "hits": 99, + "runs": 100, + "operations_per_run": 10240, + "operations_for_99_in_100": 10240 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 10240, + "operations_for_99_in_100": 10240 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 10250, + "operations_for_99_in_100": 10250 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 10251, + "operations_for_99_in_100": 10251 + } + ] + }, + { + "family": "selection", + "n": 12, + "pairs": 10, + "sweeps_per_start": 320, + "target": "exact", + "solvers": [ + { + "hits": 100, + "runs": 100, + "operations_per_run": 81920, + "operations_for_99_in_100": 81920 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 81920, + "operations_for_99_in_100": 81920 + }, + { + "hits": 99, + "runs": 100, + "operations_per_run": 81920, + "operations_for_99_in_100": 81920 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 81920, + "operations_for_99_in_100": 81920 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 81930, + "operations_for_99_in_100": 81930 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 81932, + "operations_for_99_in_100": 81932 + } + ] + }, + { + "family": "selection", + "n": 16, + "pairs": 10, + "sweeps_per_start": 40, + "target": "exact", + "solvers": [ + { + "hits": 100, + "runs": 100, + "operations_per_run": 11520, + "operations_for_99_in_100": 11520 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 11520, + "operations_for_99_in_100": 11520 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 11520, + "operations_for_99_in_100": 11520 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 11520, + "operations_for_99_in_100": 11520 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 11536, + "operations_for_99_in_100": 11536 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 11535, + "operations_for_99_in_100": 11535 + } + ] + }, + { + "family": "selection", + "n": 16, + "pairs": 10, + "sweeps_per_start": 320, + "target": "exact", + "solvers": [ + { + "hits": 100, + "runs": 100, + "operations_per_run": 92160, + "operations_for_99_in_100": 92160 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 92160, + "operations_for_99_in_100": 92160 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 92160, + "operations_for_99_in_100": 92160 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 92160, + "operations_for_99_in_100": 92160 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 92175, + "operations_for_99_in_100": 92175 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 92175, + "operations_for_99_in_100": 92175 + } + ] + }, + { + "family": "selection", + "n": 24, + "pairs": 15, + "sweeps_per_start": 40, + "target": "planted", + "solvers": [ + { + "hits": 100, + "runs": 100, + "operations_per_run": 17280, + "operations_for_99_in_100": 17280 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 17280, + "operations_for_99_in_100": 17280 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 17280, + "operations_for_99_in_100": 17280 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 17280, + "operations_for_99_in_100": 17280 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 17309, + "operations_for_99_in_100": 17309 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 17305, + "operations_for_99_in_100": 17305 + } + ] + }, + { + "family": "selection", + "n": 24, + "pairs": 15, + "sweeps_per_start": 320, + "target": "planted", + "solvers": [ + { + "hits": 100, + "runs": 100, + "operations_per_run": 138240, + "operations_for_99_in_100": 138240 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 138240, + "operations_for_99_in_100": 138240 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 138240, + "operations_for_99_in_100": 138240 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 138240, + "operations_for_99_in_100": 138240 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 138265, + "operations_for_99_in_100": 138265 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 138264, + "operations_for_99_in_100": 138264 + } + ] + }, + { + "family": "selection", + "n": 32, + "pairs": 28, + "sweeps_per_start": 40, + "target": "planted", + "solvers": [ + { + "hits": 100, + "runs": 100, + "operations_per_run": 28160, + "operations_for_99_in_100": 28160 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 28160, + "operations_for_99_in_100": 28160 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 28160, + "operations_for_99_in_100": 28160 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 28161, + "operations_for_99_in_100": 28161 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 28185, + "operations_for_99_in_100": 28185 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 28195, + "operations_for_99_in_100": 28195 + } + ] + }, + { + "family": "selection", + "n": 32, + "pairs": 28, + "sweeps_per_start": 320, + "target": "planted", + "solvers": [ + { + "hits": 100, + "runs": 100, + "operations_per_run": 225280, + "operations_for_99_in_100": 225280 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 225280, + "operations_for_99_in_100": 225280 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 225280, + "operations_for_99_in_100": 225280 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 225280, + "operations_for_99_in_100": 225280 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 225311, + "operations_for_99_in_100": 225311 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 225314, + "operations_for_99_in_100": 225314 + } + ] + }, + { + "family": "selection", + "n": 48, + "pairs": 66, + "sweeps_per_start": 40, + "target": "planted", + "solvers": [ + { + "hits": 100, + "runs": 100, + "operations_per_run": 57600, + "operations_for_99_in_100": 57600 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 57600, + "operations_for_99_in_100": 57600 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 57600, + "operations_for_99_in_100": 57600 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 57601, + "operations_for_99_in_100": 57601 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 57625, + "operations_for_99_in_100": 57625 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 57671, + "operations_for_99_in_100": 57671 + } + ] + }, + { + "family": "selection", + "n": 48, + "pairs": 66, + "sweeps_per_start": 320, + "target": "planted", + "solvers": [ + { + "hits": 100, + "runs": 100, + "operations_per_run": 460800, + "operations_for_99_in_100": 460800 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 460800, + "operations_for_99_in_100": 460800 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 460800, + "operations_for_99_in_100": 460800 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 460800, + "operations_for_99_in_100": 460800 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 460845, + "operations_for_99_in_100": 460845 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 460871, + "operations_for_99_in_100": 460871 + } + ] + }, + { + "family": "selection", + "n": 64, + "pairs": 120, + "sweeps_per_start": 40, + "target": "planted", + "solvers": [ + { + "hits": 100, + "runs": 100, + "operations_per_run": 97280, + "operations_for_99_in_100": 97280 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 97280, + "operations_for_99_in_100": 97280 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 97280, + "operations_for_99_in_100": 97280 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 97283, + "operations_for_99_in_100": 97283 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 97344, + "operations_for_99_in_100": 97344 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 97388, + "operations_for_99_in_100": 97388 + } + ] + }, + { + "family": "selection", + "n": 64, + "pairs": 120, + "sweeps_per_start": 320, + "target": "planted", + "solvers": [ + { + "hits": 100, + "runs": 100, + "operations_per_run": 778240, + "operations_for_99_in_100": 778240 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 778240, + "operations_for_99_in_100": 778240 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 778240, + "operations_for_99_in_100": 778240 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 778240, + "operations_for_99_in_100": 778240 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 778294, + "operations_for_99_in_100": 778294 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 778362, + "operations_for_99_in_100": 778362 + } + ] + }, + { + "family": "selection", + "n": 96, + "pairs": 276, + "sweeps_per_start": 40, + "target": "planted", + "solvers": [ + { + "hits": 100, + "runs": 100, + "operations_per_run": 207360, + "operations_for_99_in_100": 207360 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 207360, + "operations_for_99_in_100": 207360 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 207360, + "operations_for_99_in_100": 207360 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 207404, + "operations_for_99_in_100": 207404 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 207475, + "operations_for_99_in_100": 207475 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 207611, + "operations_for_99_in_100": 207611 + } + ] + }, + { + "family": "selection", + "n": 96, + "pairs": 276, + "sweeps_per_start": 320, + "target": "planted", + "solvers": [ + { + "hits": 100, + "runs": 100, + "operations_per_run": 1658880, + "operations_for_99_in_100": 1658880 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 1658880, + "operations_for_99_in_100": 1658880 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 1658880, + "operations_for_99_in_100": 1658880 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 1658880, + "operations_for_99_in_100": 1658880 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 1658968, + "operations_for_99_in_100": 1658968 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 1659129, + "operations_for_99_in_100": 1659129 + } + ] + }, + { + "family": "selection", + "n": 128, + "pairs": 496, + "sweeps_per_start": 40, + "target": "planted", + "solvers": [ + { + "hits": 100, + "runs": 100, + "operations_per_run": 358400, + "operations_for_99_in_100": 358400 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 358400, + "operations_for_99_in_100": 358400 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 358400, + "operations_for_99_in_100": 358400 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 358400, + "operations_for_99_in_100": 358400 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 358502, + "operations_for_99_in_100": 358502 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 358865, + "operations_for_99_in_100": 358865 + } + ] + }, + { + "family": "selection", + "n": 128, + "pairs": 496, + "sweeps_per_start": 320, + "target": "planted", + "solvers": [ + { + "hits": 100, + "runs": 100, + "operations_per_run": 2867200, + "operations_for_99_in_100": 2867200 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 2867200, + "operations_for_99_in_100": 2867200 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 2867200, + "operations_for_99_in_100": 2867200 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 2867200, + "operations_for_99_in_100": 2867200 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 2867325, + "operations_for_99_in_100": 2867325 + }, + { + "hits": 100, + "runs": 100, + "operations_per_run": 2867566, + "operations_for_99_in_100": 2867566 + } + ] + } + ], + "tally_default_start": { + "rows": 68, + "emulator_more": 2, + "tied": 45, + "classical_more": 21 + }, + "tally_hot_start": { + "rows": 68, + "emulator_more": 0, + "tied": 38, + "classical_more": 30 + }, + "start_temperature": [ + { + "family": "clusters", + "n": 32, + "default": 2.0, + "hot": 9.0, + "target": -16, + "runs": 40, + "rows": [ + { + "temperature": 0.5, + "is_default": false, + "is_hot": false, + "reached_target": 40, + "mean_energy": -16.0 + }, + { + "temperature": 1.0, + "is_default": false, + "is_hot": false, + "reached_target": 40, + "mean_energy": -16.0 + }, + { + "temperature": 1.5, + "is_default": false, + "is_hot": false, + "reached_target": 40, + "mean_energy": -16.0 + }, + { + "temperature": 2.0, + "is_default": true, + "is_hot": false, + "reached_target": 40, + "mean_energy": -16.0 + }, + { + "temperature": 3.0, + "is_default": false, + "is_hot": false, + "reached_target": 40, + "mean_energy": -16.0 + }, + { + "temperature": 4.0, + "is_default": false, + "is_hot": false, + "reached_target": 40, + "mean_energy": -16.0 + }, + { + "temperature": 6.0, + "is_default": false, + "is_hot": false, + "reached_target": 37, + "mean_energy": -15.925 + }, + { + "temperature": 8.0, + "is_default": false, + "is_hot": false, + "reached_target": 29, + "mean_energy": -15.725 + }, + { + "temperature": 9.0, + "is_default": false, + "is_hot": true, + "reached_target": 28, + "mean_energy": -15.65 + } + ] + }, + { + "family": "clusters", + "n": 64, + "default": 2.0, + "hot": 8.0, + "target": -26, + "runs": 40, + "rows": [ + { + "temperature": 0.5, + "is_default": false, + "is_hot": false, + "reached_target": 33, + "mean_energy": -25.8 + }, + { + "temperature": 1.0, + "is_default": false, + "is_hot": false, + "reached_target": 40, + "mean_energy": -26.0 + }, + { + "temperature": 1.5, + "is_default": false, + "is_hot": false, + "reached_target": 35, + "mean_energy": -25.875 + }, + { + "temperature": 2.0, + "is_default": true, + "is_hot": false, + "reached_target": 28, + "mean_energy": -25.675 + }, + { + "temperature": 3.0, + "is_default": false, + "is_hot": false, + "reached_target": 22, + "mean_energy": -25.4 + }, + { + "temperature": 4.0, + "is_default": false, + "is_hot": false, + "reached_target": 12, + "mean_energy": -25.15 + }, + { + "temperature": 6.0, + "is_default": false, + "is_hot": false, + "reached_target": 6, + "mean_energy": -24.475 + }, + { + "temperature": 8.0, + "is_default": false, + "is_hot": true, + "reached_target": 1, + "mean_energy": -23.35 + } + ] + }, + { + "family": "sparse-glass", + "n": 32, + "default": 2.449489742783178, + "hot": 13.0, + "target": -19, + "runs": 40, + "rows": [ + { + "temperature": 0.5, + "is_default": false, + "is_hot": false, + "reached_target": 40, + "mean_energy": -19.0 + }, + { + "temperature": 1.0, + "is_default": false, + "is_hot": false, + "reached_target": 38, + "mean_energy": -18.95 + }, + { + "temperature": 1.5, + "is_default": false, + "is_hot": false, + "reached_target": 36, + "mean_energy": -18.9 + }, + { + "temperature": 2.0, + "is_default": false, + "is_hot": false, + "reached_target": 37, + "mean_energy": -18.925 + }, + { + "temperature": 2.449489742783178, + "is_default": true, + "is_hot": false, + "reached_target": 33, + "mean_energy": -18.825 + }, + { + "temperature": 3.0, + "is_default": false, + "is_hot": false, + "reached_target": 37, + "mean_energy": -18.925 + }, + { + "temperature": 4.0, + "is_default": false, + "is_hot": false, + "reached_target": 36, + "mean_energy": -18.9 + }, + { + "temperature": 6.0, + "is_default": false, + "is_hot": false, + "reached_target": 26, + "mean_energy": -18.625 + }, + { + "temperature": 8.0, + "is_default": false, + "is_hot": false, + "reached_target": 25, + "mean_energy": -18.55 + }, + { + "temperature": 13.0, + "is_default": false, + "is_hot": true, + "reached_target": 15, + "mean_energy": -18.175 + } + ] + }, + { + "family": "sparse-glass", + "n": 64, + "default": 2.449489742783178, + "hot": 12.0, + "target": -51, + "runs": 40, + "rows": [ + { + "temperature": 0.5, + "is_default": false, + "is_hot": false, + "reached_target": 39, + "mean_energy": -50.975 + }, + { + "temperature": 1.0, + "is_default": false, + "is_hot": false, + "reached_target": 40, + "mean_energy": -51.0 + }, + { + "temperature": 1.5, + "is_default": false, + "is_hot": false, + "reached_target": 40, + "mean_energy": -51.0 + }, + { + "temperature": 2.0, + "is_default": false, + "is_hot": false, + "reached_target": 39, + "mean_energy": -50.975 + }, + { + "temperature": 2.449489742783178, + "is_default": true, + "is_hot": false, + "reached_target": 40, + "mean_energy": -51.0 + }, + { + "temperature": 3.0, + "is_default": false, + "is_hot": false, + "reached_target": 40, + "mean_energy": -51.0 + }, + { + "temperature": 4.0, + "is_default": false, + "is_hot": false, + "reached_target": 34, + "mean_energy": -50.85 + }, + { + "temperature": 6.0, + "is_default": false, + "is_hot": false, + "reached_target": 26, + "mean_energy": -50.5 + }, + { + "temperature": 8.0, + "is_default": false, + "is_hot": false, + "reached_target": 15, + "mean_energy": -50.1 + }, + { + "temperature": 12.0, + "is_default": false, + "is_hot": true, + "reached_target": 4, + "mean_energy": -49.1 + } + ] + }, + { + "family": "dense-glass", + "n": 32, + "default": 5.5677643628300215, + "hot": 31.0, + "target": -128, + "runs": 40, + "rows": [ + { + "temperature": 0.5, + "is_default": false, + "is_hot": false, + "reached_target": 38, + "mean_energy": -127.9 + }, + { + "temperature": 1.0, + "is_default": false, + "is_hot": false, + "reached_target": 39, + "mean_energy": -127.95 + }, + { + "temperature": 1.5, + "is_default": false, + "is_hot": false, + "reached_target": 38, + "mean_energy": -127.9 + }, + { + "temperature": 2.0, + "is_default": false, + "is_hot": false, + "reached_target": 39, + "mean_energy": -127.95 + }, + { + "temperature": 3.0, + "is_default": false, + "is_hot": false, + "reached_target": 40, + "mean_energy": -128.0 + }, + { + "temperature": 4.0, + "is_default": false, + "is_hot": false, + "reached_target": 40, + "mean_energy": -128.0 + }, + { + "temperature": 5.5677643628300215, + "is_default": true, + "is_hot": false, + "reached_target": 39, + "mean_energy": -127.95 + }, + { + "temperature": 6.0, + "is_default": false, + "is_hot": false, + "reached_target": 39, + "mean_energy": -127.95 + }, + { + "temperature": 8.0, + "is_default": false, + "is_hot": false, + "reached_target": 40, + "mean_energy": -128.0 + }, + { + "temperature": 31.0, + "is_default": false, + "is_hot": true, + "reached_target": 23, + "mean_energy": -127.075 + } + ] + }, + { + "family": "dense-glass", + "n": 64, + "default": 7.937253933193772, + "hot": 63.0, + "target": -362, + "runs": 40, + "rows": [ + { + "temperature": 0.5, + "is_default": false, + "is_hot": false, + "reached_target": 29, + "mean_energy": -360.55 + }, + { + "temperature": 1.0, + "is_default": false, + "is_hot": false, + "reached_target": 40, + "mean_energy": -362.0 + }, + { + "temperature": 1.5, + "is_default": false, + "is_hot": false, + "reached_target": 40, + "mean_energy": -362.0 + }, + { + "temperature": 2.0, + "is_default": false, + "is_hot": false, + "reached_target": 40, + "mean_energy": -362.0 + }, + { + "temperature": 3.0, + "is_default": false, + "is_hot": false, + "reached_target": 40, + "mean_energy": -362.0 + }, + { + "temperature": 4.0, + "is_default": false, + "is_hot": false, + "reached_target": 39, + "mean_energy": -361.95 + }, + { + "temperature": 6.0, + "is_default": false, + "is_hot": false, + "reached_target": 40, + "mean_energy": -362.0 + }, + { + "temperature": 7.937253933193772, + "is_default": true, + "is_hot": false, + "reached_target": 38, + "mean_energy": -361.9 + }, + { + "temperature": 8.0, + "is_default": false, + "is_hot": false, + "reached_target": 40, + "mean_energy": -362.0 + }, + { + "temperature": 63.0, + "is_default": false, + "is_hot": true, + "reached_target": 0, + "mean_energy": -352.85 + } + ] + } + ] +} diff --git a/wai/quantum-conformance/landscape/table.md b/wai/quantum-conformance/landscape/table.md new file mode 100644 index 000000000..9614bb50f --- /dev/null +++ b/wai/quantum-conformance/landscape/table.md @@ -0,0 +1,85 @@ +Each cell is the runs that reached the target, out of the runs made, then the operations a run takes for a 99 in 100 chance of reaching it ("-" if no run did). Every solver in a row has the same budget of operations a run as the emulator's sampling mode. One operation is one coupling read, one cell updated or one move considered. + +| family | variables | pairs | sweeps a start | target | emulator, sampling, default start | emulator, sampling, hot start | emulator, deterministic | simulated annealing | tabu search | greedy descent | +|---|---|---|---|---|---|---|---|---|---|---| +| clusters | 8 | 16 | 40 | exact | 100/100, 12,800 | 100/100, 12,800 | 60/100, 64,331 | 100/100, 12,801 | 100/100, 12,809 | 100/100, 12,812 | +| clusters | 8 | 16 | 320 | exact | 100/100, 102,400 | 100/100, 102,400 | 60/100, 514,650 | 100/100, 102,401 | 100/100, 102,411 | 100/100, 102,413 | +| clusters | 12 | 24 | 40 | exact | 100/100, 19,200 | 97/100, 25,215 | 55/100, 110,730 | 100/100, 19,200 | 100/100, 19,212 | 100/100, 19,219 | +| clusters | 12 | 24 | 320 | exact | 100/100, 153,600 | 100/100, 153,600 | 55/100, 885,845 | 100/100, 153,601 | 100/100, 153,613 | 100/100, 153,620 | +| clusters | 16 | 32 | 40 | exact | 100/100, 25,600 | 97/100, 33,620 | 4/100, 2,887,961 | 100/100, 25,602 | 100/100, 25,618 | 100/100, 25,625 | +| clusters | 16 | 32 | 320 | exact | 100/100, 204,800 | 100/100, 204,800 | 4/100, 23,103,693 | 100/100, 204,801 | 100/100, 204,818 | 100/100, 204,829 | +| clusters | 24 | 48 | 40 | best known | 100/100, 38,400 | 94/100, 62,855 | 20/100, 792,487 | 100/100, 38,401 | 100/100, 38,423 | 99/100, 38,434 | +| clusters | 24 | 48 | 320 | best known | 100/100, 307,200 | 100/100, 307,200 | 20/100, 6,339,902 | 100/100, 307,202 | 100/100, 307,224 | 100/100, 307,237 | +| clusters | 32 | 64 | 40 | best known | 97/100, 67,241 | 73/100, 180,079 | 19/100, 1,118,942 | 99/100, 51,201 | 100/100, 51,232 | 82/100, 137,622 | +| clusters | 32 | 64 | 320 | best known | 100/100, 409,600 | 96/100, 586,005 | 19/100, 8,951,539 | 100/100, 409,601 | 100/100, 409,633 | 100/100, 409,650 | +| clusters | 48 | 96 | 40 | best known | 96/100, 109,875 | 46/100, 573,977 | 0/100, - | 98/100, 90,409 | 90/100, 153,679 | 62/100, 365,865 | +| clusters | 48 | 96 | 320 | best known | 99/100, 614,400 | 100/100, 614,400 | 0/100, - | 99/100, 614,401 | 100/100, 614,448 | 98/100, 723,347 | +| clusters | 64 | 128 | 40 | best known | 94/100, 167,614 | 35/100, 1,094,679 | 0/100, - | 97/100, 134,484 | 60/100, 514,966 | 22/100, 1,899,749 | +| clusters | 64 | 128 | 320 | best known | 100/100, 819,200 | 93/100, 1,418,648 | 0/100, - | 99/100, 819,201 | 94/100, 1,341,027 | 40/100, 7,386,135 | +| clusters | 96 | 192 | 40 | best known | 56/100, 861,596 | 4/100, 17,327,770 | 0/100, - | 86/100, 359,777 | 18/100, 3,566,727 | 3/100, 23,245,467 | +| clusters | 96 | 192 | 320 | best known | 98/100, 1,446,523 | 80/100, 3,516,030 | 0/100, - | 99/100, 1,228,802 | 68/100, 4,966,754 | 8/100, 67,874,513 | +| clusters | 128 | 256 | 40 | best known | 46/100, 1,530,607 | 0/100, - | 0/100, - | 79/100, 604,330 | 7/100, 13,005,283 | 0/100, - | +| clusters | 128 | 256 | 320 | best known | 91/100, 3,133,422 | 67/100, 6,805,596 | 0/100, - | 94/100, 2,681,840 | 26/100, 25,060,253 | 0/100, - | +| sparse-glass | 8 | 24 | 40 | exact | 100/100, 17,920 | 100/100, 17,920 | 21/100, 350,092 | 100/100, 17,922 | 100/100, 17,929 | 100/100, 17,937 | +| sparse-glass | 8 | 24 | 320 | exact | 100/100, 143,360 | 100/100, 143,360 | 21/100, 2,800,740 | 100/100, 143,362 | 100/100, 143,371 | 100/100, 143,379 | +| sparse-glass | 12 | 36 | 40 | exact | 100/100, 26,880 | 100/100, 26,880 | 27/100, 393,335 | 100/100, 26,882 | 100/100, 26,894 | 100/100, 26,905 | +| sparse-glass | 12 | 36 | 320 | exact | 100/100, 215,040 | 100/100, 215,040 | 27/100, 3,146,685 | 100/100, 215,042 | 100/100, 215,053 | 100/100, 215,066 | +| sparse-glass | 16 | 48 | 40 | exact | 100/100, 35,840 | 90/100, 71,680 | 27/100, 524,447 | 100/100, 35,842 | 100/100, 35,859 | 100/100, 35,872 | +| sparse-glass | 16 | 48 | 320 | exact | 100/100, 286,720 | 100/100, 286,720 | 27/100, 4,195,580 | 100/100, 286,722 | 100/100, 286,737 | 100/100, 286,756 | +| sparse-glass | 24 | 72 | 40 | best known | 100/100, 53,760 | 68/100, 217,277 | 0/100, - | 100/100, 53,762 | 97/100, 70,640 | 90/100, 107,634 | +| sparse-glass | 24 | 72 | 320 | best known | 100/100, 430,080 | 100/100, 430,080 | 0/100, - | 100/100, 430,082 | 100/100, 430,107 | 100/100, 430,128 | +| sparse-glass | 32 | 96 | 40 | best known | 100/100, 71,680 | 74/100, 245,048 | 3/100, 10,837,399 | 99/100, 71,682 | 98/100, 84,423 | 96/100, 102,657 | +| sparse-glass | 32 | 96 | 320 | best known | 100/100, 573,440 | 100/100, 573,440 | 3/100, 86,699,195 | 100/100, 573,442 | 100/100, 573,473 | 100/100, 573,513 | +| sparse-glass | 48 | 144 | 40 | best known | 77/100, 336,909 | 28/100, 1,507,280 | 2/100, 24,508,987 | 82/100, 288,757 | 58/100, 571,075 | 38/100, 1,036,727 | +| sparse-glass | 48 | 144 | 320 | best known | 85/100, 2,087,998 | 83/100, 2,235,485 | 2/100, 196,071,898 | 87/100, 1,941,551 | 79/100, 2,538,320 | 91/100, 1,645,239 | +| sparse-glass | 64 | 192 | 40 | best known | 68/100, 579,407 | 2/100, 32,678,649 | 0/100, - | 90/100, 286,725 | 28/100, 2,010,616 | 12/100, 5,169,468 | +| sparse-glass | 64 | 192 | 320 | best known | 94/100, 1,877,286 | 87/100, 2,588,728 | 0/100, - | 100/100, 1,146,882 | 65/100, 5,031,245 | 42/100, 9,696,787 | +| sparse-glass | 96 | 288 | 40 | best known | 37/100, 2,143,332 | 0/100, - | 0/100, - | 76/100, 693,922 | 4/100, 24,270,642 | 1/100, 98,616,355 | +| sparse-glass | 96 | 288 | 320 | best known | 87/100, 3,883,092 | 69/100, 6,764,413 | 0/100, - | 93/100, 2,979,166 | 9/100, 84,007,918 | 7/100, 109,179,042 | +| sparse-glass | 128 | 384 | 40 | best known | 36/100, 2,958,621 | 0/100, - | 0/100, - | 71/100, 1,066,673 | 5/100, 25,753,943 | 0/100, - | +| sparse-glass | 128 | 384 | 320 | best known | 70/100, 8,773,582 | 45/100, 17,668,955 | 0/100, - | 84/100, 5,764,091 | 14/100, 70,040,338 | 0/100, - | +| dense-glass | 8 | 28 | 40 | exact | 100/100, 20,480 | 100/100, 20,480 | 88/100, 44,482 | 100/100, 20,482 | 100/100, 20,484 | 100/100, 20,506 | +| dense-glass | 8 | 28 | 320 | exact | 100/100, 163,840 | 100/100, 163,840 | 88/100, 355,857 | 100/100, 163,842 | 100/100, 163,848 | 100/100, 163,862 | +| dense-glass | 12 | 66 | 40 | exact | 100/100, 46,080 | 100/100, 46,080 | 78/100, 140,150 | 100/100, 46,083 | 100/100, 46,086 | 100/100, 46,126 | +| dense-glass | 12 | 66 | 320 | exact | 100/100, 368,640 | 100/100, 368,640 | 78/100, 1,121,206 | 100/100, 368,643 | 100/100, 368,660 | 100/100, 368,687 | +| dense-glass | 16 | 120 | 40 | exact | 100/100, 81,920 | 100/100, 81,920 | 39/100, 763,217 | 100/100, 81,927 | 100/100, 81,947 | 100/100, 82,007 | +| dense-glass | 16 | 120 | 320 | exact | 100/100, 655,360 | 100/100, 655,360 | 39/100, 6,105,739 | 100/100, 655,367 | 100/100, 655,385 | 100/100, 655,447 | +| dense-glass | 24 | 276 | 40 | best known | 100/100, 184,320 | 95/100, 283,344 | 25/100, 2,950,566 | 100/100, 184,332 | 100/100, 184,359 | 100/100, 184,502 | +| dense-glass | 24 | 276 | 320 | best known | 100/100, 1,474,560 | 100/100, 1,474,560 | 25/100, 23,604,528 | 100/100, 1,474,569 | 100/100, 1,474,593 | 100/100, 1,474,770 | +| dense-glass | 32 | 496 | 40 | best known | 94/100, 536,367 | 53/100, 1,998,645 | 4/100, 36,965,909 | 99/100, 327,697 | 95/100, 503,811 | 100/100, 328,039 | +| dense-glass | 32 | 496 | 320 | best known | 98/100, 3,085,916 | 94/100, 4,290,940 | 4/100, 295,727,278 | 100/100, 2,621,451 | 100/100, 2,621,481 | 100/100, 2,621,820 | +| dense-glass | 48 | 1,128 | 40 | best known | 94/100, 1,206,826 | 22/100, 13,665,303 | 3/100, 111,470,394 | 100/100, 737,340 | 92/100, 1,344,361 | 100/100, 738,037 | +| dense-glass | 48 | 1,128 | 320 | best known | 100/100, 5,898,240 | 96/100, 8,438,473 | 3/100, 891,763,156 | 100/100, 5,898,264 | 100/100, 5,898,330 | 100/100, 5,899,028 | +| dense-glass | 64 | 2,016 | 40 | best known | 81/100, 3,634,597 | 4/100, 147,863,639 | 0/100, - | 97/100, 1,721,561 | 54/100, 7,773,386 | 91/100, 2,509,220 | +| dense-glass | 64 | 2,016 | 320 | best known | 96/100, 15,001,731 | 90/100, 20,971,520 | 0/100, - | 100/100, 10,485,791 | 87/100, 23,668,679 | 100/100, 10,487,106 | +| selection | 8 | 10 | 40 | exact | 100/100, 8,960 | 100/100, 8,960 | 100/100, 8,960 | 100/100, 8,960 | 100/100, 8,967 | 100/100, 8,969 | +| selection | 8 | 10 | 320 | exact | 100/100, 71,680 | 100/100, 71,680 | 100/100, 71,680 | 100/100, 71,680 | 100/100, 71,686 | 100/100, 71,687 | +| selection | 12 | 10 | 40 | exact | 100/100, 10,240 | 100/100, 10,240 | 99/100, 10,240 | 100/100, 10,240 | 100/100, 10,250 | 100/100, 10,251 | +| selection | 12 | 10 | 320 | exact | 100/100, 81,920 | 100/100, 81,920 | 99/100, 81,920 | 100/100, 81,920 | 100/100, 81,930 | 100/100, 81,932 | +| selection | 16 | 10 | 40 | exact | 100/100, 11,520 | 100/100, 11,520 | 100/100, 11,520 | 100/100, 11,520 | 100/100, 11,536 | 100/100, 11,535 | +| selection | 16 | 10 | 320 | exact | 100/100, 92,160 | 100/100, 92,160 | 100/100, 92,160 | 100/100, 92,160 | 100/100, 92,175 | 100/100, 92,175 | +| selection | 24 | 15 | 40 | planted | 100/100, 17,280 | 100/100, 17,280 | 100/100, 17,280 | 100/100, 17,280 | 100/100, 17,309 | 100/100, 17,305 | +| selection | 24 | 15 | 320 | planted | 100/100, 138,240 | 100/100, 138,240 | 100/100, 138,240 | 100/100, 138,240 | 100/100, 138,265 | 100/100, 138,264 | +| selection | 32 | 28 | 40 | planted | 100/100, 28,160 | 100/100, 28,160 | 100/100, 28,160 | 100/100, 28,161 | 100/100, 28,185 | 100/100, 28,195 | +| selection | 32 | 28 | 320 | planted | 100/100, 225,280 | 100/100, 225,280 | 100/100, 225,280 | 100/100, 225,280 | 100/100, 225,311 | 100/100, 225,314 | +| selection | 48 | 66 | 40 | planted | 100/100, 57,600 | 100/100, 57,600 | 100/100, 57,600 | 100/100, 57,601 | 100/100, 57,625 | 100/100, 57,671 | +| selection | 48 | 66 | 320 | planted | 100/100, 460,800 | 100/100, 460,800 | 100/100, 460,800 | 100/100, 460,800 | 100/100, 460,845 | 100/100, 460,871 | +| selection | 64 | 120 | 40 | planted | 100/100, 97,280 | 100/100, 97,280 | 100/100, 97,280 | 100/100, 97,283 | 100/100, 97,344 | 100/100, 97,388 | +| selection | 64 | 120 | 320 | planted | 100/100, 778,240 | 100/100, 778,240 | 100/100, 778,240 | 100/100, 778,240 | 100/100, 778,294 | 100/100, 778,362 | +| selection | 96 | 276 | 40 | planted | 100/100, 207,360 | 100/100, 207,360 | 100/100, 207,360 | 100/100, 207,404 | 100/100, 207,475 | 100/100, 207,611 | +| selection | 96 | 276 | 320 | planted | 100/100, 1,658,880 | 100/100, 1,658,880 | 100/100, 1,658,880 | 100/100, 1,658,880 | 100/100, 1,658,968 | 100/100, 1,659,129 | +| selection | 128 | 496 | 40 | planted | 100/100, 358,400 | 100/100, 358,400 | 100/100, 358,400 | 100/100, 358,400 | 100/100, 358,502 | 100/100, 358,865 | +| selection | 128 | 496 | 320 | planted | 100/100, 2,867,200 | 100/100, 2,867,200 | 100/100, 2,867,200 | 100/100, 2,867,200 | 100/100, 2,867,325 | 100/100, 2,867,566 | + +Of 68 rows, the emulator's sampling mode at the default start reached the target more often than every classical solver in 2, tied the best of them in 45, and was behind the best of them in 21. + +Of 68 rows, the emulator's sampling mode at the hot start, the largest row of couplings reached the target more often than every classical solver in 0, tied the best of them in 38, and was behind the best of them in 30. + +The default start is the square root of the mean couplings a variable has, and at least 1. On instances the rows do not use, 40 runs at each start, mean energy (and the runs that reached the lowest energy found): + +- clusters, 32 variables, target -16: 0.5: -16.00 (40); 1: -16.00 (40); 1.5: -16.00 (40); 2 default: -16.00 (40); 3: -16.00 (40); 4: -16.00 (40); 6: -15.93 (37); 8: -15.72 (29); 9 hot: -15.65 (28) +- clusters, 64 variables, target -26: 0.5: -25.80 (33); 1: -26.00 (40); 1.5: -25.88 (35); 2 default: -25.68 (28); 3: -25.40 (22); 4: -25.15 (12); 6: -24.48 (6); 8 hot: -23.35 (1) +- sparse-glass, 32 variables, target -19: 0.5: -19.00 (40); 1: -18.95 (38); 1.5: -18.90 (36); 2: -18.93 (37); 2.449 default: -18.82 (33); 3: -18.93 (37); 4: -18.90 (36); 6: -18.62 (26); 8: -18.55 (25); 13 hot: -18.18 (15) +- sparse-glass, 64 variables, target -51: 0.5: -50.98 (39); 1: -51.00 (40); 1.5: -51.00 (40); 2: -50.98 (39); 2.449 default: -51.00 (40); 3: -51.00 (40); 4: -50.85 (34); 6: -50.50 (26); 8: -50.10 (15); 12 hot: -49.10 (4) +- dense-glass, 32 variables, target -128: 0.5: -127.90 (38); 1: -127.95 (39); 1.5: -127.90 (38); 2: -127.95 (39); 3: -128.00 (40); 4: -128.00 (40); 5.568 default: -127.95 (39); 6: -127.95 (39); 8: -128.00 (40); 31 hot: -127.08 (23) +- dense-glass, 64 variables, target -362: 0.5: -360.55 (29); 1: -362.00 (40); 1.5: -362.00 (40); 2: -362.00 (40); 3: -362.00 (40); 4: -361.95 (39); 6: -362.00 (40); 7.937 default: -361.90 (38); 8: -362.00 (40); 63 hot: -352.85 (0) diff --git a/wai/quantum-conformance/landscape/timing.json b/wai/quantum-conformance/landscape/timing.json new file mode 100644 index 000000000..a620ad0a7 --- /dev/null +++ b/wai/quantum-conformance/landscape/timing.json @@ -0,0 +1 @@ +[{"family":"clusters","n":8,"sweeps_per_start":40,"seconds_per_run":[0.00009411201000000019,0.00009954927999999988,0.00002395882000000004,0.00014431886000000014,0.00008401066999999997,0.0000471383100000003]},{"family":"clusters","n":8,"sweeps_per_start":320,"seconds_per_run":[0.001072021750000001,0.0008521682000000014,0.0002606620800000046,0.0012924898800000023,0.0009626620100000008,0.00043796040999999683]},{"family":"clusters","n":12,"sweeps_per_start":40,"seconds_per_run":[0.00014741545000000357,0.00012770073999999966,0.00003667233000000491,0.00025270231000000477,0.0001214911099999938,0.00007685764999999733]},{"family":"clusters","n":12,"sweeps_per_start":320,"seconds_per_run":[0.0011240881300000017,0.0010131391200000094,0.0002806175300000002,0.0019331064600000103,0.00105511973999999,0.0006341880599999972]},{"family":"clusters","n":16,"sweeps_per_start":40,"seconds_per_run":[0.0001537077299999941,0.0001419308399999944,0.00004107686000000008,0.00029138198999999784,0.00021838304000000588,0.00012198239999999978]},{"family":"clusters","n":16,"sweeps_per_start":320,"seconds_per_run":[0.0013570488800000269,0.0011195028900000015,0.00032136062999998716,0.00244110552,0.001399233640000006,0.0009077436399999939]},{"family":"clusters","n":24,"sweeps_per_start":40,"seconds_per_run":[0.00021722619000001054,0.0001918901800000139,0.00006673392999995365,0.0004899866799999719,0.00024163520000001882,0.00016707886000000283]},{"family":"clusters","n":24,"sweeps_per_start":320,"seconds_per_run":[0.0015847814199999987,0.0014760975400000297,0.00043097682999999166,0.0036831677500000293,0.0019197510000000318,0.0013803985799999995]},{"family":"clusters","n":32,"sweeps_per_start":40,"seconds_per_run":[0.00023280960999997154,0.00021448745999999957,0.00007067005000001735,0.0005945998699999747,0.00027269514000007294,0.00022454305000003456]},{"family":"clusters","n":32,"sweeps_per_start":320,"seconds_per_run":[0.0019150544800000035,0.0016860587999999877,0.000559202909999979,0.005145529690000006,0.002204727159999997,0.001891620210000015]},{"family":"clusters","n":48,"sweeps_per_start":40,"seconds_per_run":[0.00030322707000004725,0.0002751854899999806,0.00010971392000000968,0.0008934298800000295,0.0003757863900000258,0.0003502726900000219]},{"family":"clusters","n":48,"sweeps_per_start":320,"seconds_per_run":[0.002415561120000005,0.0020758080199999897,0.0008355838099999424,0.00719864742999996,0.0029209442299999466,0.0027598891800000303]},{"family":"clusters","n":64,"sweeps_per_start":40,"seconds_per_run":[0.00039589616000011317,0.00034970144000004313,0.00014401224999994967,0.0011907297700000008,0.0004817712900000259,0.0004814884599999836]},{"family":"clusters","n":64,"sweeps_per_start":320,"seconds_per_run":[0.0026577812000000128,0.0023581067800000294,0.0010443855000000944,0.009761374839999988,0.0036952745400000075,0.0038178416900000243]},{"family":"clusters","n":96,"sweeps_per_start":40,"seconds_per_run":[0.0004958160700000391,0.0004016032700000105,0.00022197149999997112,0.0017691242900000459,0.0006307119900000214,0.0008048691499999805]},{"family":"clusters","n":96,"sweeps_per_start":320,"seconds_per_run":[0.003119779979999908,0.002845693769999933,0.001449068089999992,0.013700417770000035,0.004859045779999977,0.005982222949999958]},{"family":"clusters","n":128,"sweeps_per_start":40,"seconds_per_run":[0.0005136296499999737,0.0004685680799999226,0.00026357412999990617,0.002192418860000025,0.0007159796799999363,0.001037706369999878]},{"family":"clusters","n":128,"sweeps_per_start":320,"seconds_per_run":[0.004219225819999912,0.0036075598299999245,0.0019511960499998852,0.019481173950000034,0.006463188999999956,0.009204591659999935]},{"family":"sparse-glass","n":8,"sweeps_per_start":40,"seconds_per_run":[0.00011360554000006573,0.00011013581000000271,0.000026291239999913785,0.00016362904999996707,0.00011208777999998531,0.000059282410000047746]},{"family":"sparse-glass","n":8,"sweeps_per_start":320,"seconds_per_run":[0.0009415860900000083,0.0008821396599999965,0.00020950064999994744,0.001360399949999964,0.0009072501899999885,0.00048773344000004217]},{"family":"sparse-glass","n":12,"sweeps_per_start":40,"seconds_per_run":[0.0001506233999999562,0.00014023337999995888,0.000036978479999971726,0.0002619657399999653,0.00016410659999994693,0.00008993731000001226]},{"family":"sparse-glass","n":12,"sweeps_per_start":320,"seconds_per_run":[0.0011970148999999619,0.001152880970000023,0.0003404374900000118,0.0021974593700000078,0.001306185790000045,0.0007166825899999907]},{"family":"sparse-glass","n":16,"sweeps_per_start":40,"seconds_per_run":[0.00021192924000000888,0.00022522344999999572,0.00004643392000000007,0.00037548092999999366,0.00032211832000001177,0.0001241872800000178]},{"family":"sparse-glass","n":16,"sweeps_per_start":320,"seconds_per_run":[0.0014767612699999955,0.001351631240000013,0.00035164444999995047,0.0025960193099999794,0.0016811737100000145,0.0009582057700000179]},{"family":"sparse-glass","n":24,"sweeps_per_start":40,"seconds_per_run":[0.00023543391999997888,0.00021270900999997622,0.00006408133999999066,0.0005340994199999915,0.00029645096999997734,0.0002949279200000543]},{"family":"sparse-glass","n":24,"sweeps_per_start":320,"seconds_per_run":[0.0017069234500000086,0.0015907418099999758,0.0004708800599999652,0.003852266780000049,0.0023858568500000122,0.0014564258799999409]},{"family":"sparse-glass","n":32,"sweeps_per_start":40,"seconds_per_run":[0.0002695928600000575,0.00025148001000002316,0.00008277555999996087,0.0006521599499999882,0.00036126213000001184,0.0002504833600000998]},{"family":"sparse-glass","n":32,"sweeps_per_start":320,"seconds_per_run":[0.00221936746000001,0.001932376339999995,0.0006737557700000218,0.005625348519999989,0.0031017593399999964,0.0020626601599999985]},{"family":"sparse-glass","n":48,"sweeps_per_start":40,"seconds_per_run":[0.0003499772500000109,0.0003255880499999808,0.00012788174000000652,0.001092889100000054,0.0005185889599999615,0.00041438866999994774]},{"family":"sparse-glass","n":48,"sweeps_per_start":320,"seconds_per_run":[0.002426083599999913,0.0021729475099999005,0.0008522251899999044,0.00735387648000005,0.0037077626400000875,0.003014326269999934]},{"family":"sparse-glass","n":64,"sweeps_per_start":40,"seconds_per_run":[0.00040883051000006497,0.0004368350999999337,0.00018575901999996798,0.0014161952899999811,0.0006961417800000338,0.0006356234700000258]},{"family":"sparse-glass","n":64,"sweeps_per_start":320,"seconds_per_run":[0.0034336535899999908,0.0031064985000000458,0.0013266451499999877,0.01227609369,0.00572109340999998,0.005128491960000014]},{"family":"sparse-glass","n":96,"sweeps_per_start":40,"seconds_per_run":[0.0005200167299999237,0.00047153251999998425,0.00025060943000003276,0.0019489731300000113,0.0008191752900000893,0.0008796239799999661]},{"family":"sparse-glass","n":96,"sweeps_per_start":320,"seconds_per_run":[0.0032793044400000505,0.0029998640499999674,0.0014707992600001241,0.013547217939999925,0.0054275348600000585,0.0062147468499998484]},{"family":"sparse-glass","n":128,"sweeps_per_start":40,"seconds_per_run":[0.0006651942800002786,0.0006029273099999344,0.00035363753000041244,0.0027231749600001363,0.0010553771000001077,0.0012863200900001103]},{"family":"sparse-glass","n":128,"sweeps_per_start":320,"seconds_per_run":[0.005036073699999994,0.004777135629999959,0.0029609668600001004,0.02250962286999994,0.008858994280000011,0.011420270880000067]},{"family":"dense-glass","n":8,"sweeps_per_start":40,"seconds_per_run":[0.00010216335999993831,0.00010686120999995551,0.000027883809999860175,0.00018604440999993698,0.0001108211700000794,0.00005433953999997243]},{"family":"dense-glass","n":8,"sweeps_per_start":320,"seconds_per_run":[0.0008114140699999695,0.0008632033599999467,0.00021039784999985045,0.001544117480000029,0.0008629679900002074,0.0004178786800000367]},{"family":"dense-glass","n":12,"sweeps_per_start":40,"seconds_per_run":[0.00016638801000004477,0.00016664138000004768,0.000055111100000022614,0.0003401495599999649,0.00022466032999993502,0.00010261675999990061]},{"family":"dense-glass","n":12,"sweeps_per_start":320,"seconds_per_run":[0.0012952743299998973,0.0013035093999999353,0.00040853423999997964,0.002859163110000029,0.0017607546599999302,0.0008113521300001032]},{"family":"dense-glass","n":16,"sweeps_per_start":40,"seconds_per_run":[0.0002861718299999938,0.00024888182999998064,0.00008575273999999633,0.0005993373300000127,0.0011382258400000045,0.00018590059000000547]},{"family":"dense-glass","n":16,"sweeps_per_start":320,"seconds_per_run":[0.0017636904899999627,0.0017783760599999797,0.0005654749300000272,0.00407064467999997,0.0028182502199999514,0.001147069229999964]},{"family":"dense-glass","n":24,"sweeps_per_start":40,"seconds_per_run":[0.0003888510999999717,0.00037842612000000387,0.00015267877999992408,0.0009339152300000109,0.0007665663400001321,0.00030665981000007394]},{"family":"dense-glass","n":24,"sweeps_per_start":320,"seconds_per_run":[0.002488674769999921,0.0023802991200001598,0.0009295140899998877,0.005603429840000054,0.004820939409999987,0.001806270850000047]},{"family":"dense-glass","n":32,"sweeps_per_start":40,"seconds_per_run":[0.0008134434500000024,0.0006103283100000254,0.00027100483999999733,0.001573651240000031,0.0015490305600000009,0.0005453264599999947]},{"family":"dense-glass","n":32,"sweeps_per_start":320,"seconds_per_run":[0.004180256729999945,0.0038956537700000115,0.0019060518699999917,0.010019609460000032,0.009628186669999863,0.0035789156499999386]},{"family":"dense-glass","n":48,"sweeps_per_start":40,"seconds_per_run":[0.000845137970000085,0.0007574863999998627,0.0004554835900001031,0.001953194309999731,0.0021038831599999243,0.0008115315599999207]},{"family":"dense-glass","n":48,"sweeps_per_start":320,"seconds_per_run":[0.006673816020000132,0.006062415370000096,0.0036672082199999422,0.016643323900000055,0.016840225570000023,0.006306077290000048]},{"family":"dense-glass","n":64,"sweeps_per_start":40,"seconds_per_run":[0.0016113202100000563,0.0014173205999999763,0.0009178268100000489,0.0037707767800002055,0.0056099817600001435,0.001606669779999898]},{"family":"dense-glass","n":64,"sweeps_per_start":320,"seconds_per_run":[0.010701138070000003,0.009632821400000128,0.006451613359999966,0.025279230040000166,0.029543504050000023,0.011381430859999977]},{"family":"selection","n":8,"sweeps_per_start":40,"seconds_per_run":[0.000047798939999808,0.000055309189999910304,0.00001367059000020987,0.00011206289999982744,0.00004907361000025645,0.00002756594999983264]},{"family":"selection","n":8,"sweeps_per_start":320,"seconds_per_run":[0.0004382708599998963,0.000488066359999948,0.00012670200999998827,0.0011121107900001802,0.00043920361999997935,0.0002602245600001751]},{"family":"selection","n":12,"sweeps_per_start":40,"seconds_per_run":[0.00004872567999992157,0.00005626356000024712,0.000017021029999885712,0.00014203827000011532,0.00005136882999997095,0.000036368940000149186]},{"family":"selection","n":12,"sweeps_per_start":320,"seconds_per_run":[0.0004907122100000034,0.0005436815599999982,0.00016869806000000028,0.0014761836999999994,0.00052239519,0.00037694389000000124]},{"family":"selection","n":16,"sweeps_per_start":40,"seconds_per_run":[0.00006667286999999966,0.00007570249999998891,0.000026662259999994387,0.0002175286899999973,0.00007061833000000294,0.00005728013999998949]},{"family":"selection","n":16,"sweeps_per_start":320,"seconds_per_run":[0.0005152148600000083,0.0005869362800000034,0.0002100996500000085,0.001749306900000016,0.0005763658399999949,0.00047390238999999167]},{"family":"selection","n":24,"sweeps_per_start":40,"seconds_per_run":[0.00008350120999996325,0.00009654181000002372,0.00004296206999999885,0.00034925599999999777,0.00010060017000003363,0.00009519780000001087]},{"family":"selection","n":24,"sweeps_per_start":320,"seconds_per_run":[0.0007605409000000002,0.0007638799400000025,0.00029778408000001306,0.003403230779999996,0.0008462369299999995,0.0007535200699999905]},{"family":"selection","n":32,"sweeps_per_start":40,"seconds_per_run":[0.00010283993000000714,0.00012946925000000497,0.000054749240000003805,0.0005973334000000008,0.00016547493000000136,0.00014528448999999942]},{"family":"selection","n":32,"sweeps_per_start":320,"seconds_per_run":[0.0008973164600000116,0.0009802698899999895,0.0004179100100000055,0.005088153530000007,0.0013551945599999992,0.0011881608299999969]},{"family":"selection","n":48,"sweeps_per_start":40,"seconds_per_run":[0.0001487451300000031,0.00019463402000001296,0.0000952692499999941,0.0012424156899999827,0.0003157161599999947,0.00028572168999998484]},{"family":"selection","n":48,"sweeps_per_start":320,"seconds_per_run":[0.001446239309999997,0.0020105394499999787,0.001027384070000018,0.013198201659999981,0.003171313710000018,0.003363318220000032]},{"family":"selection","n":64,"sweeps_per_start":40,"seconds_per_run":[0.0001976294400000067,0.0002896957299999947,0.00013590841000000298,0.0021211790600000014,0.0004955218400000017,0.0004712208700000164]},{"family":"selection","n":64,"sweeps_per_start":320,"seconds_per_run":[0.0016182547600000197,0.002159678319999985,0.001069446380000043,0.01876716949999997,0.004196393619999994,0.0037884513299999735]},{"family":"selection","n":96,"sweeps_per_start":40,"seconds_per_run":[0.0003094757799999659,0.00039675494999994674,0.00023630997000002375,0.004244304080000046,0.000938412049999977,0.0008690155600000348]},{"family":"selection","n":96,"sweeps_per_start":320,"seconds_per_run":[0.002108828210000038,0.0028060932499999504,0.0017096328800000826,0.03367771579999992,0.0070747372899999305,0.0062953109899998964]},{"family":"selection","n":128,"sweeps_per_start":40,"seconds_per_run":[0.0005315390399999995,0.000683133119999995,0.00040850614000000513,0.008918332020000008,0.001686978090000008,0.0016568442700000035]},{"family":"selection","n":128,"sweeps_per_start":320,"seconds_per_run":[0.003050262079999937,0.003953086989999957,0.002670026389999998,0.06029867667,0.011849296560000007,0.011371424709999934]}] diff --git a/wai/quantum-conformance/landscape/trial-assignment.json b/wai/quantum-conformance/landscape/trial-assignment.json new file mode 100644 index 000000000..05e97385a --- /dev/null +++ b/wai/quantum-conformance/landscape/trial-assignment.json @@ -0,0 +1,118 @@ +{ + "feasibility": [ + { + "side": 2, + "lowest_states": 2, + "lowest_states_that_are_assignments": 2, + "runs": 40, + "three_states_valid": 40, + "zero_or_plus_valid": 40 + }, + { + "side": 3, + "lowest_states": 21, + "lowest_states_that_are_assignments": 6, + "runs": 40, + "three_states_valid": 15, + "zero_or_plus_valid": 40 + }, + { + "side": 4, + "lowest_states": 1344, + "lowest_states_that_are_assignments": 24, + "runs": 40, + "three_states_valid": 1, + "zero_or_plus_valid": 40 + }, + { + "side": 5, + "lowest_states": null, + "lowest_states_that_are_assignments": null, + "runs": 40, + "three_states_valid": 0, + "zero_or_plus_valid": 40 + }, + { + "side": 6, + "lowest_states": null, + "lowest_states_that_are_assignments": null, + "runs": 40, + "three_states_valid": 0, + "zero_or_plus_valid": 40 + }, + { + "side": 8, + "lowest_states": null, + "lowest_states_that_are_assignments": null, + "runs": 40, + "three_states_valid": 0, + "zero_or_plus_valid": 40 + } + ], + "with_costs": [ + { + "side": 2, + "cheapest_costs_zero": false, + "instances": 10, + "runs": 100, + "valid": 0, + "cheapest": 0 + }, + { + "side": 2, + "cheapest_costs_zero": true, + "instances": 10, + "runs": 100, + "valid": 100, + "cheapest": 100 + }, + { + "side": 3, + "cheapest_costs_zero": false, + "instances": 10, + "runs": 100, + "valid": 0, + "cheapest": 0 + }, + { + "side": 3, + "cheapest_costs_zero": true, + "instances": 10, + "runs": 100, + "valid": 100, + "cheapest": 100 + }, + { + "side": 4, + "cheapest_costs_zero": false, + "instances": 6, + "runs": 60, + "valid": 0, + "cheapest": 0 + }, + { + "side": 4, + "cheapest_costs_zero": true, + "instances": 14, + "runs": 140, + "valid": 140, + "cheapest": 140 + }, + { + "side": 5, + "cheapest_costs_zero": false, + "instances": 6, + "runs": 60, + "valid": 0, + "cheapest": 0 + }, + { + "side": 5, + "cheapest_costs_zero": true, + "instances": 14, + "runs": 140, + "valid": 140, + "cheapest": 140 + } + ] +} diff --git a/wai/quantum-conformance/landscape/vectors.json b/wai/quantum-conformance/landscape/vectors.json new file mode 100644 index 000000000..388157b95 --- /dev/null +++ b/wai/quantum-conformance/landscape/vectors.json @@ -0,0 +1,17548 @@ +{ + "protocol": "wai.quantum.landscape/1", + "about": "What a member is held to: extensions/quantum-landscape.md, section 7. Rebuilt by `wai-quantum landscape vectors`; a test holds this file to the code.", + "settle": [ + { + "name": "recall/n=4", + "landscape": { + "n": 4, + "couplings": [ + [ + 0, + 1, + -1 + ], + [ + 0, + 2, + 1 + ], + [ + 0, + 3, + -1 + ], + [ + 1, + 2, + -1 + ], + [ + 1, + 3, + 1 + ], + [ + 2, + 3, + -1 + ] + ], + "field": [ + 0, + 0, + 0, + 0 + ], + "cost": 0 + }, + "start": "+++-", + "bound": 40, + "expect": { + "state": "+-+-", + "sweeps": 2, + "term": "fixed-point", + "accumulates": 24, + "state_hash": "8d1402904a33c6c5" + } + }, + { + "name": "recall/n=8", + "landscape": { + "n": 8, + "couplings": [ + [ + 0, + 1, + -1 + ], + [ + 0, + 2, + 1 + ], + [ + 0, + 3, + -1 + ], + [ + 0, + 4, + 1 + ], + [ + 0, + 5, + -1 + ], + [ + 0, + 6, + 1 + ], + [ + 0, + 7, + -1 + ], + [ + 1, + 2, + -1 + ], + [ + 1, + 3, + 1 + ], + [ + 1, + 4, + -1 + ], + [ + 1, + 5, + 1 + ], + [ + 1, + 6, + -1 + ], + [ + 1, + 7, + 1 + ], + [ + 2, + 3, + -1 + ], + [ + 2, + 4, + 1 + ], + [ + 2, + 5, + -1 + ], + [ + 2, + 6, + 1 + ], + [ + 2, + 7, + -1 + ], + [ + 3, + 4, + -1 + ], + [ + 3, + 5, + 1 + ], + [ + 3, + 6, + -1 + ], + [ + 3, + 7, + 1 + ], + [ + 4, + 5, + -1 + ], + [ + 4, + 6, + 1 + ], + [ + 4, + 7, + -1 + ], + [ + 5, + 6, + -1 + ], + [ + 5, + 7, + 1 + ], + [ + 6, + 7, + -1 + ] + ], + "field": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "cost": 0 + }, + "start": "+++-+-+-", + "bound": 40, + "expect": { + "state": "+-+-+-+-", + "sweeps": 2, + "term": "fixed-point", + "accumulates": 112, + "state_hash": "6d7f49ef0e7af465" + } + }, + { + "name": "recall/n=16", + "landscape": { + "n": 16, + "couplings": [ + [ + 0, + 1, + -1 + ], + [ + 0, + 2, + 1 + ], + [ + 0, + 3, + -1 + ], + [ + 0, + 4, + 1 + ], + [ + 0, + 5, + -1 + ], + [ + 0, + 6, + 1 + ], + [ + 0, + 7, + -1 + ], + [ + 0, + 8, + 1 + ], + [ + 0, + 9, + -1 + ], + [ + 0, + 10, + 1 + ], + [ + 0, + 11, + -1 + ], + [ + 0, + 12, + 1 + ], + [ + 0, + 13, + -1 + ], + [ + 0, + 14, + 1 + ], + [ + 0, + 15, + -1 + ], + [ + 1, + 2, + -1 + ], + [ + 1, + 3, + 1 + ], + [ + 1, + 4, + -1 + ], + [ + 1, + 5, + 1 + ], + [ + 1, + 6, + -1 + ], + [ + 1, + 7, + 1 + ], + [ + 1, + 8, + -1 + ], + [ + 1, + 9, + 1 + ], + [ + 1, + 10, + -1 + ], + [ + 1, + 11, + 1 + ], + [ + 1, + 12, + -1 + ], + [ + 1, + 13, + 1 + ], + [ + 1, + 14, + -1 + ], + [ + 1, + 15, + 1 + ], + [ + 2, + 3, + -1 + ], + [ + 2, + 4, + 1 + ], + [ + 2, + 5, + -1 + ], + [ + 2, + 6, + 1 + ], + [ + 2, + 7, + -1 + ], + [ + 2, + 8, + 1 + ], + [ + 2, + 9, + -1 + ], + [ + 2, + 10, + 1 + ], + [ + 2, + 11, + -1 + ], + [ + 2, + 12, + 1 + ], + [ + 2, + 13, + -1 + ], + [ + 2, + 14, + 1 + ], + [ + 2, + 15, + -1 + ], + [ + 3, + 4, + -1 + ], + [ + 3, + 5, + 1 + ], + [ + 3, + 6, + -1 + ], + [ + 3, + 7, + 1 + ], + [ + 3, + 8, + -1 + ], + [ + 3, + 9, + 1 + ], + [ + 3, + 10, + -1 + ], + [ + 3, + 11, + 1 + ], + [ + 3, + 12, + -1 + ], + [ + 3, + 13, + 1 + ], + [ + 3, + 14, + -1 + ], + [ + 3, + 15, + 1 + ], + [ + 4, + 5, + -1 + ], + [ + 4, + 6, + 1 + ], + [ + 4, + 7, + -1 + ], + [ + 4, + 8, + 1 + ], + [ + 4, + 9, + -1 + ], + [ + 4, + 10, + 1 + ], + [ + 4, + 11, + -1 + ], + [ + 4, + 12, + 1 + ], + [ + 4, + 13, + -1 + ], + [ + 4, + 14, + 1 + ], + [ + 4, + 15, + -1 + ], + [ + 5, + 6, + -1 + ], + [ + 5, + 7, + 1 + ], + [ + 5, + 8, + -1 + ], + [ + 5, + 9, + 1 + ], + [ + 5, + 10, + -1 + ], + [ + 5, + 11, + 1 + ], + [ + 5, + 12, + -1 + ], + [ + 5, + 13, + 1 + ], + [ + 5, + 14, + -1 + ], + [ + 5, + 15, + 1 + ], + [ + 6, + 7, + -1 + ], + [ + 6, + 8, + 1 + ], + [ + 6, + 9, + -1 + ], + [ + 6, + 10, + 1 + ], + [ + 6, + 11, + -1 + ], + [ + 6, + 12, + 1 + ], + [ + 6, + 13, + -1 + ], + [ + 6, + 14, + 1 + ], + [ + 6, + 15, + -1 + ], + [ + 7, + 8, + -1 + ], + [ + 7, + 9, + 1 + ], + [ + 7, + 10, + -1 + ], + [ + 7, + 11, + 1 + ], + [ + 7, + 12, + -1 + ], + [ + 7, + 13, + 1 + ], + [ + 7, + 14, + -1 + ], + [ + 7, + 15, + 1 + ], + [ + 8, + 9, + -1 + ], + [ + 8, + 10, + 1 + ], + [ + 8, + 11, + -1 + ], + [ + 8, + 12, + 1 + ], + [ + 8, + 13, + -1 + ], + [ + 8, + 14, + 1 + ], + [ + 8, + 15, + -1 + ], + [ + 9, + 10, + -1 + ], + [ + 9, + 11, + 1 + ], + [ + 9, + 12, + -1 + ], + [ + 9, + 13, + 1 + ], + [ + 9, + 14, + -1 + ], + [ + 9, + 15, + 1 + ], + [ + 10, + 11, + -1 + ], + [ + 10, + 12, + 1 + ], + [ + 10, + 13, + -1 + ], + [ + 10, + 14, + 1 + ], + [ + 10, + 15, + -1 + ], + [ + 11, + 12, + -1 + ], + [ + 11, + 13, + 1 + ], + [ + 11, + 14, + -1 + ], + [ + 11, + 15, + 1 + ], + [ + 12, + 13, + -1 + ], + [ + 12, + 14, + 1 + ], + [ + 12, + 15, + -1 + ], + [ + 13, + 14, + -1 + ], + [ + 13, + 15, + 1 + ], + [ + 14, + 15, + -1 + ] + ], + "field": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "cost": 0 + }, + "start": "+++-+---+-+-+-+-", + "bound": 40, + "expect": { + "state": "+-+-+-+-+-+-+-+-", + "sweeps": 2, + "term": "fixed-point", + "accumulates": 480, + "state_hash": "a12d60a06b2a6da5" + } + }, + { + "name": "recall/n=32", + "landscape": { + "n": 32, + "couplings": [ + [ + 0, + 1, + -1 + ], + [ + 0, + 2, + 1 + ], + [ + 0, + 3, + -1 + ], + [ + 0, + 4, + 1 + ], + [ + 0, + 5, + -1 + ], + [ + 0, + 6, + 1 + ], + [ + 0, + 7, + -1 + ], + [ + 0, + 8, + 1 + ], + [ + 0, + 9, + -1 + ], + [ + 0, + 10, + 1 + ], + [ + 0, + 11, + -1 + ], + [ + 0, + 12, + 1 + ], + [ + 0, + 13, + -1 + ], + [ + 0, + 14, + 1 + ], + [ + 0, + 15, + -1 + ], + [ + 0, + 16, + 1 + ], + [ + 0, + 17, + -1 + ], + [ + 0, + 18, + 1 + ], + [ + 0, + 19, + -1 + ], + [ + 0, + 20, + 1 + ], + [ + 0, + 21, + -1 + ], + [ + 0, + 22, + 1 + ], + [ + 0, + 23, + -1 + ], + [ + 0, + 24, + 1 + ], + [ + 0, + 25, + -1 + ], + [ + 0, + 26, + 1 + ], + [ + 0, + 27, + -1 + ], + [ + 0, + 28, + 1 + ], + [ + 0, + 29, + -1 + ], + [ + 0, + 30, + 1 + ], + [ + 0, + 31, + -1 + ], + [ + 1, + 2, + -1 + ], + [ + 1, + 3, + 1 + ], + [ + 1, + 4, + -1 + ], + [ + 1, + 5, + 1 + ], + [ + 1, + 6, + -1 + ], + [ + 1, + 7, + 1 + ], + [ + 1, + 8, + -1 + ], + [ + 1, + 9, + 1 + ], + [ + 1, + 10, + -1 + ], + [ + 1, + 11, + 1 + ], + [ + 1, + 12, + -1 + ], + [ + 1, + 13, + 1 + ], + [ + 1, + 14, + -1 + ], + [ + 1, + 15, + 1 + ], + [ + 1, + 16, + -1 + ], + [ + 1, + 17, + 1 + ], + [ + 1, + 18, + -1 + ], + [ + 1, + 19, + 1 + ], + [ + 1, + 20, + -1 + ], + [ + 1, + 21, + 1 + ], + [ + 1, + 22, + -1 + ], + [ + 1, + 23, + 1 + ], + [ + 1, + 24, + -1 + ], + [ + 1, + 25, + 1 + ], + [ + 1, + 26, + -1 + ], + [ + 1, + 27, + 1 + ], + [ + 1, + 28, + -1 + ], + [ + 1, + 29, + 1 + ], + [ + 1, + 30, + -1 + ], + [ + 1, + 31, + 1 + ], + [ + 2, + 3, + -1 + ], + [ + 2, + 4, + 1 + ], + [ + 2, + 5, + -1 + ], + [ + 2, + 6, + 1 + ], + [ + 2, + 7, + -1 + ], + [ + 2, + 8, + 1 + ], + [ + 2, + 9, + -1 + ], + [ + 2, + 10, + 1 + ], + [ + 2, + 11, + -1 + ], + [ + 2, + 12, + 1 + ], + [ + 2, + 13, + -1 + ], + [ + 2, + 14, + 1 + ], + [ + 2, + 15, + -1 + ], + [ + 2, + 16, + 1 + ], + [ + 2, + 17, + -1 + ], + [ + 2, + 18, + 1 + ], + [ + 2, + 19, + -1 + ], + [ + 2, + 20, + 1 + ], + [ + 2, + 21, + -1 + ], + [ + 2, + 22, + 1 + ], + [ + 2, + 23, + -1 + ], + [ + 2, + 24, + 1 + ], + [ + 2, + 25, + -1 + ], + [ + 2, + 26, + 1 + ], + [ + 2, + 27, + -1 + ], + [ + 2, + 28, + 1 + ], + [ + 2, + 29, + -1 + ], + [ + 2, + 30, + 1 + ], + [ + 2, + 31, + -1 + ], + [ + 3, + 4, + -1 + ], + [ + 3, + 5, + 1 + ], + [ + 3, + 6, + -1 + ], + [ + 3, + 7, + 1 + ], + [ + 3, + 8, + -1 + ], + [ + 3, + 9, + 1 + ], + [ + 3, + 10, + -1 + ], + [ + 3, + 11, + 1 + ], + [ + 3, + 12, + -1 + ], + [ + 3, + 13, + 1 + ], + [ + 3, + 14, + -1 + ], + [ + 3, + 15, + 1 + ], + [ + 3, + 16, + -1 + ], + [ + 3, + 17, + 1 + ], + [ + 3, + 18, + -1 + ], + [ + 3, + 19, + 1 + ], + [ + 3, + 20, + -1 + ], + [ + 3, + 21, + 1 + ], + [ + 3, + 22, + -1 + ], + [ + 3, + 23, + 1 + ], + [ + 3, + 24, + -1 + ], + [ + 3, + 25, + 1 + ], + [ + 3, + 26, + -1 + ], + [ + 3, + 27, + 1 + ], + [ + 3, + 28, + -1 + ], + [ + 3, + 29, + 1 + ], + [ + 3, + 30, + -1 + ], + [ + 3, + 31, + 1 + ], + [ + 4, + 5, + -1 + ], + [ + 4, + 6, + 1 + ], + [ + 4, + 7, + -1 + ], + [ + 4, + 8, + 1 + ], + [ + 4, + 9, + -1 + ], + [ + 4, + 10, + 1 + ], + [ + 4, + 11, + -1 + ], + [ + 4, + 12, + 1 + ], + [ + 4, + 13, + -1 + ], + [ + 4, + 14, + 1 + ], + [ + 4, + 15, + -1 + ], + [ + 4, + 16, + 1 + ], + [ + 4, + 17, + -1 + ], + [ + 4, + 18, + 1 + ], + [ + 4, + 19, + -1 + ], + [ + 4, + 20, + 1 + ], + [ + 4, + 21, + -1 + ], + [ + 4, + 22, + 1 + ], + [ + 4, + 23, + -1 + ], + [ + 4, + 24, + 1 + ], + [ + 4, + 25, + -1 + ], + [ + 4, + 26, + 1 + ], + [ + 4, + 27, + -1 + ], + [ + 4, + 28, + 1 + ], + [ + 4, + 29, + -1 + ], + [ + 4, + 30, + 1 + ], + [ + 4, + 31, + -1 + ], + [ + 5, + 6, + -1 + ], + [ + 5, + 7, + 1 + ], + [ + 5, + 8, + -1 + ], + [ + 5, + 9, + 1 + ], + [ + 5, + 10, + -1 + ], + [ + 5, + 11, + 1 + ], + [ + 5, + 12, + -1 + ], + [ + 5, + 13, + 1 + ], + [ + 5, + 14, + -1 + ], + [ + 5, + 15, + 1 + ], + [ + 5, + 16, + -1 + ], + [ + 5, + 17, + 1 + ], + [ + 5, + 18, + -1 + ], + [ + 5, + 19, + 1 + ], + [ + 5, + 20, + -1 + ], + [ + 5, + 21, + 1 + ], + [ + 5, + 22, + -1 + ], + [ + 5, + 23, + 1 + ], + [ + 5, + 24, + -1 + ], + [ + 5, + 25, + 1 + ], + [ + 5, + 26, + -1 + ], + [ + 5, + 27, + 1 + ], + [ + 5, + 28, + -1 + ], + [ + 5, + 29, + 1 + ], + [ + 5, + 30, + -1 + ], + [ + 5, + 31, + 1 + ], + [ + 6, + 7, + -1 + ], + [ + 6, + 8, + 1 + ], + [ + 6, + 9, + -1 + ], + [ + 6, + 10, + 1 + ], + [ + 6, + 11, + -1 + ], + [ + 6, + 12, + 1 + ], + [ + 6, + 13, + -1 + ], + [ + 6, + 14, + 1 + ], + [ + 6, + 15, + -1 + ], + [ + 6, + 16, + 1 + ], + [ + 6, + 17, + -1 + ], + [ + 6, + 18, + 1 + ], + [ + 6, + 19, + -1 + ], + [ + 6, + 20, + 1 + ], + [ + 6, + 21, + -1 + ], + [ + 6, + 22, + 1 + ], + [ + 6, + 23, + -1 + ], + [ + 6, + 24, + 1 + ], + [ + 6, + 25, + -1 + ], + [ + 6, + 26, + 1 + ], + [ + 6, + 27, + -1 + ], + [ + 6, + 28, + 1 + ], + [ + 6, + 29, + -1 + ], + [ + 6, + 30, + 1 + ], + [ + 6, + 31, + -1 + ], + [ + 7, + 8, + -1 + ], + [ + 7, + 9, + 1 + ], + [ + 7, + 10, + -1 + ], + [ + 7, + 11, + 1 + ], + [ + 7, + 12, + -1 + ], + [ + 7, + 13, + 1 + ], + [ + 7, + 14, + -1 + ], + [ + 7, + 15, + 1 + ], + [ + 7, + 16, + -1 + ], + [ + 7, + 17, + 1 + ], + [ + 7, + 18, + -1 + ], + [ + 7, + 19, + 1 + ], + [ + 7, + 20, + -1 + ], + [ + 7, + 21, + 1 + ], + [ + 7, + 22, + -1 + ], + [ + 7, + 23, + 1 + ], + [ + 7, + 24, + -1 + ], + [ + 7, + 25, + 1 + ], + [ + 7, + 26, + -1 + ], + [ + 7, + 27, + 1 + ], + [ + 7, + 28, + -1 + ], + [ + 7, + 29, + 1 + ], + [ + 7, + 30, + -1 + ], + [ + 7, + 31, + 1 + ], + [ + 8, + 9, + -1 + ], + [ + 8, + 10, + 1 + ], + [ + 8, + 11, + -1 + ], + [ + 8, + 12, + 1 + ], + [ + 8, + 13, + -1 + ], + [ + 8, + 14, + 1 + ], + [ + 8, + 15, + -1 + ], + [ + 8, + 16, + 1 + ], + [ + 8, + 17, + -1 + ], + [ + 8, + 18, + 1 + ], + [ + 8, + 19, + -1 + ], + [ + 8, + 20, + 1 + ], + [ + 8, + 21, + -1 + ], + [ + 8, + 22, + 1 + ], + [ + 8, + 23, + -1 + ], + [ + 8, + 24, + 1 + ], + [ + 8, + 25, + -1 + ], + [ + 8, + 26, + 1 + ], + [ + 8, + 27, + -1 + ], + [ + 8, + 28, + 1 + ], + [ + 8, + 29, + -1 + ], + [ + 8, + 30, + 1 + ], + [ + 8, + 31, + -1 + ], + [ + 9, + 10, + -1 + ], + [ + 9, + 11, + 1 + ], + [ + 9, + 12, + -1 + ], + [ + 9, + 13, + 1 + ], + [ + 9, + 14, + -1 + ], + [ + 9, + 15, + 1 + ], + [ + 9, + 16, + -1 + ], + [ + 9, + 17, + 1 + ], + [ + 9, + 18, + -1 + ], + [ + 9, + 19, + 1 + ], + [ + 9, + 20, + -1 + ], + [ + 9, + 21, + 1 + ], + [ + 9, + 22, + -1 + ], + [ + 9, + 23, + 1 + ], + [ + 9, + 24, + -1 + ], + [ + 9, + 25, + 1 + ], + [ + 9, + 26, + -1 + ], + [ + 9, + 27, + 1 + ], + [ + 9, + 28, + -1 + ], + [ + 9, + 29, + 1 + ], + [ + 9, + 30, + -1 + ], + [ + 9, + 31, + 1 + ], + [ + 10, + 11, + -1 + ], + [ + 10, + 12, + 1 + ], + [ + 10, + 13, + -1 + ], + [ + 10, + 14, + 1 + ], + [ + 10, + 15, + -1 + ], + [ + 10, + 16, + 1 + ], + [ + 10, + 17, + -1 + ], + [ + 10, + 18, + 1 + ], + [ + 10, + 19, + -1 + ], + [ + 10, + 20, + 1 + ], + [ + 10, + 21, + -1 + ], + [ + 10, + 22, + 1 + ], + [ + 10, + 23, + -1 + ], + [ + 10, + 24, + 1 + ], + [ + 10, + 25, + -1 + ], + [ + 10, + 26, + 1 + ], + [ + 10, + 27, + -1 + ], + [ + 10, + 28, + 1 + ], + [ + 10, + 29, + -1 + ], + [ + 10, + 30, + 1 + ], + [ + 10, + 31, + -1 + ], + [ + 11, + 12, + -1 + ], + [ + 11, + 13, + 1 + ], + [ + 11, + 14, + -1 + ], + [ + 11, + 15, + 1 + ], + [ + 11, + 16, + -1 + ], + [ + 11, + 17, + 1 + ], + [ + 11, + 18, + -1 + ], + [ + 11, + 19, + 1 + ], + [ + 11, + 20, + -1 + ], + [ + 11, + 21, + 1 + ], + [ + 11, + 22, + -1 + ], + [ + 11, + 23, + 1 + ], + [ + 11, + 24, + -1 + ], + [ + 11, + 25, + 1 + ], + [ + 11, + 26, + -1 + ], + [ + 11, + 27, + 1 + ], + [ + 11, + 28, + -1 + ], + [ + 11, + 29, + 1 + ], + [ + 11, + 30, + -1 + ], + [ + 11, + 31, + 1 + ], + [ + 12, + 13, + -1 + ], + [ + 12, + 14, + 1 + ], + [ + 12, + 15, + -1 + ], + [ + 12, + 16, + 1 + ], + [ + 12, + 17, + -1 + ], + [ + 12, + 18, + 1 + ], + [ + 12, + 19, + -1 + ], + [ + 12, + 20, + 1 + ], + [ + 12, + 21, + -1 + ], + [ + 12, + 22, + 1 + ], + [ + 12, + 23, + -1 + ], + [ + 12, + 24, + 1 + ], + [ + 12, + 25, + -1 + ], + [ + 12, + 26, + 1 + ], + [ + 12, + 27, + -1 + ], + [ + 12, + 28, + 1 + ], + [ + 12, + 29, + -1 + ], + [ + 12, + 30, + 1 + ], + [ + 12, + 31, + -1 + ], + [ + 13, + 14, + -1 + ], + [ + 13, + 15, + 1 + ], + [ + 13, + 16, + -1 + ], + [ + 13, + 17, + 1 + ], + [ + 13, + 18, + -1 + ], + [ + 13, + 19, + 1 + ], + [ + 13, + 20, + -1 + ], + [ + 13, + 21, + 1 + ], + [ + 13, + 22, + -1 + ], + [ + 13, + 23, + 1 + ], + [ + 13, + 24, + -1 + ], + [ + 13, + 25, + 1 + ], + [ + 13, + 26, + -1 + ], + [ + 13, + 27, + 1 + ], + [ + 13, + 28, + -1 + ], + [ + 13, + 29, + 1 + ], + [ + 13, + 30, + -1 + ], + [ + 13, + 31, + 1 + ], + [ + 14, + 15, + -1 + ], + [ + 14, + 16, + 1 + ], + [ + 14, + 17, + -1 + ], + [ + 14, + 18, + 1 + ], + [ + 14, + 19, + -1 + ], + [ + 14, + 20, + 1 + ], + [ + 14, + 21, + -1 + ], + [ + 14, + 22, + 1 + ], + [ + 14, + 23, + -1 + ], + [ + 14, + 24, + 1 + ], + [ + 14, + 25, + -1 + ], + [ + 14, + 26, + 1 + ], + [ + 14, + 27, + -1 + ], + [ + 14, + 28, + 1 + ], + [ + 14, + 29, + -1 + ], + [ + 14, + 30, + 1 + ], + [ + 14, + 31, + -1 + ], + [ + 15, + 16, + -1 + ], + [ + 15, + 17, + 1 + ], + [ + 15, + 18, + -1 + ], + [ + 15, + 19, + 1 + ], + [ + 15, + 20, + -1 + ], + [ + 15, + 21, + 1 + ], + [ + 15, + 22, + -1 + ], + [ + 15, + 23, + 1 + ], + [ + 15, + 24, + -1 + ], + [ + 15, + 25, + 1 + ], + [ + 15, + 26, + -1 + ], + [ + 15, + 27, + 1 + ], + [ + 15, + 28, + -1 + ], + [ + 15, + 29, + 1 + ], + [ + 15, + 30, + -1 + ], + [ + 15, + 31, + 1 + ], + [ + 16, + 17, + -1 + ], + [ + 16, + 18, + 1 + ], + [ + 16, + 19, + -1 + ], + [ + 16, + 20, + 1 + ], + [ + 16, + 21, + -1 + ], + [ + 16, + 22, + 1 + ], + [ + 16, + 23, + -1 + ], + [ + 16, + 24, + 1 + ], + [ + 16, + 25, + -1 + ], + [ + 16, + 26, + 1 + ], + [ + 16, + 27, + -1 + ], + [ + 16, + 28, + 1 + ], + [ + 16, + 29, + -1 + ], + [ + 16, + 30, + 1 + ], + [ + 16, + 31, + -1 + ], + [ + 17, + 18, + -1 + ], + [ + 17, + 19, + 1 + ], + [ + 17, + 20, + -1 + ], + [ + 17, + 21, + 1 + ], + [ + 17, + 22, + -1 + ], + [ + 17, + 23, + 1 + ], + [ + 17, + 24, + -1 + ], + [ + 17, + 25, + 1 + ], + [ + 17, + 26, + -1 + ], + [ + 17, + 27, + 1 + ], + [ + 17, + 28, + -1 + ], + [ + 17, + 29, + 1 + ], + [ + 17, + 30, + -1 + ], + [ + 17, + 31, + 1 + ], + [ + 18, + 19, + -1 + ], + [ + 18, + 20, + 1 + ], + [ + 18, + 21, + -1 + ], + [ + 18, + 22, + 1 + ], + [ + 18, + 23, + -1 + ], + [ + 18, + 24, + 1 + ], + [ + 18, + 25, + -1 + ], + [ + 18, + 26, + 1 + ], + [ + 18, + 27, + -1 + ], + [ + 18, + 28, + 1 + ], + [ + 18, + 29, + -1 + ], + [ + 18, + 30, + 1 + ], + [ + 18, + 31, + -1 + ], + [ + 19, + 20, + -1 + ], + [ + 19, + 21, + 1 + ], + [ + 19, + 22, + -1 + ], + [ + 19, + 23, + 1 + ], + [ + 19, + 24, + -1 + ], + [ + 19, + 25, + 1 + ], + [ + 19, + 26, + -1 + ], + [ + 19, + 27, + 1 + ], + [ + 19, + 28, + -1 + ], + [ + 19, + 29, + 1 + ], + [ + 19, + 30, + -1 + ], + [ + 19, + 31, + 1 + ], + [ + 20, + 21, + -1 + ], + [ + 20, + 22, + 1 + ], + [ + 20, + 23, + -1 + ], + [ + 20, + 24, + 1 + ], + [ + 20, + 25, + -1 + ], + [ + 20, + 26, + 1 + ], + [ + 20, + 27, + -1 + ], + [ + 20, + 28, + 1 + ], + [ + 20, + 29, + -1 + ], + [ + 20, + 30, + 1 + ], + [ + 20, + 31, + -1 + ], + [ + 21, + 22, + -1 + ], + [ + 21, + 23, + 1 + ], + [ + 21, + 24, + -1 + ], + [ + 21, + 25, + 1 + ], + [ + 21, + 26, + -1 + ], + [ + 21, + 27, + 1 + ], + [ + 21, + 28, + -1 + ], + [ + 21, + 29, + 1 + ], + [ + 21, + 30, + -1 + ], + [ + 21, + 31, + 1 + ], + [ + 22, + 23, + -1 + ], + [ + 22, + 24, + 1 + ], + [ + 22, + 25, + -1 + ], + [ + 22, + 26, + 1 + ], + [ + 22, + 27, + -1 + ], + [ + 22, + 28, + 1 + ], + [ + 22, + 29, + -1 + ], + [ + 22, + 30, + 1 + ], + [ + 22, + 31, + -1 + ], + [ + 23, + 24, + -1 + ], + [ + 23, + 25, + 1 + ], + [ + 23, + 26, + -1 + ], + [ + 23, + 27, + 1 + ], + [ + 23, + 28, + -1 + ], + [ + 23, + 29, + 1 + ], + [ + 23, + 30, + -1 + ], + [ + 23, + 31, + 1 + ], + [ + 24, + 25, + -1 + ], + [ + 24, + 26, + 1 + ], + [ + 24, + 27, + -1 + ], + [ + 24, + 28, + 1 + ], + [ + 24, + 29, + -1 + ], + [ + 24, + 30, + 1 + ], + [ + 24, + 31, + -1 + ], + [ + 25, + 26, + -1 + ], + [ + 25, + 27, + 1 + ], + [ + 25, + 28, + -1 + ], + [ + 25, + 29, + 1 + ], + [ + 25, + 30, + -1 + ], + [ + 25, + 31, + 1 + ], + [ + 26, + 27, + -1 + ], + [ + 26, + 28, + 1 + ], + [ + 26, + 29, + -1 + ], + [ + 26, + 30, + 1 + ], + [ + 26, + 31, + -1 + ], + [ + 27, + 28, + -1 + ], + [ + 27, + 29, + 1 + ], + [ + 27, + 30, + -1 + ], + [ + 27, + 31, + 1 + ], + [ + 28, + 29, + -1 + ], + [ + 28, + 30, + 1 + ], + [ + 28, + 31, + -1 + ], + [ + 29, + 30, + -1 + ], + [ + 29, + 31, + 1 + ], + [ + 30, + 31, + -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 + ], + "cost": 0 + }, + "start": "+++-+---+-+++-+---+-+-+-+-+-+-+-", + "bound": 40, + "expect": { + "state": "+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-", + "sweeps": 2, + "term": "fixed-point", + "accumulates": 1984, + "state_hash": "5f98f63477c15825" + } + }, + { + "name": "two-cycle", + "landscape": { + "n": 2, + "couplings": [ + [ + 0, + 1, + -1 + ] + ], + "field": [ + 0, + 0 + ], + "cost": 0 + }, + "start": "++", + "bound": 20, + "expect": { + "state": "++", + "sweeps": 2, + "term": "two-cycle", + "accumulates": 4, + "state_hash": "08395607b4f139a5" + } + }, + { + "name": "at-rest", + "landscape": { + "n": 2, + "couplings": [ + [ + 0, + 1, + -1 + ] + ], + "field": [ + 0, + 0 + ], + "cost": 0 + }, + "start": "+-", + "bound": 20, + "expect": { + "state": "+-", + "sweeps": 1, + "term": "fixed-point", + "accumulates": 2, + "state_hash": "08395407b4f1363f" + } + }, + { + "name": "bound", + "landscape": { + "n": 6, + "couplings": [ + [ + 0, + 1, + 1 + ], + [ + 1, + 2, + 1 + ], + [ + 2, + 3, + 1 + ], + [ + 3, + 4, + 1 + ], + [ + 4, + 5, + 1 + ] + ], + "field": [ + 3, + 0, + 0, + 0, + 0, + 0 + ], + "cost": 0 + }, + "start": "000000", + "bound": 3, + "expect": { + "state": "+++000", + "sweeps": 3, + "term": "bound", + "accumulates": 30, + "state_hash": "e1becdc5a7a62e62" + } + }, + { + "name": "chain-arrives", + "landscape": { + "n": 6, + "couplings": [ + [ + 0, + 1, + 1 + ], + [ + 1, + 2, + 1 + ], + [ + 2, + 3, + 1 + ], + [ + 3, + 4, + 1 + ], + [ + 4, + 5, + 1 + ] + ], + "field": [ + 3, + 0, + 0, + 0, + 0, + 0 + ], + "cost": 0 + }, + "start": "000000", + "bound": 20, + "expect": { + "state": "++++++", + "sweeps": 7, + "term": "fixed-point", + "accumulates": 70, + "state_hash": "fbbea7c5b66112a5" + } + }, + { + "name": "one-sweep-bound", + "landscape": { + "n": 6, + "couplings": [ + [ + 0, + 1, + 1 + ], + [ + 1, + 2, + 1 + ], + [ + 2, + 3, + 1 + ], + [ + 3, + 4, + 1 + ], + [ + 4, + 5, + 1 + ] + ], + "field": [ + 3, + 0, + 0, + 0, + 0, + 0 + ], + "cost": 0 + }, + "start": "000000", + "bound": 1, + "expect": { + "state": "+00000", + "sweeps": 1, + "term": "bound", + "accumulates": 10, + "state_hash": "1bb7783dd98232a8" + } + }, + { + "name": "field-and-cost", + "landscape": { + "n": 5, + "couplings": [ + [ + 0, + 1, + 1 + ], + [ + 0, + 2, + 1 + ], + [ + 1, + 2, + 1 + ], + [ + 2, + 3, + -1 + ], + [ + 3, + 4, + -1 + ] + ], + "field": [ + 0, + 0, + 0, + 0, + 2 + ], + "cost": 1 + }, + "start": "+++00", + "bound": 20, + "expect": { + "state": "+++-+", + "sweeps": 3, + "term": "fixed-point", + "accumulates": 30, + "state_hash": "4f62623ed6063127" + } + }, + { + "name": "zero-and-minus-at-the-start", + "landscape": { + "n": 5, + "couplings": [ + [ + 0, + 1, + 1 + ], + [ + 0, + 2, + 1 + ], + [ + 1, + 2, + 1 + ], + [ + 2, + 3, + -1 + ], + [ + 3, + 4, + -1 + ] + ], + "field": [ + 0, + 0, + 0, + 0, + 2 + ], + "cost": 1 + }, + "start": "0-0+-", + "bound": 20, + "expect": { + "state": "0000+", + "sweeps": 3, + "term": "fixed-point", + "accumulates": 30, + "state_hash": "6f6205b6f92d2361" + } + }, + { + "name": "fields-at-the-bound", + "landscape": { + "n": 4, + "couplings": [ + [ + 0, + 1, + 1 + ], + [ + 1, + 2, + -1 + ], + [ + 2, + 3, + 1 + ] + ], + "field": [ + 1099511627776, + 0, + 0, + -1099511627775 + ], + "cost": 1099511627776 + }, + "start": "+0-+", + "bound": 20, + "expect": { + "state": "0000", + "sweeps": 2, + "term": "fixed-point", + "accumulates": 12, + "state_hash": "b5d0e0774c7d7499" + } + }, + { + "name": "two-cycle-with-fields", + "landscape": { + "n": 2, + "couplings": [ + [ + 0, + 1, + 1 + ] + ], + "field": [ + 1, + -1 + ], + "cost": 0 + }, + "start": "00", + "bound": 60, + "expect": { + "state": "00", + "sweeps": 2, + "term": "two-cycle", + "accumulates": 4, + "state_hash": "082f2307b4e88e77" + } + } + ], + "exp": [ + [ + "0000000000000000", + "3ff0000000000000" + ], + [ + "8000000000000000", + "3ff0000000000000" + ], + [ + "3ff0000000000000", + "4005bf0a8b14576a" + ], + [ + "bff0000000000000", + "3fd78b56362cef38" + ], + [ + "3fe0000000000000", + "3ffa61298e1e069c" + ], + [ + "bfe0000000000000", + "3fe368b2fc6f960a" + ], + [ + "3e112e0be826d695", + "3ff000000044b830" + ], + [ + "be112e0be826d695", + "3fefffffff768fa1" + ], + [ + "3e2fc862d3e17360", + "3ff0000000fe4317" + ], + [ + "c087490a3d70a3d7", + "0000000000000001" + ], + [ + "c08749999999999a", + "0000000000000000" + ], + [ + "40862d999999999a", + "7fed75ae7a50ee14" + ], + [ + "40862e6666666666", + "7ff0000000000000" + ], + [ + "c086233333333333", + "000ff15b469edf89" + ], + [ + "c086232bdd7abcd2", + "001000000000007c" + ], + [ + "bfd89eecbfb15b57", + "3fe5c7ec94512762" + ], + [ + "3fbfc4ce62b34c91", + "3ff21d2f8d099365" + ], + [ + "bfe89eecbfb15b57", + "3fdda68f1fe886ce" + ], + [ + "3fcfc4ce62b34c91", + "3ff481e03a421fcd" + ], + [ + "bff277318fc50481", + "3fd42e8ceccc19dc" + ], + [ + "3fd7d39aca06796c", + "3ff737849a3b74bc" + ], + [ + "bff89eecbfb15b57", + "3fcb79465940a619" + ], + [ + "3fdfc4ce62b34c91", + "3ffa48cecd9719d0" + ], + [ + "bffec6a7ef9db22d", + "3fc2b33b75cadb6d" + ], + [ + "3fe3db00fdb00fdb", + "3ffdc1dabbaf7460" + ], + [ + "c00277318fc50481", + "3fb974e7bf2913df" + ], + [ + "3fe7d39aca06796c", + "4000d82eefad1f81" + ], + [ + "c004c6a7ef9db22d", + "3fb31237ca1a96f1" + ], + [ + "3feacec82cec82ce", + "40027d383635daa8" + ], + [ + "c007da858793dd98", + "3fa9f635cca4b512" + ], + [ + "3feec761f942ec61", + "4004ee9925ae86f9" + ], + [ + "c00aee631f8a0903", + "3fa1abc65cfe0c56" + ], + [ + "3ff15ffde2ccaafa", + "4007b29af55daf84" + ], + [ + "c00e0240b780346e", + "3f980e4216ae6f85" + ], + [ + "3ff35c4ac8f7dfc3", + "400ad4287a71745e" + ], + [ + "c0108b0f27bb2fec", + "3f905fa5e285e098" + ], + [ + "3ff55897af23148b", + "400e5f9dced22b6c" + ], + [ + "c01214fdf3b645a2", + "3f864a217317b009" + ], + [ + "3ff754e4954e4955", + "4011317c8da7d4cc" + ], + [ + "c0139eecbfb15b58", + "3f7e57cf7b22b2d2" + ], + [ + "3ff951317b797e1f", + "40137708ea09d38f" + ], + [ + "c014c6a7ef9db22d", + "3f76bb593e4869bf" + ], + [ + "3ffacec82cec82ce", + "40155d92790db1a4" + ], + [ + "c0165096bb98c7e2", + "3f6ef1ef65d24c05" + ], + [ + "3ffccb151317b797", + "4018303de0de7882" + ], + [ + "c017da858793dd98", + "3f65101a6b46324a" + ], + [ + "3ffec761f942ec61", + "401b6264f00f3aa1" + ], + [ + "c0196474538ef34e", + "3f5cac526627d319" + ], + [ + "400061d76fb71095", + "401f00a547703bdc" + ], + [ + "c01aee631f8a0903", + "3f538439af3ad02c" + ], + [ + "40015ffde2ccaafa", + "40218ca39f8ebf8e" + ], + [ + "c01c7851eb851eb8", + "3f4a9168d6ad8860" + ], + [ + "40025e2455e2455e", + "4023de3b23e451a2" + ], + [ + "c01e0240b780346e", + "3f4215697c61fd89" + ], + [ + "40035c4ac8f7dfc3", + "40267e405f81f04e" + ], + [ + "c01f29fbe76c8b44", + "3f3b185c0181e2b5" + ], + [ + "40041b1621b1621b", + "4028b07ae06582d0" + ], + [ + "c02059f559b3d07c", + "3f3271442e62882e" + ], + [ + "4005193c94c6fc7f", + "402bf3937b33dfdf" + ], + [ + "c0211eecbfb15b57", + "3f291b1aee8529de" + ], + [ + "4006176307dc96e4", + "402fa50277ce21fa" + ], + [ + "c021e3e425aee632", + "3f2116a3d84656bb" + ], + [ + "400715897af23149", + "4031e9adf374b0ed" + ], + [ + "c022a8db8bac710d", + "3f17433d4e070af4" + ], + [ + "400813afee07cbad", + "4034479079818b18" + ], + [ + "c0236dd2f1a9fbe8", + "3f0faaecb84fe997" + ], + [ + "400911d6611d6612", + "4036f58083892876" + ], + [ + "c02432ca57a786c2", + "3f058e046867480a" + ], + [ + "400a0ffcd4330076", + "4039fe11c87372a0" + ], + [ + "c024c6a7ef9db22d", + "3f0025e391a462a6" + ], + [ + "400acec82cec82ce", + "403c87c3b9c0164e" + ], + [ + "c0258b9f559b3d08", + "3ef5fb80b21832ce" + ], + [ + "400bcceea0021d33", + "4040266387029b77" + ], + [ + "c0265096bb98c7e2", + "3eedecc603d6f85c" + ], + [ + "400ccb151317b797", + "404248a58b67f7aa" + ], + [ + "c027158e219652bd", + "3ee45e579ed4481e" + ], + [ + "400dc93b862d51fc", + "4044b3143f7310bb" + ], + [ + "c027da858793dd98", + "3edbba558e9a6fdc" + ], + [ + "400ec761f942ec61", + "40476f38e096739e" + ], + [ + "c0289f7ced916873", + "3ed2df83e85f42fc" + ], + [ + "400fc5886c5886c6", + "404a87df3909922c" + ], + [ + "c0296474538ef34e", + "3ec9b13029640a5b" + ], + [ + "401061d76fb71095", + "404e09403dbf1d50" + ], + [ + "c029f851eb851eb8", + "3ec33f612b2e81b3" + ], + [ + "4010c13d1c13d1c1", + "40507c028af5460d" + ], + [ + "c02abd495182a993", + "3eba33b06a7b3c9e" + ], + [ + "40114050559e9ef3", + "4052a9949e85f792" + ], + [ + "c02b8240b780346e", + "3eb1d59eed9da275" + ], + [ + "4011bf638f296c26", + "405520d2065c3407" + ], + [ + "c02c47381d7dbf48", + "3ea8473934c4e21c" + ], + [ + "40123e76c8b43958", + "4057eb768e7a1444" + ], + [ + "c02d0c2f837b4a23", + "3ea0866bf42e3ca8" + ], + [ + "4012bd8a023f068a", + "405b14873db12c2b" + ], + [ + "c02dd126e978d4fe", + "3e967ee9e7113818" + ], + [ + "40133c9d3bc9d3bc", + "405ea87dd58984da" + ], + [ + "c02e961e4f765fd9", + "3e8e9faa1cf29d7c" + ], + [ + "4013bbb07554a0ef", + "40615abd08bf480f" + ], + [ + "c02f29fbe76c8b44", + "3e86f12dcd95fa10" + ], + [ + "40141b1621b1621b", + "40630c859a147603" + ], + [ + "c02feef34d6a161e", + "3e7f3b370190af19" + ], + [ + "40149a295b3c2f4d", + "406590d59c307e58" + ], + [ + "c03059f559b3d07c", + "3e7541fb3c8f6cf6" + ], + [ + "4015193c94c6fc7f", + "40686a46ea6e6368" + ], + [ + "c030bc710cb295ea", + "3e6cf038b6855a5a" + ], + [ + "4015984fce51c9b2", + "406ba418f7ac9636" + ], + [ + "c0311eecbfb15b57", + "3e63b2710a393cad" + ], + [ + "4016176307dc96e4", + "406f4b07a993104d" + ], + [ + "c031816872b020c5", + "3e5ad052f9f41bb7" + ], + [ + "4016967641676417", + "4071b6bece739e86" + ], + [ + "c031e3e425aee632", + "3e52403c34b60bc1" + ], + [ + "401715897af23149", + "40740de6870dcdcd" + ], + [ + "c0322dd2f1a9fbe7", + "3e4b5885b681d70e" + ], + [ + "401774ef274ef274", + "4076032b0d78c096" + ], + [ + "c032904ea4a8c155", + "3e429cf06ae512aa" + ], + [ + "4017f40260d9bfa7", + "4078ebb79887d314" + ], + [ + "c032f2ca57a786c2", + "3e39568eb1e26a82" + ], + [ + "401873159a648cd9", + "407c36a3d874bfcc" + ], + [ + "c03355460aa64c30", + "3e313f1b3febc405" + ], + [ + "4018f228d3ef5a0c", + "407ff0ef35af5dc6" + ], + [ + "c033b7c1bda5119d", + "3e277a53a9819a77" + ], + [ + "4019713c0d7a273e", + "408214a85da1cf6c" + ], + [ + "c0341a3d70a3d70a", + "3e1ff5ea64faf5e2" + ], + [ + "4019f04f4704f470", + "40847838968f2f93" + ], + [ + "c0347cb923a29c78", + "3e15c10f65abbc94" + ], + [ + "401a6f62808fc1a3", + "40872c966587e634" + ], + [ + "c034c6a7ef9db22d", + "3e104c20dc6f175b" + ], + [ + "401acec82cec82ce", + "40896fd6852c8fd1" + ], + [ + "c0352923a29c779a", + "3e062f8ef3ff0144" + ], + [ + "401b4ddb66775000", + "408ccc37a3627573" + ], + [ + "c0358b9f559b3d08", + "3dfe33a32d5e65d7" + ], + [ + "401bcceea0021d33", + "40904d2329319e83" + ], + [ + "c035ee1b089a0275", + "3df48e937d56ef10" + ], + [ + "401c4c01d98cea65", + "40927483d05b4cae" + ], + [ + "c0365096bb98c7e2", + "3debfbfed477947a" + ], + [ + "401ccb151317b797", + "4094e4be524164f2" + ], + [ + "c036b3126e978d50", + "3de30c35385392de" + ], + [ + "401d4a284ca284ca", + "4097a772cd9a0815" + ], + [ + "c037158e219652bd", + "3dd9ee07547cc808" + ], + [ + "401dc93b862d51fc", + "409ac786f3c4f805" + ], + [ + "c0375f7ced916873", + "3dd36cf57e4912c1" + ], + [ + "401e28a1328a1329", + "409d64e46f60722c" + ], + [ + "c037c1f8a0902de0", + "3dca71bc9e4a0271" + ], + [ + "401ea7b46c14e05b", + "40a0a38f9b680d84" + ], + [ + "c0382474538ef34d", + "3dc1ffda9639160f" + ], + [ + "401f26c7a59fad8c", + "40a2d65b763feb1a" + ], + [ + "c03886f0068db8bb", + "3db880b738896c2e" + ], + [ + "401fa5dadf2a7abf", + "40a55383668733d5" + ], + [ + "c038e96bb98c7e28", + "3db0ad8dd75a79ef" + ], + [ + "402012770c5aa3f9", + "40a824da92d0a5f6" + ], + [ + "c0394be76c8b4396", + "3da6b42f59535572" + ], + [ + "40205200a9200a92", + "40ab5580726a1d78" + ], + [ + "c039ae631f8a0903", + "3d9ee82ee654a041" + ], + [ + "4020918a45e5712b", + "40aef20cb5a1e969" + ], + [ + "c039f851eb851eb8", + "3d972781d6109e4e" + ], + [ + "4020c13d1c13d1c1", + "40b0fbc63d54cd4d" + ], + [ + "c03a5acd9e83e426", + "3d8f852c252a35d4" + ], + [ + "402100c6b8d9385a", + "40b33a39d599d1e2" + ], + [ + "c03abd495182a993", + "3d8574522b284000" + ], + [ + "40214050559e9ef3", + "40b5c493bda6b5e8" + ], + [ + "c03b1fc504816f00", + "3d7d34bfd13a5ef6" + ], + [ + "40217fd9f264058c", + "40b8a4db3276a4f9" + ], + [ + "c03b8240b780346e", + "3d73e115d69020f0" + ], + [ + "4021bf638f296c26", + "40bbe66aa390605a" + ], + [ + "c03be4bc6a7ef9db", + "3d6b0fd21982c71a" + ], + [ + "4021feed2beed2bf", + "40bf961c841360ec" + ], + [ + "c03c47381d7dbf48", + "3d626b745567c12a" + ], + [ + "40223e76c8b43958", + "40c1e13f03d35b6b" + ], + [ + "c03c9126e978d4fe", + "3d5b99475c8c204e" + ], + [ + "40226e299ee299ee", + "40c3a029ac7f4b4d" + ], + [ + "c03cf3a29c779a6b", + "3d52c90412dcaf2c" + ], + [ + "4022adb33ba80087", + "40c637fb81117446" + ], + [ + "c03d561e4f765fd9", + "3d49928f3e8b0264" + ], + [ + "4022ed3cd86d6720", + "40c92782715b0221" + ], + [ + "c03db89a02752546", + "3d4167f27b3f4845" + ], + [ + "40232cc67532cdb9", + "40cc7a551dbe3fcc" + ], + [ + "c03e1b15b573eab3", + "3d37b1ec783257e2" + ], + [ + "40236c5011f83452", + "40d01ec90f0e889a" + ], + [ + "c03e7d916872b021", + "3d3020ccd36f765c" + ], + [ + "4023abd9aebd9aec", + "40d24009e5e331ed" + ], + [ + "c03ee00d1b71758e", + "3d25f493425668c0" + ], + [ + "4023eb634b830185", + "40d4a955718c9a9e" + ], + [ + "c03f29fbe76c8b44", + "3d2072b8b4d86a3c" + ], + [ + "40241b1621b1621b", + "40d6adc71ab34e57" + ], + [ + "c03f8c779a6b50b1", + "3d1664187b425464" + ], + [ + "40245a9fbe76c8b4", + "40d9acde5d4bf8d4" + ], + [ + "c03feef34d6a161e", + "3d0e7b28263ca627" + ], + [ + "40249a295b3c2f4d", + "40dd114fca1e8908" + ], + [ + "c04028b780346dc6", + "3d04bf4194549717" + ], + [ + "4024d9b2f80195e7", + "40e0743fc38a83f9" + ], + [ + "c04059f559b3d07c", + "3cfc3e4397ad8854" + ], + [ + "4025193c94c6fc7f", + "40e2a0cb5613d8c6" + ], + [ + "c0408b3333333333", + "3cf339505dfb382e" + ], + [ + "402558c6318c6318", + "40e516df8debcaa4" + ], + [ + "c040bc710cb295ea", + "3cea2b6e927fb14d" + ], + [ + "4025984fce51c9b2", + "40e7e033a1f1617f" + ], + [ + "c040e16872b020c5", + "3ce39af5c0ab10c1" + ], + [ + "4025c802a4802a48", + "40ea34fd4e9a03f8" + ], + [ + "c04112a64c2f837b", + "3cdab05bc0d28192" + ], + [ + "4026078c414590e1", + "40edab6ae63679ff" + ], + [ + "c04143e425aee632", + "3cd22a7a417947bf" + ], + [ + "40264715de0af77a", + "40f0cb7b90e87802" + ], + [ + "c0417521ff2e48e9", + "3cc8babd616dd6e3" + ], + [ + "4026869f7ad05e14", + "40f3038dbcc26429" + ], + [ + "c041a65fd8adab9f", + "3cc0d50c657909b8" + ], + [ + "4026c6291795c4ac", + "40f586ae674bddaa" + ], + [ + "c041d79db22d0e56", + "3cb6e9f2f1dbd97c" + ], + [ + "402705b2b45b2b45", + "40f85ec849b0a54c" + ], + [ + "c04208db8bac710d", + "3caf315f6a385b2c" + ], + [ + "4027453c512091df", + "40fb97158b2a7d7f" + ], + [ + "c0422dd2f1a9fbe7", + "3ca75e5685959e2e" + ], + [ + "402774ef274ef274", + "40fe48b705a4f75a" + ], + [ + "c0425f10cb295e9e", + "3c9fcfd06b8d2e35" + ], + [ + "4027b478c414590e", + "41012485d9502f56" + ], + [ + "c042904ea4a8c155", + "3c95a7204ec4668d" + ], + [ + "4027f40260d9bfa7", + "4103685bb97962f3" + ], + [ + "c042c18c7e28240b", + "3c8d79e93309b985" + ], + [ + "4028338bfd9f2640", + "4105f8ce048971f7" + ], + [ + "c042f2ca57a786c2", + "3c841029176a35fd" + ], + [ + "402873159a648cd9", + "4108dffc070dc48b" + ], + [ + "c04324083126e979", + "3c7b4fe79627f874" + ], + [ + "4028b29f3729f373", + "410c295b6df55af8" + ], + [ + "c04355460aa64c30", + "3c729712ce9ac9ee" + ], + [ + "4028f228d3ef5a0c", + "410fe1e583253674" + ], + [ + "c0437a3d70a3d70a", + "3c6bdaa25b6f3967" + ], + [ + "402921dbaa1dbaa1", + "41117f6830a8cda2" + ], + [ + "c043ab7b4a2339c1", + "3c62f5801b30ce3a" + ], + [ + "4029616546e3213b", + "4113cf402415674c" + ], + [ + "c043dcb923a29c78", + "3c59cf1de1e2d930" + ], + [ + "4029a0eee3a887d4", + "41166d4aac44254d" + ], + [ + "c0440df6fd21ff2e", + "3c51912a6d2d597a" + ], + [ + "4029e078806dee6d", + "411963dcbfe0cda2" + ], + [ + "c0443f34d6a161e5", + "3c47ea08ef03165d" + ], + [ + "402a20021d335506", + "411cbea8cd205dda" + ], + [ + "c0447072b020c49c", + "3c4046fe114153b3" + ], + [ + "402a5f8bb9f8bba0", + "412045767314b93c" + ], + [ + "c044a1b089a02752", + "3c3628911ca31694" + ], + [ + "402a9f1556be2238", + "41226bd383a1b8c3" + ], + [ + "c044c6a7ef9db22d", + "3c3099abf14fcbae" + ], + [ + "402acec82cec82ce", + "412438460def9d3a" + ], + [ + "c044f7e5c91d14e4", + "3c26991e6bcbdc5f" + ], + [ + "402b0e51c9b1e968", + "4126e430e6309f6f" + ], + [ + "c0452923a29c779a", + "3c1ec3567bd3f719" + ], + [ + "402b4ddb66775000", + "4129ea78a3b031c7" + ], + [ + "c0455a617c1bda51", + "3c14f062f2486dca" + ], + [ + "402b8d65033cb69a", + "412d570db8031200" + ], + [ + "c0458b9f559b3d08", + "3c0c81254871b24a" + ], + [ + "402bcceea0021d33", + "41309bba351cff3f" + ], + [ + "c045bcdd2f1a9fbe", + "3c0366d653f5acd8" + ], + [ + "402c0c783cc783cc", + "4132cd7d191a2946" + ], + [ + "c045ee1b089a0275", + "3bfa69673899afaf" + ], + [ + "402c4c01d98cea65", + "41354979105886ce" + ], + [ + "c04613126e978d50", + "3bf3c962f1ecfa37" + ], + [ + "402c7bb4afbb4afc", + "41375d8d7c719cdd" + ], + [ + "c04644504816f006", + "3beaef8f2e069bd3" + ], + [ + "402cbb3e4c80b194", + "413a73de2d37669a" + ], + [ + "c046758e219652bd", + "3be2557edc32c87e" + ], + [ + "402cfac7e946182d", + "413df29a9319d773" + ], + [ + "c046a6cbfb15b574", + "3bd8f54cf1d86208" + ], + [ + "402d3a51860b7ec7", + "4140f3c74f24cff6" + ], + [ + "c046d809d495182b", + "3bd0fce879fb4108" + ], + [ + "402d79db22d0e561", + "4143312c73d0c6e6" + ], + [ + "c0470947ae147ae1", + "3bc72035db65823f" + ], + [ + "402db964bf964bf9", + "4145ba542c7c3287" + ], + [ + "c0473a858793dd98", + "3bbf7b3d3f479ba6" + ], + [ + "402df8ee5c5bb292", + "41489940fd7aa35e" + ], + [ + "c0475f7ced916873", + "3bb795ad0ccd166a" + ], + [ + "402e28a1328a1329", + "414b001c23f59e5b" + ], + [ + "c04790bac710cb29", + "3bb00d92b9ba80d7" + ], + [ + "402e682acf4f79c1", + "414e916019c949ad" + ], + [ + "c047c1f8a0902de0", + "3ba5da66c1adf59d" + ], + [ + "402ea7b46c14e05b", + "41514da739d772e4" + ], + [ + "c047f3367a0f9097", + "3b9dbfb65c3beca2" + ], + [ + "402ee73e08da46f4", + "415396ec4ccd1e6b" + ], + [ + "c0482474538ef34d", + "3b943fabd257ed94" + ], + [ + "402f26c7a59fad8c", + "41562d859ab5d34d" + ], + [ + "c04855b22d0e5604", + "3b8b9094d3f59573" + ], + [ + "402f665142651426", + "41591baab96bdd13" + ], + [ + "c04886f0068db8bb", + "3b82c31892ab7cae" + ], + [ + "402fa5dadf2a7abf", + "415c6cecd4a1c440" + ], + [ + "c048abe76c8b4395", + "3b7c1c981e4da7fe" + ], + [ + "402fd58db558db55", + "415f336f602aff2e" + ], + [ + "c048dd25460aa64c", + "3b7322657b0cd322" + ], + [ + "40300a8ba90f20f7", + "4161a963a016e4ad" + ], + [ + "c0490e631f8a0903", + "3b6a0c3bec635378" + ], + [ + "40302a507771d444", + "4163fec795efe0fb" + ], + [ + "c0493fa0f9096bba", + "3b61bac3fabbcbf2" + ], + [ + "40304a1545d48791", + "4166a319bf191eb9" + ], + [ + "c04970ded288ce70", + "3b5822aa45b92fcd" + ], + [ + "403069da14373add", + "4169a0c7dc4d208f" + ], + [ + "c049a21cac083127", + "3b506d89c0286a20" + ], + [ + "4030899ee299ee2a", + "416d03a06c495f67" + ], + [ + "c049d35a858793de", + "3b465d0a15736162" + ], + [ + "4030a963b0fca176", + "41706c80a37fb0b1" + ], + [ + "c049f851eb851eb8", + "3b40c0fb6a407791" + ], + [ + "4030c13d1c13d1c1", + "4172070660060e20" + ], + [ + "c04a298fc504816f", + "3b36cea1ec388db2" + ], + [ + "4030e101ea76850e", + "417468c97b20472c" + ], + [ + "c04a5acd9e83e426", + "3b2f0c2fbf339d8a" + ], + [ + "403100c6b8d9385a", + "41771b1d3f6afab6" + ], + [ + "c04a8c0b780346dc", + "3b2521f8a82e247b" + ], + [ + "4031208b873beba7", + "417a28a6b7adfc24" + ], + [ + "c04abd495182a993", + "3b1cc4a55a616a70" + ], + [ + "40314050559e9ef3", + "417d9d72fad0478a" + ], + [ + "c04aee872b020c4a", + "3b1394c81733e815" + ], + [ + "4031601524015240", + "4180c3935f0ff4ba" + ], + [ + "c04b1fc504816f00", + "3b0aa7f29f1f98d2" + ], + [ + "40317fd9f264058c", + "4182fa9a1854b298" + ], + [ + "c04b44bc6a7ef9db", + "3b03f83e1404917c" + ], + [ + "403197b35d7b35d7", + "4184d4fd637fb266" + ], + [ + "c04b75fa43fe5c92", + "3afb2f5845102710" + ], + [ + "4031b7782bdde924", + "4187959d047dc52a" + ], + [ + "c04ba7381d7dbf48", + "3af280e9556afd12" + ], + [ + "4031d73cfa409c70", + "418ab355e907740b" + ], + [ + "c04bd875f6fd21ff", + "3ae930672f2ac32c" + ], + [ + "4031f701c8a34fbd", + "418e3a750c077c8f" + ], + [ + "c04c09b3d07c84b6", + "3ae12522f25a1c92" + ], + [ + "403216c69706030a", + "41911c73bbeda5f6" + ], + [ + "c04c3af1a9fbe76c", + "3ad756f9436e768a" + ], + [ + "4032368b6568b656", + "41935f389f98eafb" + ], + [ + "c04c6c2f837b4a23", + "3acfc5c9ffef2f71" + ], + [ + "4032565033cb69a3", + "4195ee75dca65d62" + ], + [ + "c04c9126e978d4fe", + "3ac7cd869f309632" + ], + [ + "40326e299ee299ee", + "419812a63b94d009" + ], + [ + "c04cc264c2f837b5", + "3ac033966fb99adc" + ], + [ + "40328dee6d454d3b", + "419b40e45b4e7802" + ], + [ + "c04cf3a29c779a6b", + "3ab60e26a0cb7c5a" + ], + [ + "4032adb33ba80087", + "419edab78378bd56" + ], + [ + "c04d24e075f6fd22", + "3aae0628d0a77cf3" + ], + [ + "4032cd780a0ab3d4", + "41a1772b497d8d4a" + ], + [ + "c04d561e4f765fd9", + "3aa46f9f0f551fa6" + ], + [ + "4032ed3cd86d6720", + "41a3c5ec99263bbe" + ], + [ + "c04d875c28f5c28f", + "3a9bd1db3a48b542" + ], + [ + "40330d01a6d01a6d", + "41a662bbacd3a4ed" + ], + [ + "c04db89a02752546", + "3a92ef8696341c44" + ], + [ + "40332cc67532cdb9", + "41a957e89df2206a" + ], + [ + "c04ddd916872b021", + "3a8c5f2a13a5de53" + ], + [ + "4033449fe049fe05", + "41abd161488ead08" + ], + [ + "c04e0ecf41f212d7", + "3a834fb52be570cf" + ], + [ + "40336464aeacb151", + "41af7e4b9e3799ad" + ], + [ + "c04e400d1b71758e", + "3a7a49eab1a2a006" + ], + [ + "403384297d0f649e", + "41b1d3c3c9d3a304" + ], + [ + "c04e714af4f0d845", + "3a71e4c00b0ecb45" + ], + [ + "4033a3ee4b7217ea", + "41b42ec1111fe088" + ], + [ + "c04ea288ce703afb", + "3a685bd1b6fc4883" + ], + [ + "4033c3b319d4cb36", + "41b6d969ec722242" + ], + [ + "c04ed3c6a7ef9db2", + "3a609470b6509647" + ], + [ + "4033e377e8377e83", + "41b9de45220dbe3f" + ], + [ + "c04f0504816f0069", + "3a5691ff5054fb80" + ], + [ + "4034033cb69a31d0", + "41bd493d848e7f95" + ], + [ + "c04f29fbe76c8b44", + "3a50e8a7fa1627cf" + ], + [ + "40341b1621b1621b", + "41c012a17a5e12a3" + ], + [ + "c04f5b39c0ebedfa", + "3a4704a425df05ef" + ], + [ + "40343adaf0141567", + "41c2324732bfc475" + ], + [ + "c04f8c779a6b50b1", + "3a3f55b585209aae" + ], + [ + "40345a9fbe76c8b4", + "41c499c14e3ad50c" + ], + [ + "c04fbdb573eab368", + "3a355403c987dd12" + ], + [ + "40347a648cd97c01", + "41c7528d5f9f1beb" + ], + [ + "c04feef34d6a161e", + "3a2d08c5448a098c" + ], + [ + "40349a295b3c2f4d", + "41ca6769fbe50bc3" + ], + [ + "c05010189374bc6a", + "3a23c326a6fddc90" + ], + [ + "4034b9ee299ee299", + "41cde4812400b3a5" + ], + [ + "c05028b780346dc6", + "3a1ae7122195a7df" + ], + [ + "4034d9b2f80195e7", + "41d0ebcc24a67da4" + ], + [ + "c0503b3333333333", + "3a1427882b4a7649" + ], + [ + "4034f18c6318c631", + "41d292bfaedffa86" + ], + [ + "c05053d21ff2e48e", + "3a0b6fb868588d5c" + ], + [ + "40351151317b797d", + "41d506f8d2f4584c" + ], + [ + "c0506c710cb295ea", + "3a02acba9e5d4e41" + ], + [ + "40353115ffde2ccb", + "41d7ce330e26306a" + ], + [ + "c050850ff9724745", + "39f96c0d61c935d6" + ], + [ + "403550dace40e017", + "41daf365ec01f666" + ], + [ + "c0509daee631f8a1", + "39f14dbcae1b0e49" + ], + [ + "4035709f9ca39364", + "41de82fbeaca7173" + ], + [ + "c050b64dd2f1a9fc", + "39e78e3e5a3ed0de" + ], + [ + "403590646b0646b0", + "41e14581bf3aef7a" + ], + [ + "c050ceecbfb15b57", + "39e00883a533e6c6" + ], + [ + "4035b0293968f9fc", + "41e38db346b8e787" + ], + [ + "c050e16872b020c5", + "39d805e47311ca54" + ], + [ + "4035c802a4802a48", + "41e576735ecedea4" + ], + [ + "c050fa075f6fd220", + "39d059f42afc4132" + ], + [ + "4035e7c772e2dd94", + "41e84c6844e828ea" + ], + [ + "c05112a64c2f837b", + "39c642610ba63355" + ], + [ + "4036078c414590e1", + "41eb824801243ef8" + ], + [ + "c0512b4538ef34d7", + "39be4d4217b95a82" + ], + [ + "403627510fa8442e", + "41ef24bee4faed2e" + ], + [ + "c05143e425aee632", + "39b4a003d8cec5ab" + ], + [ + "40364715de0af77a", + "41f1a112f5084401" + ], + [ + "c0515c83126e978d", + "39ac13bc33d16da8" + ], + [ + "403666daac6daac6", + "41f3f55daa931f4a" + ], + [ + "c0517521ff2e48e9", + "39a31c5dd0122741" + ], + [ + "4036869f7ad05e14", + "41f698716a5c0e5d" + ], + [ + "c051879db22d0e56", + "399ca259ad5a3064" + ], + [ + "40369e78e5e78e5e", + "41f8cd3a9749397b" + ], + [ + "c051a03c9eecbfb1", + "39937d70297e1424" + ], + [ + "4036be3db44a41aa", + "41fc141f99e01dfa" + ], + [ + "c051b8db8bac710d", + "398a882b885b16c8" + ], + [ + "4036de0282acf4f8", + "41ffc9db79020097" + ], + [ + "c051d17a786c2268", + "39820f1f876dddce" + ], + [ + "4036fdc7510fa844", + "4201fe899f8c1614" + ], + [ + "c051ea19652bd3c3", + "39789580805d23f2" + ], + [ + "40371d8c1f725b90", + "42045f2da740ed1e" + ], + [ + "c05202b851eb851f", + "3970bbb3cbe0e052" + ], + [ + "40373d50edd50edd", + "4207103c6a113f4f" + ], + [ + "c0521b573eab367a", + "3966c771f3880131" + ], + [ + "40375d15bc37c22a", + "420a1c55efd20019" + ], + [ + "c0522dd2f1a9fbe7", + "396110b27d41d295" + ], + [ + "403774ef274ef274", + "420ca8fc66457eb1" + ], + [ + "c0524671de69ad43", + "39573b2644d5ffec" + ], + [ + "403794b3f5b1a5c2", + "42103931b4c7a838" + ], + [ + "c0525f10cb295e9e", + "394f9fe9661e73a0" + ], + [ + "4037b478c414590e", + "42125defcc7c05c9" + ], + [ + "c05277afb7e90ff9", + "394586856c6425a9" + ], + [ + "4037d43d92770c5a", + "4214cb2e9e855f31" + ], + [ + "c052904ea4a8c155", + "393d4d8681710da4" + ], + [ + "4037f40260d9bfa7", + "42178a8282f94b32" + ], + [ + "c010000000000000", + "3f92c155b8213cf4" + ], + [ + "c020000000000000", + "3f35fc21041027ad" + ], + [ + "c028000000000000", + "3ed9c54c3b43bc8b" + ], + [ + "c034000000000000", + "3e21b48655f37267" + ], + [ + "c040000000000000", + "3d0c8464f7616468" + ], + [ + "c051000000000000", + "39cdca23bae16424" + ], + [ + "c064000000000000", + "3181fc63223fac81" + ], + [ + "c074000000000000", + "231437dfdde45c6a" + ], + [ + "c00aaaaaaaaaaaab", + "3fa243dc957d03ee" + ], + [ + "c01aaaaaaaaaaaab", + "3f54d9d0239e543a" + ], + [ + "c024000000000000", + "3f07cd79b5647c9a" + ], + [ + "c030aaaaaaaaaaab", + "3e6f04e08994f94a" + ], + [ + "c03aaaaaaaaaaaab", + "3d8712ae9b9a7bd6" + ], + [ + "c04c555555555556", + "3ad2fdd222d397d1" + ], + [ + "c060aaaaaaaaaaab", + "33e8f1d7a2486492" + ], + [ + "c070aaaaaaaaaaab", + "27e371e731160929" + ], + [ + "bff6db6db6db6db7", + "3fceace299fc26b5" + ], + [ + "c006db6db6db6db7", + "3fad67ceeaf4c122" + ], + [ + "c011249249249249", + "3f8c30302f19be62" + ], + [ + "c01c924924924925", + "3f49e71d0af51880" + ], + [ + "c026db6db6db6db7", + "3ee6d13b15f2bed7" + ], + [ + "c038492492492492", + "3dbf313dfb1dabc8" + ], + [ + "c04c924924924925", + "3ac797cafeb0ed40" + ], + [ + "c05c924924924925", + "35a16503d6e9b230" + ], + [ + "bff0000000000000", + "3fd78b56362cef38" + ], + [ + "c000000000000000", + "3fc152aaa3bf81cc" + ], + [ + "c008000000000000", + "3fa97db0ccceb0af" + ], + [ + "c014000000000000", + "3f7b993fe00d5376" + ], + [ + "c020000000000000", + "3f35fc21041027ad" + ], + [ + "c031000000000000", + "3e6639e3175a689d" + ], + [ + "c044000000000000", + "3c539792499b1a24" + ], + [ + "c054000000000000", + "38b7fd974d372e44" + ], + [ + "bfe89d89d89d89d8", + "3fdda7d7ffb43a1c" + ], + [ + "bff89d89d89d89d8", + "3fcb7ba7dba58e72" + ], + [ + "c002762762762762", + "3fb97836f57c47ee" + ], + [ + "c00ec4ec4ec4ec4e", + "3f95dfda0c91c0e2" + ], + [ + "c0189d89d89d89d8", + "3f61690f5d0fc882" + ], + [ + "c02a276276276276", + "3ec18ea24e03deeb" + ], + [ + "c03ec4ec4ec4ec4e", + "3d2868e2df930b58" + ], + [ + "c04ec4ec4ec4ec4e", + "3a629eac185248e6" + ], + [ + "bfe11acee560242a", + "3fe2c0182aa00cc9" + ], + [ + "bff11acee560242a", + "3fd5f938a40b9e49" + ], + [ + "bff9a8365810363f", + "3fc9c02f909c9603" + ], + [ + "c00561829eb82d34", + "3fb1aeac3d598f29" + ], + [ + "c0111acee560242a", + "3f8c7551c3a7319c" + ], + [ + "c0222c7bd3b6266d", + "3f1da8b6f42564be" + ], + [ + "c03561829eb82d34", + "3e01cd0b38f30fa2" + ], + [ + "c04561829eb82d34", + "3c13cdfb889fb4c1" + ], + [ + "bfd611a7b9611a7c", + "3fe6aabdd463768d" + ], + [ + "bfe611a7b9611a7c", + "3fe00e54095f7e26" + ], + [ + "bff08d3dcb08d3dd", + "3fd6bf0a48293be4" + ], + [ + "bffb9611a7b9611b", + "3fc6d368e971923d" + ], + [ + "c00611a7b9611a7c", + "3fb0399d50395103" + ], + [ + "c01772c234f72c24", + "3f674f2200715fb6" + ], + [ + "c02b9611a7b9611b", + "3eb1284a9967e964" + ], + [ + "c03b9611a7b9611b", + "3d726603fc57a844" + ], + [ + "bfc2073615a240e7", + "3febcbc44cecabe1" + ], + [ + "bfd2073615a240e7", + "3fe824ecc9138bdc" + ], + [ + "bfdb0ad12073615a", + "3fe4f8e623f8a5f8" + ], + [ + "bfe689039b0ad121", + "3fdfa5bf9a98e504" + ], + [ + "bff2073615a240e7", + "3fd4bdbfe872097e" + ], + [ + "c00327a976fc64f5", + "3fb75ae183c3b282" + ], + [ + "c01689039b0ad121", + "3f6d49391bb81a44" + ], + [ + "c02689039b0ad121", + "3eeacd5f0f32126a" + ], + [ + "bfb2afa64e7b5417", + "3fedbf58785028c8" + ], + [ + "bfc2afa64e7b5417", + "3feba74883221e4e" + ], + [ + "bfcc077975b8fe22", + "3fe9b4f4a3a59d19" + ], + [ + "bfd75b8fe21a291d", + "3fe6371107486728" + ], + [ + "bfe2afa64e7b5417", + "3fe1d8a4e2f6d720" + ], + [ + "bff3daa0b3630958", + "3fd2811eb237527c" + ], + [ + "c0075b8fe21a291d", + "3fab9f42b4325cc3" + ], + [ + "c0175b8fe21a291d", + "3f67d7d92f7ebc49" + ], + [ + "bfa47ae147ae147b", + "3feebec97e700b8d" + ], + [ + "bfb47ae147ae147b", + "3fed8a2b4ac44685" + ], + [ + "bfbeb851eb851eb8", + "3fec61a6f7bb55d8" + ], + [ + "bfc999999999999a", + "3fea330ad6166159" + ], + [ + "bfd47ae147ae147b", + "3fe73c9ce7c81bce" + ], + [ + "bfe5c28f5c28f5c3", + "3fe03634d6b9dbdd" + ], + [ + "bff999999999999a", + "3fc9d7bebefb4e15" + ], + [ + "c00999999999999a", + "3fa4dec899fffb46" + ] + ], + "schedule": [ + { + "t0": 0.3, + "t0_bits": "3fd3333333333333", + "sweeps": 1, + "temperatures_bits": [ + "0000000000000000" + ] + }, + { + "t0": 0.3, + "t0_bits": "3fd3333333333333", + "sweeps": 2, + "temperatures_bits": [ + "3fd3333333333333", + "0000000000000000" + ] + }, + { + "t0": 0.3, + "t0_bits": "3fd3333333333333", + "sweeps": 7, + "temperatures_bits": [ + "3fd3333333333333", + "3fd0000000000000", + "3fc999999999999a", + "3fc3333333333333", + "3fb999999999999a", + "3fa9999999999998", + "0000000000000000" + ] + }, + { + "t0": 0.3, + "t0_bits": "3fd3333333333333", + "sweeps": 40, + "temperatures_bits": [ + "3fd3333333333333", + "3fd2b52b52b52b53", + "3fd2372372372372", + "3fd1b91b91b91b92", + "3fd13b13b13b13b1", + "3fd0bd0bd0bd0bd1", + "3fd03f03f03f03f0", + "3fcf81f81f81f81f", + "3fce85e85e85e85e", + "3fcd89d89d89d89d", + "3fcc8dc8dc8dc8dc", + "3fcb91b91b91b91c", + "3fca95a95a95a95a", + "3fc999999999999a", + "3fc89d89d89d89d8", + "3fc7a17a17a17a17", + "3fc6a56a56a56a56", + "3fc5a95a95a95a95", + "3fc4ad4ad4ad4ad4", + "3fc3b13b13b13b14", + "3fc2b52b52b52b53", + "3fc1b91b91b91b92", + "3fc0bd0bd0bd0bd1", + "3fbf81f81f81f81e", + "3fbd89d89d89d89c", + "3fbb91b91b91b91a", + "3fb999999999999a", + "3fb7a17a17a17a18", + "3fb5a95a95a95a96", + "3fb3b13b13b13b12", + "3fb1b91b91b91b90", + "3faf81f81f81f820", + "3fab91b91b91b91c", + "3fa7a17a17a17a18", + "3fa3b13b13b13b10", + "3f9f81f81f81f820", + "3f97a17a17a17a10", + "3f8f81f81f81f820", + "3f7f81f81f81f840", + "0000000000000000" + ] + }, + { + "t0": 0.3, + "t0_bits": "3fd3333333333333", + "sweeps": 41, + "temperatures_bits": [ + "3fd3333333333333", + "3fd2b851eb851eb8", + "3fd23d70a3d70a3d", + "3fd1c28f5c28f5c2", + "3fd147ae147ae148", + "3fd0cccccccccccd", + "3fd051eb851eb852", + "3fcfae147ae147ae", + "3fceb851eb851eb8", + "3fcdc28f5c28f5c2", + "3fcccccccccccccc", + "3fcbd70a3d70a3d6", + "3fcae147ae147ae1", + "3fc9eb851eb851eb", + "3fc8f5c28f5c28f6", + "3fc8000000000000", + "3fc70a3d70a3d70a", + "3fc6147ae147ae14", + "3fc51eb851eb851e", + "3fc428f5c28f5c29", + "3fc3333333333333", + "3fc23d70a3d70a3d", + "3fc147ae147ae147", + "3fc051eb851eb852", + "3fbeb851eb851eb8", + "3fbccccccccccccc", + "3fbae147ae147ae0", + "3fb8f5c28f5c28f4", + "3fb70a3d70a3d70a", + "3fb51eb851eb851e", + "3fb3333333333334", + "3fb147ae147ae148", + "3faeb851eb851eb8", + "3faae147ae147ae4", + "3fa70a3d70a3d708", + "3fa3333333333330", + "3f9eb851eb851eb0", + "3f970a3d70a3d700", + "3f8eb851eb851ec0", + "3f7eb851eb851ec0", + "0000000000000000" + ] + }, + { + "t0": 0.7, + "t0_bits": "3fe6666666666666", + "sweeps": 1, + "temperatures_bits": [ + "0000000000000000" + ] + }, + { + "t0": 0.7, + "t0_bits": "3fe6666666666666", + "sweeps": 2, + "temperatures_bits": [ + "3fe6666666666666", + "0000000000000000" + ] + }, + { + "t0": 0.7, + "t0_bits": "3fe6666666666666", + "sweeps": 7, + "temperatures_bits": [ + "3fe6666666666666", + "3fe2aaaaaaaaaaaa", + "3fddddddddddddde", + "3fd6666666666666", + "3fcdddddddddddde", + "3fbdddddddddddd8", + "0000000000000000" + ] + }, + { + "t0": 0.7, + "t0_bits": "3fe6666666666666", + "sweeps": 40, + "temperatures_bits": [ + "3fe6666666666666", + "3fe5d35d35d35d35", + "3fe5405405405405", + "3fe4ad4ad4ad4ad4", + "3fe41a41a41a41a4", + "3fe3873873873873", + "3fe2f42f42f42f42", + "3fe2612612612612", + "3fe1ce1ce1ce1ce2", + "3fe13b13b13b13b1", + "3fe0a80a80a80a80", + "3fe0150150150150", + "3fdf03f03f03f03e", + "3fddddddddddddde", + "3fdcb7cb7cb7cb7c", + "3fdb91b91b91b91b", + "3fda6ba6ba6ba6ba", + "3fd9459459459459", + "3fd81f81f81f81f7", + "3fd6f96f96f96f97", + "3fd5d35d35d35d36", + "3fd4ad4ad4ad4ad5", + "3fd3873873873873", + "3fd2612612612612", + "3fd13b13b13b13b0", + "3fd015015015014f", + "3fcdddddddddddde", + "3fcb91b91b91b91c", + "3fc9459459459458", + "3fc6f96f96f96f94", + "3fc4ad4ad4ad4ad4", + "3fc2612612612614", + "3fc0150150150150", + "3fbb91b91b91b918", + "3fb6f96f96f96f98", + "3fb2612612612610", + "3fab91b91b91b910", + "3fa2612612612610", + "3f92612612612620", + "0000000000000000" + ] + }, + { + "t0": 0.7, + "t0_bits": "3fe6666666666666", + "sweeps": 41, + "temperatures_bits": [ + "3fe6666666666666", + "3fe5d70a3d70a3d7", + "3fe547ae147ae147", + "3fe4b851eb851eb8", + "3fe428f5c28f5c29", + "3fe3999999999999", + "3fe30a3d70a3d70a", + "3fe27ae147ae147b", + "3fe1eb851eb851eb", + "3fe15c28f5c28f5c", + "3fe0cccccccccccc", + "3fe03d70a3d70a3d", + "3fdf5c28f5c28f5c", + "3fde3d70a3d70a3d", + "3fdd1eb851eb851e", + "3fdc000000000000", + "3fdae147ae147ae1", + "3fd9c28f5c28f5c2", + "3fd8a3d70a3d70a3", + "3fd7851eb851eb85", + "3fd6666666666666", + "3fd547ae147ae147", + "3fd428f5c28f5c28", + "3fd30a3d70a3d70a", + "3fd1eb851eb851eb", + "3fd0cccccccccccc", + "3fcf5c28f5c28f5c", + "3fcd1eb851eb851e", + "3fcae147ae147ae2", + "3fc8a3d70a3d70a4", + "3fc6666666666668", + "3fc428f5c28f5c28", + "3fc1eb851eb851ec", + "3fbf5c28f5c28f60", + "3fbae147ae147ae0", + "3fb6666666666668", + "3fb1eb851eb851e8", + "3faae147ae147ae0", + "3fa1eb851eb851f0", + "3f91eb851eb851e0", + "0000000000000000" + ] + }, + { + "t0": 1.3, + "t0_bits": "3ff4cccccccccccd", + "sweeps": 1, + "temperatures_bits": [ + "0000000000000000" + ] + }, + { + "t0": 1.3, + "t0_bits": "3ff4cccccccccccd", + "sweeps": 2, + "temperatures_bits": [ + "3ff4cccccccccccd", + "0000000000000000" + ] + }, + { + "t0": 1.3, + "t0_bits": "3ff4cccccccccccd", + "sweeps": 7, + "temperatures_bits": [ + "3ff4cccccccccccd", + "3ff1555555555556", + "3febbbbbbbbbbbbc", + "3fe4cccccccccccd", + "3fdbbbbbbbbbbbbc", + "3fcbbbbbbbbbbbb8", + "0000000000000000" + ] + }, + { + "t0": 1.3, + "t0_bits": "3ff4cccccccccccd", + "sweeps": 40, + "temperatures_bits": [ + "3ff4cccccccccccd", + "3ff4444444444444", + "3ff3bbbbbbbbbbbc", + "3ff3333333333333", + "3ff2aaaaaaaaaaab", + "3ff2222222222222", + "3ff199999999999a", + "3ff1111111111111", + "3ff0888888888889", + "3ff0000000000000", + "3feeeeeeeeeeeef0", + "3fedddddddddddde", + "3feccccccccccccd", + "3febbbbbbbbbbbbc", + "3feaaaaaaaaaaaab", + "3fe999999999999a", + "3fe8888888888889", + "3fe7777777777778", + "3fe6666666666666", + "3fe5555555555556", + "3fe4444444444445", + "3fe3333333333334", + "3fe2222222222222", + "3fe1111111111111", + "3fe0000000000000", + "3fdddddddddddddc", + "3fdbbbbbbbbbbbbc", + "3fd999999999999a", + "3fd7777777777778", + "3fd5555555555556", + "3fd3333333333334", + "3fd1111111111114", + "3fcddddddddddde0", + "3fc9999999999998", + "3fc5555555555558", + "3fc1111111111110", + "3fb9999999999990", + "3fb1111111111110", + "3fa1111111111120", + "0000000000000000" + ] + }, + { + "t0": 1.3, + "t0_bits": "3ff4cccccccccccd", + "sweeps": 41, + "temperatures_bits": [ + "3ff4cccccccccccd", + "3ff447ae147ae148", + "3ff3c28f5c28f5c3", + "3ff33d70a3d70a3e", + "3ff2b851eb851eb8", + "3ff2333333333333", + "3ff1ae147ae147ae", + "3ff128f5c28f5c29", + "3ff0a3d70a3d70a4", + "3ff01eb851eb851f", + "3fef333333333334", + "3fee28f5c28f5c29", + "3fed1eb851eb851f", + "3fec147ae147ae14", + "3feb0a3d70a3d70b", + "3fea000000000000", + "3fe8f5c28f5c28f6", + "3fe7eb851eb851ec", + "3fe6e147ae147ae1", + "3fe5d70a3d70a3d8", + "3fe4cccccccccccd", + "3fe3c28f5c28f5c2", + "3fe2b851eb851eb8", + "3fe1ae147ae147af", + "3fe0a3d70a3d70a4", + "3fdf333333333334", + "3fdd1eb851eb851e", + "3fdb0a3d70a3d70a", + "3fd8f5c28f5c28f8", + "3fd6e147ae147ae2", + "3fd4cccccccccccc", + "3fd2b851eb851eb8", + "3fd0a3d70a3d70a4", + "3fcd1eb851eb8520", + "3fc8f5c28f5c28f8", + "3fc4ccccccccccd0", + "3fc0a3d70a3d70a0", + "3fb8f5c28f5c28f0", + "3fb0a3d70a3d70b0", + "3fa0a3d70a3d70a0", + "0000000000000000" + ] + }, + { + "t0": 1.8708286933869707, + "t0_bits": "3ffdeeea11683f49", + "sweeps": 1, + "temperatures_bits": [ + "0000000000000000" + ] + }, + { + "t0": 1.8708286933869707, + "t0_bits": "3ffdeeea11683f49", + "sweeps": 2, + "temperatures_bits": [ + "3ffdeeea11683f49", + "0000000000000000" + ] + }, + { + "t0": 1.8708286933869707, + "t0_bits": "3ffdeeea11683f49", + "sweeps": 7, + "temperatures_bits": [ + "3ffdeeea11683f49", + "3ff8f1c30e818a12", + "3ff3f49c0b9ad4dc", + "3fedeeea11683f49", + "3fe3f49c0b9ad4dc", + "3fd3f49c0b9ad4dc", + "0000000000000000" + ] + }, + { + "t0": 1.8708286933869707, + "t0_bits": "3ffdeeea11683f49", + "sweeps": 40, + "temperatures_bits": [ + "3ffdeeea11683f49", + "3ffd2a6de99385de", + "3ffc65f1c1becc73", + "3ffba17599ea1308", + "3ffadcf97215599e", + "3ffa187d4a40a033", + "3ff95401226be6c8", + "3ff88f84fa972d5d", + "3ff7cb08d2c273f2", + "3ff7068caaedba87", + "3ff642108319011c", + "3ff57d945b4447b1", + "3ff4b918336f8e46", + "3ff3f49c0b9ad4dc", + "3ff3301fe3c61b70", + "3ff26ba3bbf16206", + "3ff1a727941ca89b", + "3ff0e2ab6c47ef30", + "3ff01e2f447335c4", + "3feeb366393cf8b4", + "3fed2a6de99385df", + "3feba17599ea1308", + "3fea187d4a40a032", + "3fe88f84fa972d5c", + "3fe7068caaedba86", + "3fe57d945b4447b0", + "3fe3f49c0b9ad4dc", + "3fe26ba3bbf16206", + "3fe0e2ab6c47ef30", + "3fdeb366393cf8b4", + "3fdba17599ea1308", + "3fd88f84fa972d60", + "3fd57d945b4447b4", + "3fd26ba3bbf16204", + "3fceb366393cf8b0", + "3fc88f84fa972d58", + "3fc26ba3bbf16200", + "3fb88f84fa972d60", + "3fa88f84fa972d60", + "0000000000000000" + ] + }, + { + "t0": 1.8708286933869707, + "t0_bits": "3ffdeeea11683f49", + "sweeps": 41, + "temperatures_bits": [ + "3ffdeeea11683f49", + "3ffd2f576a9270e7", + "3ffc6fc4c3bca285", + "3ffbb0321ce6d424", + "3ffaf09f761105c2", + "3ffa310ccf3b3760", + "3ff9717a286568fe", + "3ff8b1e7818f9a9c", + "3ff7f254dab9cc3a", + "3ff732c233e3fdd8", + "3ff6732f8d0e2f77", + "3ff5b39ce6386114", + "3ff4f40a3f6292b3", + "3ff43477988cc451", + "3ff374e4f1b6f5f0", + "3ff2b5524ae1278e", + "3ff1f5bfa40b592c", + "3ff1362cfd358aca", + "3ff0769a565fbc68", + "3fef6e0f5f13dc0d", + "3fedeeea11683f49", + "3fec6fc4c3bca285", + "3feaf09f761105c0", + "3fe9717a286568fe", + "3fe7f254dab9cc3a", + "3fe6732f8d0e2f76", + "3fe4f40a3f6292b2", + "3fe374e4f1b6f5ee", + "3fe1f5bfa40b592c", + "3fe0769a565fbc68", + "3fddeeea11683f48", + "3fdaf09f761105c0", + "3fd7f254dab9cc38", + "3fd4f40a3f6292b4", + "3fd1f5bfa40b592c", + "3fcdeeea11683f48", + "3fc7f254dab9cc38", + "3fc1f5bfa40b5928", + "3fb7f254dab9cc40", + "3fa7f254dab9cc40", + "0000000000000000" + ] + }, + { + "t0": 2.449489742783178, + "t0_bits": "4003988e1409212e", + "sweeps": 1, + "temperatures_bits": [ + "0000000000000000" + ] + }, + { + "t0": 2.449489742783178, + "t0_bits": "4003988e1409212e", + "sweeps": 2, + "temperatures_bits": [ + "4003988e1409212e", + "0000000000000000" + ] + }, + { + "t0": 2.449489742783178, + "t0_bits": "4003988e1409212e", + "sweeps": 7, + "temperatures_bits": [ + "4003988e1409212e", + "4000547666079ba6", + "3ffa20bd700c2c3e", + "3ff3988e1409212e", + "3fea20bd700c2c3e", + "3fda20bd700c2c38", + "0000000000000000" + ] + }, + { + "t0": 2.449489742783178, + "t0_bits": "4003988e1409212e", + "sweeps": 40, + "temperatures_bits": [ + "4003988e1409212e", + "400317ece592bdde", + "4002974bb71c5a8e", + "400216aa88a5f73e", + "400196095a2f93ee", + "400115682bb9309e", + "400094c6fd42cd4e", + "40001425cecc69fe", + "3fff270940ac0d5d", + "3ffe25c6e3bf46bd", + "3ffd248486d2801e", + "3ffc234229e5b97d", + "3ffb21ffccf8f2dd", + "3ffa20bd700c2c3e", + "3ff91f7b131f659e", + "3ff81e38b6329efe", + "3ff71cf65945d85e", + "3ff61bb3fc5911be", + "3ff51a719f6c4b1e", + "3ff4192f427f847e", + "3ff317ece592bddf", + "3ff216aa88a5f73e", + "3ff115682bb9309e", + "3ff01425cecc69fe", + "3fee25c6e3bf46bc", + "3fec234229e5b97c", + "3fea20bd700c2c3e", + "3fe81e38b6329efe", + "3fe61bb3fc5911be", + "3fe4192f427f847e", + "3fe216aa88a5f73e", + "3fe01425cecc6a00", + "3fdc234229e5b980", + "3fd81e38b6329f00", + "3fd4192f427f8480", + "3fd01425cecc6a00", + "3fc81e38b6329f00", + "3fc01425cecc6a00", + "3fb01425cecc6a00", + "0000000000000000" + ] + }, + { + "t0": 2.449489742783178, + "t0_bits": "4003988e1409212e", + "sweeps": 41, + "temperatures_bits": [ + "4003988e1409212e", + "40031b242055b38d", + "40029dba2ca245ec", + "4002205038eed84b", + "4001a2e6453b6aa9", + "4001257c5187fd08", + "4000a8125dd48f67", + "40002aa86a2121c6", + "3fff5a7cecdb684a", + "3ffe5fa905748d07", + "3ffd64d51e0db1c5", + "3ffc6a0136a6d682", + "3ffb6f2d4f3ffb40", + "3ffa745967d91ffe", + "3ff97985807244bc", + "3ff87eb1990b697a", + "3ff783ddb1a48e37", + "3ff68909ca3db2f5", + "3ff58e35e2d6d7b2", + "3ff49361fb6ffc71", + "3ff3988e1409212e", + "3ff29dba2ca245eb", + "3ff1a2e6453b6aa9", + "3ff0a8125dd48f68", + "3fef5a7cecdb684a", + "3fed64d51e0db1c4", + "3feb6f2d4f3ffb40", + "3fe97985807244ba", + "3fe783ddb1a48e38", + "3fe58e35e2d6d7b4", + "3fe3988e1409212e", + "3fe1a2e6453b6aa8", + "3fdf5a7cecdb6848", + "3fdb6f2d4f3ffb40", + "3fd783ddb1a48e38", + "3fd3988e14092130", + "3fcf5a7cecdb6840", + "3fc783ddb1a48e30", + "3fbf5a7cecdb6860", + "3faf5a7cecdb6840", + "0000000000000000" + ] + }, + { + "t0": 2.9, + "t0_bits": "4007333333333333", + "sweeps": 1, + "temperatures_bits": [ + "0000000000000000" + ] + }, + { + "t0": 2.9, + "t0_bits": "4007333333333333", + "sweeps": 2, + "temperatures_bits": [ + "4007333333333333", + "0000000000000000" + ] + }, + { + "t0": 2.9, + "t0_bits": "4007333333333333", + "sweeps": 7, + "temperatures_bits": [ + "4007333333333333", + "4003555555555555", + "3ffeeeeeeeeeeeef", + "3ff7333333333333", + "3feeeeeeeeeeeef0", + "3fdeeeeeeeeeeef0", + "0000000000000000" + ] + }, + { + "t0": 2.9, + "t0_bits": "4007333333333333", + "sweeps": 40, + "temperatures_bits": [ + "4007333333333333", + "40069ae9ae9ae9ae", + "400602a02a02a02a", + "40056a56a56a56a5", + "4004d20d20d20d21", + "400439c39c39c39c", + "4003a17a17a17a17", + "4003093093093093", + "400270e70e70e70e", + "4001d89d89d89d8a", + "4001405405405405", + "4000a80a80a80a80", + "40000fc0fc0fc0fc", + "3ffeeeeeeeeeeeef", + "3ffdbe5be5be5be5", + "3ffc8dc8dc8dc8dc", + "3ffb5d35d35d35d3", + "3ffa2ca2ca2ca2ca", + "3ff8fc0fc0fc0fc0", + "3ff7cb7cb7cb7cb8", + "3ff69ae9ae9ae9af", + "3ff56a56a56a56a6", + "3ff439c39c39c39c", + "3ff3093093093093", + "3ff1d89d89d89d89", + "3ff0a80a80a80a80", + "3feeeeeeeeeeeef0", + "3fec8dc8dc8dc8dc", + "3fea2ca2ca2ca2c8", + "3fe7cb7cb7cb7cb8", + "3fe56a56a56a56a4", + "3fe3093093093094", + "3fe0a80a80a80a80", + "3fdc8dc8dc8dc8e0", + "3fd7cb7cb7cb7cb8", + "3fd3093093093090", + "3fcc8dc8dc8dc8d0", + "3fc3093093093090", + "3fb30930930930a0", + "0000000000000000" + ] + }, + { + "t0": 2.9, + "t0_bits": "4007333333333333", + "sweeps": 41, + "temperatures_bits": [ + "4007333333333333", + "40069eb851eb851f", + "40060a3d70a3d70a", + "400575c28f5c28f6", + "4004e147ae147ae1", + "40044ccccccccccd", + "4003b851eb851eb8", + "400323d70a3d70a4", + "40028f5c28f5c28f", + "4001fae147ae147b", + "4001666666666666", + "4000d1eb851eb852", + "40003d70a3d70a3d", + "3fff51eb851eb852", + "3ffe28f5c28f5c29", + "3ffd000000000000", + "3ffbd70a3d70a3d7", + "3ffaae147ae147ae", + "3ff9851eb851eb85", + "3ff85c28f5c28f5c", + "3ff7333333333333", + "3ff60a3d70a3d70a", + "3ff4e147ae147ae1", + "3ff3b851eb851eb9", + "3ff28f5c28f5c28f", + "3ff1666666666666", + "3ff03d70a3d70a3d", + "3fee28f5c28f5c28", + "3febd70a3d70a3d8", + "3fe9851eb851eb84", + "3fe7333333333334", + "3fe4e147ae147ae0", + "3fe28f5c28f5c290", + "3fe03d70a3d70a40", + "3fdbd70a3d70a3d8", + "3fd7333333333330", + "3fd28f5c28f5c290", + "3fcbd70a3d70a3d0", + "3fc28f5c28f5c290", + "3fb28f5c28f5c2a0", + "0000000000000000" + ] + }, + { + "t0": 7.1, + "t0_bits": "401c666666666666", + "sweeps": 1, + "temperatures_bits": [ + "0000000000000000" + ] + }, + { + "t0": 7.1, + "t0_bits": "401c666666666666", + "sweeps": 2, + "temperatures_bits": [ + "401c666666666666", + "0000000000000000" + ] + }, + { + "t0": 7.1, + "t0_bits": "401c666666666666", + "sweeps": 7, + "temperatures_bits": [ + "401c666666666666", + "4017aaaaaaaaaaaa", + "4012eeeeeeeeeeef", + "400c666666666666", + "4002eeeeeeeeeef0", + "3ff2eeeeeeeeeeec", + "0000000000000000" + ] + }, + { + "t0": 7.1, + "t0_bits": "401c666666666666", + "sweeps": 40, + "temperatures_bits": [ + "401c666666666666", + "401babfabfabfabf", + "401af18f18f18f19", + "401a372372372372", + "40197cb7cb7cb7cb", + "4018c24c24c24c24", + "401807e07e07e07e", + "40174d74d74d74d7", + "4016930930930930", + "4015d89d89d89d8a", + "40151e31e31e31e3", + "401463c63c63c63c", + "4013a95a95a95a95", + "4012eeeeeeeeeeef", + "4012348348348348", + "40117a17a17a17a1", + "4010bfabfabfabfa", + "4010054054054054", + "400e95a95a95a95a", + "400d20d20d20d20d", + "400babfabfabfac0", + "400a372372372372", + "4008c24c24c24c24", + "40074d74d74d74d6", + "4005d89d89d89d88", + "400463c63c63c63c", + "4002eeeeeeeeeef0", + "40017a17a17a17a2", + "4000054054054054", + "3ffd20d20d20d20c", + "3ffa372372372370", + "3ff74d74d74d74d8", + "3ff463c63c63c63c", + "3ff17a17a17a17a0", + "3fed20d20d20d208", + "3fe74d74d74d74d8", + "3fe17a17a17a17a0", + "3fd74d74d74d74e0", + "3fc74d74d74d74e0", + "0000000000000000" + ] + }, + { + "t0": 7.1, + "t0_bits": "401c666666666666", + "sweeps": 41, + "temperatures_bits": [ + "401c666666666666", + "401bb0a3d70a3d70", + "401afae147ae147a", + "401a451eb851eb85", + "40198f5c28f5c28f", + "4018d99999999999", + "401823d70a3d70a4", + "40176e147ae147ae", + "4016b851eb851eb8", + "4016028f5c28f5c2", + "40154ccccccccccc", + "4014970a3d70a3d6", + "4013e147ae147ae1", + "40132b851eb851eb", + "401275c28f5c28f6", + "4011c00000000000", + "40110a3d70a3d70a", + "4010547ae147ae14", + "400f3d70a3d70a3d", + "400dd1eb851eb852", + "400c666666666666", + "400afae147ae147a", + "40098f5c28f5c28e", + "400823d70a3d70a4", + "4006b851eb851eb8", + "40054ccccccccccc", + "4003e147ae147ae0", + "400275c28f5c28f4", + "40010a3d70a3d70a", + "3fff3d70a3d70a3c", + "3ffc666666666668", + "3ff98f5c28f5c290", + "3ff6b851eb851eb8", + "3ff3e147ae147ae4", + "3ff10a3d70a3d70c", + "3fec666666666668", + "3fe6b851eb851eb8", + "3fe10a3d70a3d708", + "3fd6b851eb851ec0", + "3fc6b851eb851ec0", + "0000000000000000" + ] + }, + { + "t0": 13.7, + "t0_bits": "402b666666666666", + "sweeps": 1, + "temperatures_bits": [ + "0000000000000000" + ] + }, + { + "t0": 13.7, + "t0_bits": "402b666666666666", + "sweeps": 2, + "temperatures_bits": [ + "402b666666666666", + "0000000000000000" + ] + }, + { + "t0": 13.7, + "t0_bits": "402b666666666666", + "sweeps": 7, + "temperatures_bits": [ + "402b666666666666", + "4026d55555555555", + "4022444444444444", + "401b666666666666", + "4012444444444444", + "4002444444444444", + "0000000000000000" + ] + }, + { + "t0": 13.7, + "t0_bits": "402b666666666666", + "sweeps": 40, + "temperatures_bits": [ + "402b666666666666", + "402ab28b28b28b28", + "4029feafeafeafeb", + "40294ad4ad4ad4ad", + "402896f96f96f96f", + "4027e31e31e31e32", + "40272f42f42f42f4", + "40267b67b67b67b6", + "4025c78c78c78c78", + "402513b13b13b13b", + "40245fd5fd5fd5fd", + "4023abfabfabfac0", + "4022f81f81f81f82", + "4022444444444444", + "4021906906906906", + "4020dc8dc8dc8dc8", + "402028b28b28b28b", + "401ee9ae9ae9ae9a", + "401d81f81f81f81f", + "401c1a41a41a41a4", + "401ab28b28b28b29", + "40194ad4ad4ad4ad", + "4017e31e31e31e32", + "40167b67b67b67b6", + "401513b13b13b13a", + "4013abfabfabfabe", + "4012444444444444", + "4010dc8dc8dc8dc8", + "400ee9ae9ae9ae9c", + "400c1a41a41a41a4", + "40094ad4ad4ad4ac", + "40067b67b67b67b8", + "4003abfabfabfac0", + "4000dc8dc8dc8dc8", + "3ffc1a41a41a41a0", + "3ff67b67b67b67b8", + "3ff0dc8dc8dc8dc8", + "3fe67b67b67b67c0", + "3fd67b67b67b67c0", + "0000000000000000" + ] + }, + { + "t0": 13.7, + "t0_bits": "402b666666666666", + "sweeps": 41, + "temperatures_bits": [ + "402b666666666666", + "402ab70a3d70a3d7", + "402a07ae147ae147", + "40295851eb851eb8", + "4028a8f5c28f5c28", + "4027f99999999999", + "40274a3d70a3d70a", + "40269ae147ae147b", + "4025eb851eb851eb", + "40253c28f5c28f5c", + "40248ccccccccccc", + "4023dd70a3d70a3d", + "40232e147ae147ae", + "40227eb851eb851e", + "4021cf5c28f5c290", + "4021200000000000", + "402070a3d70a3d70", + "401f828f5c28f5c2", + "401e23d70a3d70a3", + "401cc51eb851eb85", + "401b666666666666", + "401a07ae147ae147", + "4018a8f5c28f5c28", + "40174a3d70a3d70b", + "4015eb851eb851ec", + "40148ccccccccccc", + "40132e147ae147ae", + "4011cf5c28f5c28e", + "401070a3d70a3d72", + "400e23d70a3d70a4", + "400b666666666668", + "4008a8f5c28f5c28", + "4005eb851eb851e8", + "40032e147ae147b0", + "400070a3d70a3d70", + "3ffb666666666668", + "3ff5eb851eb851e8", + "3ff070a3d70a3d70", + "3fe5eb851eb851f0", + "3fd5eb851eb85200", + "0000000000000000" + ] + }, + { + "t0": 63.0, + "t0_bits": "404f800000000000", + "sweeps": 1, + "temperatures_bits": [ + "0000000000000000" + ] + }, + { + "t0": 63.0, + "t0_bits": "404f800000000000", + "sweeps": 2, + "temperatures_bits": [ + "404f800000000000", + "0000000000000000" + ] + }, + { + "t0": 63.0, + "t0_bits": "404f800000000000", + "sweeps": 7, + "temperatures_bits": [ + "404f800000000000", + "404a400000000000", + "4045000000000000", + "403f800000000000", + "4035000000000000", + "4025000000000000", + "0000000000000000" + ] + }, + { + "t0": 63.0, + "t0_bits": "404f800000000000", + "sweeps": 40, + "temperatures_bits": [ + "404f800000000000", + "404eb13b13b13b14", + "404de27627627627", + "404d13b13b13b13b", + "404c44ec4ec4ec4f", + "404b762762762762", + "404aa76276276276", + "4049d89d89d89d8a", + "404909d89d89d89e", + "40483b13b13b13b1", + "40476c4ec4ec4ec5", + "40469d89d89d89d8", + "4045cec4ec4ec4ec", + "4045000000000000", + "4044313b13b13b14", + "4043627627627627", + "404293b13b13b13b", + "4041c4ec4ec4ec4e", + "4040f62762762762", + "4040276276276276", + "403eb13b13b13b14", + "403d13b13b13b13c", + "403b762762762762", + "4039d89d89d89d8a", + "40383b13b13b13b0", + "40369d89d89d89d8", + "4035000000000000", + "4033627627627628", + "4031c4ec4ec4ec4e", + "4030276276276276", + "402d13b13b13b138", + "4029d89d89d89d8c", + "40269d89d89d89d8", + "4023627627627628", + "4020276276276274", + "4019d89d89d89d88", + "4013627627627620", + "4009d89d89d89d90", + "3ff9d89d89d89d80", + "0000000000000000" + ] + }, + { + "t0": 63.0, + "t0_bits": "404f800000000000", + "sweeps": 41, + "temperatures_bits": [ + "404f800000000000", + "404eb66666666666", + "404deccccccccccd", + "404d233333333333", + "404c59999999999a", + "404b900000000000", + "404ac66666666666", + "4049fccccccccccd", + "4049333333333333", + "404869999999999a", + "4047a00000000000", + "4046d66666666666", + "40460ccccccccccd", + "4045433333333333", + "404479999999999a", + "4043b00000000000", + "4042e66666666666", + "40421ccccccccccd", + "4041533333333333", + "404089999999999a", + "403f800000000000", + "403deccccccccccc", + "403c599999999998", + "403ac66666666668", + "4039333333333334", + "4037a00000000000", + "40360ccccccccccc", + "4034799999999998", + "4032e66666666668", + "4031533333333334", + "402f800000000000", + "402c599999999998", + "4029333333333330", + "40260cccccccccd0", + "4022e66666666668", + "401f800000000000", + "4019333333333330", + "4012e66666666660", + "4009333333333340", + "3ff9333333333340", + "0000000000000000" + ] + } + ], + "cells": [ + { + "field": -40, + "cost": 0, + "temperature": 0.25, + "temperature_bits": "3fd0000000000000", + "cut_a": 9007199254740992, + "cut_b": 9007199254740992 + }, + { + "field": -40, + "cost": 0, + "temperature": 0.3, + "temperature_bits": "3fd3333333333333", + "cut_a": 9007199254740992, + "cut_b": 9007199254740992 + }, + { + "field": -40, + "cost": 0, + "temperature": 0.7, + "temperature_bits": "3fe6666666666666", + "cut_a": 9007199254740992, + "cut_b": 9007199254740992 + }, + { + "field": -40, + "cost": 0, + "temperature": 1.0, + "temperature_bits": "3ff0000000000000", + "cut_a": 9007199254740992, + "cut_b": 9007199254740992 + }, + { + "field": -40, + "cost": 0, + "temperature": 1.3, + "temperature_bits": "3ff4cccccccccccd", + "cut_a": 9007199254740602, + "cut_b": 9007199254740992 + }, + { + "field": -40, + "cost": 0, + "temperature": 1.8708286933869707, + "temperature_bits": "3ffdeeea11683f49", + "cut_a": 9007199250074580, + "cut_b": 9007199254740992 + }, + { + "field": -40, + "cost": 0, + "temperature": 2.9, + "temperature_bits": "4007333333333333", + "cut_a": 9007190043438292, + "cut_b": 9007199254731572 + }, + { + "field": -40, + "cost": 0, + "temperature": 7.1, + "temperature_bits": "401c666666666666", + "cut_a": 8974999336863636, + "cut_b": 9007084551556817 + }, + { + "field": -40, + "cost": 0, + "temperature": 13.7, + "temperature_bits": "402b666666666666", + "cut_a": 8522604166134673, + "cut_b": 8982393859785794 + }, + { + "field": -40, + "cost": 0, + "temperature": 25.0, + "temperature_bits": "4039000000000000", + "cut_a": 7248329002543741, + "cut_b": 8711741389436997 + }, + { + "field": -40, + "cost": 1, + "temperature": 0.25, + "temperature_bits": "3fd0000000000000", + "cut_a": 9007199254740992, + "cut_b": 9007199254740992 + }, + { + "field": -40, + "cost": 1, + "temperature": 0.3, + "temperature_bits": "3fd3333333333333", + "cut_a": 9007199254740992, + "cut_b": 9007199254740992 + }, + { + "field": -40, + "cost": 1, + "temperature": 0.7, + "temperature_bits": "3fe6666666666666", + "cut_a": 9007199254740992, + "cut_b": 9007199254740992 + }, + { + "field": -40, + "cost": 1, + "temperature": 1.0, + "temperature_bits": "3ff0000000000000", + "cut_a": 9007199254740992, + "cut_b": 9007199254740992 + }, + { + "field": -40, + "cost": 1, + "temperature": 1.3, + "temperature_bits": "3ff4cccccccccccd", + "cut_a": 9007199254740150, + "cut_b": 9007199254740992 + }, + { + "field": -40, + "cost": 1, + "temperature": 1.8708286933869707, + "temperature_bits": "3ffdeeea11683f49", + "cut_a": 9007199246777138, + "cut_b": 9007199254740992 + }, + { + "field": -40, + "cost": 1, + "temperature": 2.9, + "temperature_bits": "4007333333333333", + "cut_a": 9007186250726052, + "cut_b": 9007199254731572 + }, + { + "field": -40, + "cost": 1, + "temperature": 7.1, + "temperature_bits": "401c666666666666", + "cut_a": 8970166519716471, + "cut_b": 9007084613321670 + }, + { + "field": -40, + "cost": 1, + "temperature": 13.7, + "temperature_bits": "402b666666666666", + "cut_a": 8489787663524424, + "cut_b": 8982489373585617 + }, + { + "field": -40, + "cost": 1, + "temperature": 25.0, + "temperature_bits": "4039000000000000", + "cut_a": 7200584918426836, + "cut_b": 8713687543532530 + }, + { + "field": -40, + "cost": 2, + "temperature": 0.25, + "temperature_bits": "3fd0000000000000", + "cut_a": 9007199254740992, + "cut_b": 9007199254740992 + }, + { + "field": -40, + "cost": 2, + "temperature": 0.3, + "temperature_bits": "3fd3333333333333", + "cut_a": 9007199254740992, + "cut_b": 9007199254740992 + }, + { + "field": -40, + "cost": 2, + "temperature": 0.7, + "temperature_bits": "3fe6666666666666", + "cut_a": 9007199254740992, + "cut_b": 9007199254740992 + }, + { + "field": -40, + "cost": 2, + "temperature": 1.0, + "temperature_bits": "3ff0000000000000", + "cut_a": 9007199254740992, + "cut_b": 9007199254740992 + }, + { + "field": -40, + "cost": 2, + "temperature": 1.3, + "temperature_bits": "3ff4cccccccccccd", + "cut_a": 9007199254739174, + "cut_b": 9007199254740992 + }, + { + "field": -40, + "cost": 2, + "temperature": 1.8708286933869707, + "temperature_bits": "3ffdeeea11683f49", + "cut_a": 9007199241149614, + "cut_b": 9007199254740992 + }, + { + "field": -40, + "cost": 2, + "temperature": 2.9, + "temperature_bits": "4007333333333333", + "cut_a": 9007180896383284, + "cut_b": 9007199254731572 + }, + { + "field": -40, + "cost": 2, + "temperature": 7.1, + "temperature_bits": "401c666666666666", + "cut_a": 8964609198025238, + "cut_b": 9007084684345909 + }, + { + "field": -40, + "cost": 2, + "temperature": 13.7, + "temperature_bits": "402b666666666666", + "cut_a": 8454767194569113, + "cut_b": 8982591302121428 + }, + { + "field": -40, + "cost": 2, + "temperature": 25.0, + "temperature_bits": "4039000000000000", + "cut_a": 7151555811748292, + "cut_b": 8715686077979838 + }, + { + "field": -40, + "cost": 3, + "temperature": 0.25, + "temperature_bits": "3fd0000000000000", + "cut_a": 9007199254740992, + "cut_b": 9007199254740992 + }, + { + "field": -40, + "cost": 3, + "temperature": 0.3, + "temperature_bits": "3fd3333333333333", + "cut_a": 9007199254740992, + "cut_b": 9007199254740992 + }, + { + "field": -40, + "cost": 3, + "temperature": 0.7, + "temperature_bits": "3fe6666666666666", + "cut_a": 9007199254740992, + "cut_b": 9007199254740992 + }, + { + "field": -40, + "cost": 3, + "temperature": 1.0, + "temperature_bits": "3ff0000000000000", + "cut_a": 9007199254740992, + "cut_b": 9007199254740992 + }, + { + "field": -40, + "cost": 3, + "temperature": 1.3, + "temperature_bits": "3ff4cccccccccccd", + "cut_a": 9007199254737066, + "cut_b": 9007199254740992 + }, + { + "field": -40, + "cost": 3, + "temperature": 1.8708286933869707, + "temperature_bits": "3ffdeeea11683f49", + "cut_a": 9007199231545496, + "cut_b": 9007199254740992 + }, + { + "field": -40, + "cost": 3, + "temperature": 2.9, + "temperature_bits": "4007333333333333", + "cut_a": 9007173337419487, + "cut_b": 9007199254731572 + }, + { + "field": -40, + "cost": 3, + "temperature": 7.1, + "temperature_bits": "401c666666666666", + "cut_a": 8958219872924347, + "cut_b": 9007084766003400 + }, + { + "field": -40, + "cost": 3, + "temperature": 13.7, + "temperature_bits": "402b666666666666", + "cut_a": 8417416021294240, + "cut_b": 8982700014269389 + }, + { + "field": -40, + "cost": 3, + "temperature": 25.0, + "temperature_bits": "4039000000000000", + "cut_a": 7101229910991727, + "cut_b": 8717737472611872 + }, + { + "field": -40, + "cost": 8, + "temperature": 0.25, + "temperature_bits": "3fd0000000000000", + "cut_a": 9007199254740992, + "cut_b": 9007199254740992 + }, + { + "field": -40, + "cost": 8, + "temperature": 0.3, + "temperature_bits": "3fd3333333333333", + "cut_a": 9007199254740992, + "cut_b": 9007199254740992 + }, + { + "field": -40, + "cost": 8, + "temperature": 0.7, + "temperature_bits": "3fe6666666666666", + "cut_a": 9007199254740992, + "cut_b": 9007199254740992 + }, + { + "field": -40, + "cost": 8, + "temperature": 1.0, + "temperature_bits": "3ff0000000000000", + "cut_a": 9007199254740878, + "cut_b": 9007199254740992 + }, + { + "field": -40, + "cost": 8, + "temperature": 1.3, + "temperature_bits": "3ff4cccccccccccd", + "cut_a": 9007199254557226, + "cut_b": 9007199254740992 + }, + { + "field": -40, + "cost": 8, + "temperature": 1.8708286933869707, + "temperature_bits": "3ffdeeea11683f49", + "cut_a": 9007198918922607, + "cut_b": 9007199254740992 + }, + { + "field": -40, + "cost": 8, + "temperature": 2.9, + "temperature_bits": "4007333333333333", + "cut_a": 9007053920543100, + "cut_b": 9007199254731572 + }, + { + "field": -40, + "cost": 8, + "temperature": 7.1, + "temperature_bits": "401c666666666666", + "cut_a": 8908811945536004, + "cut_b": 9007085397451535 + }, + { + "field": -40, + "cost": 8, + "temperature": 13.7, + "temperature_bits": "402b666666666666", + "cut_a": 8190992603656871, + "cut_b": 8983359029071921 + }, + { + "field": -40, + "cost": 8, + "temperature": 25.0, + "temperature_bits": "4039000000000000", + "cut_a": 6829847315284933, + "cut_b": 8728799625334251 + }, + { + "field": -17, + "cost": 0, + "temperature": 0.25, + "temperature_bits": "3fd0000000000000", + "cut_a": 9007199254740992, + "cut_b": 9007199254740992 + }, + { + "field": -17, + "cost": 0, + "temperature": 0.3, + "temperature_bits": "3fd3333333333333", + "cut_a": 9007199254740992, + "cut_b": 9007199254740992 + }, + { + "field": -17, + "cost": 0, + "temperature": 0.7, + "temperature_bits": "3fe6666666666666", + "cut_a": 9007199254485464, + "cut_b": 9007199254740992 + }, + { + "field": -17, + "cost": 0, + "temperature": 1.0, + "temperature_bits": "3ff0000000000000", + "cut_a": 9007198881848551, + "cut_b": 9007199254740976 + }, + { + "field": -17, + "cost": 0, + "temperature": 1.3, + "temperature_bits": "3ff4cccccccccccd", + "cut_a": 9007180402879617, + "cut_b": 9007199254701536 + }, + { + "field": -17, + "cost": 0, + "temperature": 1.8708286933869707, + "temperature_bits": "3ffdeeea11683f49", + "cut_a": 9006180177842631, + "cut_b": 9007199139455405 + }, + { + "field": -17, + "cost": 0, + "temperature": 2.9, + "temperature_bits": "4007333333333333", + "cut_a": 8981570821331008, + "cut_b": 9007126539760789 + }, + { + "field": -17, + "cost": 0, + "temperature": 7.1, + "temperature_bits": "401c666666666666", + "cut_a": 8191688160783945, + "cut_b": 8939019826143707 + }, + { + "field": -17, + "cost": 0, + "temperature": 13.7, + "temperature_bits": "402b666666666666", + "cut_a": 6561534774294833, + "cut_b": 8458676925105304 + }, + { + "field": -17, + "cost": 0, + "temperature": 25.0, + "temperature_bits": "4039000000000000", + "cut_a": 5108213471221238, + "cut_b": 7696121216372729 + }, + { + "field": -17, + "cost": 1, + "temperature": 0.25, + "temperature_bits": "3fd0000000000000", + "cut_a": 9007199254740992, + "cut_b": 9007199254740992 + }, + { + "field": -17, + "cost": 1, + "temperature": 0.3, + "temperature_bits": "3fd3333333333333", + "cut_a": 9007199254740992, + "cut_b": 9007199254740992 + }, + { + "field": -17, + "cost": 1, + "temperature": 0.7, + "temperature_bits": "3fe6666666666666", + "cut_a": 9007199253674742, + "cut_b": 9007199254740992 + }, + { + "field": -17, + "cost": 1, + "temperature": 1.0, + "temperature_bits": "3ff0000000000000", + "cut_a": 9007198241114348, + "cut_b": 9007199254740976 + }, + { + "field": -17, + "cost": 1, + "temperature": 1.3, + "temperature_bits": "3ff4cccccccccccd", + "cut_a": 9007158570578939, + "cut_b": 9007199254701536 + }, + { + "field": -17, + "cost": 1, + "temperature": 1.8708286933869707, + "temperature_bits": "3ffdeeea11683f49", + "cut_a": 9005460284849205, + "cut_b": 9007199139464620 + }, + { + "field": -17, + "cost": 1, + "temperature": 2.9, + "temperature_bits": "4007333333333333", + "cut_a": 8971090527150086, + "cut_b": 9007126624609464 + }, + { + "field": -17, + "cost": 1, + "temperature": 7.1, + "temperature_bits": "401c666666666666", + "cut_a": 8090164874935670, + "cut_b": 8939864804551404 + }, + { + "field": -17, + "cost": 1, + "temperature": 13.7, + "temperature_bits": "402b666666666666", + "cut_a": 6458527097225345, + "cut_b": 8467288021889834 + }, + { + "field": -17, + "cost": 1, + "temperature": 25.0, + "temperature_bits": "4039000000000000", + "cut_a": 5049010954832854, + "cut_b": 7711316180226577 + }, + { + "field": -17, + "cost": 2, + "temperature": 0.25, + "temperature_bits": "3fd0000000000000", + "cut_a": 9007199254740992, + "cut_b": 9007199254740992 + }, + { + "field": -17, + "cost": 2, + "temperature": 0.3, + "temperature_bits": "3fd3333333333333", + "cut_a": 9007199254740992, + "cut_b": 9007199254740992 + }, + { + "field": -17, + "cost": 2, + "temperature": 0.7, + "temperature_bits": "3fe6666666666666", + "cut_a": 9007199250291818, + "cut_b": 9007199254740992 + }, + { + "field": -17, + "cost": 2, + "temperature": 1.0, + "temperature_bits": "3ff0000000000000", + "cut_a": 9007196499418665, + "cut_b": 9007199254740976 + }, + { + "field": -17, + "cost": 2, + "temperature": 1.3, + "temperature_bits": "3ff4cccccccccccd", + "cut_a": 9007111454530696, + "cut_b": 9007199254701536 + }, + { + "field": -17, + "cost": 2, + "temperature": 1.8708286933869707, + "temperature_bits": "3ffdeeea11683f49", + "cut_a": 9004231957405963, + "cut_b": 9007199139480344 + }, + { + "field": -17, + "cost": 2, + "temperature": 2.9, + "temperature_bits": "4007333333333333", + "cut_a": 8956336577449977, + "cut_b": 9007126744057741 + }, + { + "field": -17, + "cost": 2, + "temperature": 7.1, + "temperature_bits": "401c666666666666", + "cut_a": 7976358870428141, + "cut_b": 8940812012038551 + }, + { + "field": -17, + "cost": 2, + "temperature": 13.7, + "temperature_bits": "402b666666666666", + "cut_a": 6351270237484724, + "cut_b": 8476254336113271 + }, + { + "field": -17, + "cost": 2, + "temperature": 25.0, + "temperature_bits": "4039000000000000", + "cut_a": 4988832390707712, + "cut_b": 7726761657250885 + }, + { + "field": -17, + "cost": 3, + "temperature": 0.25, + "temperature_bits": "3fd0000000000000", + "cut_a": 9007199254740992, + "cut_b": 9007199254740992 + }, + { + "field": -17, + "cost": 3, + "temperature": 0.3, + "temperature_bits": "3fd3333333333333", + "cut_a": 9007199254740992, + "cut_b": 9007199254740992 + }, + { + "field": -17, + "cost": 3, + "temperature": 0.7, + "temperature_bits": "3fe6666666666666", + "cut_a": 9007199236175770, + "cut_b": 9007199254740992 + }, + { + "field": -17, + "cost": 3, + "temperature": 1.0, + "temperature_bits": "3ff0000000000000", + "cut_a": 9007191765002344, + "cut_b": 9007199254740976 + }, + { + "field": -17, + "cost": 3, + "temperature": 1.3, + "temperature_bits": "3ff4cccccccccccd", + "cut_a": 9007009774805992, + "cut_b": 9007199254701537 + }, + { + "field": -17, + "cost": 3, + "temperature": 1.8708286933869707, + "temperature_bits": "3ffdeeea11683f49", + "cut_a": 9002136426862869, + "cut_b": 9007199139507168 + }, + { + "field": -17, + "cost": 3, + "temperature": 2.9, + "temperature_bits": "4007333333333333", + "cut_a": 8935590164473988, + "cut_b": 9007126912021123 + }, + { + "field": -17, + "cost": 3, + "temperature": 7.1, + "temperature_bits": "401c666666666666", + "cut_a": 7849241836482282, + "cut_b": 8941870007236769 + }, + { + "field": -17, + "cost": 3, + "temperature": 13.7, + "temperature_bits": "402b666666666666", + "cut_a": 6239799076552418, + "cut_b": 8485572951800765 + }, + { + "field": -17, + "cost": 3, + "temperature": 25.0, + "temperature_bits": "4039000000000000", + "cut_a": 4927702757427820, + "cut_b": 7742451236423388 + }, + { + "field": -17, + "cost": 8, + "temperature": 0.25, + "temperature_bits": "3fd0000000000000", + "cut_a": 9007199254740990, + "cut_b": 9007199254740992 + }, + { + "field": -17, + "cost": 8, + "temperature": 0.3, + "temperature_bits": "3fd3333333333333", + "cut_a": 9007199254740150, + "cut_b": 9007199254740992 + }, + { + "field": -17, + "cost": 8, + "temperature": 0.7, + "temperature_bits": "3fe6666666666666", + "cut_a": 9007175769098699, + "cut_b": 9007199254740992 + }, + { + "field": -17, + "cost": 8, + "temperature": 1.0, + "temperature_bits": "3ff0000000000000", + "cut_a": 9006087815208113, + "cut_b": 9007199254740976 + }, + { + "field": -17, + "cost": 8, + "temperature": 1.3, + "temperature_bits": "3ff4cccccccccccd", + "cut_a": 8998337735475539, + "cut_b": 9007199254701575 + }, + { + "field": -17, + "cost": 8, + "temperature": 1.8708286933869707, + "temperature_bits": "3ffdeeea11683f49", + "cut_a": 8934453577265569, + "cut_b": 9007199140373557 + }, + { + "field": -17, + "cost": 8, + "temperature": 2.9, + "temperature_bits": "4007333333333333", + "cut_a": 8620135979292768, + "cut_b": 9007129465944627 + }, + { + "field": -17, + "cost": 8, + "temperature": 7.1, + "temperature_bits": "401c666666666666", + "cut_a": 6983256959122081, + "cut_b": 8949077600220467 + }, + { + "field": -17, + "cost": 8, + "temperature": 13.7, + "temperature_bits": "402b666666666666", + "cut_a": 5622348876018498, + "cut_b": 8537189720450336 + }, + { + "field": -17, + "cost": 8, + "temperature": 25.0, + "temperature_bits": "4039000000000000", + "cut_a": 4608825796123503, + "cut_b": 7824294445064343 + }, + { + "field": -5, + "cost": 0, + "temperature": 0.25, + "temperature_bits": "3fd0000000000000", + "cut_a": 9007199236175770, + "cut_b": 9007199254740992 + }, + { + "field": -5, + "cost": 0, + "temperature": 0.3, + "temperature_bits": "3fd3333333333333", + "cut_a": 9007198734327670, + "cut_b": 9007199254740962 + }, + { + "field": -5, + "cost": 0, + "temperature": 0.7, + "temperature_bits": "3fe6666666666666", + "cut_a": 9000079155337364, + "cut_b": 9007193630816972 + }, + { + "field": -5, + "cost": 0, + "temperature": 1.0, + "temperature_bits": "3ff0000000000000", + "cut_a": 8946511960311115, + "cut_b": 9006793083726375 + }, + { + "field": -5, + "cost": 0, + "temperature": 1.3, + "temperature_bits": "3ff4cccccccccccd", + "cut_a": 8814875739845569, + "cut_b": 9003176816260249 + }, + { + "field": -5, + "cost": 0, + "temperature": 1.8708286933869707, + "temperature_bits": "3ffdeeea11683f49", + "cut_a": 8387822136216712, + "cut_b": 8967181975214494 + }, + { + "field": -5, + "cost": 0, + "temperature": 2.9, + "temperature_bits": "4007333333333333", + "cut_a": 7443184281922798, + "cut_b": 8770502887452957 + }, + { + "field": -5, + "cost": 0, + "temperature": 7.1, + "temperature_bits": "401c666666666666", + "cut_a": 5179488395621572, + "cut_b": 7740701292050491 + }, + { + "field": -5, + "cost": 0, + "temperature": 13.7, + "temperature_bits": "402b666666666666", + "cut_a": 4139021846915777, + "cut_b": 7012421817653930 + }, + { + "field": -5, + "cost": 0, + "temperature": 25.0, + "temperature_bits": "4039000000000000", + "cut_a": 3618728576350694, + "cut_b": 6581492948851110 + }, + { + "field": -5, + "cost": 1, + "temperature": 0.25, + "temperature_bits": "3fd0000000000000", + "cut_a": 9007198241114364, + "cut_b": 9007199254740992 + }, + { + "field": -5, + "cost": 1, + "temperature": 0.3, + "temperature_bits": "3fd3333333333333", + "cut_a": 9007184666733569, + "cut_b": 9007199254740962 + }, + { + "field": -5, + "cost": 1, + "temperature": 0.7, + "temperature_bits": "3fe6666666666666", + "cut_a": 8977581042133591, + "cut_b": 9007193644875479 + }, + { + "field": -5, + "cost": 1, + "temperature": 1.0, + "temperature_bits": "3ff0000000000000", + "cut_a": 8844799546918205, + "cut_b": 9006797701462799 + }, + { + "field": -5, + "cost": 1, + "temperature": 1.3, + "temperature_bits": "3ff4cccccccccccd", + "cut_a": 8606504409293521, + "cut_b": 9003271901078575 + }, + { + "field": -5, + "cost": 1, + "temperature": 1.8708286933869707, + "temperature_bits": "3ffdeeea11683f49", + "cut_a": 8023154075564641, + "cut_b": 8968921762191436 + }, + { + "field": -5, + "cost": 1, + "temperature": 2.9, + "temperature_bits": "4007333333333333", + "cut_a": 7017398216735508, + "cut_b": 8784043062068339 + }, + { + "field": -5, + "cost": 1, + "temperature": 7.1, + "temperature_bits": "401c666666666666", + "cut_a": 4965918433529849, + "cut_b": 7792923809260822 + }, + { + "field": -5, + "cost": 1, + "temperature": 13.7, + "temperature_bits": "402b666666666666", + "cut_a": 4041396239252158, + "cut_b": 7059471909838008 + }, + { + "field": -5, + "cost": 1, + "temperature": 25.0, + "temperature_bits": "4039000000000000", + "cut_a": 3570794209120696, + "cut_b": 6613624316099412 + }, + { + "field": -5, + "cost": 2, + "temperature": 0.25, + "temperature_bits": "3fd0000000000000", + "cut_a": 9007143912936094, + "cut_b": 9007199254740992 + }, + { + "field": -5, + "cost": 2, + "temperature": 0.3, + "temperature_bits": "3fd3333333333333", + "cut_a": 9006790347091819, + "cut_b": 9007199254740962 + }, + { + "field": -5, + "cost": 2, + "temperature": 0.7, + "temperature_bits": "3fe6666666666666", + "cut_a": 8884903781988218, + "cut_b": 9007193702787178 + }, + { + "field": -5, + "cost": 2, + "temperature": 1.0, + "temperature_bits": "3ff0000000000000", + "cut_a": 8579653922636598, + "cut_b": 9006809739055518 + }, + { + "field": -5, + "cost": 2, + "temperature": 1.3, + "temperature_bits": "3ff4cccccccccccd", + "cut_a": 8188758219826612, + "cut_b": 9003462528649206 + }, + { + "field": -5, + "cost": 2, + "temperature": 1.8708286933869707, + "temperature_bits": "3ffdeeea11683f49", + "cut_a": 7468975520496383, + "cut_b": 8971565680694429 + }, + { + "field": -5, + "cost": 2, + "temperature": 2.9, + "temperature_bits": "4007333333333333", + "cut_a": 6493029508789963, + "cut_b": 8800718205876301 + }, + { + "field": -5, + "cost": 2, + "temperature": 7.1, + "temperature_bits": "401c666666666666", + "cut_a": 4740868221332221, + "cut_b": 7847953498195409 + }, + { + "field": -5, + "cost": 2, + "temperature": 13.7, + "temperature_bits": "402b666666666666", + "cut_a": 3941392543719185, + "cut_b": 7107668107599841 + }, + { + "field": -5, + "cost": 2, + "temperature": 25.0, + "temperature_bits": "4039000000000000", + "cut_a": 3522233953157583, + "cut_b": 6646175229112108 + }, + { + "field": -5, + "cost": 3, + "temperature": 0.25, + "temperature_bits": "3fd0000000000000", + "cut_a": 9004178689295778, + "cut_b": 9007199254740992 + }, + { + "field": -5, + "cost": 3, + "temperature": 0.3, + "temperature_bits": "3fd3333333333333", + "cut_a": 8995750958003372, + "cut_b": 9007199254740962 + }, + { + "field": -5, + "cost": 3, + "temperature": 0.7, + "temperature_bits": "3fe6666666666666", + "cut_a": 8517983810925655, + "cut_b": 9007193932066276 + }, + { + "field": -5, + "cost": 3, + "temperature": 1.0, + "temperature_bits": "3ff0000000000000", + "cut_a": 7933197550641351, + "cut_b": 9006839088129401 + }, + { + "field": -5, + "cost": 3, + "temperature": 1.3, + "temperature_bits": "3ff4cccccccccccd", + "cut_a": 7412310881087911, + "cut_b": 9003816840127415 + }, + { + "field": -5, + "cost": 3, + "temperature": 1.8708286933869707, + "temperature_bits": "3ffdeeea11683f49", + "cut_a": 6681367534255699, + "cut_b": 8975323262630580 + }, + { + "field": -5, + "cost": 3, + "temperature": 2.9, + "temperature_bits": "4007333333333333", + "cut_a": 5873432458357545, + "cut_b": 8820421650727978 + }, + { + "field": -5, + "cost": 3, + "temperature": 7.1, + "temperature_bits": "401c666666666666", + "cut_a": 4505786587881912, + "cut_b": 7905436088576817 + }, + { + "field": -5, + "cost": 3, + "temperature": 13.7, + "temperature_bits": "402b666666666666", + "cut_a": 3839198516848650, + "cut_b": 7156919922774074 + }, + { + "field": -5, + "cost": 3, + "temperature": 25.0, + "temperature_bits": "4039000000000000", + "cut_a": 3473075060201592, + "cut_b": 6679127420501431 + }, + { + "field": -5, + "cost": 8, + "temperature": 0.25, + "temperature_bits": "3fd0000000000000", + "cut_a": 55341804898, + "cut_b": 9007199254740992 + }, + { + "field": -5, + "cost": 8, + "temperature": 0.3, + "temperature_bits": "3fd3333333333333", + "cut_a": 408907649143, + "cut_b": 9007199254740992 + }, + { + "field": -5, + "cost": 8, + "temperature": 0.7, + "temperature_bits": "3fe6666666666666", + "cut_a": 122289995139881, + "cut_b": 9007199178325038 + }, + { + "field": -5, + "cost": 8, + "temperature": 1.0, + "temperature_bits": "3ff0000000000000", + "cut_a": 427173369780062, + "cut_b": 9007179861100008 + }, + { + "field": -5, + "cost": 8, + "temperature": 1.3, + "temperature_bits": "3ff4cccccccccccd", + "cut_a": 815008784106335, + "cut_b": 9006827346753621 + }, + { + "field": -5, + "cost": 8, + "temperature": 1.8708286933869707, + "temperature_bits": "3ffdeeea11683f49", + "cut_a": 1507353757782687, + "cut_b": 9000007853243569 + }, + { + "field": -5, + "cost": 8, + "temperature": 2.9, + "temperature_bits": "4007333333333333", + "cut_a": 2342299874561993, + "cut_b": 8932713140947659 + }, + { + "field": -5, + "cost": 8, + "temperature": 7.1, + "temperature_bits": "401c666666666666", + "cut_a": 3251289458726309, + "cut_b": 8212188017902452 + }, + { + "field": -5, + "cost": 8, + "temperature": 13.7, + "temperature_bits": "402b666666666666", + "cut_a": 3303275052641413, + "cut_b": 7415205110421185 + }, + { + "field": -5, + "cost": 8, + "temperature": 25.0, + "temperature_bits": "4039000000000000", + "cut_a": 3219366950860556, + "cut_b": 6849193052034528 + }, + { + "field": -3, + "cost": 0, + "temperature": 0.25, + "temperature_bits": "3fd0000000000000", + "cut_a": 9007143912596064, + "cut_b": 9007199254400960 + }, + { + "field": -3, + "cost": 0, + "temperature": 0.3, + "temperature_bits": "3fd3333333333333", + "cut_a": 9006790328528312, + "cut_b": 9007199236176613 + }, + { + "field": -3, + "cost": 0, + "temperature": 0.7, + "temperature_bits": "3fe6666666666666", + "cut_a": 8883249247648356, + "cut_b": 9005516395789519 + }, + { + "field": -3, + "cost": 0, + "temperature": 1.0, + "temperature_bits": "3ff0000000000000", + "cut_a": 8559813574558485, + "cut_b": 8985981598211198 + }, + { + "field": -3, + "cost": 0, + "temperature": 1.3, + "temperature_bits": "3ff4cccccccccccd", + "cut_a": 8119063386378565, + "cut_b": 8926833715763920 + }, + { + "field": -3, + "cost": 0, + "temperature": 1.8708286933869707, + "temperature_bits": "3ffdeeea11683f49", + "cut_a": 7254217870146732, + "cut_b": 8713603613439529 + }, + { + "field": -3, + "cost": 0, + "temperature": 2.9, + "temperature_bits": "4007333333333333", + "cut_a": 6078853915087910, + "cut_b": 8239340395566215 + }, + { + "field": -3, + "cost": 0, + "temperature": 7.1, + "temperature_bits": "401c666666666666", + "cut_a": 4320185533191774, + "cut_b": 7151562453372333 + }, + { + "field": -3, + "cost": 0, + "temperature": 13.7, + "temperature_bits": "402b666666666666", + "cut_a": 3678369623302248, + "cut_b": 6633348535956477 + }, + { + "field": -3, + "cost": 0, + "temperature": 25.0, + "temperature_bits": "4039000000000000", + "cut_a": 3369005625298665, + "cut_b": 6357045565791117 + }, + { + "field": -3, + "cost": 1, + "temperature": 0.25, + "temperature_bits": "3fd0000000000000", + "cut_a": 9004178688955972, + "cut_b": 9007199254401072 + }, + { + "field": -3, + "cost": 1, + "temperature": 0.3, + "temperature_bits": "3fd3333333333333", + "cut_a": 8995750939485343, + "cut_b": 9007199236199367 + }, + { + "field": -3, + "cost": 1, + "temperature": 0.7, + "temperature_bits": "3fe6666666666666", + "cut_a": 8516463097823218, + "cut_b": 9005585880427212 + }, + { + "field": -3, + "cost": 1, + "temperature": 1.0, + "temperature_bits": "3ff0000000000000", + "cut_a": 7916231452945102, + "cut_b": 8987576878796010 + }, + { + "field": -3, + "cost": 1, + "temperature": 1.3, + "temperature_bits": "3ff4cccccccccccd", + "cut_a": 7355160075166123, + "cut_b": 8934395117665887 + }, + { + "field": -3, + "cost": 1, + "temperature": 1.8708286933869707, + "temperature_bits": "3ffdeeea11683f49", + "cut_a": 6508991811379625, + "cut_b": 8743764704070498 + }, + { + "field": -3, + "cost": 1, + "temperature": 2.9, + "temperature_bits": "4007333333333333", + "cut_a": 5532455281013716, + "cut_b": 8308359496481523 + }, + { + "field": -3, + "cost": 1, + "temperature": 7.1, + "temperature_bits": "401c666666666666", + "cut_a": 4124110244744879, + "cut_b": 7235782105117758 + }, + { + "field": -3, + "cost": 1, + "temperature": 13.7, + "temperature_bits": "402b666666666666", + "cut_a": 3589205740205791, + "cut_b": 6690890808662645 + }, + { + "field": -3, + "cost": 1, + "temperature": 25.0, + "temperature_bits": "4039000000000000", + "cut_a": 3324003509248794, + "cut_b": 6392445484082896 + }, + { + "field": -3, + "cost": 2, + "temperature": 0.25, + "temperature_bits": "3fd0000000000000", + "cut_a": 8845193877446913, + "cut_b": 9007199254407074 + }, + { + "field": -3, + "cost": 2, + "temperature": 0.3, + "temperature_bits": "3fd3333333333333", + "cut_a": 8696944496698604, + "cut_b": 9007199236815253 + }, + { + "field": -3, + "cost": 2, + "temperature": 0.7, + "temperature_bits": "3fe6666666666666", + "cut_a": 7264804958885385, + "cut_b": 9005822996829620 + }, + { + "field": -3, + "cost": 2, + "temperature": 1.0, + "temperature_bits": "3ff0000000000000", + "cut_a": 6572879483964902, + "cut_b": 8990906715413149 + }, + { + "field": -3, + "cost": 2, + "temperature": 1.3, + "temperature_bits": "3ff4cccccccccccd", + "cut_a": 6113755823609035, + "cut_b": 8946683003228179 + }, + { + "field": -3, + "cost": 2, + "temperature": 1.8708286933869707, + "temperature_bits": "3ffdeeea11683f49", + "cut_a": 5538047580245942, + "cut_b": 8783061151917863 + }, + { + "field": -3, + "cost": 2, + "temperature": 2.9, + "temperature_bits": "4007333333333333", + "cut_a": 4909468165875775, + "cut_b": 8387052977554178 + }, + { + "field": -3, + "cost": 2, + "temperature": 7.1, + "temperature_bits": "401c666666666666", + "cut_a": 3919324409636781, + "cut_b": 7323743172786268 + }, + { + "field": -3, + "cost": 2, + "temperature": 13.7, + "temperature_bits": "402b666666666666", + "cut_a": 3497993539027362, + "cut_b": 6749754971730344 + }, + { + "field": -3, + "cost": 2, + "temperature": 25.0, + "temperature_bits": "4039000000000000", + "cut_a": 3278424160036927, + "cut_b": 6428299470062232 + }, + { + "field": -3, + "cost": 3, + "temperature": 0.25, + "temperature_bits": "3fd0000000000000", + "cut_a": 4503599627285488, + "cut_b": 9007199254570976 + }, + { + "field": -3, + "cost": 3, + "temperature": 0.3, + "temperature_bits": "3fd3333333333333", + "cut_a": 4503599622729191, + "cut_b": 9007199245458382 + }, + { + "field": -3, + "cost": 3, + "temperature": 0.7, + "temperature_bits": "3fe6666666666666", + "cut_a": 4503173082706432, + "cut_b": 9006346165412864 + }, + { + "field": -3, + "cost": 3, + "temperature": 1.0, + "temperature_bits": "3ff0000000000000", + "cut_a": 4498024882885920, + "cut_b": 8996049765771840 + }, + { + "field": -3, + "cost": 3, + "temperature": 1.3, + "temperature_bits": "3ff4cccccccccccd", + "cut_a": 4481420236990766, + "cut_b": 8962840473981533 + }, + { + "field": -3, + "cost": 3, + "temperature": 1.8708286933869707, + "temperature_bits": "3ffdeeea11683f49", + "cut_a": 4414271535389353, + "cut_b": 8828543070778707 + }, + { + "field": -3, + "cost": 3, + "temperature": 2.9, + "temperature_bits": "4007333333333333", + "cut_a": 4236057866520519, + "cut_b": 8472115733041039 + }, + { + "field": -3, + "cost": 3, + "temperature": 7.1, + "temperature_bits": "401c666666666666", + "cut_a": 3707387837852412, + "cut_b": 7414775675704824 + }, + { + "field": -3, + "cost": 3, + "temperature": 13.7, + "temperature_bits": "402b666666666666", + "cut_a": 3404912574419157, + "cut_b": 6809825148838314 + }, + { + "field": -3, + "cost": 3, + "temperature": 25.0, + "temperature_bits": "4039000000000000", + "cut_a": 3232293547547313, + "cut_b": 6464587095094627 + }, + { + "field": -3, + "cost": 8, + "temperature": 0.25, + "temperature_bits": "3fd0000000000000", + "cut_a": 18565221, + "cut_b": 9007199254740992 + }, + { + "field": -3, + "cost": 8, + "temperature": 0.3, + "temperature_bits": "3fd3333333333333", + "cut_a": 520413291, + "cut_b": 9007199254740990 + }, + { + "field": -3, + "cost": 8, + "temperature": 0.7, + "temperature_bits": "3fe6666666666666", + "cut_a": 7114478857188, + "cut_b": 9007197906961131 + }, + { + "field": -3, + "cost": 8, + "temperature": 1.0, + "temperature_bits": "3ff0000000000000", + "cut_a": 60282841769861, + "cut_b": 9007049828515740 + }, + { + "field": -3, + "cost": 8, + "temperature": 1.3, + "temperature_bits": "3ff4cccccccccccd", + "cut_a": 188346213342095, + "cut_b": 9005334933177136 + }, + { + "field": -3, + "cost": 8, + "temperature": 1.8708286933869707, + "temperature_bits": "3ffdeeea11683f49", + "cut_a": 580427564541936, + "cut_b": 8983707955459504 + }, + { + "field": -3, + "cost": 8, + "temperature": 2.9, + "temperature_bits": "4007333333333333", + "cut_a": 1337570204301935, + "cut_b": 8838242220160469 + }, + { + "field": -3, + "cost": 8, + "temperature": 7.1, + "temperature_bits": "401c666666666666", + "cut_a": 2609415941994217, + "cut_b": 7886384369629993 + }, + { + "field": -3, + "cost": 8, + "temperature": 13.7, + "temperature_bits": "402b666666666666", + "cut_a": 2918904033203237, + "cut_b": 7123472727263313 + }, + { + "field": -3, + "cost": 8, + "temperature": 25.0, + "temperature_bits": "4039000000000000", + "cut_a": 2994384165230498, + "cut_b": 6651733243634167 + }, + { + "field": -1, + "cost": 0, + "temperature": 0.25, + "temperature_bits": "3fd0000000000000", + "cut_a": 8842280974644277, + "cut_b": 9004232999928586 + }, + { + "field": -1, + "cost": 0, + "temperature": 0.3, + "temperature_bits": "3fd3333333333333", + "cut_a": 8686270844536226, + "cut_b": 8996144812856643 + }, + { + "field": -1, + "cost": 0, + "temperature": 0.7, + "temperature_bits": "3fe6666666666666", + "cut_a": 6944193009520908, + "cut_b": 8608376061504328 + }, + { + "field": -1, + "cost": 0, + "temperature": 1.0, + "temperature_bits": "3ff0000000000000", + "cut_a": 5991957841078160, + "cut_b": 8196275943176836 + }, + { + "field": -1, + "cost": 0, + "temperature": 1.3, + "temperature_bits": "3ff4cccccccccccd", + "cut_a": 5367560752655235, + "cut_b": 7854723992923104 + }, + { + "field": -1, + "cost": 0, + "temperature": 1.8708286933869707, + "temperature_bits": "3ffdeeea11683f49", + "cut_a": 4668671523378449, + "cut_b": 7404275043485528 + }, + { + "field": -1, + "cost": 0, + "temperature": 2.9, + "temperature_bits": "4007333333333333", + "cut_a": 4075486985161345, + "cut_b": 6962327506564722 + }, + { + "field": -1, + "cost": 0, + "temperature": 7.1, + "temperature_bits": "401c666666666666", + "cut_a": 3433758424239959, + "cut_b": 6416402978710121 + }, + { + "field": -1, + "cost": 0, + "temperature": 13.7, + "temperature_bits": "402b666666666666", + "cut_a": 3224021188140163, + "cut_b": 6221095837033452 + }, + { + "field": -1, + "cost": 0, + "temperature": 25.0, + "temperature_bits": "4039000000000000", + "cut_a": 3123264046941700, + "cut_b": 6124063158927432 + }, + { + "field": -1, + "cost": 1, + "temperature": 0.25, + "temperature_bits": "3fd0000000000000", + "cut_a": 4502844359369581, + "cut_b": 9005688718739162 + }, + { + "field": -1, + "cost": 1, + "temperature": 0.3, + "temperature_bits": "3fd3333333333333", + "cut_a": 4500735733158038, + "cut_b": 9001471466316076 + }, + { + "field": -1, + "cost": 1, + "temperature": 0.7, + "temperature_bits": "3fe6666666666666", + "cut_a": 4377882984059656, + "cut_b": 8755765968119312 + }, + { + "field": -1, + "cost": 1, + "temperature": 1.0, + "temperature_bits": "3ff0000000000000", + "cut_a": 4218166264310690, + "cut_b": 8436332528621380 + }, + { + "field": -1, + "cost": 1, + "temperature": 1.3, + "temperature_bits": "3ff4cccccccccccd", + "cut_a": 4066985965134831, + "cut_b": 8133971930269663 + }, + { + "field": -1, + "cost": 1, + "temperature": 1.8708286933869707, + "temperature_bits": "3ffdeeea11683f49", + "cut_a": 3843750218735044, + "cut_b": 7687500437470088 + }, + { + "field": -1, + "cost": 1, + "temperature": 2.9, + "temperature_bits": "4007333333333333", + "cut_a": 3600360808603541, + "cut_b": 7200721617207083 + }, + { + "field": -1, + "cost": 1, + "temperature": 7.1, + "temperature_bits": "401c666666666666", + "cut_a": 3269985452228774, + "cut_b": 6539970904457548 + }, + { + "field": -1, + "cost": 1, + "temperature": 13.7, + "temperature_bits": "402b666666666666", + "cut_a": 3144784790163494, + "cut_b": 6289569580326989 + }, + { + "field": -1, + "cost": 1, + "temperature": 25.0, + "temperature_bits": "4039000000000000", + "cut_a": 3081368713180062, + "cut_b": 6162737426360124 + }, + { + "field": -1, + "cost": 2, + "temperature": 0.25, + "temperature_bits": "3fd0000000000000", + "cut_a": 162004399480016, + "cut_b": 9007144908319411 + }, + { + "field": -1, + "cost": 2, + "temperature": 0.3, + "temperature_bits": "3fd3333333333333", + "cut_a": 310241140966127, + "cut_b": 9006804431378432 + }, + { + "field": -1, + "cost": 2, + "temperature": 0.7, + "temperature_bits": "3fe6666666666666", + "cut_a": 1722163003760160, + "cut_b": 8908290922629258 + }, + { + "field": -1, + "cost": 2, + "temperature": 1.0, + "temperature_bits": "3ff0000000000000", + "cut_a": 2337336324204163, + "cut_b": 8690875181285599 + }, + { + "field": -1, + "cost": 2, + "temperature": 1.3, + "temperature_bits": "3ff4cccccccccccd", + "cut_a": 2670527348237748, + "cut_b": 8433807197030301 + }, + { + "field": -1, + "cost": 2, + "temperature": 1.8708286933869707, + "temperature_bits": "3ffdeeea11683f49", + "cut_a": 2953209750340897, + "cut_b": 7993255269598176 + }, + { + "field": -1, + "cost": 2, + "temperature": 2.9, + "temperature_bits": "4007333333333333", + "cut_a": 3091543851476111, + "cut_b": 7456020045201378 + }, + { + "field": -1, + "cost": 2, + "temperature": 7.1, + "temperature_bits": "401c666666666666", + "cut_a": 3099780693503146, + "cut_b": 6668391661129356 + }, + { + "field": -1, + "cost": 2, + "temperature": 13.7, + "temperature_bits": "402b666666666666", + "cut_a": 3063784717222917, + "cut_b": 6359567439176201 + }, + { + "field": -1, + "cost": 2, + "temperature": 25.0, + "temperature_bits": "4039000000000000", + "cut_a": 3038940867406136, + "cut_b": 6201903264336005 + }, + { + "field": -1, + "cost": 3, + "temperature": 0.25, + "temperature_bits": "3fd0000000000000", + "cut_a": 3020565105409, + "cut_b": 9007198241454284 + }, + { + "field": -1, + "cost": 3, + "temperature": 0.3, + "temperature_bits": "3fd3333333333333", + "cut_a": 11448278219562, + "cut_b": 9007184685275163 + }, + { + "field": -1, + "cost": 3, + "temperature": 0.7, + "temperature_bits": "3fe6666666666666", + "cut_a": 487689135598446, + "cut_b": 8979189990295212 + }, + { + "field": -1, + "cost": 3, + "temperature": 1.0, + "temperature_bits": "3ff0000000000000", + "cut_a": 1056638398089480, + "cut_b": 8864198797856871 + }, + { + "field": -1, + "cost": 3, + "temperature": 1.3, + "temperature_bits": "3ff4cccccccccccd", + "cut_a": 1533889445584808, + "cut_b": 8677856053636922 + }, + { + "field": -1, + "cost": 3, + "temperature": 1.8708286933869707, + "temperature_bits": "3ffdeeea11683f49", + "cut_a": 2116386622254950, + "cut_b": 8280566991108373 + }, + { + "field": -1, + "cost": 3, + "temperature": 2.9, + "temperature_bits": "4007333333333333", + "cut_a": 2577331454407162, + "cut_b": 7714025630093872 + }, + { + "field": -1, + "cost": 3, + "temperature": 7.1, + "temperature_bits": "401c666666666666", + "cut_a": 2924533635745818, + "cut_b": 6800616869932025 + }, + { + "field": -1, + "cost": 3, + "temperature": 13.7, + "temperature_bits": "402b666666666666", + "cut_a": 2981184042536846, + "cut_b": 6430948490545956 + }, + { + "field": -1, + "cost": 3, + "temperature": 25.0, + "temperature_bits": "4039000000000000", + "cut_a": 2996004865019906, + "cut_b": 6241538189987230 + }, + { + "field": -1, + "cost": 8, + "temperature": 0.25, + "temperature_bits": "3fd0000000000000", + "cut_a": 6227, + "cut_b": 9007199254740990 + }, + { + "field": -1, + "cost": 8, + "temperature": 0.3, + "temperature_bits": "3fd3333333333333", + "cut_a": 662295, + "cut_b": 9007199254740150 + }, + { + "field": -1, + "cost": 8, + "temperature": 0.7, + "temperature_bits": "3fe6666666666666", + "cut_a": 408906582993, + "cut_b": 9007175770164895 + }, + { + "field": -1, + "cost": 8, + "temperature": 1.0, + "temperature_bits": "3ff0000000000000", + "cut_a": 8205007983319, + "cut_b": 9006088827661610 + }, + { + "field": -1, + "cost": 8, + "temperature": 1.3, + "temperature_bits": "3ff4cccccccccccd", + "cut_a": 41083563750669, + "cut_b": 8998378154604110 + }, + { + "field": -1, + "cost": 8, + "temperature": 1.8708286933869707, + "temperature_bits": "3ffdeeea11683f49", + "cut_a": 207008957950385, + "cut_b": 8936125573719776 + }, + { + "field": -1, + "cost": 8, + "temperature": 2.9, + "temperature_bits": "4007333333333333", + "cut_a": 710457734062639, + "cut_b": 8650727757237116 + }, + { + "field": -1, + "cost": 8, + "temperature": 7.1, + "temperature_bits": "401c666666666666", + "cut_a": 2031040001025105, + "cut_b": 7474764464343560 + }, + { + "field": -1, + "cost": 8, + "temperature": 13.7, + "temperature_bits": "402b666666666666", + "cut_a": 2550861208561461, + "cut_b": 6802820711133947 + }, + { + "field": -1, + "cost": 8, + "temperature": 25.0, + "temperature_bits": "4039000000000000", + "cut_a": 2774650724047755, + "cut_b": 6445873815858995 + }, + { + "field": 0, + "cost": 0, + "temperature": 0.25, + "temperature_bits": "3fd0000000000000", + "cut_a": 3002399751580330, + "cut_b": 6004799503160661 + }, + { + "field": 0, + "cost": 0, + "temperature": 0.3, + "temperature_bits": "3fd3333333333333", + "cut_a": 3002399751580330, + "cut_b": 6004799503160661 + }, + { + "field": 0, + "cost": 0, + "temperature": 0.7, + "temperature_bits": "3fe6666666666666", + "cut_a": 3002399751580330, + "cut_b": 6004799503160661 + }, + { + "field": 0, + "cost": 0, + "temperature": 1.0, + "temperature_bits": "3ff0000000000000", + "cut_a": 3002399751580330, + "cut_b": 6004799503160661 + }, + { + "field": 0, + "cost": 0, + "temperature": 1.3, + "temperature_bits": "3ff4cccccccccccd", + "cut_a": 3002399751580330, + "cut_b": 6004799503160661 + }, + { + "field": 0, + "cost": 0, + "temperature": 1.8708286933869707, + "temperature_bits": "3ffdeeea11683f49", + "cut_a": 3002399751580330, + "cut_b": 6004799503160661 + }, + { + "field": 0, + "cost": 0, + "temperature": 2.9, + "temperature_bits": "4007333333333333", + "cut_a": 3002399751580330, + "cut_b": 6004799503160661 + }, + { + "field": 0, + "cost": 0, + "temperature": 7.1, + "temperature_bits": "401c666666666666", + "cut_a": 3002399751580330, + "cut_b": 6004799503160661 + }, + { + "field": 0, + "cost": 0, + "temperature": 13.7, + "temperature_bits": "402b666666666666", + "cut_a": 3002399751580330, + "cut_b": 6004799503160661 + }, + { + "field": 0, + "cost": 0, + "temperature": 25.0, + "temperature_bits": "4039000000000000", + "cut_a": 3002399751580330, + "cut_b": 6004799503160661 + }, + { + "field": 0, + "cost": 1, + "temperature": 0.25, + "temperature_bits": "3fd0000000000000", + "cut_a": 159142997597382, + "cut_b": 8848056257143609 + }, + { + "field": 0, + "cost": 1, + "temperature": 0.3, + "temperature_bits": "3fd3333333333333", + "cut_a": 299923806533116, + "cut_b": 8707275448207876 + }, + { + "field": 0, + "cost": 1, + "temperature": 0.7, + "temperature_bits": "3fe6666666666666", + "cut_a": 1459191247280966, + "cut_b": 7548008007460025 + }, + { + "field": 0, + "cost": 1, + "temperature": 1.0, + "temperature_bits": "3ff0000000000000", + "cut_a": 1908999839817257, + "cut_b": 7098199414923735 + }, + { + "field": 0, + "cost": 1, + "temperature": 1.3, + "temperature_bits": "3ff4cccccccccccd", + "cut_a": 2166178607349572, + "cut_b": 6841020647391420 + }, + { + "field": 0, + "cost": 1, + "temperature": 1.8708286933869707, + "temperature_bits": "3ffdeeea11683f49", + "cut_a": 2430021802829679, + "cut_b": 6577177451911314 + }, + { + "field": 0, + "cost": 1, + "temperature": 2.9, + "temperature_bits": "4007333333333333", + "cut_a": 2640055253307007, + "cut_b": 6367144001433985 + }, + { + "field": 0, + "cost": 1, + "temperature": 7.1, + "temperature_bits": "401c666666666666", + "cut_a": 2858297472519615, + "cut_b": 6148901782221377 + }, + { + "field": 0, + "cost": 1, + "temperature": 13.7, + "temperature_bits": "402b666666666666", + "cut_a": 2928482238978769, + "cut_b": 6078717015762223 + }, + { + "field": 0, + "cost": 1, + "temperature": 25.0, + "temperature_bits": "4039000000000000", + "cut_a": 2962104492389278, + "cut_b": 6045094762351714 + }, + { + "field": 0, + "cost": 2, + "temperature": 0.25, + "temperature_bits": "3fd0000000000000", + "cut_a": 3019552837776, + "cut_b": 9004179701903216 + }, + { + "field": 0, + "cost": 2, + "temperature": 0.3, + "temperature_bits": "3fd3333333333333", + "cut_a": 11433764237296, + "cut_b": 8995765490503696 + }, + { + "field": 0, + "cost": 2, + "temperature": 0.7, + "temperature_bits": "3fe6666666666666", + "cut_a": 464008588288903, + "cut_b": 8543190666452088 + }, + { + "field": 0, + "cost": 2, + "temperature": 1.0, + "temperature_bits": "3ff0000000000000", + "cut_a": 959329581145739, + "cut_b": 8047869673595252 + }, + { + "field": 0, + "cost": 2, + "temperature": 1.3, + "temperature_bits": "3ff4cccccccccccd", + "cut_a": 1352956541286651, + "cut_b": 7654242713454341 + }, + { + "field": 0, + "cost": 2, + "temperature": 1.8708286933869707, + "temperature_bits": "3ffdeeea11683f49", + "cut_a": 1833490531516565, + "cut_b": 7173708723224427 + }, + { + "field": 0, + "cost": 2, + "temperature": 2.9, + "temperature_bits": "4007333333333333", + "cut_a": 2255731461336446, + "cut_b": 6751467793404546 + }, + { + "field": 0, + "cost": 2, + "temperature": 7.1, + "temperature_bits": "401c666666666666", + "cut_a": 2708632341368445, + "cut_b": 6298566913372547 + }, + { + "field": 0, + "cost": 2, + "temperature": 13.7, + "temperature_bits": "402b666666666666", + "cut_a": 2852926113375564, + "cut_b": 6154273141365428 + }, + { + "field": 0, + "cost": 2, + "temperature": 25.0, + "temperature_bits": "4039000000000000", + "cut_a": 2921297646648624, + "cut_b": 6085901608092369 + }, + { + "field": 0, + "cost": 3, + "temperature": 0.25, + "temperature_bits": "3fd0000000000000", + "cut_a": 55341464870, + "cut_b": 9007143913276121 + }, + { + "field": 0, + "cost": 3, + "temperature": 0.3, + "temperature_bits": "3fd3333333333333", + "cut_a": 408889086450, + "cut_b": 9006790365654542 + }, + { + "field": 0, + "cost": 3, + "temperature": 0.7, + "temperature_bits": "3fe6666666666666", + "cut_a": 120651915146329, + "cut_b": 8886547339594662 + }, + { + "field": 0, + "cost": 3, + "temperature": 1.0, + "temperature_bits": "3ff0000000000000", + "cut_a": 407832478153805, + "cut_b": 8599366776587188 + }, + { + "field": 0, + "cost": 3, + "temperature": 1.3, + "temperature_bits": "3ff4cccccccccccd", + "cut_a": 747410811482069, + "cut_b": 8259788443258923 + }, + { + "field": 0, + "cost": 3, + "temperature": 1.8708286933869707, + "temperature_bits": "3ffdeeea11683f49", + "cut_a": 1292145083260838, + "cut_b": 7715054171480155 + }, + { + "field": 0, + "cost": 3, + "temperature": 2.9, + "temperature_bits": "4007333333333333", + "cut_a": 1871178477178995, + "cut_b": 7136020777561996 + }, + { + "field": 0, + "cost": 3, + "temperature": 7.1, + "temperature_bits": "401c666666666666", + "cut_a": 2554635977546243, + "cut_b": 6452563277194749 + }, + { + "field": 0, + "cost": 3, + "temperature": 13.7, + "temperature_bits": "402b666666666666", + "cut_a": 2775883907019783, + "cut_b": 6231315347721209 + }, + { + "field": 0, + "cost": 3, + "temperature": 25.0, + "temperature_bits": "4039000000000000", + "cut_a": 2880002660975465, + "cut_b": 6127196593765527 + }, + { + "field": 0, + "cost": 8, + "temperature": 0.25, + "temperature_bits": "3fd0000000000000", + "cut_a": 114, + "cut_b": 9007199254740878 + }, + { + "field": 0, + "cost": 8, + "temperature": 0.3, + "temperature_bits": "3fd3333333333333", + "cut_a": 23626, + "cut_b": 9007199254717366 + }, + { + "field": 0, + "cost": 8, + "temperature": 0.7, + "temperature_bits": "3fe6666666666666", + "cut_a": 97997458446, + "cut_b": 9007101257282546 + }, + { + "field": 0, + "cost": 8, + "temperature": 1.0, + "temperature_bits": "3ff0000000000000", + "cut_a": 3019552837776, + "cut_b": 9004179701903216 + }, + { + "field": 0, + "cost": 8, + "temperature": 1.3, + "temperature_bits": "3ff4cccccccccccd", + "cut_a": 19061902782868, + "cut_b": 8988137351958124 + }, + { + "field": 0, + "cost": 8, + "temperature": 1.8708286933869707, + "temperature_bits": "3ffdeeea11683f49", + "cut_a": 121776508108774, + "cut_b": 8885422746632218 + }, + { + "field": 0, + "cost": 8, + "temperature": 2.9, + "temperature_bits": "4007333333333333", + "cut_a": 506646770170414, + "cut_b": 8500552484570578 + }, + { + "field": 0, + "cost": 8, + "temperature": 7.1, + "temperature_bits": "401c666666666666", + "cut_a": 1771102793292234, + "cut_b": 7236096461448757 + }, + { + "field": 0, + "cost": 8, + "temperature": 13.7, + "temperature_bits": "402b666666666666", + "cut_a": 2374632310774866, + "cut_b": 6632566943966126 + }, + { + "field": 0, + "cost": 8, + "temperature": 25.0, + "temperature_bits": "4039000000000000", + "cut_a": 2667118297939809, + "cut_b": 6340080956801182 + }, + { + "field": 1, + "cost": 0, + "temperature": 0.25, + "temperature_bits": "3fd0000000000000", + "cut_a": 2966254812406, + "cut_b": 164918280096715 + }, + { + "field": 1, + "cost": 0, + "temperature": 0.3, + "temperature_bits": "3fd3333333333333", + "cut_a": 11054441884349, + "cut_b": 320928410204767 + }, + { + "field": 1, + "cost": 0, + "temperature": 0.7, + "temperature_bits": "3fe6666666666666", + "cut_a": 398823193236664, + "cut_b": 2063006245220084 + }, + { + "field": 1, + "cost": 0, + "temperature": 1.0, + "temperature_bits": "3ff0000000000000", + "cut_a": 810923311564155, + "cut_b": 3015241413662831 + }, + { + "field": 1, + "cost": 0, + "temperature": 1.3, + "temperature_bits": "3ff4cccccccccccd", + "cut_a": 1152475261817888, + "cut_b": 3639638502085757 + }, + { + "field": 1, + "cost": 0, + "temperature": 1.8708286933869707, + "temperature_bits": "3ffdeeea11683f49", + "cut_a": 1602924211255464, + "cut_b": 4338527731362543 + }, + { + "field": 1, + "cost": 0, + "temperature": 2.9, + "temperature_bits": "4007333333333333", + "cut_a": 2044871748176270, + "cut_b": 4931712269579647 + }, + { + "field": 1, + "cost": 0, + "temperature": 7.1, + "temperature_bits": "401c666666666666", + "cut_a": 2590796276030870, + "cut_b": 5573440830501033 + }, + { + "field": 1, + "cost": 0, + "temperature": 13.7, + "temperature_bits": "402b666666666666", + "cut_a": 2786103417707539, + "cut_b": 5783178066600828 + }, + { + "field": 1, + "cost": 0, + "temperature": 25.0, + "temperature_bits": "4039000000000000", + "cut_a": 2883136095813560, + "cut_b": 5883935207799292 + }, + { + "field": 1, + "cost": 1, + "temperature": 0.25, + "temperature_bits": "3fd0000000000000", + "cut_a": 1510536001830, + "cut_b": 4504354895371411 + }, + { + "field": 1, + "cost": 1, + "temperature": 0.3, + "temperature_bits": "3fd3333333333333", + "cut_a": 5727788424914, + "cut_b": 4506463521582954 + }, + { + "field": 1, + "cost": 1, + "temperature": 0.7, + "temperature_bits": "3fe6666666666666", + "cut_a": 251433286621678, + "cut_b": 4629316270681336 + }, + { + "field": 1, + "cost": 1, + "temperature": 1.0, + "temperature_bits": "3ff0000000000000", + "cut_a": 570866726119611, + "cut_b": 4789032990430302 + }, + { + "field": 1, + "cost": 1, + "temperature": 1.3, + "temperature_bits": "3ff4cccccccccccd", + "cut_a": 873227324471328, + "cut_b": 4940213289606160 + }, + { + "field": 1, + "cost": 1, + "temperature": 1.8708286933869707, + "temperature_bits": "3ffdeeea11683f49", + "cut_a": 1319698817270904, + "cut_b": 5163449036005948 + }, + { + "field": 1, + "cost": 1, + "temperature": 2.9, + "temperature_bits": "4007333333333333", + "cut_a": 1806477637533907, + "cut_b": 5406838446137450 + }, + { + "field": 1, + "cost": 1, + "temperature": 7.1, + "temperature_bits": "401c666666666666", + "cut_a": 2467228350283443, + "cut_b": 5737213802512218 + }, + { + "field": 1, + "cost": 1, + "temperature": 13.7, + "temperature_bits": "402b666666666666", + "cut_a": 2717629674414002, + "cut_b": 5862414464577497 + }, + { + "field": 1, + "cost": 1, + "temperature": 25.0, + "temperature_bits": "4039000000000000", + "cut_a": 2844461828380868, + "cut_b": 5925830541560930 + }, + { + "field": 1, + "cost": 2, + "temperature": 0.25, + "temperature_bits": "3fd0000000000000", + "cut_a": 54346421581, + "cut_b": 8845194855260975 + }, + { + "field": 1, + "cost": 2, + "temperature": 0.3, + "temperature_bits": "3fd3333333333333", + "cut_a": 394823362559, + "cut_b": 8696958113774865 + }, + { + "field": 1, + "cost": 2, + "temperature": 0.7, + "temperature_bits": "3fe6666666666666", + "cut_a": 98908332111733, + "cut_b": 7285036250980831 + }, + { + "field": 1, + "cost": 2, + "temperature": 1.0, + "temperature_bits": "3ff0000000000000", + "cut_a": 316324073455393, + "cut_b": 6669862930536829 + }, + { + "field": 1, + "cost": 2, + "temperature": 1.3, + "temperature_bits": "3ff4cccccccccccd", + "cut_a": 573392057710691, + "cut_b": 6336671906503244 + }, + { + "field": 1, + "cost": 2, + "temperature": 1.8708286933869707, + "temperature_bits": "3ffdeeea11683f49", + "cut_a": 1013943985142816, + "cut_b": 6053989504400095 + }, + { + "field": 1, + "cost": 2, + "temperature": 2.9, + "temperature_bits": "4007333333333333", + "cut_a": 1551179209539613, + "cut_b": 5915655403264880 + }, + { + "field": 1, + "cost": 2, + "temperature": 7.1, + "temperature_bits": "401c666666666666", + "cut_a": 2338807593611636, + "cut_b": 5907418561237846 + }, + { + "field": 1, + "cost": 2, + "temperature": 13.7, + "temperature_bits": "402b666666666666", + "cut_a": 2647631815564791, + "cut_b": 5943414537518075 + }, + { + "field": 1, + "cost": 2, + "temperature": 25.0, + "temperature_bits": "4039000000000000", + "cut_a": 2805295990404986, + "cut_b": 5968258387334856 + }, + { + "field": 1, + "cost": 3, + "temperature": 0.25, + "temperature_bits": "3fd0000000000000", + "cut_a": 1013286708, + "cut_b": 9004178689635584 + }, + { + "field": 1, + "cost": 3, + "temperature": 0.3, + "temperature_bits": "3fd3333333333333", + "cut_a": 14569465829, + "cut_b": 8995750976521430 + }, + { + "field": 1, + "cost": 3, + "temperature": 0.7, + "temperature_bits": "3fe6666666666666", + "cut_a": 28009264445778, + "cut_b": 8519510119142545 + }, + { + "field": 1, + "cost": 3, + "temperature": 1.0, + "temperature_bits": "3ff0000000000000", + "cut_a": 143000456884120, + "cut_b": 7950560856651510 + }, + { + "field": 1, + "cost": 3, + "temperature": 1.3, + "temperature_bits": "3ff4cccccccccccd", + "cut_a": 329343201104070, + "cut_b": 7473309809156184 + }, + { + "field": 1, + "cost": 3, + "temperature": 1.8708286933869707, + "temperature_bits": "3ffdeeea11683f49", + "cut_a": 726632263632619, + "cut_b": 6890812632486042 + }, + { + "field": 1, + "cost": 3, + "temperature": 2.9, + "temperature_bits": "4007333333333333", + "cut_a": 1293173624647120, + "cut_b": 6429867800333830 + }, + { + "field": 1, + "cost": 3, + "temperature": 7.1, + "temperature_bits": "401c666666666666", + "cut_a": 2206582384808967, + "cut_b": 6082665618995174 + }, + { + "field": 1, + "cost": 3, + "temperature": 13.7, + "temperature_bits": "402b666666666666", + "cut_a": 2576250764195036, + "cut_b": 6026015212204146 + }, + { + "field": 1, + "cost": 3, + "temperature": 25.0, + "temperature_bits": "4039000000000000", + "cut_a": 2765661064753762, + "cut_b": 6011194389721086 + }, + { + "field": 1, + "cost": 8, + "temperature": 0.25, + "temperature_bits": "3fd0000000000000", + "cut_a": 2, + "cut_b": 9007199254734764 + }, + { + "field": 1, + "cost": 8, + "temperature": 0.3, + "temperature_bits": "3fd3333333333333", + "cut_a": 842, + "cut_b": 9007199254078696 + }, + { + "field": 1, + "cost": 8, + "temperature": 0.7, + "temperature_bits": "3fe6666666666666", + "cut_a": 23484576097, + "cut_b": 9006790348157998 + }, + { + "field": 1, + "cost": 8, + "temperature": 1.0, + "temperature_bits": "3ff0000000000000", + "cut_a": 1110427079381, + "cut_b": 8998994246757673 + }, + { + "field": 1, + "cost": 8, + "temperature": 1.3, + "temperature_bits": "3ff4cccccccccccd", + "cut_a": 8821100136881, + "cut_b": 8966115690990324 + }, + { + "field": 1, + "cost": 8, + "temperature": 1.8708286933869707, + "temperature_bits": "3ffdeeea11683f49", + "cut_a": 71073681021216, + "cut_b": 8800190296790607 + }, + { + "field": 1, + "cost": 8, + "temperature": 2.9, + "temperature_bits": "4007333333333333", + "cut_a": 356471497503875, + "cut_b": 8296741520678352 + }, + { + "field": 1, + "cost": 8, + "temperature": 7.1, + "temperature_bits": "401c666666666666", + "cut_a": 1532434790397432, + "cut_b": 6976159253715887 + }, + { + "field": 1, + "cost": 8, + "temperature": 13.7, + "temperature_bits": "402b666666666666", + "cut_a": 2204378543607045, + "cut_b": 6456338046179531 + }, + { + "field": 1, + "cost": 8, + "temperature": 25.0, + "temperature_bits": "4039000000000000", + "cut_a": 2561325438881997, + "cut_b": 6232548530693237 + }, + { + "field": 2, + "cost": 0, + "temperature": 0.25, + "temperature_bits": "3fd0000000000000", + "cut_a": 1013286708, + "cut_b": 3021578392117 + }, + { + "field": 2, + "cost": 0, + "temperature": 0.3, + "temperature_bits": "3fd3333333333333", + "cut_a": 14569465829, + "cut_b": 11462847685391 + }, + { + "field": 2, + "cost": 0, + "temperature": 0.7, + "temperature_bits": "3fe6666666666666", + "cut_a": 28009264445778, + "cut_b": 515698400044225 + }, + { + "field": 2, + "cost": 0, + "temperature": 1.0, + "temperature_bits": "3ff0000000000000", + "cut_a": 143000456884120, + "cut_b": 1199638854973601 + }, + { + "field": 2, + "cost": 0, + "temperature": 1.3, + "temperature_bits": "3ff4cccccccccccd", + "cut_a": 329343201104070, + "cut_b": 1863232646688878 + }, + { + "field": 2, + "cost": 0, + "temperature": 1.8708286933869707, + "temperature_bits": "3ffdeeea11683f49", + "cut_a": 726632263632619, + "cut_b": 2843018885887569 + }, + { + "field": 2, + "cost": 0, + "temperature": 2.9, + "temperature_bits": "4007333333333333", + "cut_a": 1293173624647120, + "cut_b": 3870505079054282 + }, + { + "field": 2, + "cost": 0, + "temperature": 7.1, + "temperature_bits": "401c666666666666", + "cut_a": 2206582384808967, + "cut_b": 5131116020554785 + }, + { + "field": 2, + "cost": 0, + "temperature": 13.7, + "temperature_bits": "402b666666666666", + "cut_a": 2576250764195036, + "cut_b": 5557434806731883 + }, + { + "field": 2, + "cost": 0, + "temperature": 25.0, + "temperature_bits": "4039000000000000", + "cut_a": 2765661064753762, + "cut_b": 5761665929773668 + }, + { + "field": 2, + "cost": 1, + "temperature": 0.25, + "temperature_bits": "3fd0000000000000", + "cut_a": 995395328, + "cut_b": 162006354458105 + }, + { + "field": 2, + "cost": 1, + "temperature": 0.3, + "temperature_bits": "3fd3333333333333", + "cut_a": 14085521410, + "cut_b": 310268341076974 + }, + { + "field": 2, + "cost": 1, + "temperature": 0.7, + "temperature_bits": "3fe6666666666666", + "cut_a": 23903060887026, + "cut_b": 1760566186422741 + }, + { + "field": 2, + "cost": 1, + "temperature": 1.0, + "temperature_bits": "3ff0000000000000", + "cut_a": 119011105523148, + "cut_b": 2509413059777742 + }, + { + "field": 2, + "cost": 1, + "temperature": 1.3, + "temperature_bits": "3ff4cccccccccccd", + "cut_a": 275089805796928, + "cut_b": 3040073228636088 + }, + { + "field": 2, + "cost": 1, + "temperature": 1.8708286933869707, + "temperature_bits": "3ffdeeea11683f49", + "cut_a": 623165141886396, + "cut_b": 3720753102506797 + }, + { + "field": 2, + "cost": 1, + "temperature": 2.9, + "temperature_bits": "4007333333333333", + "cut_a": 1156873490506383, + "cut_b": 4411911191769500 + }, + { + "field": 2, + "cost": 1, + "temperature": 7.1, + "temperature_bits": "401c666666666666", + "cut_a": 2103293994059345, + "cut_b": 5312552436628296 + }, + { + "field": 2, + "cost": 1, + "temperature": 13.7, + "temperature_bits": "402b666666666666", + "cut_a": 2513262042579041, + "cut_b": 5641780731234422 + }, + { + "field": 2, + "cost": 1, + "temperature": 25.0, + "temperature_bits": "4039000000000000", + "cut_a": 2728621079300354, + "cut_b": 5805132755364620 + }, + { + "field": 2, + "cost": 2, + "temperature": 0.25, + "temperature_bits": "3fd0000000000000", + "cut_a": 506813342, + "cut_b": 4503599880777167 + }, + { + "field": 2, + "cost": 2, + "temperature": 0.3, + "temperature_bits": "3fd3333333333333", + "cut_a": 7294009603, + "cut_b": 4503603274375298 + }, + { + "field": 2, + "cost": 2, + "temperature": 0.7, + "temperature_bits": "3fe6666666666666", + "cut_a": 14830689735598, + "cut_b": 4511014972238295 + }, + { + "field": 2, + "cost": 2, + "temperature": 1.0, + "temperature_bits": "3ff0000000000000", + "cut_a": 81737764782689, + "cut_b": 4544468509761841 + }, + { + "field": 2, + "cost": 2, + "temperature": 1.3, + "temperature_bits": "3ff4cccccccccccd", + "cut_a": 202942035908023, + "cut_b": 4605070645324508 + }, + { + "field": 2, + "cost": 2, + "temperature": 1.8708286933869707, + "temperature_bits": "3ffdeeea11683f49", + "cut_a": 501334744705797, + "cut_b": 4754266999723395 + }, + { + "field": 2, + "cost": 2, + "temperature": 2.9, + "temperature_bits": "4007333333333333", + "cut_a": 1007029792631577, + "cut_b": 5007114523686285 + }, + { + "field": 2, + "cost": 2, + "temperature": 7.1, + "temperature_bits": "401c666666666666", + "cut_a": 1995745399847763, + "cut_b": 5501472327294377 + }, + { + "field": 2, + "cost": 2, + "temperature": 13.7, + "temperature_bits": "402b666666666666", + "cut_a": 2448854320738945, + "cut_b": 5728026787739969 + }, + { + "field": 2, + "cost": 2, + "temperature": 25.0, + "temperature_bits": "4039000000000000", + "cut_a": 2691108677830952, + "cut_b": 5849153966285972 + }, + { + "field": 2, + "cost": 3, + "temperature": 0.25, + "temperature_bits": "3fd0000000000000", + "cut_a": 18231303, + "cut_b": 8845193878102738 + }, + { + "field": 2, + "cost": 3, + "temperature": 0.3, + "temperature_bits": "3fd3333333333333", + "cut_a": 502487554, + "cut_b": 8696944531315170 + }, + { + "field": 2, + "cost": 3, + "temperature": 0.7, + "temperature_bits": "3fe6666666666666", + "cut_a": 5739975400416, + "cut_b": 7267024816639169 + }, + { + "field": 2, + "cost": 3, + "temperature": 1.0, + "temperature_bits": "3ff0000000000000", + "cut_a": 44150489955186, + "cut_b": 6596664180130918 + }, + { + "field": 2, + "cost": 3, + "temperature": 1.3, + "temperature_bits": "3ff4cccccccccccd", + "cut_a": 129592112731874, + "cut_b": 6196144637794794 + }, + { + "field": 2, + "cost": 3, + "temperature": 1.8708286933869707, + "temperature_bits": "3ffdeeea11683f49", + "cut_a": 375911445150005, + "cut_b": 5818260273368510 + }, + { + "field": 2, + "cost": 3, + "temperature": 2.9, + "temperature_bits": "4007333333333333", + "cut_a": 851354477047827, + "cut_b": 5625481981556026 + }, + { + "field": 2, + "cost": 3, + "temperature": 7.1, + "temperature_bits": "401c666666666666", + "cut_a": 1884793011313863, + "cut_b": 5696371324018851 + }, + { + "field": 2, + "cost": 3, + "temperature": 13.7, + "temperature_bits": "402b666666666666", + "cut_a": 2383156191991508, + "cut_b": 5816000781577657 + }, + { + "field": 2, + "cost": 3, + "temperature": 25.0, + "temperature_bits": "4039000000000000", + "cut_a": 2653145334065807, + "cut_b": 5893704362893569 + }, + { + "field": 2, + "cost": 8, + "temperature": 0.25, + "temperature_bits": "3fd0000000000000", + "cut_a": 0, + "cut_b": 9007199254400958 + }, + { + "field": 2, + "cost": 8, + "temperature": 0.3, + "temperature_bits": "3fd3333333333333", + "cut_a": 30, + "cut_b": 9007199236175770 + }, + { + "field": 2, + "cost": 8, + "temperature": 0.7, + "temperature_bits": "3fe6666666666666", + "cut_a": 5627303629, + "cut_b": 9005493238730772 + }, + { + "field": 2, + "cost": 8, + "temperature": 1.0, + "temperature_bits": "3ff0000000000000", + "cut_a": 407896620408, + "cut_b": 8984928853861926 + }, + { + "field": 2, + "cost": 8, + "temperature": 1.3, + "temperature_bits": "3ff4cccccccccccd", + "cut_a": 4068076586605, + "cut_b": 8918956342191777 + }, + { + "field": 2, + "cost": 8, + "temperature": 1.8708286933869707, + "temperature_bits": "3ffdeeea11683f49", + "cut_a": 41112201478152, + "cut_b": 8658435460514394 + }, + { + "field": 2, + "cost": 8, + "temperature": 2.9, + "temperature_bits": "4007333333333333", + "cut_a": 247326258799881, + "cut_b": 8024779470685770 + }, + { + "field": 2, + "cost": 8, + "temperature": 7.1, + "temperature_bits": "401c666666666666", + "cut_a": 1315646613919415, + "cut_b": 6696134044833414 + }, + { + "field": 2, + "cost": 8, + "temperature": 13.7, + "temperature_bits": "402b666666666666", + "cut_a": 2040601132905613, + "cut_b": 6274703901919833 + }, + { + "field": 2, + "cost": 8, + "temperature": 25.0, + "temperature_bits": "4039000000000000", + "cut_a": 2457400989918110, + "cut_b": 6123412478686053 + }, + { + "field": 3, + "cost": 0, + "temperature": 0.25, + "temperature_bits": "3fd0000000000000", + "cut_a": 340031, + "cut_b": 55342144927 + }, + { + "field": 3, + "cost": 0, + "temperature": 0.3, + "temperature_bits": "3fd3333333333333", + "cut_a": 18564378, + "cut_b": 408926212679 + }, + { + "field": 3, + "cost": 0, + "temperature": 0.7, + "temperature_bits": "3fe6666666666666", + "cut_a": 1682858951473, + "cut_b": 123950007092635 + }, + { + "field": 3, + "cost": 0, + "temperature": 1.0, + "temperature_bits": "3ff0000000000000", + "cut_a": 21217656529795, + "cut_b": 447385680182508 + }, + { + "field": 3, + "cost": 0, + "temperature": 1.3, + "temperature_bits": "3ff4cccccccccccd", + "cut_a": 80365538977071, + "cut_b": 888135868362426 + }, + { + "field": 3, + "cost": 0, + "temperature": 1.8708286933869707, + "temperature_bits": "3ffdeeea11683f49", + "cut_a": 293595641301463, + "cut_b": 1752981384594260 + }, + { + "field": 3, + "cost": 0, + "temperature": 2.9, + "temperature_bits": "4007333333333333", + "cut_a": 767858859174777, + "cut_b": 2928345339653082 + }, + { + "field": 3, + "cost": 0, + "temperature": 7.1, + "temperature_bits": "401c666666666666", + "cut_a": 1855636801368659, + "cut_b": 4687013721549217 + }, + { + "field": 3, + "cost": 0, + "temperature": 13.7, + "temperature_bits": "402b666666666666", + "cut_a": 2373850718784515, + "cut_b": 5328829631438744 + }, + { + "field": 3, + "cost": 0, + "temperature": 25.0, + "temperature_bits": "4039000000000000", + "cut_a": 2650153688949875, + "cut_b": 5638193629442327 + }, + { + "field": 3, + "cost": 1, + "temperature": 0.25, + "temperature_bits": "3fd0000000000000", + "cut_a": 339919, + "cut_b": 3020565785020 + }, + { + "field": 3, + "cost": 1, + "temperature": 0.3, + "temperature_bits": "3fd3333333333333", + "cut_a": 18541624, + "cut_b": 11448315255648 + }, + { + "field": 3, + "cost": 1, + "temperature": 0.7, + "temperature_bits": "3fe6666666666666", + "cut_a": 1613374313780, + "cut_b": 490736156917774 + }, + { + "field": 3, + "cost": 1, + "temperature": 1.0, + "temperature_bits": "3ff0000000000000", + "cut_a": 19622375944982, + "cut_b": 1090967801795889 + }, + { + "field": 3, + "cost": 1, + "temperature": 1.3, + "temperature_bits": "3ff4cccccccccccd", + "cut_a": 72804137075104, + "cut_b": 1652039179574869 + }, + { + "field": 3, + "cost": 1, + "temperature": 1.8708286933869707, + "temperature_bits": "3ffdeeea11683f49", + "cut_a": 263434550670494, + "cut_b": 2498207443361367 + }, + { + "field": 3, + "cost": 1, + "temperature": 2.9, + "temperature_bits": "4007333333333333", + "cut_a": 698839758259469, + "cut_b": 3474743973727275 + }, + { + "field": 3, + "cost": 1, + "temperature": 7.1, + "temperature_bits": "401c666666666666", + "cut_a": 1771417149623234, + "cut_b": 4883089009996113 + }, + { + "field": 3, + "cost": 1, + "temperature": 13.7, + "temperature_bits": "402b666666666666", + "cut_a": 2316308446078346, + "cut_b": 5417993514535201 + }, + { + "field": 3, + "cost": 1, + "temperature": 25.0, + "temperature_bits": "4039000000000000", + "cut_a": 2614753770658096, + "cut_b": 5683195745492198 + }, + { + "field": 3, + "cost": 2, + "temperature": 0.25, + "temperature_bits": "3fd0000000000000", + "cut_a": 333917, + "cut_b": 162005377294078 + }, + { + "field": 3, + "cost": 2, + "temperature": 0.3, + "temperature_bits": "3fd3333333333333", + "cut_a": 17925738, + "cut_b": 310254758042388 + }, + { + "field": 3, + "cost": 2, + "temperature": 0.7, + "temperature_bits": "3fe6666666666666", + "cut_a": 1376257911372, + "cut_b": 1742394295855607 + }, + { + "field": 3, + "cost": 2, + "temperature": 1.0, + "temperature_bits": "3ff0000000000000", + "cut_a": 16292539327843, + "cut_b": 2434319770776090 + }, + { + "field": 3, + "cost": 2, + "temperature": 1.3, + "temperature_bits": "3ff4cccccccccccd", + "cut_a": 60516251512812, + "cut_b": 2893443431131956 + }, + { + "field": 3, + "cost": 2, + "temperature": 1.8708286933869707, + "temperature_bits": "3ffdeeea11683f49", + "cut_a": 224138102823128, + "cut_b": 3469151674495050 + }, + { + "field": 3, + "cost": 2, + "temperature": 2.9, + "temperature_bits": "4007333333333333", + "cut_a": 620146277186814, + "cut_b": 4097731088865217 + }, + { + "field": 3, + "cost": 2, + "temperature": 7.1, + "temperature_bits": "401c666666666666", + "cut_a": 1683456081954724, + "cut_b": 5087874845104211 + }, + { + "field": 3, + "cost": 2, + "temperature": 13.7, + "temperature_bits": "402b666666666666", + "cut_a": 2257444283010648, + "cut_b": 5509205715713629 + }, + { + "field": 3, + "cost": 2, + "temperature": 25.0, + "temperature_bits": "4039000000000000", + "cut_a": 2578899784678760, + "cut_b": 5728775094704065 + }, + { + "field": 3, + "cost": 3, + "temperature": 0.25, + "temperature_bits": "3fd0000000000000", + "cut_a": 170016, + "cut_b": 4503599627455505 + }, + { + "field": 3, + "cost": 3, + "temperature": 0.3, + "temperature_bits": "3fd3333333333333", + "cut_a": 9282610, + "cut_b": 4503599632011801 + }, + { + "field": 3, + "cost": 3, + "temperature": 0.7, + "temperature_bits": "3fe6666666666666", + "cut_a": 853089328127, + "cut_b": 4504026172034559 + }, + { + "field": 3, + "cost": 3, + "temperature": 1.0, + "temperature_bits": "3ff0000000000000", + "cut_a": 11149488969152, + "cut_b": 4509174371855072 + }, + { + "field": 3, + "cost": 3, + "temperature": 1.3, + "temperature_bits": "3ff4cccccccccccd", + "cut_a": 44358780759459, + "cut_b": 4525779017750226 + }, + { + "field": 3, + "cost": 3, + "temperature": 1.8708286933869707, + "temperature_bits": "3ffdeeea11683f49", + "cut_a": 178656183962285, + "cut_b": 4592927719351639 + }, + { + "field": 3, + "cost": 3, + "temperature": 2.9, + "temperature_bits": "4007333333333333", + "cut_a": 535083521699953, + "cut_b": 4771141388220473 + }, + { + "field": 3, + "cost": 3, + "temperature": 7.1, + "temperature_bits": "401c666666666666", + "cut_a": 1592423579036168, + "cut_b": 5299811416888580 + }, + { + "field": 3, + "cost": 3, + "temperature": 13.7, + "temperature_bits": "402b666666666666", + "cut_a": 2197374105902678, + "cut_b": 5602286680321835 + }, + { + "field": 3, + "cost": 3, + "temperature": 25.0, + "temperature_bits": "4039000000000000", + "cut_a": 2542612159646365, + "cut_b": 5774905707193679 + }, + { + "field": 3, + "cost": 8, + "temperature": 0.25, + "temperature_bits": "3fd0000000000000", + "cut_a": 0, + "cut_b": 9007199236175770 + }, + { + "field": 3, + "cost": 8, + "temperature": 0.3, + "temperature_bits": "3fd3333333333333", + "cut_a": 1, + "cut_b": 9007198734327700 + }, + { + "field": 3, + "cost": 8, + "temperature": 0.7, + "temperature_bits": "3fe6666666666666", + "cut_a": 1347779860, + "cut_b": 9000084775883804 + }, + { + "field": 3, + "cost": 8, + "temperature": 1.0, + "temperature_bits": "3ff0000000000000", + "cut_a": 149426225252, + "cut_b": 8946916412971132 + }, + { + "field": 3, + "cost": 8, + "temperature": 1.3, + "temperature_bits": "3ff4cccccccccccd", + "cut_a": 1864321563854, + "cut_b": 8818853041398896 + }, + { + "field": 3, + "cost": 8, + "temperature": 1.8708286933869707, + "temperature_bits": "3ffdeeea11683f49", + "cut_a": 23491299281488, + "cut_b": 8426771690199055 + }, + { + "field": 3, + "cost": 8, + "temperature": 2.9, + "temperature_bits": "4007333333333333", + "cut_a": 168957034580523, + "cut_b": 7669629050439056 + }, + { + "field": 3, + "cost": 8, + "temperature": 7.1, + "temperature_bits": "401c666666666666", + "cut_a": 1120814885110998, + "cut_b": 6397783312746775 + }, + { + "field": 3, + "cost": 8, + "temperature": 13.7, + "temperature_bits": "402b666666666666", + "cut_a": 1883726527477679, + "cut_b": 6088295221537755 + }, + { + "field": 3, + "cost": 8, + "temperature": 25.0, + "temperature_bits": "4039000000000000", + "cut_a": 2355466011106824, + "cut_b": 6012815089510493 + }, + { + "field": 5, + "cost": 0, + "temperature": 0.25, + "temperature_bits": "3fd0000000000000", + "cut_a": 0, + "cut_b": 18565221 + }, + { + "field": 5, + "cost": 0, + "temperature": 0.3, + "temperature_bits": "3fd3333333333333", + "cut_a": 30, + "cut_b": 520413321 + }, + { + "field": 5, + "cost": 0, + "temperature": 0.7, + "temperature_bits": "3fe6666666666666", + "cut_a": 5623924020, + "cut_b": 7120099403628 + }, + { + "field": 5, + "cost": 0, + "temperature": 1.0, + "temperature_bits": "3ff0000000000000", + "cut_a": 406171014617, + "cut_b": 60687294429877 + }, + { + "field": 5, + "cost": 0, + "temperature": 1.3, + "temperature_bits": "3ff4cccccccccccd", + "cut_a": 4022438480743, + "cut_b": 192323514895421 + }, + { + "field": 5, + "cost": 0, + "temperature": 1.8708286933869707, + "temperature_bits": "3ffdeeea11683f49", + "cut_a": 40017279526498, + "cut_b": 619377118524279 + }, + { + "field": 5, + "cost": 0, + "temperature": 2.9, + "temperature_bits": "4007333333333333", + "cut_a": 236696367288034, + "cut_b": 1564014972818192 + }, + { + "field": 5, + "cost": 0, + "temperature": 7.1, + "temperature_bits": "401c666666666666", + "cut_a": 1266497962690500, + "cut_b": 3827710859119419 + }, + { + "field": 5, + "cost": 0, + "temperature": 13.7, + "temperature_bits": "402b666666666666", + "cut_a": 1994777437087062, + "cut_b": 4868177407825214 + }, + { + "field": 5, + "cost": 0, + "temperature": 25.0, + "temperature_bits": "4039000000000000", + "cut_a": 2425706305889881, + "cut_b": 5388470678390298 + }, + { + "field": 5, + "cost": 1, + "temperature": 0.25, + "temperature_bits": "3fd0000000000000", + "cut_a": 0, + "cut_b": 1013626627 + }, + { + "field": 5, + "cost": 1, + "temperature": 0.3, + "temperature_bits": "3fd3333333333333", + "cut_a": 30, + "cut_b": 14588007424 + }, + { + "field": 5, + "cost": 1, + "temperature": 0.7, + "temperature_bits": "3fe6666666666666", + "cut_a": 5609865513, + "cut_b": 29618212607400 + }, + { + "field": 5, + "cost": 1, + "temperature": 1.0, + "temperature_bits": "3ff0000000000000", + "cut_a": 401553278193, + "cut_b": 162399707822786 + }, + { + "field": 5, + "cost": 1, + "temperature": 1.3, + "temperature_bits": "3ff4cccccccccccd", + "cut_a": 3927353662416, + "cut_b": 400694845447471 + }, + { + "field": 5, + "cost": 1, + "temperature": 1.8708286933869707, + "temperature_bits": "3ffdeeea11683f49", + "cut_a": 38277492549555, + "cut_b": 984045179176351 + }, + { + "field": 5, + "cost": 1, + "temperature": 2.9, + "temperature_bits": "4007333333333333", + "cut_a": 223156192672652, + "cut_b": 1989801038005483 + }, + { + "field": 5, + "cost": 1, + "temperature": 7.1, + "temperature_bits": "401c666666666666", + "cut_a": 1214275445480170, + "cut_b": 4041280821211143 + }, + { + "field": 5, + "cost": 1, + "temperature": 13.7, + "temperature_bits": "402b666666666666", + "cut_a": 1947727344902983, + "cut_b": 4965803015488834 + }, + { + "field": 5, + "cost": 1, + "temperature": 25.0, + "temperature_bits": "4039000000000000", + "cut_a": 2393574938641579, + "cut_b": 5436405045620295 + }, + { + "field": 5, + "cost": 2, + "temperature": 0.25, + "temperature_bits": "3fd0000000000000", + "cut_a": 0, + "cut_b": 55341804898 + }, + { + "field": 5, + "cost": 2, + "temperature": 0.3, + "temperature_bits": "3fd3333333333333", + "cut_a": 30, + "cut_b": 408907649173 + }, + { + "field": 5, + "cost": 2, + "temperature": 0.7, + "temperature_bits": "3fe6666666666666", + "cut_a": 5551953814, + "cut_b": 122295472752773 + }, + { + "field": 5, + "cost": 2, + "temperature": 1.0, + "temperature_bits": "3ff0000000000000", + "cut_a": 389515685474, + "cut_b": 427545332104394 + }, + { + "field": 5, + "cost": 2, + "temperature": 1.3, + "temperature_bits": "3ff4cccccccccccd", + "cut_a": 3736726091786, + "cut_b": 818441034914379 + }, + { + "field": 5, + "cost": 2, + "temperature": 1.8708286933869707, + "temperature_bits": "3ffdeeea11683f49", + "cut_a": 35633574046562, + "cut_b": 1538223734244610 + }, + { + "field": 5, + "cost": 2, + "temperature": 2.9, + "temperature_bits": "4007333333333333", + "cut_a": 206481048864690, + "cut_b": 2514169745951028 + }, + { + "field": 5, + "cost": 2, + "temperature": 7.1, + "temperature_bits": "401c666666666666", + "cut_a": 1159245756545582, + "cut_b": 4266331033408771 + }, + { + "field": 5, + "cost": 2, + "temperature": 13.7, + "temperature_bits": "402b666666666666", + "cut_a": 1899531147141151, + "cut_b": 5065806711021807 + }, + { + "field": 5, + "cost": 2, + "temperature": 25.0, + "temperature_bits": "4039000000000000", + "cut_a": 2361024025628883, + "cut_b": 5484965301583408 + }, + { + "field": 5, + "cost": 3, + "temperature": 0.25, + "temperature_bits": "3fd0000000000000", + "cut_a": 0, + "cut_b": 3020565445214 + }, + { + "field": 5, + "cost": 3, + "temperature": 0.3, + "temperature_bits": "3fd3333333333333", + "cut_a": 30, + "cut_b": 11448296737620 + }, + { + "field": 5, + "cost": 3, + "temperature": 0.7, + "temperature_bits": "3fe6666666666666", + "cut_a": 5322674716, + "cut_b": 489215443815337 + }, + { + "field": 5, + "cost": 3, + "temperature": 1.0, + "temperature_bits": "3ff0000000000000", + "cut_a": 360166611591, + "cut_b": 1074001704099640 + }, + { + "field": 5, + "cost": 3, + "temperature": 1.3, + "temperature_bits": "3ff4cccccccccccd", + "cut_a": 3382414613577, + "cut_b": 1594888373653081 + }, + { + "field": 5, + "cost": 3, + "temperature": 1.8708286933869707, + "temperature_bits": "3ffdeeea11683f49", + "cut_a": 31875992110411, + "cut_b": 2325831720485294 + }, + { + "field": 5, + "cost": 3, + "temperature": 2.9, + "temperature_bits": "4007333333333333", + "cut_a": 186777604013013, + "cut_b": 3133766796383446 + }, + { + "field": 5, + "cost": 3, + "temperature": 7.1, + "temperature_bits": "401c666666666666", + "cut_a": 1101763166164174, + "cut_b": 4501412666859080 + }, + { + "field": 5, + "cost": 3, + "temperature": 13.7, + "temperature_bits": "402b666666666666", + "cut_a": 1850279331966917, + "cut_b": 5168000737892341 + }, + { + "field": 5, + "cost": 3, + "temperature": 25.0, + "temperature_bits": "4039000000000000", + "cut_a": 2328071834239561, + "cut_b": 5534124194539400 + }, + { + "field": 5, + "cost": 8, + "temperature": 0.25, + "temperature_bits": "3fd0000000000000", + "cut_a": 0, + "cut_b": 9007143912936094 + }, + { + "field": 5, + "cost": 8, + "temperature": 0.3, + "temperature_bits": "3fd3333333333333", + "cut_a": 0, + "cut_b": 9006790347091849 + }, + { + "field": 5, + "cost": 8, + "temperature": 0.7, + "temperature_bits": "3fe6666666666666", + "cut_a": 76415954, + "cut_b": 8884909259601110 + }, + { + "field": 5, + "cost": 8, + "temperature": 1.0, + "temperature_bits": "3ff0000000000000", + "cut_a": 19393640984, + "cut_b": 8580025884960930 + }, + { + "field": 5, + "cost": 8, + "temperature": 1.3, + "temperature_bits": "3ff4cccccccccccd", + "cut_a": 371907987371, + "cut_b": 8192190470634656 + }, + { + "field": 5, + "cost": 8, + "temperature": 1.8708286933869707, + "temperature_bits": "3ffdeeea11683f49", + "cut_a": 7191401497423, + "cut_b": 7499845496958306 + }, + { + "field": 5, + "cost": 8, + "temperature": 2.9, + "temperature_bits": "4007333333333333", + "cut_a": 74486113793332, + "cut_b": 6664899380178999 + }, + { + "field": 5, + "cost": 8, + "temperature": 7.1, + "temperature_bits": "401c666666666666", + "cut_a": 795011236838540, + "cut_b": 5755909796014683 + }, + { + "field": 5, + "cost": 8, + "temperature": 13.7, + "temperature_bits": "402b666666666666", + "cut_a": 1591994144319806, + "cut_b": 5703924202099579 + }, + { + "field": 5, + "cost": 8, + "temperature": 25.0, + "temperature_bits": "4039000000000000", + "cut_a": 2158006202706463, + "cut_b": 5787832303880436 + }, + { + "field": 9, + "cost": 0, + "temperature": 0.25, + "temperature_bits": "3fd0000000000000", + "cut_a": 0, + "cut_b": 2 + }, + { + "field": 9, + "cost": 0, + "temperature": 0.3, + "temperature_bits": "3fd3333333333333", + "cut_a": 0, + "cut_b": 842 + }, + { + "field": 9, + "cost": 0, + "temperature": 0.7, + "temperature_bits": "3fe6666666666666", + "cut_a": 61237, + "cut_b": 23485703529 + }, + { + "field": 9, + "cost": 0, + "temperature": 1.0, + "temperature_bits": "3ff0000000000000", + "cut_a": 137162532, + "cut_b": 1111576678470 + }, + { + "field": 9, + "cost": 0, + "temperature": 1.3, + "temperature_bits": "3ff4cccccccccccd", + "cut_a": 8726772568, + "cut_b": 8870237413014 + }, + { + "field": 9, + "cost": 0, + "temperature": 1.8708286933869707, + "temperature_bits": "3ffdeeea11683f49", + "cut_a": 592265548753, + "cut_b": 73333046218776 + }, + { + "field": 9, + "cost": 0, + "temperature": 2.9, + "temperature_bits": "4007333333333333", + "cut_a": 17340421962921, + "cut_b": 403591871701568 + }, + { + "field": 9, + "cost": 0, + "temperature": 7.1, + "temperature_bits": "401c666666666666", + "cut_a": 524545567723380, + "cut_b": 2387908078829300 + }, + { + "field": 9, + "cost": 0, + "temperature": 13.7, + "temperature_bits": "402b666666666666", + "cut_a": 1354586403569205, + "cut_b": 3967407394507997 + }, + { + "field": 9, + "cost": 0, + "temperature": 25.0, + "temperature_bits": "4039000000000000", + "cut_a": 2007057860893677, + "cut_b": 4883832929637141 + }, + { + "field": 9, + "cost": 1, + "temperature": 0.25, + "temperature_bits": "3fd0000000000000", + "cut_a": 0, + "cut_b": 114 + }, + { + "field": 9, + "cost": 1, + "temperature": 0.3, + "temperature_bits": "3fd3333333333333", + "cut_a": 0, + "cut_b": 23626 + }, + { + "field": 9, + "cost": 1, + "temperature": 0.7, + "temperature_bits": "3fe6666666666666", + "cut_a": 61236, + "cut_b": 97998585897 + }, + { + "field": 9, + "cost": 1, + "temperature": 1.0, + "temperature_bits": "3ff0000000000000", + "cut_a": 137133456, + "cut_b": 3020702532684 + }, + { + "field": 9, + "cost": 1, + "temperature": 1.3, + "temperature_bits": "3ff4cccccccccccd", + "cut_a": 8716840830, + "cut_b": 19111027325975 + }, + { + "field": 9, + "cost": 1, + "temperature": 1.8708286933869707, + "temperature_bits": "3ffdeeea11683f49", + "cut_a": 588904867604, + "cut_b": 124026313483828 + }, + { + "field": 9, + "cost": 1, + "temperature": 2.9, + "temperature_bits": "4007333333333333", + "cut_a": 17039559078451, + "cut_b": 552867716294253 + }, + { + "field": 9, + "cost": 1, + "temperature": 7.1, + "temperature_bits": "401c666666666666", + "cut_a": 508630991409935, + "cut_b": 2588735654243134 + }, + { + "field": 9, + "cost": 1, + "temperature": 13.7, + "temperature_bits": "402b666666666666", + "cut_a": 1325471439083876, + "cut_b": 4075730762170263 + }, + { + "field": 9, + "cost": 1, + "temperature": 25.0, + "temperature_bits": "4039000000000000", + "cut_a": 1981233673154828, + "cut_b": 4936886998565735 + }, + { + "field": 9, + "cost": 2, + "temperature": 0.25, + "temperature_bits": "3fd0000000000000", + "cut_a": 0, + "cut_b": 6227 + }, + { + "field": 9, + "cost": 2, + "temperature": 0.3, + "temperature_bits": "3fd3333333333333", + "cut_a": 0, + "cut_b": 662295 + }, + { + "field": 9, + "cost": 2, + "temperature": 0.7, + "temperature_bits": "3fe6666666666666", + "cut_a": 61234, + "cut_b": 408907710375 + }, + { + "field": 9, + "cost": 2, + "temperature": 1.0, + "temperature_bits": "3ff0000000000000", + "cut_a": 137054482, + "cut_b": 8206156568855 + }, + { + "field": 9, + "cost": 2, + "temperature": 1.3, + "temperature_bits": "3ff4cccccccccccd", + "cut_a": 8695483940, + "cut_b": 41132493705992 + }, + { + "field": 9, + "cost": 2, + "temperature": 1.8708286933869707, + "temperature_bits": "3ffdeeea11683f49", + "cut_a": 583256659235, + "cut_b": 209225153758950 + }, + { + "field": 9, + "cost": 2, + "temperature": 2.9, + "temperature_bits": "4007333333333333", + "cut_a": 16632165234391, + "cut_b": 754999861178278 + }, + { + "field": 9, + "cost": 2, + "temperature": 7.1, + "temperature_bits": "401c666666666666", + "cut_a": 491464854864833, + "cut_b": 2805356789086861 + }, + { + "field": 9, + "cost": 2, + "temperature": 13.7, + "temperature_bits": "402b666666666666", + "cut_a": 1295517552493447, + "cut_b": 4187175372438770 + }, + { + "field": 9, + "cost": 2, + "temperature": 25.0, + "temperature_bits": "4039000000000000", + "cut_a": 1955052031351381, + "cut_b": 4990675432995921 + }, + { + "field": 9, + "cost": 3, + "temperature": 0.25, + "temperature_bits": "3fd0000000000000", + "cut_a": 0, + "cut_b": 340033 + }, + { + "field": 9, + "cost": 3, + "temperature": 0.3, + "temperature_bits": "3fd3333333333333", + "cut_a": 0, + "cut_b": 18565221 + }, + { + "field": 9, + "cost": 3, + "temperature": 0.7, + "temperature_bits": "3fe6666666666666", + "cut_a": 61225, + "cut_b": 1706017137280 + }, + { + "field": 9, + "cost": 3, + "temperature": 1.0, + "temperature_bits": "3ff0000000000000", + "cut_a": 136840267, + "cut_b": 22271545955608 + }, + { + "field": 9, + "cost": 3, + "temperature": 1.3, + "temperature_bits": "3ff4cccccccccccd", + "cut_a": 8649748269, + "cut_b": 88291350193539 + }, + { + "field": 9, + "cost": 3, + "temperature": 1.8708286933869707, + "temperature_bits": "3ffdeeea11683f49", + "cut_a": 573863443623, + "cut_b": 350914522193237 + }, + { + "field": 9, + "cost": 3, + "temperature": 2.9, + "temperature_bits": "4007333333333333", + "cut_a": 16089108978639, + "cut_b": 1024442140113835 + }, + { + "field": 9, + "cost": 3, + "temperature": 7.1, + "temperature_bits": "401c666666666666", + "cut_a": 473083570782539, + "cut_b": 3037311988052565 + }, + { + "field": 9, + "cost": 3, + "temperature": 13.7, + "temperature_bits": "402b666666666666", + "cut_a": 1264771096320240, + "cut_b": 4301568768935624 + }, + { + "field": 9, + "cost": 3, + "temperature": 25.0, + "temperature_bits": "4039000000000000", + "cut_a": 1928526832926022, + "cut_b": 5045169681559897 + }, + { + "field": 9, + "cost": 8, + "temperature": 0.25, + "temperature_bits": "3fd0000000000000", + "cut_a": 0, + "cut_b": 162005376966166 + }, + { + "field": 9, + "cost": 8, + "temperature": 0.3, + "temperature_bits": "3fd3333333333333", + "cut_a": 0, + "cut_b": 310254740734104 + }, + { + "field": 9, + "cost": 8, + "temperature": 0.7, + "temperature_bits": "3fe6666666666666", + "cut_a": 49398, + "cut_b": 1741284098048711 + }, + { + "field": 9, + "cost": 8, + "temperature": 1.0, + "temperature_bits": "3ff0000000000000", + "cut_a": 100286221, + "cut_b": 2422409043447905 + }, + { + "field": 9, + "cost": 8, + "temperature": 1.3, + "temperature_bits": "3ff4cccccccccccd", + "cut_a": 5969353677, + "cut_b": 2852093459326187 + }, + { + "field": 9, + "cost": 8, + "temperature": 1.8708286933869707, + "temperature_bits": "3ffdeeea11683f49", + "cut_a": 376495179050, + "cut_b": 3328061636713105 + }, + { + "field": 9, + "cost": 8, + "temperature": 2.9, + "temperature_bits": "4007333333333333", + "cut_a": 10614067286400, + "cut_b": 3740933654940500 + }, + { + "field": 9, + "cost": 8, + "temperature": 7.1, + "temperature_bits": "401c666666666666", + "cut_a": 366439087664541, + "cut_b": 4383069019470077 + }, + { + "field": 9, + "cost": 8, + "temperature": 13.7, + "temperature_bits": "402b666666666666", + "cut_a": 1101234883002150, + "cut_b": 4910011666754602 + }, + { + "field": 9, + "cost": 8, + "temperature": 25.0, + "temperature_bits": "4039000000000000", + "cut_a": 1791297188473432, + "cut_b": 5327098820608148 + }, + { + "field": 17, + "cost": 0, + "temperature": 0.25, + "temperature_bits": "3fd0000000000000", + "cut_a": 0, + "cut_b": 0 + }, + { + "field": 17, + "cost": 0, + "temperature": 0.3, + "temperature_bits": "3fd3333333333333", + "cut_a": 0, + "cut_b": 0 + }, + { + "field": 17, + "cost": 0, + "temperature": 0.7, + "temperature_bits": "3fe6666666666666", + "cut_a": 0, + "cut_b": 255527 + }, + { + "field": 17, + "cost": 0, + "temperature": 1.0, + "temperature_bits": "3ff0000000000000", + "cut_a": 15, + "cut_b": 372892439 + }, + { + "field": 17, + "cost": 0, + "temperature": 1.3, + "temperature_bits": "3ff4cccccccccccd", + "cut_a": 39456, + "cut_b": 18851861376 + }, + { + "field": 17, + "cost": 0, + "temperature": 1.8708286933869707, + "temperature_bits": "3ffdeeea11683f49", + "cut_a": 115285586, + "cut_b": 1019076898359 + }, + { + "field": 17, + "cost": 0, + "temperature": 2.9, + "temperature_bits": "4007333333333333", + "cut_a": 72714980202, + "cut_b": 25628433409983 + }, + { + "field": 17, + "cost": 0, + "temperature": 7.1, + "temperature_bits": "401c666666666666", + "cut_a": 68179428597285, + "cut_b": 815511093957046 + }, + { + "field": 17, + "cost": 0, + "temperature": 13.7, + "temperature_bits": "402b666666666666", + "cut_a": 548522329635687, + "cut_b": 2445664480446158 + }, + { + "field": 17, + "cost": 0, + "temperature": 25.0, + "temperature_bits": "4039000000000000", + "cut_a": 1311078038368263, + "cut_b": 3898985783519755 + }, + { + "field": 17, + "cost": 1, + "temperature": 0.25, + "temperature_bits": "3fd0000000000000", + "cut_a": 0, + "cut_b": 0 + }, + { + "field": 17, + "cost": 1, + "temperature": 0.3, + "temperature_bits": "3fd3333333333333", + "cut_a": 0, + "cut_b": 0 + }, + { + "field": 17, + "cost": 1, + "temperature": 0.7, + "temperature_bits": "3fe6666666666666", + "cut_a": 0, + "cut_b": 1066249 + }, + { + "field": 17, + "cost": 1, + "temperature": 1.0, + "temperature_bits": "3ff0000000000000", + "cut_a": 15, + "cut_b": 1013626643 + }, + { + "field": 17, + "cost": 1, + "temperature": 1.3, + "temperature_bits": "3ff4cccccccccccd", + "cut_a": 39456, + "cut_b": 40684162054 + }, + { + "field": 17, + "cost": 1, + "temperature": 1.8708286933869707, + "temperature_bits": "3ffdeeea11683f49", + "cut_a": 115276371, + "cut_b": 1738969891786 + }, + { + "field": 17, + "cost": 1, + "temperature": 2.9, + "temperature_bits": "4007333333333333", + "cut_a": 72630131527, + "cut_b": 36108727590905 + }, + { + "field": 17, + "cost": 1, + "temperature": 7.1, + "temperature_bits": "401c666666666666", + "cut_a": 67334450189587, + "cut_b": 917034379805322 + }, + { + "field": 17, + "cost": 1, + "temperature": 13.7, + "temperature_bits": "402b666666666666", + "cut_a": 539911232851157, + "cut_b": 2548672157515646 + }, + { + "field": 17, + "cost": 1, + "temperature": 25.0, + "temperature_bits": "4039000000000000", + "cut_a": 1295883074514415, + "cut_b": 3958188299908138 + }, + { + "field": 17, + "cost": 2, + "temperature": 0.25, + "temperature_bits": "3fd0000000000000", + "cut_a": 0, + "cut_b": 0 + }, + { + "field": 17, + "cost": 2, + "temperature": 0.3, + "temperature_bits": "3fd3333333333333", + "cut_a": 0, + "cut_b": 0 + }, + { + "field": 17, + "cost": 2, + "temperature": 0.7, + "temperature_bits": "3fe6666666666666", + "cut_a": 0, + "cut_b": 4449174 + }, + { + "field": 17, + "cost": 2, + "temperature": 1.0, + "temperature_bits": "3ff0000000000000", + "cut_a": 15, + "cut_b": 2755322325 + }, + { + "field": 17, + "cost": 2, + "temperature": 1.3, + "temperature_bits": "3ff4cccccccccccd", + "cut_a": 39456, + "cut_b": 87800210297 + }, + { + "field": 17, + "cost": 2, + "temperature": 1.8708286933869707, + "temperature_bits": "3ffdeeea11683f49", + "cut_a": 115260648, + "cut_b": 2967297335029 + }, + { + "field": 17, + "cost": 2, + "temperature": 2.9, + "temperature_bits": "4007333333333333", + "cut_a": 72510683250, + "cut_b": 50862677291015 + }, + { + "field": 17, + "cost": 2, + "temperature": 7.1, + "temperature_bits": "401c666666666666", + "cut_a": 66387242702441, + "cut_b": 1030840384312850 + }, + { + "field": 17, + "cost": 2, + "temperature": 13.7, + "temperature_bits": "402b666666666666", + "cut_a": 530944918627720, + "cut_b": 2655929017256267 + }, + { + "field": 17, + "cost": 2, + "temperature": 25.0, + "temperature_bits": "4039000000000000", + "cut_a": 1280437597490107, + "cut_b": 4018366864033280 + }, + { + "field": 17, + "cost": 3, + "temperature": 0.25, + "temperature_bits": "3fd0000000000000", + "cut_a": 0, + "cut_b": 0 + }, + { + "field": 17, + "cost": 3, + "temperature": 0.3, + "temperature_bits": "3fd3333333333333", + "cut_a": 0, + "cut_b": 0 + }, + { + "field": 17, + "cost": 3, + "temperature": 0.7, + "temperature_bits": "3fe6666666666666", + "cut_a": 0, + "cut_b": 18565221 + }, + { + "field": 17, + "cost": 3, + "temperature": 1.0, + "temperature_bits": "3ff0000000000000", + "cut_a": 15, + "cut_b": 7489738646 + }, + { + "field": 17, + "cost": 3, + "temperature": 1.3, + "temperature_bits": "3ff4cccccccccccd", + "cut_a": 39455, + "cut_b": 189479935000 + }, + { + "field": 17, + "cost": 3, + "temperature": 1.8708286933869707, + "temperature_bits": "3ffdeeea11683f49", + "cut_a": 115233824, + "cut_b": 5062827878123 + }, + { + "field": 17, + "cost": 3, + "temperature": 2.9, + "temperature_bits": "4007333333333333", + "cut_a": 72342719868, + "cut_b": 71609090267002 + }, + { + "field": 17, + "cost": 3, + "temperature": 7.1, + "temperature_bits": "401c666666666666", + "cut_a": 65329247504222, + "cut_b": 1157957418258710 + }, + { + "field": 17, + "cost": 3, + "temperature": 13.7, + "temperature_bits": "402b666666666666", + "cut_a": 521626302940226, + "cut_b": 2767400178188574 + }, + { + "field": 17, + "cost": 3, + "temperature": 25.0, + "temperature_bits": "4039000000000000", + "cut_a": 1264748018317603, + "cut_b": 4079496497313172 + }, + { + "field": 17, + "cost": 8, + "temperature": 0.25, + "temperature_bits": "3fd0000000000000", + "cut_a": 0, + "cut_b": 2 + }, + { + "field": 17, + "cost": 8, + "temperature": 0.3, + "temperature_bits": "3fd3333333333333", + "cut_a": 0, + "cut_b": 842 + }, + { + "field": 17, + "cost": 8, + "temperature": 0.7, + "temperature_bits": "3fe6666666666666", + "cut_a": 0, + "cut_b": 23485642292 + }, + { + "field": 17, + "cost": 8, + "temperature": 1.0, + "temperature_bits": "3ff0000000000000", + "cut_a": 15, + "cut_b": 1111439532877 + }, + { + "field": 17, + "cost": 8, + "temperature": 1.3, + "temperature_bits": "3ff4cccccccccccd", + "cut_a": 39417, + "cut_b": 8861519265452 + }, + { + "field": 17, + "cost": 8, + "temperature": 1.8708286933869707, + "temperature_bits": "3ffdeeea11683f49", + "cut_a": 114367434, + "cut_b": 72745677475423 + }, + { + "field": 17, + "cost": 8, + "temperature": 2.9, + "temperature_bits": "4007333333333333", + "cut_a": 69788796364, + "cut_b": 387063275448223 + }, + { + "field": 17, + "cost": 8, + "temperature": 7.1, + "temperature_bits": "401c666666666666", + "cut_a": 58121654520524, + "cut_b": 2023942295618911 + }, + { + "field": 17, + "cost": 8, + "temperature": 13.7, + "temperature_bits": "402b666666666666", + "cut_a": 470009534290655, + "cut_b": 3384850378722494 + }, + { + "field": 17, + "cost": 8, + "temperature": 25.0, + "temperature_bits": "4039000000000000", + "cut_a": 1182904809676649, + "cut_b": 4398373458617489 + }, + { + "field": 40, + "cost": 0, + "temperature": 0.25, + "temperature_bits": "3fd0000000000000", + "cut_a": 0, + "cut_b": 0 + }, + { + "field": 40, + "cost": 0, + "temperature": 0.3, + "temperature_bits": "3fd3333333333333", + "cut_a": 0, + "cut_b": 0 + }, + { + "field": 40, + "cost": 0, + "temperature": 0.7, + "temperature_bits": "3fe6666666666666", + "cut_a": 0, + "cut_b": 0 + }, + { + "field": 40, + "cost": 0, + "temperature": 1.0, + "temperature_bits": "3ff0000000000000", + "cut_a": 0, + "cut_b": 0 + }, + { + "field": 40, + "cost": 0, + "temperature": 1.3, + "temperature_bits": "3ff4cccccccccccd", + "cut_a": 0, + "cut_b": 390 + }, + { + "field": 40, + "cost": 0, + "temperature": 1.8708286933869707, + "temperature_bits": "3ffdeeea11683f49", + "cut_a": 0, + "cut_b": 4666412 + }, + { + "field": 40, + "cost": 0, + "temperature": 2.9, + "temperature_bits": "4007333333333333", + "cut_a": 9420, + "cut_b": 9211302700 + }, + { + "field": 40, + "cost": 0, + "temperature": 7.1, + "temperature_bits": "401c666666666666", + "cut_a": 114703184175, + "cut_b": 32199917877356 + }, + { + "field": 40, + "cost": 0, + "temperature": 13.7, + "temperature_bits": "402b666666666666", + "cut_a": 24805394955198, + "cut_b": 484595088606319 + }, + { + "field": 40, + "cost": 0, + "temperature": 25.0, + "temperature_bits": "4039000000000000", + "cut_a": 295457865303995, + "cut_b": 1758870252197250 + }, + { + "field": 40, + "cost": 1, + "temperature": 0.25, + "temperature_bits": "3fd0000000000000", + "cut_a": 0, + "cut_b": 0 + }, + { + "field": 40, + "cost": 1, + "temperature": 0.3, + "temperature_bits": "3fd3333333333333", + "cut_a": 0, + "cut_b": 0 + }, + { + "field": 40, + "cost": 1, + "temperature": 0.7, + "temperature_bits": "3fe6666666666666", + "cut_a": 0, + "cut_b": 0 + }, + { + "field": 40, + "cost": 1, + "temperature": 1.0, + "temperature_bits": "3ff0000000000000", + "cut_a": 0, + "cut_b": 0 + }, + { + "field": 40, + "cost": 1, + "temperature": 1.3, + "temperature_bits": "3ff4cccccccccccd", + "cut_a": 0, + "cut_b": 842 + }, + { + "field": 40, + "cost": 1, + "temperature": 1.8708286933869707, + "temperature_bits": "3ffdeeea11683f49", + "cut_a": 0, + "cut_b": 7963854 + }, + { + "field": 40, + "cost": 1, + "temperature": 2.9, + "temperature_bits": "4007333333333333", + "cut_a": 9420, + "cut_b": 13004014940 + }, + { + "field": 40, + "cost": 1, + "temperature": 7.1, + "temperature_bits": "401c666666666666", + "cut_a": 114641419322, + "cut_b": 37032735024520 + }, + { + "field": 40, + "cost": 1, + "temperature": 13.7, + "temperature_bits": "402b666666666666", + "cut_a": 24709881155375, + "cut_b": 517411591216567 + }, + { + "field": 40, + "cost": 1, + "temperature": 25.0, + "temperature_bits": "4039000000000000", + "cut_a": 293511711208462, + "cut_b": 1806614336314156 + }, + { + "field": 40, + "cost": 2, + "temperature": 0.25, + "temperature_bits": "3fd0000000000000", + "cut_a": 0, + "cut_b": 0 + }, + { + "field": 40, + "cost": 2, + "temperature": 0.3, + "temperature_bits": "3fd3333333333333", + "cut_a": 0, + "cut_b": 0 + }, + { + "field": 40, + "cost": 2, + "temperature": 0.7, + "temperature_bits": "3fe6666666666666", + "cut_a": 0, + "cut_b": 0 + }, + { + "field": 40, + "cost": 2, + "temperature": 1.0, + "temperature_bits": "3ff0000000000000", + "cut_a": 0, + "cut_b": 0 + }, + { + "field": 40, + "cost": 2, + "temperature": 1.3, + "temperature_bits": "3ff4cccccccccccd", + "cut_a": 0, + "cut_b": 1818 + }, + { + "field": 40, + "cost": 2, + "temperature": 1.8708286933869707, + "temperature_bits": "3ffdeeea11683f49", + "cut_a": 0, + "cut_b": 13591378 + }, + { + "field": 40, + "cost": 2, + "temperature": 2.9, + "temperature_bits": "4007333333333333", + "cut_a": 9420, + "cut_b": 18358357709 + }, + { + "field": 40, + "cost": 2, + "temperature": 7.1, + "temperature_bits": "401c666666666666", + "cut_a": 114570395083, + "cut_b": 42590056715753 + }, + { + "field": 40, + "cost": 2, + "temperature": 13.7, + "temperature_bits": "402b666666666666", + "cut_a": 24607952619564, + "cut_b": 552432060171879 + }, + { + "field": 40, + "cost": 2, + "temperature": 25.0, + "temperature_bits": "4039000000000000", + "cut_a": 291513176761154, + "cut_b": 1855643442992701 + }, + { + "field": 40, + "cost": 3, + "temperature": 0.25, + "temperature_bits": "3fd0000000000000", + "cut_a": 0, + "cut_b": 0 + }, + { + "field": 40, + "cost": 3, + "temperature": 0.3, + "temperature_bits": "3fd3333333333333", + "cut_a": 0, + "cut_b": 0 + }, + { + "field": 40, + "cost": 3, + "temperature": 0.7, + "temperature_bits": "3fe6666666666666", + "cut_a": 0, + "cut_b": 0 + }, + { + "field": 40, + "cost": 3, + "temperature": 1.0, + "temperature_bits": "3ff0000000000000", + "cut_a": 0, + "cut_b": 0 + }, + { + "field": 40, + "cost": 3, + "temperature": 1.3, + "temperature_bits": "3ff4cccccccccccd", + "cut_a": 0, + "cut_b": 3925 + }, + { + "field": 40, + "cost": 3, + "temperature": 1.8708286933869707, + "temperature_bits": "3ffdeeea11683f49", + "cut_a": 0, + "cut_b": 23195495 + }, + { + "field": 40, + "cost": 3, + "temperature": 2.9, + "temperature_bits": "4007333333333333", + "cut_a": 9420, + "cut_b": 25917321504 + }, + { + "field": 40, + "cost": 3, + "temperature": 7.1, + "temperature_bits": "401c666666666666", + "cut_a": 114488737591, + "cut_b": 48979381816645 + }, + { + "field": 40, + "cost": 3, + "temperature": 13.7, + "temperature_bits": "402b666666666666", + "cut_a": 24499240471603, + "cut_b": 589783233446753 + }, + { + "field": 40, + "cost": 3, + "temperature": 25.0, + "temperature_bits": "4039000000000000", + "cut_a": 289461782129120, + "cut_b": 1905969343749265 + }, + { + "field": 40, + "cost": 8, + "temperature": 0.25, + "temperature_bits": "3fd0000000000000", + "cut_a": 0, + "cut_b": 0 + }, + { + "field": 40, + "cost": 8, + "temperature": 0.3, + "temperature_bits": "3fd3333333333333", + "cut_a": 0, + "cut_b": 0 + }, + { + "field": 40, + "cost": 8, + "temperature": 0.7, + "temperature_bits": "3fe6666666666666", + "cut_a": 0, + "cut_b": 0 + }, + { + "field": 40, + "cost": 8, + "temperature": 1.0, + "temperature_bits": "3ff0000000000000", + "cut_a": 0, + "cut_b": 114 + }, + { + "field": 40, + "cost": 8, + "temperature": 1.3, + "temperature_bits": "3ff4cccccccccccd", + "cut_a": 0, + "cut_b": 183765 + }, + { + "field": 40, + "cost": 8, + "temperature": 1.8708286933869707, + "temperature_bits": "3ffdeeea11683f49", + "cut_a": 0, + "cut_b": 335818384 + }, + { + "field": 40, + "cost": 8, + "temperature": 2.9, + "temperature_bits": "4007333333333333", + "cut_a": 9419, + "cut_b": 145334197891 + }, + { + "field": 40, + "cost": 8, + "temperature": 7.1, + "temperature_bits": "401c666666666666", + "cut_a": 113857289456, + "cut_b": 98387309204988 + }, + { + "field": 40, + "cost": 8, + "temperature": 13.7, + "temperature_bits": "402b666666666666", + "cut_a": 23840225669071, + "cut_b": 816206651084120 + }, + { + "field": 40, + "cost": 8, + "temperature": 25.0, + "temperature_bits": "4039000000000000", + "cut_a": 278399629406741, + "cut_b": 2177351939456059 + } + ], + "chains": [ + { + "name": "sparse-with-a-cost/t0=2/sweeps=30", + "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 + }, + "seed": "1", + "t0": 2.0, + "t0_bits": "4000000000000000", + "sweeps": 30, + "chains": [ + { + "chain": "10451216379200822465", + "start": "--+--00-", + "expect": { + "state": "000---+0", + "accumulates": 540, + "draws": 232, + "term": "fixed-point", + "state_hash": "72827d80810737a7" + } + } + ] + }, + { + "name": "sparse-with-a-cost/t0=0.5/sweeps=12", + "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 + }, + "seed": "2", + "t0": 0.5, + "t0_bits": "3fe0000000000000", + "sweeps": 12, + "chains": [ + { + "chain": "10905525725756348110", + "start": "-0+-+-++", + "expect": { + "state": "000+++-0", + "accumulates": 216, + "draws": 88, + "term": "fixed-point", + "state_hash": "87cde53c2909a4ff" + } + } + ] + }, + { + "name": "sparse-with-a-cost/t0=0/sweeps=7", + "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 + }, + "seed": "3", + "t0": 0.0, + "t0_bits": "0000000000000000", + "sweeps": 7, + "chains": [ + { + "chain": "2092789425003139053", + "start": "0--+-+++", + "expect": { + "state": "00000000", + "accumulates": 126, + "draws": 0, + "term": "fixed-point", + "state_hash": "e7e395a2ad0bc74d" + } + } + ] + }, + { + "name": "sparse-with-a-cost/t0=1.8708286933869707/sweeps=41", + "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 + }, + "seed": "4", + "t0": 1.8708286933869707, + "t0_bits": "3ffdeeea11683f49", + "sweeps": 41, + "chains": [ + { + "chain": "7958955049054603978", + "start": "+--00++0", + "expect": { + "state": "000+++-0", + "accumulates": 738, + "draws": 320, + "term": "fixed-point", + "state_hash": "87cde53c2909a4ff" + } + }, + { + "chain": "16462000697783136304", + "start": "+00+0-++", + "expect": { + "state": "000+++-0", + "accumulates": 738, + "draws": 320, + "term": "fixed-point", + "state_hash": "87cde53c2909a4ff" + } + }, + { + "chain": "15847914186252977247", + "start": "0--00-0-", + "expect": { + "state": "000---+0", + "accumulates": 738, + "draws": 320, + "term": "fixed-point", + "state_hash": "72827d80810737a7" + } + }, + { + "chain": "9071633986856679582", + "start": "---++0-0", + "expect": { + "state": "000---+0", + "accumulates": 738, + "draws": 320, + "term": "fixed-point", + "state_hash": "72827d80810737a7" + } + }, + { + "chain": "7278725300257082041", + "start": "-+-0+00-", + "expect": { + "state": "000---+0", + "accumulates": 738, + "draws": 320, + "term": "fixed-point", + "state_hash": "72827d80810737a7" + } + }, + { + "chain": "10823043122334066145", + "start": "----+++0", + "expect": { + "state": "00000000", + "accumulates": 738, + "draws": 320, + "term": "fixed-point", + "state_hash": "e7e395a2ad0bc74d" + } + }, + { + "chain": "16983547147878014814", + "start": "00--+000", + "expect": { + "state": "000+++-0", + "accumulates": 738, + "draws": 320, + "term": "fixed-point", + "state_hash": "87cde53c2909a4ff" + } + }, + { + "chain": "8277778672505814866", + "start": "-+-+0+0+", + "expect": { + "state": "000+++-0", + "accumulates": 738, + "draws": 320, + "term": "fixed-point", + "state_hash": "87cde53c2909a4ff" + } + } + ] + }, + { + "name": "sparse-with-a-cost/t0=13.7/sweeps=9", + "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 + }, + "seed": "5", + "t0": 13.7, + "t0_bits": "402b666666666666", + "sweeps": 9, + "chains": [ + { + "chain": "7134611160154358618", + "start": "+---0+--", + "expect": { + "state": "0000-000", + "accumulates": 162, + "draws": 64, + "term": "bound", + "state_hash": "769215aaf9774974" + } + }, + { + "chain": "13877614986023876344", + "start": "--0--+0-", + "expect": { + "state": "0+000000", + "accumulates": 162, + "draws": 64, + "term": "bound", + "state_hash": "7bae3f4e82c5908c" + } + }, + { + "chain": "4292726422858613063", + "start": "0+--+++0", + "expect": { + "state": "000-0-+0", + "accumulates": 162, + "draws": 64, + "term": "bound", + "state_hash": "127d2788d73cf0d2" + } + } + ] + }, + { + "name": "dense/t0=2/sweeps=30", + "landscape": { + "n": 16, + "couplings": [ + [ + 0, + 1, + -1 + ], + [ + 0, + 2, + 1 + ], + [ + 0, + 3, + -1 + ], + [ + 0, + 4, + 1 + ], + [ + 0, + 5, + -1 + ], + [ + 0, + 6, + 1 + ], + [ + 0, + 7, + -1 + ], + [ + 0, + 8, + 1 + ], + [ + 0, + 9, + -1 + ], + [ + 0, + 10, + 1 + ], + [ + 0, + 11, + -1 + ], + [ + 0, + 12, + 1 + ], + [ + 0, + 13, + -1 + ], + [ + 0, + 14, + 1 + ], + [ + 0, + 15, + -1 + ], + [ + 1, + 2, + -1 + ], + [ + 1, + 3, + 1 + ], + [ + 1, + 4, + -1 + ], + [ + 1, + 5, + 1 + ], + [ + 1, + 6, + -1 + ], + [ + 1, + 7, + 1 + ], + [ + 1, + 8, + -1 + ], + [ + 1, + 9, + 1 + ], + [ + 1, + 10, + -1 + ], + [ + 1, + 11, + 1 + ], + [ + 1, + 12, + -1 + ], + [ + 1, + 13, + 1 + ], + [ + 1, + 14, + -1 + ], + [ + 1, + 15, + 1 + ], + [ + 2, + 3, + -1 + ], + [ + 2, + 4, + 1 + ], + [ + 2, + 5, + -1 + ], + [ + 2, + 6, + 1 + ], + [ + 2, + 7, + -1 + ], + [ + 2, + 8, + 1 + ], + [ + 2, + 9, + -1 + ], + [ + 2, + 10, + 1 + ], + [ + 2, + 11, + -1 + ], + [ + 2, + 12, + 1 + ], + [ + 2, + 13, + -1 + ], + [ + 2, + 14, + 1 + ], + [ + 2, + 15, + -1 + ], + [ + 3, + 4, + -1 + ], + [ + 3, + 5, + 1 + ], + [ + 3, + 6, + -1 + ], + [ + 3, + 7, + 1 + ], + [ + 3, + 8, + -1 + ], + [ + 3, + 9, + 1 + ], + [ + 3, + 10, + -1 + ], + [ + 3, + 11, + 1 + ], + [ + 3, + 12, + -1 + ], + [ + 3, + 13, + 1 + ], + [ + 3, + 14, + -1 + ], + [ + 3, + 15, + 1 + ], + [ + 4, + 5, + -1 + ], + [ + 4, + 6, + 1 + ], + [ + 4, + 7, + -1 + ], + [ + 4, + 8, + 1 + ], + [ + 4, + 9, + -1 + ], + [ + 4, + 10, + 1 + ], + [ + 4, + 11, + -1 + ], + [ + 4, + 12, + 1 + ], + [ + 4, + 13, + -1 + ], + [ + 4, + 14, + 1 + ], + [ + 4, + 15, + -1 + ], + [ + 5, + 6, + -1 + ], + [ + 5, + 7, + 1 + ], + [ + 5, + 8, + -1 + ], + [ + 5, + 9, + 1 + ], + [ + 5, + 10, + -1 + ], + [ + 5, + 11, + 1 + ], + [ + 5, + 12, + -1 + ], + [ + 5, + 13, + 1 + ], + [ + 5, + 14, + -1 + ], + [ + 5, + 15, + 1 + ], + [ + 6, + 7, + -1 + ], + [ + 6, + 8, + 1 + ], + [ + 6, + 9, + -1 + ], + [ + 6, + 10, + 1 + ], + [ + 6, + 11, + -1 + ], + [ + 6, + 12, + 1 + ], + [ + 6, + 13, + -1 + ], + [ + 6, + 14, + 1 + ], + [ + 6, + 15, + -1 + ], + [ + 7, + 8, + -1 + ], + [ + 7, + 9, + 1 + ], + [ + 7, + 10, + -1 + ], + [ + 7, + 11, + 1 + ], + [ + 7, + 12, + -1 + ], + [ + 7, + 13, + 1 + ], + [ + 7, + 14, + -1 + ], + [ + 7, + 15, + 1 + ], + [ + 8, + 9, + -1 + ], + [ + 8, + 10, + 1 + ], + [ + 8, + 11, + -1 + ], + [ + 8, + 12, + 1 + ], + [ + 8, + 13, + -1 + ], + [ + 8, + 14, + 1 + ], + [ + 8, + 15, + -1 + ], + [ + 9, + 10, + -1 + ], + [ + 9, + 11, + 1 + ], + [ + 9, + 12, + -1 + ], + [ + 9, + 13, + 1 + ], + [ + 9, + 14, + -1 + ], + [ + 9, + 15, + 1 + ], + [ + 10, + 11, + -1 + ], + [ + 10, + 12, + 1 + ], + [ + 10, + 13, + -1 + ], + [ + 10, + 14, + 1 + ], + [ + 10, + 15, + -1 + ], + [ + 11, + 12, + -1 + ], + [ + 11, + 13, + 1 + ], + [ + 11, + 14, + -1 + ], + [ + 11, + 15, + 1 + ], + [ + 12, + 13, + -1 + ], + [ + 12, + 14, + 1 + ], + [ + 12, + 15, + -1 + ], + [ + 13, + 14, + -1 + ], + [ + 13, + 15, + 1 + ], + [ + 14, + 15, + -1 + ] + ], + "field": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "cost": 0 + }, + "seed": "1", + "t0": 2.0, + "t0_bits": "4000000000000000", + "sweeps": 30, + "chains": [ + { + "chain": "10451216379200822465", + "start": "--+--00-0++-+0--", + "expect": { + "state": "+-+-+-+-+-+-+-+-", + "accumulates": 7200, + "draws": 464, + "term": "fixed-point", + "state_hash": "a12d60a06b2a6da5" + } + } + ] + }, + { + "name": "dense/t0=0.5/sweeps=12", + "landscape": { + "n": 16, + "couplings": [ + [ + 0, + 1, + -1 + ], + [ + 0, + 2, + 1 + ], + [ + 0, + 3, + -1 + ], + [ + 0, + 4, + 1 + ], + [ + 0, + 5, + -1 + ], + [ + 0, + 6, + 1 + ], + [ + 0, + 7, + -1 + ], + [ + 0, + 8, + 1 + ], + [ + 0, + 9, + -1 + ], + [ + 0, + 10, + 1 + ], + [ + 0, + 11, + -1 + ], + [ + 0, + 12, + 1 + ], + [ + 0, + 13, + -1 + ], + [ + 0, + 14, + 1 + ], + [ + 0, + 15, + -1 + ], + [ + 1, + 2, + -1 + ], + [ + 1, + 3, + 1 + ], + [ + 1, + 4, + -1 + ], + [ + 1, + 5, + 1 + ], + [ + 1, + 6, + -1 + ], + [ + 1, + 7, + 1 + ], + [ + 1, + 8, + -1 + ], + [ + 1, + 9, + 1 + ], + [ + 1, + 10, + -1 + ], + [ + 1, + 11, + 1 + ], + [ + 1, + 12, + -1 + ], + [ + 1, + 13, + 1 + ], + [ + 1, + 14, + -1 + ], + [ + 1, + 15, + 1 + ], + [ + 2, + 3, + -1 + ], + [ + 2, + 4, + 1 + ], + [ + 2, + 5, + -1 + ], + [ + 2, + 6, + 1 + ], + [ + 2, + 7, + -1 + ], + [ + 2, + 8, + 1 + ], + [ + 2, + 9, + -1 + ], + [ + 2, + 10, + 1 + ], + [ + 2, + 11, + -1 + ], + [ + 2, + 12, + 1 + ], + [ + 2, + 13, + -1 + ], + [ + 2, + 14, + 1 + ], + [ + 2, + 15, + -1 + ], + [ + 3, + 4, + -1 + ], + [ + 3, + 5, + 1 + ], + [ + 3, + 6, + -1 + ], + [ + 3, + 7, + 1 + ], + [ + 3, + 8, + -1 + ], + [ + 3, + 9, + 1 + ], + [ + 3, + 10, + -1 + ], + [ + 3, + 11, + 1 + ], + [ + 3, + 12, + -1 + ], + [ + 3, + 13, + 1 + ], + [ + 3, + 14, + -1 + ], + [ + 3, + 15, + 1 + ], + [ + 4, + 5, + -1 + ], + [ + 4, + 6, + 1 + ], + [ + 4, + 7, + -1 + ], + [ + 4, + 8, + 1 + ], + [ + 4, + 9, + -1 + ], + [ + 4, + 10, + 1 + ], + [ + 4, + 11, + -1 + ], + [ + 4, + 12, + 1 + ], + [ + 4, + 13, + -1 + ], + [ + 4, + 14, + 1 + ], + [ + 4, + 15, + -1 + ], + [ + 5, + 6, + -1 + ], + [ + 5, + 7, + 1 + ], + [ + 5, + 8, + -1 + ], + [ + 5, + 9, + 1 + ], + [ + 5, + 10, + -1 + ], + [ + 5, + 11, + 1 + ], + [ + 5, + 12, + -1 + ], + [ + 5, + 13, + 1 + ], + [ + 5, + 14, + -1 + ], + [ + 5, + 15, + 1 + ], + [ + 6, + 7, + -1 + ], + [ + 6, + 8, + 1 + ], + [ + 6, + 9, + -1 + ], + [ + 6, + 10, + 1 + ], + [ + 6, + 11, + -1 + ], + [ + 6, + 12, + 1 + ], + [ + 6, + 13, + -1 + ], + [ + 6, + 14, + 1 + ], + [ + 6, + 15, + -1 + ], + [ + 7, + 8, + -1 + ], + [ + 7, + 9, + 1 + ], + [ + 7, + 10, + -1 + ], + [ + 7, + 11, + 1 + ], + [ + 7, + 12, + -1 + ], + [ + 7, + 13, + 1 + ], + [ + 7, + 14, + -1 + ], + [ + 7, + 15, + 1 + ], + [ + 8, + 9, + -1 + ], + [ + 8, + 10, + 1 + ], + [ + 8, + 11, + -1 + ], + [ + 8, + 12, + 1 + ], + [ + 8, + 13, + -1 + ], + [ + 8, + 14, + 1 + ], + [ + 8, + 15, + -1 + ], + [ + 9, + 10, + -1 + ], + [ + 9, + 11, + 1 + ], + [ + 9, + 12, + -1 + ], + [ + 9, + 13, + 1 + ], + [ + 9, + 14, + -1 + ], + [ + 9, + 15, + 1 + ], + [ + 10, + 11, + -1 + ], + [ + 10, + 12, + 1 + ], + [ + 10, + 13, + -1 + ], + [ + 10, + 14, + 1 + ], + [ + 10, + 15, + -1 + ], + [ + 11, + 12, + -1 + ], + [ + 11, + 13, + 1 + ], + [ + 11, + 14, + -1 + ], + [ + 11, + 15, + 1 + ], + [ + 12, + 13, + -1 + ], + [ + 12, + 14, + 1 + ], + [ + 12, + 15, + -1 + ], + [ + 13, + 14, + -1 + ], + [ + 13, + 15, + 1 + ], + [ + 14, + 15, + -1 + ] + ], + "field": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "cost": 0 + }, + "seed": "2", + "t0": 0.5, + "t0_bits": "3fe0000000000000", + "sweeps": 12, + "chains": [ + { + "chain": "10905525725756348110", + "start": "-0+-+-++--0---0-", + "expect": { + "state": "+-+-+-+-+-+-+-+-", + "accumulates": 2880, + "draws": 176, + "term": "fixed-point", + "state_hash": "a12d60a06b2a6da5" + } + } + ] + }, + { + "name": "dense/t0=0/sweeps=7", + "landscape": { + "n": 16, + "couplings": [ + [ + 0, + 1, + -1 + ], + [ + 0, + 2, + 1 + ], + [ + 0, + 3, + -1 + ], + [ + 0, + 4, + 1 + ], + [ + 0, + 5, + -1 + ], + [ + 0, + 6, + 1 + ], + [ + 0, + 7, + -1 + ], + [ + 0, + 8, + 1 + ], + [ + 0, + 9, + -1 + ], + [ + 0, + 10, + 1 + ], + [ + 0, + 11, + -1 + ], + [ + 0, + 12, + 1 + ], + [ + 0, + 13, + -1 + ], + [ + 0, + 14, + 1 + ], + [ + 0, + 15, + -1 + ], + [ + 1, + 2, + -1 + ], + [ + 1, + 3, + 1 + ], + [ + 1, + 4, + -1 + ], + [ + 1, + 5, + 1 + ], + [ + 1, + 6, + -1 + ], + [ + 1, + 7, + 1 + ], + [ + 1, + 8, + -1 + ], + [ + 1, + 9, + 1 + ], + [ + 1, + 10, + -1 + ], + [ + 1, + 11, + 1 + ], + [ + 1, + 12, + -1 + ], + [ + 1, + 13, + 1 + ], + [ + 1, + 14, + -1 + ], + [ + 1, + 15, + 1 + ], + [ + 2, + 3, + -1 + ], + [ + 2, + 4, + 1 + ], + [ + 2, + 5, + -1 + ], + [ + 2, + 6, + 1 + ], + [ + 2, + 7, + -1 + ], + [ + 2, + 8, + 1 + ], + [ + 2, + 9, + -1 + ], + [ + 2, + 10, + 1 + ], + [ + 2, + 11, + -1 + ], + [ + 2, + 12, + 1 + ], + [ + 2, + 13, + -1 + ], + [ + 2, + 14, + 1 + ], + [ + 2, + 15, + -1 + ], + [ + 3, + 4, + -1 + ], + [ + 3, + 5, + 1 + ], + [ + 3, + 6, + -1 + ], + [ + 3, + 7, + 1 + ], + [ + 3, + 8, + -1 + ], + [ + 3, + 9, + 1 + ], + [ + 3, + 10, + -1 + ], + [ + 3, + 11, + 1 + ], + [ + 3, + 12, + -1 + ], + [ + 3, + 13, + 1 + ], + [ + 3, + 14, + -1 + ], + [ + 3, + 15, + 1 + ], + [ + 4, + 5, + -1 + ], + [ + 4, + 6, + 1 + ], + [ + 4, + 7, + -1 + ], + [ + 4, + 8, + 1 + ], + [ + 4, + 9, + -1 + ], + [ + 4, + 10, + 1 + ], + [ + 4, + 11, + -1 + ], + [ + 4, + 12, + 1 + ], + [ + 4, + 13, + -1 + ], + [ + 4, + 14, + 1 + ], + [ + 4, + 15, + -1 + ], + [ + 5, + 6, + -1 + ], + [ + 5, + 7, + 1 + ], + [ + 5, + 8, + -1 + ], + [ + 5, + 9, + 1 + ], + [ + 5, + 10, + -1 + ], + [ + 5, + 11, + 1 + ], + [ + 5, + 12, + -1 + ], + [ + 5, + 13, + 1 + ], + [ + 5, + 14, + -1 + ], + [ + 5, + 15, + 1 + ], + [ + 6, + 7, + -1 + ], + [ + 6, + 8, + 1 + ], + [ + 6, + 9, + -1 + ], + [ + 6, + 10, + 1 + ], + [ + 6, + 11, + -1 + ], + [ + 6, + 12, + 1 + ], + [ + 6, + 13, + -1 + ], + [ + 6, + 14, + 1 + ], + [ + 6, + 15, + -1 + ], + [ + 7, + 8, + -1 + ], + [ + 7, + 9, + 1 + ], + [ + 7, + 10, + -1 + ], + [ + 7, + 11, + 1 + ], + [ + 7, + 12, + -1 + ], + [ + 7, + 13, + 1 + ], + [ + 7, + 14, + -1 + ], + [ + 7, + 15, + 1 + ], + [ + 8, + 9, + -1 + ], + [ + 8, + 10, + 1 + ], + [ + 8, + 11, + -1 + ], + [ + 8, + 12, + 1 + ], + [ + 8, + 13, + -1 + ], + [ + 8, + 14, + 1 + ], + [ + 8, + 15, + -1 + ], + [ + 9, + 10, + -1 + ], + [ + 9, + 11, + 1 + ], + [ + 9, + 12, + -1 + ], + [ + 9, + 13, + 1 + ], + [ + 9, + 14, + -1 + ], + [ + 9, + 15, + 1 + ], + [ + 10, + 11, + -1 + ], + [ + 10, + 12, + 1 + ], + [ + 10, + 13, + -1 + ], + [ + 10, + 14, + 1 + ], + [ + 10, + 15, + -1 + ], + [ + 11, + 12, + -1 + ], + [ + 11, + 13, + 1 + ], + [ + 11, + 14, + -1 + ], + [ + 11, + 15, + 1 + ], + [ + 12, + 13, + -1 + ], + [ + 12, + 14, + 1 + ], + [ + 12, + 15, + -1 + ], + [ + 13, + 14, + -1 + ], + [ + 13, + 15, + 1 + ], + [ + 14, + 15, + -1 + ] + ], + "field": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "cost": 0 + }, + "seed": "3", + "t0": 0.0, + "t0_bits": "0000000000000000", + "sweeps": 7, + "chains": [ + { + "chain": "2092789425003139053", + "start": "0--+-+++++--++00", + "expect": { + "state": "-+-+-+-+-+-+-+-+", + "accumulates": 1680, + "draws": 0, + "term": "fixed-point", + "state_hash": "a700225ece6fb2a5" + } + } + ] + }, + { + "name": "dense/t0=1.8708286933869707/sweeps=41", + "landscape": { + "n": 16, + "couplings": [ + [ + 0, + 1, + -1 + ], + [ + 0, + 2, + 1 + ], + [ + 0, + 3, + -1 + ], + [ + 0, + 4, + 1 + ], + [ + 0, + 5, + -1 + ], + [ + 0, + 6, + 1 + ], + [ + 0, + 7, + -1 + ], + [ + 0, + 8, + 1 + ], + [ + 0, + 9, + -1 + ], + [ + 0, + 10, + 1 + ], + [ + 0, + 11, + -1 + ], + [ + 0, + 12, + 1 + ], + [ + 0, + 13, + -1 + ], + [ + 0, + 14, + 1 + ], + [ + 0, + 15, + -1 + ], + [ + 1, + 2, + -1 + ], + [ + 1, + 3, + 1 + ], + [ + 1, + 4, + -1 + ], + [ + 1, + 5, + 1 + ], + [ + 1, + 6, + -1 + ], + [ + 1, + 7, + 1 + ], + [ + 1, + 8, + -1 + ], + [ + 1, + 9, + 1 + ], + [ + 1, + 10, + -1 + ], + [ + 1, + 11, + 1 + ], + [ + 1, + 12, + -1 + ], + [ + 1, + 13, + 1 + ], + [ + 1, + 14, + -1 + ], + [ + 1, + 15, + 1 + ], + [ + 2, + 3, + -1 + ], + [ + 2, + 4, + 1 + ], + [ + 2, + 5, + -1 + ], + [ + 2, + 6, + 1 + ], + [ + 2, + 7, + -1 + ], + [ + 2, + 8, + 1 + ], + [ + 2, + 9, + -1 + ], + [ + 2, + 10, + 1 + ], + [ + 2, + 11, + -1 + ], + [ + 2, + 12, + 1 + ], + [ + 2, + 13, + -1 + ], + [ + 2, + 14, + 1 + ], + [ + 2, + 15, + -1 + ], + [ + 3, + 4, + -1 + ], + [ + 3, + 5, + 1 + ], + [ + 3, + 6, + -1 + ], + [ + 3, + 7, + 1 + ], + [ + 3, + 8, + -1 + ], + [ + 3, + 9, + 1 + ], + [ + 3, + 10, + -1 + ], + [ + 3, + 11, + 1 + ], + [ + 3, + 12, + -1 + ], + [ + 3, + 13, + 1 + ], + [ + 3, + 14, + -1 + ], + [ + 3, + 15, + 1 + ], + [ + 4, + 5, + -1 + ], + [ + 4, + 6, + 1 + ], + [ + 4, + 7, + -1 + ], + [ + 4, + 8, + 1 + ], + [ + 4, + 9, + -1 + ], + [ + 4, + 10, + 1 + ], + [ + 4, + 11, + -1 + ], + [ + 4, + 12, + 1 + ], + [ + 4, + 13, + -1 + ], + [ + 4, + 14, + 1 + ], + [ + 4, + 15, + -1 + ], + [ + 5, + 6, + -1 + ], + [ + 5, + 7, + 1 + ], + [ + 5, + 8, + -1 + ], + [ + 5, + 9, + 1 + ], + [ + 5, + 10, + -1 + ], + [ + 5, + 11, + 1 + ], + [ + 5, + 12, + -1 + ], + [ + 5, + 13, + 1 + ], + [ + 5, + 14, + -1 + ], + [ + 5, + 15, + 1 + ], + [ + 6, + 7, + -1 + ], + [ + 6, + 8, + 1 + ], + [ + 6, + 9, + -1 + ], + [ + 6, + 10, + 1 + ], + [ + 6, + 11, + -1 + ], + [ + 6, + 12, + 1 + ], + [ + 6, + 13, + -1 + ], + [ + 6, + 14, + 1 + ], + [ + 6, + 15, + -1 + ], + [ + 7, + 8, + -1 + ], + [ + 7, + 9, + 1 + ], + [ + 7, + 10, + -1 + ], + [ + 7, + 11, + 1 + ], + [ + 7, + 12, + -1 + ], + [ + 7, + 13, + 1 + ], + [ + 7, + 14, + -1 + ], + [ + 7, + 15, + 1 + ], + [ + 8, + 9, + -1 + ], + [ + 8, + 10, + 1 + ], + [ + 8, + 11, + -1 + ], + [ + 8, + 12, + 1 + ], + [ + 8, + 13, + -1 + ], + [ + 8, + 14, + 1 + ], + [ + 8, + 15, + -1 + ], + [ + 9, + 10, + -1 + ], + [ + 9, + 11, + 1 + ], + [ + 9, + 12, + -1 + ], + [ + 9, + 13, + 1 + ], + [ + 9, + 14, + -1 + ], + [ + 9, + 15, + 1 + ], + [ + 10, + 11, + -1 + ], + [ + 10, + 12, + 1 + ], + [ + 10, + 13, + -1 + ], + [ + 10, + 14, + 1 + ], + [ + 10, + 15, + -1 + ], + [ + 11, + 12, + -1 + ], + [ + 11, + 13, + 1 + ], + [ + 11, + 14, + -1 + ], + [ + 11, + 15, + 1 + ], + [ + 12, + 13, + -1 + ], + [ + 12, + 14, + 1 + ], + [ + 12, + 15, + -1 + ], + [ + 13, + 14, + -1 + ], + [ + 13, + 15, + 1 + ], + [ + 14, + 15, + -1 + ] + ], + "field": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "cost": 0 + }, + "seed": "4", + "t0": 1.8708286933869707, + "t0_bits": "3ffdeeea11683f49", + "sweeps": 41, + "chains": [ + { + "chain": "7958955049054603978", + "start": "+--00++0++0++0++", + "expect": { + "state": "-+-+-+-+-+-+-+-+", + "accumulates": 9840, + "draws": 640, + "term": "fixed-point", + "state_hash": "a700225ece6fb2a5" + } + }, + { + "chain": "16462000697783136304", + "start": "+00+0-++--0+00++", + "expect": { + "state": "-+-+-+-+-+-+-+-+", + "accumulates": 9840, + "draws": 640, + "term": "fixed-point", + "state_hash": "a700225ece6fb2a5" + } + }, + { + "chain": "15847914186252977247", + "start": "0--00-0--+0+-+0+", + "expect": { + "state": "-+-+-+-+-+-+-+-+", + "accumulates": 9840, + "draws": 640, + "term": "fixed-point", + "state_hash": "a700225ece6fb2a5" + } + }, + { + "chain": "9071633986856679582", + "start": "---++0-0++0-0--0", + "expect": { + "state": "-+-+-+-+-+-+-+-+", + "accumulates": 9840, + "draws": 640, + "term": "fixed-point", + "state_hash": "a700225ece6fb2a5" + } + }, + { + "chain": "7278725300257082041", + "start": "-+-0+00--+0++-+-", + "expect": { + "state": "+-+-+-+-+-+-+-+-", + "accumulates": 9840, + "draws": 640, + "term": "fixed-point", + "state_hash": "a12d60a06b2a6da5" + } + }, + { + "chain": "10823043122334066145", + "start": "----+++0-0-0++-0", + "expect": { + "state": "-+-+-+-+-+-+-+-+", + "accumulates": 9840, + "draws": 640, + "term": "fixed-point", + "state_hash": "a700225ece6fb2a5" + } + }, + { + "chain": "16983547147878014814", + "start": "00--+0000---+000", + "expect": { + "state": "+-+-+-+-+-+-+-+-", + "accumulates": 9840, + "draws": 640, + "term": "fixed-point", + "state_hash": "a12d60a06b2a6da5" + } + }, + { + "chain": "8277778672505814866", + "start": "-+-+0+0+-+0+-0+-", + "expect": { + "state": "-+-+-+-+-+-+-+-+", + "accumulates": 9840, + "draws": 640, + "term": "fixed-point", + "state_hash": "a700225ece6fb2a5" + } + } + ] + }, + { + "name": "dense/t0=13.7/sweeps=9", + "landscape": { + "n": 16, + "couplings": [ + [ + 0, + 1, + -1 + ], + [ + 0, + 2, + 1 + ], + [ + 0, + 3, + -1 + ], + [ + 0, + 4, + 1 + ], + [ + 0, + 5, + -1 + ], + [ + 0, + 6, + 1 + ], + [ + 0, + 7, + -1 + ], + [ + 0, + 8, + 1 + ], + [ + 0, + 9, + -1 + ], + [ + 0, + 10, + 1 + ], + [ + 0, + 11, + -1 + ], + [ + 0, + 12, + 1 + ], + [ + 0, + 13, + -1 + ], + [ + 0, + 14, + 1 + ], + [ + 0, + 15, + -1 + ], + [ + 1, + 2, + -1 + ], + [ + 1, + 3, + 1 + ], + [ + 1, + 4, + -1 + ], + [ + 1, + 5, + 1 + ], + [ + 1, + 6, + -1 + ], + [ + 1, + 7, + 1 + ], + [ + 1, + 8, + -1 + ], + [ + 1, + 9, + 1 + ], + [ + 1, + 10, + -1 + ], + [ + 1, + 11, + 1 + ], + [ + 1, + 12, + -1 + ], + [ + 1, + 13, + 1 + ], + [ + 1, + 14, + -1 + ], + [ + 1, + 15, + 1 + ], + [ + 2, + 3, + -1 + ], + [ + 2, + 4, + 1 + ], + [ + 2, + 5, + -1 + ], + [ + 2, + 6, + 1 + ], + [ + 2, + 7, + -1 + ], + [ + 2, + 8, + 1 + ], + [ + 2, + 9, + -1 + ], + [ + 2, + 10, + 1 + ], + [ + 2, + 11, + -1 + ], + [ + 2, + 12, + 1 + ], + [ + 2, + 13, + -1 + ], + [ + 2, + 14, + 1 + ], + [ + 2, + 15, + -1 + ], + [ + 3, + 4, + -1 + ], + [ + 3, + 5, + 1 + ], + [ + 3, + 6, + -1 + ], + [ + 3, + 7, + 1 + ], + [ + 3, + 8, + -1 + ], + [ + 3, + 9, + 1 + ], + [ + 3, + 10, + -1 + ], + [ + 3, + 11, + 1 + ], + [ + 3, + 12, + -1 + ], + [ + 3, + 13, + 1 + ], + [ + 3, + 14, + -1 + ], + [ + 3, + 15, + 1 + ], + [ + 4, + 5, + -1 + ], + [ + 4, + 6, + 1 + ], + [ + 4, + 7, + -1 + ], + [ + 4, + 8, + 1 + ], + [ + 4, + 9, + -1 + ], + [ + 4, + 10, + 1 + ], + [ + 4, + 11, + -1 + ], + [ + 4, + 12, + 1 + ], + [ + 4, + 13, + -1 + ], + [ + 4, + 14, + 1 + ], + [ + 4, + 15, + -1 + ], + [ + 5, + 6, + -1 + ], + [ + 5, + 7, + 1 + ], + [ + 5, + 8, + -1 + ], + [ + 5, + 9, + 1 + ], + [ + 5, + 10, + -1 + ], + [ + 5, + 11, + 1 + ], + [ + 5, + 12, + -1 + ], + [ + 5, + 13, + 1 + ], + [ + 5, + 14, + -1 + ], + [ + 5, + 15, + 1 + ], + [ + 6, + 7, + -1 + ], + [ + 6, + 8, + 1 + ], + [ + 6, + 9, + -1 + ], + [ + 6, + 10, + 1 + ], + [ + 6, + 11, + -1 + ], + [ + 6, + 12, + 1 + ], + [ + 6, + 13, + -1 + ], + [ + 6, + 14, + 1 + ], + [ + 6, + 15, + -1 + ], + [ + 7, + 8, + -1 + ], + [ + 7, + 9, + 1 + ], + [ + 7, + 10, + -1 + ], + [ + 7, + 11, + 1 + ], + [ + 7, + 12, + -1 + ], + [ + 7, + 13, + 1 + ], + [ + 7, + 14, + -1 + ], + [ + 7, + 15, + 1 + ], + [ + 8, + 9, + -1 + ], + [ + 8, + 10, + 1 + ], + [ + 8, + 11, + -1 + ], + [ + 8, + 12, + 1 + ], + [ + 8, + 13, + -1 + ], + [ + 8, + 14, + 1 + ], + [ + 8, + 15, + -1 + ], + [ + 9, + 10, + -1 + ], + [ + 9, + 11, + 1 + ], + [ + 9, + 12, + -1 + ], + [ + 9, + 13, + 1 + ], + [ + 9, + 14, + -1 + ], + [ + 9, + 15, + 1 + ], + [ + 10, + 11, + -1 + ], + [ + 10, + 12, + 1 + ], + [ + 10, + 13, + -1 + ], + [ + 10, + 14, + 1 + ], + [ + 10, + 15, + -1 + ], + [ + 11, + 12, + -1 + ], + [ + 11, + 13, + 1 + ], + [ + 11, + 14, + -1 + ], + [ + 11, + 15, + 1 + ], + [ + 12, + 13, + -1 + ], + [ + 12, + 14, + 1 + ], + [ + 12, + 15, + -1 + ], + [ + 13, + 14, + -1 + ], + [ + 13, + 15, + 1 + ], + [ + 14, + 15, + -1 + ] + ], + "field": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "cost": 0 + }, + "seed": "5", + "t0": 13.7, + "t0_bits": "402b666666666666", + "sweeps": 9, + "chains": [ + { + "chain": "7134611160154358618", + "start": "+---0+---0++--00", + "expect": { + "state": "-+-+-+-+-+-+-+-+", + "accumulates": 2160, + "draws": 128, + "term": "fixed-point", + "state_hash": "a700225ece6fb2a5" + } + }, + { + "chain": "13877614986023876344", + "start": "--0--+0-0+-++-0-", + "expect": { + "state": "-+-+-+-+-+-+-+-+", + "accumulates": 2160, + "draws": 128, + "term": "fixed-point", + "state_hash": "a700225ece6fb2a5" + } + }, + { + "chain": "4292726422858613063", + "start": "0+--+++0+-00+-0+", + "expect": { + "state": "+-+-+-+-+-+-+-+-", + "accumulates": 2160, + "draws": 128, + "term": "fixed-point", + "state_hash": "a12d60a06b2a6da5" + } + } + ] + }, + { + "name": "wide-fields/t0=2/sweeps=30", + "landscape": { + "n": 5, + "couplings": [ + [ + 0, + 1, + 1 + ], + [ + 1, + 2, + -1 + ], + [ + 2, + 3, + 1 + ], + [ + 3, + 4, + -1 + ] + ], + "field": [ + 40, + 0, + -17, + 0, + 9 + ], + "cost": 8 + }, + "seed": "1", + "t0": 2.0, + "t0_bits": "4000000000000000", + "sweeps": 30, + "chains": [ + { + "chain": "10451216379200822465", + "start": "--+--", + "expect": { + "state": "+0-0+", + "accumulates": 240, + "draws": 145, + "term": "fixed-point", + "state_hash": "5e22ce25c997e147" + } + } + ] + }, + { + "name": "wide-fields/t0=0.5/sweeps=12", + "landscape": { + "n": 5, + "couplings": [ + [ + 0, + 1, + 1 + ], + [ + 1, + 2, + -1 + ], + [ + 2, + 3, + 1 + ], + [ + 3, + 4, + -1 + ] + ], + "field": [ + 40, + 0, + -17, + 0, + 9 + ], + "cost": 8 + }, + "seed": "2", + "t0": 0.5, + "t0_bits": "3fe0000000000000", + "sweeps": 12, + "chains": [ + { + "chain": "10905525725756348110", + "start": "-0+-+", + "expect": { + "state": "+0-0+", + "accumulates": 96, + "draws": 55, + "term": "fixed-point", + "state_hash": "5e22ce25c997e147" + } + } + ] + }, + { + "name": "wide-fields/t0=0/sweeps=7", + "landscape": { + "n": 5, + "couplings": [ + [ + 0, + 1, + 1 + ], + [ + 1, + 2, + -1 + ], + [ + 2, + 3, + 1 + ], + [ + 3, + 4, + -1 + ] + ], + "field": [ + 40, + 0, + -17, + 0, + 9 + ], + "cost": 8 + }, + "seed": "3", + "t0": 0.0, + "t0_bits": "0000000000000000", + "sweeps": 7, + "chains": [ + { + "chain": "2092789425003139053", + "start": "0--+-", + "expect": { + "state": "+0-0+", + "accumulates": 56, + "draws": 0, + "term": "fixed-point", + "state_hash": "5e22ce25c997e147" + } + } + ] + }, + { + "name": "wide-fields/t0=1.8708286933869707/sweeps=41", + "landscape": { + "n": 5, + "couplings": [ + [ + 0, + 1, + 1 + ], + [ + 1, + 2, + -1 + ], + [ + 2, + 3, + 1 + ], + [ + 3, + 4, + -1 + ] + ], + "field": [ + 40, + 0, + -17, + 0, + 9 + ], + "cost": 8 + }, + "seed": "4", + "t0": 1.8708286933869707, + "t0_bits": "3ffdeeea11683f49", + "sweeps": 41, + "chains": [ + { + "chain": "7958955049054603978", + "start": "+--00", + "expect": { + "state": "+0-0+", + "accumulates": 328, + "draws": 200, + "term": "fixed-point", + "state_hash": "5e22ce25c997e147" + } + }, + { + "chain": "16462000697783136304", + "start": "+00+0", + "expect": { + "state": "+0-0+", + "accumulates": 328, + "draws": 200, + "term": "fixed-point", + "state_hash": "5e22ce25c997e147" + } + }, + { + "chain": "15847914186252977247", + "start": "0--00", + "expect": { + "state": "+0-0+", + "accumulates": 328, + "draws": 200, + "term": "fixed-point", + "state_hash": "5e22ce25c997e147" + } + }, + { + "chain": "9071633986856679582", + "start": "---++", + "expect": { + "state": "+0-0+", + "accumulates": 328, + "draws": 200, + "term": "fixed-point", + "state_hash": "5e22ce25c997e147" + } + }, + { + "chain": "7278725300257082041", + "start": "-+-0+", + "expect": { + "state": "+0-0+", + "accumulates": 328, + "draws": 200, + "term": "fixed-point", + "state_hash": "5e22ce25c997e147" + } + }, + { + "chain": "10823043122334066145", + "start": "----+", + "expect": { + "state": "+0-0+", + "accumulates": 328, + "draws": 200, + "term": "fixed-point", + "state_hash": "5e22ce25c997e147" + } + }, + { + "chain": "16983547147878014814", + "start": "00--+", + "expect": { + "state": "+0-0+", + "accumulates": 328, + "draws": 200, + "term": "fixed-point", + "state_hash": "5e22ce25c997e147" + } + }, + { + "chain": "8277778672505814866", + "start": "-+-+0", + "expect": { + "state": "+0-0+", + "accumulates": 328, + "draws": 200, + "term": "fixed-point", + "state_hash": "5e22ce25c997e147" + } + } + ] + }, + { + "name": "wide-fields/t0=13.7/sweeps=9", + "landscape": { + "n": 5, + "couplings": [ + [ + 0, + 1, + 1 + ], + [ + 1, + 2, + -1 + ], + [ + 2, + 3, + 1 + ], + [ + 3, + 4, + -1 + ] + ], + "field": [ + 40, + 0, + -17, + 0, + 9 + ], + "cost": 8 + }, + "seed": "5", + "t0": 13.7, + "t0_bits": "402b666666666666", + "sweeps": 9, + "chains": [ + { + "chain": "7134611160154358618", + "start": "+---0", + "expect": { + "state": "+0-0+", + "accumulates": 72, + "draws": 40, + "term": "fixed-point", + "state_hash": "5e22ce25c997e147" + } + }, + { + "chain": "13877614986023876344", + "start": "--0--", + "expect": { + "state": "+0-0+", + "accumulates": 72, + "draws": 40, + "term": "fixed-point", + "state_hash": "5e22ce25c997e147" + } + }, + { + "chain": "4292726422858613063", + "start": "0+--+", + "expect": { + "state": "+0-00", + "accumulates": 72, + "draws": 40, + "term": "bound", + "state_hash": "5e22cf25c997e2fa" + } + } + ] + } + ], + "statistical": { + "chains": 20000, + "seed": "3128418596", + "pool_below": 5.0, + "critical": "df + 5·sqrt(2·df), df the bins after pooling, less one", + "workloads": [ + { + "name": "one-pair", + "landscape": { + "n": 2, + "couplings": [ + [ + 0, + 1, + 1 + ] + ], + "field": [ + 0, + 0 + ], + "cost": 0 + }, + "temperatures": [ + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0 + ], + "state_order": "the sum of (s_i + 1) · 3^i", + "exact": [ + 0.24330499042671336, + 0.08950690391240243, + 0.032927749792280595, + 0.08950690391240243, + 0.08950690391240243, + 0.08950690391240243, + 0.032927749792280595, + 0.08950690391240243, + 0.24330499042671336 + ] + }, + { + "name": "a-pair-apart", + "landscape": { + "n": 3, + "couplings": [ + [ + 0, + 2, + 1 + ] + ], + "field": [ + 0, + 1, + 0 + ], + "cost": 0 + }, + "temperatures": [ + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0 + ], + "state_order": "the sum of (s_i + 1) · 3^i", + "exact": [ + 0.021904887743330933, + 0.00805835786193976, + 0.002964504187009897, + 0.05954365830713173, + 0.021904887743330933, + 0.00805835786193976, + 0.16185644437625066, + 0.05954365830713173, + 0.021904887743330933, + 0.00805835786193976, + 0.00805835786193976, + 0.00805835786193976, + 0.021904887743330933, + 0.021904887743330933, + 0.021904887743330933, + 0.05954365830713173, + 0.05954365830713173, + 0.05954365830713173, + 0.002964504187009897, + 0.00805835786193976, + 0.021904887743330933, + 0.00805835786193976, + 0.021904887743330933, + 0.05954365830713173, + 0.021904887743330933, + 0.05954365830713173, + 0.16185644437625066 + ] + }, + { + "name": "a-pair-far-apart", + "landscape": { + "n": 4, + "couplings": [ + [ + 0, + 3, + -1 + ], + [ + 1, + 2, + 1 + ] + ], + "field": [ + 0, + -1, + 0, + 0 + ], + "cost": 0 + }, + "temperatures": [ + 0.8, + 0.8, + 0.8, + 0.8, + 0.8, + 0.8, + 0.8, + 0.8, + 0.8, + 0.8, + 0.8, + 0.8, + 0.8, + 0.8, + 0.8, + 0.8, + 0.8, + 0.8, + 0.8, + 0.8, + 0.8, + 0.8, + 0.8, + 0.8, + 0.8, + 0.8, + 0.8, + 0.8, + 0.8, + 0.8, + 0.8, + 0.8 + ], + "state_order": "the sum of (s_i + 1) · 3^i", + "exact": [ + 0.013213605239596046, + 0.04612001399070494, + 0.1609746660304986, + 0.0010846387679089833, + 0.003785761284961209, + 0.013213605239596046, + 0.00008903257177123619, + 0.00031075420986645017, + 0.0010846387679089833, + 0.003785761284961209, + 0.013213605239596046, + 0.04612001399070494, + 0.0010846387679089833, + 0.003785761284961209, + 0.013213605239596046, + 0.00031075420986645017, + 0.0010846387679089833, + 0.003785761284961209, + 0.0010846387679089833, + 0.003785761284961209, + 0.013213605239596046, + 0.0010846387679089833, + 0.003785761284961209, + 0.013213605239596046, + 0.0010846387679089833, + 0.003785761284961209, + 0.013213605239596046, + 0.04612001399070494, + 0.04612001399070494, + 0.04612001399070494, + 0.003785761284961209, + 0.003785761284961209, + 0.003785761284961209, + 0.00031075420986645017, + 0.00031075420986645017, + 0.00031075420986645017, + 0.013213605239596046, + 0.013213605239596046, + 0.013213605239596046, + 0.003785761284961209, + 0.003785761284961209, + 0.003785761284961209, + 0.0010846387679089833, + 0.0010846387679089833, + 0.0010846387679089833, + 0.003785761284961209, + 0.003785761284961209, + 0.003785761284961209, + 0.003785761284961209, + 0.003785761284961209, + 0.003785761284961209, + 0.003785761284961209, + 0.003785761284961209, + 0.003785761284961209, + 0.1609746660304986, + 0.04612001399070494, + 0.013213605239596046, + 0.013213605239596046, + 0.003785761284961209, + 0.0010846387679089833, + 0.0010846387679089833, + 0.00031075420986645017, + 0.00008903257177123619, + 0.04612001399070494, + 0.013213605239596046, + 0.003785761284961209, + 0.013213605239596046, + 0.003785761284961209, + 0.0010846387679089833, + 0.003785761284961209, + 0.0010846387679089833, + 0.00031075420986645017, + 0.013213605239596046, + 0.003785761284961209, + 0.0010846387679089833, + 0.013213605239596046, + 0.003785761284961209, + 0.0010846387679089833, + 0.013213605239596046, + 0.003785761284961209, + 0.0010846387679089833 + ] + }, + { + "name": "a-triangle", + "landscape": { + "n": 3, + "couplings": [ + [ + 0, + 1, + 1 + ], + [ + 0, + 2, + 1 + ], + [ + 1, + 2, + 1 + ] + ], + "field": [ + 0, + 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 + ], + "state_order": "the sum of (s_i + 1) · 3^i", + "exact": [ + 0.11208585385919495, + 0.0487123112861923, + 0.0039985500048940244, + 0.0487123112861923, + 0.0487123112861923, + 0.009200567159790538, + 0.0039985500048940244, + 0.009200567159790538, + 0.0039985500048940244, + 0.0487123112861923, + 0.0487123112861923, + 0.009200567159790538, + 0.0487123112861923, + 0.11208585385919495, + 0.0487123112861923, + 0.009200567159790538, + 0.0487123112861923, + 0.0487123112861923, + 0.0039985500048940244, + 0.009200567159790538, + 0.0039985500048940244, + 0.009200567159790538, + 0.0487123112861923, + 0.0487123112861923, + 0.0039985500048940244, + 0.0487123112861923, + 0.11208585385919495 + ] + }, + { + "name": "wide-fields-and-cost", + "landscape": { + "n": 3, + "couplings": [ + [ + 0, + 1, + 1 + ], + [ + 1, + 2, + -1 + ] + ], + "field": [ + 17, + -9, + 40 + ], + "cost": 8 + }, + "temperatures": [ + 13.7, + 13.7, + 13.7, + 13.7, + 13.7, + 13.7, + 13.7, + 13.7, + 13.7, + 13.7, + 13.7, + 13.7, + 13.7, + 13.7, + 13.7, + 13.7, + 13.7, + 13.7, + 13.7, + 13.7, + 13.7, + 13.7, + 13.7, + 13.7, + 13.7, + 13.7, + 13.7, + 13.7, + 13.7, + 13.7, + 13.7, + 13.7 + ], + "state_order": "the sum of (s_i + 1) · 3^i", + "exact": [ + 0.0000622655414178924, + 0.00035896790487684193, + 0.0006436625440679104, + 0.000057882521482693005, + 0.00035896790487684193, + 0.0006924024000714943, + 0.000016735622850038298, + 0.00011164785556679995, + 0.00023166138976742073, + 0.0022261980547996405, + 0.012834284154199805, + 0.023013054587197598, + 0.001923814282544815, + 0.011930848288697043, + 0.023013054587197598, + 0.0005170794235948817, + 0.0034495763509604592, + 0.007157626517010874, + 0.02475566486819025, + 0.14271921433921572, + 0.25590870754996947, + 0.019887198717285202, + 0.12333370894265254, + 0.2378946834004851, + 0.004968981702732281, + 0.033149417648322334, + 0.06878269289996645 + ] + }, + { + "name": "a-small-field-cold", + "landscape": { + "n": 2, + "couplings": [ + [ + 0, + 1, + 1 + ] + ], + "field": [ + 5, + -3 + ], + "cost": 2 + }, + "temperatures": [ + 0.45, + 0.45, + 0.45, + 0.45, + 0.45, + 0.45, + 0.45, + 0.45, + 0.45, + 0.45, + 0.45, + 0.45, + 0.45, + 0.45, + 0.45, + 0.45, + 0.45, + 0.45, + 0.45, + 0.45, + 0.45, + 0.45, + 0.45, + 0.45, + 0.45, + 0.45, + 0.45, + 0.45, + 0.45, + 0.45, + 0.45, + 0.45 + ], + "state_order": "the sum of (s_i + 1) · 3^i", + "exact": [ + 9.447832090137123e-9, + 0.00583344702519856, + 0.4967329345055036, + 1.1095182979089412e-10, + 0.0006321591226904175, + 0.4967329345055036, + 1.7969722763054647e-16, + 9.447832090137123e-9, + 0.00006850583448764841 + ] + }, + { + "name": "ending-at-zero", + "landscape": { + "n": 4, + "couplings": [ + [ + 0, + 1, + 1 + ], + [ + 1, + 2, + 1 + ], + [ + 2, + 3, + -1 + ] + ], + "field": [ + 1, + 0, + 0, + 0 + ], + "cost": 1 + }, + "temperatures": [ + 1.3, + 1.3, + 1.3, + 1.3, + 1.3, + 1.3, + 1.3, + 1.3, + 1.3, + 1.3, + 1.3, + 1.3, + 1.3, + 1.3, + 1.3, + 1.3, + 1.3, + 1.3, + 1.3, + 1.3, + 1.3, + 1.3, + 1.3, + 1.3, + 1.3, + 1.3, + 1.3, + 1.3, + 1.3, + 1.3, + 1.3, + 0.0 + ], + "state_order": "the sum of (s_i + 1) · 3^i", + "exact": [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.010787363914222827, + 0.0, + 0.0, + 0.05642261376201909, + 0.0, + 0.0, + 0.0004973070505369252, + 0.0, + 0.0, + 0.024173840589758598, + 0.0009946141010738505, + 0.0, + 0.5017867665993923, + 0.17364266531470998, + 0.0, + 0.05619140863759928, + 0.06102864291509158, + 0.0, + 0.0, + 0.00010677737983457492, + 0.0, + 0.0, + 0.06412672073489217, + 0.0, + 0.0, + 0.05024127900086875, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + }, + { + "name": "cooling", + "landscape": { + "n": 3, + "couplings": [ + [ + 0, + 1, + -1 + ], + [ + 0, + 2, + -1 + ], + [ + 1, + 2, + -1 + ] + ], + "field": [ + 0, + 0, + -2 + ], + "cost": 0 + }, + "temperatures": [ + 6.0, + 6.0, + 6.0, + 6.0, + 6.0, + 6.0, + 6.0, + 6.0, + 0.7, + 0.7, + 0.7, + 0.7, + 0.7, + 0.7, + 0.7, + 0.7, + 0.7, + 0.7, + 0.7, + 0.7, + 0.7, + 0.7, + 0.7, + 0.7, + 0.7, + 0.7, + 0.7, + 0.7, + 0.7, + 0.7, + 0.7, + 0.7 + ], + "state_order": "the sum of (s_i + 1) · 3^i", + "exact": [ + 0.0005926415389637267, + 0.010318901462637631, + 0.17966969980169112, + 0.010318901462637631, + 0.043058029774657995, + 0.17966969980169112, + 0.17966969980169112, + 0.17966969980169112, + 0.17966969980169112, + 0.0005926415389637267, + 0.0024729354304616645, + 0.010318901462637631, + 0.0024729354304616645, + 0.0024729354304616645, + 0.0024729354304616645, + 0.010318901462637631, + 0.0024729354304616645, + 0.0005926415389637267, + 0.0005926415389637267, + 0.0005926415389637267, + 0.0005926415389637267, + 0.0005926415389637267, + 0.00014202715905110613, + 0.00003403695586947855, + 0.0005926415389637267, + 0.00003403695586947855, + 1.954831527480454e-6 + ] + } + ] + }, + "jobs": [ + { + "name": "talks", + "request": { + "program": "#! title Twelve talks, two rooms\n#! about Twelve talks go into room A or room B, or are left out. Talks that share an audience must not be in the same room. Talks in a series belong in the same room. Every talk that runs takes a slot, so a talk with little reason to run stays out.\n#! item talk\n#! plus room A\n#! minus room B\n#! zero left out\n#! expect works\n#\n# Three states per variable: + is room A, - is room B, 0 is left out.\n# `agree I J` talks I and J form a series: the same room.\n# `oppose I J` talks I and J share an audience: different rooms.\n# `cost 1` each talk that runs costs 1.\nvars 12\ncost 1\nagree 0 1\nagree 0 2\nagree 0 3\nagree 1 2\nagree 1 3\nagree 2 3\nagree 4 5\nagree 4 6\nagree 4 7\nagree 5 6\nagree 5 7\nagree 6 7\noppose 0 4\noppose 1 5\noppose 2 6\noppose 3 7\noppose 8 0\noppose 8 1\nagree 9 4\nagree 9 5\noppose 10 11\nsolve restarts=8 steps=40\n", + "seed": "1" + }, + "expect": { + "state": "----++++++00", + "energy": -10, + "verdict": "solved", + "mode": "sample", + "restarts": 8, + "sweeps": 40, + "accumulates": 13440, + "draws": 3744, + "replay": "6c40700b29e6193abe0a54d8424f2c65f6658a55849176b21f2e2cce4bca05fc" + } + }, + { + "name": "clusters", + "request": { + "program": "#! title Eight people, two teams, one quarrel\n#! about Eight people split into two teams, or sit out. Some pairs get on and want the same team; some do not. People 0, 1 and 2 all quarrel with each other, so no split can keep all three apart: the lowest energy says how few links have to break.\n#! item person\n#! plus team one\n#! minus team two\n#! zero sits out\n#! expect works\nvars 8\ncost 1\noppose 0 1\noppose 1 2\noppose 0 2\nagree 3 4\nagree 4 5\nagree 3 5\noppose 5 6\noppose 3 6\nagree 6 7\nsolve restarts=8 steps=40\n", + "seed": "1" + }, + "expect": { + "state": "000---+0", + "energy": -1, + "verdict": "solved", + "mode": "sample", + "restarts": 8, + "sweeps": 40, + "accumulates": 5760, + "draws": 2496, + "replay": "d9fba6c150822aeeaa220f3cc7e0c65050fdee3ec415db7fd9b66e1b0c0e5190" + } + }, + { + "name": "selection", + "request": { + "program": "#! title Six of fourteen that belong together\n#! about Fourteen items, six of which belong together, each on one of two sides. The links say which of the six agree and which oppose. Every item chosen costs 1, so the eight that do not belong should be left out. The check compares the answer with the hidden six.\n#! item item\n#! plus side one\n#! minus side two\n#! zero left out\n#! expect works\n#! check matches 0+00+0-00+0-0+\nvars 14\ncost 1\nagree 1 4\noppose 1 6\nagree 1 9\noppose 1 11\nagree 1 13\noppose 4 6\nagree 4 9\noppose 4 11\nagree 4 13\noppose 6 9\nagree 6 11\noppose 6 13\noppose 9 11\nagree 9 13\noppose 11 13\nsolve restarts=8 steps=40\n", + "seed": "1" + }, + "expect": { + "state": "0+00+0-00+0-0+", + "energy": -9, + "verdict": "solved", + "mode": "sample", + "restarts": 8, + "sweeps": 40, + "accumulates": 9600, + "draws": 4368, + "replay": "5c488cd7527064bcadafeca74e3dbaabdbb2e61e4b98d90c9317d9bbff7a583e" + } + }, + { + "name": "chain", + "request": { + "program": "#! title A chain with one end held\n#! about Variable 0 is held at +. Each link passes the sign along, or flips it. With one end held, the deterministic mode has one answer, and it gets there in a few sweeps.\n#! expect works\n#! check matches +++--++-\nvars 8\npin 0 +\nagree 0 1\nagree 1 2\noppose 2 3\nagree 3 4\noppose 4 5\nagree 5 6\noppose 6 7\nminimize\n", + "seed": "1" + }, + "expect": { + "state": "+++--++-", + "energy": -10, + "verdict": "solved", + "mode": "settle", + "restarts": 1, + "sweeps": 60, + "accumulates": 126, + "draws": 0, + "replay": "aa1a20cdf831dc2facfe3964bae8d25d29c16d338b9c0f79e4c5f7f152ef254d" + } + }, + { + "name": "assign-2x2", + "request": { + "program": "#! title Two workers, two jobs\n#! about A 2×2 grid: row r is a worker, column c a job, and + means worker r takes job c. Each row and each column must hold exactly one +. This is the exact construction: within a row or a column, cells oppose; each cell has field 2; the cost is 1. At 2×2 its lowest states are exactly the two assignments.\n#! expect works\n#! check permutation 2\nvars 4\ncost 1\noppose 0 1\noppose 2 3\noppose 0 2\noppose 1 3\nfield 0 2\nfield 1 2\nfield 2 2\nfield 3 2\nsolve restarts=8 steps=40\n", + "seed": "1" + }, + "expect": { + "state": "+00+", + "energy": -2, + "verdict": "solved", + "mode": "sample", + "restarts": 8, + "sweeps": 40, + "accumulates": 2560, + "draws": 1248, + "replay": "6215b47c9b0f73bcb1d19654924473cd6514536370cf662b0fe829bc15645f80" + } + }, + { + "name": "blocks", + "request": { + "program": "#! title Sixty-four variables in eight blocks\n#! about Eight blocks of eight. Inside a block, links agree; between neighbouring blocks, links oppose. There are too many arrangements to check them all, about 3 to the 64th power, so the answer is graded against the pattern the links were built from. This program asks for 200 sweeps a start: with the default 40, some runs stop short of the pattern.\n#! expect works\n#! check matches ++++++++--------++++++++--------++++++++--------++++++++--------\nvars 64\ncost 1\nagree 0 1\nagree 0 2\nagree 1 2\nagree 1 3\nagree 2 3\nagree 2 4\nagree 3 4\nagree 3 5\nagree 4 5\nagree 4 6\nagree 5 6\nagree 5 7\nagree 6 7\noppose 7 8\noppose 3 12\nagree 8 9\nagree 8 10\nagree 9 10\nagree 9 11\nagree 10 11\nagree 10 12\nagree 11 12\nagree 11 13\nagree 12 13\nagree 12 14\nagree 13 14\nagree 13 15\nagree 14 15\noppose 15 16\noppose 11 20\nagree 16 17\nagree 16 18\nagree 17 18\nagree 17 19\nagree 18 19\nagree 18 20\nagree 19 20\nagree 19 21\nagree 20 21\nagree 20 22\nagree 21 22\nagree 21 23\nagree 22 23\noppose 23 24\noppose 19 28\nagree 24 25\nagree 24 26\nagree 25 26\nagree 25 27\nagree 26 27\nagree 26 28\nagree 27 28\nagree 27 29\nagree 28 29\nagree 28 30\nagree 29 30\nagree 29 31\nagree 30 31\noppose 31 32\noppose 27 36\nagree 32 33\nagree 32 34\nagree 33 34\nagree 33 35\nagree 34 35\nagree 34 36\nagree 35 36\nagree 35 37\nagree 36 37\nagree 36 38\nagree 37 38\nagree 37 39\nagree 38 39\noppose 39 40\noppose 35 44\nagree 40 41\nagree 40 42\nagree 41 42\nagree 41 43\nagree 42 43\nagree 42 44\nagree 43 44\nagree 43 45\nagree 44 45\nagree 44 46\nagree 45 46\nagree 45 47\nagree 46 47\noppose 47 48\noppose 43 52\nagree 48 49\nagree 48 50\nagree 49 50\nagree 49 51\nagree 50 51\nagree 50 52\nagree 51 52\nagree 51 53\nagree 52 53\nagree 52 54\nagree 53 54\nagree 53 55\nagree 54 55\noppose 55 56\noppose 51 60\nagree 56 57\nagree 56 58\nagree 57 58\nagree 57 59\nagree 58 59\nagree 58 60\nagree 59 60\nagree 59 61\nagree 60 61\nagree 60 62\nagree 61 62\nagree 61 63\nagree 62 63\noppose 63 0\noppose 59 4\nsolve restarts=8 steps=200\n", + "seed": "1" + }, + "expect": { + "state": "++++++++--------++++++++--------++++++++--------++++++++--------", + "energy": -56, + "verdict": "solved", + "mode": "sample", + "restarts": 8, + "sweeps": 200, + "accumulates": 384000, + "draws": 101888, + "replay": "68bf7df08c806e2e9cab499b663322f02f9b7ded072b56305529dbc0488ccbcb" + } + }, + { + "name": "assign-3x3", + "request": { + "program": "#! title Three workers, three jobs: this fails\n#! about The same exact construction on a 3×3 grid. A row can now sum to 1 with three choices in it, + and + and -, so the lowest energy no longer means one choice per row and column. The check catches it. This is why assignment above 2×2 does not run on this model.\n#! expect fails\n#! check permutation 3\nvars 9\ncost 1\noppose 0 1\noppose 0 2\noppose 1 2\noppose 3 4\noppose 3 5\noppose 4 5\noppose 6 7\noppose 6 8\noppose 7 8\noppose 0 3\noppose 0 6\noppose 3 6\noppose 1 4\noppose 1 7\noppose 4 7\noppose 2 5\noppose 2 8\noppose 5 8\nfield 0 2\nfield 1 2\nfield 2 2\nfield 3 2\nfield 4 2\nfield 5 2\nfield 6 2\nfield 7 2\nfield 8 2\nsolve restarts=8 steps=60\n", + "seed": "1" + }, + "expect": { + "state": "++--+++-+", + "energy": -3, + "verdict": "failed", + "mode": "sample", + "restarts": 8, + "sweeps": 60, + "accumulates": 17280, + "draws": 4248, + "replay": "1f6807ec622e0647603186bf74e2af543a434f80f39e947403f220ff5f998b40" + } + }, + { + "name": "colour-six", + "request": { + "program": "#! title Three colours, six regions: this fails\n#! about Six regions, two rows of three, each touching the ones beside and across from it. Give neighbours different colours, with +, - and 0 as the three colours. There are twelve good ways to colour it. But the model's 0 is special: two neighbouring 0s cost nothing, so the lowest energy never colours it properly. Problems that need three interchangeable options do not run on this model.\n#! expect fails\n#! check different 0-1 1-2 0-2 3-4 4-5 3-5 0-3 1-4 2-5\nvars 6\noppose 0 1\noppose 1 2\noppose 0 2\noppose 3 4\noppose 4 5\noppose 3 5\noppose 0 3\noppose 1 4\noppose 2 5\nsolve restarts=8 steps=40\n", + "seed": "1" + }, + "expect": { + "state": "+-+-+-", + "energy": -5, + "verdict": "failed", + "mode": "sample", + "restarts": 8, + "sweeps": 40, + "accumulates": 5760, + "draws": 1872, + "replay": "548d29873c61711734c496ac5ce95fab34bca3a1bd69e48968a49b75d1aa0d0a" + } + }, + { + "name": "colour-four", + "request": { + "program": "#! title Three colours, four regions: this fails\n#! about Four regions, two triangles sharing a side. There are six good ways to colour it, and none of them has the lowest energy, for the same reason: 0 is not just a third colour.\n#! expect fails\n#! check different 0-1 0-2 1-2 1-3 2-3\nvars 4\noppose 0 1\noppose 0 2\noppose 1 2\noppose 1 3\noppose 2 3\nsolve restarts=8 steps=40\n", + "seed": "1" + }, + "expect": { + "state": "+--+", + "energy": -3, + "verdict": "failed", + "mode": "sample", + "restarts": 8, + "sweeps": 40, + "accumulates": 3200, + "draws": 1248, + "replay": "e9187c8742fde9836a0a4d97504ff0f02c52b86bccaaf6ca14f10068c1505fe5" + } + }, + { + "name": "piles", + "request": { + "program": "#! title Two equal piles: this fails\n#! about Split eight numbers, 2 7 3 9 4 1 6 8, into two piles of equal sum. A split exists: 9, 7 and 4 against the rest. The weights hold the sizes of the numbers, and the machine's couplings keep only signs, so the sizes are lost before anything runs. The compile warning says so first.\n#! expect fails\n#! check balance 2 7 3 9 4 1 6 8\n#\n# `weight I J W` is a real-valued coupling. It goes through quantisation to signs.\n# For equal piles, the weight between numbers a and b is -a×b.\nvars 8\nweight 0 1 -14\nweight 0 2 -6\nweight 0 3 -18\nweight 0 4 -8\nweight 0 5 -2\nweight 0 6 -12\nweight 0 7 -16\nweight 1 2 -21\nweight 1 3 -63\nweight 1 4 -28\nweight 1 5 -7\nweight 1 6 -42\nweight 1 7 -56\nweight 2 3 -27\nweight 2 4 -12\nweight 2 5 -3\nweight 2 6 -18\nweight 2 7 -24\nweight 3 4 -36\nweight 3 5 -9\nweight 3 6 -54\nweight 3 7 -72\nweight 4 5 -4\nweight 4 6 -24\nweight 4 7 -32\nweight 5 6 -6\nweight 5 7 -8\nweight 6 7 -48\nsolve restarts=8 steps=40\n", + "seed": "1" + }, + "expect": { + "state": "+0+-+0--", + "energy": -5, + "verdict": "failed", + "mode": "sample", + "restarts": 8, + "sweeps": 40, + "accumulates": 12160, + "draws": 2496, + "replay": "2c5cd343ab581a4f2ffe582b2e194f27ef7046196f5002b8ef873873bd3adb4e" + } + }, + { + "name": "cold-constraint", + "request": { + "program": "#! title Two workers, two jobs, deterministic: this fails\n#! about The 2×2 assignment again, at temperature 0. Every cell starts at 0, sees field 2, and turns on; then each sees its row and column full, and turns off; and so on. It never comes to rest. Constraint problems need the temperature.\n#! expect fails\n#! check permutation 2\nvars 4\ncost 1\noppose 0 1\noppose 2 3\noppose 0 2\noppose 1 3\nfield 0 2\nfield 1 2\nfield 2 2\nfield 3 2\nminimize\n", + "seed": "1" + }, + "expect": { + "state": "0000", + "energy": 0, + "verdict": "failed", + "mode": "settle", + "restarts": 1, + "sweeps": 60, + "accumulates": 16, + "draws": 0, + "replay": "1b4633b6e578f54cb838a68d2fb565b43a86a65e5676d7ea07906f820f40d6a9" + } + }, + { + "name": "cold-start", + "request": { + "program": "#! title Twelve talks, deterministic: this fails\n#! about The talks problem at temperature 0, from every talk left out. Nothing pushes any talk in, so nothing moves, and it stops at the first state it was given.\n#! item talk\n#! plus room A\n#! minus room B\n#! zero left out\n#! expect fails\nvars 12\ncost 1\nagree 0 1\nagree 0 2\nagree 0 3\nagree 1 2\nagree 1 3\nagree 2 3\nagree 4 5\nagree 4 6\nagree 4 7\nagree 5 6\nagree 5 7\nagree 6 7\noppose 0 4\noppose 1 5\noppose 2 6\noppose 3 7\noppose 8 0\noppose 8 1\nagree 9 4\nagree 9 5\noppose 10 11\nminimize\n", + "seed": "1" + }, + "expect": { + "state": "000000000000", + "energy": 0, + "verdict": "missed", + "mode": "settle", + "restarts": 1, + "sweeps": 60, + "accumulates": 42, + "draws": 0, + "replay": "6f25ae294b7662a19e036bbd83c7b4b906beee0e14e42090791a6a8ec5d61a30" + } + }, + { + "name": "too-costly", + "request": { + "program": "#! title The six of fourteen, at too high a cost: this fails\n#! about The same hidden six, with every chosen item costing 3. Each of the six has five links, so choosing them all is no longer cheaper than choosing nothing. The lowest energy is to leave everything out, and that does not find the six. The compile warning says the cost is high.\n#! expect fails\n#! check matches 0+00+0-00+0-0+\nvars 14\ncost 3\nagree 1 4\noppose 1 6\nagree 1 9\noppose 1 11\nagree 1 13\noppose 4 6\nagree 4 9\noppose 4 11\nagree 4 13\noppose 6 9\nagree 6 11\noppose 6 13\noppose 9 11\nagree 9 13\noppose 11 13\nsolve restarts=8 steps=40\n", + "seed": "1" + }, + "expect": { + "state": "00000000000000", + "energy": 0, + "verdict": "failed", + "mode": "sample", + "restarts": 8, + "sweeps": 40, + "accumulates": 9600, + "draws": 4368, + "replay": "2c7e893ef98c2605c132dac67b64b8cb8a13bcb236bcd9030077f52fce93bd7c" + } + }, + { + "name": "typo", + "request": { + "program": "#! title A typing mistake: this fails\n#! about A misspelt statement. The emulator refuses it, and says which line and what it expected.\n#! expect fails\nvars 3\nagree 0 1\noppos 1 2\nsolve\n", + "seed": "1" + }, + "expect": { + "refused": "Line 6: `oppos` is not a statement epuML knows. Did you mean `oppose`?", + "line": 6 + } + }, + { + "name": "minimize-above-zero", + "request": { + "program": "#! title A chain with one end held\n#! about Variable 0 is held at +. Each link passes the sign along, or flips it. With one end held, the deterministic mode has one answer, and it gets there in a few sweeps.\n#! expect works\n#! check matches +++--++-\nvars 8\npin 0 +\nagree 0 1\nagree 1 2\noppose 2 3\nagree 3 4\noppose 4 5\nagree 5 6\noppose 6 7\nminimize\n", + "seed": "1", + "temperature": "0.5" + }, + "expect": { + "state": "+++--++-", + "energy": -10, + "verdict": "solved", + "mode": "sample", + "restarts": 8, + "sweeps": 60, + "accumulates": 6720, + "draws": 3776, + "replay": "92d9d9e2f9422d3cf33518a03379632b902cb6a0dca3931b46579e0ae43c073f" + } + }, + { + "name": "a-temperature-in-text", + "request": { + "program": "#! title Twelve talks, two rooms\n#! about Twelve talks go into room A or room B, or are left out. Talks that share an audience must not be in the same room. Talks in a series belong in the same room. Every talk that runs takes a slot, so a talk with little reason to run stays out.\n#! item talk\n#! plus room A\n#! minus room B\n#! zero left out\n#! expect works\n#\n# Three states per variable: + is room A, - is room B, 0 is left out.\n# `agree I J` talks I and J form a series: the same room.\n# `oppose I J` talks I and J share an audience: different rooms.\n# `cost 1` each talk that runs costs 1.\nvars 12\ncost 1\nagree 0 1\nagree 0 2\nagree 0 3\nagree 1 2\nagree 1 3\nagree 2 3\nagree 4 5\nagree 4 6\nagree 4 7\nagree 5 6\nagree 5 7\nagree 6 7\noppose 0 4\noppose 1 5\noppose 2 6\noppose 3 7\noppose 8 0\noppose 8 1\nagree 9 4\nagree 9 5\noppose 10 11\nsolve restarts=8 steps=40\n", + "seed": "1", + "temperature": "1.8708286933869707" + }, + "expect": { + "state": "----++++++00", + "energy": -10, + "verdict": "solved", + "mode": "sample", + "restarts": 8, + "sweeps": 40, + "accumulates": 13440, + "draws": 3744, + "replay": "6c40700b29e6193abe0a54d8424f2c65f6658a55849176b21f2e2cce4bca05fc" + } + }, + { + "name": "pin-size", + "request": { + "program": "vars 3\nagree 0 1\nagree 1 2\nfield 1 2\ncost 1\npin 0 -\nminimize\n", + "seed": "1" + }, + "expect": { + "state": "-00", + "energy": -5, + "verdict": "solved", + "mode": "settle", + "restarts": 1, + "sweeps": 60, + "accumulates": 12, + "draws": 0, + "replay": "3bd29a7e0ed78774b1f96fe6517ff685be95d8403f4d41225974c7a63afe62a2" + } + }, + { + "name": "weights-rescaled", + "request": { + "program": "vars 3\nfield 0 3\ncost 1\nweight 0 1 2.5\nweight 1 2 -0.5\nweight 0 2 1.0\nsolve\n", + "seed": "1" + }, + "expect": { + "state": "+00", + "energy": -1, + "verdict": "failed", + "mode": "sample", + "restarts": 8, + "sweeps": 40, + "accumulates": 1280, + "draws": 936, + "replay": "8098576a231093e748533367ddde2ede8db8e3adc959da66ae36f7e36acbfa2f" + } + }, + { + "name": "two-cycle-state", + "request": { + "program": "vars 2\nagree 0 1\nfield 0 1\nfield 1 -1\nminimize\n", + "seed": "1" + }, + "expect": { + "state": "00", + "energy": 0, + "verdict": "failed", + "mode": "settle", + "restarts": 1, + "sweeps": 60, + "accumulates": 4, + "draws": 0, + "replay": "fb56e3535b84e868e489212a35f20b5c59f5a0c03b732f9c781e1d2c4f6a52c5" + } + }, + { + "name": "settle-digest", + "request": { + "program": "vars 3\nagree 0 1\noppose 1 2\nfield 0 1\nminimize steps=5\n", + "seed": "9" + }, + "expect": { + "state": "++-", + "energy": -3, + "verdict": "solved", + "mode": "settle", + "restarts": 1, + "sweeps": 5, + "accumulates": 16, + "draws": 0, + "replay": "0a3489b1677a38bba189d405a053c79f82ce6088c7eb10bf93632c349278f3d0" + } + }, + { + "name": "leading-zeros", + "request": { + "program": "vars 02\nfield 01 -007\nsolve t0=0.50 restarts=003\n", + "seed": "1" + }, + "expect": { + "state": "0-", + "energy": -7, + "verdict": "solved", + "mode": "sample", + "restarts": 3, + "sweeps": 40, + "accumulates": 0, + "draws": 234, + "replay": "72d9bc1aa0dbbf198700f5e32673796a21f581892e09396fe62312d9355751bb" + } + }, + { + "name": "a-plus-sign", + "request": { + "program": "vars +3\nsolve\n", + "seed": "1" + }, + "expect": { + "refused": "Line 1: `vars` takes one whole number.", + "line": 1 + } + }, + { + "name": "an-exponent-temperature", + "request": { + "program": "vars 2\nsolve t0=1e-3\n", + "seed": "1" + }, + "expect": { + "refused": "Line 2: `t0=` takes a number like 0.5, not `1e-3`.", + "line": 2 + } + }, + { + "name": "a-byte-order-mark", + "request": { + "program": "vars 2\nsolve\n", + "seed": "1" + }, + "expect": { + "refused": "Line 1: `vars` is not a statement epuML knows. Did you mean `vars`?", + "line": 1 + } + }, + { + "name": "a-no-break-space", + "request": { + "program": "vars 2\nsolve\n", + "seed": "1" + }, + "expect": { + "refused": "Line 1: `vars 2` is not a statement epuML knows. Did you mean `vars`?", + "line": 1 + } + }, + { + "name": "no-steps", + "request": { + "program": "vars 1\nsolve steps=0\n", + "seed": "1" + }, + "expect": { + "refused": "Line 2: `steps=` must be between 1 and 1,000,000.", + "line": 2 + } + }, + { + "name": "too-many-restarts", + "request": { + "program": "vars 1\nsolve restarts=100001\n", + "seed": "1" + }, + "expect": { + "refused": "Line 2: `restarts=` must be between 1 and 100,000.", + "line": 2 + } + }, + { + "name": "the-most-negative-field", + "request": { + "program": "vars 1\nfield 0 -9223372036854775808\nsolve\n", + "seed": "1" + }, + "expect": { + "refused": "Line 2: That `field` is too large to run here.", + "line": 2 + } + }, + { + "name": "a-tiny-weight", + "request": { + "program": "vars 2\ncost 1\nweight 0 1 1e-300\nsolve\n", + "seed": "1" + }, + "expect": { + "refused": "Quantising the weights divides the cost by their mean size, about 1e-300, and that gives a number above 1099511627776: the weights are too small beside the fields and the cost.", + "line": null + } + }, + { + "name": "a-pin-too-large", + "request": { + "program": "vars 2\nfield 1 1099511627776\npin 0 +\nminimize\n", + "seed": "1" + }, + "expect": { + "refused": "Line 3: A `pin` here needs a field of 1,099,511,627,778, above 1099511627776: the other fields or the cost are too large beside it.", + "line": 3 + } + }, + { + "name": "a-temperature-string-with-an-exponent", + "request": { + "program": "vars 1\nsolve\n", + "seed": "1", + "temperature": "1e-400" + }, + "expect": { + "refused": "A temperature is a number, or a decimal like \"0.5\" in a string.", + "line": null + } + } + ] +} diff --git a/wai/tools/quantum-docs/docs.json b/wai/tools/quantum-docs/docs.json index f20198347..10bf34f19 100644 --- a/wai/tools/quantum-docs/docs.json +++ b/wai/tools/quantum-docs/docs.json @@ -1,6 +1,6 @@ { "schema": "wai.quantum.docs-manifest/1", - "about": "The documents check_docs.py runs: the WAI Quantum READMEs and docs pages (engine, command line, service, Python, Studio, desktop, editors, providers, the compiler, estimates, verdicts, error correction, other formats, the variational and algorithm library, the extension points, the feature matrix and the version policy; the service's self-hosting, scheduler and provenance pages; the views' engine), the agent skill, the course's lessons, and their translations.", + "about": "The documents check_docs.py runs: the WAI Quantum READMEs and docs pages (engine, command line, service, Python, Studio, desktop, editors, providers, the compiler, estimates, verdicts, error correction, energy-landscape jobs, other formats, the variational and algorithm library, the extension points, the feature matrix and the version policy; the service's self-hosting, scheduler and provenance pages; the views' engine), the agent skill, the course's lessons, and their translations.", "docs": [ "wai/wai-quantum/README.md", "wai/wai-quantum/README.zh-CN.md", @@ -16,6 +16,7 @@ "wai/wai-quantum/docs/qasm-feature-matrix.md", "wai/wai-quantum/docs/qec.md", "wai/wai-quantum/docs/pulse.md", + "wai/wai-quantum/docs/landscape.md", "wai/wai-quantum/docs/variational.md", "wai/wai-quantum/VERSIONING.md", "wai/wai-quantum/DEPRECATIONS.md", diff --git a/wai/wai-quantum-cli/README.md b/wai/wai-quantum-cli/README.md index 4f1fada1e..978e6b92a 100644 --- a/wai/wai-quantum-cli/README.md +++ b/wai/wai-quantum-cli/README.md @@ -300,6 +300,21 @@ wai-quantum pulse check pulse.json --demo-key # the rec `--pulse` takes the run's options as JSON or a file of it: the sample period (`dt_ns`), the levels kept, the tolerance on the stated error, the steps per sample, each qubit's parameters, couplings and ports. `--target` gives each qubit's frequency, T1 and T2 from a target's calibration. A step or a schedule whose error cannot be stated within the tolerance is refused (`J041`). The model, the bound and what a receipt binds are on [the pulse page](../wai-quantum/docs/pulse.md). +## Energy-landscape jobs + +A job of this kind gives no circuit: a program in epuML states an energy over variables of three states, and a classical emulator of the settling model looks for a state of low energy. Nothing in it is a quantum effect, and every answer says so. + +```sh runnable +wai-quantum landscape examples # the worked examples, those that fail on purpose among them +wai-quantum landscape run --example talks --demo-key # the answer, its energy, every arrangement checked, the work, the priced joules +wai-quantum landscape run --example assign-3x3 --demo-key # a failure, reported as one +wai-quantum landscape sweep --example talks --seeds 5 --demo-key +wai-quantum landscape compare --example talks --seeds 5 --demo-key # and on simulated annealing, tabu search and greedy descent, at the same budget +wai-quantum landscape replay --example talks --seeds 100 # one line a seed, the same on any machine +``` + +A job that ran exits 0 whatever its verdict. A program that cannot run exits 1 with one plain sentence and its line. `landscape table` rebuilds the published scale table, `landscape instance` prints any of its instances as a program, and `landscape vectors` writes the conformance set a member is held to. The form of a job and its answer are the extension [quantum-landscape](../extensions/quantum-landscape.md), and the model, the rates and what each rate leaves out are on the [energy-landscape page](../wai-quantum/docs/landscape.md). + ## Gradients, optimisation and the algorithm library ```sh runnable diff --git a/wai/wai-quantum-cli/README.zh-CN.md b/wai/wai-quantum-cli/README.zh-CN.md index 761d8811c..5e5c9ba8a 100644 --- a/wai/wai-quantum-cli/README.zh-CN.md +++ b/wai/wai-quantum-cli/README.zh-CN.md @@ -298,6 +298,21 @@ wai-quantum pulse check pulse.json --demo-key # 回执 `--pulse` 以 JSON 或包含 JSON 的文件给出运行选项:采样周期(`dt_ns`)、保留的能级数、所述误差的容差、每个采样的步数、每个量子比特的参数、耦合和端口。`--target` 从目标的校准中给出每个量子比特的频率、T1 和 T2。误差无法在容差内说明的步长或时序会被拒绝(`J041`)。模型、误差上界以及回执所绑定的内容见[脉冲页面](../wai-quantum/docs/pulse.md)。 +## 能量景观作业 + +这类作业不给出线路:epuML 程序对三态变量给出能量,由沉降模型的经典模拟器寻找低能量状态。其中没有任何量子效应,每个答案都会说明这一点。 + +```sh runnable +wai-quantum landscape examples # 算例,其中包括故意失败的算例 +wai-quantum landscape run --example talks --demo-key # 答案、其能量、检查过的每一种排列、工作量、定价的焦耳 +wai-quantum landscape run --example assign-3x3 --demo-key # 失败,并如实报告为失败 +wai-quantum landscape sweep --example talks --seeds 5 --demo-key +wai-quantum landscape compare --example talks --seeds 5 --demo-key # 并以相同预算运行模拟退火、禁忌搜索和贪心下降 +wai-quantum landscape replay --example talks --seeds 100 # 每个种子一行,在任何机器上都相同 +``` + +已运行的作业无论结论如何都以 0 退出。无法运行的程序以 1 退出,并给出一句明白的话及其行号。`landscape table` 重建已发布的规模表,`landscape instance` 把其中任一实例打印为程序,`landscape vectors` 写出成员所要遵守的一致性集合。作业及其答案的形式见扩展 [quantum-landscape](../extensions/quantum-landscape.md),模型、费率以及每个费率未计入的内容见[能量景观页面](../wai-quantum/docs/landscape.md)。 + ## 梯度、优化与算法库 ```sh runnable diff --git a/wai/wai-quantum-cli/docs/llms-full.txt b/wai/wai-quantum-cli/docs/llms-full.txt index b6a117fa0..62cbea82f 100644 --- a/wai/wai-quantum-cli/docs/llms-full.txt +++ b/wai/wai-quantum-cli/docs/llms-full.txt @@ -1677,6 +1677,9 @@ Commands: pulse Pulse programs: a program's schedule on frames drawn as a timeline, and a pulse job checked by running it again; a pulse program runs with `run --method pulse` (`wai-quantum pulse --help`) + landscape Energy-landscape jobs: a program in epuML run on the settling model's reference, + every arrangement checked where it is small, the joules priced, and classical methods + on the same instance (`wai-quantum landscape --help`) eval Score a model on a task set with the engine: whether its program reads, the verdict's level and whether the intent holds, and the joules per solved task where its server's energy is metered; or write each task's answer three ways for a token count @@ -6071,6 +6074,497 @@ Options: Print version ``` +### `wai-quantum landscape` + +```text +Energy-landscape jobs: a program in epuML run on the settling model's reference, every arrangement +checked where it is small, the joules priced, and classical methods on the same instance +(`wai-quantum landscape --help`) + +Usage: wai-quantum landscape [OPTIONS] + +Commands: + run Run a program: the answer, its energy, every arrangement checked up to 16 + variables, the warnings, the work, the priced joules and the replay digest. It + exits 0 when the job ran, whatever the verdict, and 1 when it is refused + sweep The same program at each temperature, over a run of seeds, with the same budget + at each + compare The same program on the emulator and on simulated annealing, tabu search and + greedy descent, each with the same budget of operations a run + replay 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's start. + The same lines on any machine + examples The worked examples, those that fail on purpose among them; with a name, its + program + table Build the published table: how success and operations scale with problem size, + for the emulator and for classical methods, its counts the same bytes on any + machine + instance Print any instance of the table as a program + trial-assignment The trial of a {0, +1} domain for assignment, outside the job form: what it + fixes and what it does not, as JSON + call One request of the job form, its answer unsealed: the bytes the browser build's + `landscape_answer` gives + corpus A digest of every worked example at three temperatures, the same on every + platform + vectors The conformance set a member is held to, as JSON + (`quantum-conformance/landscape/vectors.json`) + help Print this message or the help of the given subcommand(s) + +Options: + --input + A value for a generator's integer `input`, as n=5 (repeat for each input): the program is + read as its family's circuit at those values + + --lang + The language of the words the command line writes itself, en or zh-CN (by default the one + LANG names); results, codes and the engine's own messages stay in English + + --from + The format of every program the command reads: qasm, qir (LLVM text) or jeff; by default + the file's extension says (.ll or .qir is QIR, .jeff is jeff), and OpenQASM otherwise + + -h, --help + Print help + + -V, --version + Print version +``` + +### `wai-quantum landscape run` + +```text +Run a program: the answer, its energy, every arrangement checked up to 16 variables, the warnings, +the work, the priced joules and the replay digest. It exits 0 when the job ran, whatever the +verdict, and 1 when it is refused + +Usage: wai-quantum landscape run [OPTIONS] [FILE] + +Arguments: + [FILE] + The program; `-` or nothing reads standard input + +Options: + --example + A worked example by name, in place of a file (`wai-quantum landscape examples`) + + --input + A value for a generator's integer `input`, as n=5 (repeat for each input): the program is + read as its family's circuit at those values + + --lang + The language of the words the command line writes itself, en or zh-CN (by default the one + LANG names); results, codes and the engine's own messages stay in English + + --seed + The seed: the same seed gives the same answer, on any machine + + --from + The format of every program the command reads: qasm, qir (LLVM text) or jeff; by default + the file's extension says (.ll or .qir is QIR, .jeff is jeff), and OpenQASM otherwise + + --temperature + The starting temperature, over the program's own, a decimal like 0.5; 0 is the + deterministic mode + + --json + Print the answer sealed with this user's key, as JSON (`wai-quantum verify` checks it) + + --key + Sign with the key in this file (64 hex digits) + + --demo-key + Sign with the published demo key: the receipt then shows a result is unaltered, not who + signed it + + -h, --help + Print help + + -V, --version + Print version +``` + +### `wai-quantum landscape sweep` + +```text +The same program at each temperature, over a run of seeds, with the same budget at each + +Usage: wai-quantum landscape sweep [OPTIONS] [FILE] + +Arguments: + [FILE] + The program; `-` or nothing reads standard input + +Options: + --example + A worked example by name, in place of a file (`wai-quantum landscape examples`) + + --input + A value for a generator's integer `input`, as n=5 (repeat for each input): the program is + read as its family's circuit at those values + + --lang + The language of the words the command line writes itself, en or zh-CN (by default the one + LANG names); results, codes and the engine's own messages stay in English + + --seed + The seed: the same seed gives the same answer, on any machine + + --from + The format of every program the command reads: qasm, qir (LLVM text) or jeff; by default + the file's extension says (.ll or .qir is QIR, .jeff is jeff), and OpenQASM otherwise + + --seeds + How many seeds at each temperature + + --temperatures + The temperatures, by default from 0 through the program's default to its hot start + + --json + Print the answer sealed with this user's key, as JSON (`wai-quantum verify` checks it) + + --key + Sign with the key in this file (64 hex digits) + + --demo-key + Sign with the published demo key: the receipt then shows a result is unaltered, not who + signed it + + -h, --help + Print help + + -V, --version + Print version +``` + +### `wai-quantum landscape compare` + +```text +The same program on the emulator and on simulated annealing, tabu search and greedy descent, each +with the same budget of operations a run + +Usage: wai-quantum landscape compare [OPTIONS] [FILE] + +Arguments: + [FILE] + The program; `-` or nothing reads standard input + +Options: + --example + A worked example by name, in place of a file (`wai-quantum landscape examples`) + + --input + A value for a generator's integer `input`, as n=5 (repeat for each input): the program is + read as its family's circuit at those values + + --lang + The language of the words the command line writes itself, en or zh-CN (by default the one + LANG names); results, codes and the engine's own messages stay in English + + --seed + The seed: the same seed gives the same answer, on any machine + + --from + The format of every program the command reads: qasm, qir (LLVM text) or jeff; by default + the file's extension says (.ll or .qir is QIR, .jeff is jeff), and OpenQASM otherwise + + --seeds + How many seeds for each solver + + --temperature + The emulator's starting temperature, over the program's own, a decimal like 0.5 + + --json + Print the answer sealed with this user's key, as JSON (`wai-quantum verify` checks it) + + --key + Sign with the key in this file (64 hex digits) + + --demo-key + Sign with the published demo key: the receipt then shows a result is unaltered, not who + signed it + + -h, --help + Print help + + -V, --version + Print version +``` + +### `wai-quantum landscape replay` + +```text +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's start. The same lines on any machine + +Usage: wai-quantum landscape replay [OPTIONS] [FILE] + +Arguments: + [FILE] + The program; `-` or nothing reads standard input + +Options: + --example + A worked example by name, in place of a file (`wai-quantum landscape examples`) + + --input + A value for a generator's integer `input`, as n=5 (repeat for each input): the program is + read as its family's circuit at those values + + --lang + The language of the words the command line writes itself, en or zh-CN (by default the one + LANG names); results, codes and the engine's own messages stay in English + + --seed + The seed: the same seed gives the same answer, on any machine + + --from + The format of every program the command reads: qasm, qir (LLVM text) or jeff; by default + the file's extension says (.ll or .qir is QIR, .jeff is jeff), and OpenQASM otherwise + + --seeds + How many seeds, from `--seed` up + + --temperature + The starting temperature, over the program's own, a decimal like 0.5 + + -h, --help + Print help + + -V, --version + Print version +``` + +### `wai-quantum landscape examples` + +```text +The worked examples, those that fail on purpose among them; with a name, its program + +Usage: wai-quantum landscape examples [OPTIONS] [NAME] + +Arguments: + [NAME] + One example's name: its program, on standard output + +Options: + --input + A value for a generator's integer `input`, as n=5 (repeat for each input): the program is + read as its family's circuit at those values + + --json + Print them as JSON + + --lang + The language of the words the command line writes itself, en or zh-CN (by default the one + LANG names); results, codes and the engine's own messages stay in English + + --from + The format of every program the command reads: qasm, qir (LLVM text) or jeff; by default + the file's extension says (.ll or .qir is QIR, .jeff is jeff), and OpenQASM otherwise + + -h, --help + Print help + + -V, --version + Print version +``` + +### `wai-quantum landscape table` + +```text +Build the published table: how success and operations scale with problem size, for the emulator and +for classical methods, its counts the same bytes on any machine + +Usage: wai-quantum landscape table [OPTIONS] + +Options: + --input + A value for a generator's integer `input`, as n=5 (repeat for each input): the program is + read as its family's circuit at those values + + --quick + A small table, to try it + + --lang + The language of the words the command line writes itself, en or zh-CN (by default the one + LANG names); results, codes and the engine's own messages stay in English + + --threads + How many threads (by default what the machine has, at most 16); the counts do not depend + on it + + --from + The format of every program the command reads: qasm, qir (LLVM text) or jeff; by default + the file's extension says (.ll or .qir is QIR, .jeff is jeff), and OpenQASM otherwise + + --out + Write table.json, table.md and timing.json here, in place of the JSON on standard output + + -h, --help + Print help + + -V, --version + Print version +``` + +### `wai-quantum landscape instance` + +```text +Print any instance of the table as a program + +Usage: wai-quantum landscape instance [OPTIONS] + +Arguments: + + The family: clusters, sparse-glass, dense-glass or selection + + + How many variables, at least 8 + + + Which instance of that size + +Options: + --input + A value for a generator's integer `input`, as n=5 (repeat for each input): the program is + read as its family's circuit at those values + + --lang + The language of the words the command line writes itself, en or zh-CN (by default the one + LANG names); results, codes and the engine's own messages stay in English + + --from + The format of every program the command reads: qasm, qir (LLVM text) or jeff; by default + the file's extension says (.ll or .qir is QIR, .jeff is jeff), and OpenQASM otherwise + + -h, --help + Print help + + -V, --version + Print version +``` + +### `wai-quantum landscape trial-assignment` + +```text +The trial of a {0, +1} domain for assignment, outside the job form: what it fixes and what it does +not, as JSON + +Usage: wai-quantum landscape trial-assignment [OPTIONS] + +Options: + --input + A value for a generator's integer `input`, as n=5 (repeat for each input): the program is + read as its family's circuit at those values + + --lang + The language of the words the command line writes itself, en or zh-CN (by default the one + LANG names); results, codes and the engine's own messages stay in English + + --from + The format of every program the command reads: qasm, qir (LLVM text) or jeff; by default + the file's extension says (.ll or .qir is QIR, .jeff is jeff), and OpenQASM otherwise + + -h, --help + Print help + + -V, --version + Print version +``` + +### `wai-quantum landscape call` + +```text +One request of the job form, its answer unsealed: the bytes the browser build's `landscape_answer` +gives + +Usage: wai-quantum landscape call [OPTIONS] [FILE] + +Arguments: + + The operation: run, sweep, compare, replay or examples + + [FILE] + The request, as JSON; `-` or nothing reads standard input + +Options: + --input + A value for a generator's integer `input`, as n=5 (repeat for each input): the program is + read as its family's circuit at those values + + --lang + The language of the words the command line writes itself, en or zh-CN (by default the one + LANG names); results, codes and the engine's own messages stay in English + + --from + The format of every program the command reads: qasm, qir (LLVM text) or jeff; by default + the file's extension says (.ll or .qir is QIR, .jeff is jeff), and OpenQASM otherwise + + -h, --help + Print help + + -V, --version + Print version +``` + +### `wai-quantum landscape corpus` + +```text +A digest of every worked example at three temperatures, the same on every platform + +Usage: wai-quantum landscape corpus [OPTIONS] + +Options: + --input + A value for a generator's integer `input`, as n=5 (repeat for each input): the program is + read as its family's circuit at those values + + --seeds + How many seeds for each + + [default: 20] + + --lang + The language of the words the command line writes itself, en or zh-CN (by default the one + LANG names); results, codes and the engine's own messages stay in English + + --from + The format of every program the command reads: qasm, qir (LLVM text) or jeff; by default + the file's extension says (.ll or .qir is QIR, .jeff is jeff), and OpenQASM otherwise + + -h, --help + Print help + + -V, --version + Print version +``` + +### `wai-quantum landscape vectors` + +```text +The conformance set a member is held to, as JSON (`quantum-conformance/landscape/vectors.json`) + +Usage: wai-quantum landscape vectors [OPTIONS] + +Options: + --input + A value for a generator's integer `input`, as n=5 (repeat for each input): the program is + read as its family's circuit at those values + + --lang + The language of the words the command line writes itself, en or zh-CN (by default the one + LANG names); results, codes and the engine's own messages stay in English + + --from + The format of every program the command reads: qasm, qir (LLVM text) or jeff; by default + the file's extension says (.ll or .qir is QIR, .jeff is jeff), and OpenQASM otherwise + + -h, --help + Print help + + -V, --version + Print version +``` + ### `wai-quantum eval` ```text diff --git a/wai/wai-quantum-cli/locales/zh-CN.json b/wai/wai-quantum-cli/locales/zh-CN.json index 7c49d45e9..449d5d73b 100644 --- a/wai/wai-quantum-cli/locales/zh-CN.json +++ b/wai/wai-quantum-cli/locales/zh-CN.json @@ -54,6 +54,10 @@ "qaoa": { "source": "Max-Cut on a graph by QAOA: the program and its cost built from the graph, optimised, and the cut set beside the maximum cut by brute force", "text": "用 QAOA 求图的最大割:由图构造程序及其代价函数,优化后把所得的割与穷举得到的最大割并列" }, "build": { "source": "A standard algorithm's program (qpe, amplitude_estimation, adder, qsvt) on standard output, checked by the equivalence ladder against a reference built from its definition", "text": "把标准算法的程序(qpe、amplitude_estimation、adder、qsvt)写到标准输出,并由等价性阶梯对照按其定义构造的参考加以检查" }, "qec": { "source": "Error correction: a code and its certified distance, a syndrome or a circuit's detectors decoded, a threshold measured, each answer signed (`wai-quantum qec --help`)", "text": "纠错:一个码及其经过认证的距离、对校验子或线路探测器的解码、测得的阈值,每个回答都带签名(`wai-quantum qec --help`)" }, + "landscape": { + "source": "Energy-landscape jobs: a program in epuML run on the settling model's reference, every arrangement checked where it is small, the joules priced, and classical methods on the same instance (`wai-quantum landscape --help`)", + "text": "能量景观作业:在沉降模型的参考实现上运行 epuML 程序,规模小时检查每一种排列,焦耳按定价给出,并在同一实例上运行经典方法(`wai-quantum landscape --help`)" + }, "pulse": { "source": "Pulse programs: a program's schedule on frames drawn as a timeline, and a pulse job checked by running it again; a pulse program runs with `run --method pulse` (`wai-quantum pulse --help`)", "text": "脉冲程序:把程序在帧上的时序画成时间线,并通过重新运行来检查脉冲作业;脉冲程序用 `run --method pulse` 运行(`wai-quantum pulse --help`)" }, "repair": { "source": "Repair a program from its diagnostics, on the lines they flag alone: the mechanical fixes, a proposed fix checked against those lines, and what needs a person (exit 0 it reads, 1 something needs a person, 2 a proposal rejected)", "text": "依据诊断修复程序,只改动诊断所标记的行:机械性的修正、对照这些行检查过的修复建议,以及需要人来处理的部分(退出状态 0 可读取,1 有需要人处理的部分,2 建议被拒绝)" }, "submit": { "source": "Send a program, a set or a sweep to the job service, follow its status to the end, and print its result", "text": "把程序、程序集或参数扫描发送给作业服务,跟踪其状态直至结束,并打印其结果" }, diff --git a/wai/wai-quantum-cli/src/landscape.rs b/wai/wai-quantum-cli/src/landscape.rs new file mode 100644 index 000000000..e2faaec9c --- /dev/null +++ b/wai/wai-quantum-cli/src/landscape.rs @@ -0,0 +1,430 @@ +//! `wai-quantum landscape`: energy-landscape jobs ([`wai_quantum::quantum_landscape`]). +//! A job is a program in epuML: an energy over variables of three states, whose +//! lowest state is the answer. It runs on the settling model's reference, in +//! software, and every answer says what it is. +//! +//! ```text +//! wai-quantum landscape run prog.epuml --seed 7 --temperature 0.5 # the answer, its energy, every arrangement checked where small, the work, the priced joules +//! wai-quantum landscape run --example talks --json --demo-key # the same seed gives the same bytes on any machine +//! wai-quantum landscape sweep prog.epuml # the same program at each temperature +//! wai-quantum landscape compare prog.epuml # and on simulated annealing, tabu search and greedy descent, at the same budget +//! wai-quantum landscape replay prog.epuml --seeds 1000 # one line a seed, to compare across machines +//! wai-quantum landscape examples # the worked examples, those that fail on purpose among them +//! wai-quantum landscape table --out results # the published scale table +//! wai-quantum landscape instance clusters 64 0 # any instance of the table, as a program +//! ``` + +use serde_json::{Value, json}; +use std::path::PathBuf; +use wai_quantum::engine::job as engine_job; +use wai_quantum::quantum_landscape::price::{group, plural}; +use wai_quantum::quantum_landscape::program::Refusal; +use wai_quantum::quantum_landscape::run::shown_temperature; +use wai_quantum::quantum_landscape::{EXAMPLES, conformance, job, table, trial}; + +#[derive(clap::Args)] +pub struct Source { + /// The program; `-` or nothing reads standard input + file: Option, + /// A worked example by name, in place of a file (`wai-quantum landscape examples`) + #[arg(long, conflicts_with = "file")] + example: Option, + /// The seed: the same seed gives the same answer, on any machine + #[arg(long, value_name = "N")] + seed: Option, +} + +#[derive(clap::Subcommand)] +pub enum Cmd { + /// Run a program: the answer, its energy, every arrangement checked up to 16 variables, the warnings, the work, the priced joules and the replay digest. It exits 0 when the job ran, whatever the verdict, and 1 when it is refused + Run { + #[command(flatten)] + source: Source, + /// The starting temperature, over the program's own, a decimal like 0.5; 0 is the deterministic mode + #[arg(long, value_name = "T")] + temperature: Option, + #[command(flatten)] + out: super::qec::Out, + }, + /// The same program at each temperature, over a run of seeds, with the same budget at each + Sweep { + #[command(flatten)] + source: Source, + /// How many seeds at each temperature + #[arg(long, value_name = "K")] + seeds: Option, + /// The temperatures, by default from 0 through the program's default to its hot start + #[arg(long, value_name = "A,B,C", value_delimiter = ',')] + temperatures: Option>, + #[command(flatten)] + out: super::qec::Out, + }, + /// The same program on the emulator and on simulated annealing, tabu search and greedy descent, each with the same budget of operations a run + Compare { + #[command(flatten)] + source: Source, + /// How many seeds for each solver + #[arg(long, value_name = "K")] + seeds: Option, + /// The emulator's starting temperature, over the program's own, a decimal like 0.5 + #[arg(long, value_name = "T")] + temperature: Option, + #[command(flatten)] + out: super::qec::Out, + }, + /// 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's start. The same lines on any machine + Replay { + #[command(flatten)] + source: Source, + /// How many seeds, from `--seed` up + #[arg(long, value_name = "K")] + seeds: Option, + /// The starting temperature, over the program's own, a decimal like 0.5 + #[arg(long, value_name = "T")] + temperature: Option, + }, + /// The worked examples, those that fail on purpose among them; with a name, its program + Examples { + /// One example's name: its program, on standard output + name: Option, + /// Print them as JSON + #[arg(long)] + json: bool, + }, + /// Build the published table: how success and operations scale with problem size, for the emulator and for classical methods, its counts the same bytes on any machine + Table { + /// A small table, to try it + #[arg(long)] + quick: bool, + /// How many threads (by default what the machine has, at most 16); the counts do not depend on it + #[arg(long, value_name = "N")] + threads: Option, + /// Write table.json, table.md and timing.json here, in place of the JSON on standard output + #[arg(long, value_name = "DIR")] + out: Option, + }, + /// Print any instance of the table as a program + Instance { + /// The family: clusters, sparse-glass, dense-glass or selection + family: String, + /// How many variables, at least 8 + variables: usize, + /// Which instance of that size + number: u32, + }, + /// The trial of a {0, +1} domain for assignment, outside the job form: what it fixes and what it does not, as JSON + TrialAssignment, + /// One request of the job form, its answer unsealed: the bytes the browser build's `landscape_answer` gives + Call { + /// The operation: run, sweep, compare, replay or examples + op: String, + /// The request, as JSON; `-` or nothing reads standard input + file: Option, + }, + /// A digest of every worked example at three temperatures, the same on every platform + Corpus { + /// How many seeds for each + #[arg(long, default_value_t = 20)] + seeds: u32, + }, + /// The conformance set a member is held to, as JSON (`quantum-conformance/landscape/vectors.json`) + Vectors, +} + +fn refused(r: Refusal) -> String { + r.plain() +} + +/// The request a source gives: the program's text, and the seed. +fn request(source: &Source) -> Result { + let program = match &source.example { + Some(name) => EXAMPLES.iter().find(|(n, _)| n == name).map(|(_, t)| (*t).to_string()).ok_or_else(|| format!("{name}: no such example; `wai-quantum landscape examples` lists them"))?, + None => super::read_text(source.file.as_deref())?, + }; + let mut req = json!({ "program": program }); + if let Some(s) = &source.seed { + req["seed"] = json!(s); + } + Ok(req) +} + +fn report_run(r: &Value) -> String { + let mut s = String::new(); + let mut line = |k: &str, v: &str| s.push_str(&format!("{k:<10} {v}\n")); + let text = |v: &Value| v.as_str().unwrap_or("").to_string(); + line("this is", &text(&r["label"])); + if let Some(t) = r["title"].as_str() { + line("program", t); + } + line("size", &format!("{}, {}", plural(r["variables"].as_u64().unwrap_or(0), "variable"), plural(r["pairs"].as_u64().unwrap_or(0), "coupling"))); + line("ran on", &format!("{}, {}", text(&r["member"]["id"]), text(&r["member"]["kernel"]))); + line("mode", &text(&r["mode_words"])); + line("seed", &text(&r["seed"])); + line("answer", &text(&r["state"])); + let item = r["item"].as_str(); + for g in r["groups"].as_array().into_iter().flatten() { + let items: Vec = g["items"].as_array().into_iter().flatten().map(Value::to_string).collect(); + let noun = item.map(|i| format!("{i}{} ", if items.len() == 1 { "" } else { "s" })).unwrap_or_default(); + line("", &format!("{}: {}", text(&g["name"]), if items.is_empty() { "none".to_string() } else { format!("{noun}{}", items.join(", ")) })); + } + line("energy", &r["energy"].to_string()); + match r["truth"].as_object() { + Some(t) => { + let mut v = format!("all {} arrangements checked: lowest energy {}, held by {}.", group(t["arrangements"].as_u64().unwrap_or(0)), t["lowest"].as_f64().unwrap_or(f64::NAN), group(t["at_lowest"].as_u64().unwrap_or(0))); + if let Some(p) = t["at_lowest_passing"].as_u64() { + v.push_str(&format!(" Of those, {} the problem.", if p == 1 { "1 solves".to_string() } else { format!("{} solve", group(p)) })); + } + v.push_str(if r["reached"] == true { " This run reached it." } else { " This run did not reach it." }); + line("truth", &v); + } + None => line("truth", "too many arrangements to check them all"), + } + if r["declared"].is_object() { + let d = &r["declared"]; + let tail = d["truth"]["lowest"].as_f64().map(|l| format!("; the lowest as declared is {l}")).unwrap_or_default(); + line("declared", &format!("this answer's energy on the weights as declared is {}{tail}", d["energy"])); + } + let warnings = r["warnings"].as_array().cloned().unwrap_or_default(); + if warnings.is_empty() { + line("warnings", "none"); + } + for (i, w) in warnings.iter().enumerate() { + let at = w["line"].as_u64().map(|l| format!("line {l}: ")).unwrap_or_default(); + line(if i == 0 { "warnings" } else { "" }, &format!("{at}{}", text(&w["text"]))); + } + for (i, c) in r["checks"].as_array().into_iter().flatten().enumerate() { + line(if i == 0 { "checks" } else { "" }, &format!("{}: {}", if c["passed"] == true { "pass" } else { "FAIL" }, text(&c["detail"]))); + } + line("verdict", &format!("{}. {}", text(&r["verdict"]).to_uppercase(), text(&r["summary"]))); + let w = &r["work"]; + line( + "work", + &format!( + "{}, {}, {}, {}. Ended: {}.", + plural(w["accumulates"].as_u64().unwrap_or(0), "accumulate"), + plural(w["sweeps"].as_u64().unwrap_or(0), "sweep"), + plural(w["cell_updates"].as_u64().unwrap_or(0), "cell update"), + plural(w["draws"].as_u64().unwrap_or(0), "random draw"), + text(&w["ended"]), + ), + ); + let j = &r["joules"]; + line("joules", &text(&j["line"])); + line("", &text(&j["rate_source"])); + line("", &text(&j["published_add"]["line"])); + line("", &text(&j["left_out"]["line"])); + line("replay", &format!("{} (the state alone: {})", text(&r["replay"]), text(&r["state_hash"]))); + s +} + +fn report_sweep(r: &Value) -> String { + let mut s = format!("this is {}\n", r["label"].as_str().unwrap_or("")); + s.push_str("temperature mode runs at lowest checks best mean accumulates a run priced a run board draw left out\n"); + let opt = |v: &Value| v.as_u64().map_or("-".to_string(), |x| x.to_string()); + for row in r["rows"].as_array().into_iter().flatten() { + s.push_str(&format!( + "{:<12} {:<8} {:<5} {:<10} {:<7} {:<8} {:<8} {:<18} {:<15} {}\n", + format!("{}{}", shown_temperature(row["temperature"].as_f64().unwrap_or(0.0)), if row["is_default"] == true { "*" } else { "" }), + row["mode"].as_str().unwrap_or(""), + row["runs"].to_string(), + opt(&row["reached_lowest"]), + opt(&row["passed_checks"]), + row["best_energy"].to_string(), + row["mean_energy"].to_string(), + group(row["accumulates_per_run"].as_u64().unwrap_or(0)), + row["priced_per_run"].as_str().unwrap_or(""), + row["left_out_per_run"].as_str().unwrap_or(""), + )); + } + s.push_str("* the default start: the square root of the mean couplings a variable has, and at least 1\n"); + s.push_str("A priced figure counts the work alone. The board draws power at every clock too, and the last column is what the fewest clocks a run takes come to.\n"); + if let Some(l) = r["lowest"].as_f64() { + s.push_str(&format!("The lowest energy is {l}, from checking every arrangement.\n")); + } + s +} + +fn report_compare(r: &Value) -> String { + let mut s = format!("this is {}\n", r["label"].as_str().unwrap_or("")); + s.push_str(&format!("Target energy {}, {}.\n", r["target"], if r["target_is_exact"] == true { "from checking every arrangement" } else { "the lowest any solver here found, which proves nothing" })); + s.push_str(&format!("{:<40} {:<10} {:<7} {:<8} {}\n", "solver", "at target", "checks", "best", "operations a run")); + for row in r["rows"].as_array().into_iter().flatten() { + s.push_str(&format!( + "{:<40} {:<10} {:<7} {:<8} {}\n", + row["solver"].as_str().unwrap_or(""), + format!("{}/{}", row["reached_target"], row["runs"]), + row["passed_checks"].as_u64().map_or("-".to_string(), |v| format!("{v}/{}", row["runs"])), + row["best_energy"].to_string(), + group(row["operations_per_run"].as_u64().unwrap_or(0)), + )); + } + s +} + +fn answer(op: &str, req: Value, out: &super::qec::Out) -> Result<(), String> { + let result = job::call(op, &req).map_err(refused)?; + let k = super::load_key(&out.key); + let body = job::seal(&req, result, &k.key); + if out.json { + println!("{body}"); + return Ok(()); + } + let r = &body["result"]; + print!("{}", match op { + "run" => report_run(r), + "sweep" => report_sweep(r), + _ => report_compare(r), + }); + let ok = engine_job::verify(&body).map(|(ok, _)| ok).unwrap_or(false); + let signer = r["receipt"]["key"].as_str().and_then(engine_job::did_of).unwrap_or_default(); + println!("receipt {}, signed by {signer}", if ok { "verifies" } else { "DOES NOT VERIFY" }); + Ok(()) +} + +/// Seconds, for the timing the table keeps apart from its counts. +fn now() -> f64 { + static START: std::sync::OnceLock = std::sync::OnceLock::new(); + START.get_or_init(std::time::Instant::now).elapsed().as_secs_f64() +} + +pub fn main(cmd: Cmd) { + let r = (|| -> Result<(), String> { + match cmd { + Cmd::Run { source, temperature, out } => { + let mut req = request(&source)?; + if let Some(t) = temperature { + req["temperature"] = json!(t); + } + answer("run", req, &out) + } + Cmd::Sweep { source, seeds, temperatures, out } => { + let mut req = request(&source)?; + if let Some(k) = seeds { + req["seeds"] = json!(k); + } + if let Some(t) = temperatures { + req["temperatures"] = json!(t); + } + answer("sweep", req, &out) + } + Cmd::Compare { source, seeds, temperature, out } => { + let mut req = request(&source)?; + if let Some(k) = seeds { + req["seeds"] = json!(k); + } + if let Some(t) = temperature { + req["temperature"] = json!(t); + } + answer("compare", req, &out) + } + Cmd::Replay { source, seeds, temperature } => { + let mut req = request(&source)?; + if let Some(k) = seeds { + req["seeds"] = json!(k); + } + if let Some(t) = temperature { + req["temperature"] = json!(t); + } + print!("{}", job::call("replay", &req).map_err(refused)?["lines"].as_str().unwrap_or("")); + Ok(()) + } + Cmd::Examples { name, json: as_json } => { + if let Some(name) = name { + let (_, text) = EXAMPLES.iter().find(|(n, _)| *n == name).ok_or_else(|| format!("{name}: no such example"))?; + print!("{text}"); + return Ok(()); + } + let all = job::examples(); + if as_json { + println!("{all}"); + return Ok(()); + } + for e in all["examples"].as_array().into_iter().flatten() { + println!("{:<16} {:<6} {}", e["name"].as_str().unwrap_or(""), e["expect"].as_str().unwrap_or(""), e["title"].as_str().unwrap_or("")); + } + Ok(()) + } + Cmd::Table { quick, threads, out } => { + let mut options = if quick { table::Options::quick() } else { table::Options::published() }; + if let Some(t) = threads { + options.threads = t; + } + let t = table::build(&options, now); + match out { + Some(dir) => { + std::fs::create_dir_all(&dir).map_err(|e| format!("{}: {e}", dir.display()))?; + let write = |name: &str, body: String| std::fs::write(dir.join(name), body).map_err(|e| format!("{}: {e}", dir.join(name).display())); + write("table.json", format!("{}\n", serde_json::to_string_pretty(&t.to_json()).unwrap_or_default()))?; + write("table.md", t.to_markdown())?; + write("timing.json", format!("{}\n", t.timing_json()))?; + print!("{}", t.to_markdown()); + } + None => println!("{}", t.to_json()), + } + Ok(()) + } + Cmd::Instance { family, variables, number } => { + let f = table::Family::from_name(&family).ok_or_else(|| format!("{family}: the families are clusters, sparse-glass, dense-glass and selection"))?; + if variables < 8 { + return Err("an instance has at least 8 variables".into()); + } + print!("{}", table::instance_program(f, variables, number)); + Ok(()) + } + Cmd::TrialAssignment => { + println!("{}", serde_json::to_string_pretty(&trial::assignment_trial()).unwrap_or_default()); + Ok(()) + } + Cmd::Call { op, file } => { + let text = super::read_text(file.as_deref())?; + let req: Value = serde_json::from_str(&text).map_err(|e| format!("the request is JSON: {e}"))?; + match job::call(&op, &req) { + Ok(a) => println!("{a}"), + Err(r) => println!("{}", json!({ "error": r.plain(), "line": r.line })), + } + Ok(()) + } + Cmd::Corpus { seeds } => { + println!("{}", job::corpus_digest(seeds)); + Ok(()) + } + Cmd::Vectors => { + println!("{}", serde_json::to_string_pretty(&conformance::vectors()).unwrap_or_default()); + Ok(()) + } + } + })(); + if let Err(e) = r { + super::die(&e); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn run(example: &str) -> Value { + job::call("run", &json!({ "program": EXAMPLES.iter().find(|(n, _)| *n == example).unwrap().1 })).unwrap() + } + + #[test] + fn a_report_says_what_the_answer_says() { + let t = report_run(&run("talks")); + for want in ["classical emulator", "cpu:reference", "room A: talks", "all 531,441 arrangements checked", "SOLVED", "Priced, not read from a meter", "Sampling rate", "Small jobs price low", "For scale: a published 2014 figure for one 8-bit integer add", "replay"] { + assert!(t.contains(want), "{want} in:\n{t}"); + } + let f = report_run(&run("colour-six")); + assert!(f.contains("FAILED") && f.contains("FAIL:") && f.contains("does not express the problem"), "{f}"); + let c = report_run(&run("chain")); + assert!(c.contains("Deterministic rate") && c.contains("came to rest"), "{c}"); + } + + #[test] + fn a_sweep_and_a_comparison_are_tables() { + let text = EXAMPLES.iter().find(|(n, _)| *n == "clusters").unwrap().1; + let s = report_sweep(&job::call("sweep", &json!({ "program": text, "seeds": 2 })).unwrap()); + assert!(s.contains("default start") && s.contains("The lowest energy is") && s.contains("board draw left out"), "{s}"); + let c = report_compare(&job::call("compare", &json!({ "program": text, "seeds": 2 })).unwrap()); + assert!(c.contains("simulated annealing") && c.contains("from checking every arrangement"), "{c}"); + } +} diff --git a/wai/wai-quantum-cli/src/main.rs b/wai/wai-quantum-cli/src/main.rs index 444c3c78b..225b550ea 100644 --- a/wai/wai-quantum-cli/src/main.rs +++ b/wai/wai-quantum-cli/src/main.rs @@ -66,6 +66,8 @@ //! wai-quantum run rabi.qasm --method pulse # a pulse program (OpenPulse) integrated, its error bounded, the same bits everywhere //! wai-quantum pulse schedule rabi.qasm # its frames' timelines, plays, waveforms and gate calls //! wai-quantum pulse check job.json # a pulse job's receipt, its program, and the run again, bit for bit +//! wai-quantum landscape run prog.epuml # an energy-landscape job: the answer, every arrangement checked where small, the priced joules +//! wai-quantum landscape compare prog.epuml # the same job on the emulator and on classical methods, at the same budget //! wai-quantum completions zsh # shell completion //! ``` //! @@ -96,6 +98,7 @@ mod guide; mod fmt; mod history; mod lab; +mod landscape; mod learn; mod locale; mod locks; @@ -450,6 +453,11 @@ enum Command { #[command(subcommand)] cmd: pulse::Cmd, }, + /// Energy-landscape jobs: a program in epuML run on the settling model's reference, every arrangement checked where it is small, the joules priced, and classical methods on the same instance (`wai-quantum landscape --help`) + Landscape { + #[command(subcommand)] + cmd: landscape::Cmd, + }, /// Score a model on a task set with the engine: whether its program reads, the verdict's level and whether the intent holds, and the joules per solved task where its server's energy is metered; or write each task's answer three ways for a token count (`wai-quantum eval --help`) Eval { #[command(subcommand)] @@ -1614,6 +1622,7 @@ fn main() { } Command::Qec { cmd } => qec::main(cmd), Command::Pulse { cmd } => pulse::main(cmd), + Command::Landscape { cmd } => landscape::main(cmd), Command::Lab { args } => lab::main(args), Command::Targets { cmd } => targets::main(cmd), #[cfg(feature = "providers")] diff --git a/wai/wai-quantum/Cargo.toml b/wai/wai-quantum/Cargo.toml index 2b57a2678..33004c889 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/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/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. @@ -102,7 +102,7 @@ quantum_mpsim = ["quantum_sv", "quantum_mps"] # gates (quantum_toolchain). The resource model and the decoders' memory # experiment come with it: a program's estimate (engine::estimate) is # calibrated from the decoders' own threshold fits. -quantum_engine = ["quantum_qasm", "quantum_tn", "quantum_cutrank", "quantum_mpsim", "quantum_noisy", "quantum_toolchain", "quantum_resource", "quantum_frame", "quantum_pulse", "dep:serde", "serde_json/float_roundtrip"] +quantum_engine = ["quantum_qasm", "quantum_tn", "quantum_cutrank", "quantum_mpsim", "quantum_noisy", "quantum_toolchain", "quantum_resource", "quantum_frame", "quantum_pulse", "quantum_landscape", "dep:serde", "serde_json/float_roundtrip"] quantum_pauli = ["quantum"] # Sparse Pauli dynamics at utility scale: arbitrary-angle Pauli rotations on up # to 1024 qubits, reproducible bit for bit, with a rigorous truncation bound. @@ -139,6 +139,10 @@ quantum_ldpc = ["quantum_frame"] # Quantum-inspired optimisation: simulated bifurcation (ballistic, discrete, # heated), simulated annealing and an exact referee, on Ising and Max-Cut. quantum_qio = [] +# Energy-landscape jobs: a program in epuML, the settling model that takes it on the CPU +# 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"] # 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- @@ -233,13 +237,21 @@ quantum_phasor_meter = ["_signing"] # Everything. full = [ - "quantum", "quantum_toolchain", "quantum_mps", "quantum_stabilizer", "quantum_tableau", "quantum_cutrank", "quantum_lang", "quantum_pulse", "quantum_qasm", "quantum_engine", "quantum_mpsim", "quantum_pauli", "quantum_spd", "quantum_spd_receipt", "quantum_bptn", "quantum_resource", "quantum_resource_receipt", "quantum_frame", "quantum_tn", "quantum_gbs", "quantum_sv", "quantum_arch", "quantum_ldpc", "quantum_qio", "quantum_uf", "quantum_noisy", "quantum_dmrg", "quantum_tdvp", "quantum_chem", "quantum_nqs", "quantum_qir", "quantum_info", "quantum_qudit", "quantum_comm", "quantum_source", "quantum_channel", + "quantum", "quantum_toolchain", "quantum_mps", "quantum_stabilizer", "quantum_tableau", "quantum_cutrank", "quantum_lang", "quantum_pulse", "quantum_qasm", "quantum_engine", "quantum_mpsim", "quantum_pauli", "quantum_spd", "quantum_spd_receipt", "quantum_bptn", "quantum_resource", "quantum_resource_receipt", "quantum_frame", "quantum_tn", "quantum_gbs", "quantum_sv", "quantum_arch", "quantum_ldpc", "quantum_qio", "quantum_landscape", "quantum_uf", "quantum_noisy", "quantum_dmrg", "quantum_tdvp", "quantum_chem", "quantum_nqs", "quantum_qir", "quantum_info", "quantum_qudit", "quantum_comm", "quantum_source", "quantum_channel", "quantum_receipt", "quantum_ops", "quantum_cal", "quantum_mitigate", "quantum_qec", "quantum_control", "quantum_noise", "quantum_compile", "quantum_atom", "quantum_qbom", "quantum_fabric", "quantum_vml", "quantum_phasor", "quantum_qfhrr", "quantum_kernel", "quantum_qdata", "quantum_phasor_meter", ] +[[test]] +name = "landscape_conformance" +required-features = ["quantum_landscape"] + +[[test]] +name = "landscape_float_read" +required-features = ["quantum_landscape"] + [[test]] name = "webgpu_member" required-features = ["quantum_fabric_webgpu"] diff --git a/wai/wai-quantum/README.md b/wai/wai-quantum/README.md index d34bc5f3b..f4d09c0b8 100644 --- a/wai/wai-quantum/README.md +++ b/wai/wai-quantum/README.md @@ -79,6 +79,7 @@ never creates information. | `quantum_resource` | **fault-tolerant resource estimation**: from a program's logical counts (qubits, T, Toffoli and rotation gates, measurements) to code distance, T-state factories found by exhaustive search, physical qubits and runtime, on six reference qubits and three codes, with the qubits-versus-time frontier. Reproduces the published worked examples to their printed precision | | `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_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 | @@ -523,6 +524,19 @@ its calibrations, the device and the target snapshot it ran on, and the integrator and its step. The model, the bound and the options are on [docs/pulse.md](docs/pulse.md). +## Energy-landscape jobs + +A job of this kind gives no circuit: it states an energy over variables of three +states and asks for a state of low energy. `wai-quantum landscape run` runs a +program on a classical emulator of the settling model. The answer gives the state, +its exact energy, every arrangement checked where the program is small enough, the +work counted, and the joules priced at rates whose sources are named, never +measured here. `wai-quantum landscape compare` runs simulated annealing, tabu search +and greedy descent on the same instance at the same budget, and the published table +says where they lead. The job form and the seam a GPU or an FPGA board runs behind +are the extension [quantum-landscape](../extensions/quantum-landscape.md), and the +rest is on [docs/landscape.md](docs/landscape.md). + ## Variational algorithms and the algorithm library ```sh runnable diff --git a/wai/wai-quantum/README.zh-CN.md b/wai/wai-quantum/README.zh-CN.md index ff68516c2..1f899b3ac 100644 --- a/wai/wai-quantum/README.zh-CN.md +++ b/wai/wai-quantum/README.zh-CN.md @@ -62,6 +62,7 @@ wai-quantum lab qutrit # d = 3,不只是量子比特 | `quantum_resource` | **容错资源估算**:从程序的逻辑计数(量子比特、T 门、Toffoli 门和旋转门、测量)出发,得到码距、通过穷举搜索找到的 T 态工厂、物理量子比特数和运行时间,覆盖六种参考量子比特和三种码,并给出量子比特数与时间之间的前沿。按已发表算例所印的精度复现了这些算例 | | `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_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⁻⁹` 以内;超过之后,其误差随它所报告的舍弃权重变化 | @@ -314,6 +315,10 @@ wai-quantum qec threshold --distances 3,5,7 --p 0.002,0.004 --shots 20000 脉冲程序以产生各个门的控制信号来给出这些门:作用于物理量子比特的 OpenPulse 校准。`wai-quantum pulse schedule` 画出它的时序;`wai-quantum run` 加上 `--method pulse` 在主方程下对它积分,结果有界,并且在每个平台上比特一致;`wai-quantum pulse check` 通过重新运行,把一个已封存的脉冲作业与其请求对照检查。同样的运行在每个入口上都是作业(两个 MCP 服务器的运行工具、作业服务、页面),两个 MCP 服务器都提供 `pulse_schedule` 和 `pulse_check`。其回执绑定控制程序、其校准、它所运行的设备与目标快照,以及积分器及其步长。模型、误差上界和选项见 [docs/pulse.md](docs/pulse.md)。 +## 能量景观作业 + +这类作业不给出线路:它对三态变量给出能量,并要求一个低能量状态。`wai-quantum landscape run` 在沉降模型的经典模拟器上运行程序。答案给出状态、其精确能量、规模足够小时检查过的每一种排列、统计的工作量,以及按注明来源的费率定价的焦耳,这里从不测量。`wai-quantum landscape compare` 在同一实例上以相同预算运行模拟退火、禁忌搜索和贪心下降,已发布的结果表说明它们在哪里领先。作业形式以及 GPU 或 FPGA 板运行所经由的接缝见扩展 [quantum-landscape](../extensions/quantum-landscape.md),其余内容见 [docs/landscape.md](docs/landscape.md)。 + ## 变分算法与算法库 ```sh runnable diff --git a/wai/wai-quantum/VERSIONING.md b/wai/wai-quantum/VERSIONING.md index fadf1fcdd..5555eb114 100644 --- a/wai/wai-quantum/VERSIONING.md +++ b/wai/wai-quantum/VERSIONING.md @@ -64,6 +64,7 @@ committed as data: | `wai.quantum.set/1`: a set's or a sweep's result and its check | `wai/wai-quantum/schemas/set.v1.json` | | `wai.quantum.provenance/1`: what a result came from | `wai/wai-quantum/schemas/provenance.v1.json` | | `wai.quantum.qec/1`: an error-correction answer (a code, a decode, a threshold) | `wai/wai-quantum/schemas/qec.v1.json` | +| `wai.quantum.landscape/1`: an energy-landscape answer: a run, a temperature sweep or a comparison with classical methods | `wai/wai-quantum/schemas/landscape.v1.json` | | `wai.quantum.distance/1`: a code's distance certificate | `wai/wai-quantum/schemas/distance.v1.json` | | `wai.quantum.mitigation/1`: a job's mitigation | `wai/wai-quantum/schemas/mitigation.v1.json` | | `wai.quantum.answer/1`: the answer view layer, a draft (below) | `wai/wai-quantum/schemas/answer.v1.json` | diff --git a/wai/wai-quantum/docs/README.md b/wai/wai-quantum/docs/README.md index a4b993a99..b90f15e06 100644 --- a/wai/wai-quantum/docs/README.md +++ b/wai/wai-quantum/docs/README.md @@ -42,6 +42,10 @@ run one says why. - [Pulse and dynamics](pulse.md): pulse programs read into schedules on frames, integrated under a master equation with their error bounded and stated, the same bits everywhere, and what their receipts bind. +- [Energy-landscape jobs](landscape.md): an energy over variables of three + states, run on a classical emulator of the settling model, every arrangement + checked where it is small, the joules priced, and classical methods on the same + instance at the same budget. - [Variational algorithms and the algorithm library](variational.md): a job's parameters, exact gradients with their rounding bound, the optimisers, the variational eigensolver, QAOA, and programs for phase and diff --git a/wai/wai-quantum/docs/README.zh-CN.md b/wai/wai-quantum/docs/README.zh-CN.md index 6c8469420..d8b9ab890 100644 --- a/wai/wai-quantum/docs/README.zh-CN.md +++ b/wai/wai-quantum/docs/README.zh-CN.md @@ -25,6 +25,7 @@ - [作业层的拒绝代码](job-refusals.md)。 - [纠错与误差缓解](qec.md):码及其经过认证的距离、解码器、阈值,以及作业中的误差缓解。 - [脉冲与动力学](pulse.md):读入为帧上时序的脉冲程序,在主方程下积分并给出误差上界,在每个平台上比特一致,以及其回执所绑定的内容。 +- [能量景观作业](landscape.md):对三态变量给出能量,在沉降模型的经典模拟器上运行,规模小时检查每一种排列,焦耳按定价给出,并在同一实例上以相同预算运行经典方法。 - [变分算法与算法库](variational.md):作业的参数、带舍入误差界的精确梯度、优化器、变分本征求解器、QAOA,以及相位估计、振幅估计、加法器和 QSVT 的程序,每个都对照参考加以检查。 ## 其他入口 diff --git a/wai/wai-quantum/docs/landscape.md b/wai/wai-quantum/docs/landscape.md new file mode 100644 index 000000000..1699e3f23 --- /dev/null +++ b/wai/wai-quantum/docs/landscape.md @@ -0,0 +1,181 @@ +# Energy-landscape jobs + +A job of this kind gives no circuit. It states an energy over variables of +three states, and asks for a state of low energy: + +```text +E(s) = − Σ_{i three.epuml <<'PROGRAM' +# Three people, one quarrel: 0 and 1 want opposite teams, 1 and 2 the same. +vars 3 +cost 1 +oppose 0 1 +agree 1 2 +solve +PROGRAM +wai-quantum landscape run three.epuml --demo-key # the answer, its energy, every arrangement checked, the work, the priced joules +wai-quantum landscape run three.epuml --seed 7 --temperature 0.5 --demo-key +wai-quantum landscape run three.epuml --temperature 0 --demo-key # the deterministic mode, from random starts +wai-quantum landscape run three.epuml --demo-key --json > job.json +wai-quantum verify job.json +echo 'vars 2 +oppos 0 1 +solve' > typo.epuml +wai-quantum landscape run typo.epuml --demo-key # exits 1 +``` + +The last run is refused: `Line 2: `oppos` is not a statement epuML knows. Did +you mean `oppose`?`. A job that ran exits 0 whatever its verdict, and a program +that cannot run exits 1 with that one sentence. + +The statements are `vars`, `agree`, `oppose`, `couple`, `weight`, `pin`, `lean`, +`field` and `cost`, then `minimize [steps=S]` or `solve [t0=T] [restarts=R] +[steps=S]`. `minimize` is temperature 0 from every variable at 0, and stops when +the state comes to rest. `solve` runs from random starts, cooling to 0, and keeps +the lowest energy found. Lines beginning `#!` are comments to the language that +say what a program means and how to check an answer against the problem it was +written for: an answer can reach the lowest energy and still not solve the +problem. + +## What an answer holds + +- **The label**: what kind of run this is, word for word. +- **What ran it**: the member's id, its class, the kernel, and how far that + kernel is held to the reference. +- **The state and its energy**. The energy is worked out from the program, not + taken from the member, and it is exact. +- **The truth**, up to 16 variables: every arrangement checked, the lowest energy, + how many hold it, and, where the program has a check, how many of those pass it. +- **The verdict**: `solved`, `missed`, `failed` or `unchecked`, with a sentence. +- **The work**: sweeps, accumulates (one read of one coupling into one cell's + field), cell updates and random draws. +- **The joules, priced**: below. +- **The replay digest**. The same program, seed and temperature give the same + digest on any machine. + +## The examples that fail + +```sh runnable +wai-quantum landscape examples # each with what it is there to show +wai-quantum landscape run --example assign-3x3 --demo-key +wai-quantum landscape run --example colour-four --demo-key +wai-quantum landscape run --example piles --demo-key +wai-quantum landscape run --example cold-constraint --demo-key +``` + +Six of the fourteen examples work. Eight do not, on purpose, because they show what this +model cannot do. Three interchangeable options are not three states: the state 0 is "left +out", so colouring a map fails. Sizes are not kept: a coupling is a sign, so two equal piles +fail. Assignment written as sums works for a 2×2 grid and not for a 3×3, where 21 +arrangements share the lowest energy and only 6 are assignments. A constraint at temperature 0 +flips between two states and never rests. Too high a cost makes leaving everything out the +lowest state. A typing mistake is refused. + +A failure is reported as one, and a landscape that does not express its problem fails even +when a run's answer happens to pass the check, because every lowest arrangement is graded. + +## The temperature + +A `solve` with no `t0` starts at the square root of the mean couplings a variable has, and at +least 1. Each answer's `mode_words` says so. + +```sh runnable +wai-quantum landscape sweep --example talks --seeds 5 --demo-key # the same program at each temperature, with the same budget +wai-quantum landscape sweep --example talks --seeds 5 --temperatures 0,0.5,2 --demo-key +``` + +That default is a choice with data behind it. On held-out instances, a start at the largest row +of couplings is far too hot. It is not the best start on every family, and the table's +[`start_temperature`](../../quantum-conformance/landscape/README.md#the-starting-temperature) +shows where it is beaten. + +## Classical methods on the same instance + +```sh runnable +wai-quantum landscape compare --example talks --seeds 5 --demo-key +wai-quantum landscape instance clusters 64 0 > clusters-64.epuml # any instance of the table, as a program +wai-quantum landscape compare clusters-64.epuml --seeds 2 --demo-key +wai-quantum landscape table --quick --out quick > /dev/null # the published table's form, small; the whole table without --quick +``` + +`compare` runs the emulator and simulated annealing, tabu search and greedy descent on the same +three-state energy, each with the same budget of operations a run. One operation is one coupling +read, one cell updated or one move considered, whoever does it. The table is published as it comes +out: simulated annealing leads on the larger instances of it +([the results](../../quantum-conformance/landscape/README.md#the-scale-table)). + +The operation count is not a machine's time, and it says nothing of cells that settle side by side. + +## The joules + +A run here is not metered. Its line is its count of accumulates, multiplied by a rate measured +elsewhere, with the rate's source named beside it: + +| | pJ per accumulate | from | +|---|---|---| +| temperature 0 | about 0.16 | one FPGA board, a dense 64-cell test design at 100 MHz, the slope of power against clock speed | +| above temperature 0 | about 0.41 | the same board: about 0.25 more, from paired readings at one temperature, 0.25, on a design that updates all its cells at once | +| for scale | about 0.03 | a published 2014 figure for one 8-bit integer add in 45 nm logic: a reference point, not a model of any processor, with no term for sampling | + +So the figure says what the work would come to on that board, and nothing of what the machine that +ran the job spent. Both board rates are differences that cancel the board's own draw of about 3.1 W, +so a small job prices low, and every answer says by how much. These are the numbers of one board and +one meter. In the terms of [energy-measurement](../../extensions/energy-measurement.md) the figure's +class is `ModelBased` and the published add's `Estimator`. + +A member with a meter puts its reading beside the priced figures, under `joules.metered`. + +## From code + +```rust compile features=quantum_landscape +use wai_quantum::quantum_landscape::job::run_text; + +fn main() { + let answer = run_text("vars 2\noppose 0 1\nsolve\n", 7).unwrap(); + println!("{} at energy {}: {}", answer["state"], answer["energy"], answer["summary"]); +} +``` + +The module is `wai_quantum::quantum_landscape`, behind the feature `quantum_landscape`, which needs no +dependency beyond the crate's own, so it runs everywhere the engine does. `quantum_engine` and `full` both +turn it on, and `wai` re-exports it. `job::call` takes the job form's JSON request, and +applies every limit of the extension before anything runs. + +In a page, the browser build answers the same request: `QuantumEngine.landscape(op, request)` seals the +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. + +## A member of your own + +A member is what runs the kernels: the trait +[`quantum_landscape::member::Member`](../src/quantum_landscape/member.rs). The reader, the truth, the +energy, the checks, the verdict, the replay digest and the seal stay on the host, and do not trust it: +the host checks every answer a member returns, and finds its parity by running its kernels on +[the conformance set](../../quantum-conformance/landscape/vectors.json) (`member::certify`). A member +says nothing of its own parity. [The extension](../../extensions/quantum-landscape.md#5-members) states +what a member takes and returns, what the host checks, and what each parity takes. diff --git a/wai/wai-quantum/schemas/landscape.v1.json b/wai/wai-quantum/schemas/landscape.v1.json new file mode 100644 index 000000000..968df348f --- /dev/null +++ b/wai/wai-quantum/schemas/landscape.v1.json @@ -0,0 +1,899 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://git.transaction.science/Transaction-Science/open-standards/raw/branch/main/wai/wai-quantum/schemas/landscape.v1.json", + "title": "wai.quantum.landscape/1", + "description": "An energy-landscape answer as every host gives it (quantum_landscape; wai-quantum landscape; the browser build): the request, and the result sealed with the host's key. extensions/quantum-landscape.md states what each member means.", + "type": "object", + "required": [ + "request", + "result" + ], + "properties": { + "request": { + "type": "object", + "description": "The job's request, as given." + }, + "result": { + "$ref": "#/$defs/result" + } + }, + "$defs": { + "result": { + "description": "A run, a temperature sweep or a comparison, with its receipt.", + "anyOf": [ + { + "$ref": "#/$defs/run" + }, + { + "$ref": "#/$defs/sweep" + }, + { + "$ref": "#/$defs/compare" + } + ] + }, + "run": { + "type": "object", + "required": [ + "protocol", + "label", + "member", + "program_digest", + "variables", + "pairs", + "seed", + "mode", + "mode_words", + "temperature", + "temperature_from", + "restarts", + "sweeps", + "state", + "groups", + "energy", + "truth", + "reached", + "declared", + "warnings", + "checks", + "verdict", + "summary", + "work", + "joules", + "state_hash", + "replay" + ], + "properties": { + "engine": { + "type": "object", + "description": "The engine that made the result: the wai-quantum crate's name and version.", + "required": [ + "name", + "version" + ], + "properties": { + "name": { + "const": "wai-quantum" + }, + "version": { + "type": "string" + } + } + }, + "schema": { + "const": "wai.quantum.landscape/1" + }, + "protocol": { + "const": "wai.quantum.landscape/1" + }, + "label": { + "type": "string", + "description": "What this run is, as the member that ran it says it." + }, + "receipt": { + "type": "object", + "description": "The receipt: an Ed25519 signature over the request and this result (wai.quantum.receipt/2)." + }, + "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." + } + } + }, + "program_digest": { + "type": "string", + "description": "BLAKE3 of the program's text." + }, + "title": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "about": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "item": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "expect": { + "anyOf": [ + { + "enum": [ + "works", + "fails" + ] + }, + { + "type": "null" + } + ] + }, + "variables": { + "type": "integer" + }, + "pairs": { + "type": "integer" + }, + "seed": { + "type": "string", + "description": "The seed, as a decimal string: all 64 bits survive." + }, + "mode": { + "enum": [ + "settle", + "quench", + "sample" + ] + }, + "mode_words": { + "type": "string" + }, + "temperature": { + "type": "number" + }, + "temperature_from": { + "anyOf": [ + { + "enum": [ + "landscape", + "program", + "request" + ] + }, + { + "type": "null" + } + ] + }, + "restarts": { + "type": "integer" + }, + "sweeps": { + "type": "integer" + }, + "state": { + "type": "string", + "description": "The answer: one +, - or 0 a variable." + }, + "groups": { + "type": "array", + "items": { + "type": "object", + "required": [ + "name", + "items" + ], + "properties": { + "name": { + "type": "string" + }, + "items": { + "type": "array", + "items": { + "type": "integer" + } + } + } + } + }, + "energy": { + "type": "integer", + "description": "The state's energy, worked out from the program, not by the member." + }, + "truth": { + "anyOf": [ + { + "type": "object", + "required": [ + "arrangements", + "lowest", + "at_lowest", + "at_lowest_passing", + "example" + ], + "properties": { + "arrangements": { + "type": "integer", + "description": "Every arrangement was checked: 3 to the number of variables." + }, + "lowest": { + "type": "number", + "description": "The lowest energy any arrangement has." + }, + "at_lowest": { + "type": "integer", + "description": "How many arrangements have it." + }, + "at_lowest_passing": { + "description": "How many of those pass all the problem's own checks; null when it has none.", + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ] + }, + "example": { + "type": "string", + "description": "One of them, one +, - or 0 a variable." + } + } + }, + { + "type": "null" + } + ] + }, + "reached": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ] + }, + "declared": { + "anyOf": [ + { + "type": "object", + "required": [ + "energy", + "truth", + "reached", + "quantised" + ], + "properties": { + "energy": { + "type": "number" + }, + "truth": { + "anyOf": [ + { + "type": "object", + "required": [ + "arrangements", + "lowest", + "at_lowest", + "at_lowest_passing", + "example" + ], + "properties": { + "arrangements": { + "type": "integer", + "description": "Every arrangement was checked: 3 to the number of variables." + }, + "lowest": { + "type": "number", + "description": "The lowest energy any arrangement has." + }, + "at_lowest": { + "type": "integer", + "description": "How many arrangements have it." + }, + "at_lowest_passing": { + "description": "How many of those pass all the problem's own checks; null when it has none.", + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ] + }, + "example": { + "type": "string", + "description": "One of them, one +, - or 0 a variable." + } + } + }, + { + "type": "null" + } + ] + }, + "reached": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ] + }, + "quantised": { + "type": "object", + "required": [ + "scale", + "declared", + "kept", + "dropped", + "relative_error", + "fits" + ], + "properties": { + "scale": { + "type": "number" + }, + "declared": { + "type": "integer" + }, + "kept": { + "type": "integer" + }, + "dropped": { + "type": "integer" + }, + "relative_error": { + "type": "number" + }, + "fits": { + "type": "boolean" + } + } + } + } + }, + { + "type": "null" + } + ] + }, + "warnings": { + "type": "array", + "items": { + "type": "object", + "required": [ + "code", + "line", + "text" + ], + "properties": { + "code": { + "type": "string" + }, + "line": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ] + }, + "text": { + "type": "string" + } + } + } + }, + "checks": { + "type": "array", + "items": { + "type": "object", + "required": [ + "kind", + "passed", + "detail" + ], + "properties": { + "kind": { + "type": "string" + }, + "passed": { + "type": "boolean" + }, + "detail": { + "type": "string" + } + } + } + }, + "verdict": { + "enum": [ + "solved", + "missed", + "failed", + "unchecked" + ] + }, + "summary": { + "type": "string" + }, + "work": { + "type": "object", + "required": [ + "sweeps", + "accumulates", + "cell_updates", + "draws", + "ended", + "rests" + ], + "properties": { + "sweeps": { + "type": "integer" + }, + "accumulates": { + "type": "integer" + }, + "cell_updates": { + "type": "integer" + }, + "draws": { + "type": "integer" + }, + "ended": { + "type": "string" + }, + "rests": { + "type": "boolean" + } + } + }, + "joules": { + "type": "object", + "description": "How the run's energy in joules is stated: priced, and a meter's reading beside it where a member has one.", + "required": [ + "kind", + "class", + "describes", + "line", + "rate_pj_per_accumulate", + "rate_source", + "pj", + "published_add", + "left_out", + "metered" + ], + "properties": { + "kind": { + "const": "priced" + }, + "class": { + "const": "ModelBased" + }, + "describes": { + "type": "string", + "description": "What the priced figure says the cost of: the board its rate was read from, not the machine that ran the job." + }, + "line": { + "type": "string" + }, + "rate_pj_per_accumulate": { + "type": "number" + }, + "rate_source": { + "type": "string" + }, + "pj": { + "type": "number" + }, + "published_add": { + "type": "object", + "required": [ + "class", + "rate_pj_per_accumulate", + "source", + "pj", + "line" + ], + "properties": { + "class": { + "const": "Estimator" + }, + "rate_pj_per_accumulate": { + "type": "number" + }, + "source": { + "type": "string" + }, + "pj": { + "type": "number" + }, + "line": { + "type": "string" + } + }, + "description": "For scale: a published per-operation figure, the same count at its rate. It models no processor." + }, + "left_out": { + "type": "object", + "required": [ + "board_watts", + "board_hz", + "clocks_at_least", + "pj", + "line" + ], + "properties": { + "board_watts": { + "type": "number" + }, + "board_hz": { + "type": "number" + }, + "clocks_at_least": { + "type": "integer" + }, + "pj": { + "type": "number" + }, + "line": { + "type": "string" + } + } + }, + "metered": { + "description": "A reading from a meter on the member (extensions/energy-measurement.md); null where there is none.", + "anyOf": [ + { + "type": "object", + "required": [ + "joules_micro", + "class" + ], + "properties": { + "joules_micro": { + "type": "integer" + }, + "class": {} + } + }, + { + "type": "null" + } + ] + } + } + }, + "state_hash": { + "type": "string", + "description": "FNV-1a of the state, each cell as the byte s + 1." + }, + "replay": { + "type": "string", + "description": "BLAKE3 of the protocol, the program, the seed, the plan, the state and its energy." + } + } + }, + "sweep": { + "type": "object", + "required": [ + "protocol", + "label", + "member", + "lowest", + "default_temperature", + "rows" + ], + "properties": { + "engine": { + "type": "object", + "description": "The engine that made the result: the wai-quantum crate's name and version.", + "required": [ + "name", + "version" + ], + "properties": { + "name": { + "const": "wai-quantum" + }, + "version": { + "type": "string" + } + } + }, + "schema": { + "const": "wai.quantum.landscape/1" + }, + "protocol": { + "const": "wai.quantum.landscape/1" + }, + "label": { + "type": "string", + "description": "What this run is, as the member that ran it says it." + }, + "receipt": { + "type": "object", + "description": "The receipt: an Ed25519 signature over the request and this result (wai.quantum.receipt/2)." + }, + "member": { + "type": "object", + "required": [ + "id", + "class" + ], + "properties": { + "id": { + "type": "string" + }, + "class": { + "type": "string" + } + } + }, + "lowest": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "default_temperature": { + "type": "number" + }, + "rows": { + "type": "array", + "items": { + "type": "object", + "required": [ + "temperature", + "is_default", + "mode", + "runs", + "reached_lowest", + "passed_checks", + "best_energy", + "mean_energy", + "accumulates_per_run", + "priced_per_run", + "left_out_per_run" + ], + "properties": { + "temperature": { + "type": "number" + }, + "is_default": { + "type": "boolean" + }, + "mode": { + "type": "string" + }, + "runs": { + "type": "integer" + }, + "reached_lowest": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ] + }, + "passed_checks": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ] + }, + "best_energy": { + "type": "integer" + }, + "mean_energy": { + "type": "number" + }, + "accumulates_per_run": { + "type": "integer" + }, + "priced_per_run": { + "type": "string", + "description": "The work alone, priced: a small job prices low (left_out_per_run)." + }, + "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." + } + } + } + } + } + }, + "compare": { + "type": "object", + "required": [ + "protocol", + "label", + "member", + "target", + "target_is_exact", + "arrangements", + "rows" + ], + "properties": { + "engine": { + "type": "object", + "description": "The engine that made the result: the wai-quantum crate's name and version.", + "required": [ + "name", + "version" + ], + "properties": { + "name": { + "const": "wai-quantum" + }, + "version": { + "type": "string" + } + } + }, + "schema": { + "const": "wai.quantum.landscape/1" + }, + "protocol": { + "const": "wai.quantum.landscape/1" + }, + "label": { + "type": "string", + "description": "What this run is, as the member that ran it says it." + }, + "member": { + "type": "object", + "required": [ + "id", + "class" + ], + "properties": { + "id": { + "type": "string" + }, + "class": { + "type": "string" + } + } + }, + "receipt": { + "type": "object", + "description": "The receipt: an Ed25519 signature over the request and this result (wai.quantum.receipt/2)." + }, + "target": { + "type": "integer" + }, + "target_is_exact": { + "type": "boolean" + }, + "arrangements": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ] + }, + "rows": { + "type": "array", + "items": { + "type": "object", + "required": [ + "solver", + "classical", + "runs", + "reached_target", + "passed_checks", + "best_energy", + "operations_per_run", + "accumulates_per_run" + ], + "properties": { + "solver": { + "type": "string" + }, + "classical": { + "type": "boolean" + }, + "runs": { + "type": "integer" + }, + "reached_target": { + "type": "integer" + }, + "passed_checks": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ] + }, + "best_energy": { + "type": "integer" + }, + "operations_per_run": { + "type": "integer" + }, + "accumulates_per_run": { + "type": "integer" + } + } + } + } + } + } + } +} diff --git a/wai/wai-quantum/src/engine/schema.rs b/wai/wai-quantum/src/engine/schema.rs index 151578fe0..5ee9d7f3b 100644 --- a/wai/wai-quantum/src/engine/schema.rs +++ b/wai/wai-quantum/src/engine/schema.rs @@ -72,6 +72,9 @@ pub const QEC: &str = "wai.quantum.qec/1"; /// A pulse answer: a pulse program's schedule, or a pulse job checked by /// running it again (`engine::pulse`). A pulse run is a job. pub const PULSE: &str = "wai.quantum.pulse/1"; +/// An energy-landscape answer: a run, a temperature sweep or a comparison with +/// classical methods (`quantum_landscape`, `extensions/quantum-landscape.md`). +pub const LANDSCAPE: &str = crate::quantum_landscape::PROTOCOL; /// A code's distance certificate (`quantum_distance`). pub const DISTANCE: &str = "wai.quantum.distance/1"; /// A job's mitigation, in its result (`engine::mitigate`). @@ -103,7 +106,7 @@ pub const DEPRECATIONS: &str = "https://git.transaction.science/Transaction-Scie pub const ID_BASE: &str = "https://git.transaction.science/Transaction-Science/open-standards/raw/branch/main/wai/wai-quantum/schemas/"; /// This crate's schemas: each file's name and its text. -pub const FILES: [(&str, &str); 24] = [ +pub const FILES: [(&str, &str); 25] = [ ("job-request.v1.json", include_str!("../../schemas/job-request.v1.json")), ("job-result.v1.json", include_str!("../../schemas/job-result.v1.json")), ("job.v1.json", include_str!("../../schemas/job.v1.json")), @@ -119,6 +122,7 @@ pub const FILES: [(&str, &str); 24] = [ ("provenance.v1.json", include_str!("../../schemas/provenance.v1.json")), ("qec.v1.json", include_str!("../../schemas/qec.v1.json")), ("pulse.v1.json", include_str!("../../schemas/pulse.v1.json")), + ("landscape.v1.json", include_str!("../../schemas/landscape.v1.json")), ("distance.v1.json", include_str!("../../schemas/distance.v1.json")), ("mitigation.v1.json", include_str!("../../schemas/mitigation.v1.json")), ("answer.v1.json", include_str!("../../schemas/answer.v1.json")), @@ -155,6 +159,7 @@ pub fn names() -> Value { "provenance": PROVENANCE, "qec": QEC, "pulse": PULSE, + "landscape": LANDSCAPE, "distance": DISTANCE, "mitigation": MITIGATION, "answer": ANSWER, diff --git a/wai/wai-quantum/src/lib.rs b/wai/wai-quantum/src/lib.rs index 2b3be6f3a..4631c67be 100644 --- a/wai/wai-quantum/src/lib.rs +++ b/wai/wai-quantum/src/lib.rs @@ -206,6 +206,10 @@ pub mod quantum_ldpc; #[cfg(feature = "quantum_qio")] pub mod quantum_qio; +// Energy-landscape jobs: a program in epuML, settled on a member of the fabric. +#[cfg(feature = "quantum_landscape")] +pub mod quantum_landscape; + #[cfg(feature = "quantum_uf")] pub mod quantum_uf; diff --git a/wai/wai-quantum/src/quantum_landscape.rs b/wai/wai-quantum/src/quantum_landscape.rs new file mode 100644 index 000000000..eaf4973cb --- /dev/null +++ b/wai/wai-quantum/src/quantum_landscape.rs @@ -0,0 +1,163 @@ +//! Energy-landscape jobs — `wai.quantum.landscape`. +//! +//! A job of this kind does not give a circuit. It states an energy over +//! variables of three states and asks for a state of low energy: +//! +//! ```text +//! E(s) = − Σ_{i Stream { + Stream { seed, j: 0 } + } + + pub fn next(&mut self) -> u64 { + self.j += 1; + out(self.seed, self.j) + } + + pub fn below(&mut self, n: u64) -> u64 { + self.next() % n + } + + /// A number in `[0, 1)`. + pub fn unit(&mut self) -> f64 { + (self.next() >> 11) as f64 * (1.0 / 9_007_199_254_740_992.0) + } +} + +/// What a solver found, and what it spent. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Found { + pub state: Vec, + pub energy: i64, + pub accumulates: u64, + pub moves: u64, +} + +impl Found { + pub fn operations(&self) -> u64 { + self.accumulates + self.moves + } +} + +struct Search<'a> { + w: &'a Workload, + l: &'a Landscape, + state: Vec, + h: Vec, + energy: i64, + best_state: Vec, + best: i64, + accumulates: u64, + moves: u64, +} + +fn random_state(n: usize, s: &mut Stream) -> Vec { + (0..n).map(|_| s.below(3) as i8 - 1).collect() +} + +impl<'a> Search<'a> { + fn new(w: &'a Workload, l: &'a Landscape, s: &mut Stream) -> Search<'a> { + let state = random_state(w.n(), s); + let h = (0..w.n()).map(|i| w.field(&state, i)).collect(); + let energy = l.energy(&state); + Search { w, l, best_state: state.clone(), best: energy, state, h, energy, accumulates: w.nonzeros(), moves: 0 } + } + + fn restart(&mut self, s: &mut Stream) { + self.state = random_state(self.w.n(), s); + self.h = (0..self.w.n()).map(|i| self.w.field(&self.state, i)).collect(); + self.energy = self.l.energy(&self.state); + self.accumulates += self.w.nonzeros(); + self.note(); + } + + fn note(&mut self) { + if self.energy < self.best { + self.best = self.energy; + self.best_state.copy_from_slice(&self.state); + } + } + + #[inline] + fn delta(&self, i: usize, new: i8) -> i64 { + let old = self.state[i]; + -i64::from(new - old) * self.h[i] + self.w.cost() * (i64::from(new.abs()) - i64::from(old.abs())) + } + + fn apply(&mut self, i: usize, new: i8, delta: i64) { + let d = i64::from(new - self.state[i]); + self.state[i] = new; + self.energy += delta; + for (j, c) in self.w.row(i) { + self.h[j] += c * d; + } + self.accumulates += self.w.degree(i); + self.note(); + } + + fn spent(&self) -> u64 { + self.accumulates + self.moves + } + + fn done(self) -> Found { + Found { state: self.best_state, energy: self.best, accumulates: self.accumulates, moves: self.moves } + } +} + +/// The other two values of a cell, in a fixed order. +#[inline] +fn others(v: i8) -> [i8; 2] { + match v { + -1 => [0, 1], + 0 => [-1, 1], + _ => [-1, 0], + } +} + +/// Simulated annealing: one move a cell a sweep, accepted by the Metropolis +/// rule, with geometric cooling. It restarts until the budget is gone, about +/// four times, and keeps the best state it saw. +pub fn simulated_annealing(w: &Workload, l: &Landscape, budget: u64, seed: u64) -> Found { + let mut r = Stream::new(seed); + let mut s = Search::new(w, l, &mut r); + let n = w.n(); + let widest = (0..n).map(|i| w.degree(i)).max().unwrap_or(0) as f64; + let hot = (1.5 * widest.sqrt()).max(w.cost() as f64).max(1.0); + let cold = 0.15f64; + let per_sweep = (n as u64 + w.nonzeros() / 4).max(1); + let sweeps = (budget / 4 / per_sweep).clamp(4, 1 << 24); + let ratio = exp(ln(cold / hot) / (sweeps - 1) as f64); + 'all: loop { + let mut t = hot; + for _ in 0..sweeps { + let beta = 1.0 / t; + for i in 0..n { + if s.spent() >= budget { + break 'all; + } + s.moves += 1; + let new = others(s.state[i])[r.below(2) as usize]; + let d = s.delta(i, new); + if d <= 0 || r.unit() < exp(-beta * d as f64) { + s.apply(i, new, d); + } + } + t *= ratio; + } + if s.spent() >= budget { + break; + } + s.restart(&mut r); + } + s.done() +} + +/// Tabu search: take the best move that is not forbidden, then forbid moving +/// that cell for a while. A move that passes the best energy seen is allowed +/// anyway. It restarts after a long stall. +pub fn tabu_search(w: &Workload, l: &Landscape, budget: u64, seed: u64) -> Found { + let mut r = Stream::new(seed); + let mut s = Search::new(w, l, &mut r); + let n = w.n(); + let wide = n as u64; + let mut until = vec![0u64; n]; + let (mut iter, mut last_gain, mut local_best) = (0u64, 0u64, s.energy); + while s.spent() < budget && n > 0 { + iter += 1; + let mut chosen: Option<(usize, i8, i64)> = None; + let mut ties = 0u64; + for i in 0..n { + for new in others(s.state[i]) { + s.moves += 1; + let d = s.delta(i, new); + if until[i] > iter && s.energy + d >= s.best { + continue; + } + match chosen { + Some((_, _, c)) if d > c => {} + Some((_, _, c)) if d == c => { + ties += 1; + if r.below(ties + 1) == 0 { + chosen = Some((i, new, d)); + } + } + _ => { + ties = 0; + chosen = Some((i, new, d)); + } + } + } + } + if let Some((i, new, d)) = chosen { + s.apply(i, new, d); + until[i] = iter + 1 + wide / 4 + r.below(wide / 4 + 3); + } + if s.energy < local_best { + local_best = s.energy; + last_gain = iter; + } + if iter - last_gain > 20 * wide.max(4) { + s.restart(&mut r); + local_best = s.energy; + last_gain = iter; + until.iter_mut().for_each(|u| *u = 0); + } + } + s.done() +} + +/// Repeated greedy descent: from a random state, move any cell that lowers the +/// energy until none does, then start again until the budget is gone. +pub fn greedy_descent(w: &Workload, l: &Landscape, budget: u64, seed: u64) -> Found { + let mut r = Stream::new(seed); + let mut s = Search::new(w, l, &mut r); + let n = w.n(); + while s.spent() < budget && n > 0 { + let mut improved = false; + for i in 0..n { + let mut pick = None; + let mut lowest = 0; + for new in others(s.state[i]) { + s.moves += 1; + let d = s.delta(i, new); + if d < lowest { + lowest = d; + pick = Some(new); + } + } + if let Some(new) = pick { + s.apply(i, new, lowest); + improved = true; + } + } + if !improved { + if s.spent() >= budget { + break; + } + s.restart(&mut r); + } + } + s.done() +} + +/// A solver's signature. +pub type Solver = fn(&Workload, &Landscape, u64, u64) -> Found; + +/// The solvers, by name. +pub const SOLVERS: [(&str, Solver); 3] = [("simulated annealing", simulated_annealing), ("tabu search", tabu_search), ("greedy descent", greedy_descent)]; + +#[cfg(test)] +mod tests { + use super::*; + use crate::quantum_landscape::table::{Family, instance}; + use crate::quantum_landscape::truth::exhaustive; + + #[test] + fn each_solver_finds_the_exact_lowest_on_small_instances_with_a_generous_budget() { + for family in Family::ALL { + for seed in 0..3u64 { + let (l, _) = instance(family, 10, seed); + let truth = exhaustive(&l).unwrap(); + let w = l.lower(); + for (name, solve) in SOLVERS { + let f = solve(&w, &l, 200_000, seed + 1); + assert_eq!(f.energy as f64, truth.lowest, "{name} on {} seed {seed}", family.name()); + assert_eq!(l.energy(&f.state) as f64, truth.lowest, "{name}: the state really has that energy"); + } + } + } + } + + #[test] + fn a_solver_keeps_to_its_budget_and_repeats_itself() { + let (l, _) = instance(Family::SparseGlass, 32, 3); + let w = l.lower(); + let budget = 20_000; + for (name, solve) in SOLVERS { + let a = solve(&w, &l, budget, 9); + assert_eq!(a, solve(&w, &l, budget, 9), "{name}: the same seed, the same result"); + // A move in flight can run past the budget by one cell's couplings, a pass by one scan. + assert!(a.operations() <= budget + w.nonzeros() + 4 * w.n() as u64, "{name} spent {}", a.operations()); + assert!(a.operations() >= budget / 2, "{name} spent only {}", a.operations()); + assert_eq!(l.energy(&a.state), a.energy, "{name}"); + } + } + + /// With no budget a solver does not find the lowest, so the test above means something. + #[test] + fn with_no_budget_a_solver_misses() { + let (l, _) = instance(Family::DenseGlass, 12, 2); + let truth = exhaustive(&l).unwrap(); + let w = l.lower(); + let misses = (0..20u64).filter(|&seed| simulated_annealing(&w, &l, 1, seed).energy as f64 > truth.lowest).count(); + assert!(misses >= 15, "only {misses} of 20 missed with no budget"); + } +} diff --git a/wai/wai-quantum/src/quantum_landscape/conformance.rs b/wai/wai-quantum/src/quantum_landscape/conformance.rs new file mode 100644 index 000000000..55552a048 --- /dev/null +++ b/wai/wai-quantum/src/quantum_landscape/conformance.rs @@ -0,0 +1,510 @@ +//! The vectors a member is held to (`quantum-conformance/landscape/vectors.json`): +//! what `extensions/quantum-landscape.md` §7 calls the conformance set. +//! +//! - `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. +//! - `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. +//! - `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. +//! - `jobs`: whole requests, what their answers hold, refusals included: the +//! worked examples and the rules a reader must follow. + +use super::EXAMPLES; +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::settle::{Outcome, state_hash}; +use serde_json::{Value, json}; + +fn pairs_json(l: &Landscape) -> Value { + let n = l.len(); + let mut out = Vec::new(); + for i in 0..n { + for j in (i + 1)..n { + if l.coupling(i, j) != 0 { + out.push(json!([i, j, l.coupling(i, j)])); + } + } + } + Value::Array(out) +} + +fn landscape_json(l: &Landscape) -> Value { + json!({ "n": l.len(), "couplings": pairs_json(l), "field": l.field(), "cost": l.cost() }) +} + +fn bits(x: f64) -> String { + format!("{:016x}", x.to_bits()) +} + +fn pattern(seed: u64, n: usize) -> Vec { + (0..n) + .map(|i| { + let mut x = seed.wrapping_add(i as u64).wrapping_mul(0x9E37_79B9_7F4A_7C15); + x ^= x >> 29; + if x & 1 == 0 { 1 } else { -1 } + }) + .collect() +} + +fn stored(p: &[i8]) -> Landscape { + let mut l = Landscape::new(p.len()); + for i in 0..p.len() { + for j in (i + 1)..p.len() { + l.couple(i, j, p[i] * p[j]); + } + } + l +} + +fn landscape(n: usize, couplings: &[(usize, usize, i8)], field: &[(usize, i64)], cost: i64) -> Landscape { + let mut l = Landscape::new(n); + for &(a, b, w) in couplings { + l.couple(a, b, w); + } + for &(i, b) in field { + l.set_field(i, b); + } + l.set_cost(cost); + l +} + +// ── settle ──────────────────────────────────────────────────────────────────────────────── + +/// A settle the reference runs, and every member's settle kernel must run the same way. +pub struct SettleCase { + pub name: String, + pub landscape: Landscape, + pub start: Vec, + pub bound: u32, +} + +pub fn settle_cases() -> Vec { + let mut out = Vec::new(); + let mut case = |name: &str, l: &Landscape, start: &[i8], bound: u32| out.push(SettleCase { name: name.into(), landscape: l.clone(), start: start.to_vec(), bound }); + for n in [4usize, 8, 16, 32] { + let p = pattern(0xC0FFEE, n); + let mut q = p.clone(); + for k in 0..(n / 8).max(1) { + let i = (k * 5 + 1) % n; + q[i] = -q[i]; + } + case(&format!("recall/n={n}"), &stored(&p), &q, 40); + } + let two = landscape(2, &[(0, 1, -1)], &[], 0); + case("two-cycle", &two, &[1, 1], 20); + case("at-rest", &two, &[1, -1], 20); + let chain = landscape(6, &[(0, 1, 1), (1, 2, 1), (2, 3, 1), (3, 4, 1), (4, 5, 1)], &[(0, 3)], 0); + case("bound", &chain, &[0; 6], 3); + case("chain-arrives", &chain, &[0; 6], 20); + case("one-sweep-bound", &chain, &[0; 6], 1); + let costed = landscape(5, &[(0, 1, 1), (1, 2, 1), (0, 2, 1), (2, 3, -1), (3, 4, -1)], &[(4, 2)], 1); + case("field-and-cost", &costed, &[1, 1, 1, 0, 0], 20); + case("zero-and-minus-at-the-start", &costed, &[0, -1, 0, 1, -1], 20); + let wide = landscape(4, &[(0, 1, 1), (1, 2, -1), (2, 3, 1)], &[(0, 1_099_511_627_776), (3, -1_099_511_627_775)], 1_099_511_627_776); + 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); + out +} + +/// Whether a member's settle kernel gives every `settle` vector exactly. +pub fn settle_vectors_hold(m: &dyn Member) -> bool { + settle_cases().iter().all(|c| { + let w = c.landscape.lower(); + 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) + }) +} + +fn settle_json() -> Vec { + settle_cases() + .iter() + .map(|c| { + let o = c.landscape.lower().settle(&c.start, c.bound); + json!({ + "name": c.name, + "landscape": landscape_json(&c.landscape), + "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)) }, + }) + }) + .collect() +} + +// ── the exponential, the schedule and the cut points ────────────────────────────────────── + +/// The arguments the exponential is pinned at: those the cut points use, and the edges. +fn exp_arguments() -> Vec { + let mut x = vec![0.0, -0.0, 1.0, -1.0, 0.5, -0.5, 1e-9, -1e-9, 3.7e-9, -745.13, -745.2, 709.7, 709.8, -708.4, -1022.0 * core::f64::consts::LN_2]; + for k in 1..=200u32 { + let v = -(f64::from(k) * 0.371_f64 + f64::from(k % 7) * 0.013_7); + x.push(v); + x.push(-v / 3.1); + } + for t in TEMPERATURES { + for d in [-1.0f64, -2.0, -3.0, -5.0, -8.0, -17.0, -40.0, -80.0] { + x.push((1.0 / t) * d); + } + } + x +} + +const TEMPERATURES: [f64; 10] = [0.25, 0.3, 0.7, 1.0, 1.3, 1.8708286933869707, 2.9, 7.1, 13.7, 25.0]; + +fn cells_json() -> Vec { + let mut out = Vec::new(); + for h in [-40i64, -17, -5, -3, -1, 0, 1, 2, 3, 5, 9, 17, 40] { + for c in [0i64, 1, 2, 3, 8] { + for t in TEMPERATURES { + let (a, b) = cuts(h, c, 1.0 / t); + out.push(json!({ "field": h, "cost": c, "temperature": t, "temperature_bits": bits(t), "cut_a": a, "cut_b": b })); + } + } + } + out +} + +fn schedule_json() -> Vec { + let mut out = Vec::new(); + for t0 in [0.3f64, 0.7, 1.3, 1.8708286933869707, 2.449489742783178, 2.9, 7.1, 13.7, 63.0] { + for sweeps in [1u32, 2, 7, 40, 41] { + out.push(json!({ "t0": t0, "t0_bits": bits(t0), "sweeps": sweeps, "temperatures_bits": schedule(t0, sweeps).iter().map(|&t| bits(t)).collect::>() })); + } + } + out +} + +// ── chains ──────────────────────────────────────────────────────────────────────────────── + +/// 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 seed: u64, + pub restarts: u64, +} + +impl ChainCase { + pub fn chains(&self) -> Vec { + (0..self.restarts) + .map(|r| { + let c = chain_seed(self.seed, r); + Chain { start: start(c, self.landscape.len()), seed: c } + }) + .collect() + } +} + +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)); + let wide = landscape(5, &[(0, 1, 1), (1, 2, -1), (2, 3, 1), (3, 4, -1)], &[(0, 40), (2, -17), (4, 9)], 8); + 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 +} + +/// Whether a member's sampling kernel gives every `chains` vector exactly, every case in one call. +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 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) + }) +} + +fn chains_json() -> Vec { + chain_cases() + .iter() + .map(|c| { + let w = c.landscape.lower(); + let temperatures = schedule(c.t0, c.sweeps); + json!({ + "name": c.name, + "landscape": landscape_json(&c.landscape), + "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::>(), + }) + }) + .collect() +} + +// ── the statistical battery ─────────────────────────────────────────────────────────────── + +/// How many chains each battery workload runs, and the test its final states are held to. +pub const BATTERY_CHAINS: u32 = 20_000; +/// A bin expected to hold fewer final states than this is pooled with the others like it. +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. +pub struct BatteryCase { + pub name: &'static str, + pub landscape: Landscape, + pub temperatures: Vec, +} + +impl BatteryCase { + /// 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. + pub fn exact(&self) -> Vec { + let l = &self.landscape; + 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(); + let low = energies.iter().copied().min().unwrap_or(0); + let t = self.equilibrium(); + let weights: Vec = energies.iter().map(|&e| crate::repro::exp(-((e - low) as f64) / t)).collect(); + let z: f64 = weights.iter().sum(); + let p: Vec = weights.iter().map(|w| w / z).collect(); + if self.temperatures.last().is_some_and(|&t| t <= 0.0) { + let w = l.lower(); + let mut q = vec![0.0; p.len()]; + for (k, s) in all.iter().enumerate() { + q[index_of(&w.sweep(s))] += p[k]; + } + q + } else { + p + } + } + + pub fn chains(&self, k: usize) -> Vec { + (0..u64::from(BATTERY_CHAINS)) + .map(|r| { + let c = chain_seed(BATTERY_SEED + k as u64, r); + Chain { start: start(c, self.landscape.len()), seed: c } + }) + .collect() + } +} + +fn state_of(mut k: usize, n: usize) -> Vec { + (0..n) + .map(|_| { + let s = (k % 3) as i8 - 1; + k /= 3; + s + }) + .collect() +} + +fn index_of(s: &[i8]) -> usize { + s.iter().rev().fold(0, |acc, &v| acc * 3 + (v + 1) as usize) +} + +pub fn battery() -> Vec { + let constant = |t: f64, sweeps: usize| vec![t; sweeps]; + let mut cooling = vec![6.0; 8]; + cooling.extend(vec![0.7; 24]); + let mut quench = vec![1.3; 31]; + quench.push(0.0); + 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 }, + ] +} + +/// What one workload of the battery found. +#[derive(Clone, Debug, PartialEq)] +pub struct BatteryResult { + pub name: &'static str, + pub chi_square: f64, + pub degrees: usize, + pub critical: f64, + pub passed: bool, +} + +/// Pearson's statistic of `counts` against `expected` (probabilities) over +/// `total` draws, every bin expected below [`BATTERY_POOL_BELOW`] pooled into +/// one, and the critical value `df + 5·√(2·df)`. +pub fn chi_square(counts: &[u64], expected: &[f64], total: u64) -> (f64, usize, f64) { + let (mut stat, mut bins) = (0.0f64, 0usize); + let (mut pooled_o, mut pooled_e) = (0.0f64, 0.0f64); + for (&o, &p) in counts.iter().zip(expected) { + let e = p * total as f64; + if e < BATTERY_POOL_BELOW { + pooled_o += o as f64; + pooled_e += e; + } else { + stat += (o as f64 - e) * (o as f64 - e) / e; + bins += 1; + } + } + if pooled_e > 0.0 { + stat += (pooled_o - pooled_e) * (pooled_o - pooled_e) / pooled_e; + bins += 1; + } else if pooled_o > 0.0 { + stat = f64::INFINITY; + } + let df = bins.saturating_sub(1).max(1); + (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. +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()]; + 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; + } + } + _ => 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 } + }) + .collect() +} + +fn battery_json() -> Value { + json!({ + "chains": BATTERY_CHAINS, + "seed": BATTERY_SEED.to_string(), + "pool_below": BATTERY_POOL_BELOW, + "critical": "df + 5·sqrt(2·df), df the bins after pooling, less one", + "workloads": battery().iter().map(|c| json!({ + "name": c.name, + "landscape": landscape_json(&c.landscape), + "temperatures": c.temperatures, + "state_order": "the sum of (s_i + 1) · 3^i", + "exact": c.exact(), + })).collect::>(), + }) +} + +// ── jobs ────────────────────────────────────────────────────────────────────────────────── + +/// Requests beyond the worked examples, each pinning a rule of the text. +fn rule_requests() -> Vec<(&'static str, Value)> { + let talks = EXAMPLES.iter().find(|(n, _)| *n == "talks").map(|(_, t)| *t).unwrap_or(""); + let chain = EXAMPLES.iter().find(|(n, _)| *n == "chain").map(|(_, t)| *t).unwrap_or(""); + vec![ + ("minimize-above-zero", json!({ "program": chain, "seed": "1", "temperature": "0.5" })), + ("a-temperature-in-text", json!({ "program": talks, "seed": "1", "temperature": "1.8708286933869707" })), + ("pin-size", json!({ "program": "vars 3\nagree 0 1\nagree 1 2\nfield 1 2\ncost 1\npin 0 -\nminimize\n", "seed": "1" })), + ("weights-rescaled", json!({ "program": "vars 3\nfield 0 3\ncost 1\nweight 0 1 2.5\nweight 1 2 -0.5\nweight 0 2 1.0\nsolve\n", "seed": "1" })), + ("two-cycle-state", json!({ "program": "vars 2\nagree 0 1\nfield 0 1\nfield 1 -1\nminimize\n", "seed": "1" })), + ("settle-digest", json!({ "program": "vars 3\nagree 0 1\noppose 1 2\nfield 0 1\nminimize steps=5\n", "seed": "9" })), + ("leading-zeros", json!({ "program": "vars 02\nfield 01 -007\nsolve t0=0.50 restarts=003\n", "seed": "1" })), + ("a-plus-sign", json!({ "program": "vars +3\nsolve\n", "seed": "1" })), + ("an-exponent-temperature", json!({ "program": "vars 2\nsolve t0=1e-3\n", "seed": "1" })), + ("a-byte-order-mark", json!({ "program": "\u{feff}vars 2\nsolve\n", "seed": "1" })), + ("a-no-break-space", json!({ "program": "vars\u{a0}2\nsolve\n", "seed": "1" })), + ("no-steps", json!({ "program": "vars 1\nsolve steps=0\n", "seed": "1" })), + ("too-many-restarts", json!({ "program": "vars 1\nsolve restarts=100001\n", "seed": "1" })), + ("the-most-negative-field", json!({ "program": "vars 1\nfield 0 -9223372036854775808\nsolve\n", "seed": "1" })), + ("a-tiny-weight", json!({ "program": "vars 2\ncost 1\nweight 0 1 1e-300\nsolve\n", "seed": "1" })), + ("a-pin-too-large", json!({ "program": "vars 2\nfield 1 1099511627776\npin 0 +\nminimize\n", "seed": "1" })), + ("a-temperature-string-with-an-exponent", json!({ "program": "vars 1\nsolve\n", "seed": "1", "temperature": "1e-400" })), + ] +} + +fn job_json(name: &str, request: Value) -> Value { + let expect = match job::call("run", &request) { + Ok(a) => json!({ "state": a["state"], "energy": a["energy"], "verdict": a["verdict"], "mode": a["mode"], "restarts": a["restarts"], "sweeps": a["sweeps"], "accumulates": a["work"]["accumulates"], "draws": a["work"]["draws"], "replay": a["replay"] }), + Err(r) => json!({ "refused": r.plain(), "line": r.line }), + }; + json!({ "name": name, "request": request, "expect": expect }) +} + +fn jobs_json() -> Vec { + let mut out: Vec = EXAMPLES.iter().map(|(name, text)| job_json(name, json!({ "program": text, "seed": "1" }))).collect(); + out.extend(rule_requests().into_iter().map(|(name, req)| job_json(name, req))); + out +} + +/// The conformance set, as the file holds it. +pub fn vectors() -> Value { + json!({ + "protocol": super::PROTOCOL, + "about": "What a member is held to: extensions/quantum-landscape.md, section 7. Rebuilt by `wai-quantum landscape vectors`; a test holds this file to the code.", + "settle": settle_json(), + "exp": exp_arguments().iter().map(|&x| json!([bits(x), bits(crate::repro::exp(x))])).collect::>(), + "schedule": schedule_json(), + "cells": cells_json(), + "chains": chains_json(), + "statistical": battery_json(), + "jobs": jobs_json(), + }) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::quantum_landscape::member::CpuReference; + + /// The reference's own sampling kernel, `anneal_with`, passes the battery, + /// and the exact distributions sum to 1. + #[test] + fn the_reference_kernel_samples_every_workload_s_exact_law() { + for c in battery() { + let p = c.exact(); + assert!((p.iter().sum::() - 1.0).abs() < 1e-12, "{}", c.name); + } + for r in run_battery(&CpuReference) { + assert!(r.passed, "{r:?}"); + } + } + + #[test] + fn a_state_s_index_is_its_digits() { + for k in 0..81 { + assert_eq!(index_of(&state_of(k, 4)), k); + } + assert_eq!(index_of(&[1, -1]), 2); + } +} diff --git a/wai/wai-quantum/src/quantum_landscape/energy.rs b/wai/wai-quantum/src/quantum_landscape/energy.rs new file mode 100644 index 000000000..4def69434 --- /dev/null +++ b/wai/wai-quantum/src/quantum_landscape/energy.rs @@ -0,0 +1,414 @@ +//! The landscape: what a program declares, and its energy. +//! +//! ```text +//! E(s) = − Σ_{i, + field: Vec, + cost: i64, +} + +/// Where the cost sits against the couplings ([`Landscape::regime`]). +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Regime { + /// Below the band: nothing to warn of. + Open, + /// In the narrow band where settling gets much harder as a problem grows. + NarrowBand, + /// At or above half the couplings a cell has: leaving everything out may be lowest. + CostDominates, +} + +impl Landscape { + /// `n` variables, with no coupling, no field and no cost. + pub fn new(n: usize) -> Landscape { + Landscape { n, couplings: vec![0; n * n], field: vec![0; n], cost: 0 } + } + + pub fn len(&self) -> usize { + self.n + } + + pub fn is_empty(&self) -> bool { + self.n == 0 + } + + /// Couple two different variables: `+1` they agree, `−1` they oppose, `0` neither. + pub fn couple(&mut self, i: usize, j: usize, w: i8) -> &mut Self { + assert!(i < self.n && j < self.n && i != j, "a coupling joins two different variables"); + assert!((-1..=1).contains(&w), "a coupling is −1, 0 or +1"); + self.couplings[i * self.n + j] = w; + self.couplings[j * self.n + i] = w; + self + } + + /// The coupling between two variables. + pub fn coupling(&self, i: usize, j: usize) -> i8 { + self.couplings[i * self.n + j] + } + + /// Set a variable's field. + pub fn set_field(&mut self, i: usize, b: i64) -> &mut Self { + self.field[i] = b; + self + } + + /// Set the cost of every variable that is not 0. + pub fn set_cost(&mut self, c: i64) -> &mut Self { + assert!(c >= 0, "the cost is not negative"); + self.cost = c; + self + } + + pub fn couplings(&self) -> &[i8] { + &self.couplings + } + + pub fn field(&self) -> &[i64] { + &self.field + } + + pub fn cost(&self) -> i64 { + self.cost + } + + /// The coupled pairs. + pub fn pairs(&self) -> u64 { + self.couplings.iter().filter(|&&w| w != 0).count() as u64 / 2 + } + + /// The energy of a state, exactly. + pub fn energy(&self, s: &[i8]) -> i64 { + assert_eq!(s.len(), self.n, "one state a variable"); + let n = self.n; + let mut e = 0i64; + for i in 0..n { + let si = i64::from(s[i]); + if si != 0 { + for j in (i + 1)..n { + e -= i64::from(self.couplings[i * n + j]) * si * i64::from(s[j]); + } + } + e += -self.field[i] * si + self.cost * si.abs(); + } + e + } + + /// What the kernels read. + pub fn lower(&self) -> Workload { + Workload::new(self.n, &self.couplings, &self.field, self.cost) + } + + /// The most couplings any one variable has, and at least 1. + pub fn coupling_scale(&self) -> i64 { + let n = self.n; + (0..n).map(|i| self.couplings[i * n..(i + 1) * n].iter().filter(|&&w| w != 0).count() as i64).max().unwrap_or(0).max(1) + } + + /// The temperature a `solve` starts from when the program names none: + /// the square root of the mean number of couplings a variable has, and at + /// least 1. A cell's field among couplings of random sign is of that + /// size, so a start there lets a cell move against its field often and + /// not always. The fields and the cost are left out: a strong field holds + /// its variable at any moderate temperature, and should not heat the rest. + pub fn default_temperature(&self) -> f64 { + if self.n == 0 { + return 1.0; + } + (2.0 * self.pairs() as f64 / self.n as f64).sqrt().max(1.0) + } + + /// Where the cost sits against the most couplings a variable has. For + /// dense couplings of one pattern the model orders through a continuous + /// transition while `cost / couplings < ln(4)/3`, and above that, up to a + /// half, through one with a barrier that grows with the problem; from a + /// half on, the all-0 state is lowest. Elsewhere this is a rule of thumb, + /// and it does not read the fields, so a program its fields hold together + /// is warned of and then solves. + pub fn regime(&self) -> Regime { + let r = self.cost as f64 / self.coupling_scale() as f64; + if r >= 0.5 { + Regime::CostDominates + } else if r >= 0.462_098_120_373_297 { + Regime::NarrowBand + } else { + Regime::Open + } + } +} + +/// Real-valued weights between variables, as a program declares them with `weight`. +#[derive(Clone, Debug, PartialEq)] +pub struct Weights { + n: usize, + weights: Vec, + field: Vec, + cost: i64, +} + +/// What quantising weights to signs kept and lost. +#[derive(Clone, Debug, PartialEq)] +pub struct Quantised { + /// The mean size of the declared weights. + pub scale: f64, + pub declared: u64, + /// Kept as a sign: larger than half the mean size. + pub kept: u64, + /// Dropped: at most half the mean size. + pub dropped: u64, + /// `‖W − scale·T‖ / ‖W‖` over the declared weights, `T` their signs kept. + pub relative_error: f64, +} + +impl Quantised { + /// Whether the signs carry the weights: a relative error under 0.35. A + /// rule of thumb, there to warn before a run, not to promise an answer. + pub fn fits(&self) -> bool { + self.relative_error < 0.35 + } +} + +impl Weights { + pub fn new(n: usize, field: &[i64], cost: i64) -> Weights { + Weights { n, weights: vec![0.0; n * n], field: field.to_vec(), cost } + } + + pub fn len(&self) -> usize { + self.n + } + + pub fn is_empty(&self) -> bool { + self.n == 0 + } + + pub fn set(&mut self, i: usize, j: usize, w: f64) { + assert!(i < self.n && j < self.n && i != j && w.is_finite()); + self.weights[i * self.n + j] = w; + self.weights[j * self.n + i] = w; + } + + /// The energy of a state on the weights as declared. + pub fn energy(&self, s: &[i8]) -> f64 { + let n = self.n; + let mut quad = 0.0; + for i in 0..n { + for j in (i + 1)..n { + quad += self.weights[i * n + j] * f64::from(s[i]) * f64::from(s[j]); + } + } + let rest: i64 = (0..n).map(|i| -self.field[i] * i64::from(s[i]) + self.cost * i64::from(s[i]).abs()).sum(); + -quad + rest as f64 + } + + /// The landscape of signs, with the fields and the cost in the same + /// units (divided by the scale and rounded), and what that kept and lost. + pub fn quantise(&self) -> Result<(Landscape, Quantised), String> { + let n = self.n; + let (mut sum, mut declared) = (0.0f64, 0u64); + for i in 0..n { + for j in (i + 1)..n { + let w = self.weights[i * n + j]; + if w != 0.0 { + sum += w.abs(); + declared += 1; + } + } + } + let scale = if declared > 0 { sum / declared as f64 } else { 1.0 }; + let mut l = Landscape::new(n); + // Each field and the cost divided by the scale, rounded half away from + // zero, and held to the bound every field is held to. + let rescaled = |v: i64, what: &str| -> Result { + let r = (v as f64 / scale).round(); + if !r.is_finite() || r.abs() > super::MAX_FIELD as f64 { + return Err(format!("Quantising the weights divides {what} by their mean size, about {scale:e}, and that gives a number above {}: the weights are too small beside the fields and the cost.", super::MAX_FIELD)); + } + Ok(r as i64) + }; + for i in 0..n { + l.set_field(i, rescaled(self.field[i], &format!("the field of variable {i}"))?); + } + l.set_cost(rescaled(self.cost, "the cost")?.max(0)); + let (mut kept, mut dropped, mut num, mut den) = (0u64, 0u64, 0.0f64, 0.0f64); + for i in 0..n { + for j in (i + 1)..n { + let w = self.weights[i * n + j]; + if w == 0.0 { + continue; + } + den += w * w; + let t = if w.abs() > scale / 2.0 { + let sign = if w > 0.0 { 1 } else { -1 }; + l.couple(i, j, sign); + kept += 1; + f64::from(sign) + } else { + dropped += 1; + 0.0 + }; + num += (w - scale * t) * (w - scale * t); + } + } + let relative_error = if den > 0.0 { (num / den).sqrt() } else { 0.0 }; + Ok((l, Quantised { scale, declared, kept, dropped, relative_error })) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn states(n: usize) -> Vec> { + let mut all = vec![vec![]]; + for _ in 0..n { + all = all.into_iter().flat_map(|s| (-1i8..=1).map(move |v| [s.clone(), vec![v]].concat())).collect(); + } + all + } + + fn random(n: usize, seed: u64) -> Landscape { + let mut l = Landscape::new(n); + let mut k = 0u64; + let mut next = || { + k += 1; + super::super::sample::out(seed, k) + }; + for i in 0..n { + for j in (i + 1)..n { + l.couple(i, j, [-1i8, 0, 0, 1][(next() % 4) as usize]); + } + l.set_field(i, (next() % 7) as i64 - 3); + } + l.set_cost((next() % 3) as i64); + l + } + + /// The energy is the formula, term by term, on small cases worked by hand. + #[test] + fn the_energy_is_the_formula() { + let mut l = Landscape::new(3); + l.couple(0, 1, 1).couple(1, 2, -1).set_field(0, 2).set_cost(1); + // s = (+, +, −): −(1·1·1) − (−1·1·−1) − 2·1 + 1·3 = −1 − 1 − 2 + 3 = −1. + assert_eq!(l.energy(&[1, 1, -1]), -1); + assert_eq!(l.energy(&[0, 0, 0]), 0); + // s = (−, 0, +): no pair is active together; −2·(−1) + 1·2 = 4. + assert_eq!(l.energy(&[-1, 0, 1]), 4); + assert_eq!(l.pairs(), 2); + } + + /// A state at which no cell would move is one no single change improves, + /// and the other way round, away from a tie: on random landscapes, over + /// every state of five variables. + #[test] + fn a_resting_state_is_one_no_single_change_improves() { + let (mut rest, mut moved) = (0, 0); + for seed in 1..60u64 { + let l = random(5, seed); + let w = l.lower(); + for s in states(5) { + let tie = (0..5).any(|i| w.field(&s, i).abs() == l.cost()); + if tie { + continue; + } + let e = l.energy(&s); + let improvable = (0..5).any(|i| { + (-1i8..=1).any(|v| { + let mut t = s.clone(); + t[i] = v; + l.energy(&t) < e + }) + }); + assert_eq!(w.rests(&s), !improvable, "seed {seed}, state {s:?}"); + if improvable { moved += 1 } else { rest += 1 } + } + } + assert!(rest > 20 && moved > 1000, "both kinds were seen: {rest} at rest, {moved} not"); + } + + #[test] + fn the_default_temperature_is_the_root_of_the_mean_couplings() { + let mut l = Landscape::new(4); + assert_eq!(l.default_temperature(), 1.0); + for (i, j) in [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)] { + l.couple(i, j, 1); + } + // Three couplings a variable: √3. A field or a cost does not move it. + assert_eq!(l.default_temperature(), 3f64.sqrt()); + l.set_field(0, 1000).set_cost(50); + assert_eq!(l.default_temperature(), 3f64.sqrt()); + } + + #[test] + fn the_regime_reads_the_cost_against_the_couplings() { + let mut l = Landscape::new(41); + for j in 1..41 { + l.couple(0, j, 1); + } + assert_eq!(l.coupling_scale(), 40); + assert_eq!(l.set_cost(18).regime(), Regime::Open); + assert_eq!(l.set_cost(19).regime(), Regime::NarrowBand); + assert_eq!(l.set_cost(20).regime(), Regime::CostDominates); + } + + /// The rows give the same energy as the declaration, state for state. + #[test] + fn the_lowered_energy_is_the_declared_one() { + for seed in 0..6 { + let l = random(5, seed); + let w = l.lower(); + for s in states(5) { + assert_eq!(w.energy(&s), l.energy(&s), "{s:?}"); + } + } + } + + #[test] + fn quantising_keeps_signs_drops_the_small_and_says_so() { + let mut w = Weights::new(3, &[0, 0, 0], 0); + w.set(0, 1, -1.0); + w.set(0, 2, -9.0); + w.set(1, 2, -30.0); + let (l, q) = w.quantise().unwrap(); + // The mean size is 40/3; half of it is 6.67: −1 is dropped, the others keep their sign. + assert_eq!((q.declared, q.kept, q.dropped), (3, 2, 1)); + assert_eq!((l.coupling(0, 1), l.coupling(0, 2), l.coupling(1, 2)), (0, -1, -1)); + assert!(!q.fits() && q.relative_error > 0.5, "{}", q.relative_error); + // Weights of one size are their signs exactly. + let mut even = Weights::new(3, &[4, 0, 0], 2); + even.set(0, 1, 2.0); + even.set(1, 2, -2.0); + let (l, q) = even.quantise().unwrap(); + assert_eq!((q.kept, q.dropped, q.relative_error), (2, 0, 0.0)); + assert!(q.fits()); + // The field and the cost are carried in the same units. + assert_eq!((l.field()[0], l.cost()), (2, 1)); + // And the declared energy is the declared formula. + assert_eq!(even.energy(&[1, 1, -1]), -2.0 - 2.0 - 4.0 + 6.0); + } +} diff --git a/wai/wai-quantum/src/quantum_landscape/job.rs b/wai/wai-quantum/src/quantum_landscape/job.rs new file mode 100644 index 000000000..74775b6c2 --- /dev/null +++ b/wai/wai-quantum/src/quantum_landscape/job.rs @@ -0,0 +1,501 @@ +//! The job form of `extensions/quantum-landscape.md`: one JSON request, one JSON +//! answer. The command line and the browser build both come through here. +//! +//! ```text +//! request: { "program": "vars 2\noppose 0 1\nsolve\n", "seed": "7", "temperature": "0.5" } +//! ``` +//! +//! `seed` is a whole number or a decimal string, so that all 64 bits survive a +//! language whose numbers are doubles. `temperature` is a number, read to the +//! nearest double, or a decimal string `[0-9]+(.[0-9]+)?`, which every reader +//! reads to the same bits. Both are optional. +//! +//! Every quantity a job can ask for is bounded before any of it runs, the same +//! way on every host: the program's length and every number in it as it is read +//! ([`super::program`]), then the operations the whole job can take +//! ([`run::job_operations`], at most [`MAX_OPERATIONS`]) and the memory it can +//! hold ([`run::job_bytes`], at most [`MAX_JOB_BYTES`], or less where a host asks). +//! Only then is the program lowered and its truth worked out. +//! +//! [`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::program::{Program, Refusal, decimal, read}; +use super::run::{self, DEFAULT_SEED, Job, Plan}; +use super::{EXAMPLES, MAX_JOB_BYTES, MAX_OPERATIONS, PROTOCOL}; +use serde_json::{Value, json}; + +/// How many seeds a request may ask for. +pub const MAX_SEEDS: u64 = 100_000; + +/// How many temperatures a sweep may ask for. +pub const MAX_TEMPERATURES: usize = 64; + +/// A request's seed. +fn seed_of(request: &Value) -> Result { + match request.get("seed") { + None | Some(Value::Null) => Ok(DEFAULT_SEED), + Some(Value::Number(n)) => n.as_u64().ok_or_else(|| Refusal::general("A seed must be a whole number, 0 or above, of at most 20 digits.")), + Some(Value::String(s)) => Some(s) + .filter(|s| (1..=20).contains(&s.len()) && s.bytes().all(|b| b.is_ascii_digit())) + .and_then(|s| s.parse().ok()) + .ok_or_else(|| Refusal::general("A seed must be a whole number, 0 or above, of at most 20 digits.")), + Some(_) => Err(Refusal::general("A seed must be a whole number, 0 or above, of at most 20 digits.")), + } +} + +/// A temperature: a number, or a decimal string `[0-9]+(.[0-9]+)?`. +fn temperature_from(v: &Value) -> Option { + match v { + Value::String(s) => decimal(s), + Value::Number(n) => n.as_f64(), + _ => None, + } +} + +fn temperature_of(request: &Value) -> Result, Refusal> { + match request.get("temperature") { + None | Some(Value::Null) => Ok(None), + Some(v) => temperature_from(v).map(Some).ok_or_else(|| Refusal::general("A temperature is a number, or a decimal like \"0.5\" in a string.")), + } +} + +fn seeds_of(request: &Value, default: u64) -> Result { + let k = match request.get("seeds") { + None | Some(Value::Null) => default, + Some(v) => v.as_u64().ok_or_else(|| Refusal::general("The number of seeds must be a whole number."))?, + }; + if k == 0 || k > MAX_SEEDS { + return Err(Refusal::general(format!("The number of seeds must be between 1 and {}.", super::price::group(MAX_SEEDS)))); + } + Ok(k as u32) +} + +fn temperatures_of(request: &Value, p: &Program) -> Result, Refusal> { + let t = match request.get("temperatures") { + None | Some(Value::Null) => run::default_temperatures(p), + Some(v) => v.as_array().and_then(|a| a.iter().map(temperature_from).collect::>>()).ok_or_else(|| Refusal::general("`temperatures` is a list of numbers."))?, + }; + if t.is_empty() || t.len() > MAX_TEMPERATURES { + return Err(Refusal::general(format!("A sweep takes between 1 and {MAX_TEMPERATURES} temperatures."))); + } + Ok(t) +} + +/// Answer a request on the CPU reference. +pub fn call(op: &str, request: &Value) -> Result { + call_certified(&Certified::reference(), op, request, MAX_JOB_BYTES) +} + +/// Run a program's text at a seed on the CPU reference: [`call`]'s `run`. +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 { + 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()); + } + 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."))); + } + let source = request.get("program").and_then(Value::as_str).ok_or_else(|| Refusal::general("A job gives its program as text, under `program`."))?; + let p = read(source)?; + let seed = seed_of(request)?; + let temperature = temperature_of(request)?; + // 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) + } + "compare" => { + let seeds = u64::from(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) + } + _ => { + let seeds = u64::from(seeds_of(request, 20)?); + (vec![(Plan::of(&p, temperature)?, seeds)], seeds) + } + }; + let operations = run::job_operations(&p, &runs); + if operations > MAX_OPERATIONS { + return Err(Refusal { + line: (p.run_line > 0).then_some(p.run_line), + message: format!( + "This job could take up to {} operations, counting every arrangement checked and every run, and the limit is {}. Lower `restarts` or `steps`, or ask for fewer seeds.", + super::price::group(operations), + super::price::group(MAX_OPERATIONS) + ), + }); + } + let bytes = run::job_bytes(&p, lines); + let most = max_bytes.min(MAX_JOB_BYTES); + 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), + _ => { + // 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)?; + 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], + )); + } + Ok(json!({ "protocol": PROTOCOL, "seeds": seeds, "lines": lines })) + } + } +} + +/// The worked examples: each with what it is there to show. +pub fn examples() -> Value { + let note = |text: &str, key: &str| -> Value { + let prefix = format!("#! {key} "); + text.lines().find_map(|l| l.strip_prefix(prefix.as_str())).map_or(Value::Null, |v| json!(v.trim())) + }; + json!({ + "protocol": PROTOCOL, + "examples": EXAMPLES.iter().map(|(name, text)| json!({ + "name": name, + "title": note(text, "title"), + "about": note(text, "about"), + "expect": note(text, "expect"), + "program": text, + })).collect::>(), + }) +} + +/// A digest of what every worked example gives, at its own temperature, at 0 +/// and at 0.5, over `seeds` seeds: every answer's digest, state, energy, verdict +/// and work, and every refusal. Pinned, it says a platform gives the same bits. +pub fn corpus_digest(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)] { + let req = json!({ "program": text, "seed": 0, "seeds": seeds, "temperature": temperature }); + h.update(name.as_bytes()); + h.update(&[0]); + match call("replay", &req) { + Ok(a) => h.update(a["lines"].as_str().unwrap_or("").as_bytes()), + Err(r) => h.update(r.plain().as_bytes()), + }; + } + } + h.finalize().to_hex().to_string() +} + +/// The answer named as this engine's, sealed by `key` in the form every job's +/// receipt takes, so `engine::job::verify` checks it. +#[cfg(all(feature = "quantum_engine", feature = "quantum_receipt"))] +pub fn seal(request: &Value, mut result: Value, key: &ed25519_dalek::SigningKey) -> Value { + crate::engine::schema::stamp_as(&mut result, PROTOCOL); + let r = crate::engine::receipt::seal(key, request, &result); + result["receipt"] = r; + json!({ "request": request, "result": result }) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn example(name: &str) -> &'static str { + EXAMPLES.iter().find(|(n, _)| *n == name).unwrap().1 + } + + fn run_example(text: &str, seed: u64) -> Result { + call("run", &json!({ "program": text, "seed": seed })) + } + + /// The digest of `corpus_digest(20)`: every worked example at three + /// temperatures. The same bits on every platform that runs this test. + const CORPUS_20: &str = "a84fb1e13592511ce7f9ebd2dde1af711bcc16786fc2f08d79032cab2e93158a"; + + #[test] + fn every_platform_gives_the_same_bits() { + assert_eq!(corpus_digest(20), CORPUS_20); + } + + #[test] + fn the_examples_that_work_solve_at_every_seed_tried() { + for (name, text) in EXAMPLES { + if !text.contains("#! expect works") { + continue; + } + for seed in 1..=20u64 { + let a = run_example(text, seed).unwrap_or_else(|e| panic!("{name}: {e}")); + assert_eq!(a["verdict"], "solved", "{name}, seed {seed}: {}", a["summary"]); + } + } + } + + #[test] + fn the_examples_that_fail_fail_at_every_seed_tried() { + for (name, text) in EXAMPLES { + if !text.contains("#! expect fails") { + continue; + } + for seed in 1..=20u64 { + match run_example(text, seed) { + Err(r) => assert!(r.line.is_some(), "{name}: a refused program names its line"), + Ok(a) => assert!(a["verdict"] == "failed" || a["verdict"] == "missed", "{name}, seed {seed}: {}", a["summary"]), + } + } + } + } + + #[test] + fn every_example_says_what_it_is_there_to_show() { + for e in examples()["examples"].as_array().unwrap() { + assert!(e["title"].is_string() && e["about"].is_string(), "{e}"); + assert!(e["expect"] == "works" || e["expect"] == "fails", "{e}"); + } + } + + /// Why each failing example fails is pinned, so a failure cannot change its reason unseen. + #[test] + fn each_failure_fails_for_its_stated_reason() { + let a = run_example(example("assign-3x3"), 1).unwrap(); + assert_eq!((a["truth"]["at_lowest"].as_u64(), a["truth"]["at_lowest_passing"].as_u64()), (Some(21), Some(6))); + assert!(a["summary"].as_str().unwrap().contains("does not express the problem")); + for name in ["colour-six", "colour-four"] { + let a = run_example(example(name), 1).unwrap(); + assert_eq!(a["truth"]["at_lowest_passing"], 0, "{name}"); + } + let a = run_example(example("piles"), 1).unwrap(); + assert!(a["warnings"].as_array().unwrap().iter().any(|w| w["code"] == "sizes-lost"), "{a}"); + assert_eq!(a["declared"]["reached"], false); + assert_eq!(a["declared"]["truth"]["lowest"], -130.0); + let a = run_example(example("cold-constraint"), 1).unwrap(); + assert_eq!(a["work"]["ended"], "kept flipping between two states"); + let a = run_example(example("cold-start"), 1).unwrap(); + assert_eq!((a["energy"].as_i64(), a["verdict"].as_str()), (Some(0), Some("missed"))); + assert!(a["warnings"].as_array().unwrap().iter().any(|w| w["code"] == "cold-start")); + let a = run_example(example("too-costly"), 1).unwrap(); + assert_eq!((a["state"].as_str().map(|s| s.contains('+')), a["verdict"].as_str()), (Some(false), Some("failed"))); + let r = run_example(example("typo"), 1).unwrap_err(); + assert_eq!(r.plain(), "Line 6: `oppos` is not a statement epuML knows. Did you mean `oppose`?"); + } + + #[test] + fn the_talks_are_solved_against_every_arrangement() { + let a = run_example(example("talks"), 1).unwrap(); + assert_eq!((a["truth"]["arrangements"].as_u64(), a["truth"]["lowest"].as_f64(), a["truth"]["at_lowest"].as_u64()), (Some(531_441), Some(-10.0), Some(2))); + assert_eq!(a["reached"], true); + assert_eq!(a["groups"].as_array().unwrap().len(), 3); + } + + /// An answer says what it is, and never that a run's joules were measured. + #[test] + fn an_answer_says_what_it_is() { + for (name, text) in EXAMPLES { + for t in [None, Some(0.0), Some(0.5)] { + let Ok(a) = call("run", &json!({ "program": text, "temperature": t })) else { continue }; + assert_eq!(a["label"], crate::quantum_landscape::LABEL, "{name}"); + assert_eq!(a["joules"]["kind"], "priced", "{name}"); + assert_eq!(a["joules"]["metered"], Value::Null, "{name}"); + assert_eq!(a["member"]["id"], "cpu:reference", "{name}"); + let text = a.to_string().replace(crate::quantum_landscape::LABEL, ""); + assert!(!text.contains('±'), "{name}"); + // A rate's source says it was measured; a run's own lines never do. + for key in ["/joules/line", "/joules/published_add/line", "/joules/left_out/line", "/summary"] { + assert!(!a.pointer(key).unwrap().as_str().unwrap().to_lowercase().contains("measured"), "{name}: {key}"); + } + } + } + } + + /// The energy in an answer is the program's, worked out again here from the state. + #[test] + fn an_answer_s_energy_is_recomputed_from_its_program_alone() { + for name in ["talks", "clusters", "blocks"] { + let a = run_example(example(name), 3).unwrap(); + let p = read(example(name)).unwrap(); + let state: Vec = a["state"].as_str().unwrap().chars().map(|c| match c { '+' => 1, '-' => -1, _ => 0 }).collect(); + assert_eq!(Some(p.landscape.energy(&state)), a["energy"].as_i64(), "{name}"); + } + } + + #[test] + fn a_bad_request_is_refused_plainly() { + for (op, req) in [ + ("run", json!({})), + ("run", json!({ "program": 3 })), + ("fly", json!({ "program": "vars 1\nsolve\n" })), + ("run", json!({ "program": "vars 1\nsolve\n", "seed": "x" })), + ("run", json!({ "program": "vars 1\nsolve\n", "seed": -1 })), + ("run", json!({ "program": "vars 1\nsolve\n", "temperature": "hot" })), + ("run", json!({ "program": "vars 1\nsolve\n", "protocol": "another/9" })), + ("sweep", json!({ "program": "vars 1\nsolve\n", "seeds": 0 })), + ("sweep", json!({ "program": "vars 1\nsolve\n", "temperatures": "warm" })), + ] { + assert!(call(op, &req).is_err(), "{op} {req}"); + } + } + + /// Every digit of a double, given as text, is that double: the same run as the default start. + #[test] + fn a_temperature_in_text_keeps_every_bit() { + let own = run_example(example("talks"), 1).unwrap(); + let t = own["temperature"].as_f64().unwrap(); + let again = call("run", &json!({ "program": example("talks"), "seed": 1, "temperature": t.to_string() })).unwrap(); + assert_eq!(own["replay"], again["replay"]); + for bad in ["warm", "NaN", "inf", "-1"] { + assert!(call("run", &json!({ "program": example("talks"), "temperature": bad })).is_err(), "{bad}"); + } + } + + // ── review of #664: every quantity a job can ask for is bounded before work starts ── + + fn refused(src: &str) -> bool { + call("run", &json!({ "program": src })).is_err() + } + + #[test] + fn steps_and_restarts_are_bounded_before_anything_runs() { + for src in ["vars 1\nsolve steps=0\n", "vars 1\nminimize steps=0\n", "vars 2\nsolve restarts=100001 steps=1\n", "vars 2\nsolve steps=1000001\n"] { + assert!(refused(src), "{src}"); + } + assert!(!refused("vars 2\nsolve restarts=100000 steps=1\n")); + } + + #[test] + fn quantised_weights_stay_within_the_field_bound() { + assert!(refused("vars 2\ncost 1\nweight 0 1 1e-300\nsolve\n")); + assert!(refused("vars 2\nfield 0 1099511627776\nweight 0 1 0.5\nsolve\n")); + } + + #[test] + fn the_most_negative_whole_number_is_refused() { + assert!(refused("vars 1\nfield 0 -9223372036854775808\nsolve\n")); + assert!(refused("vars 1\ncost -9223372036854775808\nsolve\n")); + assert!(refused("#! check balance -9223372036854775808\nvars 1\nsolve\n")); + } + + #[test] + fn the_grammar_is_ascii_and_strict() { + for src in ["vars +3\nsolve\n", "\u{feff}vars 2\nsolve\n", "vars\u{a0}2\nsolve\n", "vars 2\nsolve t0=1e-400\n", "vars 2\nsolve t0=+1\n", "vars 2\nfield 0 +1\nsolve\n", "vars 2\ncouple 0 1 +1\nsolve\n", "vars 2\nsolve t0=.5\n"] { + assert!(refused(src), "{src:?}"); + } + assert!(!refused("vars 02\nfield 1 -007\nsolve t0=0.50\n"), "leading zeros are allowed"); + } + + #[test] + fn checks_are_bounded() { + let many_lines: String = (0..17).map(|_| "#! check matches 0\n").collect::() + "vars 1\nsolve\n"; + assert!(refused(&many_lines)); + let pairs: Vec<&str> = (0..100_000).map(|_| "0-1").collect(); + assert!(refused(&format!("#! check different {}\nvars 4\nsolve\n", pairs.join(" ")))); + assert!(refused("#! check balance 4611686018427387904 4611686018427387904 4611686018427387904 4611686018427387904\nvars 4\nagree 0 1\nagree 1 2\nagree 2 3\nsolve\n")); + } + + #[test] + fn a_temperature_string_follows_the_grammar() { + for t in ["1e-400", "0x10", "", "+1", " 0.5", "inf", "1e2"] { + assert!(call("run", &json!({ "program": "vars 1\nsolve\n", "temperature": t })).is_err(), "{t:?}"); + } + assert!(call("run", &json!({ "program": "vars 1\nsolve\n", "temperature": "0.5" })).is_ok()); + } + + /// A sum of many large energies is taken in 128 bits, so the mean is the mean. + #[test] + fn the_sweep_mean_does_not_overflow() { + let n = 100; + let mut src = format!("vars {n}\n"); + for i in 0..n { + src.push_str(&format!("field {i} 1099511627776\n")); + } + src.push_str("solve restarts=1 steps=2\n"); + let seeds = 84_000u64; + let a = call("sweep", &json!({ "program": src, "seeds": seeds, "temperatures": [0.5] })).unwrap(); + // Every field pulls its cell to +, at any temperature this cold, and the last sweep is at 0. + let want = -(n as f64) * 1_099_511_627_776.0; + assert_eq!(a["rows"][0]["best_energy"].as_f64(), Some(want)); + assert_eq!(a["rows"][0]["mean_energy"].as_f64(), Some(want), "{}", a["rows"][0]); + } + + #[test] + fn a_seed_keeps_all_64_bits() { + let a = call("run", &json!({ "program": example("chain"), "seed": "18446744073709551615" })).unwrap(); + assert_eq!(a["seed"], "18446744073709551615"); + } + + /// Junk in, a plain refusal out: never a panic, and a line where a line is given. + #[test] + fn junk_never_panics() { + let words = [ + "vars", "agree", "oppose", "couple", "pin", "lean", "field", "cost", "solve", "minimize", "weight", "price", "#!", "#! check", "different", "0-1", "permutation", "+", "-", "0", "1", "2", "3", "17", "-5", + "999999999999", "t0=1", "t0=-3", "restarts=2", "steps=3", "steps=0", "x", "=", "nan", "1e308", "#", "\t", "check", "matches", "balance", "title", "modeled", "measured", "0.5", "-0", "4096", "4097", + ]; + let mut r = crate::quantum_landscape::classical::Stream::new(0x1234); + for _ in 0..4000 { + let mut src = String::new(); + if r.below(4) != 0 { + src.push_str(&format!("vars {}\n", r.below(6) + 1)); + } + for _ in 0..r.below(8) { + let k = r.below(5) + 1; + let line: Vec<&str> = (0..k).map(|_| words[r.below(words.len() as u64) as usize]).collect(); + src.push_str(&line.join(" ")); + src.push('\n'); + } + if r.below(3) != 0 { + src.push_str("solve restarts=2 steps=4\n"); + } + let _ = call("run", &json!({ "program": src })); + } + } + + #[cfg(all(feature = "quantum_engine", feature = "quantum_receipt"))] + #[test] + fn a_sealed_answer_verifies_and_follows_its_schema() { + use crate::engine::schema::{LANDSCAPE, Registry}; + let key = ed25519_dalek::SigningKey::from_bytes(&[3; 32]); + let registry = Registry::engine(); + for (op, req) in [ + ("run", json!({ "program": example("talks"), "seed": "7" })), + ("run", json!({ "program": example("chain") })), + ("run", json!({ "program": example("piles") })), + ("run", json!({ "program": example("selection"), "temperature": 0.5 })), + ("sweep", json!({ "program": example("clusters"), "seeds": 2 })), + ("compare", json!({ "program": example("colour-four"), "seeds": 2 })), + ] { + let body = seal(&req, call(op, &req).unwrap(), &key); + assert_eq!(body["result"]["schema"], LANDSCAPE); + let (ok, _) = crate::engine::job::verify(&body).unwrap(); + assert!(ok, "{op}: the receipt verifies"); + let problems = registry.check("landscape.v1.json", &body); + assert!(problems.is_empty(), "{op}: {problems:?}\n{body}"); + let mut tampered = body.clone(); + tampered["result"]["protocol"] = json!("another/1"); + assert!(!crate::engine::job::verify(&tampered).unwrap().0, "{op}: a changed answer does not verify"); + } + } +} diff --git a/wai/wai-quantum/src/quantum_landscape/member.rs b/wai/wai-quantum/src/quantum_landscape/member.rs new file mode 100644 index 000000000..d0505c96b --- /dev/null +++ b/wai/wai-quantum/src/quantum_landscape/member.rs @@ -0,0 +1,311 @@ +//! What runs a landscape job: the seam between a job and a device. +//! +//! A [`Member`] runs the two kernels, nothing else. The job's reader, the +//! exhaustive truth, the energy of the state a member returns, the problem's +//! checks, the replay digest and the seal all stay on the host, and do not +//! trust the member: +//! +//! - **What it returns is checked** ([`check_settle`], [`check_chain`]): the +//! state's length and cells; the work its sweeps come to; that a fixed point +//! it reports rests, that a two-cycle it reports has period 2, that a bound +//! it reports was reached; that a chain ran every sweep of its schedule and +//! drew once a cell at each sweep above 0. The energy of an answer is worked +//! out from the program, so a device can be wrong about a state only by +//! returning a worse one, and it cannot price itself low. +//! - **What it has earned is found, not declared** ([`certify`]). A member +//! says nothing of its own parity. The host runs its kernels on the +//! 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::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. +//! - [`Parity::Uncertified`]: what passed neither. An answer can still come +//! from it when it is asked for by name ([`named`]), and says so. +//! +//! A member says of itself what kind of run it is ([`Member::label`]) and may +//! read a meter ([`Member::meter`]); a reading stands beside the priced figures +//! and does not replace them. + +use super::conformance; +use super::settle::{Outcome, Term, Workload}; +use serde_json::{Value, json}; + +/// The deterministic kernel's name. +pub const KERNEL_SETTLE: &str = "wai.quantum.landscape/settle"; +/// The sampling kernel's name. +pub const KERNEL_SAMPLE: &str = "wai.quantum.landscape/sample"; + +/// What a member has earned for a kernel. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Parity { + Reference, + BitExact, + Statistical, + Uncertified, +} + +impl Parity { + pub fn label(self) -> &'static str { + match self { + Parity::Reference => "reference", + Parity::BitExact => "bit-exact", + Parity::Statistical => "statistical", + Parity::Uncertified => "uncertified", + } + } +} + +/// 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. +#[derive(Clone, Debug, PartialEq)] +pub struct Metered { + pub joules_micro: u64, + pub class: Value, +} + +/// One chain to run: where it starts and the seed its draws come from. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Chain { + pub start: Vec, + pub seed: u64, +} + +/// A device that runs the kernels. +pub trait Member { + /// The member's name, `class:name`: `gpu:…`, `fpga:…`. `name` is lower-case + /// letters, digits, `.`, `_` and `-`, at most 64 characters in all. + fn id(&self) -> String; + /// Its class, a label of the fabric's: `cpu`, `gpu`, `fpga`. + fn class(&self) -> &'static str; + /// What every answer it gives says of itself. + fn label(&self) -> String; + /// Settle from `start`, for at most `bound` sweeps, stopping at a fixed + /// point or a period-2 cycle ([`Workload::settle`]). + fn settle(&self, w: &Workload, start: &[i8], bound: u32) -> Result; + /// Run chains, one sweep a temperature of `temperatures`, every sweep run + /// ([`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. + fn meter(&self) -> Option { + None + } +} + +/// The reference: this crate's kernels, on this machine's CPU. +#[derive(Clone, Copy, Debug, Default)] +pub struct CpuReference; + +impl Member for CpuReference { + fn id(&self) -> String { + "cpu:reference".into() + } + + fn class(&self) -> &'static str { + "cpu" + } + + fn label(&self) -> String { + super::LABEL.into() + } + + fn settle(&self, w: &Workload, start: &[i8], bound: u32) -> Result { + Ok(w.settle(start, bound)) + } + + fn chains(&self, w: &Workload, chains: &[Chain], temperatures: &[f64]) -> Result, String> { + Ok(chains.iter().map(|c| super::sample::anneal_with(w, &c.start, temperatures, c.seed)).collect()) + } +} + +/// A member with the parity the host found for each kernel. +pub struct Certified<'a> { + member: &'a dyn Member, + settle: Parity, + sample: Parity, +} + +impl Certified<'static> { + /// The CPU reference, at parity `reference` for both kernels. + pub fn reference() -> Certified<'static> { + static REFERENCE: CpuReference = CpuReference; + Certified { member: &REFERENCE, settle: Parity::Reference, sample: Parity::Reference } + } +} + +impl<'a> Certified<'a> { + pub fn member(&self) -> &'a dyn Member { + self.member + } + + /// The parity the host found for a kernel. + pub fn parity(&self, kernel: &str) -> Parity { + if kernel == KERNEL_SETTLE { self.settle } else { self.sample } + } +} + +/// A member's id is `class:name`, its class one of the fabric's and the one it +/// gives, and `cpu:reference` is the reference's alone. +pub fn check_id(m: &dyn Member) -> Result<(), String> { + let id = m.id(); + let class = m.class(); + if !matches!(class, "cpu" | "gpu" | "fpga") { + return Err(format!("a member's class is cpu, gpu or fpga, not `{class}`")); + } + let Some(name) = id.strip_prefix(class).and_then(|r| r.strip_prefix(':')) else { + return Err(format!("a member's id is its class, a colon and a name, like `{class}:board-1`, not `{id}`")); + }; + let good = |c: char| c.is_ascii_lowercase() || c.is_ascii_digit() || matches!(c, '.' | '_' | '-'); + if name.is_empty() || id.len() > 64 || !name.chars().all(good) { + return Err(format!("a member's name is lower-case letters, digits, `.`, `_` and `-`, at most 64 characters with its class: `{id}` is not")); + } + if id == "cpu:reference" { + return Err("`cpu:reference` is the reference's id, and no other member's".into()); + } + Ok(()) +} + +/// A member asked for by name, its kernels uncertified: an answer can come from +/// it, and says so. +pub fn named(m: &dyn Member) -> Result, String> { + check_id(m)?; + Ok(Certified { member: m, settle: Parity::Uncertified, sample: Parity::Uncertified }) +} + +/// Certify a member: its settle kernel on every `settle` vector, its sampling +/// kernel on every `chains` vector and, where those differ, on the battery of +/// `statistical` workloads. +pub fn certify(m: &dyn Member) -> Result, String> { + check_id(m)?; + let settle = if conformance::settle_vectors_hold(m) { Parity::BitExact } else { Parity::Uncertified }; + let sample = if conformance::chain_vectors_hold(m) { + Parity::BitExact + } else if conformance::run_battery(m).iter().all(|r| r.passed) { + Parity::Statistical + } else { + Parity::Uncertified + }; + Ok(Certified { member: m, settle, sample }) +} + +fn check_cells(o: &Outcome, n: usize) -> Result<(), String> { + if o.state.len() != n { + return Err(format!("the member returned {} cells for {n} variables", o.state.len())); + } + if o.state.iter().any(|s| !(-1..=1).contains(s)) { + return Err("the member returned a cell that is not −1, 0 or +1".into()); + } + Ok(()) +} + +/// What a member returned for a settle of at most `bound` sweeps, held to the +/// job: its cells, the work its sweeps come to, no draws, and the ending it +/// reports: a fixed point rests, a two-cycle has period 2 exactly, and a bound +/// was reached. How many sweeps it took to get there can be checked only by +/// running it again; a bit-exact certificate is what vouches for that. +pub fn check_settle(o: &Outcome, w: &Workload, bound: u32) -> Result<(), String> { + check_cells(o, w.n())?; + if o.sweeps == 0 || o.sweeps > bound { + return Err(format!("the member reported {} sweeps, and a settle runs between 1 and {bound}", o.sweeps)); + } + let work = w.nonzeros().saturating_mul(u64::from(o.sweeps)); + if o.accumulates != work || o.draws != 0 { + return Err(format!("the member reported {} accumulates and {} draws, and {} deterministic sweeps are {work} and none", o.accumulates, o.draws, o.sweeps)); + } + match o.term { + Term::FixedPoint if !w.rests(&o.state) => Err("the member reported a fixed point that does not rest".into()), + Term::TwoCycle => { + let once = w.sweep(&o.state); + if o.sweeps < 2 || once == o.state || w.sweep(&once) != o.state { Err("the member reported a two-cycle that is not one".into()) } else { Ok(()) } + } + Term::Bound if o.sweeps != bound => Err(format!("the member reported running out of sweeps after {} of {bound}", o.sweeps)), + _ => Ok(()), + } +} + +/// What a member returned for one chain of `temperatures`, held to the job: +/// its cells, every sweep run, the work and the draws that schedule comes to, +/// and the ending, which is a fixed point exactly when the state rests. +pub fn check_chain(o: &Outcome, w: &Workload, temperatures: &[f64]) -> Result<(), String> { + let n = w.n(); + check_cells(o, n)?; + if o.sweeps as usize != temperatures.len() { + return Err(format!("the member ran {} sweeps of a chain, and the job asks for {}", o.sweeps, temperatures.len())); + } + let work = w.nonzeros().saturating_mul(temperatures.len() as u64); + let draws = (n as u64).saturating_mul(temperatures.iter().filter(|&&t| t > 0.0).count() as u64); + if o.accumulates != work || o.draws != draws { + return Err(format!("the member reported {} accumulates and {} draws, and that schedule is {work} and {draws}", o.accumulates, o.draws)); + } + let rests = w.rests(&o.state); + match o.term { + Term::FixedPoint if rests => Ok(()), + Term::Bound if !rests => Ok(()), + _ => Err("the member reported an ending its state does not have".into()), + } +} + +/// What an answer says of the member that ran it. +pub(super) fn describe(c: &Certified<'_>, kernel: &str) -> Value { + let m = c.member(); + json!({ "id": m.id(), "class": m.class(), "kernel": kernel, "parity": c.parity(kernel).label() }) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn the_reference_says_what_it_is() { + let d = describe(&Certified::reference(), KERNEL_SAMPLE); + assert_eq!(d["id"], "cpu:reference"); + assert_eq!(d["class"], "cpu"); + assert_eq!(d["parity"], "reference"); + assert!(CpuReference.meter().is_none()); + } + + /// The reference's own kernels, behind another name, earn bit-exact, never reference. + #[test] + fn a_member_earns_its_parity_and_names_itself_properly() { + struct Copy(&'static str, &'static str); + impl Member for Copy { + fn id(&self) -> String { + self.0.into() + } + fn class(&self) -> &'static str { + self.1 + } + fn label(&self) -> String { + "A copy.".into() + } + fn settle(&self, w: &Workload, start: &[i8], bound: u32) -> Result { + CpuReference.settle(w, start, bound) + } + fn chains(&self, w: &Workload, chains: &[Chain], temperatures: &[f64]) -> Result, String> { + CpuReference.chains(w, chains, temperatures) + } + } + let good = Copy("gpu:copy-1", "gpu"); + let c = certify(&good).unwrap(); + assert_eq!((c.parity(KERNEL_SETTLE), c.parity(KERNEL_SAMPLE)), (Parity::BitExact, Parity::BitExact)); + for (id, class) in [("cpu:reference", "cpu"), ("gpu:Copy", "gpu"), ("fpga:x", "gpu"), ("gpu:", "gpu"), ("tpu:x", "tpu"), ("gpu-x", "gpu")] { + assert!(certify(&Copy(id, class)).is_err(), "{id} {class}"); + } + assert_eq!(named(&good).unwrap().parity(KERNEL_SAMPLE), Parity::Uncertified); + } + + /// The classes a member can name are the fabric's. + #[cfg(feature = "quantum_fabric")] + #[test] + fn the_classes_are_the_fabric_s() { + use crate::quantum_fabric::FabricClass; + for c in ["cpu", "gpu", "fpga"] { + assert!(FabricClass::from_label(c).is_some(), "{c}"); + } + assert_eq!(FabricClass::from_label(CpuReference.class()), Some(FabricClass::Cpu)); + } +} diff --git a/wai/wai-quantum/src/quantum_landscape/price.rs b/wai/wai-quantum/src/quantum_landscape/price.rs new file mode 100644 index 000000000..fa1470487 --- /dev/null +++ b/wai/wai-quantum/src/quantum_landscape/price.rs @@ -0,0 +1,227 @@ +//! How a run's energy in joules is stated, on a member with no meter: +//! **priced**. This run's count of accumulates, multiplied by a rate measured +//! elsewhere, with that rate's source named. It is not a reading, and no answer +//! calls it one. It says what the work would come to on the board the rate was +//! read from, and nothing of what the machine that ran the job spent. In the +//! terms of `extensions/energy-measurement.md` its class is `ModelBased`, a +//! model calibrated on the board it describes, and the published add's figure is +//! `Estimator`, a constant calibrated on nothing here. +//! +//! The rates, and what stands behind each: +//! +//! - **Temperature 0, about 0.16 pJ per accumulate.** One FPGA board, a dense +//! test design of 64 cells (4032 accumulates a clock) at 100 MHz, as the +//! slope of power against clock speed, so that static power cancels. A fresh +//! build read 0.677 nJ a clock against the 0.644 the rate predicts, and +//! stopping and starting the design read 61.7 mW against the 64.4 predicted. +//! One board and one meter: that shows consistency, not the absence of bias. +//! The readings themselves are not in this repository. +//! - **Above temperature 0, about 0.41 pJ per accumulate.** The same board. A +//! sampling design drew about 100 mW more than the deterministic design in +//! paired readings, which at 100 MHz and 4032 accumulates a clock is about +//! 0.25 pJ per accumulate over the deterministic rate. Read at one +//! temperature, 0.25. That design updates all its cells at once, and the +//! reference here updates them one by one. +//! - **For scale, about 0.03 pJ per accumulate.** A published 2014 figure +//! for the energy of one 8-bit integer add in 45 nm logic. It is a reference +//! point, not a model of any processor, and it has no term for sampling. +//! - **What the rates leave out.** Both board rates are differences that cancel +//! the board's own draw, about 3.1 W, on purpose. So a small job prices low, +//! and an answer says by how much: at least one clock a sweep, at 100 MHz. + +use serde_json::{Value, json}; + +/// About 0.16 pJ per accumulate: the deterministic mode, on one FPGA board. +pub const SETTLE_PJ: f64 = 0.16; +/// About 0.41 pJ per accumulate: the sampling mode, on that board. +pub const SAMPLE_PJ: f64 = 0.41; +/// About 0.03 pJ: a published 2014 figure for one 8-bit integer add in 45 nm +/// logic, given beside the priced figure for scale. It models no processor. +pub const PUBLISHED_ADD_PJ: f64 = 0.03; +/// What that board draws whatever it is doing, in watts. +pub const BOARD_W: f64 = 3.1; +/// The clock the rates were read at, in hertz. +pub const BOARD_HZ: f64 = 100e6; + +/// Which rate a run is priced at. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Mode { + Settle, + Sample, +} + +impl Mode { + pub fn pj_per_accumulate(self) -> f64 { + match self { + Mode::Settle => SETTLE_PJ, + Mode::Sample => SAMPLE_PJ, + } + } + + fn shown(self) -> &'static str { + match self { + Mode::Settle => "0.16", + Mode::Sample => "0.41", + } + } + + pub fn source(self) -> &'static str { + match self { + Mode::Settle => { + "Deterministic rate: measured on one FPGA board, on a dense 64-cell test design at 100 MHz, as the slope of power against clock speed. A second method on the same board agrees. No other board has repeated it." + } + Mode::Sample => { + "Sampling rate: measured on one FPGA board, as the deterministic rate plus about 0.25 pJ for the sampler, from paired readings at one temperature, 0.25. That design updates all its cells at once, and this run updates them one by one." + } + } + } +} + +/// `1234567` as `1,234,567`. +pub fn group(n: u64) -> String { + let s = n.to_string(); + let mut out = String::with_capacity(s.len() + s.len() / 3); + for (i, c) in s.chars().enumerate() { + if i > 0 && (s.len() - i).is_multiple_of(3) { + out.push(','); + } + out.push(c); + } + out +} + +/// `3 sweeps`, `1 sweep`. +pub fn plural(n: u64, word: &str) -> String { + if n == 1 { format!("1 {word}") } else { format!("{} {word}s", group(n)) } +} + +/// A value to two significant figures, which is all these rates support. +pub fn two_figures(v: f64) -> String { + if v == 0.0 { + return "0".into(); + } + let mut magnitude = 0i32; + let mut a = v.abs(); + while a >= 10.0 { + a /= 10.0; + magnitude += 1; + } + while a < 1.0 { + a *= 10.0; + magnitude -= 1; + } + let decimals = (1 - magnitude).max(0) as usize; + let mut scale = 1.0f64; + for _ in 0..(1 - magnitude).abs() { + scale *= 10.0; + } + if 1 - magnitude < 0 { + scale = 1.0 / scale; + } + let scaled = (v * scale + 0.5).floor() / scale; + format!("{scaled:.decimals$}") +} + +/// Picojoules, in the unit that reads best, to two significant figures. +pub fn joules(pj: f64) -> String { + for (scale, unit) in [(1e15, "kJ"), (1e12, "J"), (1e9, "mJ"), (1e6, "µJ"), (1e3, "nJ"), (1.0, "pJ")] { + if pj >= scale { + return format!("{} {unit}", two_figures(pj / scale)); + } + } + format!("{} pJ", two_figures(pj)) +} + +/// The energy statement of a run on a member with no meter. `clocks_at_least` +/// is the fewest clocks the work could take on that board: one a sweep. +pub fn priced(accumulates: u64, mode: Mode, clocks_at_least: u64) -> Value { + let rate = mode.pj_per_accumulate(); + let pj = accumulates as f64 * rate; + let add_pj = accumulates as f64 * PUBLISHED_ADD_PJ; + let board_pj = clocks_at_least as f64 * (BOARD_W / BOARD_HZ) * 1e12; + let add_tail = match mode { + Mode::Settle => "It is a reference point, not a model of any processor.", + Mode::Sample => "It is a reference point, not a model of any processor, and it has no term for sampling.", + }; + json!({ + "kind": "priced", + "class": "ModelBased", + "describes": "the work on one FPGA board, not the machine that ran this job", + "line": format!("Priced, not read from a meter: {} × about {} pJ = about {}.", plural(accumulates, "accumulate"), mode.shown(), joules(pj)), + "rate_pj_per_accumulate": rate, + "rate_source": mode.source(), + "pj": pj, + "published_add": { + "class": "Estimator", + "rate_pj_per_accumulate": PUBLISHED_ADD_PJ, + "source": "a published 2014 figure for the energy of one 8-bit integer add in 45 nm logic", + "pj": add_pj, + "line": format!("For scale: a published 2014 figure for one 8-bit integer add, about 0.03 pJ, puts this count at about {}. {add_tail}", joules(add_pj)), + }, + "left_out": { + "board_watts": BOARD_W, + "board_hz": BOARD_HZ, + "clocks_at_least": clocks_at_least, + "pj": board_pj, + "line": format!( + "Small jobs price low. These rates count only the work: that board also draws about {} W at every clock. This run's {} need at least {} clocks, which is about {} of board power that the line above leaves out.", + two_figures(BOARD_W), + plural(clocks_at_least, "sweep"), + group(clocks_at_least), + joules(board_pj), + ), + }, + }) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn above_zero_has_its_own_rate_about_two_and_a_half_times_the_other() { + let r = SAMPLE_PJ / SETTLE_PJ; + assert!((2.5..2.6).contains(&r), "ratio {r}"); + let hot = priced(1000, Mode::Sample, 10); + let cold = priced(1000, Mode::Settle, 10); + assert!(hot["pj"].as_f64().unwrap() > 2.5 * cold["pj"].as_f64().unwrap()); + assert_eq!(hot["published_add"]["pj"], cold["published_add"]["pj"], "no sampling term is invented for a reference point"); + } + + #[test] + fn the_line_says_priced_and_never_calls_a_run_measured() { + for mode in [Mode::Settle, Mode::Sample] { + let p = priced(1920, mode, 40); + for key in ["/line", "/published_add/line", "/left_out/line"] { + let text = p.pointer(key).unwrap().as_str().unwrap(); + assert!(!text.to_lowercase().contains("measured"), "{text}"); + } + for key in ["/line", "/rate_source", "/published_add/line", "/left_out/line"] { + assert!(!p.pointer(key).unwrap().as_str().unwrap().contains('±')); + } + assert!(p["line"].as_str().unwrap().starts_with("Priced, not read from a meter")); + assert_eq!((p["kind"].as_str(), p["class"].as_str()), (Some("priced"), Some("ModelBased"))); + assert!(p["rate_source"].as_str().unwrap().contains("FPGA board")); + } + } + + #[test] + fn a_small_job_says_it_prices_low_and_by_how_much() { + let p = priced(44, Mode::Settle, 2); + assert!(p["left_out"]["line"].as_str().unwrap().contains("Small jobs price low")); + // 2 clocks × 3.1 W / 100 MHz = 62 nJ, far above the 7 pJ the work prices at. + assert!((p["left_out"]["pj"].as_f64().unwrap() - 62_000.0).abs() < 1e-6); + assert!(p["left_out"]["pj"].as_f64().unwrap() > 1000.0 * p["pj"].as_f64().unwrap()); + assert!(priced(44, Mode::Settle, 1)["left_out"]["line"].as_str().unwrap().contains("1 sweep need")); + } + + #[test] + fn numbers_are_written_to_the_figures_they_have() { + assert_eq!((group(0), group(999), group(1000), group(1234567)), ("0".into(), "999".into(), "1,000".into(), "1,234,567".into())); + assert_eq!(two_figures(5.774), "5.8"); + assert_eq!(two_figures(3.1), "3.1"); + assert_eq!(two_figures(123.0), "120"); + assert_eq!(two_figures(0.41), "0.41"); + assert_eq!((joules(5774.0), joules(7.04), joules(0.5), joules(9.9e6)), ("5.8 nJ".into(), "7.0 pJ".into(), "0.50 pJ".into(), "9.9 µJ".into())); + } +} diff --git a/wai/wai-quantum/src/quantum_landscape/program.rs b/wai/wai-quantum/src/quantum_landscape/program.rs new file mode 100644 index 000000000..5fb748135 --- /dev/null +++ b/wai/wai-quantum/src/quantum_landscape/program.rs @@ -0,0 +1,857 @@ +//! A program in epuML: the text a job gives. One statement a line, `#` starts +//! a comment, and the last statement says how to run. +//! +//! ```text +//! vars N N variables of three states; it comes first +//! agree I J I and J want the same sign (coupling +1) +//! oppose I J I and J want opposite signs (coupling −1) +//! couple I J W a coupling given directly, W is −1, 0 or +1 +//! weight I J W a real-valued coupling, quantised to its sign (Weights) +//! pin I +|- hold I at + or −, with a field larger than anything else can outvote +//! lean I +|- a field of +1 or −1 on I +//! field I B a whole-number field on I +//! cost C each variable that is not 0 costs C +//! minimize [steps=S] temperature 0, from every variable at 0 +//! solve [t0=T] [restarts=R] [steps=S] from random starts, cooling from T to 0 +//! ``` +//! +//! The grammar is ASCII and strict. Lines end at a line feed, and a carriage +//! return just before it is dropped. `#` starts a comment. Tokens are separated +//! by spaces and tabs, and by nothing else. A whole number is `-?[0-9]+`, a +//! variable `[0-9]+`, a temperature `[0-9]+(.[0-9]+)?`, and a weight a whole +//! number or a decimal with an optional exponent `e-?[0-9]{1,3}`; leading zeros +//! are allowed, and a `+` sign is not. +//! +//! A refusal is one plain sentence, with its line where a line means anything. +//! Nothing in a program is silently dropped: an unknown key, a statement +//! after the run line, a second `pin` the other way and a `pin` on a variable +//! with a field are all refused; setting a pair twice is a warning. +//! +//! Lines beginning `#!` are comments to the language, so a program with them +//! is still a plain epuML program. To this reader they say what a program +//! means in the reader's terms, and how to check an answer against the problem +//! it was written for: an answer can reach the lowest energy and still not +//! solve the problem, which is what the failing examples show. +//! +//! ```text +//! #! title T #! about T #! expect works|fails +//! #! item NOUN #! plus NAME #! minus NAME #! zero NAME +//! #! check different 0-1 1-2 each pair must end up in different states +//! #! check permutation M an M×M grid holds one + in each row and column, and no − +//! #! check balance A B C … the numbers split into two equal piles, none left out +//! #! check matches +-0+ the state is this one, or its mirror image +//! ``` + +use super::energy::{Landscape, Quantised, Regime, Weights}; +use super::{MAX_CHECK_ITEMS, MAX_CHECKS, MAX_FIELD, MAX_PROGRAM_BYTES, MAX_RESTARTS, MAX_STEPS, MAX_VARS}; +use std::collections::HashMap; + +/// Why a program, or a request, could not run. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Refusal { + pub line: Option, + pub message: String, +} + +impl Refusal { + pub fn at(line: usize, message: impl Into) -> Refusal { + Refusal { line: Some(line), message: message.into() } + } + + pub fn general(message: impl Into) -> Refusal { + Refusal { line: None, message: message.into() } + } + + /// The one sentence a person reads. + pub fn plain(&self) -> String { + match self.line { + Some(l) => format!("Line {l}: {}", self.message), + None => self.message.clone(), + } + } +} + +impl core::fmt::Display for Refusal { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_str(&self.plain()) + } +} + +impl std::error::Error for Refusal {} + +/// A warning given when a program is read, before anything runs. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Warning { + pub code: &'static str, + pub line: Option, + pub text: String, +} + +/// How a program asks to run. +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum RunLine { + Minimize { sweeps: u32 }, + Solve { t0: Option, restarts: u32, sweeps: u32 }, +} + +/// A way to check an answer against the problem, in the problem's own terms. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum Check { + Different(Vec<(usize, usize)>), + Permutation(usize), + Balance(Vec), + Matches(Vec), +} + +/// What the `#!` lines say. +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct Notes { + pub title: Option, + pub about: Option, + pub item: Option, + pub plus: Option, + pub minus: Option, + pub zero: Option, + pub expect: Option, + pub checks: Vec, +} + +/// One check's outcome. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Graded { + pub kind: &'static str, + pub passed: bool, + pub detail: String, +} + +/// A program, read. +#[derive(Clone, Debug, PartialEq)] +pub struct Program { + pub source: String, + pub landscape: Landscape, + pub run: RunLine, + /// The line of the `solve` or `minimize` statement. + pub run_line: usize, + pub notes: Notes, + pub warnings: Vec, + /// The landscape as declared, when the program gave `weight`s: + /// [`Program::landscape`] is then its quantisation. + pub declared: Option, + pub quantised: Option, +} + +const STATEMENTS: [&str; 12] = ["vars", "agree", "oppose", "couple", "weight", "pin", "lean", "field", "cost", "price", "minimize", "solve"]; + +/// The nearest statement to a typing mistake, by edit distance. +fn suggest(word: &str) -> Option<&'static str> { + fn distance(a: &str, b: &str) -> usize { + let (a, b): (Vec, Vec) = (a.chars().collect(), b.chars().collect()); + let mut row: Vec = (0..=b.len()).collect(); + for (i, ca) in a.iter().enumerate() { + let mut prev = row[0]; + row[0] = i + 1; + for (j, cb) in b.iter().enumerate() { + let cur = row[j + 1]; + row[j + 1] = (prev + usize::from(ca != cb)).min(cur + 1).min(row[j] + 1); + prev = cur; + } + } + row[b.len()] + } + let (best, d) = STATEMENTS.iter().map(|s| (*s, distance(word, s))).min_by_key(|&(_, d)| d)?; + (d <= 2).then_some(best) +} + +/// The tokens of a line: separated by spaces and tabs, and by nothing else. +fn tokens(code: &str) -> Vec<&str> { + code.split([' ', '\t']).filter(|t| !t.is_empty()).collect() +} + +fn blank_trimmed(s: &str) -> &str { + s.trim_matches([' ', '\t']) +} + +fn digits(t: &str) -> bool { + !t.is_empty() && t.bytes().all(|b| b.is_ascii_digit()) +} + +/// A variable or a count: `[0-9]+`. +pub fn index(t: &str) -> Option { + if !digits(t) { + return None; + } + let t = t.trim_start_matches('0'); + if t.len() > 19 { + return None; + } + if t.is_empty() { Some(0) } else { t.parse().ok() } +} + +/// A whole number: `-?[0-9]+`, within 64 bits. +pub fn whole_number(t: &str) -> Option { + let (negative, d) = match t.strip_prefix('-') { + Some(d) => (true, d), + None => (false, t), + }; + if !digits(d) { + return None; + } + let d = d.trim_start_matches('0'); + if d.len() > 19 { + return None; + } + let v: i128 = if d.is_empty() { 0 } else { d.parse().ok()? }; + i64::try_from(if negative { -v } else { v }).ok() +} + +/// A temperature: `[0-9]+(.[0-9]+)?`, at most 40 characters, read to the nearest double. +pub fn decimal(t: &str) -> Option { + if t.len() > 40 { + return None; + } + let (int, frac) = match t.split_once('.') { + Some((i, f)) => (i, Some(f)), + None => (t, None), + }; + if !digits(int) || frac.is_some_and(|f| !digits(f)) { + return None; + } + t.parse().ok() +} + +/// A weight: `-?[0-9]+(.[0-9]+)?(e-?[0-9]{1,3})?`, read to the nearest double. +pub fn weight_number(t: &str) -> Option { + if t.len() > 40 { + return None; + } + let (mantissa, exponent) = match t.split_once(['e', 'E']) { + Some((m, e)) => (m, Some(e)), + None => (t, None), + }; + let unsigned = mantissa.strip_prefix('-').unwrap_or(mantissa); + if decimal(unsigned).is_none() { + return None; + } + if let Some(e) = exponent { + let e = e.strip_prefix('-').unwrap_or(e); + if !digits(e) || e.len() > 3 { + return None; + } + } + t.parse().ok() +} + +/// A temperature must be an ordinary number, not negative, and either exactly +/// 0 or large enough that its inverse is an ordinary number too. +pub fn check_temperature(t: f64) -> Result<(), String> { + if !t.is_finite() { + return Err("A temperature must be an ordinary number.".into()); + } + if t < 0.0 { + return Err("A temperature cannot be negative. Use 0 for the deterministic mode.".into()); + } + if t > 0.0 && t < 1e-6 { + return Err("A temperature above 0 must be at least 0.000001. Use 0 for the deterministic mode.".into()); + } + Ok(()) +} + +fn parse_notes(src: &str) -> Result { + let mut notes = Notes::default(); + for (i, raw) in src.lines().enumerate() { + let line = i + 1; + let Some(rest) = raw.trim_start_matches([' ', '\t']).strip_prefix("#!") else { continue }; + let rest = blank_trimmed(rest); + let (key, value) = rest.split_once([' ', '\t']).unwrap_or((rest, "")); + let value = blank_trimmed(value); + let text = || -> Result { + if value.is_empty() { Err(Refusal::at(line, format!("`#! {key}` needs some text after it."))) } else { Ok(value.to_string()) } + }; + match key { + "title" => notes.title = Some(text()?), + "about" => notes.about = Some(text()?), + "item" => notes.item = Some(text()?), + "plus" => notes.plus = Some(text()?), + "minus" => notes.minus = Some(text()?), + "zero" => notes.zero = Some(text()?), + "expect" => match value { + "works" | "fails" => notes.expect = Some(value.to_string()), + _ => return Err(Refusal::at(line, "`#! expect` takes `works` or `fails`.")), + }, + "check" => { + if notes.checks.len() == MAX_CHECKS { + return Err(Refusal::at(line, format!("A program has at most {MAX_CHECKS} `#! check` lines."))); + } + notes.checks.push(parse_check(value, line)?); + if notes.checks.iter().map(check_items).sum::() > MAX_CHECK_ITEMS { + return Err(Refusal::at(line, format!("The `#! check` lines hold more than {} items between them, which is the most a program may.", super::price::group(MAX_CHECK_ITEMS as u64)))); + } + } + _ => return Err(Refusal::at(line, format!("`#! {key}` is not something this reader knows. It knows title, about, item, plus, minus, zero, expect and check."))), + } + } + Ok(notes) +} + +/// How many items a check holds: its pairs, its grid's cells, its numbers or its cells. +pub fn check_items(c: &Check) -> usize { + match c { + Check::Different(p) => p.len(), + Check::Permutation(m) => m.saturating_mul(*m), + Check::Balance(v) => v.len(), + Check::Matches(s) => s.len(), + } +} + +fn parse_check(value: &str, line: usize) -> Result { + let mut tok = tokens(value).into_iter(); + let kind = tok.next().unwrap_or(""); + let bad = |what: &str| Refusal::at(line, format!("`#! check {kind}` {what}")); + match kind { + "different" => { + let mut pairs = Vec::new(); + for t in tok { + if pairs.len() == MAX_CHECK_ITEMS { + return Err(bad(&format!("holds more than {} pairs.", super::price::group(MAX_CHECK_ITEMS as u64)))); + } + let (a, b) = t.split_once('-').ok_or_else(|| bad("takes pairs like 0-1."))?; + pairs.push((index(a).ok_or_else(|| bad("takes pairs like 0-1."))?, index(b).ok_or_else(|| bad("takes pairs like 0-1."))?)); + } + if pairs.is_empty() { + return Err(bad("needs at least one pair, like 0-1.")); + } + Ok(Check::Different(pairs)) + } + "permutation" => Ok(Check::Permutation(tok.next().and_then(index).filter(|&m| m <= MAX_VARS).ok_or_else(|| bad("takes the grid's side, like 3."))?)), + "balance" => { + let mut values = Vec::new(); + for t in tok { + if values.len() == MAX_VARS { + return Err(bad("takes one whole number per variable.")); + } + let v = whole_number(t).ok_or_else(|| bad("takes one whole number per variable."))?; + if v.unsigned_abs() > MAX_FIELD as u64 { + return Err(bad(&format!("takes numbers of at most {MAX_FIELD} in size."))); + } + values.push(v); + } + if values.is_empty() { + return Err(bad("takes one whole number per variable.")); + } + Ok(Check::Balance(values)) + } + "matches" => { + let t = tok.next().filter(|t| t.len() <= MAX_VARS).ok_or_else(|| bad("takes a state like +-0+."))?; + let state: Option> = t.chars().map(|c| match c { '+' => Some(1), '-' => Some(-1), '0' => Some(0), _ => None }).collect(); + Ok(Check::Matches(state.ok_or_else(|| bad("takes a state like +-0+."))?)) + } + _ => Err(Refusal::at(line, format!("`#! check {kind}` is not a check this reader knows. It knows different, permutation, balance and matches."))), + } +} + +/// Grade an answer against a check. Nothing here reads the energy: this is the problem's own test. +pub fn grade(check: &Check, state: &[i8]) -> Graded { + match check { + Check::Different(pairs) => { + let same = pairs.iter().filter(|&&(a, b)| state[a] == state[b]).count(); + Graded { + kind: "different", + passed: same == 0, + detail: if same == 0 { + format!("All {} pairs that must differ do differ.", pairs.len()) + } else { + format!("{same} of {} pairs that must differ are the same.", pairs.len()) + }, + } + } + Check::Permutation(m) => { + let m = *m; + let negative = state.iter().filter(|&&s| s < 0).count(); + let row_bad = (0..m).filter(|&r| (0..m).filter(|&c| state[r * m + c] == 1).count() != 1).count(); + let col_bad = (0..m).filter(|&c| (0..m).filter(|&r| state[r * m + c] == 1).count() != 1).count(); + let passed = negative == 0 && row_bad == 0 && col_bad == 0; + let minus = match negative { + 0 => String::new(), + 1 => ", and 1 cell holds a minus, which an assignment has no meaning for".to_string(), + k => format!(", and {k} cells hold a minus, which an assignment has no meaning for"), + }; + Graded { + kind: "permutation", + passed, + detail: if passed { + format!("Every row and column of the {m}×{m} grid has exactly one choice.") + } else { + format!("This is not a valid assignment on the {m}×{m} grid: {row_bad} rows and {col_bad} columns do not have exactly one choice{minus}.") + }, + } + } + Check::Balance(values) => { + let difference: i128 = values.iter().zip(state).map(|(&v, &s)| i128::from(v) * i128::from(s)).sum(); + let left_out = state.iter().filter(|&&s| s == 0).count(); + let passed = difference == 0 && left_out == 0; + Graded { + kind: "balance", + passed, + detail: if passed { "The two piles are equal, and every number is used.".into() } else { format!("The two piles differ by {}, and {left_out} numbers were left out.", difference.abs()) }, + } + } + Check::Matches(want) => { + let wrong = want.iter().zip(state).filter(|(w, s)| w != s).count(); + let wrong_mirror = want.iter().zip(state).filter(|(w, s)| **w != -**s).count(); + let passed = wrong == 0 || wrong_mirror == 0; + Graded { + kind: "matches", + passed, + detail: if passed { "This matches the planted answer.".into() } else { format!("This differs from the planted answer in {} places.", wrong.min(wrong_mirror)) }, + } + } + } +} + +fn usize_at(tok: &[&str], k: usize) -> Option { + tok.get(k).and_then(|t| index(t)) +} + +/// The value of `key=` among a run line's arguments, every key checked against those it takes. +fn key_values<'a>(tok: &[&'a str], allowed: &[&str], line: usize) -> Result, Refusal> { + let mut out = HashMap::new(); + for t in &tok[1..] { + let Some((k, v)) = t.split_once('=') else { + return Err(Refusal::at(line, format!("`{t}` is not in the form key=value."))); + }; + if !allowed.contains(&k) { + return Err(Refusal::at(line, format!("`{}` takes {}, not `{k}`.", tok[0], allowed.join(", ")))); + } + if out.insert(k, v).is_some() { + return Err(Refusal::at(line, format!("`{k}=` is given twice."))); + } + } + Ok(out) +} + +fn whole(map: &HashMap<&str, &str>, key: &str, default: u32, most: u32, line: usize) -> Result { + let v = match map.get(key) { + Some(v) => index(v).ok_or_else(|| Refusal::at(line, format!("`{key}=` takes a whole number, not `{v}`.")))?, + None => return Ok(default), + }; + if v == 0 || v > most as usize { + return Err(Refusal::at(line, format!("`{key}=` must be between 1 and {}.", super::price::group(u64::from(most))))); + } + Ok(v as u32) +} + +/// Read a program. +pub fn read(src: &str) -> Result { + if src.len() > MAX_PROGRAM_BYTES { + return Err(Refusal::general(format!("The program is {} bytes long, and the most this takes is {}.", super::price::group(src.len() as u64), super::price::group(MAX_PROGRAM_BYTES as u64)))); + } + let notes = parse_notes(src)?; + let mut landscape: Option = None; + let mut run: Option<(RunLine, usize)> = None; + let mut warnings = Vec::new(); + let mut weights: Vec<(usize, usize, usize, f64)> = Vec::new(); + let mut pairs: HashMap<(usize, usize), usize> = HashMap::new(); + let mut pinned: HashMap = HashMap::new(); + let mut fielded: HashMap = HashMap::new(); + + for (i, raw) in src.lines().enumerate() { + let line = i + 1; + let code = blank_trimmed(raw.split('#').next().unwrap_or("")); + if code.is_empty() { + continue; + } + let tok: Vec<&str> = tokens(code); + let head = tok[0]; + if run.is_some() { + return Err(Refusal::at(line, "A statement comes after the run line, which must be last.")); + } + match head { + "vars" => { + if landscape.is_some() { + return Err(Refusal::at(line, "`vars` is already declared.")); + } + let n = usize_at(&tok, 1).filter(|_| tok.len() == 2).ok_or_else(|| Refusal::at(line, "`vars` takes one whole number."))?; + if n == 0 { + return Err(Refusal::at(line, "`vars` must be at least 1.")); + } + if n > MAX_VARS { + return Err(Refusal::at(line, format!("{n} variables is more than this takes, which is {MAX_VARS}."))); + } + landscape = Some(Landscape::new(n)); + } + "agree" | "oppose" | "couple" | "weight" | "pin" | "lean" | "field" | "cost" => { + let l = landscape.as_mut().ok_or_else(|| Refusal::at(line, format!("`{head}` comes before `vars N`.")))?; + let n = l.len(); + let variable = |k: usize, form: &str| -> Result { + let v = usize_at(&tok, k).ok_or_else(|| Refusal::at(line, format!("`{head}` takes {form}.")))?; + if v >= n { + return Err(Refusal::at(line, format!("Variable {v} is out of range: only {n} are declared."))); + } + Ok(v) + }; + match head { + "agree" | "oppose" | "couple" | "weight" => { + let form = match head { + "couple" => "two variables and −1, 0 or +1, like `couple 0 1 -1`", + "weight" => "two variables and a number, like `weight 0 1 -2.5`", + _ => "two variables", + }; + let want = if head == "couple" || head == "weight" { 4 } else { 3 }; + if tok.len() != want { + return Err(Refusal::at(line, format!("`{head}` takes {form}."))); + } + let (a, b) = (variable(1, form)?, variable(2, form)?); + if a == b { + return Err(Refusal::at(line, format!("`{head}` needs two different variables."))); + } + if head == "weight" { + let w: f64 = weight_number(tok[3]).ok_or_else(|| Refusal::at(line, format!("`{head}` takes {form}.")))?; + if !w.is_finite() || w.abs() > 1e12 { + return Err(Refusal::at(line, "A `weight` must be an ordinary number, no larger than a million million.")); + } + if !pairs.is_empty() { + return Err(Refusal::at(line, "A program uses `weight`, or `agree`, `oppose` and `couple`, and not both: `weight` goes through quantisation, and the others do not.")); + } + if !pinned.is_empty() { + return Err(Refusal::at(line, "A program uses `weight` or `pin`, and not both: the size of a pin is worked out from couplings of ±1.")); + } + if weights.iter().any(|&(_, x, y, _)| (x.min(y), x.max(y)) == (a.min(b), a.max(b))) { + warnings.push(Warning { code: "pair-repeated", line: Some(line), text: format!("`weight` sets the pair {a} and {b} again. The last one wins.") }); + } + weights.push((line, a, b, w)); + } else { + if !weights.is_empty() { + return Err(Refusal::at(line, "A program uses `weight`, or `agree`, `oppose` and `couple`, and not both: `weight` goes through quantisation, and the others do not.")); + } + let w: i8 = match head { + "agree" => 1, + "oppose" => -1, + _ => match whole_number(tok[3]) { + Some(v) if (-1..=1).contains(&v) => v as i8, + _ => return Err(Refusal::at(line, "A coupling is −1, 0 or +1.")), + }, + }; + if let Some(before) = pairs.insert((a.min(b), a.max(b)), line) { + warnings.push(Warning { code: "pair-repeated", line: Some(line), text: format!("`{head}` sets the pair {a} and {b} again, after line {before}. The last one wins.") }); + } + l.couple(a, b, w); + } + } + "pin" | "lean" => { + let v = variable(1, "a variable and + or −, like `pin 0 +`")?; + let sign: i64 = match (tok.get(2).copied(), tok.len()) { + (Some("+"), 3) => 1, + (Some("-"), 3) => -1, + _ => return Err(Refusal::at(line, format!("`{head}` takes a variable and + or −, like `{head} 0 +`."))), + }; + if head == "pin" { + if !weights.is_empty() { + return Err(Refusal::at(line, "A program uses `weight` or `pin`, and not both: the size of a pin is worked out from couplings of ±1.")); + } + if let Some(&before) = fielded.get(&v) { + return Err(Refusal::at(line, format!("Variable {v} already has a field or lean from line {before}, which contradicts this `pin`."))); + } + match pinned.get(&v) { + Some(&(before, s)) if s != sign => { + return Err(Refusal::at(line, format!("Variable {v} is pinned both ways: on line {before}, and here."))); + } + _ => { + pinned.insert(v, (line, sign)); + } + } + } else { + if pinned.contains_key(&v) { + return Err(Refusal::at(line, format!("Variable {v} is pinned, which this `lean` contradicts."))); + } + fielded.insert(v, line); + l.set_field(v, sign); + } + } + "field" => { + let v = variable(1, "a variable and a whole number, like `field 0 2`")?; + let b: i64 = tok.get(2).filter(|_| tok.len() == 3).and_then(|t| whole_number(t)).ok_or_else(|| Refusal::at(line, "`field` takes a variable and a whole number, like `field 0 2`."))?; + if b.unsigned_abs() > MAX_FIELD as u64 { + return Err(Refusal::at(line, "That `field` is too large to run here.")); + } + if pinned.contains_key(&v) { + return Err(Refusal::at(line, format!("Variable {v} is pinned, which this `field` contradicts."))); + } + fielded.insert(v, line); + l.set_field(v, b); + } + _ => { + let c: i64 = tok.get(1).filter(|_| tok.len() == 2).and_then(|t| whole_number(t)).ok_or_else(|| Refusal::at(line, "`cost` takes one whole number."))?; + if c < 0 { + return Err(Refusal::at(line, "`cost` cannot be negative.")); + } + if c > MAX_FIELD { + return Err(Refusal::at(line, "That `cost` is too large to run here.")); + } + l.set_cost(c); + } + } + } + "price" => { + if !matches!(tok.get(1).copied(), Some("measured" | "modeled" | "modelled")) { + return Err(Refusal::at(line, "`price` takes `measured` or `modelled`.")); + } + warnings.push(Warning { code: "price-ignored", line: Some(line), text: "`price` has no effect here: every answer gives the board's rate and the modelled one.".into() }); + } + "minimize" => { + let map = key_values(&tok, &["steps"], line)?; + run = Some((RunLine::Minimize { sweeps: whole(&map, "steps", 60, MAX_STEPS, line)? }, line)); + } + "solve" => { + let map = key_values(&tok, &["t0", "restarts", "steps"], line)?; + let t0 = match map.get("t0") { + Some(v) => { + let t: f64 = decimal(v).ok_or_else(|| Refusal::at(line, format!("`t0=` takes a number like 0.5, not `{v}`.")))?; + check_temperature(t).map_err(|m| Refusal::at(line, m))?; + Some(t) + } + None => None, + }; + let restarts = whole(&map, "restarts", 8, MAX_RESTARTS, line)?; + run = Some((RunLine::Solve { t0, restarts, sweeps: whole(&map, "steps", 40, MAX_STEPS, line)? }, line)); + } + other => { + let hint = suggest(other).map(|s| format!(" Did you mean `{s}`?")).unwrap_or_default(); + return Err(Refusal::at(line, format!("`{other}` is not a statement epuML knows.{hint}"))); + } + } + } + + let Some(mut landscape) = landscape else { + return Err(Refusal::general("The program is empty. Its first statement must be `vars N`, with N the number of variables.")); + }; + let Some((run, run_line)) = run else { + return Err(Refusal::general("The program never says to run. Its last line must be `solve` or `minimize`.")); + }; + let n = landscape.len(); + for check in ¬es.checks { + let bad = match check { + Check::Different(p) => p.iter().any(|&(a, b)| a >= n || b >= n), + Check::Permutation(m) => m.saturating_mul(*m) != n, + Check::Balance(v) => v.len() != n, + Check::Matches(s) => s.len() != n, + }; + if bad { + return Err(Refusal::general(format!("A `#! check` line does not fit this program's {n} variables."))); + } + } + + let (mut declared, mut quantised) = (None, None); + if weights.is_empty() { + // A pin's size is what nothing else in the program can outvote: every + // coupling a cell has, the largest other field and the cost, and 1 more. + if !pinned.is_empty() { + let largest = landscape.field().iter().map(|b| b.unsigned_abs()).max().unwrap_or(0); + let size = u64::try_from(landscape.coupling_scale()).unwrap_or(0) + largest + landscape.cost().unsigned_abs() + 1; + if size > MAX_FIELD as u64 { + let first = pinned.values().map(|&(l, _)| l).min().unwrap_or(0); + return Err(Refusal::at(first, format!("A `pin` here needs a field of {}, above {MAX_FIELD}: the other fields or the cost are too large beside it.", super::price::group(size)))); + } + for (&v, &(_, sign)) in &pinned { + landscape.set_field(v, sign * size as i64); + } + } + } else { + let mut real = Weights::new(n, landscape.field(), landscape.cost()); + for &(_, a, b, w) in &weights { + real.set(a, b, w); + } + let (signs, q) = real.quantise().map_err(Refusal::general)?; + if !q.fits() { + warnings.push(Warning { + code: "sizes-lost", + line: None, + text: format!( + "The sizes of these weights matter, and the couplings keep only their signs. Of {} weights, {} were kept as a sign and {} were dropped, with a relative error of {:.2}. Expect a poor answer on the weights you declared.", + q.declared, q.kept, q.dropped, q.relative_error + ), + }); + } + landscape = signs; + declared = Some(real); + quantised = Some(q); + } + warnings.extend(landscape_warnings(&landscape, run)); + Ok(Program { source: src.to_string(), landscape, run, run_line, notes, warnings, declared, quantised }) +} + +/// What can be said of a landscape before it runs. +fn landscape_warnings(l: &Landscape, run: RunLine) -> Vec { + let n = l.len(); + let mut out = Vec::new(); + match l.regime() { + Regime::Open => {} + Regime::NarrowBand => out.push(Warning { + code: "narrow-band", + line: None, + text: "The cost per active variable sits in a narrow band where settling gets much harder as a problem grows. This check is a rule of thumb, exact only for dense couplings.".into(), + }), + Regime::CostDominates => out.push(Warning { + code: "cost-dominates", + line: None, + text: "The cost per active variable is high enough that leaving everything out may be the lowest energy. This check does not read the fields, so it can warn of a program its fields hold together, and that then solves.".into(), + }), + } + if matches!(run, RunLine::Minimize { .. }) && l.field().iter().all(|&b| b == 0) { + out.push(Warning { + code: "cold-start", + line: None, + text: "`minimize` starts with every variable at 0. With no field or pin, nothing moves it: the answer is all 0, whatever the couplings say.".into(), + }); + } + let free: Vec = (0..n).filter(|&i| l.field()[i] == 0 && (0..n).all(|j| l.coupling(i, j) == 0)).collect(); + if !free.is_empty() && free.len() < n && l.cost() == 0 { + let shown: Vec = free.iter().take(8).map(usize::to_string).collect(); + let listed = if free.len() > 8 { format!("{} and {} more", shown.join(", "), free.len() - 8) } else { shown.join(", ") }; + out.push(Warning { + code: "unconnected", + line: None, + text: if free.len() == 1 { format!("Variable {listed} has no coupling and no field, so nothing in the program decides it.") } else { format!("Variables {listed} have no coupling and no field, so nothing in the program decides them.") }, + }); + } + out +} + +#[cfg(test)] +mod tests { + use super::*; + + fn refused(src: &str) -> Refusal { + read(src).expect_err(src) + } + + #[test] + fn a_typo_gets_a_plain_message_with_its_line_and_a_suggestion() { + let r = refused("vars 3\noppos 0 1\nsolve\n"); + assert_eq!(r.line, Some(2)); + assert_eq!(r.plain(), "Line 2: `oppos` is not a statement epuML knows. Did you mean `oppose`?"); + } + + #[test] + fn each_refusal_names_its_line_where_one_means_anything() { + for (src, line) in [ + ("vars 2\nagree 0 9\nsolve\n", 2), + ("agree 0 1\nsolve\n", 1), + ("vars 2\nsolve restart=4\n", 2), + ("vars 2\nsolve\nagree 0 1\n", 3), + ("vars 2\ncouple 0 1 5\nsolve\n", 2), + ("vars 2\nagree 0 0\nsolve\n", 2), + ("vars 2\nagree 0 1 2\nsolve\n", 2), + ("vars 0\nsolve\n", 1), + ("vars 99999\nsolve\n", 1), + ("vars 2\nvars 3\nsolve\n", 2), + ("vars 2\nsolve t0=-1\n", 2), + ("vars 2\nsolve t0=0.00000001\n", 2), + ("vars 2\nsolve t0=nan\n", 2), + ("vars 2\nsolve restarts=0\n", 2), + ("vars 2\nsolve steps=x\n", 2), + ("vars 2\nsolve steps=3 steps=4\n", 2), + ("vars 2\nminimize t0=1\n", 2), + ("vars 2\nfield 0 99999999999999\nsolve\n", 2), + ("vars 2\ncost 99999999999999\nsolve\n", 2), + ("vars 2\ncost -1\nsolve\n", 2), + ("vars 2\npin 0 +\npin 0 -\nsolve\n", 3), + ("vars 2\nfield 0 3\npin 0 +\nsolve\n", 3), + ("vars 2\nlean 0 +\npin 0 -\nsolve\n", 3), + ("vars 2\npin 0 +\nfield 0 3\nsolve\n", 3), + ("vars 2\npin 0 +\nlean 0 -\nsolve\n", 3), + ("vars 2\nweight 0 1\nsolve\n", 2), + ("vars 2\nweight 0 0 1\nsolve\n", 2), + ("vars 2\nweight 0 9 1\nsolve\n", 2), + ("vars 2\nagree 0 1\nweight 0 1 2\nsolve\n", 3), + ("vars 2\nweight 0 1 2\nagree 0 1\nsolve\n", 3), + ("vars 2\npin 0 +\nweight 0 1 2\nsolve\n", 3), + ("vars 2\nprice free\nsolve\n", 2), + ("vars 2\n#! nonsense\nsolve\n", 2), + ("vars 2\n#! check different x\nsolve\n", 2), + ("vars 2\n#! check matches +x\nsolve\n", 2), + ("vars 2\n#! expect maybe\nsolve\n", 2), + ("vars 2\n#!\nsolve\n", 2), + ] { + assert_eq!(refused(src).line, Some(line), "{src:?}"); + } + assert_eq!(refused("").line, None); + assert!(refused("").plain().contains("vars N")); + assert!(refused("vars 2\n").plain().contains("solve")); + assert!(refused("#! check permutation 3\nvars 8\nsolve\n").plain().contains("does not fit")); + } + + #[test] + fn warnings_come_when_a_program_is_read() { + let has = |src: &str, c: &str| read(src).unwrap().warnings.iter().any(|w| w.code == c); + assert!(has("vars 2\nagree 0 1\nminimize\n", "cold-start")); + assert!(!has("vars 2\nagree 0 1\nfield 0 3\nminimize\n", "cold-start")); + assert!(has("vars 2\nagree 0 1\noppose 1 0\nsolve\n", "pair-repeated")); + assert!(has("vars 3\nagree 0 1\nsolve\n", "unconnected")); + assert!(!has("vars 3\nagree 0 1\ncost 1\nsolve\n", "unconnected"), "the cost decides a free variable"); + assert!(has("vars 2\nagree 0 1\ncost 1\nsolve\n", "cost-dominates")); + assert!(has("vars 2\nagree 0 1\nprice modeled\nsolve\n", "price-ignored")); + assert!(!has("vars 4\nagree 0 1\nagree 1 2\nagree 2 3\nsolve\n", "cost-dominates")); + } + + #[test] + fn a_program_means_the_landscape_its_builder_calls_stand_for() { + let p = read("# a comment\nvars 3\ncost 1\nagree 0 1 # and another\noppose 1 2\nfield 2 -4\nsolve t0=3 restarts=2 steps=9\n").unwrap(); + let mut l = Landscape::new(3); + l.couple(0, 1, 1).couple(1, 2, -1).set_field(2, -4).set_cost(1); + assert_eq!(p.landscape, l); + assert_eq!(p.run, RunLine::Solve { t0: Some(3.0), restarts: 2, sweeps: 9 }); + assert_eq!(p.run_line, 7); + } + + /// A pin outvotes everything else in the program, whatever the program holds. + #[test] + fn a_pin_outvotes_everything() { + let p = read("vars 3\ncost 2\nagree 0 1\nagree 0 2\nfield 1 500\npin 0 -\nminimize\n").unwrap(); + let w = p.landscape.lower(); + for s in [[1i8, 1, 1], [1, -1, 0], [0, 0, 0]] { + let mut t = s; + t[0] = 0; + assert!(w.field(&t, 0) < -2, "field {} on the pinned variable", w.field(&t, 0)); + } + } + + #[test] + fn weights_go_through_quantisation_and_say_what_was_lost() { + let p = read("vars 3\nweight 0 1 -1\nweight 0 2 -9\nweight 1 2 -30\nsolve\n").unwrap(); + assert_eq!(p.quantised.as_ref().unwrap().declared, 3); + assert!(p.declared.is_some()); + assert!(p.warnings.iter().any(|w| w.code == "sizes-lost"), "{:?}", p.warnings); + assert!(p.landscape.couplings().iter().all(|&w| w == 0 || w == -1)); + } + + #[test] + fn notes_are_read_and_checks_grade_both_ways() { + let p = read("#! title Hello\n#! plus in\nvars 3\n#! check different 0-1 1-2\nsolve\n").unwrap(); + assert_eq!((p.notes.title.as_deref(), p.notes.plus.as_deref()), (Some("Hello"), Some("in"))); + let d = Check::Different(vec![(0, 1), (1, 2)]); + assert!(grade(&d, &[1, -1, 0]).passed); + assert!(!grade(&d, &[0, 0, 1]).passed, "two 0s are the same state"); + let g = Check::Permutation(2); + assert!(grade(&g, &[1, 0, 0, 1]).passed); + assert!(!grade(&g, &[1, 1, 0, 0]).passed); + assert!(!grade(&g, &[-1, 0, 0, -1]).passed); + let b = Check::Balance(vec![3, 1, 2]); + assert!(grade(&b, &[1, -1, -1]).passed); + assert!(!grade(&b, &[1, 1, -1]).passed); + assert!(!grade(&Check::Balance(vec![1, 1, 5]), &[1, -1, 0]).passed, "a number left out fails"); + let m = Check::Matches(vec![1, 0, -1]); + assert!(grade(&m, &[1, 0, -1]).passed); + assert!(grade(&m, &[-1, 0, 1]).passed, "the mirror image counts"); + assert!(!grade(&m, &[1, 0, 1]).passed); + } + + #[test] + fn temperatures() { + assert!(check_temperature(0.0).is_ok()); + assert!(check_temperature(0.25).is_ok()); + assert!(check_temperature(-0.1).is_err()); + assert!(check_temperature(f64::NAN).is_err()); + assert!(check_temperature(f64::INFINITY).is_err()); + assert!(check_temperature(1e-9).is_err()); + } +} diff --git a/wai/wai-quantum/src/quantum_landscape/programs/assign-2x2.epuml b/wai/wai-quantum/src/quantum_landscape/programs/assign-2x2.epuml new file mode 100644 index 000000000..9b48e98c7 --- /dev/null +++ b/wai/wai-quantum/src/quantum_landscape/programs/assign-2x2.epuml @@ -0,0 +1,15 @@ +#! title Two workers, two jobs +#! about A 2×2 grid: row r is a worker, column c a job, and + means worker r takes job c. Each row and each column must hold exactly one +. This is the exact construction: within a row or a column, cells oppose; each cell has field 2; the cost is 1. At 2×2 its lowest states are exactly the two assignments. +#! expect works +#! check permutation 2 +vars 4 +cost 1 +oppose 0 1 +oppose 2 3 +oppose 0 2 +oppose 1 3 +field 0 2 +field 1 2 +field 2 2 +field 3 2 +solve restarts=8 steps=40 diff --git a/wai/wai-quantum/src/quantum_landscape/programs/assign-3x3.epuml b/wai/wai-quantum/src/quantum_landscape/programs/assign-3x3.epuml new file mode 100644 index 000000000..4a19fca60 --- /dev/null +++ b/wai/wai-quantum/src/quantum_landscape/programs/assign-3x3.epuml @@ -0,0 +1,34 @@ +#! title Three workers, three jobs: this fails +#! about The same exact construction on a 3×3 grid. A row can now sum to 1 with three choices in it, + and + and -, so the lowest energy no longer means one choice per row and column. The check catches it. This is why assignment above 2×2 does not run on this model. +#! expect fails +#! check permutation 3 +vars 9 +cost 1 +oppose 0 1 +oppose 0 2 +oppose 1 2 +oppose 3 4 +oppose 3 5 +oppose 4 5 +oppose 6 7 +oppose 6 8 +oppose 7 8 +oppose 0 3 +oppose 0 6 +oppose 3 6 +oppose 1 4 +oppose 1 7 +oppose 4 7 +oppose 2 5 +oppose 2 8 +oppose 5 8 +field 0 2 +field 1 2 +field 2 2 +field 3 2 +field 4 2 +field 5 2 +field 6 2 +field 7 2 +field 8 2 +solve restarts=8 steps=60 diff --git a/wai/wai-quantum/src/quantum_landscape/programs/blocks.epuml b/wai/wai-quantum/src/quantum_landscape/programs/blocks.epuml new file mode 100644 index 000000000..213d4ba6c --- /dev/null +++ b/wai/wai-quantum/src/quantum_landscape/programs/blocks.epuml @@ -0,0 +1,127 @@ +#! title Sixty-four variables in eight blocks +#! about Eight blocks of eight. Inside a block, links agree; between neighbouring blocks, links oppose. There are too many arrangements to check them all, about 3 to the 64th power, so the answer is graded against the pattern the links were built from. This program asks for 200 sweeps a start: with the default 40, some runs stop short of the pattern. +#! expect works +#! check matches ++++++++--------++++++++--------++++++++--------++++++++-------- +vars 64 +cost 1 +agree 0 1 +agree 0 2 +agree 1 2 +agree 1 3 +agree 2 3 +agree 2 4 +agree 3 4 +agree 3 5 +agree 4 5 +agree 4 6 +agree 5 6 +agree 5 7 +agree 6 7 +oppose 7 8 +oppose 3 12 +agree 8 9 +agree 8 10 +agree 9 10 +agree 9 11 +agree 10 11 +agree 10 12 +agree 11 12 +agree 11 13 +agree 12 13 +agree 12 14 +agree 13 14 +agree 13 15 +agree 14 15 +oppose 15 16 +oppose 11 20 +agree 16 17 +agree 16 18 +agree 17 18 +agree 17 19 +agree 18 19 +agree 18 20 +agree 19 20 +agree 19 21 +agree 20 21 +agree 20 22 +agree 21 22 +agree 21 23 +agree 22 23 +oppose 23 24 +oppose 19 28 +agree 24 25 +agree 24 26 +agree 25 26 +agree 25 27 +agree 26 27 +agree 26 28 +agree 27 28 +agree 27 29 +agree 28 29 +agree 28 30 +agree 29 30 +agree 29 31 +agree 30 31 +oppose 31 32 +oppose 27 36 +agree 32 33 +agree 32 34 +agree 33 34 +agree 33 35 +agree 34 35 +agree 34 36 +agree 35 36 +agree 35 37 +agree 36 37 +agree 36 38 +agree 37 38 +agree 37 39 +agree 38 39 +oppose 39 40 +oppose 35 44 +agree 40 41 +agree 40 42 +agree 41 42 +agree 41 43 +agree 42 43 +agree 42 44 +agree 43 44 +agree 43 45 +agree 44 45 +agree 44 46 +agree 45 46 +agree 45 47 +agree 46 47 +oppose 47 48 +oppose 43 52 +agree 48 49 +agree 48 50 +agree 49 50 +agree 49 51 +agree 50 51 +agree 50 52 +agree 51 52 +agree 51 53 +agree 52 53 +agree 52 54 +agree 53 54 +agree 53 55 +agree 54 55 +oppose 55 56 +oppose 51 60 +agree 56 57 +agree 56 58 +agree 57 58 +agree 57 59 +agree 58 59 +agree 58 60 +agree 59 60 +agree 59 61 +agree 60 61 +agree 60 62 +agree 61 62 +agree 61 63 +agree 62 63 +oppose 63 0 +oppose 59 4 +solve restarts=8 steps=200 diff --git a/wai/wai-quantum/src/quantum_landscape/programs/chain.epuml b/wai/wai-quantum/src/quantum_landscape/programs/chain.epuml new file mode 100644 index 000000000..0bea963af --- /dev/null +++ b/wai/wai-quantum/src/quantum_landscape/programs/chain.epuml @@ -0,0 +1,14 @@ +#! title A chain with one end held +#! about Variable 0 is held at +. Each link passes the sign along, or flips it. With one end held, the deterministic mode has one answer, and it gets there in a few sweeps. +#! expect works +#! check matches +++--++- +vars 8 +pin 0 + +agree 0 1 +agree 1 2 +oppose 2 3 +agree 3 4 +oppose 4 5 +agree 5 6 +oppose 6 7 +minimize diff --git a/wai/wai-quantum/src/quantum_landscape/programs/clusters.epuml b/wai/wai-quantum/src/quantum_landscape/programs/clusters.epuml new file mode 100644 index 000000000..b2dbde52a --- /dev/null +++ b/wai/wai-quantum/src/quantum_landscape/programs/clusters.epuml @@ -0,0 +1,19 @@ +#! title Eight people, two teams, one quarrel +#! about Eight people split into two teams, or sit out. Some pairs get on and want the same team; some do not. People 0, 1 and 2 all quarrel with each other, so no split can keep all three apart: the lowest energy says how few links have to break. +#! item person +#! plus team one +#! minus team two +#! zero sits out +#! expect works +vars 8 +cost 1 +oppose 0 1 +oppose 1 2 +oppose 0 2 +agree 3 4 +agree 4 5 +agree 3 5 +oppose 5 6 +oppose 3 6 +agree 6 7 +solve restarts=8 steps=40 diff --git a/wai/wai-quantum/src/quantum_landscape/programs/cold-constraint.epuml b/wai/wai-quantum/src/quantum_landscape/programs/cold-constraint.epuml new file mode 100644 index 000000000..e6b441c93 --- /dev/null +++ b/wai/wai-quantum/src/quantum_landscape/programs/cold-constraint.epuml @@ -0,0 +1,15 @@ +#! title Two workers, two jobs, deterministic: this fails +#! about The 2×2 assignment again, at temperature 0. Every cell starts at 0, sees field 2, and turns on; then each sees its row and column full, and turns off; and so on. It never comes to rest. Constraint problems need the temperature. +#! expect fails +#! check permutation 2 +vars 4 +cost 1 +oppose 0 1 +oppose 2 3 +oppose 0 2 +oppose 1 3 +field 0 2 +field 1 2 +field 2 2 +field 3 2 +minimize diff --git a/wai/wai-quantum/src/quantum_landscape/programs/cold-start.epuml b/wai/wai-quantum/src/quantum_landscape/programs/cold-start.epuml new file mode 100644 index 000000000..b8c9eedba --- /dev/null +++ b/wai/wai-quantum/src/quantum_landscape/programs/cold-start.epuml @@ -0,0 +1,31 @@ +#! title Twelve talks, deterministic: this fails +#! about The talks problem at temperature 0, from every talk left out. Nothing pushes any talk in, so nothing moves, and it stops at the first state it was given. +#! item talk +#! plus room A +#! minus room B +#! zero left out +#! expect fails +vars 12 +cost 1 +agree 0 1 +agree 0 2 +agree 0 3 +agree 1 2 +agree 1 3 +agree 2 3 +agree 4 5 +agree 4 6 +agree 4 7 +agree 5 6 +agree 5 7 +agree 6 7 +oppose 0 4 +oppose 1 5 +oppose 2 6 +oppose 3 7 +oppose 8 0 +oppose 8 1 +agree 9 4 +agree 9 5 +oppose 10 11 +minimize diff --git a/wai/wai-quantum/src/quantum_landscape/programs/colour-four.epuml b/wai/wai-quantum/src/quantum_landscape/programs/colour-four.epuml new file mode 100644 index 000000000..8c9cc6c1f --- /dev/null +++ b/wai/wai-quantum/src/quantum_landscape/programs/colour-four.epuml @@ -0,0 +1,11 @@ +#! title Three colours, four regions: this fails +#! about Four regions, two triangles sharing a side. There are six good ways to colour it, and none of them has the lowest energy, for the same reason: 0 is not just a third colour. +#! expect fails +#! check different 0-1 0-2 1-2 1-3 2-3 +vars 4 +oppose 0 1 +oppose 0 2 +oppose 1 2 +oppose 1 3 +oppose 2 3 +solve restarts=8 steps=40 diff --git a/wai/wai-quantum/src/quantum_landscape/programs/colour-six.epuml b/wai/wai-quantum/src/quantum_landscape/programs/colour-six.epuml new file mode 100644 index 000000000..3c3091df8 --- /dev/null +++ b/wai/wai-quantum/src/quantum_landscape/programs/colour-six.epuml @@ -0,0 +1,15 @@ +#! title Three colours, six regions: this fails +#! about Six regions, two rows of three, each touching the ones beside and across from it. Give neighbours different colours, with +, - and 0 as the three colours. There are twelve good ways to colour it. But the model's 0 is special: two neighbouring 0s cost nothing, so the lowest energy never colours it properly. Problems that need three interchangeable options do not run on this model. +#! expect fails +#! check different 0-1 1-2 0-2 3-4 4-5 3-5 0-3 1-4 2-5 +vars 6 +oppose 0 1 +oppose 1 2 +oppose 0 2 +oppose 3 4 +oppose 4 5 +oppose 3 5 +oppose 0 3 +oppose 1 4 +oppose 2 5 +solve restarts=8 steps=40 diff --git a/wai/wai-quantum/src/quantum_landscape/programs/piles.epuml b/wai/wai-quantum/src/quantum_landscape/programs/piles.epuml new file mode 100644 index 000000000..bf9b85885 --- /dev/null +++ b/wai/wai-quantum/src/quantum_landscape/programs/piles.epuml @@ -0,0 +1,37 @@ +#! title Two equal piles: this fails +#! about Split eight numbers, 2 7 3 9 4 1 6 8, into two piles of equal sum. A split exists: 9, 7 and 4 against the rest. The weights hold the sizes of the numbers, and the machine's couplings keep only signs, so the sizes are lost before anything runs. The compile warning says so first. +#! expect fails +#! check balance 2 7 3 9 4 1 6 8 +# +# `weight I J W` is a real-valued coupling. It goes through quantisation to signs. +# For equal piles, the weight between numbers a and b is -a×b. +vars 8 +weight 0 1 -14 +weight 0 2 -6 +weight 0 3 -18 +weight 0 4 -8 +weight 0 5 -2 +weight 0 6 -12 +weight 0 7 -16 +weight 1 2 -21 +weight 1 3 -63 +weight 1 4 -28 +weight 1 5 -7 +weight 1 6 -42 +weight 1 7 -56 +weight 2 3 -27 +weight 2 4 -12 +weight 2 5 -3 +weight 2 6 -18 +weight 2 7 -24 +weight 3 4 -36 +weight 3 5 -9 +weight 3 6 -54 +weight 3 7 -72 +weight 4 5 -4 +weight 4 6 -24 +weight 4 7 -32 +weight 5 6 -6 +weight 5 7 -8 +weight 6 7 -48 +solve restarts=8 steps=40 diff --git a/wai/wai-quantum/src/quantum_landscape/programs/selection.epuml b/wai/wai-quantum/src/quantum_landscape/programs/selection.epuml new file mode 100644 index 000000000..b87ae655d --- /dev/null +++ b/wai/wai-quantum/src/quantum_landscape/programs/selection.epuml @@ -0,0 +1,26 @@ +#! title Six of fourteen that belong together +#! about Fourteen items, six of which belong together, each on one of two sides. The links say which of the six agree and which oppose. Every item chosen costs 1, so the eight that do not belong should be left out. The check compares the answer with the hidden six. +#! item item +#! plus side one +#! minus side two +#! zero left out +#! expect works +#! check matches 0+00+0-00+0-0+ +vars 14 +cost 1 +agree 1 4 +oppose 1 6 +agree 1 9 +oppose 1 11 +agree 1 13 +oppose 4 6 +agree 4 9 +oppose 4 11 +agree 4 13 +oppose 6 9 +agree 6 11 +oppose 6 13 +oppose 9 11 +agree 9 13 +oppose 11 13 +solve restarts=8 steps=40 diff --git a/wai/wai-quantum/src/quantum_landscape/programs/talks.epuml b/wai/wai-quantum/src/quantum_landscape/programs/talks.epuml new file mode 100644 index 000000000..ab45b54ec --- /dev/null +++ b/wai/wai-quantum/src/quantum_landscape/programs/talks.epuml @@ -0,0 +1,36 @@ +#! title Twelve talks, two rooms +#! about Twelve talks go into room A or room B, or are left out. Talks that share an audience must not be in the same room. Talks in a series belong in the same room. Every talk that runs takes a slot, so a talk with little reason to run stays out. +#! item talk +#! plus room A +#! minus room B +#! zero left out +#! expect works +# +# Three states per variable: + is room A, - is room B, 0 is left out. +# `agree I J` talks I and J form a series: the same room. +# `oppose I J` talks I and J share an audience: different rooms. +# `cost 1` each talk that runs costs 1. +vars 12 +cost 1 +agree 0 1 +agree 0 2 +agree 0 3 +agree 1 2 +agree 1 3 +agree 2 3 +agree 4 5 +agree 4 6 +agree 4 7 +agree 5 6 +agree 5 7 +agree 6 7 +oppose 0 4 +oppose 1 5 +oppose 2 6 +oppose 3 7 +oppose 8 0 +oppose 8 1 +agree 9 4 +agree 9 5 +oppose 10 11 +solve restarts=8 steps=40 diff --git a/wai/wai-quantum/src/quantum_landscape/programs/too-costly.epuml b/wai/wai-quantum/src/quantum_landscape/programs/too-costly.epuml new file mode 100644 index 000000000..01e62a37a --- /dev/null +++ b/wai/wai-quantum/src/quantum_landscape/programs/too-costly.epuml @@ -0,0 +1,22 @@ +#! title The six of fourteen, at too high a cost: this fails +#! about The same hidden six, with every chosen item costing 3. Each of the six has five links, so choosing them all is no longer cheaper than choosing nothing. The lowest energy is to leave everything out, and that does not find the six. The compile warning says the cost is high. +#! expect fails +#! check matches 0+00+0-00+0-0+ +vars 14 +cost 3 +agree 1 4 +oppose 1 6 +agree 1 9 +oppose 1 11 +agree 1 13 +oppose 4 6 +agree 4 9 +oppose 4 11 +agree 4 13 +oppose 6 9 +agree 6 11 +oppose 6 13 +oppose 9 11 +agree 9 13 +oppose 11 13 +solve restarts=8 steps=40 diff --git a/wai/wai-quantum/src/quantum_landscape/programs/typo.epuml b/wai/wai-quantum/src/quantum_landscape/programs/typo.epuml new file mode 100644 index 000000000..4997cb72e --- /dev/null +++ b/wai/wai-quantum/src/quantum_landscape/programs/typo.epuml @@ -0,0 +1,7 @@ +#! title A typing mistake: this fails +#! about A misspelt statement. The emulator refuses it, and says which line and what it expected. +#! expect fails +vars 3 +agree 0 1 +oppos 1 2 +solve diff --git a/wai/wai-quantum/src/quantum_landscape/run.rs b/wai/wai-quantum/src/quantum_landscape/run.rs new file mode 100644 index 000000000..6509df261 --- /dev/null +++ b/wai/wai-quantum/src/quantum_landscape/run.rs @@ -0,0 +1,868 @@ +//! A job, run: the plan a program and its options give, the kernels on a +//! member, and what comes back, graded. The state and the work come from the +//! member. The energy, the truth, the checks, the verdict, the price and the +//! replay digest do not. +//! +//! Nothing here checks a job's budget: [`super::job`] does, before a [`Job`] +//! is made, with [`job_operations`] and [`job_bytes`]. + +use super::classical; +use super::energy::Weights; +use super::member::{self, Certified, Chain, KERNEL_SAMPLE, KERNEL_SETTLE}; +use super::price::{self, Mode, group, joules}; +use super::program::{Check, Graded as CheckGraded, Program, Refusal, RunLine, check_items, check_temperature, grade}; +use super::sample; +use super::settle::{Term, Workload, state_hash}; +use super::truth::{Truth, exhaustive_checked, exhaustive_declared}; +use super::{CHUNK_CELLS, CHUNK_CHAINS, EXHAUSTIVE_MAX_VARS, PROTOCOL}; +use serde_json::{Value, json}; + +/// The seed when a request gives none. +pub const DEFAULT_SEED: u64 = 1; +/// Starts per run when a `minimize` program is run above temperature 0, as `solve`'s default. +pub const DEFAULT_RESTARTS: u32 = 8; + +/// Where a run's starting temperature came from. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum From { + /// The landscape's own default ([`super::energy::Landscape::default_temperature`]). + Landscape, + /// The program's `t0=`. + Program, + /// The request. + Request, +} + +impl From { + fn label(self) -> &'static str { + match self { + From::Landscape => "landscape", + From::Program => "program", + From::Request => "request", + } + } +} + +/// What a run does, once the program and the request are read together. +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum Plan { + /// Temperature 0, from every cell at 0, stopping when it comes to rest. `minimize`. + Settle { sweeps: u32 }, + /// Temperature 0, from random starts, every sweep run. `solve` at temperature 0. + Quench { restarts: u32, sweeps: u32 }, + /// Above temperature 0, from random starts, cooling to 0. `solve`, or a + /// `minimize` asked for a temperature above 0 (with [`DEFAULT_RESTARTS`]). + Anneal { t0: f64, from: From, restarts: u32, sweeps: u32 }, +} + +impl Plan { + /// The plan of a program, with a request's temperature if it gives one. + pub fn of(p: &Program, temperature: Option) -> Result { + if let Some(t) = temperature { + check_temperature(t).map_err(Refusal::general)?; + } + Ok(match (p.run, temperature) { + (RunLine::Minimize { sweeps }, None) => Plan::Settle { sweeps }, + (RunLine::Minimize { sweeps }, Some(t)) if t == 0.0 => Plan::Settle { sweeps }, + (RunLine::Minimize { sweeps }, Some(t)) => Plan::Anneal { t0: t, from: From::Request, restarts: DEFAULT_RESTARTS, sweeps }, + (RunLine::Solve { restarts, sweeps, .. }, Some(t)) if t == 0.0 => Plan::Quench { restarts, sweeps }, + (RunLine::Solve { restarts, sweeps, .. }, Some(t)) => Plan::Anneal { t0: t, from: From::Request, restarts, sweeps }, + (RunLine::Solve { t0: None, restarts, sweeps }, None) => Plan::Anneal { t0: p.landscape.default_temperature(), from: From::Landscape, restarts, sweeps }, + (RunLine::Solve { t0: Some(t), restarts, sweeps }, None) if t == 0.0 => Plan::Quench { restarts, sweeps }, + (RunLine::Solve { t0: Some(t), restarts, sweeps }, None) => Plan::Anneal { t0: t, from: From::Program, restarts, sweeps }, + }) + } + + pub fn restarts(self) -> u64 { + match self { + Plan::Settle { .. } => 1, + Plan::Quench { restarts, .. } | Plan::Anneal { restarts, .. } => u64::from(restarts), + } + } + + pub fn sweeps(self) -> u32 { + match self { + Plan::Settle { sweeps } | Plan::Quench { sweeps, .. } | Plan::Anneal { sweeps, .. } => sweeps, + } + } + + pub fn mode(self) -> Mode { + match self { + Plan::Settle { .. } | Plan::Quench { .. } => Mode::Settle, + Plan::Anneal { .. } => Mode::Sample, + } + } + + fn name(self) -> &'static str { + match self { + Plan::Settle { .. } => "settle", + Plan::Quench { .. } => "quench", + Plan::Anneal { .. } => "sample", + } + } + + /// 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 { + match self { + Plan::Settle { .. } => KERNEL_SETTLE, + _ => KERNEL_SAMPLE, + } + } + + /// Every sweep's temperature, for a plan that runs chains. + pub fn temperatures(self) -> Vec { + match self { + Plan::Settle { sweeps } | Plan::Quench { sweeps, .. } => vec![0.0; sweeps as usize], + Plan::Anneal { t0, sweeps, .. } => sample::schedule(t0, sweeps), + } + } + + fn words(self) -> String { + match self { + Plan::Settle { sweeps } => format!("Deterministic (temperature 0): every variable starts at 0, then settles, for at most {}.", price::plural(u64::from(sweeps), "sweep")), + Plan::Quench { restarts, sweeps } => format!("Deterministic (temperature 0): {} of {} each. The lowest energy found is kept.", price::plural(u64::from(restarts), "random start"), price::plural(u64::from(sweeps), "sweep")), + Plan::Anneal { t0, from, restarts, sweeps } => format!( + "Sampling (above temperature 0): {} of {} each, cooling from temperature {} to 0. {} The lowest energy found is kept. The randomness is classical, from a seeded generator.", + price::plural(u64::from(restarts), "random start"), + price::plural(u64::from(sweeps), "sweep"), + shown_temperature(t0), + match from { + From::Landscape => "That temperature is the default: the square root of the mean couplings a variable has, and at least 1.", + From::Program => "The program asks for that temperature.", + From::Request => "The request asks for that temperature.", + }, + ), + } + } + + /// The most operations the sweeps of this plan can take: every coupling + /// read and every cell updated, over every start. + pub fn operations_bound(self, p: &Program) -> u64 { + let per_sweep = 2 * p.landscape.pairs() + p.landscape.len() as u64; + 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. + 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) + } +} + +/// The plan `compare` runs the emulator's sampling mode with: the program's own +/// starts and sweeps, at a request's temperature above 0, else the program's, +/// else the default. +pub fn compare_plan(p: &Program, temperature: Option) -> Result { + if let Some(t) = temperature { + check_temperature(t).map_err(Refusal::general)?; + } + let l = &p.landscape; + let (restarts, sweeps, t0) = match p.run { + RunLine::Solve { restarts, sweeps, t0 } => (restarts, sweeps, t0.unwrap_or_else(|| l.default_temperature())), + RunLine::Minimize { sweeps } => (DEFAULT_RESTARTS, sweeps, l.default_temperature()), + }; + let t0 = match temperature { + Some(t) if t > 0.0 => t, + _ if t0 > 0.0 => t0, + _ => l.default_temperature(), + }; + Ok(Plan::Anneal { t0, from: From::Request, restarts, sweeps }) +} + +/// The most operations a job can take, known before any of it runs: reading +/// the couplings into rows, the exhaustive truth where there is one (every +/// arrangement, its field updates and its checks, and on declared weights +/// every arrangement's energy), and `runs`: each plan, that many times. +pub fn job_operations(p: &Program, runs: &[(Plan, u64)]) -> u64 { + let n = p.landscape.len() as u64; + let mut total = n * n; + if p.landscape.len() <= EXHAUSTIVE_MAX_VARS { + let all = 3u64.pow(n as u32); + let checks: u64 = p.notes.checks.iter().map(|c| check_items(c) as u64).sum(); + total = total.saturating_add(all.saturating_mul(n + 1 + checks)); + if p.declared.is_some() { + total = total.saturating_add(all.saturating_mul(n * n)); + } + } + for &(plan, count) in runs { + total = total.saturating_add(plan.run_bound(p).saturating_mul(count)); + } + total +} + +/// 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. +pub fn job_bytes(p: &Program, lines: u64) -> u64 { + let n = p.landscape.len() as u64; + let nonzeros = 2 * p.landscape.pairs(); + let weights = if p.declared.is_some() { 8 * n * n } else { 0 }; + let batch = 2 * (CHUNK_CELLS as u64) + 64 * (CHUNK_CHAINS as u64); + (p.source.len() as u64) + .saturating_add(n * n) + .saturating_add(weights) + .saturating_add(16 * nonzeros + 64 * n) + .saturating_add(batch) + .saturating_add(lines.saturating_mul(256)) + .saturating_add(1 << 20) +} + +/// A temperature as a sentence gives it, to three places. The answer's `temperature` keeps every bit. +pub fn shown_temperature(t: f64) -> String { + let s = format!("{t:.3}"); + s.trim_end_matches('0').trim_end_matches('.').to_string() +} + +/// The exhaustive truth for a program, worked out once. +#[derive(Clone, Debug, PartialEq)] +pub struct Exact { + pub truth: Option, + pub declared: Option, +} + +impl Exact { + pub fn of(p: &Program) -> Exact { + Exact { truth: exhaustive_checked(&p.landscape, &p.notes.checks), declared: p.declared.as_ref().and_then(exhaustive_declared) } + } +} + +/// A program ready to run: read, its budget checked, lowered once, its truth worked out. +pub struct Job { + pub program: Program, + pub workload: Workload, + pub exact: Exact, +} + +impl Job { + /// Lower a program and work out its truth. [`super::job`] checks the budget first. + pub fn new(program: Program) -> Job { + let workload = program.landscape.lower(); + let exact = Exact::of(&program); + Job { program, workload, exact } + } +} + +/// A state as text: one `+`, `-` or `0` a variable. +pub fn state_text(state: &[i8]) -> String { + state.iter().map(|&s| match s { 1 => '+', -1 => '-', _ => '0' }).collect() +} + +/// The digest of a run: the program, how it was run and what state it reached. +/// Two members that give the same bits give the same digest, whatever they +/// spent, and a change to any of those inputs changes it. It checks that a run +/// reproduced; the seal is what says who ran it. +fn replay_digest(p: &Program, seed: u64, plan: Plan, state: &[i8], energy: i64) -> String { + let mut h = blake3::Hasher::new(); + h.update(PROTOCOL.as_bytes()); + h.update(&[0]); + h.update(&(p.source.len() as u64).to_le_bytes()); + h.update(p.source.as_bytes()); + h.update(&seed.to_le_bytes()); + h.update(plan.name().as_bytes()); + h.update(&[0]); + if let Plan::Anneal { t0, .. } = plan { + h.update(&t0.to_bits().to_le_bytes()); + } + h.update(&plan.restarts().to_le_bytes()); + h.update(&plan.sweeps().to_le_bytes()); + let cells: Vec = state.iter().map(|&s| (s + 1) as u8).collect(); + h.update(&cells); + h.update(&energy.to_le_bytes()); + h.finalize().to_hex().to_string() +} + +/// What the kernels leave, over every start of a run. +struct Found { + state: Vec, + energy: i64, + accumulates: u64, + draws: u64, + sweeps: u64, + term: Option, +} + +fn execute(job: &Job, plan: Plan, seed: u64, c: &Certified<'_>) -> Result { + 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())); + match plan { + Plan::Settle { sweeps } => { + 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); + Ok(Found { energy, accumulates: o.accumulates, draws: o.draws, sweeps: u64::from(o.sweeps), term: Some(o.term), state: o.state }) + } + Plan::Quench { restarts, .. } | Plan::Anneal { restarts, .. } => run_chains(job, &plan.temperatures(), restarts, seed, c), + } +} + +/// Every start of a run, a batch at a time, keeping the first state of lowest energy. +fn run_chains(job: &Job, temperatures: &[f64], restarts: u32, seed: u64, c: &Certified<'_>) -> Result { + 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) + .map(|k| { + let s = sample::chain_seed(seed, k); + Chain { start: sample::start(s, n), seed: s } + }) + .collect(); + let outcomes = m.chains(w, &chains, temperatures).map_err(refused)?; + if outcomes.len() != chains.len() { + return Err(refused(format!("it returned {} chains for {}", outcomes.len(), chains.len()))); + } + for o in outcomes { + member::check_chain(&o, w, temperatures).map_err(refused)?; + accumulates += o.accumulates; + draws += o.draws; + let e = w.energy(&o.state); + if best.as_ref().is_none_or(|b| e < b.1) { + best = Some((o.state, e)); + } + } + r = 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 }) +} + +fn truth_json(t: &Truth) -> Value { + json!({ + "arrangements": t.arrangements, + "lowest": t.lowest, + "at_lowest": t.at_lowest, + "at_lowest_passing": t.at_lowest_passing, + "example": state_text(&t.example), + }) +} + +fn graded_json(g: &CheckGraded) -> Value { + json!({ "kind": g.kind, "passed": g.passed, "detail": g.detail }) +} + +/// The verdict and the sentence that goes with it. +fn judge(found: &Found, exact: &Exact, reached: Option, declared_reached: Option, declared_energy: Option, checks: &[CheckGraded]) -> (&'static str, String) { + let e = found.energy; + let failed = checks.iter().find(|c| !c.passed); + let tail = |base: String| match failed { + Some(c) => format!("{base} {}", c.detail), + None => base, + }; + match found.term { + Some(Term::TwoCycle) => { + return ("failed", tail("It never came to rest: the deterministic mode kept flipping between two states, so this answer means nothing. Try a temperature above 0.".into())); + } + Some(Term::Bound) => return ("failed", tail("It ran out of sweeps before coming to rest.".into())), + _ => {} + } + if declared_reached == Some(false) { + let lowest = exact.declared.as_ref().map(|t| t.lowest).unwrap_or(f64::NAN); + return ( + "failed", + tail(format!("It reached energy {e} on the signs its couplings could keep. On the weights as you declared them, this answer has energy {} and the lowest possible is {lowest}.", declared_energy.unwrap_or(f64::NAN))), + ); + } + if let Some(t) = &exact.truth + && let Some(passing) = t.at_lowest_passing + && passing < t.at_lowest + { + let held = if t.at_lowest == 1 { "Only one arrangement has the lowest energy".to_string() } else { format!("{} arrangements share the lowest energy", group(t.at_lowest)) }; + let solve = match passing { + 0 if t.at_lowest == 1 => "and it does not solve the problem".to_string(), + 0 => "and none of them solves the problem".to_string(), + 1 => "and only 1 of them solves the problem".to_string(), + k => format!("and only {} of them solve the problem", group(k)), + }; + let this = match (failed, reached) { + (None, Some(true)) => " This run's answer is one of those that do, by luck of the draw.", + (None, _) => " This run's answer passes the check, without the lowest energy.", + (Some(_), _) => "", + }; + return ("failed", tail(format!("{held}, {solve}: this landscape does not express the problem, whatever a run returns.{this}"))); + } + if failed.is_some() { + let base = if reached == Some(true) { format!("It reached energy {e}, the lowest this landscape has, and that still does not solve the problem.") } else { format!("It reached energy {e}, and that does not solve the problem.") }; + return ("failed", tail(base)); + } + match (&exact.truth, reached) { + (Some(t), Some(true)) => { + let shared = if t.at_lowest == 1 { "only this arrangement has it".to_string() } else { format!("{} of them share it", group(t.at_lowest)) }; + let mut s = format!("It reached energy {e}. That is the lowest possible: all {} arrangements were checked, and {shared}.", group(t.arrangements)); + if found.state.iter().all(|&x| x == 0) { + s.push_str(" The answer leaves everything out, so it took no search to find."); + } + ("solved", s) + } + (Some(t), _) => ("missed", format!("It reached energy {e}, and the lowest possible is {}: all {} arrangements were checked. This run missed it.", t.lowest, group(t.arrangements))), + (None, _) if !checks.is_empty() => ("solved", format!("It reached energy {e}, and the answer passes the problem's own check. There are too many arrangements to check them all, so nothing here says {e} is the lowest.")), + (None, _) => ("unchecked", format!("It reached energy {e}. There are too many arrangements to check them all, so nothing here says whether that is the lowest.")), + } +} + +fn grade_all(p: &Program, state: &[i8]) -> Vec { + p.notes + .checks + .iter() + .map(|check: &Check| { + let mut g = grade(check, state); + if let (Check::Matches(want), false) = (check, g.passed) { + g.detail.push_str(&format!(" The planted answer has energy {}.", p.landscape.energy(want))); + } + g + }) + .collect() +} + +fn declared_json(real: &Weights, p: &Program, state: &[i8], exact: &Exact) -> (Value, Option, Option) { + let energy = real.energy(state); + let reached = exact.declared.as_ref().map(|t| (energy - t.lowest).abs() <= 1e-9); + let q = p.quantised.as_ref().expect("a program with weights has their quantisation"); + ( + json!({ + "energy": energy, + "truth": exact.declared.as_ref().map(truth_json), + "reached": reached, + "quantised": { "scale": q.scale, "declared": q.declared, "kept": q.kept, "dropped": q.dropped, "relative_error": q.relative_error, "fits": q.fits() }, + }), + reached, + Some(energy), + ) +} + +/// 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 (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 { + Some(real) => { + let (d, r, e) = declared_json(real, p, &found.state, exact); + (Some(d), r, e) + } + None => (None, None, None), + }; + let checks = grade_all(p, &found.state); + 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::>() }))) + .collect(); + let (temp, from) = match plan { + Plan::Anneal { t0, from, .. } => (t0, Some(from.label())), + _ => (0.0, None), + }; + let ended = match found.term { + Some(Term::FixedPoint) => "came to rest", + Some(Term::TwoCycle) => "kept flipping between two states", + Some(Term::Bound) => "ran out of sweeps", + None => "every sweep run", + }; + 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() { + Some(r) => json!({ "joules_micro": r.joules_micro, "class": r.class }), + None => Value::Null, + }; + Ok(json!({ + "protocol": PROTOCOL, + "label": m.label(), + "member": member::describe(c, plan.kernel()), + "program_digest": blake3::hash(p.source.as_bytes()).to_hex().to_string(), + "title": p.notes.title, + "about": p.notes.about, + "item": p.notes.item, + "expect": p.notes.expect, + "variables": n, + "pairs": p.landscape.pairs(), + "seed": seed.to_string(), + "mode": plan.name(), + "mode_words": plan.words(), + "temperature": temp, + "temperature_from": from, + "restarts": plan.restarts(), + "sweeps": plan.sweeps(), + "state": state_text(&found.state), + "groups": groups, + "energy": found.energy, + "truth": exact.truth.as_ref().map(truth_json), + "reached": reached, + "declared": declared, + "warnings": p.warnings.iter().map(|w| json!({ "code": w.code, "line": w.line, "text": w.text })).collect::>(), + "checks": checks.iter().map(graded_json).collect::>(), + "verdict": verdict, + "summary": summary, + "work": { + "sweeps": found.sweeps, + "accumulates": found.accumulates, + "cell_updates": found.sweeps * n as u64, + "draws": found.draws, + "ended": ended, + "rests": rests, + }, + "joules": joules_statement, + "state_hash": format!("{:016x}", state_hash(&found.state)), + "replay": replay_digest(p, seed, plan, &found.state, found.energy), + })) +} + +// ── the temperature sweep ──────────────────────────────────────────────────────────────────── + +/// The temperatures a sweep tries when none are asked for: from cold, through +/// the default, to the largest row of couplings, which is the hot end. +pub fn default_temperatures(p: &Program) -> Vec { + let mut t = vec![0.0, 0.25, 0.5, 1.0, 2.0, 4.0, 8.0]; + for extra in [p.landscape.default_temperature(), p.landscape.coupling_scale() as f64] { + if !t.contains(&extra) { + t.push(extra); + } + } + t.sort_by(f64::total_cmp); + t +} + +/// The plan at each temperature of a sweep, with how many runs it takes: +/// `seeds`, or one for a settle, which has no randomness. +pub fn sweep_plans(p: &Program, temperatures: &[f64], seeds: u32) -> Result, Refusal> { + temperatures + .iter() + .map(|&t| { + let plan = Plan::of(p, Some(t))?; + Ok((t, plan, if matches!(plan, Plan::Settle { .. }) { 1 } else { seeds })) + }) + .collect() +} + +/// One program at each temperature, over a run of seeds, with the same budget at each. +pub fn sweep_on(c: &Certified<'_>, job: &Job, temperatures: &[f64], seeds: u32, first_seed: u64) -> Result { + let (p, exact) = (&job.program, &job.exact); + let m = c.member(); + let plans = sweep_plans(p, temperatures, seeds)?; + let default = p.landscape.default_temperature(); + 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)?; + best = best.min(found.energy); + sum += i128::from(found.energy); + accumulates = found.accumulates; + sweeps = found.sweeps; + reached += u32::from(exact.truth.as_ref().is_some_and(|tr| found.energy as f64 == tr.lowest)); + passed += u32::from(p.notes.checks.iter().all(|c| grade(c, &found.state).passed)); + } + let figures = price::priced(accumulates, plan.mode(), sweeps); + let left_out = figures["left_out"]["pj"].as_f64().unwrap_or(0.0); + rows.push(json!({ + "temperature": t, + "is_default": t == default && t > 0.0, + "mode": plan.name(), + "runs": runs, + "reached_lowest": exact.truth.as_ref().map(|_| reached), + "passed_checks": (!p.notes.checks.is_empty()).then_some(passed), + "best_energy": best, + "mean_energy": (sum as f64) / f64::from(runs), + "accumulates_per_run": accumulates, + "priced_per_run": format!("about {}", joules(figures["pj"].as_f64().unwrap_or(0.0))), + "left_out_per_run": format!("about {}", joules(left_out)), + })); + } + Ok(json!({ + "protocol": PROTOCOL, + "label": m.label(), + "member": { "id": m.id(), "class": m.class() }, + "lowest": exact.truth.as_ref().map(|t| t.lowest), + "default_temperature": default, + "rows": rows, + })) +} + +// ── the same instance on classical solvers ─────────────────────────────────────────────────── + +/// The emulator and the classical solvers on one program, at the same budget of operations a run. +pub fn compare_on(c: &Certified<'_>, job: &Job, seeds: u32, first_seed: u64, temperature: Option) -> Result { + let (p, exact) = (&job.program, &job.exact); + let l = &p.landscape; + let w = &job.workload; + let n = l.len() as u64; + let plan = compare_plan(p, temperature)?; + let Plan::Anneal { t0, restarts, sweeps, .. } = plan else { unreachable!("a comparison samples") }; + let cell_updates = n * u64::from(restarts) * u64::from(sweeps); + let check = |state: &[i8]| p.notes.checks.iter().all(|k| grade(k, state).passed); + let sampling = format!("emulator, sampling, starting at {}", shown_temperature(t0)); + let mut tallies: Vec<(String, bool, Vec, u32, u64, u64)> = Vec::new(); + 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)?; + t.2.push(found.energy); + t.3 += u32::from(check(&found.state)); + t.5 += found.accumulates; + t.4 += found.accumulates + cell_updates; + } + tallies.push(t); + } + let budget = tallies[0].4 / u64::from(seeds.max(1)); + for (name, solve) in classical::SOLVERS { + let mut t = (name.to_string(), true, Vec::new(), 0u32, 0u64, 0u64); + for k in 0..seeds { + let f = solve(w, l, budget, first_seed.wrapping_add(u64::from(k))); + t.2.push(f.energy); + t.3 += u32::from(check(&f.state)); + t.5 += f.accumulates; + t.4 += f.operations(); + } + tallies.push(t); + } + let found = tallies.iter().flat_map(|t| t.2.iter().copied()).min().unwrap_or(0); + let (target, exact_target) = match &exact.truth { + Some(t) => (t.lowest as i64, true), + None => (found, false), + }; + let rows: Vec = tallies + .iter() + .map(|t| { + json!({ + "solver": t.0, + "classical": t.1, + "runs": seeds, + "reached_target": t.2.iter().filter(|&&v| v == target).count(), + "passed_checks": (!p.notes.checks.is_empty()).then_some(t.3), + "best_energy": t.2.iter().copied().min(), + "operations_per_run": t.4 / u64::from(seeds.max(1)), + "accumulates_per_run": t.5 / u64::from(seeds.max(1)), + }) + }) + .collect(); + Ok(json!({ + "protocol": PROTOCOL, + "label": c.member().label(), + "member": { "id": c.member().id(), "class": c.member().class() }, + "target": target, + "target_is_exact": exact_target, + "arrangements": exact.truth.as_ref().map(|t| t.arrangements), + "rows": rows, + })) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::quantum_landscape::member::{CpuReference, Member, Parity, certify, named}; + use crate::quantum_landscape::settle::Outcome; + use crate::quantum_landscape::program::read; + use crate::quantum_landscape::settle::Workload; + + fn job(src: &str) -> Job { + Job::new(read(src).unwrap()) + } + + fn run(src: &str, seed: u64, t: Option) -> Value { + run_on(&Certified::reference(), &job(src), seed, t).unwrap() + } + + const FOUR: &str = "vars 4\ncost 1\nagree 0 1\nagree 0 2\nagree 1 2\noppose 2 3\nagree 3 0\nsolve restarts=2 steps=9\n"; + + #[test] + fn the_temperature_picks_the_mode_and_the_rate() { + let hot = run(FOUR, 1, None); + assert_eq!((hot["mode"].as_str(), hot["temperature_from"].as_str()), (Some("sample"), Some("landscape"))); + let acc = hot["work"]["accumulates"].as_u64().unwrap(); + assert_eq!(hot["joules"]["pj"].as_f64().unwrap(), acc as f64 * price::SAMPLE_PJ); + assert!(hot["member"]["kernel"] == KERNEL_SAMPLE); + let cold = run(FOUR, 1, Some(0.0)); + assert_eq!(cold["mode"], "quench"); + assert_eq!(cold["joules"]["pj"].as_f64().unwrap(), cold["work"]["accumulates"].as_u64().unwrap() as f64 * price::SETTLE_PJ); + assert_eq!(cold["work"]["draws"], 0); + assert!(cold["member"]["kernel"] == KERNEL_SAMPLE, "a quench runs chains, every sweep at 0"); + assert!(run("vars 2\nagree 0 1\nfield 0 1\nminimize\n", 1, None)["member"]["kernel"] == KERNEL_SETTLE); + let asked = run(FOUR, 1, Some(0.5)); + assert_eq!((asked["temperature"].as_f64(), asked["temperature_from"].as_str()), (Some(0.5), Some("request"))); + assert!(run_on(&Certified::reference(), &job(FOUR), 1, Some(-1.0)).is_err()); + // A `minimize` program run hot becomes a sampling run, with the default starts. + let r = run("vars 3\npin 0 +\nagree 0 1\nagree 1 2\nminimize steps=5\n", 1, Some(1.0)); + assert_eq!((r["mode"].as_str(), r["restarts"].as_u64()), (Some("sample"), Some(8))); + } + + #[test] + fn the_same_seed_gives_the_same_answer_and_another_seed_another_run() { + assert_eq!(run(FOUR, 42, None).to_string(), run(FOUR, 42, None).to_string()); + assert_ne!(run(FOUR, 42, None)["replay"], run(FOUR, 43, None)["replay"]); + // A different temperature, or a different program, is a different run. + assert_ne!(run(FOUR, 42, Some(0.5))["replay"], run(FOUR, 42, Some(0.6))["replay"]); + } + + #[test] + fn a_run_too_big_is_refused_before_it_starts() { + let r = crate::quantum_landscape::job::call("run", &json!({ "program": "vars 4000\nagree 0 1\nsolve restarts=1000 steps=100000\n" })).unwrap_err(); + assert_eq!(r.line, Some(3)); + assert!(r.message.contains("limit")); + } + + /// A stand-in for a device: the reference's bits behind its own name, class and parity. + struct Board { + meter: bool, + } + + impl Member for Board { + fn id(&self) -> String { + "fpga:test-board".into() + } + fn class(&self) -> &'static str { + "fpga" + } + fn label(&self) -> String { + "A run on a test board.".into() + } + fn settle(&self, w: &Workload, start: &[i8], bound: u32) -> Result { + CpuReference.settle(w, start, bound) + } + fn chains(&self, w: &Workload, chains: &[Chain], temperatures: &[f64]) -> Result, String> { + 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" }) }) + } + } + + /// The seam: another member takes the same job, its answer names it, a + /// reading stands beside the price, and the same bits give the same digest. + #[test] + fn a_job_runs_on_another_member_and_its_answer_names_it() { + let j = job(FOUR); + let reference = run_on(&Certified::reference(), &j, 5, None).unwrap(); + let metered = Board { meter: true }; + let board = run_on(&certify(&metered).unwrap(), &j, 5, None).unwrap(); + 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["joules"]["metered"]["joules_micro"], 12); + assert_eq!(board["joules"]["kind"], "priced", "a reading stands beside the price"); + assert_eq!(board["replay"], reference["replay"]); + assert_eq!(board["state"], reference["state"]); + assert_eq!(reference["joules"]["metered"], Value::Null); + 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"); + } + + /// A member cannot pass off a broken answer: the host checks the state and works out its energy. + struct Broken(usize); + + impl Member for Broken { + fn id(&self) -> String { + "gpu:broken".into() + } + fn class(&self) -> &'static str { + "gpu" + } + fn label(&self) -> String { + "A broken device.".into() + } + fn settle(&self, _: &Workload, _: &[i8], _: u32) -> Result { + Err("no such device".into()) + } + fn chains(&self, _: &Workload, chains: &[Chain], temperatures: &[f64]) -> Result, String> { + let state = if self.0 == 0 { vec![0i8; 2] } else { vec![7i8; self.0] }; + Ok(chains.iter().map(|_| Outcome { state: state.clone(), sweeps: temperatures.len() as u32, term: Term::Bound, accumulates: 0, draws: 0 }).collect()) + } + } + + /// A member that says it came to rest where it did not, and claims more than it earned. + struct Liar; + + impl Member for Liar { + fn id(&self) -> String { + "gpu:liar".into() + } + fn class(&self) -> &'static str { + "gpu" + } + fn label(&self) -> String { + "A device that lies.".into() + } + fn settle(&self, _: &Workload, _: &[i8], _: u32) -> Result { + Ok(Outcome { state: vec![1, -1], sweeps: 1, term: Term::FixedPoint, accumulates: 2, draws: 0 }) + } + fn chains(&self, w: &Workload, chains: &[Chain], temperatures: &[f64]) -> Result, String> { + CpuReference.chains(w, chains, temperatures) + } + } + + /// The reference two-cycles here (00, +-, 00): a fixed point it claims must rest. + #[test] + fn a_member_is_held_to_its_ending() { + let j = job("vars 2\nagree 0 1\nfield 0 1\nfield 1 -1\nminimize\n"); + let reference = run_on(&Certified::reference(), &j, 1, None).unwrap(); + assert_eq!(reference["verdict"], "failed"); + assert!(run_on(&named(&Liar).unwrap(), &j, 1, None).is_err(), "a fixed point that does not rest is refused"); + } + + /// Parity is what the host found, not what a member says. + #[test] + fn a_member_s_claim_of_parity_is_not_taken_on_its_word() { + let c = certify(&Liar).unwrap(); + assert_eq!((c.parity(KERNEL_SETTLE), c.parity(KERNEL_SAMPLE)), (Parity::Uncertified, Parity::BitExact)); + let a = run_on(&c, &job("vars 2\nagree 0 1\nminimize\n"), 1, None).unwrap_err(); + assert!(a.message.contains("gpu:liar"), "{}", a.message); + } + + #[test] + fn a_member_s_bad_answer_is_refused() { + let j = job(FOUR); + for member in [Broken(0), Broken(4)] { + let r = run_on(&named(&member).unwrap(), &j, 1, None).unwrap_err(); + assert!(r.message.contains("gpu:broken"), "{}", r.message); + } + let cold = job("vars 2\nagree 0 1\nminimize\n"); + assert!(run_on(&named(&Broken(2)).unwrap(), &cold, 1, None).unwrap_err().message.contains("no such device")); + } + + #[test] + fn a_sweep_keeps_the_budget_and_the_cold_row_of_a_minimize_runs_once() { + let p = read(FOUR).unwrap(); + let s = sweep_on(&Certified::reference(), &Job::new(p), &[0.0, 1.0], 5, 1).unwrap(); + assert_eq!(s["rows"][0]["runs"], 5); + assert_eq!(s["rows"][0]["accumulates_per_run"], s["rows"][1]["accumulates_per_run"]); + let chain = read("vars 3\npin 0 +\nagree 0 1\nagree 1 2\nminimize\n").unwrap(); + let s = sweep_on(&Certified::reference(), &Job::new(chain), &[0.0, 1.0], 5, 1).unwrap(); + assert_eq!((s["rows"][0]["mode"].as_str(), s["rows"][0]["runs"].as_u64()), (Some("settle"), Some(1))); + } + + #[test] + fn a_comparison_gives_every_solver_the_same_budget() { + let p = read(FOUR).unwrap(); + let c = compare_on(&Certified::reference(), &Job::new(p), 4, 1, Some(1.5)).unwrap(); + let rows = c["rows"].as_array().unwrap(); + assert_eq!(rows.len(), 5); + assert!(rows[0]["solver"].as_str().unwrap().contains("starting at 1.5")); + let budget = rows[0]["operations_per_run"].as_u64().unwrap(); + for r in rows { + let spent = r["operations_per_run"].as_u64().unwrap(); + // A classical solver may finish its move in flight past the budget, by little. + assert!(spent >= budget / 2 && spent <= budget + 64, "{}: {spent} against {budget}", r["solver"]); + } + assert_eq!(c["target_is_exact"], true); + assert_eq!(rows[2]["classical"], true); + } +} diff --git a/wai/wai-quantum/src/quantum_landscape/sample.rs b/wai/wai-quantum/src/quantum_landscape/sample.rs new file mode 100644 index 000000000..3990b42e3 --- /dev/null +++ b/wai/wai-quantum/src/quantum_landscape/sample.rs @@ -0,0 +1,400 @@ +//! The sampling kernel, `wai.quantum.landscape/sample`. +//! +//! At a temperature `T > 0` a cell does not move to its lowest state: it +//! draws one of its three states with the Boltzmann weights of its field, +//! +//! ```text +//! P(s) ∝ exp((h·s − c·|s|) / T), s ∈ {−1, 0, +1} +//! ``` +//! +//! and cells are updated one at a time, each reading the states the cells +//! before it have just taken. That order matters: drawn from one common old +//! state, two coupled cells would each follow the other's old value, and the +//! pair would not sample its joint distribution (a test below shows the +//! difference). A run cools: sweep `k` of `K` is at `T_k = T_0 · (1 − k/(K−1))`, +//! so the last sweep is at 0, and a sweep at 0 is the deterministic one of +//! [`super::settle`]. +//! +//! # One draw, two cut points +//! +//! The draw is made the way a device can make it. For the cell's field the +//! weights give two cut points on a 53-bit scale, +//! +//! ```text +//! a = ⌊ P(−1) · 2⁵³ ⌋, b = ⌊ (P(−1) + P(0)) · 2⁵³ ⌋, +//! ``` +//! +//! and one 53-bit number `u` decides: `−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 numbers `u` come from a counter, not from a state carried along: +//! +//! ```text +//! mix(x): x ^= x >> 30; x *= 0xBF58476D1CE4E5B9; +//! x ^= x >> 27; x *= 0x94D049BB133111EB; x ^= x >> 31 +//! out(s, j) = mix(s + j · 0x9E3779B97F4A7C15) (64-bit, wrapping; j ≥ 1) +//! ``` +//! +//! which is the `j`-th output of the SplitMix64 generator started at `s`. +//! For a run's seed `S`, start `r` (from 0) of a run has the chain seed +//! `out(S, r + 1)`; its cell `i` starts at `out(!chain, i + 1) mod 3 − 1`; and +//! the draw of cell `i` in sweep `k` is the top 53 bits of +//! `out(chain, k·n + i + 1)`. Any draw can be computed alone, so a device may +//! run chains side by side and still give the reference's bits. +//! +//! The weights are computed with this crate's `exp`, which gives the same +//! bits on every platform, as `exp((l − m)/T)` for the three exponents `l` +//! and their largest `m`, so nothing overflows at any temperature and tied +//! states get equal weights. + +use super::settle::{Outcome, Term, Workload, threshold}; +use crate::repro::exp; + +const GOLDEN: u64 = 0x9E37_79B9_7F4A_7C15; +const TWO53: f64 = 9_007_199_254_740_992.0; + +#[inline] +fn mix(mut x: u64) -> u64 { + 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) +} + +/// The `j`-th output (from 1) of the SplitMix64 generator started at `s`. +#[inline] +pub fn out(s: u64, j: u64) -> u64 { + mix(s.wrapping_add(j.wrapping_mul(GOLDEN))) +} + +/// The seed of start `r` (from 0) of a run with seed `seed`. +pub fn chain_seed(seed: u64, r: u64) -> u64 { + out(seed, r.wrapping_add(1)) +} + +/// The state a chain starts from: each cell −1, 0 or +1. +pub fn start(chain: u64, n: usize) -> Vec { + (0..n).map(|i| (out(!chain, i as u64 + 1) % 3) as i8 - 1).collect() +} + +/// The temperature of sweep `k` of `sweeps`, cooling in a line from `t0` to 0: +/// with `q = k / (sweeps − 1)`, it is `t0 − t0 · q`, each step rounded to the +/// nearest double. A run of one sweep is at 0. +pub fn temperature(t0: f64, sweeps: u32, k: u32) -> f64 { + if sweeps <= 1 { + return 0.0; + } + let q = f64::from(k.min(sweeps - 1)) / f64::from(sweeps - 1); + t0 - t0 * q +} + +/// Every sweep's temperature, cooling from `t0`: what a host gives a member. +pub fn schedule(t0: f64, sweeps: u32) -> Vec { + (0..sweeps).map(|k| temperature(t0, sweeps, k)).collect() +} + +/// The two cut points for a field `h` at cost `c` and inverse temperature +/// `beta = 1 / T`: with `l₋ = −h − c`, `l₊ = h − c` and `m` the largest of +/// them and 0, `w_x = exp(beta · (l_x − m))`, `z = (w₋ + w₀) + w₊`, +/// `a = ⌊(w₋ / z) · 2⁵³⌋` and `b = ⌊((w₋ + w₀) / z) · 2⁵³⌋`, each operation +/// rounded to the nearest double and `exp` this crate's. +pub fn cuts(h: i64, cost: i64, beta: f64) -> (u64, u64) { + let (lm, lp) = ((-h - cost) as f64, (h - cost) as f64); + let m = lm.max(lp).max(0.0); + let (wm, w0, wp) = (exp(beta * (lm - m)), exp(beta * (0.0 - m)), exp(beta * (lp - m))); + let z = wm + w0 + wp; + ((wm / z * TWO53) as u64, ((wm + w0) / z * TWO53) as u64) +} + +/// The state a 53-bit draw gives between two cut points. +#[inline] +pub fn pick(u: u64, a: u64, b: u64) -> i8 { + if u < a { + -1 + } else if u < b { + 0 + } else { + 1 + } +} + +/// The cut points of one sweep, kept by field: they depend on nothing else. +struct Table { + half: i64, + at: Vec<(u64, u64)>, + known: Vec, + stamp: u32, +} + +impl Table { + fn new(bound: i64) -> Table { + let half = bound.clamp(0, 1024); + let len = (2 * half + 1) as usize; + Table { half, at: vec![(0, 0); len], known: vec![0; len], stamp: 0 } + } + + #[inline] + fn get(&mut self, h: i64, cost: i64, beta: f64) -> (u64, u64) { + if h < -self.half || h > self.half { + return cuts(h, cost, beta); + } + let k = (h + self.half) as usize; + if self.known[k] != self.stamp { + self.at[k] = cuts(h, cost, beta); + self.known[k] = self.stamp; + } + self.at[k] + } +} + +/// Run one chain: `sweeps` sweeps from `start`, cooling from `t0` to 0, with +/// the draws of `chain`. With `t0` at 0 every sweep is the deterministic one. +/// Every sweep is run: a chain does not stop when it comes to rest. +pub fn anneal(w: &Workload, start: &[i8], t0: f64, sweeps: u32, chain: u64) -> Outcome { + anneal_with(w, start, &schedule(t0, sweeps), chain) +} + +/// Run one chain through the given temperatures, one a sweep: a sweep at 0 is +/// the deterministic one, and a sweep above 0 samples cell by cell, in order, +/// each cell reading the states the cells before it have just taken. +pub fn anneal_with(w: &Workload, start: &[i8], temperatures: &[f64], chain: u64) -> Outcome { + let n = w.n(); + assert_eq!(start.len(), n, "one state a cell"); + let mut s = start.to_vec(); + let (mut accumulates, mut draws) = (0u64, 0u64); + let mut table = Table::new(w.field_bound()); + for (k, &t) in temperatures.iter().enumerate() { + if t <= 0.0 { + s = w.sweep(&s); + } else { + let beta = 1.0 / t; + table.stamp += 1; + let base = (k as u64).wrapping_mul(n as u64); + for i in 0..n { + let (a, b) = table.get(w.field(&s, i), w.cost(), beta); + s[i] = pick(out(chain, base.wrapping_add(i as u64 + 1)) >> 11, a, b); + } + draws += n as u64; + } + accumulates += w.nonzeros(); + } + let term = if w.rests(&s) { Term::FixedPoint } else { Term::Bound }; + Outcome { state: s, sweeps: temperatures.len() as u32, term, accumulates, draws } +} + +/// One cell's draw at a fixed field, for the tests of the cell and for a +/// device's own check of its table: draw `j` of the stream `chain`. +pub fn cell(h: i64, cost: i64, beta: f64, chain: u64, j: u64) -> i8 { + if !beta.is_finite() { + return threshold(h, cost); + } + let (a, b) = cuts(h, cost, beta); + pick(out(chain, j) >> 11, a, b) +} + +#[cfg(test)] +mod tests { + use super::*; + + /// The Boltzmann weights of one cell, from the formula. + fn boltzmann(h: i64, c: i64, beta: f64) -> [f64; 3] { + let e = |s: i64| (beta * ((h * s) as f64 - (c * s.abs()) as f64)).exp(); + let z = e(-1) + e(0) + e(1); + [e(-1) / z, e(0) / z, e(1) / z] + } + + fn frequencies(h: i64, c: i64, beta: f64, draws: u64) -> [f64; 3] { + let mut count = [0u64; 3]; + for j in 1..=draws { + count[(cell(h, c, beta, 0x5EED, j) + 1) as usize] += 1; + } + count.map(|k| k as f64 / draws as f64) + } + + /// Five operating points spread over field, cost and temperature. Each + /// frequency is within four standard errors of its analytic weight, and + /// not all of them sit on it: a sampler that lands on the weight every + /// time is not sampling. + #[test] + fn a_cell_samples_its_boltzmann_weights() { + let draws = 300_000u64; + let mut worst = 0.0f64; + for (h, c, t) in [(2i64, 0i64, 1.0f64), (1, 1, 0.5), (3, 1, 2.0), (0, 2, 1.0), (-2, 1, 0.7)] { + let want = boltzmann(h, c, 1.0 / t); + let got = frequencies(h, c, 1.0 / t, draws); + for k in 0..3 { + let se = (want[k] * (1.0 - want[k]) / draws as f64).sqrt(); + let z = (got[k] - want[k]).abs() / se; + assert!(z < 4.0, "h = {h}, c = {c}, T = {t}, state {}: {} against {}, {z:.2} standard errors", k as i64 - 1, got[k], want[k]); + worst = worst.max(z); + } + } + assert!(worst > 0.5, "every frequency within half a standard error of its weight: {worst:.2}"); + } + + /// The cut points are the weights, to the last of 53 bits but one. + #[test] + fn the_cut_points_are_the_weights() { + for h in -5i64..=5 { + for c in 0i64..=3 { + for t in [0.25f64, 0.7, 1.0, 3.0] { + let p = boltzmann(h, c, 1.0 / t); + let (a, b) = cuts(h, c, 1.0 / t); + assert!((a as f64 / TWO53 - p[0]).abs() < 1e-12, "h = {h}, c = {c}, T = {t}"); + assert!((b as f64 / TWO53 - (p[0] + p[1])).abs() < 1e-12, "h = {h}, c = {c}, T = {t}"); + assert!(a <= b && b <= 1 << 53); + } + } + } + } + + /// Cold, and away from a tie, the cell is the threshold. + #[test] + fn a_cold_cell_away_from_a_tie_is_the_threshold() { + for h in -6i64..=6 { + for c in 0i64..=3 { + if h.abs() == c { + continue; + } + for j in 1..=200 { + assert_eq!(cell(h, c, 1e6, 7, j), threshold(h, c), "h = {h}, c = {c}"); + } + } + } + } + + /// Cold, at a tie, the cell is uniform over the tied states and never + /// takes the state of higher energy. The deterministic sweep gives 0 there. + #[test] + fn a_cold_cell_at_a_tie_is_uniform_over_the_tied_states() { + let draws = 60_000u64; + // h = c = 2: 0 and +1 tie, −1 is higher. h = c = 0: all three tie. + let f = frequencies(2, 2, 1e6, draws); + assert_eq!(f[0], 0.0); + assert!((f[1] - 0.5).abs() < 0.01 && (f[2] - 0.5).abs() < 0.01, "{f:?}"); + let f = frequencies(-2, 2, 1e6, draws); + assert_eq!(f[2], 0.0); + assert!((f[0] - 0.5).abs() < 0.01 && (f[1] - 0.5).abs() < 0.01, "{f:?}"); + let f = frequencies(0, 0, 1e6, draws); + assert!(f.iter().all(|x| (x - 1.0 / 3.0).abs() < 0.01), "{f:?}"); + } + + /// With the state 0 set aside, the cell is the two-state cell: `P(+1) = 1/(1 + e^(−2h/T))`. + #[test] + fn without_zero_the_cell_is_the_two_state_cell() { + for (h, t) in [(1i64, 1.0f64), (2, 3.0), (-1, 0.8)] { + let f = frequencies(h, 0, 1.0 / t, 300_000); + let got = f[2] / (f[0] + f[2]); + let want = 1.0 / (1.0 + (-2.0 * h as f64 / t).exp()); + assert!((got - want).abs() < 0.005, "h = {h}, T = {t}: {got} against {want}"); + } + } + + /// Two cells that agree, swept one at a time at a fixed temperature, + /// sample their joint distribution. Drawn both from the old state, they + /// do not: the control fails the same bound by a wide margin. + #[test] + fn two_coupled_cells_sample_their_joint_distribution() { + let beta = 1.0f64; + let energy = |a: i64, b: i64| -(a * b) as f64; + let states: Vec<(i64, i64)> = (-1..=1).flat_map(|a| (-1..=1).map(move |b| (a, b))).collect(); + let z: f64 = states.iter().map(|&(a, b)| (-beta * energy(a, b)).exp()).sum(); + let want: Vec = states.iter().map(|&(a, b)| (-beta * energy(a, b)).exp() / z).collect(); + let run = |in_place: bool| -> f64 { + let mut count = [0u64; 9]; + let (mut a, mut b) = (0i8, 0i8); + let sweeps = 400_000u64; + for k in 0..sweeps { + let (old_a, old_b) = (a, b); + a = cell(i64::from(old_b), 0, beta, 11, 2 * k + 1); + b = cell(i64::from(if in_place { a } else { old_a }), 0, beta, 11, 2 * k + 2); + count[((a + 1) * 3 + (b + 1)) as usize] += 1; + } + (0..9).map(|i| (count[i] as f64 / sweeps as f64 - want[i]).abs()).fold(0.0, f64::max) + }; + let (one_at_a_time, all_at_once) = (run(true), run(false)); + assert!(one_at_a_time < 0.005, "one at a time: off by {one_at_a_time}"); + assert!(all_at_once > 0.05, "all at once should miss the joint distribution: off by {all_at_once}"); + } + + /// The schedule, as the extension's text states it, gives the code's bits. + #[test] + fn the_schedule_is_the_spec_s_formula() { + let mut differ = 0; + for t0 in [0.25f64, 0.3, 0.7, 1.0, 1.3, 1.8708286933869707, 2.449489742783178, 2.9, 7.1, 13.7, 63.0] { + for sweeps in [2u32, 3, 7, 12, 40, 41, 320] { + for k in 0..sweeps { + let q = f64::from(k) / f64::from(sweeps - 1); + let spec = t0 - t0 * q; + differ += usize::from(spec.to_bits() != temperature(t0, sweeps, k).to_bits()); + } + } + } + assert_eq!(differ, 0, "{differ} sweeps differ from the stated formula"); + } + + /// The cut points, as the extension's text states them, give the code's bits. + #[test] + fn the_cut_points_are_the_spec_s_formula() { + let mut differ = 0; + for h in -40i64..=40 { + for c in [0i64, 1, 2, 3, 8] { + for t in [0.3f64, 0.7, 1.3, 2.9, 7.1, 13.7, 1.8708286933869707] { + let (lm, lp) = ((-h - c) as f64, (h - c) as f64); + let m = lm.max(lp).max(0.0); + let beta = 1.0 / t; + let (wm, w0, wp) = (exp(beta * (lm - m)), exp(beta * (0.0 - m)), exp(beta * (lp - m))); + let z = (wm + w0) + wp; + let spec = ((wm / z * TWO53) as u64, ((wm + w0) / z * TWO53) as u64); + differ += usize::from(spec != cuts(h, c, 1.0 / t)); + } + } + } + assert_eq!(differ, 0, "{differ} cut points differ from the stated formula"); + } + + #[test] + fn a_chain_at_temperature_zero_is_the_deterministic_sweep() { + let w = Workload::new(3, &[0, 1, -1, 1, 0, 1, -1, 1, 0], &[1, 0, 0], 1); + let from = start(99, 3); + let mut s = from.clone(); + for _ in 0..7 { + s = w.sweep(&s); + } + let out = anneal(&w, &from, 0.0, 7, 99); + assert_eq!(out.state, s); + assert_eq!((out.draws, out.accumulates), (0, 7 * w.nonzeros())); + } + + #[test] + fn the_schedule_ends_at_zero_and_a_chain_repeats_itself() { + assert_eq!(temperature(5.0, 40, 0), 5.0); + assert_eq!(temperature(5.0, 40, 39), 0.0); + assert_eq!(temperature(5.0, 1, 0), 0.0); + assert!(temperature(5.0, 40, 20) > 2.0 && temperature(5.0, 40, 20) < 3.0); + let w = Workload::new(4, &[0, 1, 0, -1, 1, 0, 1, 0, 0, 1, 0, 1, -1, 0, 1, 0], &[0; 4], 0); + let from = start(chain_seed(3, 0), 4); + let a = anneal(&w, &from, 2.0, 30, chain_seed(3, 0)); + assert_eq!(a, anneal(&w, &from, 2.0, 30, chain_seed(3, 0))); + assert_eq!(a.draws, 29 * 4, "the last sweep is at 0 and draws nothing"); + assert_ne!(chain_seed(3, 0), chain_seed(3, 1)); + } + + /// The first outputs of the generator from seed 0, as its reference gives them. + #[test] + fn the_generator_is_splitmix64() { + assert_eq!([out(0, 1), out(0, 2), out(0, 3)], [0xE220_A839_7B1D_CDAF, 0x6E78_9E6A_A1B9_65F4, 0x06C4_5D18_8009_454F]); + } + + #[test] + fn starts_use_all_three_states() { + let s = start(42, 3000); + for v in -1i8..=1 { + let share = s.iter().filter(|&&x| x == v).count() as f64 / 3000.0; + assert!((share - 1.0 / 3.0).abs() < 0.04, "state {v}: {share}"); + } + } +} diff --git a/wai/wai-quantum/src/quantum_landscape/settle.rs b/wai/wai-quantum/src/quantum_landscape/settle.rs new file mode 100644 index 000000000..57ff13b04 --- /dev/null +++ b/wai/wai-quantum/src/quantum_landscape/settle.rs @@ -0,0 +1,309 @@ +//! The deterministic kernel, `wai.quantum.landscape/settle`. +//! +//! Each cell `i` adds up its field +//! +//! ```text +//! h_i = b_i + Σ_j W_ij s_j +//! ``` +//! +//! and moves to `+1` when `h_i > c`, to `−1` when `h_i < −c`, and to `0` +//! otherwise, where `c` is the cost of a cell that is not 0. That is the state +//! of lowest energy for the cell with every other cell held, and at a tie +//! (`|h_i| = c`) it is 0. One read of one coupling into one cell's field is an +//! *accumulate*, the unit of work. +//! +//! A sweep updates every cell from the same state: all the fields are read, +//! then all the cells move. Sweeps repeat until the state does not change (a +//! fixed point), until it returns to the state two sweeps back (a period-2 +//! cycle), or until the bound. +//! +//! Everything here is whole-number arithmetic, which is associative: the +//! result cannot depend on the order a field is added up in, on how many +//! lanes a device has or on how it splits the cells. A member's settle kernel +//! is therefore held to the reference bit for bit. + +/// A landscape lowered to what the kernels read: couplings in compressed +/// rows, a field for each cell and the cost. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Workload { + n: usize, + start: Vec, + col: Vec, + val: Vec, + field: Vec, + cost: i64, +} + +/// How a settle ended. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Term { + /// The state did not change over a sweep. + FixedPoint, + /// The state returned to the one two sweeps back. + TwoCycle, + /// Neither, within the bound. + Bound, +} + +impl Term { + pub fn label(self) -> &'static str { + match self { + Term::FixedPoint => "fixed-point", + Term::TwoCycle => "two-cycle", + Term::Bound => "bound", + } + } +} + +/// What a kernel leaves: the state, the sweeps it took, how it ended, and its work. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Outcome { + pub state: Vec, + pub sweeps: u32, + pub term: Term, + /// Coupling reads. + pub accumulates: u64, + /// Random draws (none at temperature 0). + pub draws: u64, +} + +/// The cell's move for a field `h` at cost `c`: the state of lowest energy, 0 at a tie. +#[inline] +pub fn threshold(h: i64, cost: i64) -> i8 { + if h > cost { + 1 + } else if h < -cost { + -1 + } else { + 0 + } +} + +/// FNV-1a (64-bit) over a state, each cell as the byte `s + 1`: the digest a +/// device and the reference compare a settled state by. +pub fn state_hash(state: &[i8]) -> u64 { + let mut h: u64 = 0xcbf2_9ce4_8422_2325; + for &s in state { + h ^= u64::from((s + 1) as u8); + h = h.wrapping_mul(0x0000_0100_0000_01b3); + } + h +} + +impl Workload { + /// From a dense `n×n` coupling matrix in rows (symmetric, zero diagonal, + /// each entry −1, 0 or +1), the fields and the cost. + pub fn new(n: usize, couplings: &[i8], field: &[i64], cost: i64) -> Workload { + assert_eq!(couplings.len(), n * n, "couplings are n×n"); + assert_eq!(field.len(), n, "one field a cell"); + assert!(cost >= 0, "the cost is not negative"); + let mut start = Vec::with_capacity(n + 1); + let (mut col, mut val) = (Vec::new(), Vec::new()); + for i in 0..n { + start.push(col.len()); + for j in 0..n { + let w = couplings[i * n + j]; + if w != 0 { + col.push(j as u32); + val.push(w); + } + } + } + start.push(col.len()); + Workload { n, start, col, val, field: field.to_vec(), cost } + } + + /// The number of cells. + pub fn n(&self) -> usize { + self.n + } + + /// The cost of a cell that is not 0. + pub fn cost(&self) -> i64 { + self.cost + } + + /// The couplings read in one sweep of every cell: twice the coupled pairs. + pub fn nonzeros(&self) -> u64 { + self.col.len() as u64 + } + + /// The couplings one cell reads. + #[inline] + pub fn degree(&self, i: usize) -> u64 { + (self.start[i + 1] - self.start[i]) as u64 + } + + /// Cell `i`'s field in the state `s`. + #[inline] + pub fn field(&self, s: &[i8], i: usize) -> i64 { + let mut h = self.field[i]; + for k in self.start[i]..self.start[i + 1] { + h += i64::from(self.val[k]) * i64::from(s[self.col[k] as usize]); + } + h + } + + /// The couplings of cell `i`: each other cell and its coupling. + #[inline] + pub fn row(&self, i: usize) -> impl Iterator + '_ { + (self.start[i]..self.start[i + 1]).map(move |k| (self.col[k] as usize, i64::from(self.val[k]))) + } + + /// The energy of a state, from the rows: the same whole number as the + /// landscape's, in the time of one sweep rather than of every pair. + pub fn energy(&self, s: &[i8]) -> i64 { + assert_eq!(s.len(), self.n, "one state a cell"); + let (mut twice_pairs, mut rest) = (0i64, 0i64); + for i in 0..self.n { + let si = i64::from(s[i]); + if si != 0 { + twice_pairs += si * self.row(i).map(|(j, c)| c * i64::from(s[j])).sum::(); + } + rest += -self.field[i] * si + self.cost * si.abs(); + } + rest - twice_pairs / 2 + } + + /// The largest field any state can give a cell: the bound a device with + /// narrow lanes checks before it takes the workload. + pub fn field_bound(&self) -> i64 { + let most = (0..self.n).map(|i| self.field[i].unsigned_abs().saturating_add(self.degree(i))).max().unwrap_or(0); + i64::try_from(most).unwrap_or(i64::MAX) + } + + /// One sweep of every cell from the same state. + pub fn sweep(&self, s: &[i8]) -> Vec { + (0..self.n).map(|i| threshold(self.field(s, i), self.cost)).collect() + } + + /// Whether no cell would move. + pub fn rests(&self, s: &[i8]) -> bool { + s.len() == self.n && (0..self.n).all(|i| threshold(self.field(s, i), self.cost) == s[i]) + } + + /// Settle from `start`, for at most `bound` sweeps. + pub fn settle(&self, start: &[i8], bound: u32) -> Outcome { + assert_eq!(start.len(), self.n, "one state a cell"); + let mut s = start.to_vec(); + let mut two_back: Option> = None; + let (mut sweeps, mut term, mut accumulates) = (0u32, Term::Bound, 0u64); + while sweeps < bound { + let next = self.sweep(&s); + accumulates += self.nonzeros(); + sweeps += 1; + if next == s { + term = Term::FixedPoint; + break; + } + // The state a cycle is reported in is the one just computed. + if two_back.as_ref() == Some(&next) { + s = next; + term = Term::TwoCycle; + break; + } + two_back = Some(std::mem::replace(&mut s, next)); + } + Outcome { state: s, sweeps, term, accumulates, draws: 0 } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + /// A ±1 pattern from a seed, and the coupling that stores it: `W_ij = p_i p_j`. + fn pattern(seed: u64, n: usize) -> Vec { + (0..n) + .map(|i| { + let mut x = seed.wrapping_add(i as u64).wrapping_mul(0x9E37_79B9_7F4A_7C15); + x ^= x >> 29; + if x & 1 == 0 { 1 } else { -1 } + }) + .collect() + } + + fn store(p: &[i8]) -> Workload { + let n = p.len(); + let w: Vec = (0..n * n).map(|k| if k / n == k % n { 0 } else { p[k / n] * p[k % n] }).collect(); + Workload::new(n, &w, &vec![0; n], 0) + } + + /// Recall of one stored pattern from a query with about one cell in eight + /// flipped: four digests held since before this module, which a device's + /// settle kernel is held to as well. + #[test] + fn four_recall_vectors_are_pinned() { + for (n, pinned) in [(4usize, 0x8d14_0290_4a33_c6c5u64), (8, 0x6d7f_49ef_0e7a_f465), (16, 0xa12d_60a0_6b2a_6da5), (32, 0x5f98_f634_77c1_5825)] { + let stored = pattern(0xC0FFEE, n); + let mut q = stored.clone(); + for k in 0..(n / 8).max(1) { + let i = (k * 5 + 1) % n; + q[i] = -q[i]; + } + let out = store(&stored).settle(&q, 40); + assert_eq!(out.term, Term::FixedPoint, "n = {n}"); + assert_eq!(out.state, stored, "n = {n}: the stored pattern is recalled"); + assert_eq!(state_hash(&out.state), pinned, "n = {n}"); + } + } + + #[test] + fn the_three_endings_are_told_apart() { + // Two cells that must differ, from equal states: each flips every sweep. + let w = Workload::new(2, &[0, -1, -1, 0], &[0, 0], 0); + let out = w.settle(&[1, 1], 20); + assert_eq!((out.term, out.sweeps), (Term::TwoCycle, 2)); + assert_eq!(out.state, vec![1, 1], "the state reported is the one just computed"); + // The same cells from differing states rest at once. + let out = w.settle(&[1, -1], 20); + assert_eq!((out.term, out.sweeps, out.accumulates), (Term::FixedPoint, 1, 2)); + // A chain with one end held passes the sign along one cell a sweep: a + // bound of 3 stops it on the way, and a larger one lets it arrive. + let mut c = vec![0i8; 36]; + for i in 0..5 { + c[i * 6 + i + 1] = 1; + c[(i + 1) * 6 + i] = 1; + } + let chain = Workload::new(6, &c, &[3, 0, 0, 0, 0, 0], 0); + let out = chain.settle(&[0; 6], 3); + assert_eq!((out.term, out.sweeps), (Term::Bound, 3)); + assert_eq!(out.state, vec![1, 1, 1, 0, 0, 0]); + let out = chain.settle(&[0; 6], 20); + assert_eq!((out.term, out.sweeps), (Term::FixedPoint, 7)); + assert_eq!(out.state, vec![1; 6]); + assert!(chain.rests(&out.state)); + } + + #[test] + fn a_tie_goes_to_zero_and_the_cost_widens_it() { + assert_eq!([threshold(1, 0), threshold(0, 0), threshold(-1, 0)], [1, 0, -1]); + assert_eq!([threshold(2, 2), threshold(3, 2), threshold(-2, 2), threshold(-3, 2)], [0, 1, 0, -1]); + } + + /// The cell's move is the state of lowest energy for that cell: over every + /// field and cost in a range, no other state has a lower `−h·s + c·|s|`. + #[test] + fn the_move_is_the_cell_s_lowest_energy() { + for h in -6i64..=6 { + for c in 0i64..=4 { + let e = |s: i64| -h * s + c * s.abs(); + let m = i64::from(threshold(h, c)); + assert!((-1..=1).all(|s| e(m) <= e(s)), "h = {h}, c = {c}"); + } + } + } + + #[test] + fn the_digest_tells_every_state_of_three_cells_apart() { + let mut seen = std::collections::BTreeSet::new(); + for a in -1i8..=1 { + for b in -1i8..=1 { + for c in -1i8..=1 { + seen.insert(state_hash(&[a, b, c])); + } + } + } + assert_eq!(seen.len(), 27); + } +} diff --git a/wai/wai-quantum/src/quantum_landscape/table.rs b/wai/wai-quantum/src/quantum_landscape/table.rs new file mode 100644 index 000000000..2afabd64e --- /dev/null +++ b/wai/wai-quantum/src/quantum_landscape/table.rs @@ -0,0 +1,585 @@ +//! The published table: how success and operations scale with problem size, +//! for the emulator and for standard classical methods, on the same instances +//! at the same budget of operations a run. +//! +//! Every count in it is a function of the code and the options alone, so anyone +//! can rebuild it and get the same bytes. Wall-clock time is kept apart +//! ([`Table::timing_json`]), because it is not. +//! +//! The emulator's sampling mode appears twice: starting at the default +//! temperature, and starting at the largest row of couplings, which is a hot +//! start. The table's `start_temperature` section shows why the default is what +//! it is, on instances the rows do not use. + +use super::classical::{self, Stream}; +use super::energy::Landscape; +use super::price::group; +use super::sample::{anneal, chain_seed, start}; +use super::truth::exhaustive; +use super::{EXHAUSTIVE_MAX_VARS, PROTOCOL}; +use crate::quantum_qio::step_to_solution; +use serde_json::{Value, json}; + +/// A family of instances. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Family { + /// Sparse signed clustering with some contradictions, and a cost. + Clusters, + /// A sparse random spin glass, with a cost. + SparseGlass, + /// Random signs on every pair, with no cost. + DenseGlass, + /// A hidden group to pick out, with a known answer. + Selection, +} + +impl Family { + pub const ALL: [Family; 4] = [Family::Clusters, Family::SparseGlass, Family::DenseGlass, Family::Selection]; + + pub fn name(self) -> &'static str { + match self { + Family::Clusters => "clusters", + Family::SparseGlass => "sparse-glass", + Family::DenseGlass => "dense-glass", + Family::Selection => "selection", + } + } + + pub fn from_name(s: &str) -> Option { + Family::ALL.into_iter().find(|f| f.name() == s) + } + + pub fn about(self) -> &'static str { + match self { + Family::Clusters => "sparse signed clustering, about degree 4, one link in eight contradicting the rest, cost 1", + Family::SparseGlass => "random signs on a sparse graph, about degree 6, cost 1", + Family::DenseGlass => "random signs on every pair, cost 0", + Family::Selection => "a hidden group of a quarter of the variables, all linked by sign, cost 1, with a known answer", + } + } +} + +fn links(n: usize, degree: usize, r: &mut Stream) -> Vec<(usize, usize)> { + let mut seen = std::collections::BTreeSet::new(); + let want = (n * degree / 2).min(n * (n - 1) / 2); + let mut guard = 0; + while seen.len() < want && guard < 100 * want + 100 { + guard += 1; + let (a, b) = (r.below(n as u64) as usize, r.below(n as u64) as usize); + if a != b { + seen.insert((a.min(b), a.max(b))); + } + } + seen.into_iter().collect() +} + +/// One instance. The second value is a state known to be lowest, when one is +/// known by how the instance was built. +pub fn instance(family: Family, n: usize, seed: u64) -> (Landscape, Option>) { + let mut r = Stream::new(seed ^ (n as u64).wrapping_mul(0xA5A5_5A5A_1234_5678)); + let mut l = Landscape::new(n); + match family { + Family::Clusters => { + let labels: Vec = (0..n) + .map(|_| match r.below(8) { + 0..=2 => 1, + 3..=5 => -1, + _ => 0, + }) + .collect(); + for (a, b) in links(n, 4, &mut r) { + let mut w = if labels[a] == 0 || labels[b] == 0 { + if r.below(2) == 0 { 1 } else { -1 } + } else { + labels[a] * labels[b] + }; + if r.below(8) == 0 { + w = -w; + } + l.couple(a, b, w); + } + l.set_cost(1); + (l, None) + } + Family::SparseGlass => { + for (a, b) in links(n, 6, &mut r) { + l.couple(a, b, if r.below(2) == 0 { 1 } else { -1 }); + } + l.set_cost(1); + (l, None) + } + Family::DenseGlass => { + for a in 0..n { + for b in (a + 1)..n { + l.couple(a, b, if r.below(2) == 0 { 1 } else { -1 }); + } + } + (l, None) + } + Family::Selection => { + // Each of k cells linked to every other by its sign: with a cost below + // (k−1)/2 the hidden group, up to its mirror image, is the lowest state. + let k = (n / 4).max(5); + assert!(k <= n && 2 < k - 1, "a hidden group needs at least 4 cells"); + let mut order: Vec = (0..n).collect(); + for i in (1..n).rev() { + order.swap(i, r.below(i as u64 + 1) as usize); + } + let mut star = vec![0i8; n]; + for &p in order.iter().take(k) { + star[p] = if r.below(2) == 0 { 1 } else { -1 }; + } + for a in 0..n { + for b in (a + 1)..n { + if star[a] != 0 && star[b] != 0 { + l.couple(a, b, star[a] * star[b]); + } + } + } + l.set_cost(1); + (l, Some(star)) + } + } +} + +/// The salt of a table instance, so that an instance can be named and printed. +pub fn salt(family: Family, n: usize, k: u32) -> u64 { + 0x7AB1E ^ (u64::from(k) << 32) ^ ((n as u64) << 8) ^ family as u64 +} + +/// A table instance as a program, so that anyone can run, sweep or compare it. +pub fn instance_program(family: Family, n: usize, k: u32) -> String { + let (l, planted) = instance(family, n, salt(family, n, k)); + let mut out = format!("#! title Table instance: {}, {n} variables, number {k}\n#! about {}.\n#! expect works\n", family.name(), family.about()); + if let Some(star) = planted { + out.push_str(&format!("#! check matches {}\n", super::run::state_text(&star))); + } + out.push_str(&format!("vars {n}\n")); + if l.cost() != 0 { + out.push_str(&format!("cost {}\n", l.cost())); + } + for i in 0..n { + for j in (i + 1)..n { + match l.coupling(i, j) { + 1 => out.push_str(&format!("agree {i} {j}\n")), + -1 => out.push_str(&format!("oppose {i} {j}\n")), + _ => {} + } + } + } + out.push_str("solve restarts=8 steps=40\n"); + out +} + +/// What to run. +#[derive(Clone, Debug, PartialEq)] +pub struct Options { + pub sizes: Vec, + /// Dense instances grow as n², so they stop earlier. + pub dense_max: usize, + pub instances: u32, + pub seeds: u32, + /// Sweeps a start, in multiples of 40, the default `solve` budget. + pub budgets: Vec, + /// 0 is what the machine has, at most 16. + pub threads: usize, + /// Also sweep the starting temperature on the held-out instances. + pub held_out: bool, +} + +impl Options { + pub fn published() -> Options { + Options { sizes: vec![8, 12, 16, 24, 32, 48, 64, 96, 128], dense_max: 64, instances: 5, seeds: 20, budgets: vec![1, 8], threads: 0, held_out: true } + } + + pub fn quick() -> Options { + Options { sizes: vec![8, 12, 16, 24], dense_max: 24, instances: 2, seeds: 5, budgets: vec![1], threads: 0, held_out: false } + } +} + +/// Every solver in the table, the emulator first. +pub const SOLVERS: [&str; 6] = ["emulator, sampling, default start", "emulator, sampling, hot start", "emulator, deterministic", "simulated annealing", "tabu search", "greedy descent"]; +const CLASSICAL: usize = 3; +const RESTARTS: u32 = 8; + +/// The lowest energy over a run's starts. +fn lowest_of_chains(l: &Landscape, t0: f64, sweeps: u32, seed: u64) -> (i64, u64) { + let w = l.lower(); + let (mut best, mut accumulates) = (i64::MAX, 0u64); + for r in 0..u64::from(RESTARTS) { + let c = chain_seed(seed, r); + let o = anneal(&w, &start(c, l.len()), t0, sweeps, c); + best = best.min(l.energy(&o.state)); + accumulates += o.accumulates; + } + (best, accumulates) +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Cell { + pub hits: u32, + pub runs: u32, + pub operations: u64, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct Row { + pub family: Family, + pub n: usize, + pub pairs: u64, + pub budget: u32, + pub target: &'static str, + pub cells: Vec, + pub seconds: Vec, +} + +pub struct Table { + pub options: Options, + pub rows: Vec, + pub start_temperature: Value, +} + +/// One instance's result: hits and operations a solver, seconds a solver, the kind of target, and the pairs. +type Outcome = (Vec, Vec, Vec, &'static str, u64); + +struct Task { + family: Family, + n: usize, + budget: u32, + instance: u32, +} + +/// A lowest energy to grade against when every arrangement cannot be checked: +/// the best that long runs of the two strongest classical solvers find. It proves nothing. +fn best_known(l: &Landscape, salt: u64) -> i64 { + let w = l.lower(); + let budget = 64 * (w.nonzeros() + w.n() as u64) * u64::from(RESTARTS) * 40; + (0..4u64).flat_map(|k| { + let seed = salt ^ 0xBE57 ^ k; + [classical::simulated_annealing(&w, l, budget, seed).energy, classical::tabu_search(&w, l, budget, seed).energy] + }).min().expect("four long runs") +} + +fn run_task(t: &Task, seeds: u32, now: fn() -> f64) -> Outcome { + let salted = salt(t.family, t.n, t.instance); + let (l, planted) = instance(t.family, t.n, salted); + let w = l.lower(); + let n = t.n as u64; + let sweeps = 40 * t.budget; + let cell_updates = n * u64::from(RESTARTS) * u64::from(sweeps); + let (default_t, hot_t) = (l.default_temperature(), l.coupling_scale() as f64); + let mut energies: Vec> = vec![Vec::new(); SOLVERS.len()]; + let mut operations = vec![0u64; SOLVERS.len()]; + let mut seconds = vec![0f64; SOLVERS.len()]; + for k in 0..seeds { + let seed = salted.rotate_left(17) ^ u64::from(k).wrapping_mul(0x9E37_79B9_7F4A_7C15); + for (slot, t0) in [(0usize, default_t), (1, hot_t), (2, 0.0)] { + let began = now(); + let (e, acc) = lowest_of_chains(&l, t0, sweeps, seed); + seconds[slot] += now() - began; + energies[slot].push(e); + operations[slot] += acc + cell_updates; + } + let budget = operations[0] / (u64::from(k) + 1); + for (j, (_, solve)) in classical::SOLVERS.iter().enumerate() { + let began = now(); + let f = solve(&w, &l, budget, seed); + seconds[CLASSICAL + j] += now() - began; + energies[CLASSICAL + j].push(f.energy); + operations[CLASSICAL + j] += f.operations(); + } + } + let (target, kind) = if t.n <= EXHAUSTIVE_MAX_VARS { + (exhaustive(&l).expect("small enough").lowest as i64, "exact") + } else if let Some(star) = planted { + (l.energy(&star), "planted") + } else { + (best_known(&l, salted), "best known") + }; + // A run cannot pass an exact or planted answer. If one passes the best known, it + // becomes the target, so nothing is graded against a target a run has shown wrong. + let found = energies.iter().flatten().copied().min().unwrap_or(target); + let target = if kind == "best known" { target.min(found) } else { target }; + assert!(found >= target, "a run went below an exact answer: {found} < {target}"); + let hits = energies.iter().map(|v| v.iter().filter(|&&x| x == target).count() as u32).collect(); + (hits, operations, seconds, kind, l.pairs()) +} + +/// The evidence for the default starting temperature: on instance 99 of three +/// families, which the rows do not use, the mean energy at each start. +fn start_temperature(threads: usize) -> Value { + let mut tasks = Vec::new(); + for family in [Family::Clusters, Family::SparseGlass, Family::DenseGlass] { + for n in [32usize, 64] { + tasks.push((family, n)); + } + } + let work = |&(family, n): &(Family, usize)| -> Value { + let salted = salt(family, n, 99); + let (l, _) = instance(family, n, salted); + let (default_t, hot_t) = (l.default_temperature(), l.coupling_scale() as f64); + let mut temps = vec![0.5, 1.0, 1.5, 2.0, 3.0, 4.0, 6.0, 8.0]; + for extra in [default_t, hot_t] { + if !temps.contains(&extra) { + temps.push(extra); + } + } + temps.sort_by(f64::total_cmp); + let runs = 40u32; + let results: Vec<(f64, Vec)> = temps + .iter() + .map(|&t| (t, (0..runs).map(|k| lowest_of_chains(&l, t, 40, salted ^ 0x7E57 ^ u64::from(k)).0).collect())) + .collect(); + let best_known_here = best_known(&l, salted); + let target = results.iter().flat_map(|r| r.1.iter().copied()).min().unwrap_or(best_known_here).min(best_known_here); + let rows: Vec = results + .iter() + .map(|(t, es)| { + json!({ + "temperature": t, + "is_default": *t == default_t, + "is_hot": *t == hot_t, + "reached_target": es.iter().filter(|&&e| e == target).count(), + "mean_energy": es.iter().sum::() as f64 / f64::from(runs), + }) + }) + .collect(); + json!({ "family": family.name(), "n": n, "default": default_t, "hot": hot_t, "target": target, "runs": runs, "rows": rows }) + }; + in_parallel(&tasks, threads, &work) + .into_iter() + .collect::>() + .into() +} + +fn in_parallel(tasks: &[T], threads: usize, work: &(dyn Fn(&T) -> R + Sync)) -> Vec { + let threads = if threads == 0 { std::thread::available_parallelism().map(|p| p.get()).unwrap_or(1).min(16) } else { threads }; + // WebAssembly has no threads here, and one thread needs none. + if threads <= 1 || cfg!(target_family = "wasm") { + return tasks.iter().map(work).collect(); + } + let chunk = tasks.len().div_ceil(threads).max(1); + let mut out: Vec> = (0..tasks.len()).map(|_| None).collect(); + std::thread::scope(|scope| { + let handles: Vec<_> = tasks.chunks(chunk).enumerate().map(|(c, slice)| (c, scope.spawn(move || slice.iter().map(work).collect::>()))).collect(); + for (c, h) in handles { + for (k, r) in h.join().expect("a table worker panicked").into_iter().enumerate() { + out[c * chunk + k] = Some(r); + } + } + }); + out.into_iter().map(|r| r.expect("every task ran")).collect() +} + +/// Build the table. `now` gives seconds, for the timing kept apart from it. +/// Tasks run side by side where `threads` allows; the counts are the same either way. +pub fn build(options: &Options, now: fn() -> f64) -> Table { + let mut tasks = Vec::new(); + for family in Family::ALL { + for &n in &options.sizes { + if family == Family::DenseGlass && n > options.dense_max { + continue; + } + for &budget in &options.budgets { + for instance in 0..options.instances { + tasks.push(Task { family, n, budget, instance }); + } + } + } + } + let seeds = options.seeds; + let results = in_parallel(&tasks, options.threads, &|t: &Task| run_task(t, seeds, now)); + let mut rows: Vec = Vec::new(); + for (t, (hits, operations, seconds, kind, pairs)) in tasks.iter().zip(results) { + let fresh = rows.last().is_none_or(|r| r.family != t.family || r.n != t.n || r.budget != t.budget); + if fresh { + rows.push(Row { + family: t.family, + n: t.n, + pairs: 0, + budget: t.budget, + target: kind, + cells: SOLVERS.iter().map(|_| Cell { hits: 0, runs: 0, operations: 0 }).collect(), + seconds: vec![0.0; SOLVERS.len()], + }); + } + let row = rows.last_mut().expect("just pushed"); + row.pairs += pairs; + for k in 0..SOLVERS.len() { + row.cells[k].hits += hits[k]; + row.cells[k].runs += seeds; + row.cells[k].operations += operations[k]; + row.seconds[k] += seconds[k]; + } + } + for row in &mut rows { + row.pairs /= u64::from(options.instances); + } + let start_temperature = if options.held_out { start_temperature(options.threads) } else { Value::Null }; + Table { options: options.clone(), rows, start_temperature } +} + +/// Who reached the target more often, row by row. Counted, not written by hand. +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub struct Tally { + pub rows: u32, + pub emulator_more: u32, + pub tied: u32, + pub classical_more: u32, +} + +impl Table { + pub fn tally_for(&self, slot: usize) -> Tally { + let mut t = Tally::default(); + for r in &self.rows { + let (emulator, classical) = (r.cells[slot].hits, r.cells[CLASSICAL..].iter().map(|c| c.hits).max().unwrap_or(0)); + if emulator > classical { + t.emulator_more += 1; + } else if emulator == classical { + t.tied += 1; + } else { + t.classical_more += 1; + } + t.rows += 1; + } + t + } + + /// The counts, and nothing timed. + pub fn to_json(&self) -> Value { + let o = &self.options; + let tally = |t: Tally| json!({ "rows": t.rows, "emulator_more": t.emulator_more, "tied": t.tied, "classical_more": t.classical_more }); + json!({ + "protocol": PROTOCOL, + "options": { "sizes": o.sizes, "dense_max": o.dense_max, "instances": o.instances, "seeds": o.seeds, "budgets": o.budgets }, + "families": Family::ALL.iter().map(|f| json!({ "name": f.name(), "about": f.about() })).collect::>(), + "solvers": SOLVERS, + "rows": self.rows.iter().map(|r| json!({ + "family": r.family.name(), + "n": r.n, + "pairs": r.pairs, + "sweeps_per_start": 40 * r.budget, + "target": r.target, + "solvers": r.cells.iter().map(|c| { + let per_run = c.operations as f64 / f64::from(c.runs.max(1)); + json!({ + "hits": c.hits, + "runs": c.runs, + "operations_per_run": per_run as u64, + "operations_for_99_in_100": operations_to_solution(c, per_run).map(|v| v as u64), + }) + }).collect::>(), + })).collect::>(), + "tally_default_start": tally(self.tally_for(0)), + "tally_hot_start": tally(self.tally_for(1)), + "start_temperature": self.start_temperature, + }) + } + + pub fn to_markdown(&self) -> String { + let mut out = String::from("Each cell is the runs that reached the target, out of the runs made, then the operations a run takes for a 99 in 100 chance of reaching it (\"-\" if no run did). Every solver in a row has the same budget of operations a run as the emulator's sampling mode. One operation is one coupling read, one cell updated or one move considered.\n\n"); + out.push_str("| family | variables | pairs | sweeps a start | target | "); + out.push_str(&SOLVERS.join(" | ")); + out.push_str(" |\n|---|---|---|---|---|"); + out.push_str(&"---|".repeat(SOLVERS.len())); + out.push('\n'); + for r in &self.rows { + out.push_str(&format!("| {} | {} | {} | {} | {} |", r.family.name(), r.n, group(r.pairs), 40 * r.budget, r.target)); + for c in &r.cells { + let per_run = c.operations as f64 / f64::from(c.runs.max(1)); + let v = operations_to_solution(c, per_run).map(|v| group(v as u64)).unwrap_or_else(|| "-".into()); + out.push_str(&format!(" {}/{}, {} |", c.hits, c.runs, v)); + } + out.push('\n'); + } + for (slot, which) in [(0, "at the default start"), (1, "at the hot start, the largest row of couplings")] { + let t = self.tally_for(slot); + out.push_str(&format!("\nOf {} rows, the emulator's sampling mode {which} reached the target more often than every classical solver in {}, tied the best of them in {}, and was behind the best of them in {}.\n", t.rows, t.emulator_more, t.tied, t.classical_more)); + } + out.push_str("\nThe default start is the square root of the mean couplings a variable has, and at least 1. On instances the rows do not use, 40 runs at each start, mean energy (and the runs that reached the lowest energy found):\n\n"); + for s in self.start_temperature.as_array().into_iter().flatten() { + let cells: Vec = s["rows"].as_array().into_iter().flatten().map(|r| { + let tag = if r["is_default"] == true { " default" } else if r["is_hot"] == true { " hot" } else { "" }; + format!("{}{tag}: {:.2} ({})", super::run::shown_temperature(r["temperature"].as_f64().unwrap_or(f64::NAN)), r["mean_energy"].as_f64().unwrap_or(f64::NAN), r["reached_target"]) + }).collect(); + out.push_str(&format!("- {}, {} variables, target {}: {}\n", s["family"].as_str().unwrap_or(""), s["n"], s["target"], cells.join("; "))); + } + out + } + + /// Wall-clock seconds a run, on this machine. Not repeatable, so kept out of [`Table::to_json`]. + pub fn timing_json(&self) -> Value { + Value::Array(self.rows.iter().map(|r| json!({ + "family": r.family.name(), + "n": r.n, + "sweeps_per_start": 40 * r.budget, + "seconds_per_run": r.seconds.iter().zip(&r.cells).map(|(s, c)| s / f64::from(c.runs.max(1))).collect::>(), + })).collect()) + } +} + +fn operations_to_solution(c: &Cell, per_run: f64) -> Option { + if c.hits == 0 || c.runs == 0 { + return None; + } + if c.hits == c.runs { + return Some(per_run); + } + step_to_solution(f64::from(c.hits) / f64::from(c.runs), per_run) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn no_clock() -> f64 { + 0.0 + } + + #[test] + fn the_table_is_the_same_on_one_thread_and_many() { + let mut o = Options { sizes: vec![8, 20], dense_max: 8, instances: 2, seeds: 3, budgets: vec![1], threads: 1, held_out: false }; + let tasks = |o: &Options| { + let t = build(o, no_clock); + t.to_json().to_string() + }; + let one = tasks(&o); + o.threads = 4; + assert_eq!(one, tasks(&o)); + assert!(one.contains("\"target\":\"exact\"")); + assert!(one.contains("\"target\":\"planted\"")); + assert!(one.contains("\"target\":\"best known\"")); + } + + #[test] + fn a_printed_instance_is_the_same_landscape() { + for family in Family::ALL { + let p = crate::quantum_landscape::program::read(&instance_program(family, 12, 1)).unwrap(); + assert_eq!(p.landscape, instance(family, 12, salt(family, 12, 1)).0, "{}", family.name()); + } + } + + #[test] + fn a_hidden_group_really_is_the_lowest_state() { + for seed in 0..4u64 { + let (l, star) = instance(Family::Selection, 12, seed); + assert_eq!(l.energy(&star.unwrap()) as f64, exhaustive(&l).unwrap().lowest, "seed {seed}"); + } + } + + #[test] + fn the_tally_counts_what_it_says() { + let cell = |hits| Cell { hits, runs: 10, operations: 100 }; + let row = |own: u32, classical: u32| Row { + family: Family::Clusters, + n: 8, + pairs: 1, + budget: 1, + target: "exact", + cells: vec![cell(own), cell(0), cell(0), cell(classical), cell(0), cell(0)], + seconds: vec![0.0; 6], + }; + let t = Table { options: Options::quick(), rows: vec![row(9, 8), row(8, 8), row(7, 8), row(1, 9)], start_temperature: Value::Null }; + assert_eq!(t.tally_for(0), Tally { rows: 4, emulator_more: 1, tied: 1, classical_more: 2 }); + } +} diff --git a/wai/wai-quantum/src/quantum_landscape/trial.rs b/wai/wai-quantum/src/quantum_landscape/trial.rs new file mode 100644 index 000000000..fcb5a6945 --- /dev/null +++ b/wai/wai-quantum/src/quantum_landscape/trial.rs @@ -0,0 +1,192 @@ +//! A trial, outside the job form: does limiting each cell of an assignment to +//! {0, +1} fix assignment above a 2×2 grid? +//! +//! Written as sums, each row and column of a grid must sum to 1: within a row +//! or a column cells oppose, each cell has a field of 2, and the cost is 1. Then +//! `E = Σ_G ½(Σ_{i∈G} s_i − 1)² − ½·|G|`, over the rows and columns `G`. A row +//! can sum to 1 with three active cells, because a −1 cancels a +1, so above 2×2 +//! most lowest states are not assignments. If a cell can only be 0 or +1, a sum +//! of 1 means exactly one choice, and the lowest states are the assignments. +//! +//! The sampler here is the kernel's rule over two values. It is written here +//! because the kernel's cell has three, which the job form keeps. It is not what +//! a job runs: it exists to say how far the idea goes, which is that it fixes +//! whether an answer is valid and not whether it is cheap. The couplings are +//! fixed at 1, so the constraint's weight is 1, and a cost of 1 ties with +//! leaving a row empty. + +use super::classical::Stream; +use super::energy::Landscape; +use super::program::{Check, grade}; +use super::sample::temperature; +use super::truth::exhaustive; +use crate::repro::exp; +use serde_json::{Value, json}; + +/// The grid's landscape: every cell in one row and one column. +pub fn assignment(m: usize) -> Landscape { + let mut l = Landscape::new(m * m); + for a in 0..m { + for b in 0..m { + for c in (b + 1)..m { + l.couple(a * m + b, a * m + c, -1); + l.couple(b * m + a, c * m + a, -1); + } + } + } + for i in 0..m * m { + l.set_field(i, 2); + } + l.set_cost(1); + l +} + +/// Cooling one cell at a time, each cell limited to 0 or +1. +pub fn sample_zero_or_plus(l: &Landscape, restarts: u32, sweeps: u32, t0: f64, seed: u64) -> (Vec, i64) { + let w = l.lower(); + let n = l.len(); + let mut r = Stream::new(seed); + let mut best = vec![0i8; n]; + let mut best_e = l.energy(&best); + for _ in 0..restarts { + let mut s: Vec = (0..n).map(|_| r.below(2) as i8).collect(); + for k in 0..sweeps { + let t = temperature(t0, sweeps, k); + for i in 0..n { + let gain = (w.field(&s, i) - l.cost()) as f64; + s[i] = if t <= 0.0 { i8::from(gain > 0.0) } else { i8::from(r.unit() < 1.0 / (1.0 + exp(-gain / t))) }; + } + } + let e = l.energy(&s); + if e < best_e { + best_e = e; + best = s; + } + } + (best, best_e) +} + +fn permutations(m: usize, f: &mut impl FnMut(&[usize])) { + fn go(k: usize, p: &mut Vec, f: &mut impl FnMut(&[usize])) { + if k == p.len() { + f(p); + return; + } + for i in k..p.len() { + p.swap(k, i); + go(k + 1, p, f); + p.swap(k, i); + } + } + let mut p: Vec = (0..m).collect(); + go(0, &mut p, f); +} + +/// Grids from 2×2 up, on the job's three-state kernel and on {0, +1}, at the +/// same budget, and the same trial with costs of 0 or 1 a cell. +pub fn assignment_trial() -> Value { + use super::sample::{anneal, chain_seed, start}; + let (restarts, sweeps, runs) = (8u32, 60u32, 40u32); + let mut feasibility = Vec::new(); + for m in [2usize, 3, 4, 5, 6, 8] { + let l = assignment(m); + let w = l.lower(); + let t0 = l.default_temperature(); + let check = Check::Permutation(m); + let truth = exhaustive(&l); + let mut assignments_lowest = None; + if let Some(t) = &truth { + let mut count = 0u64; + permutations(m, &mut |p: &[usize]| { + let mut s = vec![0i8; m * m]; + for (r, &c) in p.iter().enumerate() { + s[r * m + c] = 1; + } + count += u64::from(l.energy(&s) as f64 == t.lowest); + }); + assignments_lowest = Some(count); + } + let (mut three, mut two) = (0u32, 0u32); + for k in 0..runs { + let seed = 0x5EED_0000 + u64::from(k); + let mut best: Option<(Vec, i64)> = None; + for r in 0..u64::from(restarts) { + let c = chain_seed(seed, r); + let o = anneal(&w, &start(c, m * m), t0, sweeps, c); + let e = l.energy(&o.state); + if best.as_ref().is_none_or(|b| e < b.1) { + best = Some((o.state, e)); + } + } + three += u32::from(grade(&check, &best.expect("eight starts").0).passed); + two += u32::from(grade(&check, &sample_zero_or_plus(&l, restarts, sweeps, t0, seed).0).passed); + } + feasibility.push(json!({ + "side": m, + "lowest_states": truth.as_ref().map(|t| t.at_lowest), + "lowest_states_that_are_assignments": assignments_lowest, + "runs": runs, + "three_states_valid": three, + "zero_or_plus_valid": two, + })); + } + let mut with_costs = Vec::new(); + for m in [2usize, 3, 4, 5] { + let check = Check::Permutation(m); + let mut classes = [(0u32, 0u32, 0u32, 0u32), (0, 0, 0, 0)]; + for inst in 0..20u64 { + let mut r = Stream::new(0xC057 ^ (inst << 8) ^ m as u64); + let costs: Vec = (0..m * m).map(|_| r.below(2) as i64).collect(); + let mut l = assignment(m); + for (i, &c) in costs.iter().enumerate() { + l.set_field(i, 2 - c); + } + let mut lowest = i64::MAX; + permutations(m, &mut |p: &[usize]| { + lowest = lowest.min(p.iter().enumerate().map(|(row, &c)| costs[row * m + c]).sum()); + }); + let class = &mut classes[usize::from(lowest == 0)]; + class.0 += 1; + let t0 = l.default_temperature(); + for k in 0..10u64 { + class.1 += 1; + let (s, _) = sample_zero_or_plus(&l, restarts, sweeps, t0, 0xA11 + k + (inst << 20)); + if grade(&check, &s).passed { + class.2 += 1; + let cost: i64 = s.iter().zip(&costs).map(|(&v, &c)| i64::from(v) * c).sum(); + class.3 += u32::from(cost == lowest); + } + } + } + for (zero, c) in [(false, classes[0]), (true, classes[1])] { + if c.0 > 0 { + with_costs.push(json!({ "side": m, "cheapest_costs_zero": zero, "instances": c.0, "runs": c.1, "valid": c.2, "cheapest": c.3 })); + } + } + } + json!({ "feasibility": feasibility, "with_costs": with_costs }) +} + +#[cfg(test)] +mod tests { + use super::*; + + /// At 2×2 the lowest states are the assignments. At 3×3 every assignment is lowest, and others are too. + #[test] + fn the_grid_s_lowest_states_are_as_the_text_says() { + let t = exhaustive(&assignment(2)).unwrap(); + assert_eq!(t.at_lowest, 2); + let check = [Check::Permutation(3)]; + let t = crate::quantum_landscape::truth::exhaustive_checked(&assignment(3), &check).unwrap(); + assert_eq!((t.at_lowest, t.at_lowest_passing), (21, Some(6))); + } + + #[test] + fn the_two_value_cell_picks_the_lower_energy_when_cold() { + let mut l = Landscape::new(1); + l.set_field(0, 3); + assert_eq!(sample_zero_or_plus(&l, 1, 1, 1.0, 1).0, vec![1]); + l.set_field(0, -3); + assert_eq!(sample_zero_or_plus(&l, 1, 1, 1.0, 1).0, vec![0]); + } +} diff --git a/wai/wai-quantum/src/quantum_landscape/truth.rs b/wai/wai-quantum/src/quantum_landscape/truth.rs new file mode 100644 index 000000000..ebff4d027 --- /dev/null +++ b/wai/wai-quantum/src/quantum_landscape/truth.rs @@ -0,0 +1,218 @@ +//! Exhaustive truth: every arrangement of a small program, checked. An answer +//! is graded against this, so it does not go through a kernel. It walks a +//! reflected Gray code, changing one cell at a time and keeping each cell's +//! field up to date, so that `3^16` arrangements take seconds. + +use super::EXHAUSTIVE_MAX_VARS; +use super::energy::{Landscape, Weights}; +use super::program::{Check, grade}; + +/// What checking every arrangement found. +#[derive(Clone, Debug, PartialEq)] +pub struct Truth { + pub arrangements: u64, + pub lowest: f64, + pub at_lowest: u64, + /// One arrangement at the lowest energy, the first the walk met. + pub example: Vec, + /// How many of those at the lowest energy pass every one of the problem's + /// checks, when it has any. Fewer than `at_lowest` means the landscape + /// does not express the problem, whatever any one run returns. + pub at_lowest_passing: Option, +} + +/// The exact lowest energy of a landscape of at most [`EXHAUSTIVE_MAX_VARS`] variables. +pub fn exhaustive(l: &Landscape) -> Option { + exhaustive_checked(l, &[]) +} + +/// As [`exhaustive`], grading every arrangement at the lowest energy against `checks`. +pub fn exhaustive_checked(l: &Landscape, checks: &[Check]) -> Option { + let n = l.len(); + if n > EXHAUSTIVE_MAX_VARS { + return None; + } + let w = l.lower(); + let cost = l.cost(); + let mut s = vec![-1i8; n]; + let mut h: Vec = (0..n).map(|i| w.field(&s, i)).collect(); + let mut energy = l.energy(&s); + let mut best = energy; + let mut count = 1u64; + let mut example = s.clone(); + let mut arrangements = 1u64; + let passes = |s: &[i8]| u64::from(checks.iter().all(|c| grade(c, s).passed)); + let mut passing = passes(&s); + let mut dir = vec![1i8; n]; + loop { + // The lowest digit that can still move in its direction moves; those below it turn round. + let mut i = 0; + loop { + if i == n { + let at_lowest_passing = (!checks.is_empty()).then_some(passing); + return Some(Truth { arrangements, lowest: best as f64, at_lowest: count, example, at_lowest_passing }); + } + if (-1..=1).contains(&(s[i] + dir[i])) { + break; + } + dir[i] = -dir[i]; + i += 1; + } + let old = s[i]; + s[i] += dir[i]; + let d = i64::from(s[i] - old); + energy += -d * h[i] + cost * (i64::from(s[i].abs()) - i64::from(old.abs())); + for (j, c) in w.row(i) { + h[j] += c * d; + } + arrangements += 1; + if energy < best { + best = energy; + count = 1; + example.copy_from_slice(&s); + if !checks.is_empty() { + passing = passes(&s); + } + } else if energy == best { + count += 1; + if !checks.is_empty() { + passing += passes(&s); + } + } + } +} + +/// The lowest energy on real-valued weights as declared, judged within 1e-9. +pub fn exhaustive_declared(real: &Weights) -> Option { + let n = real.len(); + if n > EXHAUSTIVE_MAX_VARS { + return None; + } + let (mut best, mut count, mut arrangements) = (f64::INFINITY, 0u64, 0u64); + let mut example = vec![0i8; n]; + let mut s = vec![-1i8; n]; + loop { + let e = real.energy(&s); + arrangements += 1; + if e < best - 1e-9 { + best = e; + count = 1; + example.copy_from_slice(&s); + } else if (e - best).abs() <= 1e-9 { + count += 1; + } + let mut i = 0; + loop { + if i == n { + return Some(Truth { arrangements, lowest: best, at_lowest: count, example, at_lowest_passing: None }); + } + if s[i] < 1 { + s[i] += 1; + break; + } + s[i] = -1; + i += 1; + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::quantum_landscape::sample::out; + + /// The plain way: every state, through the declared energy. + fn plain(l: &Landscape) -> (u64, i64, u64) { + let n = l.len(); + let mut s = vec![-1i8; n]; + let (mut seen, mut best, mut count) = (0u64, i64::MAX, 0u64); + loop { + let v = l.energy(&s); + seen += 1; + if v < best { + best = v; + count = 1; + } else if v == best { + count += 1; + } + let mut i = 0; + loop { + if i == n { + return (seen, best, count); + } + if s[i] < 1 { + s[i] += 1; + break; + } + s[i] = -1; + i += 1; + } + } + } + + fn random(n: usize, seed: u64) -> Landscape { + let mut l = Landscape::new(n); + let mut k = 0u64; + let mut next = || { + k += 1; + out(seed, k) + }; + for i in 0..n { + for j in (i + 1)..n { + l.couple(i, j, [-1i8, 0, 0, 1][(next() % 4) as usize]); + } + l.set_field(i, (next() % 7) as i64 - 3); + } + l.set_cost((next() % 3) as i64); + l + } + + #[test] + fn the_fast_walk_agrees_with_the_plain_one() { + for seed in 1..40u64 { + let n = 1 + (seed % 8) as usize; + let l = random(n, seed.wrapping_mul(0x9E37_79B9)); + let t = exhaustive(&l).unwrap(); + let (seen, best, count) = plain(&l); + assert_eq!(t.arrangements, seen, "n = {n}"); + assert_eq!(t.arrangements, 3u64.pow(n as u32)); + assert_eq!(t.lowest, best as f64, "n = {n}, seed {seed}"); + assert_eq!(t.at_lowest, count, "n = {n}, seed {seed}"); + assert_eq!(l.energy(&t.example) as f64, t.lowest, "the example really has that energy"); + } + } + + #[test] + fn a_known_answer_two_cells_that_must_oppose() { + let mut l = Landscape::new(2); + l.couple(0, 1, -1); + let t = exhaustive(&l).unwrap(); + // E = s0·s1: lowest −1, at (+,−) and (−,+). + assert_eq!((t.lowest, t.at_lowest, t.arrangements), (-1.0, 2, 9)); + } + + #[test] + fn the_lowest_states_are_graded_against_the_checks() { + let d = [Check::Different(vec![(0, 1)])]; + let mut l = Landscape::new(2); + l.couple(0, 1, -1); + assert_eq!(exhaustive_checked(&l, &d).unwrap().at_lowest_passing, Some(2)); + l.couple(0, 1, 1); + assert_eq!(exhaustive_checked(&l, &d).unwrap().at_lowest_passing, Some(0)); + assert_eq!(exhaustive(&l).unwrap().at_lowest_passing, None); + } + + #[test] + fn too_large_is_none_and_the_largest_is_walked_whole() { + assert!(exhaustive(&Landscape::new(EXHAUSTIVE_MAX_VARS + 1)).is_none()); + assert_eq!(exhaustive(&Landscape::new(EXHAUSTIVE_MAX_VARS)).unwrap().arrangements, 3u64.pow(16)); + } + + #[test] + fn the_declared_weights_are_checked_too() { + let mut r = Weights::new(2, &[0, 0], 0); + r.set(0, 1, -3.0); + let t = exhaustive_declared(&r).unwrap(); + assert_eq!((t.lowest, t.at_lowest), (-3.0, 2)); + } +} diff --git a/wai/wai-quantum/tests/landscape_conformance.rs b/wai/wai-quantum/tests/landscape_conformance.rs new file mode 100644 index 000000000..96ee12177 --- /dev/null +++ b/wai/wai-quantum/tests/landscape_conformance.rs @@ -0,0 +1,283 @@ +//! The conformance set in `quantum-conformance/landscape/vectors.json`, held to +//! the code and read as a member's checker reads it: from the file alone. + +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::run::state_text; +use wai_quantum::quantum_landscape::sample::{anneal_with, cuts, schedule}; +use wai_quantum::quantum_landscape::settle::{Outcome, Term, Workload}; + +fn text() -> String { + let path = concat!(env!("CARGO_MANIFEST_DIR"), "/../quantum-conformance/landscape/vectors.json"); + std::fs::read_to_string(path).expect("the conformance set is in the repository") +} + +fn file() -> Value { + serde_json::from_str(&text()).expect("it is JSON") +} + +fn cells(text: &str) -> Vec { + text.chars().map(|c| match c { '+' => 1, '-' => -1, _ => 0 }).collect() +} + +fn bits(v: &Value) -> f64 { + f64::from_bits(u64::from_str_radix(v.as_str().unwrap(), 16).unwrap()) +} + +fn landscape(v: &Value) -> Landscape { + let mut l = Landscape::new(v["n"].as_u64().unwrap() as usize); + for c in v["couplings"].as_array().unwrap() { + l.couple(c[0].as_u64().unwrap() as usize, c[1].as_u64().unwrap() as usize, c[2].as_i64().unwrap() as i8); + } + for (i, b) in v["field"].as_array().unwrap().iter().enumerate() { + l.set_field(i, b.as_i64().unwrap()); + } + l.set_cost(v["cost"].as_i64().unwrap()); + l +} + +#[test] +fn the_file_is_what_the_code_writes() { + // As text: the numbers in it are compared by the bytes they are written as. + let written = format!("{}\n", serde_json::to_string_pretty(&vectors()).unwrap()); + assert!(text() == written, "rebuild it with `wai-quantum landscape vectors`, and say why in the change"); +} + +#[test] +fn the_settle_vectors_hold() { + let f = file(); + let cases = f["settle"].as_array().unwrap(); + assert!(cases.len() >= 12); + let terms: std::collections::BTreeSet<&str> = cases.iter().map(|c| c["expect"]["term"].as_str().unwrap()).collect(); + assert_eq!(terms.len(), 3, "every ending is exercised: {terms:?}"); + for c in cases { + let o = landscape(&c["landscape"]).lower().settle(&cells(c["start"].as_str().unwrap()), c["bound"].as_u64().unwrap() as u32); + let e = &c["expect"]; + assert_eq!(state_text(&o.state), e["state"].as_str().unwrap(), "{}", c["name"]); + assert_eq!(u64::from(o.sweeps), e["sweeps"].as_u64().unwrap(), "{}", c["name"]); + assert_eq!(o.term.label(), e["term"].as_str().unwrap(), "{}", c["name"]); + assert_eq!(o.accumulates, e["accumulates"].as_u64().unwrap(), "{}", c["name"]); + } +} + +/// The exponential, the schedule and the cut points, bit for bit, from the text's formulas. +#[test] +fn the_exponential_schedule_and_cut_points_hold() { + let f = file(); + for v in f["exp"].as_array().unwrap() { + let x = bits(&v[0]); + // The platform's exp is close; the crate's is the one pinned, through the cut points. + let y = bits(&v[1]); + let d = (y - x.exp()).abs(); + assert!(y == x.exp() || d <= 4.0 * f64::EPSILON * y.abs().max(x.exp().abs()) || d < 1e-300, "{x}"); + } + for v in f["schedule"].as_array().unwrap() { + let (t0, sweeps) = (bits(&v["t0_bits"]), v["sweeps"].as_u64().unwrap() as u32); + let want: Vec = v["temperatures_bits"].as_array().unwrap().iter().map(bits).collect(); + // The text: q = k / (K − 1), T = t0 − t0·q; a run of one sweep is at 0. + let spec: Vec = (0..sweeps).map(|k| if sweeps <= 1 { 0.0 } else { t0 - t0 * (f64::from(k) / f64::from(sweeps - 1)) }).collect(); + assert_eq!(spec.iter().map(|t| t.to_bits()).collect::>(), want.iter().map(|t| t.to_bits()).collect::>()); + assert_eq!(schedule(t0, sweeps), want); + } + for c in f["cells"].as_array().unwrap() { + let (h, k, t) = (c["field"].as_i64().unwrap(), c["cost"].as_i64().unwrap(), bits(&c["temperature_bits"])); + assert_eq!(cuts(h, k, 1.0 / t), (c["cut_a"].as_u64().unwrap(), c["cut_b"].as_u64().unwrap()), "{c}"); + } +} + +#[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); + 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); + let e = &ch["expect"]; + assert_eq!(state_text(&o.state), e["state"].as_str().unwrap(), "{}", c["name"]); + assert_eq!((o.accumulates, o.draws), (e["accumulates"].as_u64().unwrap(), e["draws"].as_u64().unwrap()), "{}", c["name"]); + } + } +} + +#[test] +fn the_job_vectors_hold() { + let f = file(); + let cases = f["jobs"].as_array().unwrap(); + assert!(cases.iter().filter(|c| c["expect"]["refused"].is_string()).count() >= 10, "refusals are held too"); + assert!(cases.iter().any(|c| c["expect"]["verdict"] == "failed")); + assert!(cases.iter().any(|c| c["expect"]["verdict"] == "solved")); + for c in cases { + match job::call("run", &c["request"]) { + Ok(a) => { + let e = &c["expect"]; + for key in ["state", "energy", "verdict", "mode", "restarts", "sweeps", "replay"] { + assert_eq!(&a[key], &e[key], "{}: {key}", c["name"]); + } + assert_eq!(&a["work"]["accumulates"], &e["accumulates"], "{}", c["name"]); + assert_eq!(&a["work"]["draws"], &e["draws"], "{}", c["name"]); + } + Err(r) => assert_eq!(c["expect"]["refused"].as_str(), Some(r.plain().as_str()), "{}", c["name"]), + } + } +} + +/// The battery's distributions are the formula, worked out here with the +/// platform's own exponential, from the file alone. +#[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 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]; + } + p = q; + } + for (want, got) in p.iter().zip(c["exact"].as_array().unwrap()) { + assert!((want - got.as_f64().unwrap()).abs() < 1e-12, "{}", c["name"]); + } + } +} + +// ── kernels that the battery must refuse ─────────────────────────────────────────────────── + +/// A sampling kernel that is wrong in one way, its draws its own. +#[derive(Clone, Copy)] +enum Mutant { + /// Every cell from one common old state, as a sweep at 0 does. + AllAtOnce, + /// Even-indexed cells, then odd-indexed ones, each group from one state. + EvenThenOdd, + /// Every sweep at the first temperature, the last one too. + IgnoresSchedule, + /// A table that holds fields only up to 5. + ClampedTable, + /// The last sweep sampled, not run at 0. + NoFinalZero, + /// A proper colouring of the coupling graph: correct, so it is granted `statistical`. + ProperColouring, +} + +impl Member for Mutant { + fn id(&self) -> String { + "gpu:mutant".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 { + CpuReference.settle(w, start, bound) + } + fn chains(&self, w: &Workload, chains: &[Chain], temperatures: &[f64]) -> Result, String> { + Ok(chains.iter().map(|c| self.chain(w, c, temperatures)).collect()) + } +} + +impl Mutant { + fn chain(self, w: &Workload, c: &Chain, temperatures: &[f64]) -> Outcome { + let n = w.n(); + let mut s = c.start.clone(); + let mut x = c.seed ^ 0x9E37_79B9_7F4A_7C15; + let mut draw = move || { + x ^= x << 13; + x ^= x >> 7; + x ^= x << 17; + (x >> 11) as f64 / (1u64 << 53) as f64 + }; + let colour: Vec = match self { + Mutant::AllAtOnce => vec![0; n], + Mutant::EvenThenOdd => (0..n).map(|i| i % 2).collect(), + Mutant::ProperColouring => { + let mut colour = vec![usize::MAX; n]; + for i in 0..n { + let taken: Vec = w.row(i).map(|(j, _)| colour[j]).collect(); + colour[i] = (0..).find(|k| !taken.contains(k)).unwrap(); + } + colour + } + _ => (0..n).collect(), + }; + let groups = colour.iter().copied().max().map_or(0, |m| m + 1); + let last = temperatures.len().saturating_sub(1); + for (k, &t) in temperatures.iter().enumerate() { + let t = match self { + Mutant::IgnoresSchedule => temperatures[0], + Mutant::NoFinalZero if k == last => temperatures[last.saturating_sub(1)], + _ => t, + }; + if t <= 0.0 { + s = w.sweep(&s); + continue; + } + for g in 0..groups { + let old = s.clone(); + for i in (0..n).filter(|&i| colour[i] == g) { + let mut h = w.field(&old, i); + if matches!(self, Mutant::ClampedTable) { + h = h.clamp(-5, 5); + } + let (a, b) = cuts(h, w.cost(), 1.0 / t); + let u = (draw() * (1u64 << 53) as f64) as u64; + s[i] = if u < a { -1 } else if u < b { 0 } else { 1 }; + } + } + } + // What `check_chain` holds a member to: the schedule's draws, even where it sampled the last sweep. + let draws_due = (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: draws_due } + } +} + +/// The battery refuses every wrong kernel and grants `statistical` to a correct +/// one with draws of its own; the reference's own kernel passes it. +#[test] +fn the_battery_tells_a_sampler_from_a_wrong_one() { + let show = |who: &str, results: &[wai_quantum::quantum_landscape::conformance::BatteryResult]| { + for r in results { + eprintln!("{who:<18} {:<22} chi-square {:>12.1} against {:>6.1} ({} degrees): {}", r.name, r.chi_square, r.critical, r.degrees, if r.passed { "pass" } else { "FAIL" }); + } + }; + 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)] { + 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); +} + +#[test] +fn the_statistic_pools_small_bins() { + let (chi, df, critical) = chi_square(&[50, 50, 0, 0], &[0.5, 0.5, 0.0, 0.0], 100); + assert_eq!((chi, df), (0.0, 1)); + assert!(critical > 1.0); + let (chi, _, _) = chi_square(&[50, 49, 1], &[0.5, 0.5, 0.0], 100); + assert!(chi.is_infinite(), "a state that cannot occur, occurring, fails"); +} diff --git a/wai/wai-quantum/tests/landscape_float_read.rs b/wai/wai-quantum/tests/landscape_float_read.rs new file mode 100644 index 000000000..d39b1b712 --- /dev/null +++ b/wai/wai-quantum/tests/landscape_float_read.rs @@ -0,0 +1,16 @@ +//! A temperature given as a JSON number is read to the nearest double, with +//! only this module's feature on. +#[test] +fn a_json_number_is_read_to_the_nearest_double() { + let mut x = 0x2545_F491_4F6C_DD1Du64; + let mut differ = 0; + for _ in 0..20_000 { + x ^= x << 13; + x ^= x >> 7; + x ^= x << 17; + let text = format!("{}.{:016}", x % 100, x % 10_000_000_000_000_000); + let parsed: f64 = serde_json::from_str(&text).unwrap(); + differ += usize::from(parsed.to_bits() != text.parse::().unwrap().to_bits()); + } + assert_eq!(differ, 0); +} diff --git a/wai/wai-rs/src/lib.rs b/wai/wai-rs/src/lib.rs index 9b53d17c1..21b165f2c 100644 --- a/wai/wai-rs/src/lib.rs +++ b/wai/wai-rs/src/lib.rs @@ -1279,6 +1279,9 @@ pub use wai_quantum::qasm; #[cfg(feature = "quantum_engine")] pub use wai_quantum::engine; +#[cfg(feature = "quantum_engine")] +pub use wai_quantum::quantum_landscape; + // Matrix Product State backend (`wai.quantum.mps`) — the tensor-network classical // referee: a bounded-bond MPS evolved with a deterministic complex Jacobi SVD // truncation, bit-reproducible on every machine, reporting max bond + retained diff --git a/wai/wai-web/demo/README.md b/wai/wai-web/demo/README.md index f334ea439..4a4376ba3 100644 --- a/wai/wai-web/demo/README.md +++ b/wai/wai-web/demo/README.md @@ -27,6 +27,27 @@ python3 -m http.server 8741 Needs a browser with WebGPU enabled. No build steps beyond wasm-pack; no JS dependencies. +## `landscape.html` — energy-landscape jobs in a tab + +A classical emulator of the settling model, running in a worker on this machine +(`extensions/quantum-landscape.md`): one button, the worked examples that fail on +purpose among them, a temperature sweep, classical methods on the same program, and +the published table. Each answer is sealed with a key made in the page, and its +receipt is checked there. + +``` +# from wai-web/ +cargo build --locked --release --target wasm32-unknown-unknown --features quantum_engine +wasm-bindgen --target web --out-dir demo/pkg-engine --out-name wai_web \ + target/wasm32-unknown-unknown/release/wai_web.wasm +# serve wai/, so that the page reaches the published table, and open /wai-web/demo/landscape.html +cd .. && python3 -m http.server 8741 +``` + +`verify_landscape_wasm.mjs` compares the browser build's answers with `wai-quantum +landscape call`, byte for byte, in Node. `verify_landscape_page.mjs` does so in Chrome, +after pressing every button at two widths in both colour schemes. + ## `int_synth.html` — integer-exact neural image sink (no ML runtime) `int_synth.html` decodes a `wai.neural.int_synth` payload to RGB with the diff --git a/wai/wai-web/demo/landscape.html b/wai/wai-web/demo/landscape.html new file mode 100644 index 000000000..1872c28b9 --- /dev/null +++ b/wai/wai-web/demo/landscape.html @@ -0,0 +1,137 @@ + + + + + +Energy-landscape jobs + + + + + + +
+

Energy-landscape jobs

+

A classical emulator of the settling model of a planned energy-based processor, running on your machine. 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.

+ +

Press one button

+

Twelve talks go into room A or room B, or are left out. Talks that share an audience must not be in the same room; talks in a series belong together; and a talk with little reason to run stays out. The emulator describes the problem as an energy landscape, settles into a low point, and then checks every one of the 531,441 possible arrangements to see whether that low point is the lowest.

+ +

+
+
+ What the words mean +
+
Energy
A score the program gives every arrangement. Lower is better. It is a whole number, computed exactly, so anyone can work it out again.
+
Arrangement
One choice for every variable: +, - or 0. Twelve variables have 3 to the 12th power of them.
+
Accumulate
Adding one link's pull to one variable's total. It is the unit of work, and the joule line prices it.
+
Temperature
How much randomness the settling allows. At 0 it is deterministic and can get stuck; above 0 it can climb out of a poor low point.
+
Replay hash
A short code for the whole run. The same program, seed and temperature give the same code on any machine.
+
+
+ +

Worked examples, including the ones that fail

+

Each example says what it is there to show. Those marked as failing fail on purpose: they show what this model cannot do.

+
+

+ + +
+
+
+ + +
+

+
+ +

Sweep the temperature

+

The program in the editor, twenty times at each of several temperatures, with the same budget at each.

+ +

+
+ +

The same program on classical solvers

+

Simulated annealing, tabu search and repeated greedy descent, on the program in the editor, each given the same number of operations a run as the emulator.

+ +

+
+ +

How it scales

+

The published table: four families of problem, from 8 to 128 variables, each solver given the same budget per run. The count under it is worked out from the table, not written by hand.

+
+
+ + + diff --git a/wai/wai-web/demo/landscape.js b/wai/wai-web/demo/landscape.js new file mode 100644 index 000000000..37d6097a5 --- /dev/null +++ b/wai/wai-web/demo/landscape.js @@ -0,0 +1,324 @@ +// Energy-landscape jobs in a page (extensions/quantum-landscape.md). It binds to markup carrying +// data-landscape-* attributes. Every run happens in a worker, on this machine: nothing is sent anywhere. + +const root = document.querySelector("[data-landscape]"); +if (root) mount(root); + +function mount(root) { + const $ = (sel) => root.querySelector(sel); + const status = (name) => root.querySelector(`[data-landscape-status="${name}"]`); + const editor = $("[data-landscape-editor]"); + const seedInput = $("[data-landscape-seed]"); + const tempInput = $("[data-landscape-temperature]"); + const about = $("[data-landscape-example-about]"); + const list = $("[data-landscape-examples]"); + const stopButton = $("[data-landscape-stop]"); + const actionButtons = [...root.querySelectorAll("[data-landscape-quick],[data-landscape-run],[data-landscape-sweep],[data-landscape-compare]")]; + let examples = []; + + // ── the worker ──────────────────────────────────────────────────────────────────────────────── + let worker = null; + const pending = new Map(); + let nextId = 1; + function getWorker() { + if (worker) return worker; + worker = new Worker(new URL("./landscape_worker.js", import.meta.url), { type: "module" }); + worker.onmessage = (e) => { + const p = pending.get(e.data.id); + if (!p) return; + pending.delete(e.data.id); + if (e.data.error !== undefined) { + p.reject(new Error(e.data.error)); + // A trap can leave the module's memory in any state, so start again from a fresh one. + worker.terminate(); + worker = null; + } else { + p.resolve({ body: JSON.parse(e.data.text), verified: e.data.verified }); + } + }; + worker.onerror = () => fail(new Error("the emulator could not start in this browser")); + return worker; + } + function fail(error) { + for (const p of pending.values()) p.reject(error); + pending.clear(); + if (worker) worker.terminate(); + worker = null; + } + // A sealed answer, or `{ error, line }` for a refusal: `{ refused }` then says so in one sentence. + function ask(op, program, options = {}) { + const request = { ...options }; + if (program !== undefined) request.program = program; + return new Promise((resolve, reject) => { + const id = nextId++; + pending.set(id, { resolve, reject }); + getWorker().postMessage({ id, op, request }); + }).then(({ body, verified }) => (body.error === undefined ? { result: body.result, verified } : { refused: body.error })); + } + + let stopTimer = null; + async function busy(name, message, job) { + actionButtons.forEach((b) => (b.disabled = true)); + status(name).textContent = message; + stopTimer = setTimeout(() => { if (stopButton) stopButton.hidden = false; }, 700); + try { + return await job(); + } catch (e) { + status(name).textContent = e.message === "stopped" ? "Stopped. Nothing was kept from that run." : `The emulator stopped: ${e.message}. Press the button again to start it afresh.`; + return null; + } finally { + clearTimeout(stopTimer); + if (stopButton) stopButton.hidden = true; + actionButtons.forEach((b) => (b.disabled = false)); + } + } + if (stopButton) stopButton.addEventListener("click", () => fail(new Error("stopped"))); + + // ── small helpers ───────────────────────────────────────────────────────────────────────────── + const n = (v) => Number(v).toLocaleString("en-GB"); + function el(tag, attrs = {}, ...kids) { + const e = document.createElement(tag); + for (const [k, v] of Object.entries(attrs)) { + if (k === "class") e.className = v; + else if (k === "text") e.textContent = v; + else e.setAttribute(k, v); + } + for (const k of kids) if (k !== null && k !== undefined) e.append(k); + return e; + } + const VERDICT = { solved: "Solved", missed: "Missed", failed: "Did not solve it", unchecked: "Not checked" }; + const plural = (count, word) => (count === 1 ? `1 ${word}` : `${n(count)} ${word}s`); + function listWords(items) { + if (items.length <= 1) return items.join(""); + return `${items.slice(0, -1).join(", ")} and ${items[items.length - 1]}`; + } + + function groupsSentence(r) { + const item = r.item; + if (!r.groups.length) return `The answer is ${r.state}: one sign per variable, + or - or 0, in order.`; + return r.groups + .map((g) => { + const name = g.name.charAt(0).toUpperCase() + g.name.slice(1); + if (!g.items.length) return `${name}: none.`; + const words = item ? `${g.items.length === 1 ? item : item + "s"} ${listWords(g.items.map(String))}` : listWords(g.items.map(String)); + return `${name}: ${words}.`; + }) + .join(" "); + } + + function refusal(box, answer) { + box.replaceChildren( + el("div", { class: "vq-card vq-failed" }, + el("p", { class: "vq-verdict" }, el("span", { class: "vq-badge", text: "Refused" }), " ", answer.refused), + el("p", { class: "vq-note", text: "Nothing ran. Fix that line and press Run again." }), + ), + ); + } + + function badge(r) { + return el("span", { class: "vq-badge", text: VERDICT[r.verdict] ?? r.verdict }); + } + + // ── the one-button run ──────────────────────────────────────────────────────────────────────── + const quick = $("[data-landscape-quick]"); + if (quick) { + quick.addEventListener("click", () => busy("quick", "Running, and checking every possible arrangement…", async () => { + const source = (await loadExamples()).find((e) => e.name === quick.dataset.landscapeQuick).program; + const answer = await ask("run", source, { seed: "1" }); + const box = $("[data-landscape-quick-result]"); + if (answer.refused) { + refusal(box, answer); + status("quick").textContent = `Refused. ${answer.refused}`; + return; + } + const r = answer.result; + box.replaceChildren( + el("div", { class: `vq-card vq-${r.verdict}` }, + el("p", { class: "vq-verdict" }, badge(r), " ", r.summary), + el("p", { text: groupsSentence(r) }), + el("p", { text: `It took ${plural(r.work.accumulates, "accumulate")}: each one adds one link's pull to one variable's total. ${r.joules.line}` }), + el("p", { class: "vq-note", text: "The price comes from rates measured on an FPGA board, applied to this run's count. This run itself was not measured: it ran on your machine." }), + el("p", { class: "vq-note", text: r.joules.left_out.line }), + ), + ); + status("quick").textContent = "Done. Press it again and the answer comes back the same: the seed fixes every random draw."; + })); + } + + // ── the editor and a full run ───────────────────────────────────────────────────────────────── + async function loadExamples() { + if (examples.length) return examples; + const answer = await ask("examples"); + examples = answer.result.examples; + return examples; + } + + function choose(name) { + const ex = examples.find((e) => e.name === name); + if (!ex || !editor) return; + editor.value = ex.program; + if (about) about.textContent = ex.about; + list.querySelectorAll("button").forEach((b) => b.setAttribute("aria-pressed", String(b.dataset.name === name))); + $("[data-landscape-result]").replaceChildren(); + status("run").textContent = `${ex.title} is in the editor. Press Run.`; + } + + if (list) { + loadExamples().then((all) => { + list.replaceChildren(...all.map((ex) => { + const fails = ex.expect === "fails"; + const b = el("button", { type: "button", class: `vq-example${fails ? " vq-example-fails" : ""}`, "data-name": ex.name, "aria-pressed": "false" }, + el("span", { class: "vq-example-title", text: ex.title.replace(/: this fails$/, "") }), + el("span", { class: "vq-example-tag", text: fails ? "Fails, on purpose" : "Works" }), + ); + b.addEventListener("click", () => choose(ex.name)); + return b; + })); + if (editor && !editor.value) choose(all[0].name); + }).catch((e) => { status("run").textContent = `The emulator could not start: ${e.message}`; }); + } + + function options() { + const o = { seed: seedInput?.value.trim() || "1" }; + const t = tempInput?.value.trim(); + if (t) o.temperature = t; + return o; + } + + function renderReport(r, verified) { + const facts = el("dl", { class: "vq-facts" }); + const fact = (term, ...detail) => facts.append(el("dt", { text: term }), el("dd", {}, ...detail)); + fact("Answer", el("code", { class: "vq-state", text: r.state }), el("span", { class: "vq-block", text: groupsSentence(r) })); + fact("Energy", String(r.energy)); + if (r.truth) { + let t = `All ${n(r.truth.arrangements)} arrangements checked. The lowest energy is ${r.truth.lowest}, held by ${n(r.truth.at_lowest)}.`; + if (r.truth.at_lowest_passing !== null) t += ` Of those, ${n(r.truth.at_lowest_passing)} solve the problem.`; + t += r.reached ? " This run reached it." : " This run did not reach it."; + fact("Every arrangement", t); + } else { + fact("Every arrangement", "Too many to check them all, so nothing here says whether this energy is the lowest."); + } + if (r.declared) { + fact("As declared", `On the weights as declared, this answer has energy ${r.declared.energy}${r.declared.truth ? `, and the lowest possible is ${r.declared.truth.lowest}` : ""}.`); + } + fact("Warnings", r.warnings.length ? el("ul", {}, ...r.warnings.map((w) => el("li", { text: (w.line ? `Line ${w.line}: ` : "") + w.text }))) : "None."); + if (r.checks.length) fact("The problem's own check", el("ul", {}, ...r.checks.map((c) => el("li", { text: `${c.passed ? "Passes" : "Fails"}. ${c.detail}` })))); + fact("How it ran", `${r.mode_words} Seed ${r.seed}. Ran on ${r.member.id}.`); + fact("Work", `${plural(r.work.accumulates, "accumulate")}, ${plural(r.work.sweeps, "sweep")}, ${plural(r.work.cell_updates, "cell update")}, ${plural(r.work.draws, "random draw")}. Ended: ${r.work.ended}.`); + fact("Joules", el("span", { class: "vq-block", text: r.joules.line }), el("span", { class: "vq-block vq-note", text: r.joules.rate_source }), el("span", { class: "vq-block vq-note", text: r.joules.published_add.line }), el("span", { class: "vq-block vq-note", text: r.joules.left_out.line })); + fact("Replay digest", el("code", { class: "vq-state", text: r.replay }), el("span", { class: "vq-block vq-note", text: `The same program, seed and temperature give this digest on any machine. The answer alone hashes to ${r.state_hash}.` })); + fact("Receipt", verified ? "Sealed with this page's key, and it verifies." : "It does not verify."); + return el("div", { class: `vq-card vq-${r.verdict}` }, el("p", { class: "vq-verdict" }, badge(r), " ", r.summary), facts); + } + + const runButton = $("[data-landscape-run]"); + if (runButton) { + runButton.addEventListener("click", () => busy("run", "Running…", async () => { + const source = editor.value; + const answer = await ask("run", source, options()); + const box = $("[data-landscape-result]"); + if (answer.refused) { + refusal(box, answer); + status("run").textContent = `Refused. ${answer.refused}`; + return; + } + box.replaceChildren(renderReport(answer.result, answer.verified)); + status("run").textContent = `Done: ${VERDICT[answer.result.verdict] ?? answer.result.verdict}.`; + })); + } + + // ── the temperature sweep ──────────────────────────────────────────────────────────────────── + function table(caption, head, rows) { + return el("div", { class: "vq-scroll", tabindex: "0", role: "region", "aria-label": caption }, + el("table", { class: "vq-table" }, + el("caption", { text: caption }), + el("thead", {}, el("tr", {}, ...head.map((h) => el("th", { scope: "col", text: h })))), + el("tbody", {}, ...rows.map((cells) => el("tr", {}, ...cells.map((c, i) => (i === 0 ? el("th", { scope: "row", text: c }) : el("td", { text: c })))))), + ), + ); + } + + const sweepButton = $("[data-landscape-sweep]"); + if (sweepButton) { + sweepButton.addEventListener("click", () => busy("sweep", "Sweeping: twenty runs at each temperature…", async () => { + const source = editor.value; + const answer = await ask("sweep", source, { seed: seedInput?.value.trim() || "1", seeds: 20 }); + const box = $("[data-landscape-sweep-result]"); + if (answer.refused) { + refusal(box, answer); + status("sweep").textContent = `Refused. ${answer.refused}`; + return; + } + const s = answer.result; + const rows = s.rows.map((r) => [ + `${Math.round(r.temperature * 1000) / 1000}${r.is_default ? " (the default)" : ""}`, + r.mode === "sample" ? "sampling" : "deterministic", + r.reached_lowest === null ? "unknown" : `${r.reached_lowest} of ${r.runs}`, + r.passed_checks === null ? "no check" : `${r.passed_checks} of ${r.runs}`, + String(r.best_energy), + String(Math.round(r.mean_energy * 100) / 100), + n(r.accumulates_per_run), + r.priced_per_run, + r.left_out_per_run, + ]); + box.replaceChildren( + table("The same program at each temperature, with the same budget", ["Temperature", "Mode", "Reached the lowest", "Passed the check", "Best energy", "Average energy", "Accumulates per run", "Priced per run", "Board draw left out"], rows), + el("p", { class: "vq-note", text: s.lowest === null ? "Too many arrangements to check them all, so no lowest energy is known here." : `The lowest energy, from checking every arrangement, is ${s.lowest}. Temperature 0 without random starts runs once: it does not depend on the seed.` }), + el("p", { class: "vq-note", text: "A priced figure counts the work alone. The board draws power at every clock too: the last column is what the fewest clocks a run takes come to, and the priced figure leaves it out." }), + ); + status("sweep").textContent = "Done."; + })); + } + + // ── the same instance on classical solvers ─────────────────────────────────────────────────── + const compareButton = $("[data-landscape-compare]"); + if (compareButton) { + compareButton.addEventListener("click", () => busy("compare", "Running the emulator and three classical solvers, twenty times each…", async () => { + const source = editor.value; + const answer = await ask("compare", source, { seed: seedInput?.value.trim() || "1", seeds: 20 }); + const box = $("[data-landscape-compare-result]"); + if (answer.refused) { + refusal(box, answer); + status("compare").textContent = `Refused. ${answer.refused}`; + return; + } + const v = answer.result; + const rows = v.rows.map((r) => [ + r.solver, + `${r.reached_target} of ${r.runs}`, + r.passed_checks === null ? "no check" : `${r.passed_checks} of ${r.runs}`, + String(r.best_energy), + n(r.operations_per_run), + ]); + const most = Math.max(...v.rows.map((r) => r.reached_target)); + const leaders = v.rows.filter((r) => r.reached_target === most).map((r) => r.solver); + box.replaceChildren( + table("The emulator and classical solvers on this program, at the same budget", ["Solver", "Reached the target", "Passed the check", "Best energy", "Operations per run"], rows), + el("p", { class: "vq-note", text: `Target energy ${v.target}: ${v.target_is_exact ? "the lowest possible, from checking every arrangement" : "the lowest any solver here found, which proves nothing about the lowest"}. Reached the target most often: ${listWords(leaders)}. One operation is one coupling read, one cell updated or one move considered.` }), + ); + status("compare").textContent = "Done."; + })); + } + + // ── the published table, on the standalone page ───────────────────────────────────────────── + const scale = $("[data-landscape-scale]"); + if (scale && scale.dataset.landscapeScale) { + fetch(new URL(scale.dataset.landscapeScale, import.meta.url)).then((r) => r.json()).then((t) => { + const head = ["Family", "Variables", ...t.solvers.map((s) => s.replace("emulator, ", "Emulator, "))]; + const shown = t.rows.filter((r) => r.sweeps_per_start === 40); + const rows = shown.map((r) => [r.family, String(r.n), ...r.solvers.map((c) => `${c.hits} of ${c.runs}`)]); + // The count under the table is of the rows above it: the default start against the best of the three classical methods. + const classical = t.solvers.map((name, i) => [name, i]).filter(([name]) => !name.startsWith("emulator")).map(([, i]) => i); + let more = 0, tied = 0, behind = 0; + for (const r of shown) { + const own = r.solvers[0].hits; + const best = Math.max(...classical.map((i) => r.solvers[i].hits)); + if (own > best) more += 1; + else if (own === best) tied += 1; + else behind += 1; + } + scale.replaceChildren(table("Runs that reached the target, at the same budget, 40 sweeps per start", head, rows), + el("p", { class: "vq-note", "data-landscape-tally": `${more} ${tied} ${behind}`, text: `Of these ${shown.length} rows, the emulator's sampling mode at the default start reached the target more often than every classical method in ${more}, tied the best of them in ${tied}, and was behind the best of them in ${behind}. The table's other ${t.rows.length - shown.length} rows, at 320 sweeps a start, are in the published table.` })); + }).catch(() => { scale.textContent = "The table did not load."; }); + } +} diff --git a/wai/wai-web/demo/landscape_worker.js b/wai/wai-web/demo/landscape_worker.js new file mode 100644 index 000000000..c50c266c6 --- /dev/null +++ b/wai/wai-web/demo/landscape_worker.js @@ -0,0 +1,18 @@ +// Energy-landscape jobs run here, off the page's main thread, so a long run never freezes the page. A run +// that traps is answered with an error, and the page starts a fresh worker. The module is the browser +// build with its job engine (demo/README.md). +import init, { QuantumEngine, engine_verify } from "./pkg-engine/wai_web.js"; + +const ready = init().then(() => new QuantumEngine(crypto.getRandomValues(new Uint8Array(32)), 256)); + +self.onmessage = async (event) => { + const { id, op, request } = event.data; + try { + const engine = await ready; + const text = engine.landscape(op, JSON.stringify(request)); + const verified = JSON.parse(engine_verify(text)).valid === true; + self.postMessage({ id, text, verified }); + } catch (e) { + self.postMessage({ id, error: String(e && e.message ? e.message : e) }); + } +}; diff --git a/wai/wai-web/demo/verify_landscape_page.mjs b/wai/wai-web/demo/verify_landscape_page.mjs new file mode 100644 index 000000000..d513452fb --- /dev/null +++ b/wai/wai-web/demo/verify_landscape_page.mjs @@ -0,0 +1,163 @@ +// The page in a real browser: at 1280×800 and 390×844, in the light and dark schemes. Press the one +// button, run a failing example and a typo, sweep and compare; after each, axe finds no WCAG 2.2 A or AA +// violation and nothing overflows sideways. Then, in the same browser, the build answers every worked +// example over many seeds, and each answer must be the command line's, byte for byte. +// +// E2E_MODULES= \ +// node verify_landscape_page.mjs [seeds] +// +// is the directory served (wai/), so that the page, its module and the published table are all +// reachable. Exit 0 iff every check passes. + +import { execFileSync } from 'node:child_process'; +import { createServer } from 'node:http'; +import { existsSync, mkdtempSync, readFileSync, statSync, writeFileSync } from 'node:fs'; +import { createRequire } from 'node:module'; +import { tmpdir } from 'node:os'; +import { extname, join, normalize, resolve } from 'node:path'; + +const [rootArg, bin, seedsArg] = process.argv.slice(2); +const ROOT = resolve(rootArg); +const SEEDS = Number(seedsArg ?? 500); +const require = createRequire(join(resolve(process.env.E2E_MODULES ?? '.'), 'x.js')); +const { chromium } = require('playwright'); +const AXE = require.resolve('axe-core/axe.min.js'); + +const TYPES = { '.html': 'text/html; charset=utf-8', '.js': 'text/javascript', '.mjs': 'text/javascript', '.css': 'text/css', '.json': 'application/json', '.wasm': 'application/wasm' }; +const server = createServer((req, res) => { + const file = normalize(join(ROOT, decodeURIComponent(new URL(req.url, 'http://x').pathname))); + if (!file.startsWith(ROOT) || !existsSync(file) || !statSync(file).isFile()) { res.writeHead(404).end(); return; } + res.writeHead(200, { 'content-type': TYPES[extname(file)] ?? 'application/octet-stream' }); + res.end(readFileSync(file)); +}); +await new Promise((r) => server.listen(0, '127.0.0.1', r)); +const BASE = `http://127.0.0.1:${server.address().port}/wai-web/demo/`; + +let passes = 0; +let fails = 0; +const check = (ok, what) => { if (ok) passes++; else fails++; console.log(`${ok ? 'PASS' : 'FAIL'} ${what}`); }; +const browser = await chromium.launch({ channel: process.env.E2E_CHANNEL ?? 'chrome' }); +const BANNED = /\b(coming|upcoming|soon|beta|will|won['’]t|not yet|yet to|early access|waitlist)\b|%|\b(faster|slower|rather than|unlike|outperforms?|quantum advantage|speed-?up)\b|±/i; // allow-phrase: the words this check looks for on the page +const LABEL = "A classical emulator of the settling model of a planned energy-based processor, running on your machine. 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."; + +async function axe(page, tag) { + await page.addScriptTag({ path: AXE }); + const r = await page.evaluate(() => window.axe.run(document, { runOnly: { type: 'tag', values: ['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa', 'wcag22aa'] } })); + const v = r.violations.map((x) => `${x.id} (${x.nodes.length}: ${x.nodes[0]?.target?.join(' ')})`); + check(v.length === 0, `${tag}: axe finds no WCAG 2.2 A or AA violation${v.length ? ': ' + v.join(' | ') : ''}`); + const overflow = await page.evaluate(() => document.documentElement.scrollWidth - window.innerWidth); + check(overflow <= 0, `${tag}: no horizontal overflow (${overflow} px)`); +} + +try { + for (const viewport of [{ width: 1280, height: 800 }, { width: 390, height: 844 }]) { + for (const colorScheme of ['light', 'dark']) { + const tag = `${viewport.width} ${colorScheme}`; + const ctx = await browser.newContext({ viewport, colorScheme, reducedMotion: 'reduce' }); + const page = await ctx.newPage(); + const errors = []; + page.on('console', (m) => { if (m.type() === 'error') errors.push(m.text()); }); + page.on('pageerror', (e) => errors.push(e.message)); + await page.goto(`${BASE}landscape.html`); + check((await page.locator('[data-landscape-label]').textContent()).trim() === LABEL, `${tag}: the label, word for word`); + await page.waitForSelector('[data-landscape-examples] button'); + await axe(page, `${tag} before any run`); + + await page.click('[data-landscape-quick]'); + await page.waitForSelector('[data-landscape-quick-result] .vq-card', { timeout: 60_000 }); + const quick = await page.locator('[data-landscape-quick-result]').innerText(); + check(/Solved/.test(quick) && /lowest possible/.test(quick) && /531,441/.test(quick), `${tag}: one button solves the talks and says so plainly`); + check(/Room A: talks/.test(quick) && /Priced, not read from a meter/.test(quick), `${tag}: the answer in the problem's words, and the joule line priced`); + + await page.click('[data-landscape-examples] button[data-name="assign-3x3"]'); + await page.click('[data-landscape-run]'); + await page.waitForSelector('[data-landscape-result] .vq-card', { timeout: 60_000 }); + const failed = await page.locator('[data-landscape-result]').innerText(); + check(/Did not solve it/.test(failed) && /does not express the problem/.test(failed), `${tag}: a failing example shows as a failure`); + check(/Replay digest/.test(failed) && /[0-9a-f]{64}/.test(failed) && /Warnings/.test(failed), `${tag}: the replay digest and the warnings are shown`); + check(/cpu:reference/.test(failed) && /it verifies/.test(failed), `${tag}: the answer names what ran it, and its receipt verifies in the page`); + + await page.fill('[data-landscape-editor]', 'vars 3\nagree 0 1\noppos 1 2\nsolve\n'); + await page.click('[data-landscape-run]'); + await page.waitForFunction(() => /Refused/.test(document.querySelector('[data-landscape-result]').innerText), null, { timeout: 60_000 }); + const refused = await page.locator('[data-landscape-result]').innerText(); + check(/Line 3:/.test(refused) && /Did you mean `oppose`\?/.test(refused), `${tag}: a typo gets a plain message with its line`); + + // A refusal through Sweep and Compare says so in the live status too, not "Sweeping…". + for (const [button, region] of [['sweep', 'sweep'], ['compare', 'compare']]) { + await page.click(`[data-landscape-${button}]`); + await page.waitForFunction((r) => /Refused/.test(document.querySelector(`[data-landscape-${r}-result]`).innerText), region, { timeout: 60_000 }); + const said = await page.locator(`[data-landscape-status="${region}"]`).innerText(); + check(/^Refused\. Line 3:/.test(said), `${tag}: a refusal through ${button} is in the live status ("${said.slice(0, 40)}")`); + } + + await page.click('[data-landscape-examples] button[data-name="talks"]'); + await page.click('[data-landscape-sweep]'); + await page.waitForSelector('[data-landscape-sweep-result] table', { timeout: 120_000 }); + check(/Board draw left out/.test(await page.locator('[data-landscape-sweep-result]').innerText()), `${tag}: the sweep says what each priced figure leaves out`); + const sweepRows = await page.locator('[data-landscape-sweep-result] tbody tr').count(); + check(sweepRows >= 7, `${tag}: the temperature sweep has a row a temperature (${sweepRows})`); + await page.click('[data-landscape-compare]'); + await page.waitForSelector('[data-landscape-compare-result] table', { timeout: 120_000 }); + const compared = await page.locator('[data-landscape-compare-result]').innerText(); + check(/simulated annealing/.test(compared) && /tabu search/.test(compared) && /greedy descent/.test(compared), `${tag}: classical methods on the same program`); + await page.waitForSelector('[data-landscape-scale] table', { timeout: 30_000 }); + // The count under the table is of the rows it shows, worked out again here from the cells. + const counted = await page.evaluate(() => { + let more = 0, tied = 0, behind = 0; + for (const tr of document.querySelectorAll('[data-landscape-scale] tbody tr')) { + const hits = [...tr.querySelectorAll('td')].slice(1).map((td) => Number(td.textContent.split(' of ')[0])); + const best = Math.max(hits[3], hits[4], hits[5]); + if (hits[0] > best) more += 1; else if (hits[0] === best) tied += 1; else behind += 1; + } + return `${more} ${tied} ${behind}`; + }); + const stated = await page.locator('[data-landscape-tally]').getAttribute('data-landscape-tally').catch(() => null); + const note = await page.locator('[data-landscape-scale] .vq-note').innerText(); + const [m, t, b] = counted.split(' '); + check(stated === counted && note.includes(`in ${m}, tied the best of them in ${t}, and was behind the best of them in ${b}`), `${tag}: the count under the table is of the rows shown (${counted})`); + await axe(page, `${tag} with every result shown`); + + const text = (await page.locator('main').innerText()).split(LABEL).join(' '); + const bad = text.match(BANNED); + check(!bad, `${tag}: no banned word on screen${bad ? ` ("${bad[0]}")` : ''}`); + check(errors.length === 0, `${tag}: no console error${errors.length ? ': ' + errors.join(' | ') : ''}`); + await ctx.close(); + } + } + + // The same requests in this browser's wasm engine as on the command line. + const tmp = mkdtempSync(join(tmpdir(), 'landscape-page-')); + const native = (op, text) => { + const f = join(tmp, 'request.json'); + writeFileSync(f, text); + return execFileSync(bin, ['landscape', 'call', op, f], { encoding: 'utf8', maxBuffer: 1 << 30 }).replace(/\n$/, ''); + }; + const page = await browser.newPage(); + await page.goto(`${BASE}landscape.html`); + const requests = await page.evaluate(async (seeds) => { + const { default: init, QuantumEngine } = await import('./pkg-engine/wai_web.js'); + await init(); + window.engine = new QuantumEngine(new Uint8Array(32).fill(4), 256); + const { examples } = JSON.parse(window.engine.landscape_answer('examples', '{}')); + return examples.flatMap(({ name, program }) => [undefined, 0, 0.5].flatMap((temperature) => { + const base = { program, ...(temperature === undefined ? {} : { temperature }) }; + return [['replay', { ...base, seed: '0', seeds }], ['run', { ...base, seed: '7' }]].map(([op, request]) => ({ name, op, text: JSON.stringify(request) })); + })); + }, SEEDS); + const inBrowser = await page.evaluate((all) => all.map(({ op, text }) => window.engine.landscape_answer(op, text)), requests); + let same = 0; + let seeded = 0; + requests.forEach(({ name, op, text }, i) => { + const a = native(op, text); + if (a === inBrowser[i]) same++; + else console.log(`DIFFER ${name} ${op}\n native ${a.slice(0, 200)}\n browser ${inBrowser[i].slice(0, 200)}`); + if (op === 'replay' && !a.startsWith('{"error"')) seeded += SEEDS; + }); + check(same === requests.length, `in the browser, ${same} of ${requests.length} answers are the command line's bytes (${seeded} seeded runs)`); +} finally { + await browser.close(); + server.close(); +} +console.log(`\npage: ${passes} passed, ${fails} failed`); +process.exit(fails ? 1 : 0); diff --git a/wai/wai-web/demo/verify_landscape_wasm.mjs b/wai/wai-web/demo/verify_landscape_wasm.mjs new file mode 100644 index 000000000..dbfc9a59f --- /dev/null +++ b/wai/wai-web/demo/verify_landscape_wasm.mjs @@ -0,0 +1,59 @@ +// Native and WebAssembly, compared byte for byte (extensions/quantum-landscape.md). For every worked +// example, at the program's own temperature and at 0 and 0.5, it sends the same request to `wai-quantum +// landscape call` and to the browser build, and compares the answers: a replay of many seeds (one line a +// seed, carrying a digest of that seed's whole answer), whole answers for the first seeds and the largest +// seed, a temperature sweep and the comparison with classical methods. +// +// node verify_landscape_wasm.mjs [seeds] +// +// Exit 0 iff every answer is the same bytes. + +import { execFileSync } from 'node:child_process'; +import { mkdtempSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join, resolve } from 'node:path'; +import { pathToFileURL } from 'node:url'; + +const [pkg, bin, seedsArg] = process.argv.slice(2); +const SEEDS = Number(seedsArg ?? 2000); +const { QuantumEngine } = await import(pathToFileURL(resolve(pkg)).href); +const engine = new QuantumEngine(new Uint8Array(32).fill(4), 256); +const tmp = mkdtempSync(join(tmpdir(), 'landscape-compare-')); + +const native = (op, text) => { + const f = join(tmp, 'request.json'); + writeFileSync(f, text); + return execFileSync(bin, ['landscape', 'call', op, f], { encoding: 'utf8', maxBuffer: 1 << 30 }).replace(/\n$/, ''); +}; + +const { examples } = JSON.parse(engine.landscape_answer('examples', '{}')); +// A temperature with every digit of a double, as text: a JSON number that long is read differently by +// serde_json's two float parsers, which is why the job form takes a string. +const temperatures = [undefined, 0, 0.5, '1.8708286933869707']; +let same = 0; +let differ = 0; +let seeded = 0; +for (const { name, program } of examples) { + for (const temperature of temperatures) { + const base = { program, ...(temperature === undefined ? {} : { temperature }) }; + const requests = [ + ['replay', { ...base, seed: '0', seeds: SEEDS }], + ...Array.from({ length: 10 }, (_, k) => ['run', { ...base, seed: String(k) }]), + ['run', { ...base, seed: '18446744073709551615' }], + ]; + if (temperature === undefined) requests.push(['sweep', { program, seed: '1', seeds: 5 }], ['compare', { program, seed: '1', seeds: 5 }]); + for (const [op, request] of requests) { + const text = JSON.stringify(request); + const a = native(op, text); + const b = engine.landscape_answer(op, text); + if (a === b) same += 1; + else { + differ += 1; + console.log(`DIFFER ${name} ${op} ${temperature ?? 'own'}\n native ${a.slice(0, 200)}\n wasm ${b.slice(0, 200)}`); + } + if (op === 'replay' && !a.startsWith('{"error"')) seeded += SEEDS; + } + } +} +console.log(`native and wasm: ${same} answers the same bytes, ${differ} different; ${seeded} seeded runs inside the replays.`); +process.exit(differ === 0 ? 0 : 1); diff --git a/wai/wai-web/src/quantum_engine.rs b/wai/wai-web/src/quantum_engine.rs index 4e47f0ed3..1bfc96fe5 100644 --- a/wai/wai-web/src/quantum_engine.rs +++ b/wai/wai-web/src/quantum_engine.rs @@ -125,6 +125,34 @@ impl QuantumEngine { } } + /// An energy-landscape call ([`wai::quantum_landscape::job::call`]): `run`, + /// `sweep`, `compare` or `examples`, its request the job's JSON. The answer + /// is sealed with the page's key, as a job's is. On a refusal, `{"error": + /// the plain sentence, "line": its line or null}`. + pub fn landscape(&self, op: &str, args: &str) -> String { + let req = match read(args) { + Ok(v) => v, + Err(e) => return error_json(e), + }; + match wai::quantum_landscape::job::call_certified(&wai::quantum_landscape::member::Certified::reference(), op, &req, self.job_bytes) { + Ok(result) => wai::quantum_landscape::job::seal(&req, result, &self.key).to_string(), + Err(r) => json!({ "error": r.plain(), "line": r.line }).to_string(), + } + } + + /// The same call's answer, unsealed and as the command line's `wai-quantum + /// landscape call` writes it: the bytes the two are compared on. + pub fn landscape_answer(&self, op: &str, args: &str) -> String { + let req = match read(args) { + Ok(v) => v, + Err(e) => return error_json(e), + }; + match wai::quantum_landscape::job::call_certified(&wai::quantum_landscape::member::Certified::reference(), op, &req, self.job_bytes) { + Ok(result) => result.to_string(), + Err(r) => json!({ "error": r.plain(), "line": r.line }).to_string(), + } + } + /// A pulse call ([`wai::engine::pulse::call`]): `schedule` (a pulse /// program's schedule on frames: every instruction at its sample, the /// waveforms' samples and the gate calls, which the Studio's pulse view @@ -871,6 +899,8 @@ mod tests { /// A pulse program, and the digest of its run's final density matrix: /// the same bits natively and in wasm32 (the engine pins it too). pub(super) const PULSE_XM: &str = "OPENQASM 3.0;\ndefcalgrammar \"openpulse\";\ncal {\n extern port d0;\n extern port m0;\n extern port a0;\n frame q0 = newframe(d0, 5.0e9, 0);\n frame r0 = newframe(m0, 7.1e9, 0);\n frame c0 = newframe(a0, 7.1e9, 0);\n}\ndefcal x $0 { play(q0, gaussian(0.4, 64dt, 16dt)); }\ndefcal measure $0 -> bit { play(r0, constant(0.1, 200dt)); return capture_v2(c0, 200dt); }\nbit[1] c;\nx $0;\nc[0] = measure $0;\n"; + /// `corpus_digest(3)` of the worked examples of `wai::quantum_landscape`: native and in wasm32. + pub(super) const LANDSCAPE_CORPUS_3: &str = "b9855a210329dad0d08c591cf17cf4594acf110de916e8fd88ac22ff1df82c11"; pub(super) const PULSE_XM_DENSITY: &str = "03c0c48a8a3568fdd86e792633c5b4ea98eca3d09dc40d028bb50e13c489828b"; pub(super) const COMPILE_DIGEST: &str = "198ea9bd9431f491a02dbd3af3a4b1309999837dfb0916902b41023aeab21040"; @@ -1283,6 +1313,22 @@ mod tests { assert_eq!(job["result"]["mitigation"]["method"], "vd", "{job}"); } + /// An energy-landscape job runs in the page, its answer sealed with the + /// page's key, a program refused with its line, and every worked example + /// to the engine's pinned digest. + #[test] + fn a_landscape_job_runs_in_the_page_to_the_pinned_bits() { + let e = QuantumEngine::new(&[4u8; 32], 512).unwrap(); + let job: Value = serde_json::from_str(&e.landscape("run", &json!({ "program": "vars 2\noppose 0 1\nsolve\n", "seed": "3" }).to_string())).unwrap(); + assert_eq!(job["result"]["truth"]["lowest"], -1.0, "{job}"); + assert_eq!(job["result"]["member"]["id"], "cpu:reference"); + let v: Value = serde_json::from_str(&engine_verify(&job.to_string())).unwrap(); + assert_eq!(v["valid"], true, "{v}"); + let bad: Value = serde_json::from_str(&e.landscape("run", r#"{"program": "vars 3\noppos 0 1\nsolve\n"}"#)).unwrap(); + assert_eq!(bad["line"], 2, "{bad}"); + assert_eq!(wai::quantum_landscape::job::corpus_digest(3), LANDSCAPE_CORPUS_3); + } + /// A pulse program's schedule draws in the page, its run is a job of /// the method pulse whose bits are the engine's, and the run checks. #[test] @@ -1391,6 +1437,15 @@ mod wasm_tests { assert_eq!(v["candidates"][0]["qasm"], "qubit[1] q;\nh q[0];\n", "{v}"); } + /// Every worked example of an energy-landscape job, to the same bits in wasm32 as natively. + #[wasm_bindgen_test] + fn landscape_jobs_are_the_native_bits_in_wasm() { + assert_eq!(wai::quantum_landscape::job::corpus_digest(3), super::tests::LANDSCAPE_CORPUS_3); + let e = QuantumEngine::new(&[4u8; 32], 512).unwrap(); + let a: Value = serde_json::from_str(&e.landscape_answer("run", &json!({ "program": "vars 2\noppose 0 1\nsolve\n" }).to_string())).unwrap(); + assert_eq!(a["joules"]["kind"], "priced", "{a}"); + } + /// The engine's pinned gradient, to the same bytes in wasm32, and a /// program built and checked. #[wasm_bindgen_test]