Some checks failed
Build wheels / build (ubuntu-latest, 3.11) (push) Has been cancelled
Build wheels / build (ubuntu-latest, 3.12) (push) Has been cancelled
Build wheels / build (ubuntu-latest, 3.13) (push) Has been cancelled
Tests / check (push) Has been cancelled
Tests / build (ubuntu-latest, 3.11) (push) Has been cancelled
Tests / build (ubuntu-latest, 3.12) (push) Has been cancelled
Tests / build (ubuntu-latest, 3.13) (push) Has been cancelled
341 lines
10 KiB
Bash
Executable File
341 lines
10 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
# Contest-style Vidal/MPI MPS cases.
|
|
#
|
|
# Usage:
|
|
# tools/run_vidal_mpi_contest_cases.sh main1
|
|
# tools/run_vidal_mpi_contest_cases.sh main2
|
|
# tools/run_vidal_mpi_contest_cases.sh strong
|
|
# tools/run_vidal_mpi_contest_cases.sh all
|
|
#
|
|
# Common overrides:
|
|
# PYTHON_BIN=.venv/bin/python
|
|
# MPIEXEC=mpiexec
|
|
# MPIEXEC_FULL="mpirun -np 4 -hostfile /home/yx/qibotn/hostfile -perhost 2"
|
|
# HOSTFILE=hostfile # optional; used only if the file exists
|
|
# RANKS=8
|
|
# TORCH_THREADS=8
|
|
# CUT_RATIO=1e-12
|
|
# OBS_FILTER="boundary_ZZ_q2 ring_xz dense3_spread complex_iZ0"
|
|
#
|
|
# Per-case overrides:
|
|
# MAIN1_NQ=128 MAIN1_LAYERS=50 MAIN1_BOND=1024 MAIN1_SEED=31001
|
|
# MAIN2_NQ=128 MAIN2_LAYERS=64 MAIN2_BOND=2048 MAIN2_SEED=31002
|
|
# STRONG_NQ=256 STRONG_LAYERS=64 STRONG_BOND=2048 STRONG_SEED=41001
|
|
|
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
cd "$ROOT_DIR"
|
|
|
|
PYTHON_BIN="${PYTHON_BIN:-.venv/bin/python}"
|
|
MPIEXEC="${MPIEXEC:-mpiexec}"
|
|
HOSTFILE="${HOSTFILE:-}"
|
|
RANKS="${RANKS:-4}"
|
|
TORCH_THREADS="${TORCH_THREADS:-1}"
|
|
CUT_RATIO="${CUT_RATIO:-1e-12}"
|
|
OBS_FILTER="${OBS_FILTER:-}"
|
|
|
|
RUNNER_DIR="$ROOT_DIR/.tmp"
|
|
mkdir -p "$RUNNER_DIR"
|
|
RUNNER="$(mktemp "$RUNNER_DIR/qibotn_vidal_contest.XXXXXX.py")"
|
|
cleanup() {
|
|
rm -f "$RUNNER"
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
cat > "$RUNNER" <<'PY'
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import math
|
|
import time
|
|
|
|
import numpy as np
|
|
from mpi4py import MPI
|
|
from qibo import Circuit, gates, hamiltonians
|
|
from qibo.symbols import X, Y, Z
|
|
|
|
from qibotn.backends.vidal import VidalBackend
|
|
|
|
|
|
def set_torch_threads(nthreads):
|
|
try:
|
|
import torch
|
|
|
|
torch.set_num_threads(nthreads)
|
|
except Exception:
|
|
pass
|
|
|
|
|
|
def build_circuit(kind, nqubits, nlayers, seed):
|
|
rng = np.random.default_rng(seed)
|
|
circuit = Circuit(nqubits)
|
|
|
|
for layer in range(nlayers):
|
|
for q in range(nqubits):
|
|
circuit.add(gates.RY(q, theta=rng.uniform(-math.pi, math.pi)))
|
|
circuit.add(gates.RZ(q, theta=rng.uniform(-math.pi, math.pi)))
|
|
if kind in ("rxx_rzz", "scramble"):
|
|
circuit.add(gates.RX(q, theta=rng.uniform(-math.pi, math.pi)))
|
|
|
|
if kind == "reversed_cnot":
|
|
for q in range(0, nqubits - 1, 2):
|
|
circuit.add(gates.CNOT(q + 1, q) if layer % 2 else gates.CNOT(q, q + 1))
|
|
for q in range(1, nqubits - 1, 2):
|
|
circuit.add(gates.CNOT(q + 1, q) if layer % 2 == 0 else gates.CNOT(q, q + 1))
|
|
elif kind == "rxx_rzz":
|
|
for q in range(layer % 2, nqubits - 1, 2):
|
|
circuit.add(gates.RXX(q, q + 1, theta=rng.uniform(-0.9, 0.9)))
|
|
circuit.add(gates.RZZ(q, q + 1, theta=rng.uniform(-0.9, 0.9)))
|
|
elif kind == "scramble":
|
|
for q in range(layer % 2, nqubits - 1, 2):
|
|
circuit.add(gates.RXX(q, q + 1, theta=rng.uniform(-0.8, 0.8)))
|
|
circuit.add(gates.RZZ(q, q + 1, theta=rng.uniform(-0.8, 0.8)))
|
|
if layer % 5 == 4:
|
|
circuit.add(gates.SWAP(q, q + 1))
|
|
else:
|
|
raise ValueError(f"Unknown circuit kind {kind!r}.")
|
|
|
|
return circuit
|
|
|
|
|
|
def ring_xz(nqubits):
|
|
form = 0
|
|
for q in range(nqubits):
|
|
form += 0.5 * X(q) * Z((q + 1) % nqubits)
|
|
return hamiltonians.SymbolicHamiltonian(form=form)
|
|
|
|
|
|
def open_zz(nqubits):
|
|
form = 0
|
|
for q in range(nqubits - 1):
|
|
form += (1.0 / (nqubits - 1)) * Z(q) * Z(q + 1)
|
|
return hamiltonians.SymbolicHamiltonian(form=form)
|
|
|
|
|
|
def range2_xx(nqubits):
|
|
form = 0
|
|
for q in range(nqubits - 2):
|
|
form += (1.0 / (nqubits - 2)) * X(q) * X(q + 2)
|
|
return hamiltonians.SymbolicHamiltonian(form=form)
|
|
|
|
|
|
def dense_observable(nqubits, qubits, seed, dim):
|
|
rng = np.random.default_rng(seed)
|
|
raw = rng.normal(size=(dim, dim)) + 1j * rng.normal(size=(dim, dim))
|
|
matrix = (raw + raw.conj().T) / 2.0
|
|
matrix = matrix / np.linalg.norm(matrix)
|
|
return {"matrix": matrix, "qubits": list(qubits)}
|
|
|
|
|
|
def observables_for_case(nqubits, seed):
|
|
q1 = nqubits // 4
|
|
q2 = nqubits // 2
|
|
q3 = (3 * nqubits) // 4
|
|
last = nqubits - 1
|
|
|
|
return [
|
|
("boundary_ZZ_q1", hamiltonians.SymbolicHamiltonian(form=Z(q1 - 1) * Z(q1))),
|
|
("boundary_ZZ_q2", hamiltonians.SymbolicHamiltonian(form=Z(q2 - 1) * Z(q2))),
|
|
("boundary_ZZ_q3", hamiltonians.SymbolicHamiltonian(form=Z(q3 - 1) * Z(q3))),
|
|
(
|
|
"long_Z_5_sites",
|
|
hamiltonians.SymbolicHamiltonian(form=Z(0) * Z(q1) * Z(q2) * Z(q3) * Z(last)),
|
|
),
|
|
(
|
|
"mixed_XZYZX",
|
|
hamiltonians.SymbolicHamiltonian(form=X(0) * Z(q1) * Y(q2) * Z(q3) * X(last)),
|
|
),
|
|
("ring_xz", ring_xz(nqubits)),
|
|
("open_zz", open_zz(nqubits)),
|
|
("range2_xx", range2_xx(nqubits)),
|
|
("complex_iZ0", hamiltonians.SymbolicHamiltonian(form=1.0j * Z(0))),
|
|
("dense2_mid", dense_observable(nqubits, (q2 - 1, q2), seed + 101, 4)),
|
|
("dense3_spread", dense_observable(nqubits, (q1, q2, q3), seed + 202, 8)),
|
|
]
|
|
|
|
|
|
def run_case(args):
|
|
set_torch_threads(args.torch_threads)
|
|
comm = MPI.COMM_WORLD
|
|
rank = comm.Get_rank()
|
|
size = comm.Get_size()
|
|
|
|
circuit = build_circuit(args.kind, args.nqubits, args.nlayers, args.seed)
|
|
observables = observables_for_case(args.nqubits, args.seed)
|
|
if args.obs_filter:
|
|
wanted = set(args.obs_filter.split(","))
|
|
observables = [(name, obs) for name, obs in observables if name in wanted]
|
|
if not observables:
|
|
raise ValueError(f"OBS_FILTER matched no observables: {args.obs_filter!r}")
|
|
|
|
if rank == 0:
|
|
print("=" * 88, flush=True)
|
|
print(
|
|
"case "
|
|
f"label={args.label} kind={args.kind} ranks={size} "
|
|
f"nqubits={args.nqubits} nlayers={args.nlayers} gates={len(circuit.queue)} "
|
|
f"bond={args.bond} cut_ratio={args.cut_ratio:g} "
|
|
f"torch_threads={args.torch_threads} seed={args.seed} "
|
|
f"obs_filter={args.obs_filter or 'all'}",
|
|
flush=True,
|
|
)
|
|
print(
|
|
"observable value seconds trunc_sum trunc_max status",
|
|
flush=True,
|
|
)
|
|
|
|
for obs_name, observable in observables:
|
|
backend = VidalBackend()
|
|
backend.configure_tn_simulation(
|
|
max_bond_dimension=args.bond,
|
|
cut_ratio=args.cut_ratio,
|
|
tensor_module="torch",
|
|
mpi_approach="CT",
|
|
mpi_num_procs=size,
|
|
fallback=False,
|
|
)
|
|
|
|
comm.Barrier()
|
|
start = time.perf_counter()
|
|
try:
|
|
value = backend.expectation(
|
|
circuit,
|
|
observable,
|
|
preprocess=True,
|
|
compile_circuit=False,
|
|
)
|
|
status = "ok"
|
|
except Exception as exc: # pragma: no cover - printed for manual runs
|
|
value = np.nan
|
|
status = type(exc).__name__ + ":" + str(exc).split("\n", 1)[0]
|
|
seconds = time.perf_counter() - start
|
|
|
|
if rank == 0:
|
|
print(
|
|
f"{obs_name} {value!r} {seconds:.3f} "
|
|
f"{backend.last_truncation_error:.6e} "
|
|
f"{backend.last_max_truncation_error:.6e} {status}",
|
|
flush=True,
|
|
)
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--label", required=True)
|
|
parser.add_argument("--kind", choices=("reversed_cnot", "rxx_rzz", "scramble"), required=True)
|
|
parser.add_argument("--nqubits", type=int, required=True)
|
|
parser.add_argument("--nlayers", type=int, required=True)
|
|
parser.add_argument("--bond", type=int, required=True)
|
|
parser.add_argument("--cut-ratio", type=float, required=True)
|
|
parser.add_argument("--seed", type=int, required=True)
|
|
parser.add_argument("--torch-threads", type=int, required=True)
|
|
parser.add_argument("--obs-filter", default="")
|
|
run_case(parser.parse_args())
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|
|
PY
|
|
|
|
if [[ -n "${MPIEXEC_FULL:-}" ]]; then
|
|
read -r -a mpi_prefix <<< "$MPIEXEC_FULL"
|
|
else
|
|
mpi_prefix=("$MPIEXEC")
|
|
if [[ -n "$HOSTFILE" && -f "$HOSTFILE" ]]; then
|
|
mpi_prefix+=("-hostfile" "$HOSTFILE")
|
|
fi
|
|
mpi_prefix+=("-n" "$RANKS")
|
|
fi
|
|
|
|
run_case() {
|
|
local label="$1"
|
|
local kind="$2"
|
|
local nq="$3"
|
|
local layers="$4"
|
|
local bond="$5"
|
|
local seed="$6"
|
|
|
|
echo
|
|
echo "Running $label: kind=$kind nqubits=$nq layers=$layers bond=$bond seed=$seed"
|
|
echo "MPI: ${mpi_prefix[*]}"
|
|
"${mpi_prefix[@]}" "$PYTHON_BIN" -u "$ROOT_DIR/tools/vidal_mpi_contest_runner.py" \
|
|
--label "$label" \
|
|
--kind "$kind" \
|
|
--nqubits "$nq" \
|
|
--nlayers "$layers" \
|
|
--bond "$bond" \
|
|
--cut-ratio "$CUT_RATIO" \
|
|
--seed "$seed" \
|
|
--torch-threads "$TORCH_THREADS" \
|
|
--obs-filter "$(tr ' ' ',' <<< "$OBS_FILTER")"
|
|
}
|
|
|
|
case "${1:-help}" in
|
|
main1)
|
|
run_case \
|
|
"main1-reversed-cnot" \
|
|
"reversed_cnot" \
|
|
"${MAIN1_NQ:-128}" \
|
|
"${MAIN1_LAYERS:-50}" \
|
|
"${MAIN1_BOND:-1024}" \
|
|
"${MAIN1_SEED:-31001}"
|
|
;;
|
|
main2)
|
|
run_case \
|
|
"main2-rxx-rzz" \
|
|
"rxx_rzz" \
|
|
"${MAIN2_NQ:-128}" \
|
|
"${MAIN2_LAYERS:-64}" \
|
|
"${MAIN2_BOND:-2048}" \
|
|
"${MAIN2_SEED:-31002}"
|
|
;;
|
|
strong)
|
|
run_case \
|
|
"strong-scramble" \
|
|
"scramble" \
|
|
"${STRONG_NQ:-256}" \
|
|
"${STRONG_LAYERS:-64}" \
|
|
"${STRONG_BOND:-2048}" \
|
|
"${STRONG_SEED:-41001}"
|
|
;;
|
|
all)
|
|
"$0" main1
|
|
"$0" main2
|
|
"$0" strong
|
|
;;
|
|
smoke)
|
|
MAIN1_NQ="${MAIN1_NQ:-32}" \
|
|
MAIN1_LAYERS="${MAIN1_LAYERS:-6}" \
|
|
MAIN1_BOND="${MAIN1_BOND:-128}" \
|
|
"$0" main1
|
|
;;
|
|
help|*)
|
|
cat >&2 <<'EOF'
|
|
Usage: tools/run_vidal_mpi_contest_cases.sh [main1|main2|strong|all|smoke]
|
|
|
|
Cases:
|
|
main1 128 qubits, 50 layers, reversed-CNOT brickwall, chi=1024
|
|
main2 128 qubits, 64 layers, RXX/RZZ brickwall, chi=2048
|
|
strong 256 qubits, 64 layers, RXX/RZZ + periodic SWAP scramble, chi=2048
|
|
smoke Small syntax/runtime check of main1
|
|
|
|
Common overrides:
|
|
PYTHON_BIN=.venv/bin/python
|
|
MPIEXEC=mpiexec
|
|
MPIEXEC_FULL="mpirun -np 4 -hostfile /home/yx/qibotn/hostfile -perhost 2"
|
|
HOSTFILE=hostfile
|
|
RANKS=8
|
|
TORCH_THREADS=8
|
|
CUT_RATIO=1e-12
|
|
OBS_FILTER="boundary_ZZ_q2 ring_xz dense3_spread complex_iZ0"
|
|
|
|
Per-case overrides:
|
|
MAIN1_NQ=128 MAIN1_LAYERS=50 MAIN1_BOND=1024 MAIN1_SEED=31001
|
|
MAIN2_NQ=128 MAIN2_LAYERS=64 MAIN2_BOND=2048 MAIN2_SEED=31002
|
|
STRONG_NQ=256 STRONG_LAYERS=64 STRONG_BOND=2048 STRONG_SEED=41001
|
|
EOF
|
|
exit 2
|
|
;;
|
|
esac
|