QuantumEngine.pulse gives the page the schedule and check calls, sealed with the page's key; a pulse run is the page's run with method pulse. A native test and a wasm32 test run the same pulse job to one pinned digest of its final density matrix, the engine's own; wai-web-wasm.yml's pins move from 26 to 27 (native) and from 18 to 19 (wasm32). wai-quantum-pulse.yml runs the pulse reader's, the simulator's and the pulse jobs' 26 tests by name in a release build on Linux, builds each feature set that holds them without warnings, runs the pinned digests in debug and release builds on arm64 Linux and on macOS, and runs the 26 tests under wasm32-wasip2 by wasmtime. Co-Authored-By: Claude Opus 5.5 <[email protected]>
201 lines
10 KiB
YAML
201 lines
10 KiB
YAML
name: wai-quantum pulse and dynamics
|
|
|
|
# Pulse programs (wai/wai-quantum): OpenPulse read into a schedule on frames
|
|
# (qasm::pulse), the pulse simulator that integrates it under a Lindblad
|
|
# master equation (quantum_pulse), and pulse runs as jobs, their schedules
|
|
# and their checks (engine::pulse).
|
|
#
|
|
# The simulator is held to closed forms within the error it states (a Rabi
|
|
# turn, a quarter turn of phase, a detuned drive, T1 decay, T2 dephasing),
|
|
# and its result is the same bits on every platform: a test pins the final
|
|
# density matrix's digest, and this workflow runs it on x86_64 Linux, arm64
|
|
# Linux, arm64 macOS and wasm32-wasip2 (wasmtime). The browser build runs a
|
|
# pulse job to the same pinned digest (wai-web-wasm.yml, wai-web's wasm32
|
|
# tests).
|
|
|
|
concurrency:
|
|
group: ${{ github.workflow }}-${{ github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
paths:
|
|
- 'wai/wai-quantum/**'
|
|
- 'rust-toolchain.toml'
|
|
- '.github/workflows/wai-quantum-pulse.yml'
|
|
pull_request:
|
|
paths:
|
|
- 'wai/wai-quantum/**'
|
|
- 'rust-toolchain.toml'
|
|
- '.github/workflows/wai-quantum-pulse.yml'
|
|
|
|
jobs:
|
|
relevant:
|
|
name: does this change touch it
|
|
runs-on: linux-arm64
|
|
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-pulse.yml \
|
|
"${{ github.event.pull_request.base.sha }}" \
|
|
"${{ github.event.pull_request.head.sha }}")
|
|
echo "this change touches the pulse layer: $r"
|
|
echo "run=$r" >> "$GITHUB_OUTPUT"
|
|
|
|
native:
|
|
name: reader, simulator and pulse jobs · 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.98.0
|
|
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.98.0
|
|
rustup toolchain install 1.98.0 --profile minimal
|
|
rustup default 1.98.0
|
|
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 pulse reader, the simulator and pulse jobs
|
|
shell: bash
|
|
working-directory: wai/wai-quantum
|
|
run: |
|
|
set -euo pipefail
|
|
cargo test --locked --release --features full --lib -- qasm::pulse:: quantum_pulse:: engine::pulse:: 2>&1 | tee "$RUNNER_TEMP/pulse.log"
|
|
grep -qE '^test result: ok\. 26 passed; 0 failed' "$RUNNER_TEMP/pulse.log" || { echo "::error::the pulse tests did not all pass (26 expected)"; exit 1; }
|
|
for t in qasm::pulse::tests::a_program_reads_to_its_schedule_on_frames \
|
|
qasm::pulse::tests::barriers_align_frames_and_a_gate_starts_its_frames_together \
|
|
qasm::pulse::tests::phase_and_frequency_follow_the_frame_and_the_carrier_runs_on \
|
|
qasm::pulse::tests::each_template_samples_as_its_formula_says \
|
|
qasm::pulse::tests::a_parametric_calibration_binds_its_angle_and_a_fixed_one_wins \
|
|
qasm::pulse::tests::durations_in_nanoseconds_must_be_whole_samples \
|
|
qasm::pulse::tests::ports_map_by_name_and_by_the_options \
|
|
qasm::pulse::tests::what_the_pulse_reader_does_not_take_is_refused_with_its_code_and_place \
|
|
qasm::pulse::tests::the_schedule_writes_as_json_with_every_instruction \
|
|
quantum_pulse::tests::a_resonant_drive_turns_the_qubit_as_rabi_says \
|
|
quantum_pulse::tests::a_quarter_turn_of_phase_turns_the_axis_to_minus_y \
|
|
quantum_pulse::tests::a_detuned_drive_turns_less_and_faster \
|
|
quantum_pulse::tests::relaxation_and_dephasing_decay_as_t1_and_t2_say \
|
|
quantum_pulse::tests::the_same_schedule_gives_the_same_bits \
|
|
quantum_pulse::tests::simultaneous_captures_give_the_joint_reading \
|
|
quantum_pulse::tests::three_levels_show_leakage_and_keep_the_trace \
|
|
quantum_pulse::tests::the_bound_is_stated_and_a_step_past_it_is_refused \
|
|
quantum_pulse::tests::what_the_model_does_not_hold_is_refused \
|
|
quantum_pulse::tests::a_detuned_play_split_in_two_is_the_same_play \
|
|
engine::pulse::tests::a_pulse_job_runs_end_to_end_and_its_answer_follows_its_schemas \
|
|
engine::pulse::tests::a_target_gives_the_qubits_their_frequency_and_times_and_its_hash_is_bound \
|
|
engine::pulse::tests::what_a_pulse_job_cannot_be_is_refused_with_its_code \
|
|
engine::pulse::tests::a_pulse_job_is_checked_by_running_it_again \
|
|
engine::pulse::tests::a_schedule_is_drawn_with_its_digests \
|
|
engine::pulse::tests::the_request_schema_holds_the_options_the_engine_reads \
|
|
engine::pulse::tests::a_pulse_job_is_the_same_bits_on_every_platform; do
|
|
grep -q "^test $t \.\.\. ok$" "$RUNNER_TEMP/pulse.log" || { echo "::error::$t did not run and pass"; exit 1; }
|
|
done
|
|
# 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 pulse layer 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_pulse --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: the pinned
|
|
# digests of the simulator's and a pulse job's final density matrix.
|
|
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.98.0
|
|
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.98.0
|
|
rustup toolchain install 1.98.0 --profile minimal
|
|
rustup default 1.98.0
|
|
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
|
|
- name: The pinned digests, 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_engine,quantum_receipt --lib -- \
|
|
quantum_pulse::tests::the_same_schedule_gives_the_same_bits \
|
|
engine::pulse::tests::a_pulse_job_is_the_same_bits_on_every_platform 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; }
|
|
done
|
|
|
|
wasip2:
|
|
name: reader, simulator and pulse jobs 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.98.0 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.98.0
|
|
rustup toolchain install 1.98.0 --profile minimal
|
|
rustup default 1.98.0
|
|
rustup target add --toolchain 1.98.0 wasm32-wasip2
|
|
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
|
|
# The release binary, pinned by version and SHA-256, rather than a build from source.
|
|
- name: Install wasmtime 49.0.2
|
|
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=49.0.2
|
|
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 "a4d6e9e3a5a60f527cf7793d674c48930c80c2e8977995b8a275cad3254b9322 $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 pulse tests under wasmtime, to the pinned bits
|
|
shell: bash
|
|
working-directory: wai/wai-quantum
|
|
env:
|
|
CARGO_TARGET_WASM32_WASIP2_RUNNER: wasmtime
|
|
run: |
|
|
set -euo pipefail
|
|
cargo test --locked --target wasm32-wasip2 --features full --lib -- qasm::pulse:: quantum_pulse:: engine::pulse:: 2>&1 | tee "$RUNNER_TEMP/wasip2.log"
|
|
grep -qE '^test result: ok\. 26 passed; 0 failed' "$RUNNER_TEMP/wasip2.log" || { echo "::error::the pulse tests did not all pass under wasip2 (26 expected)"; exit 1; }
|