diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/.gitignore b/tracks/qcs/solutions/frustration-free/challenge-113/.gitignore new file mode 100644 index 000000000..4d7659529 --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/.gitignore @@ -0,0 +1,6 @@ +results/ +.pytest_cache/ +.venv/ +.runtime/ +__pycache__/ +*.py[cod] diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/.superpowers/sdd/task-10a-report.md b/tracks/qcs/solutions/frustration-free/challenge-113/.superpowers/sdd/task-10a-report.md new file mode 100644 index 000000000..47ccb0454 --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/.superpowers/sdd/task-10a-report.md @@ -0,0 +1,197 @@ +# Task 10A controller report: current gate + +## Status + +The pre-Task10C scientific and reproducibility blockers are closed locally. +Production was not submitted. The only execution blocker is frozen-runtime +compatibility on both authorized clusters: glibc 2.17 cannot load the locked +`jaxlib==0.11.0` manylinux 2.27 wheel. + +## RED/GREEN record + +- RED: effective Hessian rank was also used as the available model-Hessian + search basis, so approved high-k trials could not construct. +- GREEN: dense `p<=80` landscapes retain all `p` exact-Hessian eigenvectors in + descending absolute-curvature order. Effective ranks remain 3 (`d=2`) and 15 + (`d=4`) at `1e-8`. Matrix-free paths report their actual available column + count and never synthesize missing vectors. +- RED: public artifacts hashed only the selected basis slice. +- GREEN: model-Hessian artifacts additionally hash the complete source basis. + Real `d=2,k=24` and `d=4,k=20,30,80` fixtures prove orthonormality, nested + subspaces, unchanged ranks, and full-span `k=p` equivalence. Every canonical + production search-space configuration constructs without spending budgets. +- RED: evidence accepted coercible JSON values, stale cross-document inputs, + an environment record with x64 disabled, and deployment metadata that did + not hash an actual archive file. +- GREEN: evidence requires exact JSON types, finite ranges, x64, provisional + projection status, persisted-input arithmetic, config binding, + cross-document hashes, and pilot/timing/validation consistency. Deployment + verification hashes the supplied archive bytes, enforces revision naming, + validates the complete evidence directory, and binds report/evidence/archive + hashes. + +## Current local rerun + +Measured source revision: `dd16192953c130d738716238525760de73343e09`. + +- Calibration: first query 0.217 s; 19 warm queries 0.0338 s (562/s); + open-loop 7.75 s; dense landscape 5.81 s; exact trajectory 0.0340 s; + geometry 1.67 s; restricted optimization 0.430 s/8 evaluations. +- Environment: JAX CPU, x64 enabled, 32 logical CPUs. +- Full pilot: 881 exact queries, 21.91 s wall, 864,260 KiB peak RSS, strict + validation `valid=true`. +- Projection: provisional only; computed directly from persisted pilot wall + time, artifact bytes, 9,500 trials, and eight cores as an arithmetic scenario. + No resource class or concurrency is selected before Task 10C pilots. + +The earlier calibration, seed-risk, and concurrency statements are superseded +and remain available only in Git history. + +## Deployment state + +The reviewed candidate is archived and validated locally after the final +evidence/documentation commit. Deployment metadata is generated beside the +extracted runtime rather than tracked in the source tree. No remote deployment +or Slurm submission was made. + +## Remaining Task 10C decisions + +1. Approve an exact cluster runtime solution for the glibc 2.17 / manylinux + 2.27 incompatibility. +2. Run representative CPU-count, memory, and concurrency pilots in that exact + runtime. +3. Recompute the measured allocation gate before submitting the 9,500-trial + array. + +## Final review correction + +- RED: a full-column orthonormal Hessian basis spanned the full pulse space but + did not equal the bounded full baseline because rotating a coordinate box + changes its feasible pulse set. +- GREEN: model-Hessian `k=p` now returns the exact identity-basis full search + space. Boundary, alternating-corner, and axis-corner samples at `p=24` and + `p=80` have exactly equal origins, bases, coordinate bounds, and pulse + mappings. The method label and complete model-source-basis hash remain in + trial provenance; `k.tar.gz`, and + bind and validate it under that same basename. Traversal, option-like, + control-character, wrong-revision, and otherwise noncanonical basenames fail + before the first Apptainer call. +- Fake-container tests cover the exact realistic prepare/job argv and prove + malicious names enter neither preparation nor pilot runtime execution. + +## Frozen networked preparation correction + +- RED: network-none preparation could not fetch locked NumPy because LASG02 had + no bound complete uv cache or wheelhouse. +- GREEN: after all source/runtime hashes pass, preparation requires + `CHALLENGE113_ACK_NETWORKED_PREPARE=1` and runs exactly one network-enabled + command: `uv sync --frozen --group dev --project /workspace` under + `--no-home --cleanenv`. It runs no qcontrol, smoke, analysis, scheduler, or + physics entry point. +- The post-sync runtime gate immediately returns to + `--cleanenv --net --network none`. Its marker records the one-time frozen + networked mode, strict execution isolation, exact runtime versions, + lock/source/runtime hashes, metadata digest, and isolated objective/ + propagation smoke. Jobs reconstruct and compare that marker and never invoke + uv or a package manager. +- Tests prove missing/wrong acknowledgement reaches no container, the sole sync + command has no network-namespace flags, every later command has network-none, + and no repository script submits a scheduler job. + +## Slurm spool-path correction + +- RED: pilot 2817990 resolved its shared gate relative to `BASH_SOURCE[0]`; + Slurm had copied that launcher into its private spool, so the gate path did + not exist and execution stopped before the runtime gate. +- GREEN: pilot and array launchers now require an absolute, canonical + `CHALLENGE113_DEPLOYMENT`, reject a missing or symlinked gate, and source the + gate only from that deployment. The existing gate still verifies the source + revision, archive, evidence, runtime, and readiness marker before execution. +- Integration tests copy both launchers to an unrelated simulated Slurm spool. + Correct deployment succeeds; relative, missing, and symlinked gate paths fail + before any container command. No job was submitted. + +## Compute-gate correction + +- RED: traced pilot 2818000 rejected `.venv/bin/python` on the host because uv + created an absolute, container-owned symlink whose target exists only inside + the SIF. A manually truncated expected digest also failed without identifying + the malformed variable. +- GREEN: the host now checks only that the Python path entry exists beneath the + canonical deployment; executable resolution occurs first inside the same + network-none Apptainer environment used by the runtime gate and physics. + Every required SHA input must be exactly 64 lowercase hex, and path/format/ + content failures name the variable plus expected and actual values where + applicable. +- Compute-like tests use an absolute container-only Python symlink, exercise + every truncated SHA variable, and require actionable gate/path diagnostics + before any container call. No job was submitted. + +## One-dimensional optimizer correction + +- Production was stopped after 98 old-revision trials. Array element + `2818032_2` preserved `not yet initialized (dimension needed)` while executing + canonical trial `trial-20e15a66415a0832b8c82051` (one-qubit, random `k=1`, + gap 0.2, 1,000 shots, seed 8). +- The full local traceback locates the exception in pycma 4.4.4 + `DiagonalDecoding.set_i`, called by `_stds_into_limits`. pycma supports + `N=1`, but its sentinel check cannot distinguish an initialized length-one + scaling vector when the default bound-range standard-deviation limiter + activates. +- The minimal correction keeps CMA-ES for every method and disables only that + defective limiter for `k=1`. Bounds, clipping, population size, + ask/evaluate/tell generations, deterministic seed offset, query/shot + accounting, and validation remain unchanged; `k>=2` options are byte-for-byte + unchanged at the qcontrol boundary. +- RED reproduced the exact traceback and missing option. GREEN covers the exact + canonical trial, exact and finite-shot devices, certification, optimizer + stop, budget exhaustion, all four search-space factories, reproducibility, + bounds, ledger reconciliation, JSON roundtrip, publication/resume, all 9,500 + constructions, and bounded execution of all 600 canonical `k=1` configs. +- The old `3862d4f` production root is revision-incompatible and must remain + quarantined. Any resumed production must start under a fresh revision/run-ID. + No cluster job was submitted. diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/CHALLENGE.md b/tracks/qcs/solutions/frustration-free/challenge-113/CHALLENGE.md new file mode 100644 index 000000000..a449423df --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/CHALLENGE.md @@ -0,0 +1,164 @@ +### Released by + +[Lei Wang (王磊)](https://wangleiphy.github.io), Institute of Physics, Chinese Academy of Sciences + +### Contact email + +wangleiphy@gmail.com + +### Method + +Differentiable programming / quantum optimal control + +### Challenge issue + +> Track: quantum control · differentiable programming +> Difficulty: advanced (comfortable with autodiff, quantum dynamics, and reading ML/physics papers) +> Compute: one GPU per team is enough for the week +> Starting notebook: a differentiable quantum-control notebook (link below) — fork it and build outward + +Calibrating a quantum gate on real hardware is slow and expensive: every trial is a physical experiment, and there are far more pulse parameters to tune than you can afford to explore blindly. But here is the surprise — most of those parameters do not matter. Hidden in the geometry of the control problem is a small set of directions, exactly $d^2 - 1$ of them, that carry everything; all the rest are free. Your job this week is to find those directions in a cheap, differentiable simulator and use them to calibrate a device you can only poke at, reaching target fidelity in a fraction of the experiments. That device can be a black box you build in software — or, for the bold, a real superconducting quantum computer on a Hefei cloud. It is a clean piece of sim-to-real physics, it starts from a single notebook you can fork today, and done well it is a publishable result. + +## The problem + +There are two ways to find a control pulse for a quantum gate, and they have opposite strengths. + +The **open-loop** way uses a *model* of the device. You write down the drift Hamiltonian and the control Hamiltonians, simulate the Schrödinger equation, differentiate through the simulation, and optimize the pulse in software. This is the classic gradient-based optimal-control idea (GRAPE, Khaneja et al. 2005), made especially convenient by modern automatic differentiation: exact gradients, fast and precise. Its weakness is that the answer is only as good as the model, and no model matches the real chip — there are always miscalibrated couplings, stray terms, and drift that you did not put in. + +The **closed-loop** way puts the real device in the loop. You apply a pulse, *measure* the fidelity on hardware, feed that number back to an optimizer, and repeat. This is adaptive feedback control, an old and powerful idea (Judson & Rabitz 1992) that is now routine for calibrating real qubits (Kelly et al. 2014). It corrects for everything the model got wrong. Its weakness is cost: every evaluation is a real experiment costing many measurement shots, and because you cannot differentiate through hardware, you are stuck with *derivative-free* optimization, whose number of experiments grows quickly with the number of pulse parameters. + +So one loop is cheap but wrong, and the other is right but expensive. The best real-world methods combine them — optimize open-loop on the model, then refine closed-loop on the device (Egger & Wilhelm 2014). This challenge is about making that combination *efficient*, using a structural fact about control landscapes that tells you exactly where the expensive closed-loop effort should go. + +## The observation you will exploit + +Optimize the model to a target gate and look at the Hessian of the infidelity at the optimum. Writing the final propagator as $U(T) = U_{\text{target}} e^{iA}$ for a small Hermitian generator $A$ (a $d\times d$ Hermitian matrix, so $d^2$ real directions), the infidelity expands to second order as + +```math +\mathcal{L} \;\approx\; \frac{1}{2d}\,\mathrm{Tr}\!\left[\Big(A - \tfrac{\mathrm{Tr}\,A}{d}\,I\Big)^{\!2}\right]. +``` + +This only sees the traceless part of $A$. The identity direction, $A \propto I$, is a global phase, and the fidelity is phase-blind because of the absolute value in $\tfrac{1}{d}\lvert\mathrm{Tr}(U^\dagger U_{\text{target}})\rvert$; so that one direction is flat. The remaining traceless Hermitian directions form $\mathfrak{su}(d)$ and carry all the curvature — exactly $d^2-1$ of them (15 for a two-qubit gate), no matter how many Fourier or piecewise-constant parameters your pulse has. The extra parameters do not add curvature; they enlarge a flat *solution manifold* of pulses that all realize the gate equally well. + +That the effective dimension of a control landscape is set by the size of the state space rather than by the number of control knobs is a known feature (Shen, Hsieh & Rabitz 2006; Roslund & Rabitz 2014), and it has a close cousin in machine learning, where the Hessian of an over-parametrized network has a few large eigenvalues and a bulk near zero (Sagun et al. 2017). It also rests on controllability: for a controllable system with enough resources the landscape is trap-free (Rabitz, Hsieh & Rosenthal 2004), which is why the model optimizer converges so cleanly in the first place. + +Two consequences drive the whole challenge: + +- The number of directions worth optimizing is an invariant of the *target*, not of your ansatz. The closed-loop search therefore has an intrinsic dimension of only $d^2-1$, even if the pulse has hundreds of parameters. +- That invariant holds only while the system stays controllable and over-resourced. Starve it — too little time, too little bandwidth — and the curved rank drops below $d^2-1$; the subspace the model hands you is then no longer the right one. + +## The idea: a cheap simulator guiding an expensive reality + +This is the *sim-to-real* pattern from robotics, applied to quantum control: use a cheap, differentiable simulator to do most of the thinking, then spend a small amount of expensive real-world effort where it actually matters. + +| | Sim-to-real transfer | This challenge | +|---|---|---| +| Cheap, differentiable model | physics simulator | the Schrödinger model $(H_0, h_i)$, autodiff through the ODE solver | +| Expensive black box | the real robot | the true device: query-only, noisy fidelity from finite shots | +| What the model buys you | a policy to fine-tune | a warm-start pulse and the $d^2-1$ directions that matter | +| Real-world optimization | fine-tune on hardware | derivative-free search inside the $d^2-1$-dim subspace | +| Failure mode | the sim-to-real gap | the model–truth gap rotates the true subspace away from the model's | + +The pipeline has three stages, and the first two are essentially the starting notebook. + +1. **Open-loop, model-based.** Differentiate through the model and optimize the pulse to a model-optimal $u^\star$. +2. **Landscape extraction.** Compute the model Hessian at $u^\star$ and take its top $k$ eigenvectors $\lbrace v_1,\dots,v_k \rbrace$ with $k \approx d^2-1$. Use Hessian–vector products with a Krylov eigensolver so this scales without ever forming the full Hessian. These are your reduced coordinates. +3. **Closed-loop, model-free.** Parametrize the pulse as $u = u^\star + \sum_{j=1}^k c_j v_j$ and optimize the coefficients $c_j$ against the noisy black-box device with a derivative-free optimizer. Compare the cost of this against searching all raw parameters directly. + +## What you will build + +- **A model** you can differentiate: the notebook's simulator, with a drift $H_0$, control operators $h_i$, and a pulse ansatz (the notebook uses a truncated Fourier basis, in the spirit of CRAB, Caneva, Calarco & Montangero 2011). +- **A true device** you can only query: take the model and perturb it — a shifted drift $H_0^{\text{true}} = H_0 + \varepsilon V$, mis-scaled control couplings, a small unmodeled term — then return a fidelity *estimated from a finite number of measurement shots*, so the returned number is noisy. Your code may call this device and read its scalar output, but may not differentiate through it or inspect its internals. The gap size $\varepsilon$ and the shot budget are your experimental knobs. This is what turns a software exercise into a genuine sim-to-real problem. +- **The three-stage pipeline** above, with the closed-loop stage driven by a derivative-free optimizer (Nelder–Mead, CMA-ES, or Bayesian optimization). + +## Core task and questions + +The core deliverable is one plot: black-box queries to reach a target fidelity (say $1-F \le 10^{-3}$ on the true device) versus the dimension of the search space, showing that the model-informed subspace reaches the target with far fewer experiments than the full-parameter search. Build that, then push on the questions that make it research. + +1. **The saving.** How many device queries — and how many total measurement shots — does each method need to hit the target? Sweep the subspace dimension $k$: too small and the search plateaus below target because it cannot reach the true optimum; too large and it wastes queries. Is the sweet spot near $d^2-1$? +2. **The failure mode.** As the model–truth gap $\varepsilon$ grows, the true device's relevant subspace rotates away from the model's $d^2-1$ directions. When does the model subspace stop being good enough? Does carrying a few extra directions as a safety margin, or re-estimating the subspace from device data as you go, recover the advantage? +3. **The invariant.** Does the required subspace dimension track $d^2-1$ across systems — a single-qubit gate ($d=2$, three directions), a two-qubit gate ($d=4$, fifteen), a three-qubit gate ($d=8$, sixty-three)? Confirming this shows the method is not tuned to one case. +4. **Noise.** Finite shots make the black box noisy, and derivative-free search in high dimensions suffers most from noise. Quantify how the subspace reduction changes the query count as a function of shot budget. + +## Getting started + +- **Start from the notebook.** It already integrates the Schrödinger equation through a differentiable ODE solver, optimizes a pulse to a target gate, and computes the Hessian and its principal directions — so stages 1 and 2 are largely in place. The sandbox cell that perturbs the optimum along the top eigenvectors and watches the loss barely move is a direct preview of the reduced coordinates you will search in. +- **Scale stage 2 with Hessian–vector products.** For larger $d$, use `hvp` plus a Krylov eigensolver (`scipy.sparse.linalg.eigsh`) rather than forming the full Hessian; the notebook sets this up. The autodiff cookbook explains forward-over-reverse and HVPs. +- **Keep a strict model/device boundary.** The model is differentiable and free to call; the device is query-only, counted, and noisy. Count every device query and every shot — those counts, not wall-clock time, are the currency of closed-loop control. +- **Note on integration.** Treating the Schrödinger equation as a generic ODE can let the propagator drift from unitarity; a structure-preserving integrator avoids this, and exact gradients can also be obtained by the discrete-adjoint route (Petersson et al. 2020). This matters more as you scale up. + +## Optional: close the loop on a real quantum computer + +The simulated device is a faithful stand-in, but the whole point of the sim-to-real framing is that the same pipeline connects to real hardware — and in Hefei you have a pulse-level machine within reach. The superconducting cloud of the Chinese Academy of Sciences / USTC quantum platform (`quantumcomputer.ac.cn`), and its QuantumCTek (国盾量子) sibling, expose pulse-level control on a Zuchongzhi-class chip through the QCIS `PULSE` instruction: you specify a waveform with amplitude, sideband frequency, phase, and DRAG coefficient, and use the coupler and two-qubit instructions (`G`, `AACZ`) for entangling control. Programs are submitted from Python with the `pyezQ` SDK and return a measurement probability distribution over output states — exactly the noisy, model-free fidelity signal your closed loop consumes. + +Pointing the pipeline at this device makes the exercise a genuine sim-to-real problem: your differentiable simulator is the model, the chip is the truth, and the model–truth gap is no longer injected by hand but is whatever the real hardware does. A convincing result — the subspace reduction cutting the number of real-hardware experiments needed to calibrate a gate, measured head-to-head against a full-parameter search on the same chip — would be publishable on its own. + +Two practical points to plan for, not around: + +- The `PULSE` instruction is a beta (内测) feature that must be enabled for your account. Email the platform support early to request pulse-level access, confirm which machine exposes it, and learn how runs are metered. The permission grant, not the physics, is the schedule risk. +- Pulse programs go through raw QCIS text via `pyezQ`; the friendlier gate-level SDK will not emit them. Debug the entire loop against the simulator first and spend real-device budget only on the final comparison. + +Teams without access to the Chinese cloud can run the same capstone on Amazon Braket's pulse interface on a Rigetti superconducting processor, which offers arbitrary waveforms and a documented gate-calibration override flow. + +## Deliverables + +Scoped to a few focused days, with a tail that can become a paper. + +1. A reproducible pipeline: model optimizer, landscape extractor, simulated query-only device, and subspace closed-loop optimizer, with a clean interface between the differentiable model and the black box. +2. The headline result: queries-to-target versus search dimension, with error bars over several seeds and a few model–truth gaps, comparing full-parameter and subspace search. +3. The failure-mode study: advantage versus gap size $\varepsilon$, and whether a safety margin or subspace re-estimation extends the useful range. +4. The invariant check across at least two system sizes, testing the $d^2-1$ prediction. +5. A short report or notebook and a pull request, including one honest account of a case where the reduction failed and what you learned from it. + +## Judging + +- Correctness and reproducibility of the three-stage pipeline, with a clean model/black-box boundary. +- Rigor of the headline comparison: real baselines, honest query and shot counting, error bars. +- Depth on the failure mode — the model–truth gap is where the physics is. +- Insight connecting the empirical subspace dimension to the $d^2-1$ prediction. +- Clarity. + +Bonus: a data-driven rule for choosing the subspace dimension; a demonstration that iterative subspace re-estimation beats a fixed subspace when the model is poor; or a closed-loop calibration run on real superconducting hardware. + +## The part that makes it research + +The clean version — extract $d^2-1$ directions from the model, search only those on hardware — works when the model is good. The interesting question is what happens when it is not. A large model–truth gap rotates the true relevant subspace, so the model's directions no longer span where the real optimum lives, and the reduced search converges to the wrong pulse. Detecting that, and deciding when to widen the subspace, when to re-estimate it from device data, and when to fall back to a full search, is the real content. This is the quantum-control version of the sim-to-real gap, and it is exactly the regime the Ad-HOC method (Egger & Wilhelm 2014) was built for — you are making that idea sharper by asking not just *how* to refine on hardware, but in *which* few directions. + +## Research extensions + +- **Close the loop on the subspace itself:** use device feedback to update the reduced coordinates, not only the coefficients within them. +- **The other side of the invariant:** shorten the control time toward the quantum speed limit and watch the curved rank fall below $d^2-1$. There the landscape itself changes character and optimal control becomes genuinely hard — a glassy phase with exponentially many near-degenerate near-optima (Day et al. 2019). Map where your subspace method must give up because the geometry, not just the model, has changed. +- **Connect to variational quantum algorithms.** The same controllability and dimension-counting ideas explain when quantum models are trainable and when they hit barren plateaus (Larocca et al. 2022; Larocca et al. 2023). A clean bridge between your control-landscape measurements and that language would be a strong result. +- **Better black-box optimizers:** trust-region or Bayesian methods that exploit the known low intrinsic dimension. +- **A realistic device:** two coupled transmons with leakage out of the computational subspace, where the model–truth gap and the shot noise are physically grounded. + +## Resources + +**Starting notebook.** A differentiable quantum-control notebook (fork this): https://colab.research.google.com/drive/1T0_sJMwmk7rbpxHMcBZwdD9pnYZx93oh — it integrates the time-dependent Schrödinger equation with a differentiable ODE solver, optimizes a pulse to a target gate, and analyzes the control landscape through its Hessian. + +**Optimal control — the two loops.** +- N. Khaneja, T. Reiss, C. Kehlet, T. Schulte-Herbrüggen, S. J. Glaser, "Optimal control of coupled spin dynamics: design of NMR pulse sequences by gradient ascent algorithms," *J. Magn. Reson.* **172**, 296–305 (2005). [doi:10.1016/j.jmr.2004.11.004](https://doi.org/10.1016/j.jmr.2004.11.004) — GRAPE, the gradient-based open-loop method. +- T. Caneva, T. Calarco, S. Montangero, "Chopped random-basis quantum optimization," *Phys. Rev. A* **84**, 022326 (2011). [arXiv:1103.0855](https://arxiv.org/abs/1103.0855) — the truncated-basis pulse ansatz the notebook uses. +- N. A. Petersson et al., "Discrete Adjoints for Accurate Numerical Optimization with Application to Quantum Control," [arXiv:2001.01013](https://arxiv.org/abs/2001.01013) (2020) — exact gradients through the dynamics with a structure-preserving integrator. +- R. S. Judson, H. Rabitz, "Teaching lasers to control molecules," *Phys. Rev. Lett.* **68**, 1500 (1992). [doi:10.1103/PhysRevLett.68.1500](https://doi.org/10.1103/PhysRevLett.68.1500) — the origin of closed-loop / adaptive feedback control. +- D. J. Egger, F. K. Wilhelm, "Adaptive Hybrid Optimal Quantum Control for Imprecisely Characterized Systems," *Phys. Rev. Lett.* **112**, 240503 (2014). [arXiv:1402.7193](https://arxiv.org/abs/1402.7193) — Ad-HOC: open-loop model optimization plus closed-loop experimental refinement. +- J. Kelly et al., "Optimal Quantum Control Using Randomized Benchmarking," *Phys. Rev. Lett.* **112**, 240504 (2014). [arXiv:1403.0035](https://arxiv.org/abs/1403.0035) — closed-loop calibration of superconducting-qubit gates. + +**Control landscapes and their dimensionality.** +- H. A. Rabitz, M. M. Hsieh, C. M. Rosenthal, "Quantum Optimally Controlled Transition Landscapes," *Science* **303**, 1998–2001 (2004). [doi:10.1126/science.1093649](https://doi.org/10.1126/science.1093649) — controllable systems have trap-free landscapes. +- Z. Shen, M. Hsieh, H. Rabitz, "Quantum optimal control: Hessian analysis of the control landscape," *J. Chem. Phys.* **124**, 204106 (2006). [doi:10.1063/1.2198836](https://doi.org/10.1063/1.2198836) — the Hessian rank is set by the number of participating states. +- J. Roslund, H. Rabitz, "Dynamic Dimensionality Identification for Quantum Control," *Phys. Rev. Lett.* **112**, 143001 (2014). [doi:10.1103/PhysRevLett.112.143001](https://doi.org/10.1103/PhysRevLett.112.143001) — the effective search dimension is set by the state space, not the number of controls. +- L. Sagun, U. Evci, V. U. Guney, Y. Dauphin, L. Bottou, "Empirical Analysis of the Hessian of Over-Parametrized Neural Networks," [arXiv:1706.04454](https://arxiv.org/abs/1706.04454) (2017; ICLR 2018 Workshop) — the machine-learning parallel: a few large Hessian eigenvalues, a bulk near zero. + +**Hardness, and the bridge to quantum machine learning.** +- A. G. R. Day, M. Bukov, P. Weinberg, P. Mehta, D. Sels, "Glassy Phase of Optimal Quantum Control," *Phys. Rev. Lett.* **122**, 020601 (2019). [arXiv:1803.10856](https://arxiv.org/abs/1803.10856) — a spin-glass-like hard phase near the quantum speed limit. +- M. Larocca, P. Czarnik, K. Sharma, G. Muraleedharan, P. J. Coles, M. Cerezo, "Diagnosing Barren Plateaus with Tools from Quantum Optimal Control," *Quantum* **6**, 824 (2022). [arXiv:2105.14377](https://arxiv.org/abs/2105.14377) — control-landscape geometry and VQA trainability. +- M. Larocca, N. Ju, D. García-Martín, P. J. Coles, M. Cerezo, "Theory of overparametrization in quantum neural networks," *Nat. Comput. Sci.* **3**, 542–551 (2023). [doi:10.1038/s43588-023-00467-6](https://doi.org/10.1038/s43588-023-00467-6) — the overparametrization phase transition. + +**Real hardware for the optional capstone.** +- 中国科学院 / USTC quantum computing cloud (Hefei): [quantumcomputer.ac.cn](https://quantumcomputer.ac.cn/) — pulse-level control through the QCIS `PULSE` instruction and the `pyezQ` Python SDK ([instruction reference](https://docs.quantumcomputer.ac.cn/Appendix/C2/), [getting started](https://docs.quantumcomputer.ac.cn/Start/1/)). The `PULSE` instruction is a beta feature; request access from platform support in advance. +- 国盾量子 (QuantumCTek) cloud (Hefei): the same QCIS pulse instruction on a sibling stack — [docs.quantumctek-cloud.com](https://docs.quantumctek-cloud.com/Start/4/). +- Non-China alternative: [Amazon Braket Pulse](https://docs.aws.amazon.com/braket/latest/developerguide/braket-pulse.html) on Rigetti superconducting processors — arbitrary waveforms and a native-gate [calibration override](https://docs.aws.amazon.com/braket/latest/developerguide/braket-native-gate-pulse.html) flow. + +**Tools.** JAX (autodiff through the ODE, Hessian–vector products; see the [autodiff cookbook](https://jax.readthedocs.io/en/latest/notebooks/autodiff_cookbook.html)); SciPy (`Nelder-Mead`, `L-BFGS-B`, `eigsh`); a CMA-ES or Bayesian-optimization library for the closed-loop stage. + + diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/README.md b/tracks/qcs/solutions/frustration-free/challenge-113/README.md new file mode 100644 index 000000000..804e71ff8 --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/README.md @@ -0,0 +1,155 @@ +# Challenge 113: frustration-free sim-to-real control + +This directory contains the pinned JAX implementation, restartable artifact +store, paired analysis, publication figures, and guarded production runners for +Challenge 113. Generated outputs belong under `results/` and are never tracked. + +## Local verification and development + +```bash +uv sync --frozen --group dev +uv run python -m pytest -q +CHALLENGE113_DEVELOPMENT_OUTPUT="$PWD/results/development-task10a" \ + bash scripts/run_development.sh +uv run python run.py validate --output results/development-task10a +``` + +The development runner explicitly selects JAX CPU with x64 enabled and writes +only to `results/development`. Override the device only by setting +`CHALLENGE113_JAX_PLATFORM` to the exact platform expected from JAX. + +## Production safety gate + +The approved runtime is the immutable LASG02 SIF +`uv-0.9.9-python3.12-bookworm-slim.sif`, SHA256 +`2405a769d520e6d0f680c0f1dff0d9f92083724f1ffd85ea0c26b5e36defa323`. +It provides Python 3.12.12, uv 0.9.9, and glibc 2.36. The unchanged lock +resolves JAX/JAXLIB 0.11.0, NumPy 2.5.1, and SciPy 1.18.0. + +Create a revision archive and external metadata from a clean challenge checkout: + +```bash +export CHALLENGE113_EXPECTED_REVISION="$(git rev-parse HEAD)" +RUNTIME_DIR="$(mktemp -d)" +export CHALLENGE113_ARCHIVE_PATH="${RUNTIME_DIR}/challenge-113-${CHALLENGE113_EXPECTED_REVISION:0:7}.tar.gz" +export CHALLENGE113_DEPLOYMENT_METADATA="${RUNTIME_DIR}/deployment.json" +git archive --format=tar.gz -o "${CHALLENGE113_ARCHIVE_PATH}" \ + "${CHALLENGE113_EXPECTED_REVISION}" \ + tracks/qcs/solutions/frustration-free/challenge-113 +uv run python scripts/write_deployment_metadata.py \ + --root . \ + --archive "${CHALLENGE113_ARCHIVE_PATH}" \ + --revision "${CHALLENGE113_EXPECTED_REVISION}" \ + --output "${CHALLENGE113_DEPLOYMENT_METADATA}" +export CHALLENGE113_DEPLOYMENT_METADATA_SHA256="$( + sha256sum "${CHALLENGE113_DEPLOYMENT_METADATA}" | awk '{print $1}' +)" +export CHALLENGE113_ACK_PRODUCTION=1 +export CHALLENGE113_ARCHIVE_SHA256="$(sha256sum "${CHALLENGE113_ARCHIVE_PATH}" | awk '{print $1}')" +export CHALLENGE113_CHECK_ONLY=1 +export CHALLENGE113_CLUSTER_PROFILE=lasg02-cpu-v1 +export CHALLENGE113_EVIDENCE_REVISION=dd16192953c130d738716238525760de73343e09 +export CHALLENGE113_JAX_PLATFORM=cpu +export CHALLENGE113_PRODUCTION_OUTPUT="${RUNTIME_DIR}/production" +export CHALLENGE113_PYPROJECT_SHA256=a51151c7947bc44ded698c9081df99b1b84a60ea51fcb041553c7cbfd60e4ecc +export CHALLENGE113_SIF_SHA256=2405a769d520e6d0f680c0f1dff0d9f92083724f1ffd85ea0c26b5e36defa323 +export CHALLENGE113_UV_LOCK_SHA256=1d16a82284cebf3ae050ee79bcba4f2c9166820cf5fcae6a277334e1614a35dc +bash scripts/run_production.sh +``` + +`write_deployment_metadata.py` is the supported helper for creating the +authoritative archive, SIF, project, lock, evidence, and report bindings; +`verify_deployment.py` checks those values before use. Do not manually shorten +or normalize digests: every supplied SHA must be exactly 64 lowercase hex, and +the gate reports the named expected/actual mismatch. + +The production plan contains 9,500 canonical trials. `run.py sweep` also +accepts `--shard-index I --shard-count N`; each shard binds the complete plan +but runs only positions whose canonical zero-based index is congruent to `I` +modulo `N`. Task 8 claims and atomic publication make retries restartable. + +Closed-loop searches use pycma CMA-ES for every search-space kind and +dimension. For `k=1` only, the pycma bound-range standard-deviation limiter is +disabled because pycma 4.4.4 cannot represent its initialized length-one +diagonal scaling vector; the existing coordinate bounds, bound transform, +candidate clipping, population ask/evaluate/tell semantics, seeds, and query +budget remain unchanged. No dummy coordinate or duplicate evaluation is used. + +## LASG02 Apptainer gate + +`scripts/calibrate_pilot.py` measures the representative two-qubit, 80-parameter +setup with a bounded 20–100-query sample. The current canonical local evidence +was measured from source revision +`dd16192953c130d738716238525760de73343e09`: + +```bash +JAX_ENABLE_X64=1 JAX_PLATFORMS=cpu \ + uv run python scripts/calibrate_pilot.py --queries 20 \ + --output results/task10a-dd16192/calibration.raw.json +uv run python run.py validate --output results/task10a-dd16192/pilot +uv run python -m pytest tests/test_evidence.py -q +``` + +Compact, tracked summaries and hashes are under `evidence/task10a/`; bulky raw +results remain ignored. Stage the current source into a new revision directory; +the old `ch113-runtime-d15818c` source tree must not be reused. The already +verified SIF may be referenced only by its exact path and hash: + +```bash +scp "${CHALLENGE113_ARCHIVE_PATH}" lasg02-student090:~/.scratch/ +scp "${CHALLENGE113_DEPLOYMENT_METADATA}" \ + "lasg02-student090:~/.scratch/challenge-113-${CHALLENGE113_EXPECTED_REVISION:0:7}.deployment.json" +ssh lasg02-student090 +export CHALLENGE113_EXPECTED_REVISION=REVISION_FROM_LOCAL_GIT +export CHALLENGE113_ARCHIVE_PATH="$HOME/.scratch/challenge-113-${CHALLENGE113_EXPECTED_REVISION:0:7}.tar.gz" +export CHALLENGE113_DEPLOYMENT_METADATA="$HOME/.scratch/challenge-113-${CHALLENGE113_EXPECTED_REVISION:0:7}.deployment.json" +export CHALLENGE113_DEPLOYMENT_METADATA_SHA256="$( + sha256sum "${CHALLENGE113_DEPLOYMENT_METADATA}" | awk '{print $1}' +)" +export CHALLENGE113_DEPLOYMENT="$HOME/.scratch/ch113-runtime-${CHALLENGE113_EXPECTED_REVISION:0:7}/tracks/qcs/solutions/frustration-free/challenge-113" +mkdir -p "$HOME/.scratch/ch113-runtime-${CHALLENGE113_EXPECTED_REVISION:0:7}" +tar -xzf "${CHALLENGE113_ARCHIVE_PATH}" \ + -C "$HOME/.scratch/ch113-runtime-${CHALLENGE113_EXPECTED_REVISION:0:7}" +printf '%s\n' "${CHALLENGE113_EXPECTED_REVISION}" \ + > "${CHALLENGE113_DEPLOYMENT}/.source-revision" +export CHALLENGE113_SIF_PATH="$HOME/.scratch/ch113-runtime-d15818c/uv-0.9.9-python3.12-bookworm-slim.sif" +export CHALLENGE113_SIF_SHA256=2405a769d520e6d0f680c0f1dff0d9f92083724f1ffd85ea0c26b5e36defa323 +export CHALLENGE113_ARCHIVE_SHA256="$(sha256sum "${CHALLENGE113_ARCHIVE_PATH}" | awk '{print $1}')" +export CHALLENGE113_PYPROJECT_SHA256=a51151c7947bc44ded698c9081df99b1b84a60ea51fcb041553c7cbfd60e4ecc +export CHALLENGE113_UV_LOCK_SHA256=1d16a82284cebf3ae050ee79bcba4f2c9166820cf5fcae6a277334e1614a35dc +export CHALLENGE113_EVIDENCE_REVISION=dd16192953c130d738716238525760de73343e09 +export CHALLENGE113_CLUSTER_PROFILE=lasg02-cpu-v1 +export CHALLENGE113_ACK_NETWORKED_PREPARE=1 +bash "${CHALLENGE113_DEPLOYMENT}/scripts/prepare_apptainer_runtime.sh" +``` + +Preparation first verifies every source/runtime hash, then requires the explicit +acknowledgement above for the sole network-enabled container command: +`uv sync --frozen --group dev --project /workspace`. That command uses +`--no-home --cleanenv` but intentionally does not create a network namespace; +it runs no qcontrol, smoke, analysis, scheduler, or physics code. This is a +one-time frozen networked preparation, not a wheelhouse or offline preparation. +uv 0.9.9 verifies distributions against the hashes in the unchanged frozen +lock; `uv sync` has no separate sync-level `--require-hashes` option. + +The immediately following runtime smoke and all pilot/array calls are +strictly network-isolated and no-sync, use +`apptainer exec --no-home --cleanenv --net --network none`, bind source +explicitly, and fail unless `.venv` plus the hash-bound pre-submit marker are +current. LASG02 Apptainer 1.3.4 accepted this unprivileged network namespace in +a hash-verified Python 3.12.12 no-physics probe on 2026-07-30. +The separate scheduler profile is `scripts/lasg02_profile.env`: +account `chenkun2025`, QOS `user_student090`, partition `ihicnormal`. + +```bash +# Future Task 10C pilot only; do not submit the array before this validates. +export CHALLENGE113_RUN_ROOT="$HOME/.scratch/ch113-runs/${CHALLENGE113_EXPECTED_REVISION:0:7}/pilot-001" +sbatch "${CHALLENGE113_DEPLOYMENT}/scripts/slurm_pilot.sh" + +# Production remains withheld pending pilot timing/resource review: +# CHALLENGE113_ACK_PRODUCTION=1 sbatch --array=0-9499%MEASURED_CONCURRENCY \ +# "${CHALLENGE113_DEPLOYMENT}/scripts/slurm_production_array.sh" +``` + +No mutable image tag, source directory, package resolution, or platform +fallback is accepted by these gates. diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/REPORT.md b/tracks/qcs/solutions/frustration-free/challenge-113/REPORT.md new file mode 100644 index 000000000..ac1d43754 --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/REPORT.md @@ -0,0 +1,100 @@ +# Challenge 113 measured production-gate report + +## Current status + +Production remains blocked and has not been submitted. The canonical 9,500 +paired trials all construct, including model-Hessian dimensions `k=24` for +`d=2` and `k=20,30,80` for `d=4`. Dense landscapes retain a complete +curvature-ordered orthonormal basis while effective Hessian ranks remain 3 and +15 at the `1e-8` relative threshold. Matrix-free results expose only the +columns actually computed. For the bounded `k=p` comparison, model-Hessian +dispatches to exactly the full baseline's identity coordinates, bounds, and +mapping while retaining its model-Hessian label and complete source-basis hash. + +Model preparation uses canonical `model_seed=5`; `perturbation_seed` controls +truth orientation and `trial_seed` controls search and measurement randomness. +The seed-zero statistical pilot therefore shares the accepted canonical model +without coupling statistical seeds to model acceptance. + +## Current measured gate + +Revision `dd16192953c130d738716238525760de73343e09` was rerun locally with the +actual JAX CPU x64 runtime. The representative `d=4`, `p=80`, gap 0.05, +model-Hessian `k=4`, exact-observation, budget-2,000 calibration measured: + +- compilation-inclusive first query: 0.217 s; +- 19 warm queries: 0.0338 s (562 queries/s); +- open-loop setup: 7.75 s; dense landscape: 5.81 s; +- exact trajectory: 0.0340 s; geometry: 1.67 s; +- restricted optimization: 0.430 s (8 evaluations); +- peak RSS: 862,252 KiB; 32 logical CPUs; JAX CPU x64. + +The full representative pilot completed 881 exact queries and strict validation +reported `valid=true`. Wall time was 21.91 s and peak RSS was 864,260 KiB. +The compact evidence records the measured artifact size and an arithmetic, +provisional 9,500-trial projection. No resource class or concurrency has been +selected; those require Task 10C resource pilots. + +Historical preliminary measurements are superseded by this rerun and are kept +only in Git history. + +## Task 10C runtime gate + +The host glibc 2.17 incompatibility is resolved by the compute-verified LASG02 +Apptainer image `uv-0.9.9-python3.12-bookworm-slim.sif` at SHA256 +`2405a769d520e6d0f680c0f1dff0d9f92083724f1ffd85ea0c26b5e36defa323`. +The image provides Python 3.12.12, uv 0.9.9, and glibc 2.36; the unchanged +frozen lock installs JAX/JAXLIB 0.11.0 on CPU with x64 enabled. A LASG02 +compute smoke has passed, but no representative pilot for the current source +candidate has run. + +Runtime preparation now verifies source/archive/evidence/report/SIF/project/lock +hashes and the exact external deployment-metadata byte hash before requiring +`CHALLENGE113_ACK_NETWORKED_PREPARE=1` for one command only: frozen uv sync in +a clean, no-home container with normal cluster networking. No qcontrol code is +executed in that network-enabled container, and no wheelhouse/offline +preparation is claimed. + +The post-sync smoke returns to network-none isolation, validates exact package +versions, finite propagation, and a deterministic objective, then writes a +marker recording the networked preparation mode, isolated execution policy, +runtime versions, lock/hash bindings, metadata digest, and successful smoke. +Slurm jobs use +`apptainer exec --no-home --cleanenv --net --network none`, never sync +packages, and fail closed before entering the container on a metadata mismatch +or on an absent/stale marker. Deployment metadata remains external to the +source tree. An unprivileged no-physics probe verified this network-isolation +mode with LASG02 Apptainer 1.3.4 on 2026-07-30. + +The remaining Task 10C gate is operational measurement: stage the final source, +run one representative pilot on LASG02, validate its artifact, measure the +resource class, and approve concurrency/cost before any production array. + +## Interpretation and production claims + +The rank `d²−1` is the regular controllable pure-state expectation, not an +unconditional identity. Production comparisons, confidence intervals, +model-gap crossover, and failure-case claims remain unset until the complete +canonical artifacts pass strict validation. The finite-shot device is an +abstract Bernoulli estimator, not a hardware noise model. + +## Reproduction identity + +- measured source revision: `dd16192953c130d738716238525760de73343e09`; +- frozen `uv.lock` SHA256: + `1d16a82284cebf3ae050ee79bcba4f2c9166820cf5fcae6a277334e1614a35dc`; +- canonical evidence index: `evidence/task10a/index.json`. + +```bash +uv sync --frozen --group dev +JAX_ENABLE_X64=1 JAX_PLATFORMS=cpu uv run python -m pytest -q +JAX_ENABLE_X64=1 JAX_PLATFORMS=cpu uv run python \ + scripts/build_task10a_evidence.py \ + --run-root results/task10a-dd16192 \ + --time results/task10a-dd16192-pilot.time \ + --validation results/task10a-dd16192-validation.json \ + --output evidence/task10a \ + --revision dd16192953c130d738716238525760de73343e09 \ + --report REPORT.md +uv run python -m pytest tests/test_evidence.py -q +``` diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/SHA256SUMS b/tracks/qcs/solutions/frustration-free/challenge-113/SHA256SUMS new file mode 100644 index 000000000..b063968e1 --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/SHA256SUMS @@ -0,0 +1,12 @@ +412a01dcf0784e09051f450e24b2153f039ec8b66972ab10244e154fb0f65420 code/inverse_schrodinger_reference.ipynb +9ce8766af618e3bd9c706d4dd00114f036fe17d6fbe6f45c380d1cb03c7ef384 code/neural_schrodinger_reference.ipynb +773a24c82a568ddfd99034454bd438822f50ced8d02cec68344972fe9a2b100c references/rendered/.raw/arxiv/0710.0684.pdf +4a6d0a62c4bca352a6799cd637fca80f03b150d559c1106c652c216e851738e6 references/rendered/.raw/arxiv/1103.0855.pdf +9ccaf0547f0c527d24aa0893fe71bb31df0160395dd1aab231eddfc9995b2932 references/rendered/.raw/arxiv/1402.7193.pdf +1243f124d8ccd175ecd6198658aab22d0392b9300e5d0508f7111d0c993ba96d references/rendered/.raw/arxiv/1403.0035.pdf +a648b6289435cf780fa05c210a21d90acc5b30c4d8fad63ab962bf877f551f5f references/rendered/.raw/arxiv/1706.04454.pdf +b6fb35eeb1232a916b0ea27fc619ab133fd02aaa7960704cd17a2113064497c7 references/rendered/.raw/arxiv/1803.10856.pdf +99240c3226531bbc7c9cecf020f71a8e6d374d3d5eb234d88cf5d2f5b943239f references/rendered/.raw/arxiv/2001.01013.pdf +2bc72b2affc407e9b24934f6e27547f7ad50c57431bc4bb9900e8bdbb755d55c references/rendered/.raw/arxiv/2105.14377.pdf +4b7f3e251e802101f141a221514aaa81eaf576c2218521e5a7423bbd6293cf2c references/rendered/.raw/arxiv/2109.11676.pdf +d0d29744db03f4458a22d84840445155d73196b4216df9af9367e48cb107c7c9 references/rendered/.raw/doi/10.1016-j.jmr.2004.11.004.pdf diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/SOURCES.md b/tracks/qcs/solutions/frustration-free/challenge-113/SOURCES.md new file mode 100644 index 000000000..2148a3355 --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/SOURCES.md @@ -0,0 +1,44 @@ +# Challenge 113 source bundle + +## Official materials + +- Challenge specification: `CHALLENGE.md`, captured from + [QuantumBFS/quantum.harness#113](https://github.com/QuantumBFS/quantum.harness/issues/113). +- Official starting notebook: + [Google Colab](https://colab.research.google.com/drive/1T0_sJMwmk7rbpxHMcBZwdD9pnYZx93oh). + The file currently requires an authenticated Google session, so it could not + be exported by the unattended downloader. + +## Downloaded code + +The `code/` directory contains two pinned differentiable-Schrödinger notebooks +from the challenge author's +[`wangleiphy/ml4p`](https://github.com/wangleiphy/ml4p) repository: + +- `inverse_schrodinger_reference.ipynb` +- `neural_schrodinger_reference.ipynb` + +Their source revision is recorded in `code/ml4p_commit.txt`. These are reference +implementations, not substitutes for the inaccessible official starting +notebook. + +## Papers + +`references/ref.bib` records all 14 references listed by the challenge: + +- GRAPE, CRAB, discrete adjoints, adaptive hybrid control, and randomized + benchmarking calibration; +- control-landscape topology, Hessian rank, and dynamic dimensionality; +- the neural-network Hessian analogy, glassy control, barren plateaus, and + quantum-network overparameterization. + +The nine arXiv papers and the open GRAPE paper were downloaded and rendered to +searchable Markdown under `references/rendered/`. Four closed DOI-only papers +have metadata entries but no local full text. Raw PDFs are intentionally +gitignored; `SHA256SUMS` records their checksums. + +Verify tracked notebooks and local PDFs from this directory with: + +```bash +sha256sum -c SHA256SUMS +``` diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/code/inverse_schrodinger_reference.ipynb b/tracks/qcs/solutions/frustration-free/challenge-113/code/inverse_schrodinger_reference.ipynb new file mode 100644 index 000000000..26cdba49f --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/code/inverse_schrodinger_reference.ipynb @@ -0,0 +1,469 @@ +{ + "nbformat": 4, + "nbformat_minor": 0, + "metadata": { + "colab": { + "provenance": [], + "include_colab_link": true + }, + "kernelspec": { + "name": "python3", + "display_name": "Python 3" + }, + "language_info": { + "name": "python" + }, + "accelerator": "GPU" + }, + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "view-in-github", + "colab_type": "text" + }, + "source": [ + "\"Open" + ] + }, + { + "cell_type": "code", + "metadata": { + "id": "XSKyIkjHQd49" + }, + "source": [ + "import jax\n", + "from jax.config import config\n", + "config.update(\"jax_enable_x64\", True)\n", + "import jax.numpy as jnp\n", + "from jax import grad, value_and_grad\n", + "\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "MrFGrB5WTjkX", + "outputId": "88924d85-717f-4874-8301-36177c2f798e" + }, + "source": [ + "!nvidia-smi\n", + "jax.devices()" + ], + "execution_count": null, + "outputs": [ + { + "output_type": "stream", + "text": [ + "Fri Jul 16 03:00:22 2021 \n", + "+-----------------------------------------------------------------------------+\n", + "| NVIDIA-SMI 470.42.01 Driver Version: 460.32.03 CUDA Version: 11.2 |\n", + "|-------------------------------+----------------------+----------------------+\n", + "| GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC |\n", + "| Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. |\n", + "| | | MIG M. |\n", + "|===============================+======================+======================|\n", + "| 0 Tesla T4 Off | 00000000:00:04.0 Off | 0 |\n", + "| N/A 57C P8 10W / 70W | 0MiB / 15109MiB | 0% Default |\n", + "| | | N/A |\n", + "+-------------------------------+----------------------+----------------------+\n", + " \n", + "+-----------------------------------------------------------------------------+\n", + "| Processes: |\n", + "| GPU GI CI PID Type Process name GPU Memory |\n", + "| ID ID Usage |\n", + "|=============================================================================|\n", + "| No running processes found |\n", + "+-----------------------------------------------------------------------------+\n" + ], + "name": "stdout" + }, + { + "output_type": "execute_result", + "data": { + "text/plain": [ + "[GpuDevice(id=0, process_index=0)]" + ] + }, + "metadata": { + "tags": [] + }, + "execution_count": 2 + } + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "8rYk0Q9GPuAa" + }, + "source": [ + "Consider the following \"inverse Schrodinger problem\": optimize a one-dimensional potential $V(x)$ to match its ground-state density to a given target density. This problem comes from the [lecture note](https://math.mit.edu/~stevenj/18.336/adjoint.pdf) of Steven G. Johnson at MIT.\n", + "\n", + "The computation involves solving the Schrodinger equation\n", + "$$\n", + "\\left( -\\frac{1}{2} \\frac{d^2}{d x^2} + V(x) \\right) \\psi_0(x) = E_0 \\psi_0(x).\n", + "$$\n", + "Fortunately, such an eigen-decomposition process is differentiable, thus allows us to compute the gradient via the machinery of AD.\n", + "\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "wZwptSKbWgaJ" + }, + "source": [ + "First, set up the 1D axis and a target density:" + ] + }, + { + "cell_type": "code", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 316 + }, + "id": "yGhJJ7dtRENu", + "outputId": "2c6ab3cc-ff61-4438-b644-ad28cd4f4ab9" + }, + "source": [ + "xmin, xmax, N = -1., 1., 300\n", + "xmesh = jnp.linspace(xmin, xmax, N, endpoint=False) \n", + "target = np.zeros(N) \n", + "idx = (np.abs(xmesh) < 0.5) \n", + "target[idx] = 1. - np.abs(xmesh[idx]) \n", + "target /= jnp.linalg.norm(target)\n", + "\n", + "plt.plot(xmesh, target, label=\"target\")\n", + "plt.xlabel(\"x\")\n", + "plt.ylabel(\"$\\psi(x)$\")\n", + "plt.legend()" + ], + "execution_count": null, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": [ + "" + ] + }, + "metadata": { + "tags": [] + }, + "execution_count": 3 + }, + { + "output_type": "display_data", + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "tags": [], + "needs_background": "light" + } + } + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "k0WHLIusXMnS" + }, + "source": [ + "The kinetic energy term is constructed by using a finite-difference representation of the second-order derivative. On the other hand, the potential term is diagonal." + ] + }, + { + "cell_type": "code", + "metadata": { + "id": "h2pQyjXIR8SU" + }, + "source": [ + "h = (xmax - xmin) / N\n", + "K = -0.5/h**2 * (jnp.diag(-2 * jnp.ones(N))\n", + " + jnp.diag(jnp.ones(N - 1), k=1)\n", + " + jnp.diag(jnp.ones(N - 1), k=-1))" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": { + "id": "jnHGDCzMSbGj" + }, + "source": [ + "def make_loss(K, target):\n", + " def loss_fn(potential): \n", + " H = K + jnp.diag(potential)\n", + " _, psis = jax.scipy.linalg.eigh(H)\n", + " psi0 = psis[:, 0]\n", + " loss = jnp.abs(psi0**2 - target**2).sum()\n", + " return loss\n", + " return loss_fn" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "iY9iCJ23Y2fw" + }, + "source": [ + "We initialize the potential as that of a harmonic oscillator. For training, we use the quasi-Newton (L-BFGS) optimizer." + ] + }, + { + "cell_type": "code", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "R0QjtD8HSBdA", + "outputId": "d7bcc4e5-a977-42d9-a4b2-b9016aa27d6b" + }, + "source": [ + "init_potential = 0.5 * xmesh**2\n", + "loss_fn = make_loss(K, target)\n", + "loss = loss_fn(init_potential)\n", + "print(\"initial loss:\", loss)\n", + "\n", + "import scipy.optimize\n", + "def value_and_grad_numpy(f):\n", + " def val_grad_f(*args):\n", + " value, grad = value_and_grad(f)(*args)\n", + " return np.array(value), np.array(grad)\n", + " return val_grad_f\n", + "results = scipy.optimize.minimize(value_and_grad_numpy(loss_fn), np.array(init_potential),\n", + " method=\"L-BFGS-B\", jac=True, options={'gtol': 1e-07})\n", + "print(\"success:\", results.success, \"\\nniterations:\", results.nit, \"\\nfinal loss:\", results.fun)" + ], + "execution_count": null, + "outputs": [ + { + "output_type": "stream", + "text": [ + "initial loss: 0.38652714355091977\n", + "success: True \n", + "niterations: 987 \n", + "final loss: 0.024938118793835066\n" + ], + "name": "stdout" + } + ] + }, + { + "cell_type": "code", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 316 + }, + "id": "ABzOC5uOVUFn", + "outputId": "8f63b65b-3819-4333-a49e-e15cc99232db" + }, + "source": [ + "fig, (ax_psi, ax_potential) = plt.subplots(1, 2, figsize=(12, 4))\n", + "\n", + "ax_psi.plot(xmesh, target, label=\"target\")\n", + "H = K + jnp.diag(results.x)\n", + "_, psis = jax.scipy.linalg.eigh(H)\n", + "psi0 = psis[:, 0]\n", + "ax_psi.plot(xmesh, jnp.abs(psi0), label=\"optimized\")\n", + "ax_psi.set_xlabel(\"x\")\n", + "ax_psi.set_ylabel(\"$\\psi(x)$\")\n", + "ax_psi.legend()\n", + "\n", + "ax_potential.plot(xmesh, init_potential, label=\"initial\")\n", + "ax_potential.plot(xmesh, results.x/300, label=\"optimized\")\n", + "ax_potential.set_xlabel(\"x\")\n", + "ax_potential.set_ylabel(\"$V(x)$\")\n", + "ax_potential.legend()" + ], + "execution_count": null, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": [ + "" + ] + }, + "metadata": { + "tags": [] + }, + "execution_count": 7 + }, + { + "output_type": "display_data", + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAtsAAAEGCAYAAAC0OFnEAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4yLjIsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy+WH4yJAAAgAElEQVR4nOzdd3hU1dbH8e+eVNIgCaEGCAZCDQQCCb1LUwELKIJIURQVOyreV73qtZeLDUGkiFIUEQRFFBWUJhAg9NBbqAkhIb3Nfv84CXKRkjIzZ5Ksz/PwJJmcOecnhsmaffZeW2mtEUIIIYQQQtiexewAQgghhBBClFdSbAshhBBCCGEnUmwLIYQQQghhJ1JsCyGEEEIIYSdSbAshhBBCCGEnrmYHsJeqVavqkJAQs2MIIUSJbN68OVFrHWR2DkeS120hRFl1rdfscltsh4SEEBMTY3YMIYQoEaXUUbMzOJq8bgshyqprvWbLNBIhhBBCCCHsRIptIYQQQggh7ESKbSGEEEIIIeyk3M7ZFkLYR25uLvHx8WRlZZkdpVzw9PQkODgYNzc3s6MIIYSwAym2hRDFEh8fj6+vLyEhISilzI5TpmmtOXfuHPHx8dSvX9/sOEIIIexAppEIIYolKyuLwMBAKbRtQClFYGCg3CUQQohyTIptIUSxSaFtO/J3KYQQ5ZsU20KUwIZD54g9nmx2DCEqtFMpmbyydDd5+VazowghxFVJsS1EMR04eoyjs+5j3+djOBF/zOw4FU5ycjKTJ0+2+3UWL17M7t277X4dUXIxR84zY+1hPvhtv9lRhBDlwNFz6RxMSLP5eaXYFqIYsrMyYPYgbrP8wa2sJHvWreTnZpsdq0IpbrGttcZqLf7IpxTbzu+WlrUY0iaYj1ceYO2BRLPjCCHKsOy8fB6eu4UR0zeSa+O7ZVJsC1EMW76YQIP8g+zq+BGbo97nhrwDbP1qotmxKpTnnnuOgwcPEhERwRNPPEHPnj1p3bo14eHhfP/99wAcOXKERo0aMWLECJo3b87x48d59dVXadSoEZ06dWLo0KG8++67ABw8eJC+ffsSGRlJ586diYuLY926dSxZsoQJEyYQERHBwYMHzfxPFtfw7wHNCA3y4fGvY0lMkze+QoiSeXv5XnaeuMBLtzTFzcW25bG0/hOiiHat/4nok3PYEDiA6BuHobXmr7hltD0yg4NbbiG0dXezIzrcy0t3sfvkBZues2ktP166pdlVv//mm2+yc+dOYmNjycvLIyMjAz8/PxITE2nXrh0DBgwAYP/+/XzxxRe0a9eOTZs2sXDhQrZt20Zubi6tW7cmMjISgLFjxzJlyhQaNmzIhg0beOihh/j9998ZMGAAN998M3fccYdN//uEbXm5u/Lx3a0Y8PFanvpmGzNHtsVikUWnQoiiWxl3lulrDjOifT16N6th8/NLsS1EEaQkJ+H/y6OcslQnfPRHgNFFovHoyZz5oB0eS8eRGbaRSj5+JietWLTWPP/88/z5559YLBZOnDjBmTNnAKhXrx7t2rUDYO3atQwcOBBPT088PT255ZZbAEhLS2PdunUMHjz44jmzs2V0tKxpXMOPF29uyv8t3snnaw4xtkuo2ZGEEGXE2QtZPL1gG41r+PJ8/yZ2uYYU20IUQdzMh2hjTeDgLd9S26fKxcer+FflWK8PaL5iGJtnjaftI1+YmNLxrjUC7Qhz5swhISGBzZs34+bmRkhIyMWe1d7e3td9vtVqpUqVKsTGxto7qrCzYdF1WXsgkbeX76VtSACt6vqbHUkI4eTy8q2Mn7eV9Jw85g9th6ebi12uI3O2hbiODT/NJjrlJ7bUHUlYm17/+H6LTjexocZdtE1czLaV35iQsGLx9fUlNTUVgJSUFKpVq4abmxsrV67k6NGjV3xOx44dWbp0KVlZWaSlpfHDDz8A4OfnR/369VmwYAFgjJRv27btH9cRzk8pxZu3taC6nyfj520lJTPX7EhCCCc36df9bDicxH8GhdOwuq/driPFthDXcPrEEcI2PM8h11Ba3fPmVY9rNfI9DllCCP7jac6fPeHAhBVPYGAgHTt2pHnz5sTGxhITE0N4eDizZ8+mcePGV3xO27ZtGTBgAC1atKBfv36Eh4dTuXJlwBgdnz59Oi1btqRZs2YXF1neddddvPPOO7Rq1UoWSJYRlb3c+HBoK06lZPH8dzvQWpsdSQjhpFbtPcvHKw8wpE0wd0QG2/Vaqry+GLVp00bHxMSYHUOUYdZ8K9vfvpHGWds4N/xXajeMuObxh3ZuIHhBf3Z7R9Hy6R9RlvL5XnbPnj00aWKfeW32lJaWho+PDxkZGXTp0oXPPvuM1q1bmx0LuPLfqVJqs9a6jUmRTGGr1+3Jqw7w9vK9vHZrc4ZF17NBMiFEeXIqJZP+H6ymup8nix7qSCX30k8fudZrdvmsBoSwgb/mv0FEdgy7mj973UIb4Ibm0Wxu+BgRGevYvOgDByQUxTF27FgiIiJo3bo1t99+u9MU2sL2HuwSSpewIF5eupsd8SlmxxFCOJHcfCvj524lJ8/KJ8Na26TQvh6HFttKqb5Kqb1KqQNKqeeu8P0uSqktSqk8pdQdl33vXqXU/oI/9zoutaiIDu7cROt9/2WHVzStb3+qyM+LHvovdri3oun2NzhxYLsdE4rimjt3LrGxscTFxTFxovRGL88sFsWkOyOo6u3OuDmbSc7IMTuSEMJJvPvzXmKOnuf128IJDfJxyDUdVmwrpVyAT4B+QFNgqFKq6WWHHQNGAnMve24A8BIQDUQBLymlZKm5sIuszAz47n4yVCWCR84o1nQQi4sLQSNmkKtcyZg/hrwcaSMnhBkCvN35ZFhrzlzI4omvY7Fay+eUSSFE0f26+wxT/zzEsOi6DIyo7bDrOnJkOwo4oLU+pLXOAeYDAy89QGt9RGu9Hbh8n8w+wAqtdZLW+jywAujriNCi4tky6ylCrYc50eUd/KsVf9FEjeAb2Bf1Gg3z9rH5y+ftkFAIURSt6vrz4s1NWbk3gcmrDpgdRwhhouNJGTy1YBvNavnxws2Xj/XalyOL7drA8Uu+ji94zGbPVUqNVUrFKKViEhISShxUVFw7Vi+h3el5bKo6iPAed5X4PG37j2JD5b60OTadfTG/2jChEKI4hrerx8CIWry3Yh+r98vvBSEqouy8fB6ZuwWrVTN5WGu79dO+mnK1QFJr/ZnWuo3Wuk1QUJDZcUQZk5J0lmq/PU68Sy2aj/q41OdrPOpTTluq4fPjQ6RfSLJBQiFEcSmleOO2cBpW8+Gx+bGcTM40O1Lp5WbBmV1gvfwmsBDiSv69ZBfb4lN4Z3BL6gVef8MzW3NksX0CqHPJ18EFj9n7uUJcl7ZaOTDjfgJ0MjkDplLJu/TN7StXCeB8n4+obj3LnpkP2yClKIlJkyaRkZFx8ev+/fuTnJxc5OcvWbKEN9+8eo/1ourWrRvSjtQcXu6ufDo8kuzcfB6as4WcvDJcpB5ZC5Oaw6cdjD8Z8kZe2EFuJsTOha1fQU662WlKZd7GY8zbeJyHuoXSt3kNUzI4stjeBDRUStVXSrkDdwFLivjcn4HeSin/goWRvQseE8ImYpZ8SmTaKjbf8CANIjrb7LzN2/VhffAo2pxfxrafK9ZW7s7i8mJ72bJlVKlSpcjPHzBgAM8994/mSRXa9TpLXXLc7UoprZQyvV94aJAP7wxuSezxZF77cbfZcUrm7B6YMxgq+UPft+DcflgunXWEjVmtMPdOWDwOvn8YPu8FaWVzCtbWY+d56ftddAkL4qnejUzL4bBiW2udBzyCUSTvAb7RWu9SSr2ilBoAoJRqq5SKBwYDU5VSuwqemwS8ilGwbwJeKXhMiFI7eTiOJltfZbdbOG2HvWLz87cd8QZ7XRpSb/3znDt5xObnr4jef/99mjdvTvPmzZk0aRJHjhyhcePGDBs2jCZNmnDHHXeQkZHBhx9+yMmTJ+nevTvdu3cHICQkhMTExIvPGTlyJGFhYQwbNoxff/2Vjh070rBhQzZu3AjArFmzeOSRRwCIiIi4+KdSpUr88ccfpKenM3r0aKKiomjVqtXFHSgzMzO56667aNKkCbfeeiuZmeVg+gJF7iyFUsoXeAzY4NiEV9c/vCZjOtXni/VHWbg53uw4xZOfB4seALdKMGIJtHsQ2j8M2+dD8vHrP1+Ioor9Cg7/YbyhG/o1JB2Gb0aANd/sZMWSkJrNuK+2UL2yBx/eFYGLRZmWxdWRF9NaLwOWXfbYi5d8vgljisiVnjsDmGHXgKLCyc/L5cLcUfgCVYZNx8XV9v8kPDw88RgyHfe5N3Jo9mgCnlmBsjh2cYbd/PQcnN5h23PWCId+V5+2sXnzZmbOnMmGDRvQWhMdHU3Xrl3Zu3cv06dPp2PHjowePZrJkyfz9NNP8/7777Ny5UqqVq36j3MdOHCABQsWMGPGDNq2bcvcuXNZs2YNS5Ys4fXXX2fx4sX/c3xsbCwAS5cu5e2336ZDhw689NJL9OjRgxkzZpCcnExUVBS9evVi6tSpeHl5sWfPHrZv316eNtG52FkKQClV2Fnq8uHiV4G3gAmOjXdtz/VrzO6TF5i4aAeh1XyIqFP0uxym2jwTTm2DwV+AX03jsdb3wtoPYM8So/AWwhY2TYcaLSD6AVAKbn7fGOXeMBXaP2R2uiLJzbfy8JwtJGfmsHBcB6p4uZuap1wtkBSiuDZ99QKNc3cTF/lvaoXY7xZTSKOWbG36DM2zNrP5mzfsdp2KYM2aNdx66614e3vj4+PDbbfdxurVq6lTpw4dO3YEYPjw4axZs+a656pfvz7h4eFYLBaaNWtGz549UUoRHh7OkSNHrvic/fv3M2HCBL755hvc3Nz45ZdfePPNN4mIiKBbt25kZWVx7Ngx/vzzT4YPHw5AixYtaNGihc3+Dkx23e5QSqnWQB2t9Y+ODFYUbi4WPhnWmiAfDx78cjNnU7PMjnR92Wmw6g0I6QxNL+mYGxgK1cNh1+KrP1eI4kg6DKdiIXywUWgDtBwKoT3hj7cg87y5+YrotR/3sPFIEm/e1oJmtSqbHcexI9tCOJMDW/6gzeGpbPLrQZtbHrD79drf8SRb3v2V8D2TOB7XhzqN29r9mnZ3jRFoR1NKXfPrK/Hw8Lj4ucViufi1xWIhLy/vH8enpaUxZMgQpk2bRs2axuii1pqFCxfSqJF58wGdiVLKAryPsUFZUY4fC4wFqFu3rv2CXSLA251pI9pw+6frGPfVFubeH42HqxPfbdr6FWScg54v/l0AFWrUF1a/BzkZ4O5lTj5RfuxZany89E2dUnDjyzClM6yZZHzuxL7bEs+sdUcY1TGEQa0ct3HNtcjItqiQMtNS8Fz6IAkqgLBRnxWpMCsti4uF4Hs/J1V5kb/gPnKzM67/JPEPnTt3ZvHixWRkZJCens6iRYvo3Lkzx44dY/369YCxNXunTp0A8PX1JTU11SbXHj16NKNGjaJz578X0fbp04ePPvoIrY0dCrdu3QpAly5dmDvX2Ax3586dbN++3SYZnMD1ukP5As2BVUqpI0A7YMnVFkma1bK1aS0/3h3cks1HjQVUhf//nE5+Hvz1CdSJhjpR//x+rVagrXBmp+OzifLn+AYICAX/ev/7eI1waDEENkyBFOdtBrfl2HmeW7iDdjcE8Hz/JmbHuUiKbVEh7Zr5CLWsp0i88UMqBzjuF3y1GsEc6fQuIflH2D7rCYddtzxp3bo1I0eOJCoqiujoaO677z78/f1p1KgRn3zyCU2aNOH8+fOMGzcOgLFjx9K3b9+LCyRL6ujRo3z77bfMmDHj4iLJmJgYXnjhBXJzc2nRogXNmjXjhRdeAGDcuHGkpaXRpEkTXnzxRSIjI0v93+4krtlZSmudorWuqrUO0VqHAH8BA7TWTtf38KYWNXm4eyjzNx3nq7+Omh3nyuKWQvIx6DD+yt+vGWF8PBnruEyi/Dq1zXgDdyXd/wXWPFj3kWMzFdHJ5EzGzt5MjcqefDosEjcX5ylxldO+my+lNm3aaOlpK65kx69zCV8zjrU17qHjg6XfvKYkVn84ms5JC9nXaxZhnW41JUNJ7dmzhyZNnGfEAODIkSPcfPPN7NxZNkf3rvR3qpTarLU2vWXelSil+gOTABdghtb6NaXUK0CM1nrJZceuAp4uSrFtxuu21aq5f3YMf+xL4Kv7oml3Q6BDr39NWsPnPY1e2uM3w5UWVmsN7zaEhr1h0GTHZxTlR1oCvNsAer8GHR658jHfPWAsyH1iF3gFODbfNWTk5DF4ynqOnsvgu4c6EFa99HtlFNe1XrOdp+wXwgGSzhwjeM2z7HcJJXLkO6blaDXmQw6pugT++jhpSadMyyFESWitl2mtw7TWoVrr1woee/HyQrvg8W7OOKpdyGJR/PeuCOoFejHuq80cSXSiDTyO/QUnNhudRq7WwUgpY3T71DbHZhPlz6mCuyNXG9kG4w5LbgbETHdMpiKwWjVPfbON3acu8OHQCFMK7euRYltUGNpq5cSsMVTSmVhun4anZyXTsvh4+5A5YCo+Oo1Dsx4wRqdEiYWEhJTZUW1hPj9PN2aMNBYsj561iZSMXJMTFVj3kbGBTcSwax9XNQySDsnriCidwjauNcKvfkyN5tCgF2z4DHKdo5PPB7/t56edp5nYrzE9Glc3O84VSbEtKozN371HeOZGNjd6itCm5s+fbdaqAxvqPUCLC38Q+9PnZscplvI6/cwM8nfpHOoFejP1njYcP5/BuDmbyc03eUv3xAOwdxm0ve/6XUb86xmjjellc5c/4STOHwHvIPD0u/ZxHR6F9LPGhkom+2H7ST74bT+3tw7m/s43mB3nqqTYFhVC/P5tNNvxNts8Iml/57Nmx7mo/T0vE+famPobXyLhpJMu0LqMp6cn586dkyLRBrTWnDt3Dk9PT7OjCCCqfgBv3taCdQfP8cLineb+jP/1Cbi4QdTY6x/rH2J8PF82XkOEkzp/+O+fpWup3wVqtoR1Hxtbu5sk9ngyTy/YRmQ9f16/rblDuoqVlPTZFuVeXk42mV+PIVu5U2PEdCxOtELZzc0N7yFT8ZjTi0Oz7ydwwnKnynclwcHBxMfHk5Ago2i24OnpSXDwFTfOFSa4PTKYw4npfLzyADcEeTO2S6jjQ6QnQuxcaHEn+FS7/vFVCtq0nT8CdcpB/35hjvNHoE676x+nlDG6vXAM7FsOjfvbPdrljp3LYMysTQT5ejD1nkjn7pOPFNuiAoj5ciLt8vYTE/UBbWrXNzvOP9QJiyCmyeO0iXuH1Qs/oPMQ524J6ObmRv36zvf3KIStPHljGIcT03njpzjqBXrTp1kNxwbYNB3ysqD9VTpCXK5KwWZAyUfsFkmUc/m5kBIPLUKKdnzTQfDrv+GvyQ4vtpMzchg5ayN5Vs2sUVFU9fG4/pNM5txDaEKU0t6Y32h7bAYbK/elTf+RZse5qsghE4nzaEHErrc4dHCv2XGEqNAsFsV7Q1rSIrgKj8+PZdvxZMddPDcLNn5mtPKr1rhoz3H3Ap/qxsikECWRfMzYHKko00gAXFyNKU5HVju0x3t2Xj5jv9xMfFImn90TSWiQj8OuXRpSbItyKz01GZ8fH+KsqkrjUc7df1ZZXAga/jkuSpMybyzZuf/cKlwI4Tiebi5MGxFJoI87o2dt4ug5B7UE3D4fMhKvvonN1VSpJ3O2RckVvlErarEN0HoEuPsYo9sOYLVqnvl2OxsPJ/HO4BZEO1NP/OuQYluUW7tmPkJN6xnO9/kIvyrO/48ysE4jjraeSKu8WFbOedPsOEJUeNV8PflidBRWrRkxYyOJadn2vaDVCus/gRotIKRz8Z7rVxNST9snlyj/UuKNj1XqFP05lapAq3tg50K4cNI+uS7x/op9fB97kgl9GjEworbdr2dLUmyLcil2xVyikpayodY9NG3fz+w4RdbklsfY5xNF18MfsD1mjdlxhKjwQoN8+PzetpxOyWLMrE1k5NjxrtP+XyBxn7H4rLidFXyqG+3YhCiJtIKfHZ9i9qmOfsCYfrLxM9tnusS8jcf4eOUBhkbV4aFuJixaLiUptkW5k3j6GHXXPstBl/qm7hJZIkoRPPoL0iw+BPw4hpTziWYnEqLCi6znz0dDW7HjRAoPz9lCnr16cK/7CPxqQ7NBxX+udzXISnGajUZEGZN2BjyrgGsxFxsG1IfGN0PMDMhOs0u0n3ed5l+LdtA1LIhXBjp3i7+rkWJblCvaauXkrNF46Uxc75iOu0fZ61/sFVCL8zdNo7o1gePT7zG1j6kQwtC7WQ1eHdSclXsT+NciO/TgPrkVjq6BduOM/trF5RNkfJSNbURJpJ0pWpvJK2n/iPFGb9s822YC/jp0jvHzttIiuAqfDm+Nm5O3xr2asplaiKvYtOAtWmRtIrbJBOo1MX+XyJIKa9OLtaFP0DxtHXsWvGR2HCEEMCy6HuN7NODrmOP8d8U+25587Yfg7mssOiuJwtv/MpVElER6QvGnkBSqEwW12xgLJa35Nou062QK938RQ90AL2aObIuXe9ntVi3Ftig3jsdtouXu94j1jCZ6yASz45Rap7uf50+PrjTa8xHntv1kdhwhBEYP7sGRwXz4+wFmrDlsm5MmHYLdi6HtaPCsXLJzeBeMSqZJsS1KoDQj20pB+4eNn+N9y20S5+i5dO6dsQkfT1dmj47C39vdJuc1ixTbolzIzc4gf8EYUpUXtUZOR1nK/o+2q6sLIaOmc1DXxv37sVjPHzM7khAVnlKKN24Lp2+zGrzyw24WxBwv/UnXfQwWV4geV/JzFE4jkWJblETa2b/fsJVEkwFQuQ6sL30bwLOpWYyYsZE8q5Uvx0RRq0qlUp/TbGW/IhEC2D7zCULyj3Kk07tUq1GM1kVOrm6NIPZ3m4LKz+HElw+AreeJCiGKzdXFwgdDI+jcsCrPLtzO8p2nSn6ytASInWNsze5Xs+TnKSyUZBqJKK6cdMhJK/nINhib3EQ/YKw7OLm1xKdJycxl5IxNnL2QzYyRbWlQzbfkmZyIFNuizNu7dhGRp+ezJvB22vQaYnYcm+vXrRM/VB1DnaR1xK+ZY3YcIQTg4erC1HsiiahThfHztvLnvhIuTNw4FfKyoeNjpQvk5gkelY3iXYjiKGnbv8sVbnKz7uOSxcjOY+TMjew/m8qnw1vTuq5/6fI4ESm2RZmWmnyOKr8+xWFVh4hRH5gdxy6UUvQe+QK7VSiVfv8/slLPmx1JCAF4ubsyc2QUoUE+PPDlZjYfTSreCbLTYOM0aHwTVG1Y+kA+QcbcWyGK42KxXYqRbTDWG0SOhF3fGfO3iyEzJ58xszaxPT6Fj4a2olujUmZxMlJsizJt96zxVLUmkXXTR/j4lI/bTVcS4FuJjN7v4m9NZtuXz5gdRwhRoLKXG1+Oiaa6nwejZm5i54mUoj95y2zISoaOj9smjFcgZBaz4Bcio2A/B++qpT9Xh/HG+oM1k4r8lOy8fMZ+GcPGI0m8P6QlfZuXYjqVk5JiW5RZG1d8Q3Tyj2wOHkGTNt3NjmN3bdr3ICZoEG3OLGDLRtldUghnEeTrwVf3RePr6cbw6RvYffLC9Z+Un2tszV6vI9Rpa5sgnlUgM9k25xIVR+HPjGeV0p/Ltwa0Gg6xcyHlxHUPz8238vCcrazen8hbt7Uoc9uwF5UU26JMOnP2LHXXPscxl7q0GvGm2XEcJvyed0mz+KB+eprk9Gyz4wghCgT7ezHv/nZUcnNh2Od/EXf6OgV37Fy4EG+7UW2ASlWMzUWEKI6sgmK7kg2KbTDWH2grrL/23O28fCuPfx3Lr3vO8MrAZgxpW36aG1xOim1R5litmj1fjCdIJ+Fy62TcPLzMjuQwlSpXJa3Tv2il97Bo9iTb72InhCixuoFGwe3h6sKwaRvYdyb1ygfm5cDqd6FWa2h4o+0CeFb+u3ASoqgykwFlLLC1Bf8QaDEEYmZCeuIVD8m3aiZ8u50ft5/i+f6NGdE+xDbXdlJSbIsy59clX9ItfTlxoaOo3byz2XEcrnb3Bzjj24ybTk9m6V+7zI4jhLhESFVv5o1th4tFcfe0v9h/pYJ721xIPgbdJhobgtiKZxXIugBWq+3OKcq/rGTjjZot96fo9CTkZRm7Sl4mL9/Kk9/EsmjrCZ66MYyxXUJtd10n5dBiWynVVym1Vyl1QCn13BW+76GU+rrg+xuUUiEFj7sppb5QSu1QSu1RSk10ZG7hPA4ePkzrrf/Hcbf6NB36utlxzGGxUPWuTwhQafgsf4zj59LNTiSEuET9goJbKcXQaRs4cDbt72/m5cCf70HtSNuOakPB7pMasoswZ1yIQpnJtptCUigoDJoOMLrtXLKOIDffymNfx/J97Ekm9GnE+J426MJTBjis2FZKuQCfAP2ApsBQpVTTyw4bA5zXWjcA/gu8VfD4YMBDax0ORAIPFBbiouLIzs0jYe5YfFUGPnfPRLmV/V2lSsqlditSO79ADxXDb7P+Tb5VppMI4UxCg3yYd380AEOn/fX3lJLYOZBih1Ft+Ltgkqkkojiykm2zOPJynZ8y3vhtmAoYhfaj87by4/ZTTOzXmIe7N7D9NZ2UI0e2o4ADWutDWuscYD4w8LJjBgJfFHz+LdBTKaUADXgrpVyBSkAOIG/dK5i/Zk2kXe5GjrZ6Bv/6rcyOYzr/Ho9xskYPhl2YztLF88yOI4S4TINqvswfG40C7py6nl3HEmD1e1C7DTToZfsLFhZM0pFEFIc9RrYBaraExjfD+o/JST3Hw3O28NPO0/zfTU14oGv5nzpyKUcW27WB45d8HV/w2BWP0VrnASlAIEbhnQ6cAo4B72qt/9FMVCk1VikVo5SKSUiQXbTKk7hV8+l64jO2+vchbMAEs+M4B6Woee9MEj2C6b7tafbuKvkWuUII+2hQzZcFD7bHy92VhdPfgpTj9hnVhoJpJEhHElE89hrZBuj+L3R2Kr99PpFfdp/h5QHNuK/zDfa5lhMrKwsko4B8oBZQH3hKKfWP/1ta68+01m201m2CgoIcnVHYSerxXdRZ9Rh7LA1pdN90+/ySKqNUpSp4j/wOq3LB+9uhZCbLm0whnE29QON0hBkAACAASURBVG++uS+CBy3fEasbso6W9rmQTCMRJWGvkW0grUoYayt1p1vyIt7pV4N7O4TY5TrOzpHF9gng0iaKwQWPXfGYgikjlYFzwN3Acq11rtb6LLAWaGP3xMJ8WSlkfHknGdoD65Cv8PIuv7tElpRfrQYc7z2NIGsCJ6YPlU4EQjih2nGzqabPMddnJKO+iGFl3FnbX6RwdFJGtkVRaW23ke3z6TkMm/YXL6bcgocln8HpX9v8GmWFI4vtTUBDpVR9pZQ7cBew5LJjlgD3Fnx+B/C7NhoJHwN6ACilvIF2QJxDUgtTnZh9HwHZJ/mj5bs0a9zY7DhOq0WHvvwa8hQNUjdxYMnbZscRQlwqIwlWvw8N+zDxobGEVfdl7Jcx/LD9pG2vUziNROZsi6LKzYT8HJuPbJ9OyWLI1PXsOZ3KxOE3YWk1HDbPNFpeVkAOK7YL5mA/AvwM7AG+0VrvUkq9opQaUHDYdCBQKXUAeBIobA/4CeCjlNqFUbTP1Fpvd1R2YY5zMQupffIXvvYZzqCBd5gdx+n1HPYMa93aUTf2HZIPxZgdRwhR6M93ICcVbnwZf2935twfTas6/oyft5VZaw/b7joevqAsMo1EFF3hz4oNR7YPJ6Zz+6frOJWSxRejorixaXXo+gyg4I+3rvv88sihc7a11su01mFa61Ct9WsFj72otV5S8HmW1nqw1rqB1jpKa32o4PG0gsebaa2baq3fcWRu4XjWjGTUsgnE6Xp0vvcVXF3KyvIC83i6u1Lt7qmc176kzr8fnSfbuQv7KMKeCU8qpXYrpbYrpX5TStUzI6dTSDps9BpuNRyqNQHAz9ON2WOi6N20Ov9eupu3l8fZZjdYpQp2kZRpJKKIMm27VfvukxcYPGU9mbn5zLu/He1DA41vVA6GtmMgdi4k7LXJtcoSqWCEU9oz52mq5CdxvPOb1Ktmp1XS5VDD+iFsa/kSdXIOsevrl8yOI8qhIu6ZsBVoo7VugdFNquLObfr9VXBxg27P/8/Dnm4uTB4Wyd3RdZm86iBPL9hObr4N1ltIsS2Ko3ADJA+/Up9qzf5E7py6HjcXxTcPtCc8+LLt3zs/De6+8Mv/lfpaZY0U28LpHN7yG81OLGBllVvp1bOf2XHKnF6DRrKmUg8a7fuME3EbzY4jyp/r7pmgtV6ptc4o+PIvjAXxFc+JzbBzIbR/BPxq/uPbLhbFa4Oa80SvMBZuiWfs7BgycvJKd013H8jJuP5xQgBkF+xu6lG65gMLYo4zcuZGavtXYuG4DjSo5vPPg7wDoesE2P8LHPi1VNcra6TYFk4lKzMD9cNjnKIqrUa+h5I2f8VmsSga3PsxKcqHrG8fJC9HppMImyrKngmXGgP8dLVvltv9EaxWWD4RvIOg46NXPUwpxWO9GvL6reH8sS+BodM2kJBain+z7t7G/HAhiiKnoNh2v0JxXARaa/67Yh8Tvt1OuxsC+ebB9tSqco3dnaPGgn99+PlfkF/KN5ZliBTbwqms//JFQqzHSez6BgH+AWbHKbNq1KjNkehXCM07yIY5Mp1EmEMpNRyjTetV19mU2/0Rts+H4xug18tFGjW8O7ouU4ZHsu90KoM+WUvc6RJukuzuAznpJXuuqHguFtvexX9qnpWnF2zng9/2c0dkMDNHtcXP0+3aT3L1gN6vQkIcbJlV/LxllBTbwmnExPxFhxMz2enfk/DuQ8yOU+a16TeSrb7daXvkM/Zs+8vsOKL8KMqeCSilegH/AgZorSvW7ZXMZFjxIgRHQcuhRX5a72Y1WPBge/KsVm6fvI7f484U/9ru3lJsi6Ir/Fkp5jSSlMxcRs3ayMIt8Tx5Yxjv3NECt6I2Mmh8M4R0hpWvV5g2lVJsC6dwPjUTjx/Hk608aTDiY7PjlBsNRn1KuvJGff8w6ZlZZscR5cN190xQSrUCpmIU2nbYvcXJrXoD0hOh/ztgKd6v2ea1K/P9w52oH+TNfV/EMGPN4eJ1KnH3+XserhDXk10w5agYI9sHE9K49ZO1bDiUxLuDW/Joz4bFm/KpFPR53eg//2fFaC4nxbYwndaalbNeJFzvI6X763j61zI7UrnhG1CTxC6v0dh6gHWzJpodR5QDRdwz4R3AB1iglIpVSl2+gVn5dXonbPwM2oyGWhElOkWNyp5880B7bmxanVd+2M3/Ld5Z9E4lHj5/Tw0Q4npy0sHiZkzvKIKVe88y6OO1pGTmMvf+dtwRWcK1zzVbGO0wN0yBs3tKdo4yRIptYboVq1ZxU+JMDlXtTp0uI8yOU+407DGCnVX70v30LDb+udzsOKIcKMKeCb201tW11hEFfwZc+4zlhNaw7Gljg5AepWtv5uXuyqfDInmwayhzNhzjnukbSEwrwmwcmUYiiiMnrUij2lprpv5xkNGzNlEnwIsl4zsRVb+U66p6vWy0HPzhCWNBcTkmxbYwVfzpBBqsephMFx/qjZhq3F4SNhc2cgqJLkHU+n08CYnlqOODEM4kdi4cWw+9XgKv0i/wtlgUz/VrzPtDWrL1WDK3fLSG2OPXmePq7g3WXMjLKfX1RQWQk37d+dpZufk88XUsb/wUR//wmnw7rj21r9VxpKi8A43FksfWQ+yc0p/PiUmxLUyTn2/l0Kz7qcdJcgdNw8WvutmRyi13H39yB0yhpk5g38xxttmtTgjxt9TT8PNEqNsBWtn2Dt1trYNZOK4DLhbFkCnrmbfx2NUPLmzhJlNJRFFkp16z7d/xpAwGT1nP4tiTTOjTiI+HtsLL3dV21295t/FvZsULkH7Odud1MlJsC9Osnv82XbJWsrfJQwS1uNHsOOVenYge7Ah9gI7pK1izeKrZcYQoP7SGH5+C3CwY8FGxF0UWRfPalVn6SCeibwhg4nc7eG7hdrLz8v95oBTbojhy0q86jeTX3We46cPVHDmXzrQRbXi4ewPb731hscDN7xtF/4oXbHtuJyLFtjDF/q1/0mHfO+zyiqLJkFfNjlNhtBz2Hw64N6Fl7MscORhndhwhyofdiyHuB+g+Eao2sNtl/L3dmTUqioe7hzJ/03GGTFnP8aTLdossLJxk3rYoipw0Y1HtJfLyrbzx0x7umx1D3UAvfhzfmRub2vHOc7Um0GG8MZXkyBr7XcdEUmwLh8tMTsB3yRjOqSrUGfMVyuJidqQKQ7m4UWX4LCxKkzZvNDk5uWZHEqJsy0iCZROgZgS0H2/3y7lYFBP6NGbqPZEcSkyn/wer+XH7qb8PuDiyLcW2KIKc9P+ZRnI6JYu7p21g6h+HGBZdl28f7EDdQC/75+jyDFSpB98/Ui5/dqXYFo6lNUdn3Iu/NYmEvtPwC5R52o5WtW5jDrX9N83zdrHhy9J1TBCiwls+ETLPw8CPwcWGc1mvo0+zGix7tDOh1Xx4eO4WJn63g8yc/L9HtrNly3ZRBJfM2f5jXwI3fbianSdT+OCuCF67NRxPNwcNhrl7waDJcP4wrCh/ux5LsS0cas9PU2h8YS2r6j5Ci3Y9zI5TYbXo/wCxlXvS/thn7N70u9lxhCibdi02tmXv9CTUCHf45esEeLHgwfaM6xbKvI3HGPjJGo6kFfxaL4ejg8IOctLIc/Xi30t2ce+MjQT6uLPkkY4MjKjt+CwhnSB6HGyaBodWOf76diTFtnCYpNNHqb3xVXa4NKPrPTKiaiqlaDB6GomWQPyWjSP1wnmzEwlRtqTEw9JHoVZr6PqMaTHcXCw827cxs0dHkZSew9j5xgYhWhZIiiKwZqexcGcys9YdYVTHEJY80okG1Yq3dbtN9XwRAhsY00myLpiXw8ak2BYOoa1Wjs5+EHedg9fgT/F0dzM7UoXnUzmQlL6fUNN6hp0z7D/XVIhyw5oPix6E/Dy4/XNwMf/1rEtYEMse60yjejUBmLlyFyeSM01OJZyV1aqZtmovlvxszue588XoKF66pZnjpo1cjbsXDJoCF07Az8+bm8WGpNgWDrF26QxaZaxjW8NHCG3c0uw4okCj6D5srTWU9slLWbfqR7PjCFE2rPsQjqyG/m9DYKjZaS6q5uvJh/d0BCAp6Rx9//sn38Qcl7764n8cO5fBsM838NHybQDc07UZXcOCTE51iTptocOjsPVLiCsfv5ek2BZ2dzT+BI22vMphtwa0vetfZscRl2k54i0SLVWpuuo5TifJoiohrunEFvj9P9B0EEQMMzvNP6iCxW73R1enSS0/nvl2O/d9EcPZC1kmJxNmy7dqpq85TJ9Jf7LjRAr/6R8CgLdPZXODXUn356FmS1j8kDFlq4yTYlvYVW6+lbivnsRfXcBn8GQsrubfbhX/y62SH3l93iKMY6z84mWsVhkFE+KKMs/Dt6PApzrcMglsvcGHLVgs4OZNZdcc5t/fjhdubsqaA4n0nvQn32ySUe6Kav+ZVO6Yso5Xf9hNuxsC+OWJLgxoUsX45lU2tTGVqwfcMROsefDtGGPKVhkmxbawq8XfzadP1nKONhxJUFi02XHEVdSIup34at0YmDybBb+tMzuOEM7HajXmaaecgMGzoJK/2Ymuzq0S5GZgsSjGdKrPssc607CaD88s3M6dU/9i/xm5g1VR5ORZ+fC3/dz04RqOJKYz6c4IZoxsS60qlSCvYE6/WyVzQ15NYCjcPAmO/wWr3jA7TalIsS3sZseePXTf+Sxn3esQOvg1s+OIa1GK2kM/xGKx0GDNE+w7mWR2IiGcy5r3YN9y6PM61IkyO821uVUyto4vEBrkw9dj2/PW7eHsPZNK/w9X8+7Pe8nKvcJ276LcWHcgkf4frub9Ffvo07wGK57syqBWtf/ecj23oNh29TQv5PW0GAyt7oHV78He5WanKTEptoVdpKenYVlwL14qB68R840VxsKpKf965PT/L5FqL9u+eIrsPPlFLAQAB36F31+D8MEQdb/Zaa6vYGT7UhaL4s62dfntqa7c0qIWH688QJ9Jf7Jy71mTQgp7OZ2SxSNzt3D35xvIzstn+r1t+GhoK6r6ePzvgYXFtpuT/37u/w7UbAHf3Q+J+81OUyJSbAvbu3CKxI9708y6lxNd38UnuLnZiUQR+bUdSnzoUAZnf8eKOf81O44Q5jsbBwtGQfVmcMsHzjlP+3KunpB35QWRVX08eP/OCObeF42LUoyauYl7Z2yUqSXlQHZePlP/OEiP91axYvcZHu/VkBVPdKVnk6vs1Fz4M+LmxCPbYLx5vHOO0WJz/t1lsv+2FNvCtuJjyJrcmaoZB1gU9iYNu99jdiJRTMF3TeKATxv6H/oPRxe/ArKgSlRU6Ykwd4hRvA6d75wLya7EzesfI9uX69CgKssf78L/3dSELcfO0/eD1byweCdJ6TkOCilsRWvN0m0n6fX+H7zxUxwdQgNZ8URXHu8Vdu2+2YU/I84+sg1QpQ4M/gLOHYTvxhq97ssQKbaF7cTORc/sR2KWYkLl97hpyANmJxIl4eZJrXGLWOnWiXqx75E7bzhky6iXqGBys4xRtLQzRqFdpY7ZiYrOrdLfUwSuwd3Vwn2db+CPCd0ZFl2XuRuP0fWdlUz785DM5y4jNh1J4tbJ6xg/byve7q7MHh3F5/e2pW5gEQrownn9zjxn+1L1O0O/t2DfT/DTs2VqIEiKbWEb2xfA4nHsdmvG7Xmv8eTwQbi7yo9XWeXl7UfVEV/yWt5wLPuWwfQ+kJVidiwhHCM/z2jxd3wDDPoUgiPNTlQ8ly2QvJ4Ab3deGdic5Y91JrKeP68t20P3d1cxd8MxcvOtdgwqSmrXyRTu+yKGwVPWcyolk3fuaMGPj3amS3E2pylLI9uFou6H9o/Apmmw7iOz0xSZVEOi9FJOwJLxnPGP5Nbkx3mofxQNqvmanUqUUsu6/vh1f5xROROwJuyF7+ROhagArFZY8gjsXQb93oHmt5mdqPiusECyKBpW92XWqCjm3BdNjcqePL9oBz3f+4OFm+PJl/77TiHu9AUe/HIzN324hg2Hz/F07zBWPd2dwW3q4GIp5nqCsjJn+3I3vgrNboUVLxgDfWWAQ4ttpVRfpdRepdQBpdRzV/i+h1Lq64Lvb1BKhVzyvRZKqfVKqV1KqR1KqTL201GOrXwdbc1naMK9tA+rxYj29cxOJGxkXLdQ0ut04319l3Hr7uDvZkcSwn60hp8nwrZ50P1fED3W7EQl41rpqgski6Jjg6p8N64DM0e2xdfTlacWbOPG9//g603HpEuRSXafvMDDc7fQd9Jq1h5I5LGeDVnzbA8e6dGQSu7XmJd9LRdb/zlpn+2rsVhg0BSo1wkWPQC7vzc70XU5rNhWSrkAnwD9gKbAUKVU08sOGwOc11o3AP4LvFXwXFfgK+BBrXUzoBuQ66Do4loyktDbv+YH996cd6/JO3e0+LuHpyjzXF0s/HdIBF9Z+3LWpTp65ZtmRxLCPqxW+OkZ2DAF2j0MXSaYnajkSjiyfSmlFN0bV2PpI534dFhrKrm78OzCHXR9exWfrz5EWnbZ3tGvLNBas/ZAIvdM30D/D1ezKu4s43s0YPWz3XnixjAqVyrljsy5mWBxAxdX2wR2JDdPuHs+BLeBb0dD3DKzE12TI0e2o4ADWutDWuscYD4w8LJjBgJfFHz+LdBTGZVbb2C71nobgNb6nNZa3l47g50LUdZcJqd05I3bWlDNT244lDd1A714fkAE07J6ouI3GKvBhShPrPnww2Ow8TNjPmif18pGi7+rcfMs0gLJorBYFP3Ca/LD+E7MHh1F/are/OfHPXR44zfe/CmO+POlK+rFP+XkWVmy7SS3fLyGYZ9vYM+pVCb0acS653ryVO9GVPFyt82F8rKcd/fIovDwhWELoGZL+GYE7PnB7ERX5chiuzZw/JKv4wseu+IxWus8IAUIBMIArZT6WSm1RSn1zJUuoJQaq5SKUUrFJCQk2Pw/QPxTasx84qx1CI/sSN/mNcyOI+xkcGQwqQ0GYtWKhLVfmh1HCNvJzTI2y9gyG7o8A73/U7YLbTAWvOVlGaP1NqKUoktYEPPGtmPRQx3oEFqVz/48SJe3VzJ2dgxrDySiy1B3CGd0MjmT937ZS4c3f+fReVvJyM7nzdvCWfNsdx7u3oDKXqUcyb5cbkbZLrYBPCvD8O8KCu57IGam2YmuqKzcO3AFOgFtgQzgN6XUZq31b5cepLX+DPgMoE2bNvKv3s4upCThdXYLG91v58VbmpkdR9iRUopnhvQg9t0mVI5dhG+/F67dv1WIsiAtwWjvF78Rer0MnR43O5FtFBZQeVl22b23VV1/ptwTyYnkTOb8dZT5m47zy+4zhAZ5M6RNHW5tXZtqvnKXsyjy8q2s3p/I3I3H+G3PGTTQo1E1hrerR9ewICzFXfRYHLlZZaft37VUqgL3LoEFI+GHxyHtLHR9xqneNDuy2D4BXNqoNLjgsSsdE18wT7sycA5jFPxPrXUigFJqGdAa+A1hmm++ncd9WInudSs+HmXlfZsoqQBvd1LC+1J/+/u8/cNfPHNrR7MjCVFyp3fCvKGQngBDZkPTy2c1lmGu9i22C9WuUoln+jbm0Z4N+XH7KeZsOMobP8Xx9s976RoWxB2RwfRsUg0PV3ljfrldJ1P4bssJvo89SWJaNoHe7jzYNZShUXWpE+CgVny5GWWr7d+1uHvDXXNhyaOw6nU4sxMGfgKefmYnAxxbbG8CGiql6mMU1XcBd192zBLgXmA9cAfwu9ZaK6V+Bp5RSnkBOUBXjAWUwiQ/bj+F5fCf5Ll70Ciyp9lxhIPUb9sPtr/P4U0/s7p5GJ0bFqOnqxDOQGvYMBVWvAiV/GHUMqjd2uxUtlU4sp2bAQTY/XKebi7cHhnM7ZHBHExIY+HmeL7bcoKH4rbg6+nKjU2r0795TTqHVa2whbfWmgNn01i+8zQ/7jhF3OlU3FwUPRpX47bWwXRvVM3xe1PkZZW9tn/X4uIGgyZDtSbw67/h7B6480vja5MVu9hWSnkDWcVdoKi1zlNKPQL8DLgAM7TWu5RSrwAxWuslwHTgS6XUASAJoyBHa31eKfU+RsGugWVa6x+Lm13YxumULJ5ftINvPQ9iCY4qX/9YxbXVaoV296Gf2z6eXrCN5Y91wd/bRot1hM2V9PW63LpwEpY+Bvt/gbC+xsiXd1WzU9nexWK75O3/Sio0yIdn+jbmqd6NWHMgkR+2neSX3Wf4bssJfDxc6dWkGj2bVKdzw6q2W+jnpPKtmh0nUvhl12mW7zrNoYR0ACLr+fPqoObcHF7T3NfP3Myy1/bvepSCjo8ab6AXjILPukG356D9eFO7rlz3ykopC0bROwxjznQ24KGUSgR+BKZqrQ8U5WJa62XAsssee/GSz7OAwVd57lcY7f+EiaxWzdMLtqHzcmigjqJq9zc7knAkFzdU7dbcmBrPU6dyeH7RDiYPay3tHp2ELV+vy5W8bFj/Mfz5Huh86P8utL3PqeZ02tT/jGybw8Wi6BoWRNewIF7Ls7L+0DmWbT/FL7tPszj2JBYFEXWq0DWsGl3CqtK8dmXcXMr+PnsnkjNZsz+BP/cnsvZAIskZubhYFO1vCGRUx/r0blqd6s7StSs301hgWB6FdIIH18Cyp4xR7p3fwS2ToLY5u8EWpcxfCfwKTAR2aq2tAEqpAKA78JZSalFBMSzKuZnrjrDmQCKf9nBFrcuBmhFmRxKOVjOCSsemMKHXDbz+80EWbjnBHZHBZqcSBnm9vlReDmyfD6vfg/NHoPHNRls//xCzk9nXxWLbNu3/Ssvd1XKx8H7dGs62+GRW7U3gj30JTPptH//9dR+V3FxoVbcKbUMCaBsSQMs6lfH1tHH3DRvLt2r2nk5l87HzbDl6ns1Hz3MsyXiDU93Pg14FI/hdGgY55x3AvCxwK8ddxHyrw51fGZve/Pg0TOsBTQZA12ehRnOHRilKsd1La/2PDWS01knAQmChUsq5/0UIm9h7OpW3lsfRq0k1+gbsNB6s1crcUMLxarWC/BzGhGXy674A/r1kF9H1Axy3qEdci0Ner5VSfYEPMKYEfq61fvOy73sAs4FIjEXud2qtj5T2ukWitTFXc/vXsG0+pJ2GGi2M9mANKsj6kosLJJ2j2L6Ui0XRuq4/rev68+SNYZxLy2b9oXPEHDnPxsNJfPj7fgo7CNYL9KJZLT+a1apM05p+hAb5UKuKJ64mjICnZOZy4Gwqu0+lEnfqAnGnjY/pOcYMrao+HrSp58+I9vXo3DCIsOo+zn/Hrzy0/iuKpgPhhu6w/hPjDteeJRDSGVoNh0b9HbKI8rrFduELt1LqA+BxfYVGmld6cRflS3ZePo/N34qfpytv3t4CtfIr8KxS/keIxD/VMu5muJzexvtDhtBv0mqe+DqWrx9oj4s921SJ63LE6/UluwHfiNEpapNSaonWevclh13cDVgpdRfGbsB3lua6V5SRBIn7jFZfyUfh9A44shYuxINygYa9Ieo+CO1ZfqeMXImTjWxfS6CPBze3qMXNLWoBcCErly1Hz7MjPoXdpy6w88QFlu04ffF4NxdFHX8vQqp6UzfAiyBfD6r5elDNz5Nqvh5UruSGt4crPh6u13090lqTnWclIyefpPQcEtOyOZeWw7n0bM5cyOLouQyOJRl/kjP+/mfj6+lKkxp+3B4ZTKu6VYisG0CdgErOX1xfrry0/isKTz/oPhGiHzB66m+abmz17uJuTC2pEwVVw6ByHfCpZvNFlcWZLZ4KLFFK3aW1TldK9QFe1FpL/68K4L1f9hF3OpXp97ahqo+HMXJUvVnF+gUmDP71waMynNpOcORIXh3UnMe/jmXKHwd5uHsDs9MJgz1fry/uBgyglCrcDfjSYnsg8O+Cz78FPlZKqSsV/6Wy/xfjF2Yh31oQHGn02A3ra9xGrojKULF9OT9PN7o1qka3RtUuPnYhK5e4U6kcTkzjcGIGR8+lczgxnU2Hk0i9xrbxldxc8HJ3QSmFUqAwfmXlWzWZOflk5uZjvcpPpKtFUdu/EnUDvLgpvCZ1A7y4IciHJjV9qV2lDBbWV1KeWv8VlVeA0U+/w6NwIgb2LIWj64xRb2vBz1JgQxgfY9PLFrnY1lr/n1LqbmCVUioHSAOes2ka4ZQOnE1l2upDDIuuS88m1Qtu08ZB+B1mRxNmUAqqNYaEvQAMjKjFb3Fn+e+KfdzWujY1K1eA25JOzs6v11faDTj6ascUdKIq3A048fKTKaXGAmMB6tatW7wk9bvAsIXGSJRfbfAOLN7zy6syXGxfiZ+nG1H1A4iq/882hhk5eSSkZnM2NZuzF7K5kJVLenYeadl5pGfnkZFTWFBrtDZ+fVksCi93oxD3LCjIA7zdCfT2oKqvO1V9PPD3ci//d+rKW+u/4rBYjNHsOlHG1/l5kHIMUuIx3pbZVpGLbaVUT+B+IB2oCYzWWu+1eSLhdE6lZKE1DGpV23gg9TRkp0BQY3ODCfMENYY4o/umUopBEbVYuu0kCanZUmw7gbL0el2qnX/9ahl/xP8qHK00sRuJo3i5u1Iv0JV6gd5mRylbrFaj2C5vrf9KysUVAm4w/thBcVYZ/At4QWvdDWPDma+VUj3skko4lfyC+2yWwttmCXuMj9Wk2K6wghpDRiKkGwOVhVsK51/tnqxwNHu+XhdnN2Au2w1YOELhPNw8x/fZFmVE4c9GRVgg6QSKXGxrrXtordcUfL4D6Af8x17BhPMonGV58Y5awfQBGdmuwIIaGR8T4oC/34hJre0c7Px6fXE3YKWUO0Zf7yWXHVO4GzBcshuwja4vrqecTSMRdiDFtkNdt9hWV1kFoLU+BfS81jGifCgcrbw4fy1hr9GJxFu26q6wCt9onTXucrhcLLalnjKTI16vtdZ5QOFuwHuAbwp3A1ZKDSg4bDoQWLAb8JPI+h7HcnEDi6sU2+LqCqcYVbQFkiYp0qY2SqmFwPda62OFDxaMaLRXSt2LsZHCLPtEFGYrLKAuTiNJDuGUDAAAIABJREFUOgiBDaQTSUXmVwvcfeDcQeDvux5WGdo2m0Ner0uzG7BwEDcvKbbF1RX+bMjItkMUpdjuC4wG5iqlbgCSgUoYo+K/AJO01lvtF1GYrbB+ulhbnzsEIdLxsUJTymgBeP5wwZcyjcRJPAR0A+YVvF6fBzwxNp+R1+uKxNXTKTe1EU7i4si2FNuOUJRNbbKAyUqpzhgv1geAFVrrBHuHE86hcGTbxaKMd8MX4iEg1ORUwnQB9S/O2S6cYiTTSEz3A/AHMBQ4BVQFMrXWyaamEo7nVklGtsXVyci2QxWnG8mnGKMkPYAVSqlFSilf+8QSzuR/ppEkGSOZBEqxXeEF1IfzR8Ca//c0Eim2zdYY2IxRcL8D5EqhXUG5eVWI1n+ihGTOtkMVp9gOwXgBf1BrHYGxK9jL9gglnIv10m4kScYcXSm2BQE3QH4OXDgp00ichNY6R2v9MdCEv7dSf1Up5WdyNOFobp7GdtxCXImMbDtUcYrthhito+KUUluB3sBtSqmeSilpS1GOWS/ts12wIM5ejd9FGeJf3/iYdOjvaSRSbTsFrXWW1vpdoDmQCWxWSj1tcizhSLJAUlzLxWJbRrYdoTjF9kda6wFa6wYYU0m+AryAYVy2Kl2UL/8zjeT8EagUAJ6VzQ0lzFf4huv8YZlG4mSUUiFKqb7Aff/f3p3HR1Wfix//PDPJJISwJuxhU9kFURAXXFC0olfFWheu+/ajtbWt19p7aXtbba+12nrVVm2r4i7WBaviihtedwUUkaUsIktYQ4BACFnn+/vje04ymcwWMhs5z/v1ymtmzpyZeXJmcvKcZ57v9wADgD3ArZmNSqWVDpBUsegAybRK+HTtwBsi0g34F7AC2xv4D2PMT1MSmcoaTW0kArvWQbeBmQ1IZYfO/cCfBzvW4OutbSTZQEQWA/2A9dh99XLgHeBeYGUGQ1PpltsB9mzOdBQqW2llO60STraNMUeIiB+bZA8D3gbeSFVgKns0tpH4gJ3roM+YzAaksoPPZw+8dqxpnINdT9eececA3+rZGpUOkFQxac92WrWmso0xpgFY6vwoj2hsIzFB2LUeRpyV4YhU1uh+EOxYaw/EAM3xMssYsybTMagsoQMkVSxusp2Tn9k4PKI1PdvKoxqcBCp331YI1mkbiWrSbbAdIOncbNBkW6nsoAMkvWXNe/ab50TVVdnPiJ4JOi002VZxuZ0BObudsz931WRbObofBHV7yakuB7RnW6ms0doBkuXfwIOT4evZqYtJpcbqd+DxqXDPEbDu48QeU7dPW0jSSJNtFZfbGpBT4STb3QZlLhiVXbrb6f8Cu9cC2kaiVNbILbDz4Acb4q8bDMKs82DjAnjpR1C2IvXxqeQIBuHVG6DoEHt79duJPa5uH+Rosp0ummyruNxBbzm7NwACXUoyG5DKHs70f3m77deXOkBSqSyR6/TiJtJKsvNb2LEGTvpv8OXA//0xtbGp5Fn/sZ2Sd9IvbFvf9gQnHaqr0sp2GmmyreJy8yd/xTo73VtOXmYDUtmjSwkg5O7ZAGgbiVJZw53SLZFke/Miezn0NBh3BSz9Z+v6f1ViKjZC7d7kPufXz9n3etjp0GMYbF+V2OO0jSStNNlWcbmtAb6K9To4UjWXkwed+5HjVLb1pDZKZQk3kUpk+r/NX4E/AD2Gw4TpYIKw7KXUxuc12/4Fdx8Kt/aFTYuS85zBBlj+sk20Ax2heIjtvW+oj/9Yd4CkSgtNtlVcbmuAr2KdDo5ULXUb6LQY6enalcoa7pRu9QlM/7f5K+g5EnICtqDScySsejO18XnNktn2IAZg2YvJec6NC6GqHIadYW8XD7Uzhu1cG/+xWtlOK022VVxBAwHqkD1btLKtWuo6kJwKt7Kd4ViUUlZjG0kCle2tS6H3oU23h3wH1n8C1RVtj2PzV7D2I/Dyt17GwNIXYPCJMPgEWDk3Oc+7ci6IHw6ZbG8XD7WX5Qm0ktTt08p2GmmyreIKGkM/2Y5gtLKtWuo2CF/lFgLUaRuJUtmicYBknMp2sAH2brfjcVyHTIZgPaz/NPJjls2Bv02E56+xj41mbzk8djY8ega8cn3r4j9QBIOwfXXsg4nyb6B8NYw8G4ZOgW3L7Ani2mr129D/KOjQzd7u1MdeVm6N/1gdIJlWaU22RWSKiKwQkdUiMiPC/Xki8oxz/2ciMijs/gEiUikiN6YrZmVbA/rLNntDK9sqXLeBCPaATJNtpbJEopXtqh2AgYLipmV9jwDx2TaFcHu2wvNX2/aUZS/BI6fD7s2Rn/v9P0HNHhh5Dix8NPE5oJNl40K4/wTndwzx0Z/h43va/vx11fDspXDvOJg5Gfbtirze2g/spVvZBlj/WeR1131it1MwGPu1a6tgy9cw8NimZQXd7WVVeQKxaxtJOqUt2RYRP3AfcDowEvh3ERkZttrVwE5jzCHAXcDtYfffCbye6lhVc0EDA9xkWyvbKpzzmRgg27RnW6ls0ThAMs5sJFVOZbpjUdOyvELoMSJysj3/QWiog4uehUtfhN2bbOW6sqzluqvm2kruOX+Dwt7w4V0t11n6gj2ZTnhCHEt1hU1Kaypjr/fWTbaNZfNXYct/A2/+d+xts3MtfPlk7KR34aPwr1dg/FV20OPcX0Zeb91H0LGnnQu7xwh7ILRxQcv1Ni6Ex860BzAzJ8Mn90H17sjPuelLMA1QcmTTstwOkNsxsW1Zr20k6ZTOyvYEYLUxZo0xphZ4Gpgats5U4DHn+mxgsog9l6iInAN8CyxNU7zK0WAMJVKG8QeavqZSyuWc5Ki/bKNBc22lsoN7wpJ4AyTdNpDQyjZAvyNs8hf6bZUxNgEdehoUHQyDJsIlzjSBH/857HnL7dzd/SdAoADGXmTPdLhnS9M6tXvh9Rk28XzvtuaPX/kmLH3RJuOhveMbPod7j4RHpsB9E5rir9rRPHn+9v2mivKukGkMQ3+flXNtG82Hd9uWmPJv7PJgAzx7mT3Bzz+vgfqaltutoc4mwwOOgTPvgmN/DItmtZx6zxjbsz5ooj01uj8H+h4OpfNbPucrN0BhL/t8lVtt8v7P6ZFbVNzHhybbYA+aYrX2uLSynVbpTLb7ARtCbpc6yyKuY4ypByqAIhEpBP4L+G2sFxCR6SKyQEQWlJVFOMpW+8UYwwDZhnQdAD5t81dhCnth/Hn0l216BkmlskWiU/81VrbDku2SI2HfzubJ45bFsGczjDi7admAo2DUObDg0eZJsVu5LRlvL8deZCuxi59tWue9P0DlFhh4HHx+P8yfaZdvXgz/uBCeuxyeuwLe+R+7vPwbeOoCO83d2ffC3jJ49Wc2Ob7/RLhjKHz+oD3l/Nu/hU597Ul6QucM37ez6fr8mTDv9/D2TTaxf/GHtpI9/yFbDR9xFix5Hp44t/njjIE3fgEV6+G4G+yyY35kX2vho823485vYc8mGDixaVm/cbYFJDSJL//Gznd+zHW2Un7DMphyO6x8HZ65pGW1unS+PYlN6DcSAAVF8dtIgkF7EKaV7bQ5UDKnm4G7jDExvzMyxjxgjBlvjBnfo0eP9ETmAUFj6C9l2kKiIvP5MF0H0F/KtGdbqWzRmGzvZ2Xb7S3+5t2mZSvnAgJDTm2+7oTpULun+anCS+fbvu++h9vbxUNsAr/oKZusrv8UPr7XJpaX/hOGnm4T51dusEl2h+5w+cu2crzoKVvRnnWefc5LnocjLrXV5GUv2ep3xXqo2Q2v3Wh7yjcugBNutAM/QwcjVjg1v0HH28r3B/8Lh18KZ/0ZNnxqE/B3/wcOOgkueALOnQkbPoO7x9gKONjHzH/Qvv7Q79hlhT1tcr5oFtTXNr3e2o+c1zuuaVnJeGiotQm3y53XfMRZTcuO+j585xa73UN7z4NB29cd2q/tSiTZrne+AdDKdtqkM9neCPQPuV3iLIu4jojkAF2AcuAo4I8isha4HviliFyX6oCV1RCEEinTwZEqKtN1oG0jiTOmRymVJglXtp3EzB1c5+o+2PYYhybQK+fa9pLCns3X7TceAp3g2w+alq3/FHodaqvQrrEXQdlyKF0Ab8yAzn1tMpmTBxc8DqPPhwUP2/moz3/UJvxn/MlWYR861faFT/sHdD/IPt/hlwDGJtc5+fBf6+DGVfDDz+D/zbOJfLeBzdtIKkrt5eSb4OCT4ahr4cy74bBp0HUAvP6f9vXOuMO2fYw5H66ea5P+t2+CZy+3yfiYC+GU3zXfDoddZCvgq99qWrbuI5sA9xjefHuB3Q6uZS/ZinfXkDRJxCb0l71kDxK+fNIuL1sO+3Y0r5a7Coqavq2Ipk6T7XRLZ7I9HxgiIoNFJABMA+aErTMHuNy5fh7wrrGON8YMMsYMAu4GbjXG3JuuwL0uULeHblKplW0VXdeBdoCkVraVyg45CQ6Q3Lsd8ruCP7flfYecaqu/tXttortxoR3wGM6fY6usbo90fY2tbIdWcwFGnWunqXv8bDvA75Sbm5LxnAB8byb8ugx+/CUMPt4u7z0avv9/Nvm9br5tW3F1P8hWqMHOeNKhqz0Q6DncHhSI2P9bzSrbTrLdfTBc+gKcfpuN3+eHcVcCBiZeD8WHND2m3zi48EkYOdUmxUOnwNT7WrZVHnySTXbdVplg0B6ADDzWxuLq0s+2uLitNjvX2RaS0PacUIMmwoBj7YFIMBi5Wu4qKI4/QNI9ANNkO21y0vVCxph6pxo9F/ADDxtjlorI74AFxpg5wEPAEyKyGtiBTchVhnWucb6A0Mq2iqbbILpIFYHaJJwEQynVdj4f+POaWgaiqdresl/bNfJs+OxvIaduN/aEN5EMPt7OPlJRapPb+uqWyWCHrjDtKXjmUtviMfr8ls8TKenvPdr+RDLtKdu73W1w5Pu7DrSDDd0BgRUbbBW8oKjluhOm2z7mcVe0vC8nYKvvVTsgv4tNziPFPvoC24qye5NtE9ldCqdGGG5WMq5pkONyp+44MkqyDTDhGph9Fax4Ff71sq3CR/qfXNAdaitt+5A713o4rWynXdqSbQBjzGvAa2HLfhNyvRqI8NfXbP2bUxKciqrzvk32ijPrhFLhxNnpd9y3CRiX2WCUUlZuh8Qq2+H92q4Bx0D3g21FFexsVH0Oi7zusDPsdHpfPe28ptjHhxt4LPx8dfNKb1vkd7Y/0XTqZS/3ltkEtaLU9nFHev28Qjj6B7FfL7zdJtzRP7CDPT+5z04H2KmvrYiHG3Q8LH8ZZp1vq98lRza1x0QyYqo9oHjmEnv7O7dEic85iKgqtxX0SBor2zpAMl3SmmyrA1PXxsp2lMqB8jw32e60L3wYhlIqYxJJtqvKoyd5InaWjVedGTe+91D0JLnoYJtAzn8IqnfZ5DtaYpqsRDsRbkJZ6ySY1RXxE+a26DbI9nN/4nS6nnFH5Gr9+Ktt9fvr2bY3fep9sZ/XnwOn/d5OBRgotP3okbjfUsRMtrWynW6abKu4utWUsotCunbomulQVJaS7vZArHO1JttKZY1Eku19u2x7RzRHXm37m/dshdHnxX6u438GT18Evlw444+tjzcV3J7wur3OZRrmlz7jDnt69oJim1RH4s+x7SWRWkyiGf5v8J9rbE986MDTUB0SOIukVrbTTpNtFVe3mo1spBeaaquo8ruwy3TUZLudE5HuwDPAIGAtcIExZmfYOmOBvwGdgQbg98aYZ9IbqQLsIMl4J7Wp2Q15XWKvc/DJib3ewSfBjA32NfMKE3tMqoVXtuuqID/FJ2fLK4SrnRlJkl3Fz8mzP9HkO+9lTZQzT4JWtjPgQJlnW2VQt5qNbJRemQ5DZbkNphddqjdlOgyVWjOAd4wxQ4B3nNvhqoDLjDGjgCnA3SKix+qZkNsh9tR/DfV2MF2snufW8udkT6INIZVtJ8GsrUpPkimS3nYZl/teRjvNO4Qk21rZThdNtlVsDXV0rd3KRumd6UhUltsoPehSo8l2OzcVeMy5/hhwTvgKxpiVxphVzvVNwDZAzzKWCfHaSNzqZ36cyvaBzE0om7WRtOMk030vq2PMDKWV7bTTZFvFVrEBPw1slBR/7aYOeBvpaZPtoJ7Zph3rZYzZ7FzfAsT8yktEJgAB4JtUB6YiiJdsuwlZXhIr29kmEKGNpD0n24FOgGgbSZbRnm0V2861AGzyaWVbxbaRXuSYOtizOfooeJX1RORtINIf/K9CbxhjjIhEPYuRiPQBngAuN8ZEPQITkenAdIABAwbsV8wqCq1sQ67bRuIm22kYIJlJPh/kdYrTRqIDJNNNk20V245vAdjs12RbxbbBbTXasUaT7QOYMeaUaPeJyFYR6WOM2ewk09uirNcZeBX4lTHm0ziv9wDwAMD48eP1FKTJlNMh9klt3Mp2Mnu2s42bWNfutd+61bfzNhKwB0/x2kjEB/5A+mLyOG0jUbHt/JY6yWWHRDjbllIhNkhfe6V8dWYDUak0B7jcuX458FL4CiISAF4AHjfGzE5jbCpc3DYSL1S23Z7tqqYDj/Zc2QbbFhSzjaTKHohlYgCnR2myrWLb8S3luX0Qn35UVGxlUkydBDTZbt9uA04VkVXAKc5tRGS8iMx01rkAOAG4QkQWOT9jMxOux+UWaM+2z2cTy9q9Tdsi2hzV7UV+5/iV7fZ+wJFltI1ExbZzLWW5ffH59AhYxSY+P9tz+tOnXMfCtVfGmHJgcoTlC4BrnOtPAk+mOTQVSW6+TayMiVzF9ELPNthBknVVIb3K7TzRzO9iz04ZTXufkSULablSRWcM7FzL9ty++PTrJhWHT2B7XolWtpXKFrkdwDRAQ13k+71Q2QY7SLJun3dm4UikjaS9b4Mso8m2im7vdqitZJu/L1rYVvH4RCjL7Q87v7Uny1BKZVaOk1BFGyRZvRsChfZENO1ZoMBpI/HILByJDJDUZDutNNlW0W1fCcDm3H5a2VZx+X1CWaAEgvWwa12mw1FKuQlVtL7t6or2X9UGp3e9qmmu7faeaOZ3tgdSJsrkPu19rvEspMm2im77CgA25QzQZFvFJQJbAyX2xo41mQ1GKRU/2a6paP/92mATy9qqkDaSdj5AMq+zbR9yK/nhtLKddppsq+jKVkJuAdt9xfi1j0TF4fcJW3OcZFv7tpXKvEQq2+15jm1XoMCert1LAyQheiuJJttpp8m2im77CigeQgM+7dlWcflE2OPrYnf0mmwrlXmNc0zH6NnO65S+eDKlRWW7nSea7gFUtLNIeuHEPllGk20VXdlKKB5G0BhE20hUHCIQBCg6RJNtpbJBTr69jDZAsrbSG8l2oGPY1H/tPNF0+/Br9kS+XyvbaafJtoqsphJ2l0KPoQSN0TYSFZdfhKAx0P1g0Lm2lcq8eJXtGo8k27lhs5EE2nmyHSi0l7VRku1aHSCZbppsq8icmUgoHkYwiLaRqLh8IgSDQPFQqNhg/5ErpTKnsWc7ykC5mj0Q8ECyHXDOpOmZyrbznkaqbBtjv9Fo72fRzDKabKvI3GS7h7aRqMSIYCvbvUbZBduWZzYgpbzOreDWRki2gw120GBeYXpjyoTcjtBQY5NPXw74czMdUWq572mkgkd9jZ2ppL1X97OMJtsqsrIVdqfU/SDbRqLJtorD75PmyfbWJZkNSCmvc6e4q9vb8r5aJxHzQhuJm3zu3d7+q9rQ1LNdGyHZbmyl8cBBVhbRZFtFtn0ldD8I/LkEDfj0k6Li8IkQNEDXAfar6a1LMx2SUt7mtgrURki23aqnF5Iu94Cicqs3Bga672mkU7a7Cbi2kaSVplAqsrIVtvcW2xqgJ7VR8fjcNhIR6DVSk22lMi03RhuJpyrbzu+4e5M3ksycAPgDkdtI3AMvL1T4s4gm26qlun32DIA9RwAQDGqyreLz+YSGoHN64F6jbLId7XTBSqnU8/mcmTgiJF3u4DkvJdu7NkB+18zGki55nSIPkKzVNpJM0GRbtbR1qR1A0ecwANtGorm2isMn0pRb9xplTwVdUZrRmJTyvEDHKG0kTiLmhaQrzzmjYu0e6OCRZDtQGPkgq7GNRCvb6aTJtmpp05f2ss9YABq0sq0S4BNCKtuH2kttJVEqs3ILIk/958U2Emg6lXl7l9c5dhuJF9ppsogm26qlzYugQ3foUgI4Pdta2lZx+NyT2kBjC5LOSKJUhgUKY1e2vTD1nyeT7cLIAyR1NpKMSGuyLSJTRGSFiKwWkRkR7s8TkWec+z8TkUHO8lNFZKGIfO1cnpzOuD1n01fQd6wd6IZtu9VcW8XTrI0kv4udlWTbsozGpJTnRW0jcdsJvFbZ1jYSQAdIplnakm0R8QP3AacDI4F/F5GRYatdDew0xhwC3AXc7izfDpxljBkNXA48kZ6oPaiuGsqWN7aQADTo6dpVAvw+oSF0QGSvQ2Hz4swFpJSyvbkRk22n6umFNpJAIeD8D/NMZTvaAEltI8mEdFa2JwCrjTFrjDG1wNPA1LB1pgKPOddnA5NFRIwxXxpjNjnLlwIdRCQvLVF7zbalEKy3lW2HnkFSJaLxDJKufuOgfBVU7chcUEp5XaAwes+2LwdyPPCv1OdrOqjwTLJdGKVn220j0WQ7ndKZbPcDNoTcLnWWRVzHGFMPVABFYet8D/jCGFMT/gIiMl1EFojIgrKysqQF7imbFtlLZyYScNtINNlWsTWe1MbV/yh7WbogI/EopYgx9V+lTUC9sm93k22vzEaS1zl6G4k/0P5PWZ9lDqgBkiIyCtta8v1I9xtjHjDGjDfGjO/Ro0d6g2svNi+yPW1dBzYuagga/B7ZH6v95/cJwdBsu98RIH7Y8FnmglLK62JN/eeFfm2Xewpzr1S23Z7tYLD58tq9WtXOgHQm2xuB/iG3S5xlEdcRkRygC1Du3C4BXgAuM8Z8k/JovWrTl80GR4KeQVIlxhfeRhLoCL0PhdLPMxeUUl4X6Bj9DJJemInE1dhG4pXKtvPehle366ogV5PtdEtnsj0fGCIig0UkAEwD5oStMwc7ABLgPOBdY4wRka7Aq8AMY8xHaYvYa6p323mR3a//HfYM3Jpsq9gkvI0E7GepdCE01GckJqU8L9AR6va2rHBWVzRVe73Ac8m28/uGD5KsrdTKdgakLdl2erCvA+YCy4FnjTFLReR3InK2s9pDQJGIrAZuANzpAa8DDgF+IyKLnJ+e6YrdM0o/BxOEAcc0W9wQNPgPqIYjlQl+CWsjASiZYP/Rb9OT2yiVEW5iVb+v+fKa3ZDvxWTbI20k7oFU+Fzb2kaSETnpfDFjzGvAa2HLfhNyvRo4P8LjbgFuSXmAXrfuE9tjW3Jks8XaRqIS4fOFtZEA9J9gLzd83mzQrVIqTdz5lMOTrOrdUDQkMzFlQr7Herbd37c6PNmu0mQ7A7ReqZqs+xj6jGnRx6dT/6lEiITNsw32xDaFvWH9p5kJSimvC0Tp3fVaZbuwNxQUQU4g05GkR55zUNGisq1tJJmgybayaiqhdD4MPrHFXUGDtpGouPyhZ5B0icBBJ8KaeS17RpVSqecmVqGDJI2xFU8v9WxP/Alc/Vamo0gft4JfXdF8eW2lnj0yAzSFUta6jyFYBwef1OIubSNRiWgxG4nrkFOhqtzOdKOUSq9ASBuJq77a7u+9VNnO6wRFB2c6ivRpbCMJS7Zr9njrfc8Sae3ZVllszTzIyYf+R7e4qyGoybaKz+cTGlpMRwIcMhkQWPUmlIxLe1xKeZo7l3ZtyKwUbh9vEirbdXV1lJaWUl1d3ebnUpCfn09JSQm5uW086Uy0AZJe+0YjS2iyrexXiivfgIETITc/4t2abKt4fJHaSAAKuttBt6vehJN+kfa4VHKJSHfgGWAQsBa4wBizM8q6nYFlwIvGmOvSFaMKEWmgnJuAJWGwYGlpKZ06dWLQoEE6tqeNjDGUl5dTWlrK4MGD2/ZkuR3Al9P8fa+vgYYarWxngLaRKNi+CnasgeFnRLzbtpGkOSZ1wInaRgIw5Duw6QuoLEtvUCoVZgDvGGOGAO/QNEVrJP8DvJ+WqFRkkSqcSaxsV1dXU1RUpIl2EogIRUVFyfmWQMS+v6FtJI3vu0dmZMkimmwrWOHMxjj09Ih323m2dUeqYvNHayMBGHKqvVz9dvoCUqkyFXjMuf4YcE6klURkHNALeDNNcalIIla2K5rf10aaaCdPUrdlfpfmB1mN32hoZTvdNNlWsOxF6Hs4dOkX8W49g6RKRMQzSLp6j4FOfe1nTR3oehljNjvXt2AT6mZExAf8L3BjvCcTkekiskBEFpSV6TcfSRcoBPGlrLKtslh+58jtQ/q+p50m215X/o2dJeLQ70VdRdtIVCJ8YnsOI9/pgzEXwKq3oHJbegNTrSYib4vIkgg/U0PXM/YNj/Sm/xB4zRhTGu+1jDEPGGPGG2PG9+jRI0m/gWokYmfiiNiz3T6SrmOPPTbuOtdccw3Lli0D4NZbb2314wsLC+Ouk3XyOkc+yGon7/uBRJNtr/t6tr0c9d2oqzQYbSNR8fkjndQm1GH/DqYBvn4ufUGp/WKMOcUYc2iEn5eArSLSB8C5jHT0dAxwnYisBe4ALhOR29L2C6jmoiVd7aTC+fHHH8ddZ+bMmYwcORJomWwn8vgDUn6X5j3bjZXtTpmJx8N0NhIvCwbhyyftiWy6lERcxRijbSQqISJCMGofCdBzuG1X+uofcMyP0heYSrY5wOXAbc7lS+ErGGMudq+LyBXAeGNMrIGUKpXyorUTJDfp+u3LS1m2aXf8FVthZN/O3HTWqJjrFBYWUllZyXvvvcfNN99McXExS5YsYdy4cTz55JOICJMmTeKOO+5g9uzZ7Nu3j7FjxzJq1ChmzZrV+PjKykqmTp3Kzp07qaur45ZbbmHq1KkxXzur5Xdp/r63s4OsA4lWtr1szTyoWA9HXBZ1FTd30sK2iscXq2fbddhFsOVr+6MOVLcBp4rIKuAU5zYiMl5EZmahU4D2AAAWWElEQVQ0MhVZfoTKdqAT+PyZiylFvvzyS+6++26WLVvGmjVr+Oijj5rdf9ttt9GhQwcWLVrErFmzmt2Xn5/PCy+8wBdffMG8efP42c9+Fr017kAQ/o1GEqd8VK2jlW0v+/wBKCiG4WdGXcWdys2vlW0Vh98XY+o/1+jz4K3fwGd/h6n3pScwlVTGmHJgcoTlC4BrIix/FHg05YGp6PI6w57NTbdrdqekbzdeBTodJkyYQEmJ/aZ27NixrF27luOOOy6hxxpj+OUvf8n777+Pz+dj48aNbN26ld69e6cy5NTJ72zPGBkM2nEz1dpGkila2faq7avsiWyOvCbiiWxcbvLk09K2isNWtuMk2wXd7TcpXz0NuzakJzClvC68sl21w/4ttkN5eXmN1/1+P/X19Qk/dtasWZSVlbFw4UIWLVpEr169DuwzY+Z3BQxU77K3a3ZDbgH423h2StVqmmx71Qf/CzkdbLIdQzBoL/UMkioen08aPy8xHftje/nxPSmNRynlCO/ZriqHgqLMxZNhubm51NXVtVheUVFBz549yc3NZd68eaxbty4D0SVRx2J7WbXDXtboqdozRZNtLypbCYufgSOvhsLYU201VrY111ZxxDyDZKiu/WHMNPjiMdizNfWBKeV1bmXb/fus2u7pZHv69OmMGTOGiy++uNnyiy++mAULFjB69Ggef/xxhg8fnqEIk8T99qJqu72s3q0tJBmiPdteYwy8/nM7OGbi9XFXb0q2NdtWsSXURuI6/gZ7wPfWb+Dc+1MbmFJel9cZgvVQXw25HZzKdnGmo0qayspKACZNmsSkSZMal997772N1997773G67fffju33357i8cXFxfzySefxHyNA4r7HleV28sU9eqr+LSy7TVLX4A178HkX8etakNIG4mWtlUc7mwkCY3eLzoYJv4UFj8N336Q+uCU8rLQU7Y31Nm5lz1c2fYM9z3e61a2K3QmkgzRZNtL9u2Eub+EPofB+KsSeoi2kahEud9+JDxT1gk3QteB8OrPoG5f6gJTyuvynASrusL+H4B2O0BShXCTbbeyXVkGHXtmLh4P02TbK4JB+Of37RHumXcnPL+qtpGoRLkHZAm3kuR2gDPvgu0r4NUbW5GlK6VapbF3t7ypyqmV7fYvUGBnH6kqt/vXyq0JfaOtkk+Tba94/0+wai5M+QP0OyLhhzXo1H8qQe5nJOYp28MdMhlO+E9Y9CQseDhFkSnlcYW97GXl1qYqpybb3lBQZN/zmt3QUNP0WVBppQMkvWD+Q/DerXYGiDhT/YUzegZJlaBWt5G4Js2ATV/Caz+3FbhR301+cEp5WWOyva1pWcf2M0BSxVDQ3SbblWX2traRZIRWttu7zx+EV2+AIafB2X+BVraDNAS1jUQlxj0ga4h7zvbwB/rh/Eeg/wSYfTV8PTv5wSnlZQVFIH6o3KKVba8pKLatQ3udAy1tI8kITbbbq/paeOU/4LUbYegUuPAJyMmL/7gwerp2lSi/k20n3LMdKq8TXPwc9D8Knr8a3v4tBBuSHKFSHuXzQWHP5m0kHbw7QPLuu++mqqqq8fYZZ5zBrl27En78nDlzuO2229ocx6RJk1iwYEGbnycmt43E/VZDK9sZocl2e7TxC5g52fbAHvsTuHDWfiXa0NQSoLm2ikfETbb38wnyOsGlL8ARl8OHd8IjZ8C25ckLUCkvK+xpE649W+z0bzmBTEeUMeHJ9muvvUbXrl0TfvzZZ5/NjBkzUhFa8nUshr1lTcm29mxnhPZstyc7vrWnYV80yx69XjgLRpzZpqd0WwL82rSt4vC7s5Hsd7YN5ObbdqeBx8Ibv4C/HwfjroTj/gO69EtOoEp5UWEvW9kO1kP3g1LzGq/PgC1fJ/c5e4+G0+NXke+8804eftgOsr7mmms455xzmDJlCuPGjeOLL75g1KhRPP7448ycOZNNmzZx0kknUVxczLx58xg0aBALFiygsrKSKVOmcPTRR/Pxxx9z5JFHcuWVV3LTTTexbds2Zs2axYQJE3j00UdZsGAB9957L2PHjm2MYcWKFbzxxhuMHz+eH//4xyxZsoS6ujpuvvlmpk6dyr59+7jyyiv56quvGD58OPv2pWHK0+4HQV0VbPkKxKdTPmaIJtsHuoY6WPWWTbBXvmH78o76gR10loTJ63XqP5UoX1vaSMIdNg0OORXe/R0sfMSe2n34v8HYi+Ggk8Cvuy6lWqWwp02Eqyug37hMR5NUCxcu5JFHHuGzzz7DGMNRRx3FiSeeyIoVK3jooYeYOHEiV111FX/961+58cYbufPOO5k3bx7FxS0Hia5evZrnnnuOhx9+mCOPPJKnnnqKDz/8kDlz5nDrrbfy4osvNlt/0aJFALz88sv88Y9/5Nhjj+Wmm27i5JNP5uGHH2bXrl1MmDCBU045hfvvv5+CggKWL1/O4sWLOeKIxGcG22/FQ+3l2g9t/3aC0/6q5NL/WAcaY6CiFNZ+AN/Mg2/esf1YHXvC0T+0P537JO3lgtpGohLU5jaScB2L4Kw/w/E/g0/+ak/vvvQF+1k/+GQ4+CQYdDx07qsfUKXiKewFezYDAqPPT81rJFCBToUPP/yQ7373u3Ts2BGAc889lw8++ID+/fszceJEAC655BL+8pe/cOONN8Z8rsGDBzN69GgARo0axeTJkxERRo8ezdq1ayM+ZtWqVfz85z9n3rx55Obm8uabbzJnzhzuuOMOAKqrq1m/fj3vv/8+P/nJTwAYM2YMY8aMScavH1uPYfZy51ronYbXUxGlNdkWkSnAnwE/MNMYc1vY/XnA48A4oBy40Biz1rnvF8DVQAPwE2PM3DSGnn4N9bC7FHaug13r7Yk/tnwNmxfDvh12nY49bNJx6Hl2vmJ/btLDaBwgqW0kKg6/JLGyHarrAPtP/NTf2bnil/wTVr1pT/UOtlrTe7T9KR4K3Qbax3Qu0Qq4Uq5ObhHGpK6NJMtI2EF4+O1I8vKaxjf5fL7G2z6fj/r6+hbrV1ZWcsEFF/Dggw/Sp4/dxsYYnn/+eYYNG9aW8JMjtEf7oEmZisLz0vafSET8wH3AqUApMF9E5hhjloWsdjWw0xhziIhMA24HLhSRkcA0YBTQF3hbRIYaYzIzXYExtn0jWB/5p6HOzqQQrIeGWnsq6rq99rK2yl6vrbJ9VLV7oXqXrU5X7bBT9FSV24TaBJte0x+AniPsV+m9x8CAo6HXoXaUeQppG4lKVKvPINlaOQEYcZb9CQZtD+KG+bBlsf357O/2780lPujQzSbjBUW2V7GgCDp0hUChPbNaoCDkekd7mRMAX679m/PnOJcB8LnXc+39Kf7bUyqpDj656Xo7S7aPP/54rrjiCmbMmIExhhdeeIEnnniCn/70p3zyySccc8wxPPXUUxx33HEAdOrUiT179kRsI2mtq666iiuvvJLjjz++cdlpp53GPffcwz333IOI8OWXX3L44Ydzwgkn8NRTT3HyySezZMkSFi9e3ObXjyv0f/fIqal/PRVROss+E4DVxpg1ACLyNDAVCE22pwI3O9dnA/eKPRSdCjxtjKkBvhWR1c7zfZLMAD996EZKNr6K39TjJ4ifBnKox2/sdT/u8mD8J0tQHTlUSkcqpDO7pTO7pIjdMphduV3Y6uvBFl8vtvh6USbFNOzNgW+wPx/tBD5IWhzR1NTb31UL2yoe94Dsogc/IydtH5hhzs/5+Avq6WG20zu4lV7BbfQObqNLXQVddu2my87ddDaldDUVdDR7CdCyQtVa9SF7hAZ8BMVHA36CNF0GxceGvqdz9DV3tfn1lGqTooObrrezZPuII47giiuuYMKECYAdINmtWzeGDRvGfffdx1VXXcXIkSO59tprAZg+fTpTpkyhb9++zJs3b79fd926dcyePZuVK1c2Ds6cOXMmv/71r7n++usZM2YMwWCQwYMH88orr3Dttddy5ZVXMmLECEaMGMG4cWnqnR91Liz9J/RNQ4+4ikhMqqpQ4S8kch4wxRhzjXP7UuAoY8x1IesscdYpdW5/AxyFTcA/NcY86Sx/CHjdGDM77DWmA9MBBgwYMG7dunWtivGz5+/G9+3/ERQ/DZJj/2GKnwZx/qE2XubY5SHX3fubHpNj03LxUyt51Pg6UOvLp0byqfXlUyv51PjyCUr2f82dn+vnl2eMoLhw/6YPVN6wcdc+/vTGv6htSN7BaKr4TD15wWoCZh95wWrygvsImGrygtX2YNvUk0MdftOA39TZ28ZJr009OabpPh9BfKbBubSptr3dgM8EaRh4HEef/7NWxygiC40x41Pw62et8ePHm5TPO+xlW5fCsjl2AH2Svq1cvnw5I0aMSMpzJdPatWs588wzWbJkSaZDabWkb9MG51v2QEHynlO1EGufnf2ZXisYYx4AHgC7027t44/63vXA9ckOSylP6Ne1A3dPOzzTYSilouk1yv4ob/Hn6PiVDEtn0+FGoH/I7RJnWcR1RCQH6IIdKJnIY5VSSimlGDRo0AFZ1VbtUzqT7fnAEBEZLCIB7IDHOWHrzAEud66fB7xrbJ/LHGCaiOSJyGBgCPB5muJWSimlVBTpakf1At2W7VPavlcwxtSLyHXAXOzUfw8bY5aKyO+ABcaYOcBDwBPOAMgd2IQcZ71nsYMp64EfZWwmEqWUUkoBkJ+fT3l5OUVFRQlNraeiM8ZQXl5Ofn5+pkNRSZbWJh5jzGvAa2HLfhNyvRqIONu+Meb3wO9TGqBSSimlElZSUkJpaSllZWWZDqVdyM/Pp6SkJNNhqCTTjnmllFJK7Zfc3FwGDx6c6TCUymp6VgallFJKKaVSRJNtpZRSSimlUkSTbaWUUkoppVIkbWeQTDcRKQNadwpJqxjYnuRw9pfG0lK2xAEaSyTZEgcc+LEMNMb0SEUw2Wo/99sH+vucKhpLS9kSB2gskWRLHJDkfXa7Tbb3l4gsyJZTJGss2RsHaCzZHAdoLF6RTdtWY4ksW2LJljhAY8nmOCD5sWgbiVJKKaWUUimiybZSSimllFIposl2Sw9kOoAQGktL2RIHaCyRZEscoLF4RTZtW40lsmyJJVviAI0lkmyJA5Ici/ZsK6WUUkoplSJa2VZKKaWUUipFNNlWSimllFIqRTyZbIvI+SKyVESCIhJ1ahcRmSIiK0RktYjMCFk+WEQ+c5Y/IyKBNsTSXUTeEpFVzmW3COucJCKLQn6qReQc575HReTbkPvGpioOZ72GkNeaE7I83dtkrIh84ryPi0XkwpD72rxNor33IffnOb/nauf3HhRy3y+c5StE5LTWvnYr47hBRJY52+AdERkYcl/E9yqFsVwhImUhr3lNyH2XO+/nKhG5PA2x3BUSx0oR2RVyX9K2i4g8LCLbRGRJlPtFRP7ixLlYRI4IuS+p26Q90332/sfirJfS/bbus1sVh+6zvbjPNsZ47gcYAQwD3gPGR1nHD3wDHAQEgK+Akc59zwLTnOt/B65tQyx/BGY412cAt8dZvzuwAyhwbj8KnJeEbZJQHEBllOVp3SbAUGCIc70vsBnomoxtEuu9D1nnh8DfnevTgGec6yOd9fOAwc7z+FMYx0khn4Vr3ThivVcpjOUK4N4on9k1zmU353q3VMYStv6PgYdTtF1OAI4AlkS5/wzgdUCAo4HPUrFN2vsPus9uUyzRPvPJ2i6JxIHus911dJ/twX22JyvbxpjlxpgVcVabAKw2xqwxxtQCTwNTRUSAk4HZznqPAee0IZypznMk+lznAa8bY6ra8JrJiKNRJraJMWalMWaVc30TsA1I1tn2Ir73MWKcDUx2tsNU4GljTI0x5ltgtfN8KYnDGDMv5LPwKVCyn6/V5lhiOA14yxizwxizE3gLmJLGWP4d+EcbXi8qY8z72EQqmqnA48b6FOgqIn1I/jZp13SfnbRYGiV5u+g+O8E4dJ/tzX22J5PtBPUDNoTcLnWWFQG7jDH1Ycv3Vy9jzGbn+hagV5z1p9HyQ/h75+uOu0QkL8Vx5IvIAhH51P1alAxvExGZgD1a/iZkcVu2SbT3PuI6zu9dgd0OiTw2mXGEuhp7RO6K9F7tr0Rj+Z6z3WeLSP9WPjbZseB8RTsYeDdkcTK3SzzRYk32NlHe22e3JpZU77d1n514HKF0nx2mve6zc5ISWhYSkbeB3hHu+pUx5qVsiSX0hjHGiEjUuRido6vRwNyQxb/A7twC2Hkh/wv4XQrjGGiM2SgiBwHvisjX2J1WqyR5mzwBXG6MCTqLE94m7YWIXAKMB04MWdzivTLGfBP5GZLiZeAfxpgaEfk+top0cgpfLxHTgNnGmIaQZeneLioBus9OaSxt3m/rPju5dJ8dVbvcZ7fbZNsYc0obn2Ij0D/kdomzrBz7tUKOc3TsLt+vWERkq4j0McZsdnZC22I81QXAC8aYupDndqsJNSLyCHBjKuMwxmx0LteIyHvA4cDzZGCbiEhn4FXsP+NPQ5474W0SRbT3PtI6pSKSA3TBfjYSeWwy40BETsH+wzvRGFPjLo/yXu3vDipuLMaY8pCbM7F9nO5jJ4U99r39jCOhWEJMA34UFmcyt0s80WJN9jY54Ok+O3WxJGO/rfvspMWh+2wP7rO1jSS6+cAQsaO1A9gPwBxjjAHmYfvwAC4H2lJ1meM8RyLP1aKPydmxuf135wARR9gmIw4R6eZ+vScixcBEYFkmtonznryA7a2aHXZfW7dJxPc+RoznAe8622EOME3syPfBwBDg81a+fsJxiMjhwP3A2caYbSHLI75X+xlHorH0Cbl5NrDcuT4X+I4TUzfgOzSv9CU9Fiee4diBLJ+ELEv2dolnDnCZWEcDFU5ikextory3z04oljTtt3WfnWAcus/26D7bJGmE54H0A3wX229TA2wF5jrL+wKvhax3BrASewT1q5DlB2H/GFcDzwF5bYilCHgHWAW8DXR3lo8HZoasNwh7ZOULe/y7wNfYndOTQGGq4gCOdV7rK+fy6kxtE+ASoA5YFPIzNlnbJNJ7j/1a82zner7ze652fu+DQh77K+dxK4DT2/hZjRfH285n2N0Gc+K9VymM5Q/AUuc15wHDQx57lbOtVgNXpjoW5/bNwG1hj0vqdsEmUpudz2IptgfzB8APnPsFuM+J82tCZtJI9jZpzz/oPnu/Y4n1mU/WdkkwDt1nG91nR4vFuX0z7XSfradrV0oppZRSKkW0jUQppZRSSqkU0WRbKaWUUkqpFNFkWymllFJKqRTRZFsppZRSSqkU0WRbKaWUUkqpFNFkWymllFJKqRTRZFsppZRSSqkU0WRbqVYQkSNFZLGI5ItIRxFZKiKHZjoupZRSLek+W2UDPamNUq0kIrdgz0bWASg1xvwhwyEppZSKQvfZKtM02VaqlUQkAMwHqoFjjTENGQ5JKaVUFLrPVpmmbSRKtV4RUAh0wlZLlFJKZS/dZ6uM0sq2Uq0kInOAp4HBQB9jzHUZDkkppVQUus9WmZaT6QCUOpCIyGVAnTHmKRHxAx+LyMnGmHczHZtSSqnmdJ+tsoFWtpVSSimllEoR7dlWSimllFIqRTTZVkoppZRSKkU02VZKKaWUUipFNNlWSimllFIqRTTZVkoppZRSKkU02VZKKaWUUipFNNlWSimllFIqRf4/kNq5HM4ikf8AAAAASUVORK5CYII=\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "tags": [], + "needs_background": "light" + } + } + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Yg1tdKwAZ0-F" + }, + "source": [ + "In view of discontinous nature of the funny-looking target wave function, the optimization result is satisfying!" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "KpShok8uanJ9" + }, + "source": [ + "To dive deeper into the AD of eigen-decomposition, note the differention of the eigenvalue equation $A v_i = \\lambda_i v_i$ results to the following relations:\n", + "$$\n", + "\\begin{eqnarray}\n", + "d \\lambda_0 =& v_0^T dA v_0, \\\\\n", + "d v_0 =& \\sum_{i \\neq 0} \\frac{1}{\\lambda_0 - \\lambda_i} v_i v_i^T dA v_0.\n", + "\\end{eqnarray}\n", + "$$\n", + "These are nothing but the standard formulas of perturbation theory. We thus can manually implement the jvp rule of eigen-decomposition as follows: (See the jax documentation about the [basics](https://jax.readthedocs.io/en/latest/notebooks/autodiff_cookbook.html) and [customization](https://jax.readthedocs.io/en/latest/notebooks/Custom_derivative_rules_for_Python_code.html) of jvp and vjp mechanisms.)" + ] + }, + { + "cell_type": "code", + "metadata": { + "id": "dtwWW97PXM05", + "colab": { + "base_uri": "https://localhost:8080/", + "height": 173 + }, + "outputId": "2a61f561-9716-475b-969c-182fc33546f3" + }, + "source": [ + "@jax.custom_jvp\n", + "def symeig(A):\n", + " eigvals, eigvectors = jax.scipy.linalg.eigh(A)\n", + " eigval, eigvector = eigvals[0], eigvectors[:, 0]\n", + " return eigval, eigvector\n", + "\n", + "@symeig.defjvp\n", + "def symeig_jvp(primals, tangents):\n", + " A, = primals\n", + " dA, = tangents\n", + " eigvals, eigvectors = jax.scipy.linalg.eigh(A)\n", + " eigval, eigvector = primal_out = eigvals[0], eigvectors[:, 0]\n", + "\n", + " deigval = eigvector.dot(dA).dot(eigvector)\n", + " F = eigvals - eigvals[:, None] + jnp.eye(A.shape[0])\n", + " F = 1. / F - jnp.eye(A.shape[0])\n", + " deigvector = eigvectors.dot( (F * eigvectors.T.dot(dA).dot(eigvectors))[:, 0] )\n", + " tangent_out = deigval, deigvector\n", + "\n", + " return primal_out, tangent_out" + ], + "execution_count": null, + "outputs": [ + { + "output_type": "error", + "ename": "NameError", + "evalue": "ignored", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mF\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0meigvals\u001b[0m \u001b[0;34m-\u001b[0m \u001b[0meigvals\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0mjnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0meye\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mA\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mshape\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;31mNameError\u001b[0m: name 'eigvals' is not defined" + ] + } + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "YIIwTD_whYGZ" + }, + "source": [ + "One can convince the correctness of this manual implementation by checking the gradients:" + ] + }, + { + "cell_type": "code", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "ZqCBnLf4gaSb", + "outputId": "c8c50a6d-f519-4e12-c28a-6ccec406ad9c" + }, + "source": [ + "def make_loss2(K, target):\n", + " def loss_fn(potential): \n", + " H = K + jnp.diag(potential)\n", + " _, psi0 = symeig(H)\n", + " loss = jnp.abs(psi0**2 - target**2).sum()\n", + " return loss\n", + " return loss_fn\n", + "loss_fn2 = make_loss2(K, target)\n", + "\n", + "grad1 = grad(loss_fn)(init_potential)\n", + "grad2 = grad(loss_fn2)(init_potential)\n", + "jnp.allclose(grad1, grad2)" + ], + "execution_count": null, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": [ + "DeviceArray(True, dtype=bool)" + ] + }, + "metadata": { + "tags": [] + }, + "execution_count": 33 + } + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "3gaKV1ZPf_sB" + }, + "source": [ + "**Explore more**: Note that we only need the ground state of a sparse Hamiltonian, one can instead resort to sparse eigensolvers such as [Lanczos algorithm](https://en.wikipedia.org/wiki/Lanczos_algorithm). It turns out one can also devise the `jvp` and `vjp` rule for this calculation.\n", + "\n", + "See, e.g., Steven G. Johnson's [lecture note](https://math.mit.edu/~stevenj/18.336/adjoint.pdf) and [https://journals.aps.org/prb/abstract/10.1103/PhysRevB.101.245139](https://journals.aps.org/prb/abstract/10.1103/PhysRevB.101.245139). You can keep track of related features in this JAX issue https://github.com/google/jax/issues/1531. \n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "metadata": { + "id": "mAIKTMZ6SCiK" + }, + "source": [], + "execution_count": null, + "outputs": [] + } + ] +} \ No newline at end of file diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/code/ml4p_commit.txt b/tracks/qcs/solutions/frustration-free/challenge-113/code/ml4p_commit.txt new file mode 100644 index 000000000..adce3fc1e --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/code/ml4p_commit.txt @@ -0,0 +1 @@ +3a25106c532ed02f4ca6635d9996aa448ebe16d5 diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/code/neural_schrodinger_reference.ipynb b/tracks/qcs/solutions/frustration-free/challenge-113/code/neural_schrodinger_reference.ipynb new file mode 100644 index 000000000..a6b1e9364 --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/code/neural_schrodinger_reference.ipynb @@ -0,0 +1,567 @@ +{ + "nbformat": 4, + "nbformat_minor": 0, + "metadata": { + "accelerator": "GPU", + "colab": { + "provenance": [], + "collapsed_sections": [ + "d5KdRfk6vpf9" + ], + "authorship_tag": "ABX9TyORFSQKx9+3+E84wdo6WOFZ", + "include_colab_link": true + }, + "kernelspec": { + "display_name": "Python 3", + "name": "python3" + } + }, + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "view-in-github", + "colab_type": "text" + }, + "source": [ + "\"Open" + ] + }, + { + "cell_type": "code", + "metadata": { + "id": "Gn5z5_RHwdG7" + }, + "source": [ + "import jax\n", + "jax.config.update(\"jax_enable_x64\", True)\n", + "import jax.numpy as jnp\n", + "from jax.experimental import ode " + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "F61QrnuFHFmJ", + "outputId": "b8d5a22f-107e-4981-c4a8-9dffc1af095f" + }, + "source": [ + "!nvidia-smi\n", + "jax.devices()" + ], + "execution_count": null, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Thu Mar 16 03:36:25 2023 \n", + "+-----------------------------------------------------------------------------+\n", + "| NVIDIA-SMI 525.85.12 Driver Version: 525.85.12 CUDA Version: 12.0 |\n", + "|-------------------------------+----------------------+----------------------+\n", + "| GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC |\n", + "| Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. |\n", + "| | | MIG M. |\n", + "|===============================+======================+======================|\n", + "| 0 Tesla T4 Off | 00000000:00:04.0 Off | 0 |\n", + "| N/A 55C P0 26W / 70W | 0MiB / 15360MiB | 0% Default |\n", + "| | | N/A |\n", + "+-------------------------------+----------------------+----------------------+\n", + " \n", + "+-----------------------------------------------------------------------------+\n", + "| Processes: |\n", + "| GPU GI CI PID Type Process name GPU Memory |\n", + "| ID ID Usage |\n", + "|=============================================================================|\n", + "| No running processes found |\n", + "+-----------------------------------------------------------------------------+\n" + ] + }, + { + "output_type": "execute_result", + "data": { + "text/plain": [ + "[StreamExecutorGpuDevice(id=0, process_index=0, slice_index=0)]" + ] + }, + "metadata": {}, + "execution_count": 2 + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "# Problem setup " + ], + "metadata": { + "id": "TlWpnzxN_gyg" + } + }, + { + "cell_type": "markdown", + "metadata": { + "id": "x1gUrQ0M-GlB" + }, + "source": [ + "In quantum optimal control, we integrate time-dependent Schrodinger euqation from $0$ to $T$, under the initial condition $U(0)= I$\n", + "\n", + "$$ \\frac{dU}{dt} = -i H (t)U.$$ \n", + "\n", + "We'd like find an optimal control Hamiltonian so that the final evolution operator $U(T)$ match a desired gate, say, the CNOT gate. \n", + "\n", + "Here $$H(t)= h_0 + \\sum_{i=1}^{n_{ctrl}} u_i(t) h_i ,$$\n", + "where $h_i$'s are time-independent Hamiltonian. For generalirity, let's assume they are just random Hamiltonains (Fill in your favorate ones if you like). \n", + "\n", + "$u_i(t)$ are control field. We parametrize them as superpositions of Fourier series:\n", + "\n", + "$$u_i(t) = \\sum_{j=1}^{n_{basis}} u_{ij} \\sin\\left(\\frac{\\pi j t}{T}\\right).$$ So, in the end, we try to find out suitiable $u_{ij}$'s to achieve our goal. \n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "fPYCUOsUoZhe" + }, + "source": [ + "First, set up the target unitary and the Hamiltonian terms" + ] + }, + { + "cell_type": "code", + "metadata": { + "id": "etGL2dY9zzhC" + }, + "source": [ + "dim = 1<<2 # Hilbert space dimension\n", + "n_ctrl = 4 # number of contorl term\n", + "t_final = 1.0 # final control time\n", + "\n", + "U_target = jnp.array([[1, 0, 0, 0], \n", + " [0, 1, 0, 0], \n", + " [0, 0, 0, 1], \n", + " [0, 0, 1, 0]]) #CNOT\n", + "\n", + "key = jax.random.PRNGKey(42)\n", + "\n", + "#drift term\n", + "key, *subkeys = jax.random.split(key, 3)\n", + "H0 = jax.random.normal(subkeys[0], (dim, dim)) + 1J*jax.random.normal(subkeys[1], (dim, dim)) \n", + "H0 = (H0 + jnp.transpose(jnp.conjugate(H0)))/2\n", + "\n", + "#control term\n", + "H_ctrl = []\n", + "for i in range(n_ctrl): \n", + " key, *subkeys = jax.random.split(key, 3)\n", + " H1 = jax.random.normal(subkeys[0], (dim, dim)) + 1J*jax.random.normal(subkeys[1], (dim, dim)) \n", + " H1 = (H1 + jnp.transpose(jnp.conjugate(H1)))/2\n", + " H_ctrl.append(H1)" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "RqYxr0G7pKV5" + }, + "source": [ + "Here is our time-dependent Hamiltonian:" + ] + }, + { + "cell_type": "code", + "metadata": { + "id": "i2tilsWCNpaj" + }, + "source": [ + "def buildH(t, params):\n", + " H = H0\n", + " params = params.reshape(n_ctrl, n_basis)\n", + " for i, H1 in enumerate(H_ctrl):\n", + " u = jnp.sum(jnp.sin((jnp.arange(n_basis)+1)*jnp.pi*t/t_final) * params[i])\n", + " H = H + H1 * u \n", + " return H" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "# Objective function" + ], + "metadata": { + "id": "TMXTbbfc_Sez" + } + }, + { + "cell_type": "markdown", + "source": [ + "So, our loss function will be the gate infidelity \n", + "\n", + "$$\\mathcal{L} = 1- \\frac{1}{d} |\\mathrm{tr} (U(T)^\\dagger U_{target})| $$" + ], + "metadata": { + "id": "mmrPdd_s_csJ" + } + }, + { + "cell_type": "code", + "metadata": { + "id": "dvrEw28CwqaH" + }, + "source": [ + "def rhs(psi, t, params):\n", + " H = buildH(t, params)\n", + " return -1J*jnp.dot(H, psi)" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": { + "id": "S2jRWqNryNbe" + }, + "source": [ + "def fidelity(U_target, params):\n", + " Ut = ode.odeint(rhs, \n", + " jnp.eye(dim)+1J*0, \n", + " jnp.array([0.0, t_final]), \n", + " params) \n", + "\n", + " return jnp.abs(jnp.trace(jnp.transpose(jnp.conjugate(Ut[-1]))@U_target))/dim" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": { + "id": "4VIafgZsOye5" + }, + "source": [ + "def loss(params):\n", + " return 1.- fidelity(U_target, params)" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "# Optimization" + ], + "metadata": { + "id": "qdfYCF6F_j7S" + } + }, + { + "cell_type": "markdown", + "metadata": { + "id": "8rHbwVG78hRr" + }, + "source": [ + "For training, we can use quasi-Newton (BFGS) optimizer" + ] + }, + { + "cell_type": "code", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "s8jg5wD-ZKlo", + "outputId": "cf92824e-3f83-4587-92b7-fb0bd304a538" + }, + "source": [ + "#initial parameters\n", + "key, subkey = jax.random.split(key)\n", + "n_basis = 10\n", + "params = jax.random.normal(subkey, (n_ctrl*n_basis, )) *0.01\n", + "\n", + "import jax.scipy.optimize\n", + "results = jax.scipy.optimize.minimize(loss, params, method='BFGS')\n", + "\n", + "print (results.success, results.fun, fidelity(U_target, results.x))" + ], + "execution_count": null, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "True 3.7698282162601515e-08 0.9999999623017178\n" + ] + } + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "mUrHQoU1s_ku" + }, + "source": [ + "Whoa, that is almost perfect! \n", + "\n", + "Let's now plot the control field after optimization" + ] + }, + { + "cell_type": "code", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 401 + }, + "id": "Tv8YmC55WjxY", + "outputId": "a9c798b8-ccc1-44c4-ac2e-18401d0e0e83" + }, + "source": [ + "tlist = jnp.linspace(0, t_final, 51)\n", + "import matplotlib.pyplot as plt\n", + "\n", + "fig = plt.figure(figsize=(6, 4), dpi=100)\n", + "for i in range(n_ctrl):\n", + " coef_optimized = [jnp.sum(jnp.sin((jnp.arange(n_basis)+1)*jnp.pi*t/t_final) * results.x.reshape(n_ctrl, n_basis)[i] ) for t in tlist]\n", + " plt.plot(tlist, coef_optimized, label='%g'%(i))\n", + "plt.legend()\n", + "plt.xlabel('$t$')\n", + "plt.ylabel('control field')" + ], + "execution_count": null, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": [ + "Text(0, 0.5, 'control field')" + ] + }, + "metadata": {}, + "execution_count": 9 + }, + { + "output_type": "display_data", + "data": { + "text/plain": [ + "
" + ], + "image/png": "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\n" + }, + "metadata": { + "needs_background": "light" + } + } + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "G63p4I94Gl3g" + }, + "source": [ + "Let's investigate hessian of the control. This is something you can easily do with `jax`. \n", + "\n", + "We simply compute seconder order gradient through the whole evolution and plot eigenvalues of the Hesssian matrix. Recall that at optimal we have \n", + "\n", + "$$ \\mathcal{L} = \\mathcal{L}(u^\\ast) + \\frac{1}{2}\\frac{\\partial ^2 \\mathcal{L}}{\\partial u_i \\partial u_j} du_i du_j $$" + ] + }, + { + "cell_type": "code", + "metadata": { + "id": "9Tw7huXi7vPY", + "colab": { + "base_uri": "https://localhost:8080/", + "height": 265 + }, + "outputId": "3d405530-da20-403e-b6d3-a5d979ccde84" + }, + "source": [ + "params = results.x\n", + "hess = jax.jacrev(jax.jacrev(loss))(params) # jax.hessian does fwd over rev\n", + "\n", + "w, v = jnp.linalg.eigh(hess)\n", + "\n", + "import matplotlib.pyplot as plt\n", + "plt.plot(w[::-1], 'ro')\n", + "plt.yscale('log')" + ], + "execution_count": null, + "outputs": [ + { + "output_type": "display_data", + "data": { + "text/plain": [ + "
" + ], + "image/png": "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\n" + }, + "metadata": { + "needs_background": "light" + } + } + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "6VwRnnlavAt9" + }, + "source": [ + "Hmmm! Looks like there are only 15 dominant eigenvalues. Those are the relevant directions in the search. \n", + "\n", + "Related discussions in quantum control\n", + "* Quantum optimal control: Hessian analysis of the control landscape, https://aip.scitation.org/doi/10.1063/1.2198836\n", + "* Dynamic Dimensionality Identification for Quantum Control, https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.112.143001\n", + "\n", + "\n", + "Interestingly, there are also similar discussions for overparametrized neural networks:\n", + "* Empirical Analysis of the Hessian of Over-Parametrized Neural Networks, https://arxiv.org/abs/1706.04454\n", + "\n", + "\n", + "This observation can actually be useful when you want to perform \"closed-loop\" control afterwards." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "TNiPwmkA6lOh" + }, + "source": [ + "More things to explore:\n", + "\n", + "* Treating the Schrodinger equation as a general ordinary differential equation may break the unitarity. However, if you wish, you can always restore it by choosing a specialized integrator, see, for example, https://arxiv.org/abs/2001.01013. \n", + "\n", + "* What do you do if the Hessian matrix is too large to diagonalize ? Note that we only need dominant eigenvalues/states anyway ? HINT: look for `hvp` here https://jax.readthedocs.io/en/latest/notebooks/autodiff_cookbook.html. \n", + "\n", + "* Note that we were using `jacrev` twice to compute the Hessian matrix other than the \"forward-over-reverse\" mode recommened in the jax [autodiff cookbook](https://jax.readthedocs.io/en/latest/notebooks/autodiff_cookbook.html). This is because currently jax does not support forward mode AD for neural ODE. You can follow its progress in this issue https://github.com/google/jax/issues/1927. \n", + "\n", + "* How about adding a noise term in the Hamiltonian ? HINT: try to simulate a batch of noisy evolution with `jax.vmap`" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "d5KdRfk6vpf9" + }, + "source": [ + "# sandbox" + ] + }, + { + "cell_type": "code", + "metadata": { + "id": "Z1zBNPfVHnjW", + "colab": { + "base_uri": "https://localhost:8080/", + "height": 54 + }, + "outputId": "4359d0f1-7dd0-429a-d5ae-70ac8b30a490" + }, + "source": [ + "#pertub the optimal parameter in the principal direction\n", + "xlist = jnp.linspace(-0.1, 0.1, 11)\n", + "for i in range(1, 18):\n", + " ylist = []\n", + " for delta in xlist:\n", + " ylist.append(loss(params + delta * v[:, -i]))\n", + " plt.plot(xlist, ylist, label='i=%g'%(i))\n", + "plt.legend()\n", + "plt.yscale('log')" + ], + "execution_count": null, + "outputs": [ + { + "output_type": "display_data", + "data": { + "text/plain": [ + "
" + ], + "image/png": "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\n" + }, + "metadata": { + "needs_background": "light" + } + } + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "MlX7cIT88ia6" + }, + "source": [ + "Or, the L-BFGS implementation from `scipy`" + ] + }, + { + "cell_type": "code", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "LvfPuE01eP0i", + "outputId": "f8d0d64b-754d-4e35-b11e-a11b2a165e5b" + }, + "source": [ + "import numpy as np\n", + "from scipy.optimize import minimize\n", + "def fun(x):\n", + " f, g = jax.value_and_grad(loss)(x)\n", + " return f, np.array(g)\n", + "results = minimize(fun, np.array(params), method='L-BFGS-B', jac=True, options={'gtol': 1e-07})\n", + "\n", + "print (results.success, results.fun, fidelity(U_target, results.x))" + ], + "execution_count": null, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "True 3.737056253338977e-08 0.9999999626294375\n" + ] + } + ] + }, + { + "cell_type": "code", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "51eWHfOdDuQ1", + "outputId": "d72bc018-5983-484b-8d52-f2c7235ae651" + }, + "source": [ + "def hvp(f, x, v):\n", + " return jax.grad(lambda x: jnp.vdot(jax.grad(f)(x), v))(x)\n", + " \n", + "from scipy.sparse.linalg import eigsh\n", + "from scipy.sparse.linalg import LinearOperator\n", + "nparams = params.shape[0]\n", + "hess = LinearOperator((nparams, nparams), matvec=lambda v: hvp(loss, params, v))\n", + "%timeit eigenvalues, eigenvectors = eigsh(hess, k=6, which='LM')" + ], + "execution_count": null, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "7.16 s ± 100 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/docs/superpowers/plans/2026-07-29-challenge113-sim-to-real.md b/tracks/qcs/solutions/frustration-free/challenge-113/docs/superpowers/plans/2026-07-29-challenge113-sim-to-real.md new file mode 100644 index 000000000..c0133f573 --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/docs/superpowers/plans/2026-07-29-challenge113-sim-to-real.md @@ -0,0 +1,1078 @@ +# Challenge 113 Sim-to-Real Quantum-Gate Control Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Build a reproducible d=2/4 study proving when a model-Hessian subspace reduces query and shot cost for noisy black-box gate calibration, and identify the model-gap regime where it fails. + +**Architecture:** A dedicated JAX package uses piecewise-constant controls and matrix-exponential propagation for open-loop optimization and landscape extraction. A strict query-only device boundary feeds the same derivative-free optimizer in full, model-Hessian, random, and oracle coordinate spaces. Content-addressed, atomic artifacts support paired statistical analysis and restartable production sweeps. + +**Tech Stack:** Python 3.12, uv, JAX/JAXlib, NumPy, SciPy, CMA-ES (`cma`), Matplotlib, pytest. + +## Global Constraints + +- All files created by this plan remain under `tracks/qcs/solutions/frustration-free/challenge-113/`. +- Do not modify repository-root lockfiles, shared skills, or another challenge directory. +- Use JAX x64 for all geometry, rank, and scientific-acceptance runs. +- System duration is immutable and hashed: one-qubit defaults to `1.0`; + two-qubit defaults to `8.0`. +- Use process infidelity `1 - |Tr(U_target† U)|² / d²` without regularization for landscape rank. +- Hard amplitude bounds replace fluence/smoothness penalties in the rank objective. +- Model open-loop acceptance is infidelity at most `1e-8`. +- Black-box target is independently certified fidelity at least `0.999`. +- Numerical rank is reported over relative thresholds `1e-6`, `1e-8`, and `1e-10`; `1e-8` is primary. +- Device optimizers receive no exact truth loss, Hamiltonian, gradient, or Hessian. +- The device boundary is an in-process capability/API boundary, not a security + sandbox against hostile Python memory or closure introspection. +- Development and production artifacts use distinct run kinds and are never aggregated together. +- Every task follows red-green-refactor, runs focused tests, runs `git diff --check`, and creates one local commit. + +--- + +### Task 1: Dedicated runtime, package skeleton, and validated configuration + +**Files:** +- Create: `tracks/qcs/solutions/frustration-free/challenge-113/pyproject.toml` +- Create: `tracks/qcs/solutions/frustration-free/challenge-113/uv.lock` +- Create: `tracks/qcs/solutions/frustration-free/challenge-113/src/qcontrol/__init__.py` +- Create: `tracks/qcs/solutions/frustration-free/challenge-113/src/qcontrol/config.py` +- Create: `tracks/qcs/solutions/frustration-free/challenge-113/tests/test_config.py` +- Create: `tracks/qcs/solutions/frustration-free/challenge-113/tests/test_runtime.py` + +**Interfaces:** +- Produces: `SystemConfig(name: str, segments: int, amplitude_bound: float, duration: float | None = None)`. +- Produces: `DeviceConfig(gap: float = 0.0, shots: int | None = None, perturbation_seed: int = 0)`. +- Produces: `SearchConfig(method: str, dimension: int, budget: int)`. +- Produces: `ExperimentConfig(run_kind, system, device, search, trial_seed)`. +- Produces: `ExperimentConfig.canonical_dict() -> dict[str, object]`. +- Produces: `ExperimentConfig.content_id() -> str`. +- Consumes: no earlier implementation task. + +- [ ] **Step 1: Write failing configuration tests** + +```python +from dataclasses import replace + +import pytest + +from qcontrol.config import DeviceConfig, ExperimentConfig, SearchConfig, SystemConfig + + +def valid_config() -> ExperimentConfig: + return ExperimentConfig( + run_kind="development", + system=SystemConfig(name="two_qubit", segments=20, amplitude_bound=4.0), + device=DeviceConfig(gap=0.05, shots=1000, perturbation_seed=7), + search=SearchConfig(method="model_hessian", dimension=15, budget=200), + trial_seed=11, + ) + + +def test_config_id_is_stable_and_semantic() -> None: + config = valid_config() + assert config.content_id() == config.content_id() + assert replace(config, trial_seed=12).content_id() != config.content_id() + + +@pytest.mark.parametrize( + ("field", "value"), + [("gap", -0.1), ("shots", -1)], +) +def test_device_config_rejects_invalid_values(field: str, value: float) -> None: + with pytest.raises(ValueError): + DeviceConfig(**{field: value}) + + +def test_production_cannot_use_development_budget() -> None: + with pytest.raises(ValueError, match="production budget"): + replace(valid_config(), run_kind="production").validate() +``` + +- [ ] **Step 2: Run tests and verify the package is absent** + +Run: + +```bash +cd tracks/qcs/solutions/frustration-free/challenge-113 +uv run --with pytest pytest tests/test_config.py -q +``` + +Expected: collection fails because `qcontrol.config` does not exist. + +- [ ] **Step 3: Create the isolated project and lock** + +Create a `pyproject.toml` with: + +```toml +[project] +name = "challenge-113-sim-to-real" +version = "0.1.0" +requires-python = ">=3.12,<3.13" +dependencies = [ + "cma", + "jax", + "matplotlib", + "numpy", + "scipy", +] + +[dependency-groups] +dev = ["pytest"] + +[tool.pytest.ini_options] +pythonpath = ["src"] +testpaths = ["tests"] +``` + +Run `uv lock` rather than writing package versions manually. Commit the +resulting exact resolver output. + +- [ ] **Step 4: Implement frozen validated dataclasses** + +Use frozen dataclasses, reject booleans where integers are required, serialize +floats and enums without environment-dependent values, and compute: + +```python +def content_id(self) -> str: + payload = json.dumps( + self.canonical_dict(), + sort_keys=True, + separators=(",", ":"), + allow_nan=False, + ).encode("utf-8") + return hashlib.sha256(payload).hexdigest()[:20] +``` + +Validation must enforce: + +- systems are `one_qubit` or `two_qubit`; +- segments and budgets are positive integers; +- amplitude bounds are finite and positive; +- explicit durations are finite and positive; `None` resolves canonically to + `1.0` for one qubit and `8.0` for two qubits; +- gaps are finite and nonnegative; +- shots are either `None` for exact mode or a positive integer; +- search methods are `full`, `model_hessian`, `random`, or `oracle`; +- dimensions are positive and do not exceed the system parameter count; +- development budget is 200 and production budget is 2000. + +- [ ] **Step 5: Add runtime smoke tests** + +```python +import jax + + +def test_jax_x64_can_be_enabled() -> None: + jax.config.update("jax_enable_x64", True) + assert jax.config.x64_enabled + assert jax.devices() +``` + +- [ ] **Step 6: Verify and commit** + +Run: + +```bash +uv sync --group dev +uv run pytest tests/test_config.py tests/test_runtime.py -q +git diff --check +``` + +Expected: all tests pass. Commit with `Build isolated Challenge 113 runtime`. + +--- + +### Task 2: Physical systems, target gates, and controllability + +**Files:** +- Create: `tracks/qcs/solutions/frustration-free/challenge-113/src/qcontrol/systems.py` +- Create: `tracks/qcs/solutions/frustration-free/challenge-113/tests/test_systems.py` + +**Interfaces:** +- Consumes: `SystemConfig`. +- Produces: `ControlSystem(drift, controls, target, amplitude_scales, name, duration)`. +- Produces: `make_system(config: SystemConfig) -> ControlSystem`. +- Produces: `lie_algebra_dimension(system: ControlSystem, tolerance=1e-10) -> int`. +- Produces: `perturb_system(system, gap, seed) -> ControlSystem`. + +- [ ] **Step 1: Write exact system tests** + +```python +import numpy as np + +from qcontrol.config import SystemConfig +from qcontrol.systems import lie_algebra_dimension, make_system, perturb_system + + +def test_one_qubit_system_is_su2_controllable() -> None: + system = make_system(SystemConfig("one_qubit", 12, 4.0)) + assert system.dimension == 2 + assert system.duration == 1.0 + assert len(system.controls) == 2 + assert lie_algebra_dimension(system) == 3 + + +def test_two_qubit_system_is_su4_controllable() -> None: + system = make_system(SystemConfig("two_qubit", 20, 4.0)) + assert system.dimension == 4 + assert system.duration == 8.0 + assert len(system.controls) == 4 + assert lie_algebra_dimension(system) == 15 + + +def test_gap_zero_preserves_model_and_nonzero_gap_is_reproducible() -> None: + model = make_system(SystemConfig("two_qubit", 20, 4.0)) + zero_gap = perturb_system(model, 0.0, 3) + np.testing.assert_allclose(zero_gap.drift, model.drift) + for actual, expected in zip(zero_gap.controls, model.controls, strict=True): + np.testing.assert_allclose(actual, expected) + truth_a = perturb_system(model, 0.05, 3) + truth_b = perturb_system(model, 0.05, 3) + np.testing.assert_allclose(truth_a.drift, truth_b.drift) + assert not np.allclose(truth_a.drift, model.drift) +``` + +- [ ] **Step 2: Verify red** + +Run `uv run pytest tests/test_systems.py -q`. + +Expected: import failure for `qcontrol.systems`. + +- [ ] **Step 3: Implement normalized Pauli systems** + +Use normalized Hermitian Pauli products. The one-qubit drift contains `0.37 Z` +and controls `X`, `Y`. The two-qubit drift is: + +```python +0.31 * kron(Z, I) + 0.47 * kron(I, Z) + 0.23 * kron(Z, Z) +``` + +and controls are `XI`, `YI`, `IX`, `IY`. Use Hadamard as the one-qubit target +and CNOT as the two-qubit target. Verify all matrices are Hermitian and targets +unitary during construction. + +- [ ] **Step 4: Implement deterministic Lie closure** + +Represent skew-Hermitian generators as real vectors formed by concatenating +real and imaginary matrix parts. Repeatedly add commutators whose residual +after projection onto the current orthonormal basis exceeds the tolerance. +Remove the trace before rank tests. Stop at `d**2 - 1` or when no generator is +added. + +- [ ] **Step 5: Implement hidden-truth perturbations** + +Generate a seeded traceless Hermitian drift direction with unit Frobenius norm, +seeded independent control gain changes, and a seeded unmodeled traceless term. +Scale the aggregate perturbation so: + +```python +np.linalg.norm(truth.drift - model.drift, "fro") / np.linalg.norm(model.drift, "fro") +``` + +equals `gap` within `1e-12`. Store perturbation descriptors only in the private +truth object used by experiment construction. + +- [ ] **Step 6: Verify and commit** + +Run: + +```bash +uv run pytest tests/test_systems.py -q +git diff --check +``` + +Expected: all tests pass. Commit with `Add controllable gate-control systems`. + +--- + +### Task 3: Pulse coordinates, unitary propagation, and fidelity objective + +**Files:** +- Create: `tracks/qcs/solutions/frustration-free/challenge-113/src/qcontrol/pulses.py` +- Create: `tracks/qcs/solutions/frustration-free/challenge-113/src/qcontrol/propagation.py` +- Create: `tracks/qcs/solutions/frustration-free/challenge-113/src/qcontrol/objectives.py` +- Create: `tracks/qcs/solutions/frustration-free/challenge-113/tests/test_dynamics.py` + +**Interfaces:** +- Consumes: `ControlSystem`. +- Produces: `PulseSpace(control_count, segments, amplitude_scales, bound)`. +- Produces: `PulseSpace.to_physical(normalized) -> jax.Array`. +- Produces: `PulseSpace.to_normalized(physical) -> jax.Array`. +- Produces: `propagate(system, physical_pulse, duration=None) -> jax.Array`; + `None` uses the immutable system duration. +- Produces: `process_infidelity_from_unitary(unitary, target) -> jax.Array`. +- Produces: `normalized_infidelity(normalized, system, space) -> jax.Array`. + +- [ ] **Step 1: Write dynamics tests** + +```python +import jax +import jax.numpy as jnp +import numpy as np + +from qcontrol.config import SystemConfig +from qcontrol.objectives import normalized_infidelity, process_infidelity_from_unitary +from qcontrol.propagation import propagate +from qcontrol.pulses import PulseSpace +from qcontrol.systems import make_system + + +jax.config.update("jax_enable_x64", True) + + +def test_normalized_coordinates_round_trip() -> None: + space = PulseSpace.from_system(make_system(SystemConfig("two_qubit", 20, 4.0)), 20) + pulse = jnp.linspace(-0.8, 0.8, space.parameter_count) + np.testing.assert_allclose(space.to_normalized(space.to_physical(pulse)), pulse) + + +def test_propagator_is_unitary() -> None: + system = make_system(SystemConfig("two_qubit", 20, 4.0)) + pulse = jnp.zeros((4, 20)) + unitary = propagate(system, pulse) + np.testing.assert_allclose(unitary.conj().T @ unitary, jnp.eye(4), atol=1e-12) + + +def test_infidelity_is_global_phase_invariant() -> None: + system = make_system(SystemConfig("one_qubit", 12, 4.0)) + pulse = jnp.zeros((2, 12)) + unitary = propagate(system, pulse) + assert np.isclose( + process_infidelity_from_unitary(unitary, system.target), + process_infidelity_from_unitary(jnp.exp(0.3j) * unitary, system.target), + ) + + +def test_gradient_matches_central_difference() -> None: + system = make_system(SystemConfig("one_qubit", 12, 4.0)) + space = PulseSpace.from_system(system, 12) + x = jnp.linspace(-0.1, 0.1, space.parameter_count) + value, gradient = jax.value_and_grad(normalized_infidelity)(x, system, space) + direction = jnp.arange(space.parameter_count, dtype=jnp.float64) + direction /= jnp.linalg.norm(direction) + step = 1e-5 + finite = ( + normalized_infidelity(x + step * direction, system, space) + - normalized_infidelity(x - step * direction, system, space) + ) / (2 * step) + np.testing.assert_allclose(jnp.vdot(gradient, direction), finite, rtol=1e-6, atol=1e-8) +``` + +- [ ] **Step 2: Verify red** + +Run `uv run pytest tests/test_dynamics.py -q`. + +- [ ] **Step 3: Implement normalized pulse mapping** + +Store normalized coordinates as a flat vector ordered by control then segment. +Map to a `(control_count, segments)` physical array. Reject wrong shapes, +nonfinite values, and normalized amplitudes outside `[-1, 1]`. + +- [ ] **Step 4: Implement segment-exponential propagation** + +Use `jax.lax.scan` over segment Hamiltonians and +`jax.scipy.linalg.expm(-1j * dt * hamiltonian)`. JIT a stable-shape kernel. +Do not project the result back to a unitary matrix; projection would hide +numerical errors. The total duration defaults to `system.duration`; an explicit +override is permitted only for resolution and reachability diagnostics. + +- [ ] **Step 5: Implement the smooth phase-insensitive objective** + +```python +overlap = jnp.trace(target.conj().T @ unitary) +fidelity = jnp.real(overlap.conj() * overlap) / (dimension**2) +return jnp.clip(1.0 - fidelity, 0.0, 1.0) +``` + +Keep the unclipped internal loss available for derivatives near the optimum; +clipping is only for reported observations. + +- [ ] **Step 6: Verify and commit** + +Run: + +```bash +uv run pytest tests/test_dynamics.py -q +git diff --check +``` + +Expected: all tests pass. Commit with `Add differentiable unitary propagation`. + +--- + +### Task 4: Deterministic open-loop optimization + +**Files:** +- Create: `tracks/qcs/solutions/frustration-free/challenge-113/src/qcontrol/open_loop.py` +- Create: `tracks/qcs/solutions/frustration-free/challenge-113/tests/test_open_loop.py` + +**Interfaces:** +- Consumes: `ControlSystem`, `PulseSpace`. +- Produces: `OpenLoopResult(normalized_pulse, loss, gradient_norm, starts, evaluations)`. +- Produces: `optimize_open_loop(system, space, seed, starts=5) -> OpenLoopResult`. + +The accepted two-qubit system uses total duration `8.0`. This is not a tuning +convenience: with normalized Pauli products, the configured `0.23 ZZ` term has +effective nonlocal strength `0.115`, giving the ideal-local CNOT lower bound +`pi / (4 * 0.115) = 6.82955`. Controlled diagnostics found losses `0.4422`, +`0.3273`, `0.1814`, `0.1472`, `0.00868`, and `1.03e-13` at durations +`1`, `2`, `3.5`, `4`, `7`, and `8`, respectively. + +- [ ] **Step 1: Write open-loop acceptance tests** + +```python +from qcontrol.config import SystemConfig +from qcontrol.open_loop import optimize_open_loop +from qcontrol.pulses import PulseSpace +from qcontrol.systems import make_system + + +def test_one_qubit_open_loop_reaches_acceptance() -> None: + system = make_system(SystemConfig("one_qubit", 12, 4.0)) + space = PulseSpace.from_system(system, 12) + result = optimize_open_loop(system, space, seed=5, starts=5) + assert result.loss <= 1e-8 + assert result.gradient_norm <= 1e-5 + assert result.evaluations > 0 + + +def test_open_loop_is_reproducible() -> None: + system = make_system(SystemConfig("one_qubit", 12, 4.0)) + space = PulseSpace.from_system(system, 12) + assert optimize_open_loop(system, space, 5) == optimize_open_loop(system, space, 5) +``` + +- [ ] **Step 2: Verify red** + +Run `uv run pytest tests/test_open_loop.py -q`. + +- [ ] **Step 3: Implement JAX-to-SciPy value-and-gradient bridge** + +Compile one `jax.value_and_grad` function. Convert values to Python floats and +gradients to contiguous NumPy float64 arrays. Use SciPy `L-BFGS-B` with +`(-1, 1)` bounds in normalized coordinates. + +- [ ] **Step 4: Implement deterministic multistart** + +Use a seeded NumPy generator to create one zero start and four bounded random +starts. Select by `(loss, gradient_norm, start_index)`. Raise +`OpenLoopAcceptanceError` with all start diagnostics if no start reaches +`1e-8`; never silently publish a poor optimum. + +- [ ] **Step 5: Add the two-qubit development acceptance** + +Add a marked integration test with five starts and assert loss at most `1e-8`. +Record its measured wall time with `pytest --durations=5`; do not weaken the +scientific threshold to make the test faster. + +- [ ] **Step 6: Verify and commit** + +Run: + +```bash +uv run pytest tests/test_open_loop.py -q +git diff --check +``` + +Expected: all tests pass. Commit with `Add deterministic open-loop gate optimization`. + +--- + +### Task 5: Hessian geometry and endpoint-map validation + +**Files:** +- Create: `tracks/qcs/solutions/frustration-free/challenge-113/src/qcontrol/landscape.py` +- Create: `tracks/qcs/solutions/frustration-free/challenge-113/tests/test_landscape.py` + +**Interfaces:** +- Consumes: accepted `OpenLoopResult`. +- Produces: `hessian_vector_product(loss_fn, point, vector) -> jax.Array`. +- Produces: `endpoint_jacobian(system, space, point) -> np.ndarray`. +- Produces: `analyze_landscape(...) -> LandscapeResult`. +- `LandscapeResult` contains dense/leading eigenpairs, Jacobian singular values, + ranks at all three thresholds, and orthonormal subspace bases. + +- [ ] **Step 1: Write dense/HVP and rank tests** + +```python +import jax +import numpy as np + +from qcontrol.landscape import analyze_landscape, dense_hessian, hessian_vector_product + + +def test_hvp_matches_dense_hessian(accepted_one_qubit_fixture) -> None: + loss_fn, point = accepted_one_qubit_fixture + dense = dense_hessian(loss_fn, point) + vector = np.linspace(-1.0, 1.0, point.size) + np.testing.assert_allclose( + hessian_vector_product(loss_fn, point, vector), + dense @ vector, + rtol=1e-7, + atol=1e-9, + ) + + +def test_one_qubit_geometry_has_rank_three(accepted_one_qubit_fixture) -> None: + result = analyze_landscape(*accepted_one_qubit_fixture, leading_count=6) + assert result.hessian_ranks[1e-8] == 3 + assert result.jacobian_ranks[1e-8] == 3 + np.testing.assert_allclose(result.model_basis.T @ result.model_basis, np.eye(6), atol=1e-10) +``` + +- [ ] **Step 2: Verify red** + +Run `uv run pytest tests/test_landscape.py -q`. + +- [ ] **Step 3: Implement a single linearization convention** + +Map endpoint variations to coefficients in an orthonormal generalized-Pauli +basis of traceless Hermitian matrices. Hold `reference = U(point)` fixed, +define `relative = reference.conj().T @ U(x)`, and use the local Hermitian +tangent: + +```python +delta_a = (relative - relative.conj().T) / (2j) +coefficient_j = real(trace(generator_j.conj().T @ traceless(delta_a))) +``` + +Differentiate this local endpoint-coordinate function at the accepted optimum +with JAX. This construction is branch-free and is used only for the endpoint +Jacobian at `relative = I`. + +- [ ] **Step 4: Implement dense Hessian and matrix-free HVP** + +Use `jax.hessian` only for validation dimensions. Implement: + +```python +_, hvp = jax.jvp(jax.grad(loss_fn), (point,), (vector,)) +``` + +Wrap the compiled HVP in SciPy `LinearOperator` and use `eigsh(..., which="LA")` +for leading algebraic eigenpairs. + +- [ ] **Step 5: Implement stable eigenspace comparisons** + +Sort descending, symmetrize dense Hessians, orthonormalize selected columns, +and compare degenerate spaces with projection residuals and principal angles. +Never compare eigenvector signs. + +- [ ] **Step 6: Add d=4 acceptance and resolution checks** + +Assert the accepted d=4 fixture has Hessian and endpoint-Jacobian rank 15 at +the primary threshold, and that leading dense and HVP projectors differ by at +most `1e-7` in operator norm. Repeat propagation with twice as many segments +representing the same held pulse and require the rank conclusion to remain +unchanged. + +- [ ] **Step 7: Verify and commit** + +Run: + +```bash +uv run pytest tests/test_landscape.py -q +git diff --check +``` + +Expected: all tests pass. Commit with `Validate Hessian control geometry`. + +--- + +### Task 6: Opaque noisy device and immutable accounting ledger + +**Files:** +- Create: `tracks/qcs/solutions/frustration-free/challenge-113/src/qcontrol/device.py` +- Create: `tracks/qcs/solutions/frustration-free/challenge-113/tests/test_device.py` + +**Interfaces:** +- Consumes: a private truth `ControlSystem`, `PulseSpace`, `DeviceConfig`. +- Produces: public `QueryDevice.query(normalized_pulse) -> Observation`. +- Produces: `QueryDevice.validate(normalized_pulse, shots=100_000) -> Observation`. +- Produces: append-only `QueryLedger`. +- `Observation` contains estimate, shots, optimizer_query_index, validation flag, + and deterministic observation seed; it does not contain exact fidelity. + +- [ ] **Step 1: Write boundary and accounting tests** + +```python +import pytest + + +def test_fixed_seed_observations_are_reproducible(device_factory, pulse) -> None: + first = device_factory(seed=4).query(pulse) + second = device_factory(seed=4).query(pulse) + assert first == second + + +def test_query_and_validation_accounting(device_factory, pulse) -> None: + device = device_factory(seed=4, shots=1000) + device.query(pulse) + device.query(pulse) + device.validate(pulse, shots=100_000) + assert device.ledger.optimizer_queries == 2 + assert device.ledger.optimizer_shots == 2000 + assert device.ledger.validation_shots == 100_000 + + +def test_public_device_has_no_truth_api(device_factory) -> None: + device = device_factory(seed=4) + assert not hasattr(device, "exact_fidelity") + assert not hasattr(device, "hamiltonian") + with pytest.raises(AttributeError): + _ = device.truth +``` + +- [ ] **Step 2: Verify red** + +Run `uv run pytest tests/test_device.py -q`. + +- [ ] **Step 3: Implement exact and finite-shot observations** + +Exact black-box mode returns the clipped scalar process fidelity as the +observation and records zero shots; it exposes no separately named truth field +or truth object. +Finite-shot mode draws: + +```python +successes = rng.binomial(shots, exact_process_fidelity) +estimate = successes / shots +``` + +Derive each observation seed from the immutable device seed, query index, and +validation flag so resume and replay are deterministic. + +Every invocation reserves and records a monotonic attempt index before +evaluation. Failed propagation, validation, or sampling attempts remain in the +internal ledger with explicit failure status and cannot reuse an index. Public +observations and snapshots are detached copies; mutating one cannot alter +internal accounting. QueryDevice and ledger serialization are disabled. + +- [ ] **Step 4: Implement statistical certification** + +Use a one-sided Wilson 95% lower confidence bound. `Observation.certifies(0.999)` +returns true only for validation observations whose lower bound is at least +`0.999`. Optimizer observations never certify completion. + +- [ ] **Step 5: Enforce the optimizer boundary** + +Create the truth evaluator in a closure and expose only the `QueryDevice` +protocol to closed-loop code. Offline analysis receives a separate evaluator +constructed from the same private experiment factory; the optimizer module +must not import it. + +- [ ] **Step 6: Verify and commit** + +Run: + +```bash +uv run pytest tests/test_device.py -q +git diff --check +``` + +Expected: all tests pass. Commit with `Add query-only calibration device`. + +--- + +### Task 7: Fair full, informed, random, and oracle closed-loop searches + +**Files:** +- Create: `tracks/qcs/solutions/frustration-free/challenge-113/src/qcontrol/closed_loop.py` +- Create: `tracks/qcs/solutions/frustration-free/challenge-113/tests/test_closed_loop.py` + +**Interfaces:** +- Consumes: model optimum, coordinate basis, `QueryDevice`, `SearchConfig`. +- Produces: `SearchSpace(origin, basis, lower_bounds, upper_bounds)`. +- Produces: `run_closed_loop(device, space, budget, seed) -> ClosedLoopResult`. +- `ClosedLoopResult` contains best pulse, observation history, budget status, + provisional crossings, and validation result. + +- [ ] **Step 1: Write search-space fairness tests** + +```python +import numpy as np + +from qcontrol.closed_loop import SearchSpace, run_closed_loop + + +def test_subspace_coordinates_map_to_identical_origin(model_optimum, model_basis) -> None: + space = SearchSpace(model_optimum, model_basis[:, :15], bound=1.0) + np.testing.assert_allclose(space.to_pulse(np.zeros(15)), model_optimum) + + +def test_budget_is_never_exceeded(device_factory, search_fixture) -> None: + device = device_factory(shots=1000) + result = run_closed_loop(device, *search_fixture, budget=37, seed=2) + assert device.ledger.optimizer_queries <= 37 + assert result.evaluations == device.ledger.optimizer_queries + + +def test_seeded_random_basis_is_orthonormal_and_reproducible(random_space_factory) -> None: + first = random_space_factory(dimension=15, seed=9) + second = random_space_factory(dimension=15, seed=9) + np.testing.assert_allclose(first.basis, second.basis) + np.testing.assert_allclose(first.basis.T @ first.basis, np.eye(15), atol=1e-12) +``` + +- [ ] **Step 2: Verify red** + +Run `uv run pytest tests/test_closed_loop.py -q`. + +- [ ] **Step 3: Implement common candidate spaces** + +Use the identity basis for full search, leading model eigenvectors for informed +search, QR-orthonormalized seeded Gaussian columns for random search, and +leading private truth eigenvectors only in the oracle experiment factory. +Clip or reject any mapped pulse outside normalized amplitude bounds consistently +for every method. + +- [ ] **Step 4: Implement production CMA-ES** + +Initialize all methods at zero coordinate displacement. Use the same initial +coordinate-space scale after whitening, population policy, bounds, and stopping +rules. Evaluate candidates sequentially through `QueryDevice.query` so every +call is ledgered. Stop at budget, not at an unvalidated noisy crossing. + +- [ ] **Step 5: Implement independent target validation** + +Whenever the current best optimizer estimate is at least 0.999, issue one +independent 100,000-shot validation. If it certifies the threshold, stop and +record the first optimizer query index. If it fails, continue without altering +optimizer history. + +- [ ] **Step 6: Add deterministic scientific fixtures** + +Use a constructed small-gap d=2 fixture to verify: + +- top-3 reaches target within the fixture budget; +- paired random-3 does not outperform top-3; +- oracle-3 reaches at least as low an exact restricted floor; +- zero-gap starts at target and needs only a validation observation. + +- [ ] **Step 7: Verify and commit** + +Run: + +```bash +uv run pytest tests/test_closed_loop.py -q +git diff --check +``` + +Expected: all tests pass. Commit with `Compare query-only calibration spaces`. + +--- + +### Task 8: Atomic artifacts and restartable experiment orchestration + +**Files:** +- Create: `tracks/qcs/solutions/frustration-free/challenge-113/src/qcontrol/artifacts.py` +- Create: `tracks/qcs/solutions/frustration-free/challenge-113/src/qcontrol/experiments.py` +- Create: `tracks/qcs/solutions/frustration-free/challenge-113/run.py` +- Create: `tracks/qcs/solutions/frustration-free/challenge-113/tests/test_artifacts.py` +- Create: `tracks/qcs/solutions/frustration-free/challenge-113/tests/test_experiments.py` + +**Interfaces:** +- Consumes: all previous scientific components. +- Produces: `ArtifactStore.create_or_resume(config)`. +- Produces: `run_trial(config, store) -> TrialResult`. +- Produces CLI commands `geometry`, `trial`, `sweep`, `validate`, and `status`. + +- [ ] **Step 1: Write crash and provenance tests** + +```python +from dataclasses import replace +import json + +import pytest + +from qcontrol.artifacts import ArtifactConflict, ArtifactStore + + +def test_failed_publish_preserves_previous_artifact(tmp_path, monkeypatch) -> None: + store = ArtifactStore(tmp_path) + store.publish_json("summary.json", {"version": 1}) + monkeypatch.setattr(store, "_replace", lambda *_: (_ for _ in ()).throw(OSError("boom"))) + with pytest.raises(OSError): + store.publish_json("summary.json", {"version": 2}) + assert json.loads((tmp_path / "summary.json").read_text()) == {"version": 1} + + +def test_resume_rejects_changed_config(tmp_path, config) -> None: + ArtifactStore.create(tmp_path, config) + with pytest.raises(ArtifactConflict): + ArtifactStore.resume(tmp_path, replace(config, trial_seed=config.trial_seed + 1)) +``` + +- [ ] **Step 2: Verify red** + +Run `uv run pytest tests/test_artifacts.py tests/test_experiments.py -q`. + +- [ ] **Step 3: Implement canonical atomic publication** + +Write canonical UTF-8 JSON with `allow_nan=False`, fsync the temporary file, +atomically replace, and fsync the parent directory. A failed overwrite restores +the prior valid artifact. Immutable trial IDs cannot be overwritten with +different bytes. + +- [ ] **Step 4: Implement provenance** + +Bind: + +- Git revision and dirty state; +- SHA256 of `uv.lock`; +- hashes of all `src/qcontrol/*.py` files; +- Python, JAX, JAXlib, NumPy, and SciPy versions; +- JAX device platform and x64 state; +- canonical experiment configuration. + +Resume recomputes and verifies every field. + +- [ ] **Step 5: Implement paired trial generation** + +Generate trial IDs from system, gap, perturbation orientation, shot regime, +search dimension, method, and seed. Reuse the same private device instance +identity across paired methods but provide independent observation streams. +Generate full-space trials once per device/shot/seed, not once for every `k`. + +- [ ] **Step 6: Implement strict CLI modes** + +Examples: + +```bash +uv run python run.py geometry --system one_qubit --output results/dev-geometry +uv run python run.py sweep --kind development --output results/dev-sweep +uv run python run.py validate --output results/dev-sweep +uv run python run.py status --output results/dev-sweep +``` + +`validate` checks schemas, hashes, expected trial coverage, ledger totals, and +absence of unexpected files. It exits nonzero for partial production sweeps but +`status` reports partial progress successfully. + +- [ ] **Step 7: Test interruption and resume** + +Interrupt a five-trial fixture after two completed trials. Resume and assert +that exactly three trials execute, prior trial hashes do not change, and no +query ledger is duplicated. + +- [ ] **Step 8: Verify and commit** + +Run: + +```bash +uv run pytest tests/test_artifacts.py tests/test_experiments.py -q +git diff --check +``` + +Expected: all tests pass. Commit with `Make Challenge 113 sweeps restartable`. + +--- + +### Task 9: Statistical analysis and required figures + +**Files:** +- Create: `tracks/qcs/solutions/frustration-free/challenge-113/src/qcontrol/analysis.py` +- Create: `tracks/qcs/solutions/frustration-free/challenge-113/src/qcontrol/figures.py` +- Create: `tracks/qcs/solutions/frustration-free/challenge-113/tests/test_analysis.py` +- Create: `tracks/qcs/solutions/frustration-free/challenge-113/tests/test_figures.py` + +**Interfaces:** +- Consumes: verified trial and landscape artifacts. +- Produces: `aggregate_run(store) -> Summary`. +- Produces paired bootstrap confidence intervals and success probabilities. +- Produces the five required publication figures. + +- [ ] **Step 1: Write statistical tests from hand-computable fixtures** + +```python +from qcontrol.analysis import first_certified_query, success_probability + + +def test_first_certified_query_ignores_unvalidated_crossing() -> None: + history = fixture_history(provisional_query=4, certified_query=9) + assert first_certified_query(history) == 9 + + +def test_budget_exhaustion_counts_as_failure() -> None: + trials = [successful_trial(7), exhausted_trial(), successful_trial(12)] + estimate = success_probability(trials) + assert estimate.value == 2 / 3 + assert estimate.denominator == 3 +``` + +- [ ] **Step 2: Verify red** + +Run `uv run pytest tests/test_analysis.py tests/test_figures.py -q`. + +- [ ] **Step 3: Implement paired summaries** + +Compute: + +- first certified query with failures right-censored at the budget for plots + and separately reported as failures; +- total optimizer plus validation shots; +- success probability within budget; +- median best exact infidelity trajectories; +- paired differences between model-Hessian and each baseline; +- deterministic seeded bootstrap 95% confidence intervals. + +Never drop failed trials. + +- [ ] **Step 4: Implement failure diagnostics** + +Aggregate principal angles, restricted noiseless fidelity floors, Hessian +effective ranks, and eigenvalue gaps by model-gap magnitude. Join only records +with matching system, device orientation, and trial seed. + +- [ ] **Step 5: Implement figures** + +Generate: + +1. `queries_vs_dimension.png`; +2. `advantage_vs_gap.png`; +3. `subspace_rotation_and_floor.png`; +4. `rank_invariant_d2_d4.png`; +5. `failure_case.png`. + +Tests assert exact panel labels, logarithmic infidelity axes where used, +presence of all required methods, and deterministic file hashes for a fixed +small fixture after stripping metadata timestamps. + +- [ ] **Step 6: Verify and commit** + +Run: + +```bash +MPLBACKEND=Agg uv run pytest tests/test_analysis.py tests/test_figures.py -q +git diff --check +``` + +Expected: all tests pass. Commit with `Add paired analysis and challenge figures`. + +--- + +### Task 10: Local pilot, production gate, and report + +**Files:** +- Create: `tracks/qcs/solutions/frustration-free/challenge-113/scripts/run_development.sh` +- Create: `tracks/qcs/solutions/frustration-free/challenge-113/scripts/run_production.sh` +- Create: `tracks/qcs/solutions/frustration-free/challenge-113/README.md` +- Create: `tracks/qcs/solutions/frustration-free/challenge-113/REPORT.md` +- Create locally, gitignored: `tracks/qcs/solutions/frustration-free/challenge-113/results/` +- Create: `tracks/qcs/solutions/frustration-free/challenge-113/.gitignore` + +**Interfaces:** +- Consumes: the verified CLI and analysis pipeline. +- Produces: a three-seed development report and a guarded 20-seed production + command. +- Produces: documented evidence for runtime, memory, and device selection. + +- [ ] **Step 1: Add result-ignore and runner tests** + +Add `results/` to the challenge-local `.gitignore`. Test both scripts with +`bash -n`. The production script must require: + +```bash +: "${CHALLENGE113_ACK_PRODUCTION:?set CHALLENGE113_ACK_PRODUCTION=1}" +test "${CHALLENGE113_ACK_PRODUCTION}" = "1" +``` + +- [ ] **Step 2: Run the complete local verification** + +Run: + +```bash +uv sync --frozen --group dev +uv run pytest -q +git diff --check +``` + +Expected: all tests pass with no uncommitted generated results. + +- [ ] **Step 3: Run and validate the development sweep** + +Run: + +```bash +bash scripts/run_development.sh +uv run python run.py validate --output results/development +``` + +The development sweep uses three seeds and a 200-query budget. Record wall +time, peak RSS, JAX platform, compilation time, warm trial throughput, and +projected production cost in `REPORT.md`. + +- [ ] **Step 4: Enforce the production resource gate** + +Production may proceed locally if projected wall time is at most 12 hours and +peak RSS is at most 70% of available memory. Otherwise generate a cluster +submission plan using one GPU, exact locked environment, immutable source +revision, and restartable trial shards. Do not submit until the recorded +projection exists. + +- [ ] **Step 5: Run the production sweep** + +After the gate passes: + +```bash +CHALLENGE113_ACK_PRODUCTION=1 bash scripts/run_production.sh +uv run python run.py validate --output results/production +``` + +Expected coverage: + +- d=2 invariant study with 20 seeds; +- d=4 headline study with 20 seeds; +- gap values `0, 0.02, 0.05, 0.10, 0.20`; +- exact, 1,000-shot, and 10,000-shot regimes; +- full, model-Hessian, random, and oracle baselines; +- no missing or duplicate paired trials. + +- [ ] **Step 6: Complete the report** + +`REPORT.md` must state: + +- the conditional nature of the \(d^2-1\) rank; +- whether model-Hessian beats random dimensionality reduction; +- query and shot savings with paired confidence intervals; +- the model-gap crossover; +- an explicit failure case; +- numerical-resolution and rank-threshold sensitivity; +- that the finite-shot device is an abstract estimator, not randomized + benchmarking; +- the official Colab authentication limitation; +- commands and hashes needed to reproduce the study. + +- [ ] **Step 7: Final verification and commit** + +Run: + +```bash +uv run pytest -q +uv run python run.py validate --output results/production +git diff --check +git status --short +``` + +Expected: tests and production validation pass; only intended source, +documentation, and selected compact figure/report artifacts are tracked. +Commit with `Complete Challenge 113 sim-to-real study`. + +## Execution order and review gates + +Tasks are sequential because later scientific claims depend on validated +interfaces from earlier tasks. After every task: + +1. run the focused tests; +2. inspect the commit diff; +3. review scientific correctness before continuing; +4. keep generated results untracked unless the final report explicitly selects + compact artifacts. + +Task 5 is the first scientific gate. If the d=2/d=4 geometry does not pass, +stop and diagnose controllability, objective, propagation, and regularity +before building closed-loop experiments. + +Task 7 is the second scientific gate. If a deterministic small-gap fixture does +not separate model-Hessian from random subspaces, do not launch broad sweeps. + +Task 10 is the compute gate. Production is authorized only by measured pilot +telemetry, not an estimate from code inspection. diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/docs/superpowers/specs/2026-07-29-challenge113-sim-to-real-design.md b/tracks/qcs/solutions/frustration-free/challenge-113/docs/superpowers/specs/2026-07-29-challenge113-sim-to-real-design.md new file mode 100644 index 000000000..fad42e776 --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/docs/superpowers/specs/2026-07-29-challenge113-sim-to-real-design.md @@ -0,0 +1,404 @@ +# Challenge 113 Sim-to-Real Quantum-Gate Control Design + +## Status and decision + +This design implements the complete simulated core and the model-mismatch +failure study. Real hardware and a leakage-aware two-transmon model are +extensions after the simulated evidence chain passes. + +The numerical core will use JAX with piecewise-constant controls and a product +of segment matrix exponentials. This preserves unitarity to numerical +precision, supports automatic differentiation and Hessian-vector products, and +avoids interpreting ODE-integrator drift as landscape curvature. + +The official Colab notebook is authentication-gated. Stages 1 and 2 are +therefore reconstructed from the challenge text and the pinned author notebook, +not claimed as an exact reproduction of the Colab. + +## Scientific claim + +For phase-insensitive full-unitary synthesis on a controllable, over-resourced +closed system, the Hessian at a regular optimum has at most +\(d^2-1\) nonzero directions. A model-derived approximation to those directions +should reduce the number of expensive black-box queries needed to calibrate a +nearby true device. + +The submission must establish both parts: + +1. the observed local rank is consistent with the endpoint-map geometry; and +2. the model Hessian subspace provides a query advantage over both full-space + search and uninformed dimensionality reduction. + +The claim is falsified if a random subspace performs comparably, if the +endpoint Jacobian does not support the reported rank, or if the reduced +subspace cannot contain a correction that reaches the target. + +## Scope + +### Included + +- One-qubit \(d=2\) gate synthesis, predicted maximal rank 3. +- Two-qubit \(d=4\) gate synthesis, predicted maximal rank 15. +- Structure-preserving differentiable propagation. +- Open-loop model optimization. +- Dense-Hessian validation and matrix-free Hessian-vector products. +- Endpoint-Jacobian and dynamical-Lie-algebra diagnostics. +- Opaque simulated devices with drift, gain, and unmodeled-term mismatch. +- Exact and finite-shot black-box observations. +- Full, top-\(k\), random-\(k\), and oracle-\(k\) closed-loop searches. +- Search-dimension, model-gap, shot-count, and seed sweeps. +- Failure diagnostics and independently validated target crossings. +- Reproducible, resumable artifacts and publication figures. + +### Deferred + +- Live cloud-hardware experiments. +- Leakage-aware coupled-transmon production studies. +- Device-driven subspace re-estimation. +- Bayesian optimization. +- Three-qubit \(d=8\) production sweeps. + +These are extensions and must not delay the core result. + +## Mathematical conventions + +### Dynamics + +For segment \(n\) of duration \(\Delta t\), + +\[ +H_n = H_0 + \sum_a u_{a,n} H_a,\qquad +U_{n+1} = e^{-i H_n\Delta t}U_n. +\] + +All Hamiltonians are Hermitian and all amplitudes are real. The propagator is +the ordered product from the identity. JAX x64 is mandatory for rank and +curvature measurements. Total gate duration is part of the immutable system +configuration and therefore part of every artifact hash. + +### Objective + +The phase-insensitive process infidelity is + +\[ +\mathcal L(U) = +1-\frac{|\operatorname{Tr}(U_\mathrm{target}^\dagger U)|^2}{d^2}. +\] + +The squared trace is smooth at the optimum and has the same +\(\mathfrak{su}(d)\) dimensionality as the challenge's unsquared fidelity. +No fluence or smoothness penalty is included in the Hessian-rank objective, +because such penalties add artificial curvature to physically flat directions. +Amplitude limits are hard bounds. + +The open-loop model gate must reach \(\mathcal L\le10^{-8}\). The black-box +calibration target is true-device infidelity \(\mathcal L\le10^{-3}\), confirmed +by a separate validation observation. + +### Coordinates and rank + +Control parameters are normalized by their declared amplitude scales before +forming Hessian directions. This prevents arbitrary unit choices from defining +the "top" subspace. + +At an optimum, the control Hessian is checked against the endpoint Jacobian +from normalized pulse coordinates to a fixed orthonormal basis of traceless +Hermitian generators. Numerical rank uses a relative singular/eigenvalue +threshold of \(10^{-8}\), with threshold sweeps over \(10^{-6}\) through +\(10^{-10}\) included in diagnostics. + +The implementation reports a rank plateau; it does not force exactly +\(d^2-1\). A lower rank is a scientific result if controllability, resource, or +regularity checks explain it. + +## Systems + +### One qubit + +Use a nonzero drift proportional to \(Z\), independent \(X\) and \(Y\) +controls, and a noncommuting target gate. Use 12 segments per control +(\(p=24\)) and total duration \(T=1\) so the pulse is clearly +overparameterized relative to rank 3. + +### Two qubits + +Use a coupled drift containing \(ZZ\) and unequal local \(Z\) terms, with local +\(X/Y\) controls on both qubits. The target is CNOT up to global phase. Use +20 segments per control (\(p=80\)), well above rank 15, and total duration +\(T=8\). With the normalized Pauli convention the configured `0.23 ZZ` term +has effective nonlocal strength \(J=0.115\), so a CNOT-class gate requires at +least \(\pi/(4J)\approx6.83\) under ideal instantaneous local controls. +The original \(T=1\) proposal is physically unreachable; a controlled duration +sweep reached \(10^{-8}\) only at \(T=8\). + +Before optimization, compute the dynamical Lie closure and verify dimensions 3 +and 15 respectively. Failure to reach the expected Lie dimension is a +configuration error, not an optimization result. + +## Architecture + +The implementation lives entirely under the Challenge 113 solution directory. + +- `qcontrol/config.py`: closed, validated experiment dataclasses and canonical + JSON serialization. +- `qcontrol/systems.py`: Pauli generators, one- and two-qubit systems, targets, + Lie-closure diagnostics, and perturbation generation. +- `qcontrol/pulses.py`: normalized piecewise-constant parameterization, + bounds, and subspace-coordinate maps. +- `qcontrol/propagation.py`: JAX matrix-exponential propagation and unitarity + diagnostics. +- `qcontrol/objectives.py`: model loss, exact true loss, and fidelity + conversions. +- `qcontrol/open_loop.py`: deterministic multistart L-BFGS-B optimization. +- `qcontrol/landscape.py`: dense Hessian, HVP operator, endpoint Jacobian, + eigenspaces, rank, and principal angles. +- `qcontrol/device.py`: query-only device protocol, hidden truth model, + finite-shot estimator, validation observations, and immutable ledger. +- `qcontrol/closed_loop.py`: common derivative-free search interface for all + candidate spaces. +- `qcontrol/experiments.py`: trial generation and execution. +- `qcontrol/artifacts.py`: canonical hashing, JSON/JSONL schemas, atomic + publication, locking, resume, and verification. +- `qcontrol/analysis.py`: aggregation, confidence intervals, target-query + statistics, and figure-ready tables. +- `run.py`: explicit command-line entry point. + +The dedicated environment is defined inside the challenge directory. Core +dependencies are JAX/JAXlib, NumPy, SciPy, Matplotlib, pytest, and `cma`. +No root lockfile is modified. + +## Three-stage data flow + +### Stage 1: open-loop model optimization + +For each system and seed: + +1. construct the model and target; +2. validate Hermiticity and Lie-algebra dimension; +3. run bounded multistart L-BFGS-B; +4. require model infidelity at most \(10^{-8}\); +5. save the normalized optimum, diagnostics, and source/config hashes. + +The best pulse is selected by loss, then gradient norm, with deterministic +tie-breaking. + +### Stage 2: landscape extraction + +At the accepted optimum: + +1. compute a dense Hessian for d=2 and the initial d=4 validation case; +2. verify HVPs against dense products; +3. compute leading eigenpairs matrix-free for production; +4. compute endpoint-Jacobian singular values; +5. compare Hessian and Jacobian ranks; +6. save top eigenvectors in normalized coordinates. + +Degenerate eigenspaces are compared as subspaces, never by individual vector +signs or ordering. + +### Stage 3: query-only calibration + +The device accepts a complete pulse and returns only an observation plus query +metadata. Optimizers cannot access its Hamiltonian, exact fidelity, gradients, +or Hessian. Exact truth is exposed only to the offline evaluator. + +This is an architectural capability boundary inside one Python process, not a +security sandbox against hostile memory/closure introspection. Optimizer code +is restricted to the documented device protocol and is tested not to import or +receive private truth objects. Real adversarial isolation would require a +separate process or service and is outside the simulated-core scope. + +Candidate spaces are: + +- full \(p\)-dimensional normalized pulse space; +- model-Hessian top-\(k\) space; +- seeded random orthonormal \(k\)-space; +- true-device oracle top-\(k\) space, simulation-only. + +All methods start from the same model-optimal pulse and use the same +derivative-free optimizer, amplitude constraints, query budget, stopping rule, +and validation policy. CMA-ES is the production optimizer because it is robust +to moderate observation noise. A deterministic Nelder-Mead smoke case checks +the noiseless interface. + +## Simulated device + +Truth-model perturbations are private and generated from: + +- normalized drift perturbation; +- independent control-gain errors; +- one normalized unmodeled Hermitian term. + +Gap magnitudes are relative to the model drift norm: +\(\varepsilon\in\{0,0.02,0.05,0.10,0.20\}\). Each nonzero magnitude uses +multiple seeded perturbation orientations. + +The exact black-box mode returns the scalar process fidelity as its observation +without exposing a separately named truth field, Hamiltonian, derivative, or +truth object. The finite-shot mode uses a binomial estimator whose success +probability is the exact process fidelity. This is an explicitly labeled +abstract measurement model, not a claim to implement randomized benchmarking. +Shot settings are +\(N_\mathrm{shot}\in\{10^3,10^4\}\) per query. + +A target crossing is provisional until an independent \(10^5\)-shot validation +query has a one-sided 95% confidence bound above fidelity 0.999. Validation +shots are included in total shot cost but not optimizer-query count. + +## Experiment matrix + +### Geometry acceptance + +- d=2 with parameter counts 2, 4, 6, and 24. +- d=4 with parameter counts 8, 16, 32, and 80. +- At least five open-loop initializations per configuration. +- Report model fidelity, Hessian spectrum, endpoint-Jacobian spectrum, + Lie-algebra dimension, rank-threshold sensitivity, and unitarity error. + +### Headline calibration study + +For d=4, p=80: + +- \(k\in\{5,10,15,20,30,80\}\); +- all five gap magnitudes; +- exact, \(10^3\)-shot, and \(10^4\)-shot observations; +- top-\(k\), random-\(k\), full-space, and oracle-\(k\) methods; +- 20 independent trial seeds; +- a fixed optimizer-query budget of 2000. + +Development runs use three seeds and a 200-query budget. They are never mixed +with production summaries. + +### Invariant check + +For d=2, compare \(k\in\{1,2,3,4,6,24\}\) under exact and \(10^3\)-shot +observations, using the same gap family and 20 seeds. The expected useful +dimension is near 3 rather than 15. + +## Metrics and figures + +Primary metrics: + +- first optimizer query with independently validated target attainment; +- total shots to validated target; +- success probability within the fixed budget. + +Secondary metrics: + +- median best exact infidelity versus query count with bootstrap confidence + bands; +- final infidelity under fixed budget; +- attainable noiseless fidelity floor within each subspace; +- principal angles between model and truth subspaces; +- Hessian eigenvalue gaps and effective ranks; +- amplitude-bound activity and propagation unitarity. + +Required figures: + +1. queries-to-target versus search dimension, with full/random/oracle + baselines and confidence intervals; +2. success probability and query advantage versus model-gap magnitude; +3. model/true subspace principal angles and restricted fidelity floor versus + gap; +4. d=2 and d=4 rank/invariant comparison; +5. one explicit failure case where the fixed model subspace loses its + advantage. + +## Fairness and failure policy + +- All compared methods share the same device instances and trial seeds. +- Random subspaces are independently seeded and paired by device instance. +- Query and shot counters are monotonic and cannot be reset by optimizers. +- Optimizer compilation and model-only work never count as device queries. +- Noisy target crossings require independent validation. +- Failed or budget-exhausted trials remain in the dataset. +- If random-\(k\) matches top-\(k\), the reported conclusion is generic + dimensionality reduction, not model-informed transfer. +- If oracle-\(k\) fails, diagnose optimizer/noise/constraints before blaming + model-subspace rotation. +- If restricted noiseless optimization has a fidelity floor, report + representational failure directly. +- Results are rejected if changing propagation resolution materially changes + rank or headline conclusions. + +## Artifacts and restartability + +Each run uses a content-derived identifier and stores: + +```text +results// + config.json + manifest.json + open_loop.json + landscape.npz + trials/.json + ledger/.jsonl + summary.json + figures/ +``` + +Manifests bind source revision, lockfile hash, Python/JAX versions, device kind, +precision, system definition, optimizer settings, and input hashes. + +Each trial publishes through a temporary file, fsync, and atomic rename. +Completed artifacts are immutable. Resume validates hashes and continues only +missing trials. A stale, malformed, or provenance-mismatched artifact fails +closed. + +## Testing + +### Unit tests + +- Hamiltonians are Hermitian and dimensions agree. +- Lie closure returns dimensions 3 and 15. +- Propagation is unitary within \(10^{-12}\) in x64. +- Fidelity is invariant under global phase. +- JAX gradients agree with central differences. +- HVPs agree with dense Hessian products. +- Endpoint-Jacobian rank agrees with controlled analytic cases. +- Pulse normalization and subspace maps are inverse-consistent. +- Device observations are deterministic under fixed seeds. +- Device clients cannot retrieve truth internals through the public protocol. +- Query and shot counts are exact. +- Atomic publication preserves the previous valid artifact on failure. + +### Integration tests + +- d=2 open-loop optimization reaches \(10^{-8}\). +- d=2 Hessian rank is stable near 3 over threshold sweeps. +- A zero-gap device needs no calibration beyond validation. +- A small-gap top-3 search beats a paired random one in a deterministic fixture. +- Interrupted sweeps resume without duplicate trials or query-ledger mutation. + +### Scientific acceptance + +- Both system Lie dimensions pass. +- Dense and matrix-free leading eigenspaces agree. +- Rank conclusions survive timestep refinement. +- Headline summaries contain all methods, gaps, shot regimes, and 20 seeds. +- The main conclusion includes a paired uncertainty estimate and an explicit + failure regime. + +## Compute strategy + +CPU is sufficient for unit tests, geometry validation, and development trials. +One GPU is used only after CPU correctness passes, primarily for batched +production seeds and repeated HVP/propagation calls. The runtime records compile +time separately from warm execution and fails if a requested GPU silently +falls back to CPU. + +No cluster submission occurs until a local three-seed pilot estimates runtime, +memory, and projected total cost. + +## Completion criteria + +The simulated core is complete when: + +1. all software and scientific acceptance tests pass; +2. d=2 and d=4 geometry diagnostics support the reported ranks; +3. the paired headline study demonstrates whether model-informed top-\(k\) + directions beat random-\(k\) and full-space searches; +4. the model-gap crossover and at least one honest failure case are reported; +5. all figures derive from verified immutable artifacts; and +6. a fresh environment can reproduce the development study from documented + commands. diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/evidence/task10a/calibration.json b/tracks/qcs/solutions/frustration-free/challenge-113/evidence/task10a/calibration.json new file mode 100644 index 000000000..5d6519499 --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/evidence/task10a/calibration.json @@ -0,0 +1 @@ +{"evidence_type":"calibration","inputs":{"raw_calibration":"c94ed8de9d3fdc5a99a322f40040bd63517c876d0784473a8ec0e94f50eee48f","uv_lock":"1d16a82284cebf3ae050ee79bcba4f2c9166820cf5fcae6a277334e1614a35dc"},"payload":{"config_sha256":"68b331e03fc52d0645189b254a2725e47ea2ad362ddc63fc1d4afaccc6f1808f","cpu_count":32,"exact_trajectory_seconds":0.033985568999924,"first_query_compilation_inclusive_seconds":0.21653232199969352,"geometry_seconds":1.6681537879994721,"jax_platform":"cpu","landscape_seconds":5.809491739000805,"open_loop_seconds":7.750589924999076,"parameter_count":80,"peak_rss_kib":862252,"queries":20,"restricted_nfev":8,"restricted_optimization_seconds":0.43032219400265603,"search_dimension":4,"warm_queries_per_second":562.3972995803553,"warm_query_seconds":0.03378394600076717,"x64_enabled":true},"schema_version":1,"source_revision":"dd16192953c130d738716238525760de73343e09"} diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/evidence/task10a/environment.json b/tracks/qcs/solutions/frustration-free/challenge-113/evidence/task10a/environment.json new file mode 100644 index 000000000..bb54df44b --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/evidence/task10a/environment.json @@ -0,0 +1 @@ +{"evidence_type":"environment","inputs":{"uv_lock":"1d16a82284cebf3ae050ee79bcba4f2c9166820cf5fcae6a277334e1614a35dc"},"payload":{"cpu_count":32,"jax":"0.11.0","jax_platform":"cpu","jaxlib":"0.11.0","numpy":"2.5.1","platform":"Linux-6.6.87.2-microsoft-standard-WSL2-x86_64-with-glibc2.43","python":"3.12.13","scipy":"1.18.0","uv_lock_sha256":"1d16a82284cebf3ae050ee79bcba4f2c9166820cf5fcae6a277334e1614a35dc","x64_enabled":true},"schema_version":1,"source_revision":"dd16192953c130d738716238525760de73343e09"} diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/evidence/task10a/index.json b/tracks/qcs/solutions/frustration-free/challenge-113/evidence/task10a/index.json new file mode 100644 index 000000000..cb3459976 --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/evidence/task10a/index.json @@ -0,0 +1 @@ +{"documents":{"calibration.json":"d1dbdb2635b8913013968dc3db1d7e8e358bc2da106b576fe13c389c9e17f6da","environment.json":"2afea9d563ad4247191324c2610459b87d2c5e20aa67e0b7c18bb2711db210d5","pilot.json":"1c5548ec73d5fff7c4a35d822ec7bdaefcb28ecd4b827844aa056acb4b4e58b3","projection.json":"4124dfd9594ef7c184682b8418a49888533f797a21787f19ce9f42bcc96868fa","report_metadata.json":"71769d3a6a464deb98315b38fee15c6955e9e9c3792696cbf2787390c78eb562","time.json":"779a6291854e6bc8db268278dcf35d2c81c2b76dcb86a66d71658109c70981ca","validation.json":"0c0c63192bdf6b8181483cfe5753a5907fb8bd71c166caf42d37b5ae97edb3ea"},"schema_version":1,"source_revision":"dd16192953c130d738716238525760de73343e09"} diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/evidence/task10a/pilot.json b/tracks/qcs/solutions/frustration-free/challenge-113/evidence/task10a/pilot.json new file mode 100644 index 000000000..89b1e6e6d --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/evidence/task10a/pilot.json @@ -0,0 +1 @@ +{"evidence_type":"pilot","inputs":{"plan":"06cf801aa8e0fdb07a509cffbc4d7053282600cd94fc33abbf0adf6069fea25d","trial":"f37a68183e61f93edd69fecffe02e31c8be1d6d981e259b1ad3464fe66fc01fe","uv_lock":"1d16a82284cebf3ae050ee79bcba4f2c9166820cf5fcae6a277334e1614a35dc"},"payload":{"artifact_bytes":525776,"config_sha256":"68b331e03fc52d0645189b254a2725e47ea2ad362ddc63fc1d4afaccc6f1808f","evaluations":881,"manifest_sha256":"7c7d9b7c51c50c4d1c273ce3eda775ed655a2c0173769d5acddb318afd6b9c17","plan_sha256":"06cf801aa8e0fdb07a509cffbc4d7053282600cd94fc33abbf0adf6069fea25d","ready_sha256":"6fa66678afe7adacdec82c788916cada2a3911f147c37bd29bb6365a6a64cb89","total_queries":881,"trial_id":"trial-095b735fd3d2f531584b282b","trial_sha256":"f37a68183e61f93edd69fecffe02e31c8be1d6d981e259b1ad3464fe66fc01fe"},"schema_version":1,"source_revision":"dd16192953c130d738716238525760de73343e09"} diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/evidence/task10a/projection.json b/tracks/qcs/solutions/frustration-free/challenge-113/evidence/task10a/projection.json new file mode 100644 index 000000000..3e03cc8da --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/evidence/task10a/projection.json @@ -0,0 +1 @@ +{"evidence_type":"projection","inputs":{"pilot":"1c5548ec73d5fff7c4a35d822ec7bdaefcb28ecd4b827844aa056acb4b4e58b3","time":"779a6291854e6bc8db268278dcf35d2c81c2b76dcb86a66d71658109c70981ca"},"payload":{"cpus_per_trial":8,"formula":"trial_hours=pilot_wall_seconds*trial_count/3600; core_hours=trial_hours*cpus_per_trial; storage_bytes=pilot_artifact_bytes*trial_count","pilot_artifact_bytes":525776,"pilot_wall_seconds":21.91,"projected_core_hours":462.5444444444444,"projected_storage_bytes":4994872000,"projected_trial_hours":57.81805555555555,"provisional":true,"trial_count":9500},"schema_version":1,"source_revision":"dd16192953c130d738716238525760de73343e09"} diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/evidence/task10a/report_metadata.json b/tracks/qcs/solutions/frustration-free/challenge-113/evidence/task10a/report_metadata.json new file mode 100644 index 000000000..bc909ea91 --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/evidence/task10a/report_metadata.json @@ -0,0 +1 @@ +{"evidence_type":"report_metadata","inputs":{"uv_lock":"1d16a82284cebf3ae050ee79bcba4f2c9166820cf5fcae6a277334e1614a35dc"},"payload":{"report_sha256":"9788013d9f9860729aa3416a7dd77610e00c5bfb72ede91049a3d869feb5da40"},"schema_version":1,"source_revision":"dd16192953c130d738716238525760de73343e09"} diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/evidence/task10a/time.json b/tracks/qcs/solutions/frustration-free/challenge-113/evidence/task10a/time.json new file mode 100644 index 000000000..2bbfb2e65 --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/evidence/task10a/time.json @@ -0,0 +1 @@ +{"evidence_type":"time","inputs":{"raw_time":"ab0c87e1f3cc74f46ce7d11a5e59e6b78ce59c977249506e5043968309b8891f","trial":"f37a68183e61f93edd69fecffe02e31c8be1d6d981e259b1ad3464fe66fc01fe"},"payload":{"command_sha256":"1cd7abe89e5e4fb2f1214aef863e45fae8ae4923ad8d1d83e0ecb7bc2ac240b8","cpu_percent":847,"exit_status":0,"peak_rss_kib":864260,"system_seconds":3.23,"user_seconds":182.45,"wall_seconds":21.91},"schema_version":1,"source_revision":"dd16192953c130d738716238525760de73343e09"} diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/evidence/task10a/validation.json b/tracks/qcs/solutions/frustration-free/challenge-113/evidence/task10a/validation.json new file mode 100644 index 000000000..41bbff53c --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/evidence/task10a/validation.json @@ -0,0 +1 @@ +{"evidence_type":"validation","inputs":{"raw_validation":"0b3b97193332e6dfb8276be908a9758261cf3176445e205c2c8ba54e5f2627b7","ready":"6fa66678afe7adacdec82c788916cada2a3911f147c37bd29bb6365a6a64cb89","trial":"f37a68183e61f93edd69fecffe02e31c8be1d6d981e259b1ad3464fe66fc01fe"},"payload":{"completed":1,"errors":[],"expected":1,"pending":0,"valid":true},"schema_version":1,"source_revision":"dd16192953c130d738716238525760de73343e09"} diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/pyproject.toml b/tracks/qcs/solutions/frustration-free/challenge-113/pyproject.toml new file mode 100644 index 000000000..e51e01bca --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/pyproject.toml @@ -0,0 +1,19 @@ +[project] +name = "challenge-113-sim-to-real" +version = "0.1.0" +requires-python = ">=3.12,<3.13" +dependencies = [ + "cma", + "jax", + "matplotlib", + "numpy", + "scipy", +] + +[dependency-groups] +dev = ["pytest"] + +[tool.pytest.ini_options] +pythonpath = ["src"] +testpaths = ["tests"] +markers = ["integration: exercises full scientific acceptance paths"] diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/references/.gitignore b/tracks/qcs/solutions/frustration-free/challenge-113/references/.gitignore new file mode 100644 index 000000000..39910209f --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/references/.gitignore @@ -0,0 +1,3 @@ +.raw/ +.figures/ +manifest.json diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/references/ref.bib b/tracks/qcs/solutions/frustration-free/challenge-113/references/ref.bib new file mode 100644 index 000000000..4f8e40a8f --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/references/ref.bib @@ -0,0 +1,137 @@ +@article{khaneja_2005_optimal, + author = {Navin Khaneja and Timo Reiss and Cindie Kehlet and Thomas Schulte-Herbrueggen and Steffen J. Glaser}, + title = {Optimal control of coupled spin dynamics: design of NMR pulse sequences by gradient ascent algorithms}, + year = {2005}, + journal = {Journal of Magnetic Resonance}, + doi = {10.1016/j.jmr.2004.11.004}, + keywords = {quantum-circuit-simulation} +} + +@article{caneva_2011_chopped, + author = {Tommaso Caneva and Tommaso Calarco and Simone Montangero}, + title = {Chopped random-basis quantum optimization}, + year = {2011}, + journal = {Physical Review A}, + eprint = {1103.0855}, + archivePrefix = {arXiv}, + doi = {10.1103/PhysRevA.84.022326}, + keywords = {quantum-circuit-simulation} +} + +@article{petersson_2020_discrete, + author = {N. Anders Petersson and Fortino M. Garcia and Anil R. Bhat and Daniel M. Copeland and Marissa D. Grace and Jason L. DuBois}, + title = {Discrete Adjoints for Accurate Numerical Optimization with Application to Quantum Control}, + year = {2020}, + eprint = {2001.01013}, + archivePrefix = {arXiv}, + keywords = {quantum-circuit-simulation} +} + +@article{egger_2014_adaptive, + author = {Daniel J. Egger and Frank K. Wilhelm}, + title = {Adaptive Hybrid Optimal Quantum Control for Imprecisely Characterized Systems}, + year = {2014}, + journal = {Physical Review Letters}, + eprint = {1402.7193}, + archivePrefix = {arXiv}, + doi = {10.1103/PhysRevLett.112.240503}, + keywords = {quantum-circuit-simulation} +} + +@article{kelly_2014_optimal, + author = {Julian Kelly and Rami Barends and Austin G. Fowler and Anthony Megrant and Evan Jeffrey and Theodore C. White and Daniel Sank and Josh Y. Mutus and Brooks Campbell and Yu Chen and Zijun Chen and Ben Chiaro and Andrew Dunsworth and I.-C. Hoi and Charles Neill and Peter J. J. O'Malley and Chris Quintana and Peter Roushan and Amit Vainsencher and John Wenner and Alexey N. Korotkov and Andrew N. Cleland and John M. Martinis}, + title = {Optimal Quantum Control Using Randomized Benchmarking}, + year = {2014}, + journal = {Physical Review Letters}, + eprint = {1403.0035}, + archivePrefix = {arXiv}, + doi = {10.1103/PhysRevLett.112.240504}, + keywords = {quantum-circuit-simulation} +} + +@article{judson_1992_teaching, + author = {Richard S. Judson and Herschel Rabitz}, + title = {Teaching Lasers to Control Molecules}, + year = {1992}, + journal = {Physical Review Letters}, + doi = {10.1103/PhysRevLett.68.1500}, + keywords = {quantum-circuit-simulation} +} + +@article{rabitz_2004_quantum, + author = {Herschel A. Rabitz and Michael M. Hsieh and Carey M. Rosenthal}, + title = {Quantum Optimally Controlled Transition Landscapes}, + year = {2004}, + journal = {Science}, + doi = {10.1126/science.1093649}, + keywords = {quantum-circuit-simulation} +} + +@article{shen_2006_quantum, + author = {Zhenwen Shen and Michael Hsieh and Herschel Rabitz}, + title = {Quantum optimal control: Hessian analysis of the control landscape}, + year = {2006}, + journal = {The Journal of Chemical Physics}, + doi = {10.1063/1.2198836}, + keywords = {quantum-circuit-simulation} +} + +@article{roslund_2014_dynamic, + author = {Jonathan Roslund and Herschel Rabitz}, + title = {Dynamic Dimensionality Identification for Quantum Control}, + year = {2014}, + journal = {Physical Review Letters}, + doi = {10.1103/PhysRevLett.112.143001}, + keywords = {quantum-circuit-simulation} +} + +@article{rabitz_2007_quantum, + author = {Herschel Rabitz and Michael Hsieh and Carey Rosenthal}, + title = {Quantum Control Landscapes}, + year = {2007}, + eprint = {0710.0684}, + archivePrefix = {arXiv}, + keywords = {quantum-circuit-simulation} +} + +@article{sagun_2017_empirical, + author = {Levent Sagun and Utku Evci and V. Ugur Guney and Yann Dauphin and Leon Bottou}, + title = {Empirical Analysis of the Hessian of Over-Parametrized Neural Networks}, + year = {2017}, + eprint = {1706.04454}, + archivePrefix = {arXiv}, + keywords = {quantum-circuit-simulation} +} + +@article{day_2019_glassy, + author = {Andrew G. R. Day and Marin Bukov and Phillip Weinberg and Pankaj Mehta and Dries Sels}, + title = {Glassy Phase of Optimal Quantum Control}, + year = {2019}, + journal = {Physical Review Letters}, + eprint = {1803.10856}, + archivePrefix = {arXiv}, + doi = {10.1103/PhysRevLett.122.020601}, + keywords = {quantum-circuit-simulation} +} + +@article{larocca_2023_theory, + author = {Martin Larocca and Nathan Ju and Diego Garcia-Martin and Patrick J. Coles and M. Cerezo}, + title = {Theory of overparametrization in quantum neural networks}, + year = {2023}, + journal = {Nature Computational Science}, + eprint = {2109.11676}, + archivePrefix = {arXiv}, + doi = {10.1038/s43588-023-00467-6}, + keywords = {quantum-circuit-simulation} +} + +@article{larocca_2022_diagnosing, + author = {Martin Larocca and Piotr Czarnik and Kunal Sharma and Gopikrishnan Muraleedharan and Patrick J. Coles and M. Cerezo}, + title = {Diagnosing Barren Plateaus with Tools from Quantum Optimal Control}, + year = {2022}, + journal = {Quantum}, + eprint = {2105.14377}, + archivePrefix = {arXiv}, + doi = {10.22331/q-2022-09-29-824}, + keywords = {quantum-circuit-simulation} +} diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/references/rendered/0710.0684_quantum-control-landscapes.md b/tracks/qcs/solutions/frustration-free/challenge-113/references/rendered/0710.0684_quantum-control-landscapes.md new file mode 100644 index 000000000..6daf7a928 --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/references/rendered/0710.0684_quantum-control-landscapes.md @@ -0,0 +1,1911 @@ +--- +source: "https://arxiv.org/abs/0710.0684" +type: "arxiv" +canonical_id: "0710.0684" +title: "Quantum control landscapes" +authors: "Herschel Rabitz, Michael Hsieh, Carey Rosenthal" +year: "2007" +venue: "International Reviews in Physical Chemistry" +arxiv_id: "0710.0684" +doi: "10.1080/01442350701633300" +full_text: yes +--- + +# Quantum control landscapes + +**Authors:** Herschel Rabitz, Michael Hsieh, Carey Rosenthal + +**Citation:** International Reviews in Physical Chemistry, vol. 26, pp. 671 - 735, 2007 + +**arXiv:** [0710.0684](https://arxiv.org/abs/0710.0684) + +**DOI:** [10.1080/01442350701633300](https://doi.org/10.1080/01442350701633300) + +## Abstract + +Numerous lines of experimental, numerical and analytical evidence indicate that it is surprisingly easy to locate optimal controls steering quantum dynamical systems to desired objectives. This has enabled the control of complex quantum systems despite the expense of solving the Schrödinger equation in simulations and the complicating effects of environmental decoherence in the laboratory. Recent work indicates that this simplicity originates in universal properties of the solution sets to quantum control problems that are fundamentally different from their classical counterparts. Here, we review studies that aim to systematically characterize these properties, enabling the classification of quantum control mechanisms and the design of globally efficient quantum control algorithms. + +## Full Text + +# Quantum Control Landscapes + +## Raj Chakrabarti and Herschel Rabitz + +Department of Chemistry, Princeton University, Princeton, New Jersey 08544, USA + +Numerous lines of experimental, numerical and analytical evidence indicate that it is surprisingly easy to locate optimal controls steering quantum dynamical systems to desired objectives. This has enabled the control of complex quantum systems despite the expense of solving the Schrodinger equation in simulations and the complicating effects of environmental decoherence in the laboratory. Recent work indicates that this simplicity originates in universal properties of the solution sets to quantum control problems that are fundamentally different from their classical counterparts. Here, we review studies that aim to systematically characterize these properties, enabling the classification of quantum control mechanisms and the design of globally efficient quantum control algorithms. + +PACS numbers: 03.67.-a,03.67.Lx,03.65.Yz,02.30.Yy + +### Contents + +- I. Introduction 2 + +- II. Hamiltonian-independent properties of solution sets to quantum control problems 4 A. Observable maximization 6 B. Quantum gate control 8 C. Continuous variable quantum control 9 + +- III. Analytical features of quantum control landscape geometry 10 A. The role of analysis in exploring control landscape geometry 10 + +- B. Analytical solutions to state control problems 13 + +- C. Analytical solutions to gate control problems 14 + +- IV. Numerical exploration of quantum control landscape level sets 16 A. Algorithms for level set exploration 16 B. Quantum control mechanisms and robustness 17 + +- C. Hamiltonian-dependence of landscape level set geometry 18 + +- V. Experimental exploration of quantum control landscapes 20 A. Level sets 20 B. Landscape topology 22 + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0001-08.png) + + +> ∗Electronic address: rajchak@princeton.edu + +- VI. Quantum system controllability and landscape structure 24 A. Exact-time controllability of discrete quantum systems 25 + +- B. Controllability of classical and continuous variable quantum systems 26 + +- VII. Computational complexity of quantum control landscapes 26 A. Gradient flows and search complexity 27 B. Relation between dynamic and kinematic gradient flows 30 + +- VIII. Global search algorithms for quantum control 31 A. Scalar and matrix tracking algorithms 31 B. Extremals of the input-state map 34 + +- IX. Open quantum systems 36 X. Conclusion and future challenges 37 A. Mathematical appendices 38 1. Critical topology 38 a. Landscape mapping 38 b. Hessian quadratic form: observable maximization 38 + +- 2. Maximum principle and adjoint control systems 38 + +- 3. Rotating wave approximation 39 4. Analytical solutions to state and gate control problems 40 a. Low-dimensional gate control problems 40 b. Low-dimensional state control problems 41 + +- 5. Diffeomorphic homotopy on control landscapes 42 + +2 + +6. Controllability on compact Lie groups 43 7. Kraus superoperator formalism 44 8. Symplectic propagators 44 + +Acknowledgements 44 + +- References 44 + +### I. INTRODUCTION + +The notion of controlling quantum systems seems inherently problematic on several counts. First, the extreme sensitivity of quantum systems to environmental interactions would appear to place limits on the maximal achievable control fidelity. Second, from a numerical perspective, given the considerable cost of propagating the Schrodinger equation, unless the search space for optimal controls has particularly simple properties, it would appear impossible to locate controls for all but the smallest quantum systems in reasonable time. However, once the methods of optimal control began to be applied to molecular systems (thanks to remarkable advances in laser pulse shaping technology) it rapidly became clear that quantum control was not an ill-fated concept, but rather, that controlling quantum systems was surprisingly easy. In the laboratory, this conclusion was particularly apparent in the case of so-called adaptive learning control of quantum dynamics, wherein learning (i.e., typically genetic) algorithms are applied to search the space of laser control parameters for the maximization of the expectation value of a quantum observable. This search space is high-dimensional, normally suggesting that it should be replete with local optima and other unfavorable features that would trap unsophisticated search algorithms, especially in the presence of environmental decoherence. The repeated successes of quantum optimal control experiments and simulations indicated that the so-called ”curse of dimensionality”, common in the theory of optimization in high-dimensional spaces, was not prohibitive here. + +This attractive circumstance for quantum control rests on the mathematical underpinnings of quantum theory being surprisingly simple, owing to the linearity of quantum mechanics and the unitarity of the accompanying transformations. Although quantum systems can be highly sensitive to environmental perturbations, the rules governing their dynamics are in many ways simpler than those governing classical dynamics. Furthermore, the presence of an environment, rather than being an impediment, may under the right conditions aid + +the control process. Recent work has aimed to understand the precise mathematical properties of quantum mechanics are responsible for the surprising simplicity with which quantum phenomena can be controlled. Analytical, numerical and experimental treatments of the problem have been explored. In order to enable the systematic study of these features, the notion of a quantum control landscape, defined as the map between the space of time-dependent controls and associated values of the objective functional, was introduced (Figs. 1, 2). + +From an analytical perspective, it was recently found that for several classes of low-dimensional problems, it is possible to exactly solve for quantum optimal controls, without any need for numerical search. By contrast, for the analogous classical problems, analytical solutions do not exist. Of course, analytical solutions are still only possible for special small systems; however, beyond this, it has become clear that the numerical or experimental search for optimal controls is often easier for quantum systems than for classical systems. In this regard, the topology of the search space is of fundamental importance. Evidence suggests that the landscapes for both observable maximization and control of dynamical transformations have simpler topological properties for quantum versus classical systems, contributing to rapid convergence of numerical or experimental searches for effective controls. Moreover, besides the simplicity of locating quantum controls, it has been observed that the controls themselves have remarkably simple functional properties, in some cases enabling a direct interpretation of the mechanisms involved in steering about the dynamics. + +The ease of locating optimal quantum controls, and the comparatively simple structure of these controls, have pervasive implications for a wide range of quantum technologies. The study of quantum control landscapes is motivated by the practical goals of achieving higher objective function yields and designing control fields with desired properties, but in order to attain these goals, it is necessary to embrace the mathematical framework that underlies the remarkable properties of these landscapes. The origin of these counterintuitive properties, and their differences with respect to classical control, constitute the primary subject of this review. + +The review is organized as follows. In section II, we examine the topology of solution sets to quantum observable and unitary transformation control problems. Section III reviews the analytical solutions obtained for low-dimensional quantum control problems. In section IV, we consider numerical studies of the solution sets to higher dimensional quantum control problems without + +3 + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0003-01.png) + + +FIG. 1: A control landscape is defined as the map between the time-dependent control Hamiltonian and associated values of the control cost functional. The entry point into their study is (a) the controllability of the quantum system, which allows search algorithms to freely traverse the landscape. Quantum control landscape features can be conveniently subdivided into those pertaining to (b) critical topology, i.e., the maxima, minima and saddle points of the landscape, and (c) landscape geometry, namely the characteristic local structures encountered while climbing toward the global optimum. Study of the geometry of quantum control landscapes reveals (d) the existence of multiple control solutions corresponding to any given objective function value. The topology and geometry of quantum control landscapes together determine (e) the search complexity of the control problems, i.e. the scaling of the effort required to locate optimal controls. An ultimate goal in the study of quantum control landscapes is the design of global search algorithms that attain lower bounds on this search complexity. Such algorithms may be applied to either of the two major classes of quantum system manipulation problems, (f) control of quantum observables and (g) control of quantum dynamical transformations (i.e., unitary propagators). An overarching conclusion pertaining to both these types of landscapes is that they contain no suboptimal traps, which has broad-scale implications for both the experimental and computational feasibility of quantum control. + +analytical solution, focusing on the degeneracy in the set of controls that reach the same objective. Then, section V reviews experimental work that has probed the structure of these level sets of multiple solutions, as well as the topology of quantum control landscapes. Section VI examines how the controllability of quantum systems, compared to that of classical systems, impacts control landscape properties, in particular with respect to search + +efficiency. In section VII and VIII, we review approaches to the quantification of quantum control search effort complexity and the design of global search algorithms that aim to attain lower bounds on complexity scaling. In Section IX, we consider the effects of quantum decoherence on the structure of quantum control landscapes derived in previous sections. Finally, in section X, we draw conclusions and discuss future directions. + +4 + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0004-01.png) + + +FIG. 2: Schematic representation of a quantum control landscape depicting various features discussed in the review. xi, xj indicate two of possibly many control degrees of freedom, and J denotes objective function value. A point on the landscape corresponds to a time-dependent control field. a) Critical points of the map correspond to locally optimal solutions to the control problem. The number of positive, negative, and null Hessian eigenvalues at these points determine whether they are saddle points or local traps (Section II). In the most common case of state-to-state population transfer, there are no local optima and the global maximum is a continuous manifold, depicted in the picture as the solid curve. Level sets (b) of the control landscape correspond to control fields that produce the same objective function value at the final dynamical time T (shown as a dotted line). Any point on the landscape corresponds to a control field ε(t), with one on the extremal manifold indicated. + +### II. HAMILTONIAN-INDEPENDENT PROPERTIES OF SOLUTION SETS TO QUANTUM CONTROL PROBLEMS + +The most fundamental property of landscapes of solution sets to variational problems is their critical topology, i.e., the number of solutions, their associated functional values, and their optimality status (Fig. 1). In the context of optimal control theory, these critical points correspond to (possibly suboptimal) solutions of the optimal control problem, including both global and local minimizers of the objective functional. + +An early work [1] explicitly explored the multiplicity of solutions to quantum optimal control problems aimed at the maximization of the expectation value of an ob- + +servable operator at a final dynamical time T . Through the application of a perturbation theory approach to the nonlinear variational equation, it was shown that in general, a denumerably infinite number of solutions (control fields) exist to such quantum control problems. Moreover, multiple unitary propagators U (T ) are typically associated with the various possible local and global optima of the objective. Recent work, discussed throughout this review, has aimed to identify how the infinite number of solutions to quantum optimal control problems are distributed among the global and local optima of the control landscape. The optimality status of these solutions plays an important role in determining the performance of local search algorithms as they traverse the landscape. A remarkable feature of quantum opti- + +5 + +mal control landscapes is that these properties can all be determined analytically for many problems of interest, whereas for general variational problems outside of quantum control, such information is exceedingly difficult to acquire. Moreover, many of these properties are independent of the Hamiltonian of the quantum system. + +In this section we review work aimed at characterizing the critical topologies of quantum optimal control problems. In the absence of additional information, it is natural to expect that the control landscape will possess multiple local maxima and minima that are capable of trapping the search for optimal controls at suboptimal values of the objective. We will show that in stark contrast to this generically expected situation, the critical topologies of quantum optimal control landscapes are surprisingly simple. In what follows we consider a controllable (see Appendix A 6 for a review of the definition of controllability) quantum system of N discrete levels whose dynamics are driven by the Hamiltonian H = H [H0, {εk}] , depending on a free Hamiltonian H0 describing the uncontrolled evolution of the system and an appropriate set {εk} of control variables (e.g., phases and amplitudes in an optimal control experiment (OCE) pulse shaper). + +A generic quantum optimal control cost functional can be written: + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0005-04.png) + + +where β(t) is a Lagrange multiplier operator constraining the quantum system dynamics to obey the Schr¨odinger equation, ε(t) is the time-dependent control field, and λ weights the importance of an auxiliary physically motivated penalty term on the field. This latter penalty decreases the degeneracy of solutions to the optimal control problem; a common choice for f0 in simulations is 1wheres(t)isthepulseenvelope, s(t)|ε(t)|2, corresponding to a penalty on the total field fluence. Other, even more general cost functions can easily be generated with alternate choices for f0 as well as additional terms involving the evolving quantum state. + +The two most common types of quantum optimal control problems are the maximization of the expectation value of a Hermitian observable and the maximization of + +the fidelity of a quantum unitary transformation. Optimizing the expectation value of a Hermitian observable operator describes a broad variety of problems in quantum control, such as performing selective chemical fragmentation and rearrangement [2, 3], redirecting energy transfer in biomolecules [4], creating ultra-fast optical switches and tailoring high harmonic generation [5]. This problem corresponds to the following choice of Φ: + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0005-08.png) + + +where ρ(0) is the initial density matrix of the system, Θ is an arbitrary observable operator, and T is the final dynamical time. + +The optimal control of quantum unitary transformations has recently received increasing attention due to its applications to the field of quantum information processing (QIP). Over the past several years, it has become clear that the physical implementation of logical gates in QIP, which are represented by unitary propagators, may be facilitated by optimal control theory (OCT) [6, 7, 8, 9]. However, as we will see below, the optimal control of unitary transformations also has applications to population transfer in atoms and molecules. Within OCT, the problem of maximizing the fidelity of a dynamical transformation W can be framed using [6]: + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0005-11.png) + + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0005-12.png) + + +where W is the target unitary transformation. + +Solutions to these optimal control problems correspond to the condition δεδJ(t)=0.Inthissection,we assume λ = 0, and show that under this assumption many properties of the critical points of the functionals Φ1 and Φ2 can be characterized analytically. An infinitesimal functional change in the Hamiltonian δH(t) produces an infinitesimal change in the dynamical propagator U (t, 0) as follows: + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0005-15.png) + + +It can be shown that for the objective functions Φ1 and Φ2 the respective changes in Φ are δΦ1 = −Tr� [Θ(T ), ρ(0)] U (T, 0)δU (T, 0)�, where Θ(T ) ≡ U (T )ΘU (T ), and δΦ2 = Tr�(W U − U W )U (T, 0)δU (T, 0)�. In the special case of the electric dipole approximation, the Hamiltonian has the form + +6 + +H(t) = H0 − µ · ε(t), where H0 is the internal Hamiltonian of the system and µ is the electric dipole operator. Then δH(t) = ▽εH(t) · δε(t), U (T, 0)δU (T, 0) = T − ℏi �0U †(t, 0)▽εH(t)U(t, 0) · δε(t)dt,andthegradients of the control objective functionals can be written: + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0006-02.png) + + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0006-03.png) + + +and + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0006-05.png) + + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0006-06.png) + + +where B(t) ≡− ℏiU †(t, 0)▽εH(t)U(t, 0),andwehave adopted the shorthand notation U ≡ U (T ). Within the dipole approximation, B(t) = − ℏiU †(t, 0)µU(t, 0). + +The variational problems of optimal control theory admit two types of minimizers1 . According to the chain rule, + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0006-09.png) + + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0006-10.png) + + +The first type of minimizer corresponds to those control Hamiltonians that are critical points of the control objective functional, but are not critical points of the map between control fields and associated dynamical propagators (i.e., points at which ddUJ=0,whiletheFrechet derivative mapping from the control variation δε(t) to δU (T ) at t = T is surjective). The second type corresponds to critical points of the latter map (i.e., points at which the mapping from δε(t) to δU (T ) is not locally surjective) [10]. In this section, we consider the first type of critical point, which are referred to as kinematic critical points or normal extremal controls. The second type, which are called abnormal extremal controls, are comparatively rare in quantum control problems, and we defer their study to section VII. + +The class of normal extremal controls is Hamiltonianindependent and captures the most universal topological + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0006-13.png) + + +> 1 In mechanics, the Lagrangian functional that determines the equations of motion is uniquely determined by symmetries of the system. However, in optimal control theory, the objective functional, which determines the control law, is chosen by the controller. This distinction lends an additional component to the study of the topology of optimal control problems, namely the topology of the map between dynamical propagators and associated values of the chosen objective function. + +features of general quantum control landscapes. Because the map ε(t) → U (T ) is locally surjective at these points, each of the matrix elements Upq must be uniquely addressable by the control field ε(t) for all p and q values in keeping with U being unitary, i.e., the set of N2 functionsδU δε(pq t)shouldbelinearlyindependent.Hence,the critical condition for normal extremal controls is equivalent to dd UJ= 0forarbitrary δεδU(t).Moreover,itcanbe shown (see Appendix A 1 a) that the optimality status (i.e., minimum, maximum or saddle point) of a critical control field ε(t) will be equivalent to that of the resulting propagator U (T ) on the unitary group U (N ). The local surjectivity of ε(t) → U (T ) has important connections to the controllability of the quantum system, discussed in sections VI and VII. In what follows, we provide expressions for the gradient and Hessian of the above objective functions on both the domain of control fields and unitary propagators, and use these results to characterize their critical topologies. + +### A. Observable maximization + +In 1937, John von Neumann first addressed the critical topology of a problem that has direct implications for optimizing quantum observables [11, 12]. Although it is unclear whether von Neumann anticipated the applications of his work to quantum control, this paper may be considered the first work in the theory of quantum optimal control landscapes. This work was recently extended by several authors [13, 14]. + +Within the electric dipole approximation, the gradient (6) can be explicitly written [15]: + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0006-19.png) + + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0006-20.png) + + +where the initial density matrix is given as ρ(0) = n n �i=1pi|i⟩⟨i|, p1>...>pn>0, �i=1pi=1,and µ(t) ≡ U (t)µU (t). The local surjectivity of ε(t) → U (T ) at normal extremal controls implies that the N2 functions of time ⟨i|µ(t)|j⟩ are linearly independent. As discussed above, under this assumption the critical condition is equivalent to that on the domain of unitary + +7 + +propagators, dU1 = 0. Because the gradient δεδΦ(t1)de- pends on the eigenvalue spectra of ρ(0) and Θ, it is convenient to simplify the analysis by investigating the critical topology on the domain U (N ). Expanding the argument of the objective function by U → U exp (iAs), where s parametrizes an arbitrary curve in the Lie algebra of U (N ) in the direction A, the critical condition dΦdU1= 0canbeexpressedas + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0007-02.png) + + +The maximal subset of U (N ) which satisfies this condition is composed of the matrices of the form + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0007-04.png) + + +where Pl, l = 1, · · · , N ! is an N −fold permutation matrix whose nonzero entries are complex numbers eiφ1 , · · · , eiφN of unit modulus, and ρ(0) = Q εQ and Θ = R λR. ε1, ε2, ..., εN and λ1, λ2, ..., λN are the eigenvalues of ρ(0) and Θ with associated unitary diagonalization transformations Q and R, respectively. The critical set is the union of N −torii lT N l with each torus corresponding to a distinct permutation Pl. + +The number of critical submanifolds corresponding to suboptimal landscape values scales factorially with system dimension for fully nondegenerate ρ(0) and Θ. If ρ(0) and Θ have arbitrary numbers of degeneracies D1, ...Dm, and E1, ..., En, respectively, it can be shown [16] that the critical submanifold dimension on the domain U (N ) for a particular critical manifold Mk is + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0007-07.png) + + +where the ol’s are the overlap numbers (numbers of overlapping elements) between the degenerate blocks D1, ..., Dm and E1, ..., En, for the permutation matrix corresponding to that manifold. These degeneracies cause neighboring submanifolds to merge, and give rise to subspaces that are invariant to eigenvalue permutations. If ρ(0) or Θ is a pure state projector, the number of critical manifolds scales linearly with Hilbert space dimension N [17]. In the limiting case where both ρ(0) and Θ are pure state projectors, Φ at the extrema only has the values zero and unity, corresponding respectively to no control or perfect control (i.e., the landscape is convex). + +In the electric dipole approximation, the Hessian of the objective function can be written + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0007-10.png) + + +The Hessian quadratic form (HQF), defined as + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0007-12.png) + + +where ω(t) is an arbitrary real function, is a polynomial representation of the Hessian that facilitates the identification of increasing, null and decreasing directions at each critical point. The explicit representation of the HQF for the general case of nondegenerate ρ and Θ is complicated and is reviewed in Appendix A.1.b. Based on this representation, it can be shown that all of the suboptimal critical submanifolds corresponding to Φ values less than the global maximum are saddle regions, and thus will not act as traps for optimal control searches. + +As we will show in Section III below, the dimension of the global optimum on U (N ) is useful for exploring the degeneracy of solutions to quantum observable control problems. This number ranges from N , for fully nondegenerate ρ(0) and Θ, to N2 − (2N − 2), when ρ(0) and Θ are both pure state projectors. In the former case, the number of decreasing directions at the global maximum is the greatest (N2 − N ), whereas in the latter case it is the smallest (2N − 2). Note that the landscape mapping analysis in Appendix A 1 a indicates that the number of positive and negative principal axis directions of the Hessian matrix will be preserved upon passage from the domain U (N ) to the domain ε(t), with the remainder of the directions on ε(t) being flat. A recent numerical analysis [18] confirmed these predictions, by considering the problem of optimizing the expectation value of a pure state projector over a N = 4 quantum system, initialized in a pure state, with the goal of |0⟩→|3⟩ population transfer. In this case, the Hessian can be expanded on a basis of 2N − 2 linearly independent functions βl, as + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0007-15.png) + + +8 + +where η is a vector of appropriate control parameters. Diagonalization of the Hessian evaluated at the landscape maximum revealed 10 = 42 − 2 × 4+2 zero-valued eigenvalues, as expected (Fig. 3). Importantly, when the dipole coupling strengths were reduced to negligible values for all but two of the levels, the number of zerovalues Hessian eigenvalues dropped to 2 = 22 − 2 × 2+2, revealing the reduction of the 4-level to a quasi 2-level system. Thus, the robustness of observable maximization control solutions depends not only on the actual Hilbert space dimension, but also on the effective number of states that are accessible to the dynamics. + +A related work recently studied the critical topology of observable maximization on the domain SU (N ) (instead of U (N )) [19]. It was shown that the set of maxima decomposes into two connected components, and an explicit description of both components was derived. Separately, Glaser et al. [20] examined the topology of observable maximization for non-Hermitian observables, such as those operators that arise in quadrature detection. These works, as well as others [8, 9], were concerned with the problem of identifying optimal controls for quantum observables in two steps: first, solving numerically for the set of unitary matrices U that maximize the expectation value Tr(Uρ(0)U Θ) of the observable Θ, and then, finding a control field ε(t) that produces the quantum gate U at time t = T . We discuss analytical solutions to the latter problem in section III below. + +plicitly written [21, 22] + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0008-04.png) + + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0008-05.png) + + +The critical topology of the gate fidelity cost function on U (N ) was first studied by Frankel [23]. Assuming local surjectivity of ε(t) → U (T ), a necessary and sufficient condition for the critical points is W U = U W or + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0008-07.png) + + +The solutions to this equation are the roots of I, i.e. W U = diag(λ1, ..., λn, λi = (−1)ni ), ni = 0, 1. These solutions fall into equivalence classes indexed by the number of eigenvalues λi = 1. Thus, there are a total of N + 1 critical manifolds, taking on Φ values of 0, 4, ..., 4N . The number of suboptimal critical regions hence grows only linearly with respect to the system Hilbert space dimension N , a slower scaling than that of the landscape for observable maximization when both ρ and Θ are nondegenerate full rank matrices. + +The Hessian at the critical points is + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0008-10.png) + + +### B. Quantum gate control + +which can be expanded as [22] + +In the electric dipole approximation, the critical point condition corresponding to the gradient (7) can be ex- + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0008-14.png) + + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0008-15.png) + + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0008-16.png) + + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0008-17.png) + + +The number of positive, negative and null directions at the critical points can be determined by simple inspec- + +tion of the Hessian quadratic form. The expression for the HQF is + +9 + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0009-01.png) + + +FIG. 3: The dominant eigenvalues of the Hessian matrix for state-to-state population transfer in a simple four level system. Only the last 20 eigenvalues are shown, and the remaining ones are essentially zero. For this system with dimension N=4, it is evident that the 2N-2 rule is satisfied with six nonzero eigenvalues being present. (From ref [18].) + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0009-03.png) + + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0009-04.png) + + +At the suboptimal critical points, there are N −m even and m odd integers ni. It can be shown that the number of positive and negative Hessian eigenvalues equals the number of odd and even ni, respectively, and that the remaining eigenvalues are zero [21]. The numbers of positive and negative directions at a critical point m are thus + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0009-06.png) + + +whereas all the remaining principal axis directions are flat. In particular, all the local suboptima are saddle manifolds, and we see again that there are no local traps in the quantum control landscape. In contrast to the multiplicity of unitary matrices that solve the observable maximization problem, the kinematic critical regions of + +the landscape corresponding to global optima are isolated unitary matrices [21], although an infinite number of controls may steer the system to those matrices. + +### C. Continuous variable quantum control + +The kinematic landscape critical topology for controlling continuous variable quantum dynamical transformations for systems with quadratic Hamiltonians was recently studied [24]. These systems are relevant for the implementation of continuous variable quantum information processing[25]. Continuous variable transformations can be realized by harmonic oscillators, molecular rotors, or coupled modes of the electromagnetic field. + +10 + +Dynamical transformations for such systems can be represented by symplectic propagators (Appendix A 8). + +The critical topology of such landscapes offer insight into the differences between control landscapes for discrete and continuous quantum systems. Since the (quantum) symplectic gate U is a faithful unitary representation of a symplectic matrix S, it is reasonable to define the gate fidelity analogously to that for discrete gates as + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0010-03.png) + + +where s and w denote phase space displacements. Importantly, the symmetries of this objective functional again permit an analytical characterization of the critical topology, although this topology is more complex than that of the control landscapes for discrete quantum systems. If we write the singular value decomposition of W as W = UEV , the critical submanifolds can be expressed as [24]: + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0010-05.png) + + +where R is an arbitrary orthogonal symplectic matrix in the stabilizer of E in OSp(2N, R): + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0010-07.png) + + +The characteristic matrix D consists of different operations on the separate modes represented by diagonal blocks of three different types, depending on the singular values of the target gate W . In particular, D, and hence the critical topology, differs depending on the degeneracy of the singular values. Again, all suboptimal critical points are found to be saddle manifolds, indicating that the control landscape for these infinite-dimensional quantum gates is devoid of local traps. + +Although this landscape is devoid of traps, it can be shown [26] that the lower symmetry of the continuous variable fidelity function, compared to that of the discrete quantum fidelity function, can result in a more rapid scaling of the number of critical manifolds. Moreover, the critical topology is dependent on the target gate (Fig. 4). + +The dynamical transformations of classical systems with quadratic Hamiltonians can also be represented by symplectic propagators. However, unlike quantum observables, classical observables may be expressed as arbitrary smooth functions on phase space (and hence the + +space of symplectic propagators); thus, unlike quantum control landscapes, classical observable control landscapes have no universally characterizable critical topology, either for quadratic or more general classes of Hamiltonians. + +For each of the control landscapes discussed above, an important issue is the size of the attracting regions of these critical manifolds and the behavior of the gradient flows of the objective function around them. Unlike the critical topology, these gradient flows (which represent a geometric property of the landscape) are Hamiltoniandependent, and we will revisit them, including their connection to topology, in Section VII. + +Although the above analytical results were derived under the assumption that the fluence penalty coefficient λ = 0, they remain valid in many cases in the presence of a significant cost on the field fluence. In particular, in the case of observable maximization where ρ(0) and Θ are pure state projectors, it was shown for the model system described in section IIA [18] that the Hessian retains 2N − 2 nonzero eigenvalues in the presence of substantial fluence costs. In the next section, we discuss how the imposition of such constraints facilitates the identification of analytical solutions for certain (low-dimensional) observable and gate control problems in quantum mechanics. + +### III. ANALYTICAL FEATURES OF QUANTUM CONTROL LANDSCAPE GEOMETRY + +### A. The role of analysis in exploring control landscape geometry + +The previous sections showed that the critical point topology of the most common quantum optimal control problems can be established analytically, and display properties favorable for optimal search. In this section, we show that analytical statements can also be made regarding the geometry of quantum control landscapes. The geometry of a control landscape can be broken down into two components: 1) the relationship among controls producing the same objective function value (level sets), and 2) the search trajectory followed in locating the optimal objective function value. + +The geometry of quantum control landscapes is Hamiltonian-dependent. The universal monotonicity of quantum control landscapes ensures the convergence of local algorithms, but does not provide a direct measure of the search effort involved in finding optimal solutions. + +11 + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0011-01.png) + + +FIG. 4: The scaling of the numbers of critical submanifolds for discrete quantum (unitary) and continuous quantum (symplectic) gate control landscapes with system size[24]. Degenerate/non-degenerate refer to the singular values of the symplectic matrix representing the continuous quantum propagator. Landscapes for the control of discrete quantum propagators all have identical critical topologies, whereas those for continuous quantum propagators are target-dependent. See the text for definitions of quantum gate terminology. + +ing optimal controls could be aided by analytical landscape geometry. Although these analytical results may not fully identify the solution set to an optimal control problem, they may nonetheless restrict its structure. + +In the case that no constraints are placed directly on the controls or on the time required for reaching the objective, an infinite number of solutions exist to quantum control problems. As such, analytical results pertaining to the geometry of the landscape are restricted to statements regarding generic features of the controls. When constraints or auxiliary costs are imposed in the objective function, it is possible in the case of many low-dimensional problems to explicitly solve for the optimal controls. In these cases, multiple solutions may still exist, but they are distributed among distinct unitary propagators. + +Studying analytical solutions to quantum control problems provides insight into the geometry of control landscapes in several ways. First, there can exist quantum symmetries that reduce the dimensionality of the domain of control fields ε(t) over which the control search must be carried out. From either a computational or experimental perspective, this means that simpler parametrizations of the control fields can be used in + +optimizations, thereby reducing search effort. + +Second, several important low-dimensional quantum optimal control problems are analytically soluble (integrable) once auxiliary constraints are imposed on the objective function. In particular, the problem of realizing a quantum unitary transformation in minimal time or with minimal fluence can be solved for Hilbert spaces of several different dimensions. As shown in Section II, a continuous submanifold of unitary matrices is associated with the maximization of any observable expectation value. The set of points on this submanifold can be identified (either analytically or numerically) at minimal cost. Therefore, a submanifold of the set of all solutions (one corresponding to each unitary matrix) to any observable control problem for such systems can be obtained through a combination of analytical and numerical methods with substantially reduced search effort, due to the existence of analytical solutions to gate control problems [8, 9]. This submanifold consists of all solutions that minimize the auxiliary cost (e.g., field fluence). Thus, analytical solutions to gate control problems provide a means of further delineating the level set geometry of observable control landscapes. + +For these reasons, we review in this section analyti- + +12 + +cal results pertaining to the solution of quantum control problems. We discuss 1) analytical results pertaining to control mechanisms in arbitrary Hilbert space dimension; 2) the integrability of low dimensional problems (which in some cases relies on 1), and 3) the reasons that integrability breaks down in higher dimensions, whereas mechanistic simplicity is retained. The basic theorem of optimal control theory used for solving problems of this type is the Pontryagin maximum principle, reviewed in Appendix A 2. Consider the control problem of minimizing a cost associated with steering the system + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0012-02.png) + + +from some initial state x(0) = x0 to some final state x1. For quantum gate and state control, we are˙ dealing with the right-invariant control systems U = f (U, ε) = − ℏi[Hd + µε(t)] Uandψ˙=f(ψ, ε)=irespectively(seeAppendixA 6fora[Hd + µε(t)] ψ, definition of right-invariance). Note that these equations can be generalized to the case of m-independent controls, which could take the form of, e.g., components of the time-dependent electromagnetic field coupled to independent Pauli spin operators in an NMR control experiment. When analytically solving for optimal controls satisfying Pontryagin’s maximum principle (PMP), the maximization of the function Φ in equation (1) is often imposed as a fixed constraint in addition to the Schrodinger equation, and the ”cost” to be minimized takes the form of the last term in this equation. Such problems are framed most conveniently in Hamiltonian form. If we denote the cost as 0Tf 0(x, ε)dt,thenthe PMP-Hamiltonian function is as: + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0012-04.png) + + +where the first term on the RHS is either a matrix or vector inner product, depending on whether the problem is defined on the space of state vectors or dynamical propagators, and the λ play the role of PMP-conjugate momenta (Appendix A 2). The PMP-Hamiltonian function takes on the following form for gate and state control, respectively: + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0012-06.png) + + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0012-07.png) + + +where M is the conjugate PMP-momentum for gate control, P is the conjugate PMP-momentum for state control, and where we have considered only the case of pure state population transfer in the latter case, for simplicity. The Pontryagin maximum principle (Appendix A 2) then specifies the PMP-Hamiltonian equations of ”motion” for the control system; the solutions to these equations of motion correspond to the solutions to the control problem, i.e., trajectory/control couples (x(t), ε(t)). + +T The auxiliary cost �0f 0dt can take on several canon- ical forms. The most common are (assuming m independent controls): 1) The field fluence for fixed transfer m time T , E = 0T �i=1ε i2dt,and2)Thetotaltransfer time, with fluences either unconstrained or subject to the constraint 0T �mi=1ε i2≤C,withCanarbitrary positive constant, on the field amplitudes. Note that in the case of 2), the final time in Φ(T ) can be taken to be a variable rather than a fixed parameter. An important distinguishing feature between solutions corresponding to these different auxiliary costs is the time-dependent structure of the corresponding optimal control fields. When controls are bounded, the optimal fields are typically resonant with the system transition frequencies, as discussed further below. By contrast, when controls are unbounded, the optimal fields are often singular (i.e., short sequences of hard pulses) [8]. + +Since landscape geometry is Hamiltonian-dependent, it is important to specify the class of Hamiltonians when studying analytical solutions. We restrict the analysis primarily to state and gate control problems defined on the special unitary group SU (N ). Consider the following right-invariant control system on U (N ) with m controls: + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0012-11.png) + + +The matrices Hd, µi, i = 1, ..., m are skew-Hermitian matrices. If we write H¯ d = DHd + Hd and µ¯i = Dµk + µk, i = 1, ..., m, with DHd = diag(1 2Tr( ¯Hd), 21Tr( ¯Hd)), Dµi = diag1 2Tr(¯µk), 21Tr(¯µi)), i=1, ..., m,thematrices DHd , Dµi give a pure phase contribution to the solution. These terms do not contribute to the relative phases of the components of the state vector and therefore can be neglected since states that differ only by a phase are physically indistinguishable. Thus, we consider the physically equivalent problem on the domain of traceless skew-Hermitian matrices, the Lie group SU (N ). + +Just as analytical solutions to the Schrodinger equation exist only for the simplest quantum systems, for- + +13 + +mal analytical solutions to the PMP for quantum control problems involving arbitrary Hamiltonians are very scarce. However, for certain classes of Hamiltonians, or under certain physically reasonable approximations, control problems of real practical interest may be integrable. In what follows, we assume that the rotating wave approximation (RWA), reviewed in Appendix A 3, holds for the dynamics under consideration. It is important to note that this latter condition is often not satisfied, necessitating the use of numerical methods to solve the most general class of control problems (Section IV). However, in several of the most commonly encountered quantum control problems, the RWA does hold to a reasonable approximation. + +The methods of geometric control theory and subRiemannian geometry [8, 27] provide a means of obtaining, in certain specific cases, analytical solutions for the optimal control fields reaching a given objective. Although geometric control theory was originally developed in the context of classical control, it has recently been shown that the Pontryagin maximum principle in a geometric framework can be used to obtain analytical solutions for optimal control fields for such lowdimensional quantum control problems. We examine these specific solutions after briefly reviewing analytical results pertaining to the control mechanisms for fluenceminimizing state controls. + +### B. Analytical solutions to state control problems + +For discrete quantum control problems, the existence of symmetries on the Hilbert space of states often allows a significant reduction in the dimensionality of the problem and the parameterization of the controls. This feature extends beyond the limited subset of lowdimensional problems with analytical solution to discrete quantum control problems in arbitrarily high dimensions. Let V (t) =�m i=1εi(t)µi denote the total time dependent control Hamiltonian. A problem of particular interest for chemical applications is where control laser fields couple only neighboring energy levels of the system, i.e., Vj,k = Vk,j = 0 if j = k ± 1. This is a common scenario in strong field control experiments. The optimal controls for these problems are often in resonance with the transition frequencies of the uncontrolled system. It can be shown that for auxiliary cost 1 above (i.e., fluence minimization) with Hamiltonians of this form, the controls will always satisfy a more general condition of ”weak” resonance. In either case, the search space of the problem is then reduced from the Hilbert sphere S2N−1 + +to SN . + +Definition 1 (Resonance, weak resonance of optimal controls) A control Vj,k(t) is said to be resonant with respect to the uncontrolled system with state function ψ if it has the following form: + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0013-07.png) + + +where Aj,k(.) : [0, T ] → R, Aj,k = −Ak,j, φj,k ≡ arg(ψj (0)) − arg(ψk(0)) ∈ [−π, π] . Physically, this means that the lasers oscillate with frequency (Ej − Ek)/2π; Aj,k describes the field amplitudes. A control Vj,k is weakly-resonant if it is resonant in each interval of time in which the states that it is coupling (i.e., ψj and ψk) are different from zero. [28]. + +Let us denote these latter intervals Ij,k,l, where j, k indexes the matrix elements of the Hamiltonian and l indexes the time interval where ψj, ψk = 0. Then according to the terminology above, Vj,k is weakly resonant if + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0013-10.png) + + +The phenomenon of resonance may be viewed geometrically as originating from a rotational symmetry in Hilbert space, under the transformation Rotα : (ψ1, ..., ψn) → (eiα1 ψ1, ..., eiαn ψn). The two admissible curves ψ(.) = (ψ1(.), ..., ψn(.)) and Rotα(ψ(.)) on [0, T ] have the same cost. In particular, any point within the set Tψ2 generated by the action of any element of Rotα(ψ(.)) on the state vector ψ2 can be reached at the same cost from any point within the set Tψ1, defined analogously. Let us represent the controls as + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0013-12.png) + + +decomposing them into non-resonant and resonant timedependent parts. If ψ(.) : [0, T ] → S2n−1 is a minimizing trajectory between sets Mψ1, Mψ2, then the transversality condition of the maximum principle (Appendix A 2) implies that ⟨P (t), T Mψ(t)⟩ = 0. We write ˙ ψj = k �ul j,kF j,kl(ψ) + v j,klGl j,k(ψ) �, where F (ψ) and G(ψ) are vector fields subsuming the action of the resonant contribution to the (real, imaginary) control Hamiltonians on the state vector ψ. It can then be shown [29] that Gl j,k(ψ)isalwaystangenttoasubmanifoldof S2n−1 whose points are reached with the same cost, i.e. Gl∀t. j,k(ψ(t)) ∈T Mψ(t), + +14 + +Therefore, in the maximum principle, ⟨P (t), Gl j,k(ψ(t))⟩ = 0, and the maximality condition of the maximum principle implies that vj,k(t) = 0. It follows that it is possible to join any two eigenstates ψj, ψk by a trajectory that is in resonance. For states that are not eigenstates, the weak resonance condition holds. State control and gate control for discrete quantum systems can both be framed in terms of the identification of geodesic trajectories under suitable metrics. The existence of a set of symmetries is important for the identification of analytical solutions to these problems. In particular, for state control problems, the reduction in control dimensionality following from resonance is essential for obtaining analytical solutions in low Hilbert space dimensions. + +Analytical solutions to problems of the general class described above can be obtained for population transfer in two- and three-level quantum systems, for off-diagonal control Hamiltonians whose Lie algebra spans the entire dynamical group. For instance, for population transfer in three-level systems using fluence as the cost with two controls, the PMP-Hamiltonian (31) becomes + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0014-03.png) + + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0014-04.png) + + +Since our primary focus here is the relationship between multiple quantum control solutions, we relegate a summary of this problem to Appendix A 4. For our present purpose, its most important features are that the assumption of resonant controls permits the (subRiemannian) problem to be mapped from S5 to S3 , and that the resulting reduced Hamiltonian system is integrable. In four dimensions, the corresponding state control problem resides on S7 . It appears that the Hamiltonian system given by the maximum principle is not integrable in this case, but the resonance condition still holds, and simplifies numerical search in this and higher dimensions. + +Although the the class of systems described above - where controls couple only two neighboring levels - is practically important, for more general systems the optimal controls may not be resonant or weakly resonant. Control mechanisms for such systems have been studied using numerical methods described in Section IV. + +### C. Analytical solutions to gate control problems + +As discussed above, quantum gate control solutions can be used to significantly reduce the search effort required to obtain solutions to the corresponding observable control problems. Analytical solutions have been found for gate control problems in dimensions 2,4 and 8 for important classes of Hamiltonians, under the assumption of unbounded controls [8, 9]. These problems can be framed as so-called adjoint control problems, a type of sub-Riemannian control problem where the optimal control minimizes length on a geometric space under constraints on the possible paths. An essential prerequisite for their analytical solution is that the Riemannian space display certain symmetries, which in this case are endowed by the geometry of the special unitary group. + +Instead of seeking fluence minimizing controls (cost 1) we consider here the minimization of the transfer time as the auxiliary cost (cost 2 above), with unbounded controls. This problem is particularly important for minimizing the time required for coherence transfer in nuclear magnetic resonance (NMR) experiments with radiofrequency pulses; the resultant time optimal pulses outperform those typically used in NMR by a significant margin [8, 9]. In the previous section we focused attention on systems whose control Hamiltonians span the entire dynamical group. Here, we examine the more common case where the control Hamiltonians span only a subgroup of the dynamical group; if controls are unbounded, the latter assumption is required for a lower bound on the evolution time to exist, since unbounded controls can attain the target in arbitrarily small time. The PMP-Hamiltonian is then + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0014-10.png) + + +Note (Appendix A2) that the maximum principle for time optimal control differs slightly from that for the problem of minimal field fluence with fixed final time. In fact, for time-optimal control problems where the controls do not span the dynamical group, framing the problem in terms of an adjoint control system (and an associated ”adjoint maximum principle”) facilitates solution, as shown below. + +To understand this approach, let G denote the special unitary group SU (N ). Under the assumption of full controllability of the system, the algebra s generated by the entire control system {Hd, µ1, ..., µm} is equal to the Lie subalgebra su(N ), and the corresponding group S is equal to G. We call the subalgebra generated by the controls {µ1, ..., µm} l, and the corresponding subgroup K. + +15 + +Since the controls are unbounded, any element of K can be reached in arbitrarily small time. Consider the problem of driving the evolution from U1 to U2 in the shortest possible time, and let the coset KU1 = {kU1|k ∈ K}. We then need to find the fastest way to move from KU1 to KU2, since the time required to travel anywhere within a coset is negligible. + +If we decompose G = p ⊕ l such that p is orthogonal to l, then p represents all possible directions to move in G/K. All directions in this set can be generated by using the control Hamiltonians to place the system at appropriate starting points k ∈ K, from which the drift Hamiltonian moves the system in the directions given by k1†Hdk1.However, we cannot access all these possible di- rections directly. All motion in G/K is generated by the drift Hamiltonian Hd. These directions are represented by + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0015-03.png) + + +called the adjoint orbit of Hd under the action of the subgroup K. This form of direction control has been defined as an adjoint control system, reviewed in Appendix A 2. + +The goal is to find the shortest path between two points in G/K under the constraint that the tangent direction must always be in the adjoint orbit (Fig. ??).The shortest paths between points on a manifold subject to the constraint that the tangent to the path always belongs to a subset of all permissible directions are called sub-Riemannian geodesics. The problem of finding time optimal control laws then reduces to finding subRiemannian geodesics in the space G/K, where the set of accessible directions is the set AdK(−iHd). This problem can be framed in terms of an adjoint cost function, f (P ) = Tr(λ HP ), with Pλ ∈ p. The adjoint-PMP Hamiltonian is h(P (t), λ(t), H(t)) = Tr(λ (t)H(t)P (t)). Solutions to the control problem follow from the adjoint maximum principle (Appendix A2). + +For one and two spin systems, G/K is a Riemannian symmetric space. In this case, the decomposition + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0015-07.png) + + +satisfies the commutation relations [l, l] ⊂ l, [p, l] = p, [p, p] ⊂ l. This property implies that the tangent vectors to the path through G/K must commute, since if they do not, a component of the path must lie within K, and hence the path cannot be time optimal. Let h ⊂ p denote a subspace of maximally commuting directions or generators in G/K space. Any unitary propagator UF + +can then be written UF = k2 exp(Y )k1, where Y ∈ h. According to the time-optimal torus theorem, the fastest way to reach UF is on the shortest path between the identity and the propagator exp(Y ) such that all tangent directions commute. If we express Y as + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0015-10.png) + + +it can be shown that the shortest path corresponds to the choice of αi with the smallest value of�p i=1αi. For two-dimensional systems, G/K is of rank 1, so Y = αAdk(Hd), α > 0 for some k ∈ K, and the time optimal path to coset exp(Y ) is to flow along Adk(Hd) for time α. Qualitatively, the optimal controls are pulsedrift-pulse sequences, i.e. hard pulses followed by evolution under drift and then some hard pulses again. For four-dimensional systems, it is necessary to pulse the controls intermittently to generate new ks, to create a chained pulse-drift-pulse sequence. + +Appendix A 4 reviews the explicit construction of the geodesic trajectories and associated minimal times for the problems in dimensions 4 and 8, based on Pontryagin’s maximum principle and the time-optimal torus theorem. By first numerically determining the set of unitary propagators mapping to a particular observable expectation value, and then applying these analytical results to identify the optimal field producing that propagator in minimal time, we can obtain analytical insight into the relationship among time-minimizing controls on any given level set of a quantum observable control landscape. + +In higher dimensions, G/K is no longer a Riemannian symmetric space, and it is necessary to move back and forth in noncommuting directions to obtain the optimal path through G/K. In these cases, the approach of representing the invariant control system on a Lie group as an adjoint control system is still applicable, but analytic solutions have not yet been found. Nonetheless, the application of sub-Riemannian geometry to problems of quantum gate control is currently a topic of intense interest, and promises to afford additional analytical insights into the geometry of quantum control landscape level sets. + +Further analytical studies on the control of unitary transformations (in two-level quantum systems) were carried out by D’Alessandro and Dahleh [30]. These authors studied the related problem of fluence minimizing controls driving a two-level quantum system to a target unitary propagator at fixed final time T . In two dimensions, this problem also has analytical solutions, + +16 + +for both single and multi-input control systems. The resulting optimal controls have a more complicated temporal structure; it was shown that the optimal fields are always Jacobi elliptic functions [30]. Thus, the fluence minimizing solution does not have the simple singular behavior of the unbounded time-minimizing solutions, consistent with the phenomenon of resonance discussed in the previous section. + +### IV. NUMERICAL EXPLORATION OF QUANTUM CONTROL LANDSCAPE LEVEL SETS + +fields on a landscape level set, referred to as diffeomorphic modulation under observable-response-preserving homotopy (D-MORPH), which we summarize here. The procedure can selectively explore the control fields on a level set that display desired properties. For example, the algorithm allows one to numerically explore the control fields producing the various unitary propagators on a level set with minimal fluence or in minimal time, for control systems that are analytically intractable. + +It is convenient to parametrize the field and its variation by the exploration variable s: + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0016-05.png) + + +### A. Algorithms for level set exploration + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0016-07.png) + + +We have seen that for certain classes of lowdimensional quantum optimal control problems, analytical solutions for the control fields exist. However, as the system dimension increases, analytical solutions become increasingly difficult to obtain. Moreover, we have seen that the optimal controls for these problems display particularly simple mechanistic properties, such as resonance with the transition frequencies of the system. + +For the problems studied above, auxiliary costs were imposed on the controls, such as minimal time or minimal fluence. In the absence of these constraints, quantum optimal control problems generally possess an infinite number of solutions [1]. A quantum control level set (Fig. 2) consists of the collection of all fields that produce a particular value for the target observable, regardless of the intervening temporal dynamics (i.e., control mechanism). A natural question concerns the relationship between these degenerate solutions, and whether their associated control mechanisms retain the simplicity of those for low-dimensional systems. From a practical standpoint, a high degeneracy of solutions will enable control fields to be tailored for specialized applications in quantum technology, through the imposition of auxiliary costs. Level set degeneracies also improve the robustness of control solutions. There will inevitably be physical inaccuracies in the experimental implementation (owing to the presence of noise, decoherence) of a particular solution, and one would like the nonideal fields to also produce dynamics that reach the objective. + +Conventional algorithms for optimal control, being designed for identification of the optima of the objective function, are not well-suited to exploring the level sets of quantum control landscapes. For this purpose, Rothman et al. [31, 32, 33] developed a diffeomorphic homotopy procedure for systematically exploring diverse control + +where 0 ≤ s ≤ 1. Since the goal is to explore the set of control fields that are compatible with a given observable expectation value, the solutions ε(s, t) satisfy the nonlinear equation + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0016-12.png) + + +as a function of s, where CT is the desired observable expectation value. + +The maintenance of in ⟨Θ⟩ over an infinitesimal step ds through the level set can be written + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0016-15.png) + + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0016-16.png) + + +The neglected higher-order terms only become relevant near an extremum, where δεδ⟨Θ (t )= 0.Therelationshipin equation (45) is highly underspecified for determining ε(s, t) as s traverses a level set. As shown in Appendix A 5, the integral equation may be expressed as an equivalent initial value problem + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0016-18.png) + + +where + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0016-20.png) + + +17 + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0017-01.png) + + + +G
U1
KU2 KU1
U2
+ +FIG. 5: Time optimal path (dashed arrows) between elements U1 and U2 belonging to G. The long arrows depict the fast portion of the path involving movement within coset KU and correspond to the pulse; the short arrow depicts the slow portion of the path connecting different cosets and corresponds to evolution of the couplings. + +Here S(t) is an arbitrary weight function (e.g., it can bias the control field towards a short pulse that approaches zero at the endpoints of the time interT val), γ(s) = �0S(t)f(s, t)a0(s, t, T )dtandΓ(s) = T �0S(t) [a0(s, t, T )]2 dt.Theabilitytofreelychoosethe function f (s, t) permits exploration of the multiplicity of solutions to the original integral equation. Regardless of the choice of f (s, t), ⟨Θ(s)⟩ will remain invariant over the s ≥ 0 trajectory. + +### B. Quantum control mechanisms and robustness + +Perhaps the most compelling reason to explore quantum control level sets is the insight they offer into control mechanisms. The ability to transform one successful control into another, and therefore one control mechanism into another, must be considered when seeking to establish the mechanism of any particular quantum control problem. Indeed, before making any definitive statements about mechanisms, it is necessary to understand the diversity of controls on a level set. How diverse can the solutions be, given the relatively simple structure of the optimal controls for integrable problems? + +In order to investigate this question, Rothman et al. [33] applied the D-MORPH technique to an eight-level + +Hamiltonian with nondegenerate energy levels and with couplings only between adjacent, next-nearest and nextnext nearest states. The control objective was statestate population transfer |1⟩→|8⟩; level sets of both high and low yield were explored (Fig. 6.) In the case of a choice of f (s, t) corresponding to fluence minimization, the control field asymptotes as s →∞ towards a field of minimal fluence, although a different asymptotic field is produced for each initial field ε(0, t). By contrast, for a fluence maximizing function f (s, t), the distance between ε(0, t) and ε(s, t) increases without bound. In the latter case, the use of multiple transition pathways over the s interval suggests that the level sets are rich with fields producing vastly different dynamics. The observation that controls of minimal fluence often involve simpler mechanisms is consistent with the mechanisms apparent in the fluence and time-minimizing analytical control solutions discussed in the previous section. In order to assess the robustness of control fields to noise along the level set, the Hessian (13) was evaluated at various points along the trajectory. In the case of fluence maximization, the trace of the Hessian was not preserved, indicating varying degrees of robustness of ⟨Θ(T )⟩ to noise in the control field ε(s, t). + +In the weak-field regime, optimal fields for control of discrete quantum systems are typically in resonance + +18 + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0018-01.png) + + +FIG. 6: Starting from an initial control field ε(s = 0, t), the control fields ε±(s, t) are evolved on the interval s ∈ [0, 1] subject to the functions f±, where ± refer to fluence maximization and minimization, respectively. a) Under fluence maximization the control field grows in amplitude and incorporates complex structure; under fluence minimization the progression of fields decreases in amplitude. b) Cross sections of the fields in a) are plotted. The field at s = 0 is the same for both ε±(s, t), but different free functions f (s, t) cause the fields to evolve in dramatically different ways with s. (From ref [33].) + +with the transition frequencies of the system. By contrast, for continuous quantum systems, the fields are usually not simply related to natural resonant frequencies. Wu, Chakrabarti and Rabitz compared the mechanisms of optimal control for discrete and continuous variable quantum gates [24]. Figure 7 depicts the optimal control fields for achievement of the SUM logic gate (a 4-dimensional symplectic matrix) for a model two-mode continuous variable system. The resulting optimal control fields typically display complicated Fourier spectra that suggest a richer variety of possible control mechanisms. The larger diversity of mechanisms at work in continuous variable quantum control may have implications for the comparative effort of locating continuous variable versus discrete quantum control, since in the latter case the dimension of the search space cannot be reduced by requiring that the optimal controls adopt a canonical shape. + +### C. Hamiltonian-dependence of landscape level set geometry + +An important quantum control goal is to discern the distinct controls that can achieve the same objective in each member of a set of similar quantum systems. A common outcome might be, for example, breaking the same type of bond in a set of molecules or creating an analogous excited state in a family of related systems. The notion of families of reactants in chemistry can be given a rigorous meaning in terms of the similarity of optimal control fields driving systems with related internal Hamiltonians to the same state. + +The diffeomorphic homotopy approach described above for level set exploration can be extended to study the relationship among controls producing the same expectation value for homologous quantum systems [31]. Diffeomorphic changes in the system Hamiltonian are + +19 + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0019-01.png) + + +FIG. 7: The optimal control fields and corresponding Fourier power spectra for continuous quantum SUM gate control in a controllable system, using two distinct control Hamiltonians. The solid and dashed lines depict the associated control fields in arbitrary units. Compare to the characteristic beat field structure in Fig. 6 representative of optimal controls for discrete quantum systems. (From ref [24].) + +introduced by scanning over a homotopy parameter s and then monitoring the control field response needed to maintain the value of a specified target observable. The time-dependent Hamiltonian is written as a function of the homotopy parameter s as + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0019-04.png) + + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0019-05.png) + + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0019-06.png) + + +Figure 8 schematically displays the concept of a trajectory through Hamiltonian space. + +It is possible to derive a differential equation for∂ε ∂s(s,t) for remaining on the level set under s-dependent changes in the system Hamiltonian. The following two terms + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0019-09.png) + + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0019-10.png) + + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0019-11.png) + + +analogous to a0 above account for changes in the internal Hamiltonian and dipole operator, respectively, along the trajectory. As summarized in Appendix A 5, we obtain the explicit initial value problem: + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0019-13.png) + + +Rothman et al. carried out numerical D-MORPH simulations across a family of related three-level model systems forming a homologous set. In these studies, trans- + +20 + +fer of pure state population was considered, although the D-MORPH methodology is applicable to arbitrary observable maximization problems originating from arbitrary mixed states. The dipole moment operator and internal Hamiltonian were varied both independently and in unison. + +In the case of independent dipole diffeomorphism, the goal was to move the population from state |1⟩ to state |3⟩. The direct transition was initially allowed, by setting µ13(0) = 0, but finally forbidden, µ13(1) = 0, while along the alternative dynamical route the opposite situation exists, i.e. µ12(0) = µ23(0) = 0 and µ12(1) = 0 and µ23(1) = 0. Therefore, the population transfer occurs by two different mechanisms at s = 0.0 and s = 1.0. Two different trajectories were followed through Hamiltonian space, one along a straight path between the two dipole operators, and one along a curved path. Figure 8 depicts the variations in the control field required to preserve the observable across these homologous quantum systems. + +It is possible to redefine the problem of level set exploration for families of related quantum systems, by identifying the set of dynamically homologous quantum systems that produce the same expectation value of a quantum observable when subjected to a fixed timedependent control field [34]. Rather than tracking over an arbitrary path in Hamiltonian space and determining the change in the field that preserves the observable expectation value, in this case the path in Hamiltonian space is determined by the constraint that the control field does not change. An infinite number of such paths are possible, just as in the former problem. Topologically connected and disconnected families of homologous Hamiltonians have been shown to exist under various conditions. Numerical calculation of the Hessian of the associated cost functional indicates that the critical topology of this landscapes displays remarkably similar features to that for observable expectation value control; in particular, the critical points appear to be saddles rather than local traps, and the rank of the Hessian at the critical points displays the same behavior with respect to the degeneracies in the matrices ρ, Θ. + +### V. EXPERIMENTAL EXPLORATION OF QUANTUM CONTROL LANDSCAPES + +### A. Level sets + +Experimental methods are currently being developed for exploring control landscape level sets. Because the + +domain of control fields is infinite dimensional, the experimental investigation of quantum control landscapes requires careful choice of parametrization of the field such that the landscape can be sampled sufficiently. + +Roslund and Rabitz [35] explored the level set surfaces for second harmonic generation and related nonresonant two-photon absorption. The second harmonic spectral field is given by + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0020-09.png) + + +where E2(Ω2) and E1(Ω) are the complex spectral envelopes of the second harmonic and control pulses, respectively, and the frequencies of these envelopes are relative to their spectral center, i.e. Ω= ω − ω0 and Ω2 = ω − 2ω0. The time integrated signal, given by + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0020-11.png) + + +is measured. The spectral phase φ(Ω) serving as the control is a truncated Taylor expansion around the center frequency ω0, + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0020-13.png) + + +where the zeroth and first-order terms are discarded because they simply correspond to an arbitrary constant phase and shift in the time origin of the pulse, respectively. The level sets were expressed on the domain of phase parameters a, b, c; multiple level sets were identified, with one surface at 50% yield shown in Fig. 9. The level sets of different yield were nested in the phase parameter space (Fig. 10). Each of the continuously varying control fields over a given level set preserves the observable value by its own distinct manipulation of constructive and destructive quantum interferences. Thus, the richness of quantum control fields meeting a particular observable value is accompanied by an equally diverse family of control mechanisms. + +For these systems, the level sets were shown to be closed surfaces in the control parameter space. In order to explore the origin of phenomenon, the Hessian of the cost functional was computed numerically at several points progressively farther away from the global optimum. Based on the positive-definiteness of the Hessian, it was shown that the level sets are predicted to be ellipsoids (see below), consistent with the experimental observation. In general, however, level sets can be unbounded in extent. + +21 + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0021-01.png) + + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0021-02.png) + + +FIG. 8: (a) Starting from quantum system A with Hamiltonian HA, three paths are shown passing through systems B and C with their associated Hamiltonians HB and HC. Three particular control fields εi(s, t), i = 1, 2, 3 characterize the pathways along which the common observable ⟨Θ(T )⟩ is preserved; these distinct observable-preserving controls are specified by the auxiliary functions fi(s, t), i = 1, 2, 3. (b) Control fields as functions of s and t for the combined internal Hamiltonian/dipole diffeomorphism example discussed in the text (with fluence minimization). Target observable population in state | 3⟩ is preserved at t = T for all s. (From ref [31].) + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0021-04.png) + + +FIG. 9: Experimental second harmonic generation (SHG) level set surface for a yield of 50%. Included are three experimentally retrieved control fields located on the surface. (From ref [35].) + +22 + +### B. Landscape topology + +In section II, we demonstrated that formally, no traps exist in quantum control landscapes in the absence of direct costs or constraints placed on the control field2 . Roslund and Rabitz [36] made the first explicit experimental demonstration of the trap-free, monotonic behavior of unconstrained quantum control landscapes in the case of two systems, unfiltered and filtered second harmonic generation (SHG). These landscapes were randomly sampled and interpolated up to landscape level of data noise. In order to explore the topology of the landscapes, 1500 trajectories originating at random points were propagated along the gradient flow of the objective functional, according to the equation + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0022-03.png) + + +where the gradient in the integrand was determined from the laboratory SHG landscape data. 3% (48) of these trajectories did not converge to the global optimum, and ended up distributed among two other local maxima, but these latter maxima were demonstrated to be artifacts due to noise in the control apparatus. + +Figure 11 depicts several possible search trajectories along the filtered SHG landscape. (Filtering the SHG signal to be evaluated at Ω2 = 2ω0 removes the dependence of the signal on the cubic Taylor coefficient in the polynomial basis.) Several of these paths correspond to simple parameterizations of the control field. As can be seen, restricted parameterization of the control field will generally produce artificial structure by forcing projections of the original full infinite dimensional control space. + +Although linear trajectories in the polynomial phase representation considered above are incapable of following the gradient flow trajectory, they do display the favorable property of preserving the intrinsic topology of the landscape. This can be verified by perturbation analysis near the global maximum of the filtered SHG landscape. Expanding the exponential phase to second order around the optimal solution φ(ω) = 0, it may be shown [36] that the perturbative signal δSf (Ω) = Sf∗−Sf(Ω),whereS f∗isthetransformlimitedsig- nal, indicates that the level sets are ellipsoidal. In + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0022-07.png) + + +> 2 Again, this statement holds rigorously in the absence of abnormal extremal controls. See section VII for a detailed discussion. + +addition, assuming a spectral amplitude of the form A(Ω) = exp( 2∆Ω22 ),thenormalizedsignalvariationun- der a Taylor expansion of the phase functional preserves this landscape topology. In general, however, the appropriate choice of control parameters or variables that preserve landscape topology may not be apparent a priori. Methodologies exist [37] for transforming to an optimal local basis set of laboratory control parameters. A physically convenient parameterization is first chosen prior to the outset of the experiment, followed by a Hessian analysis to determine a locally separable representation. This methodology has been successfully illustrated using the example of molecular fragmentation of CH2BrI. + +A possible cause for the appearance of local traps in quantum control landscapes is the existence of costs or constraints on the controls, which in some cases may be impossible to avoid. Numerical results [18] indicate that landscape topology should be reasonably preserved even in the presence of small penalties on the field fluence. These results suggest that in weak-field experiments, where the control field constraints may not be particularly limiting, the topology of experimental quantum control landscapes should remain monotonic. + +In the strong field regime, however, local traps may appear more readily. For example, Wells et al. [38] applied adaptive search algorithms to the fragmentation of a complex molecule, octahedral sulfur (S8), investigating several different control field parametrizations. The structure of the optimal pulses obtained using these various parameterizations were considerably different, although they produced comparable signal enhancements. The fluence of the control fields were constrained considerably, corresponding to large values of λ in equation (1). Although the sampling was not exhaustive enough to rigorously establish level set structure or landscape topology, the results suggested that local maxima existed in the S8 fragmentation landscape for the parameterizations employed. However, even in the strong field regime, the fundamental monotonicity of the landscape need not be compromised. In related work [39], the control landscape for the strong-field ionization of potassium atoms was sampled by phase modulated pulses. The intensity of the Autler-Townes components in the photoelectron spectra were controlled by a sinusoidal phase modulation function. The use of a twodimensional parameter space enabled constrained, but effective sampling of the control landscape. The maxima and minima of the landscape were identified, with clear level sets, and no evidence for local traps was found. + +In most cases of practical interest in the weak-field regime, unconstrained quantum control landscapes pos- + +23 + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0023-01.png) + + +FIG. 10: Experimental second harmonic generation (SHG) level set surfaces with increasing magnification moving to the right for α = 0.10 (light blue), 0.25 (red), 0.50 (dark blue), 0.75 (green) and 0.90 (gold). Each surface is sliced along its a-c plane at b = 0 so that the interior is visible. (From ref [35].) + +sess no suboptimal traps. Thus, local experimental search algorithms should be effective in locating optimal controls; moreover, since gradient-based algorithms can take advantage of landscape structure, they may perform better than ”blind” algorithms (e.g. genetic algorithms) under suitable conditions. In the aforementioned work of Roslund and Rabitz, only the total SHG yield was measured; the gradient of the objective was determined based on radial basis function interpolation. More recent work [40] has addressed the question of how to experimentally measure and follow the gradient flow of the observable maximization objective function, given that the basis must change at each step along the curvilinear path. The gradient of the filtered SHG objective was measured using a moment-based method with only 30 observable measurements on a 128-dimensional parameter space. Despite the statistical uncertainty in the measurements and the presence of noise, following this flow resulted in convergence to > 90% achievement in half the number of steps that were required for a GA. Note that accurate estimation of the gradients of observable expectation values with respect to control field parameters is properly a subject of quantum statistical inference [41]. Quantitative assessment of the statistical uncertainties associated with these estimates, as a function of the number of measurements made, is essen- + +tial for determining the speedup that can be achieved by using gradient versus adaptive search algorithms. + +Following the gradient flow of the objective function exploits the favorable topology of quantum control landscapes, but does not explicitly exploit their geometry. In particular, the best path to the global maximum in Fig. 11 is not necessarily the gradient flow path. A further possibility is to use local algorithms that make use of local gradient information, but do not follow the gradient flow directly, instead tracking alternative observable expectation value paths. In this regard, an important question is whether certain observable paths are expected to display more rapid convergence to the global optimum than others. Section VIII discusses how geometric features of quantum control landscapes indicate that certain observable paths may in fact be globally more efficient than the gradient flow. Because they do not follow the path of steepest ascent, these algorithms may require more accurate estimation of the gradient. Such methodologies could also be applied to explicit experimental tracking of predetermined level set trajectories [42]. + +In summary, improved methods for the measurement of the gradient in the presence of noise, and the accessibility of control field parameterizations capable of tracking the gradient are the central challenges for implement- + +24 + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0024-01.png) + + +FIG. 11: Experimental, unsmoothed quantum control landscape for filtered second harmonic generation (SHG). Four trajectories are shown; the landscape possesses a single global optimum that may be reached monotonically by the curvilinear channels that slice through the landscape. (From ref [36].) + +ing experimental control algorithms that exploit the favorable topological and geometric structure of quantum control landscapes. Note that the analytical results of Section III pertaining to dominant quantum control mechanisms can also be used to help choose appropriate control field parameterizations that simplify experimental landscape search. + +### VI. QUANTUM SYSTEM CONTROLLABILITY AND LANDSCAPE STRUCTURE + +As discussed in section II, the full controllability of a quantum system is a necessary condition for the nonexistence of local traps in the control landscape. If the target transformation is reachable at the final time T, but intermediate dynamical propagators are not, the path to the target may be plagued with local traps. It is therefore important to review the conditions for controllability of quantum systems at a fixed dynamical time T. Moreover, since the choice of T is to some extent arbitrary, it is important to identify the possible choices of T that lead to full controllability. + +The conditions for the controllability of finitedimensional quantum systems were established [43] based upon earlier work on the controllability of systems + +on compact Lie groups [44]. In [43], an easily implementable algorithm for ascertaining the controllability of a finite-level quantum system was provided. Consider the right-invariant system described by Definition 7 (Appendix A 6); let S be the subgroup of the dynamical group G generated by the internal and control Hamiltonians (with corresponding Lie algebra s), and let L be the subgroup generated by the control Hamiltonians alone (with corresponding Lie algebra l). The following theorem establishes sufficient conditions for the full controllability of such systems. + +Theorem 1 Theorem (Controllability of right-invariant systems on Lie groups). The reachable set from the identity matrix in G is contained in S. If S is compact then the reachable set from the identity matrix equals S. In particular, if the dimension of the Lie algebra l equals the dimension of the ambient Lie group G, and the Lie group is compact, then the control system is controllable. Furthermore, in this case it is possible to reach any matrix with an admissible control which is bounded in amplitude. + +Controllability can be checked using the rank condition, which states that if the dimension is N2 for the Lie algebra spanned by Hd, µi and their commutators such as [Hd, µi], [Hd, [Hd, µi]], [µi, [Hd, µi]], etc., then the sys- + +25 + +tem is controllable. This is equivalent to requiring that l be the Lie algebra of all N × N skew-Hermitian matrices, which in turn is equivalent to requiring that the dimension of l as a vector space over the real numbers is precisely N2 . This criterion for controllability of quantum dynamical propagators extends to that of states; all coherent superpositions of states can be achieved if S equals U (N ). The fact that the controllability of a linear control system can be checked via a simple rank criterion which, in addition, does not vary from point to point, is an important property which is generally not valid for a nonlinear control system. Usually this condition guarantees only accessibility [43]. + +### A. Exact-time controllability of discrete quantum systems + +The above theorem establishes the necessary conditions for the existence of a time T at which the system is controllable, but does not constructively define T . Landscape search would be simplest if the quantum control system satisfied the conditions for strong controllability. + +Definition 2 A control system F = (A, Bi, ui(t)) is strongly controllable over a subgroup M if for any T > 0 any point of M is reachable from any other point by F in T or fewer units of time. A control system is said to be strongly controllable if the property of strong controllability holds for the entire dynamical group G. A control system is exact time controllable at time T if any point of M is reachable from any other point by F in exactly T units of time. + +Just as there exist analytical solutions to the Pontryagin maximum principle for finite-dimensional quantum systems (i.e., right-invariant systems on a compact Lie group), there are also powerful general theorems for establishing exact-time and strong controllability of these systems. Also by analogy, there are differences in the conditions establishing exact-time and strong controllability of dynamical transformations versus that of quantum states. + +For finite-dimensional quantum systems, strong controllability can be guaranteed if two controls are used, and these controls span the whole Lie algebra of the dynamical group. In the more common case of one control, strong controllability cannot be guaranteed, but exacttime controllability across a wide range of times T can be straightforwardly established. + +D’Alessandro and Dahleh [45] have studied the exacttime controllability of two-qubit gates. They showed + +that if a two qubit gate is controllable at time T1 (called the critical time), then it is also controllable at any time T2 > T1. We summarize their proof here because of its possible extensions to higher-dimensional quantum systems. Let R(T ) be the reachable set from I, i.e., the set of possible values for X(T ) obtained by varying the controls u1, · · · , um within the set of continuous functions defined on [0, T ]. We also define the sets + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0025-09.png) + + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0025-10.png) + + +Because of right-invariance, R(I, T )S = R(S, T ) for every S ∈ SU (2) and every T . Therefore, it suffices to consider the (exact-time) controllability properties of the set reachable from the identity. For quantum systems of arbitrary dimension, the following theorem establishes the existence of a critical time beyond which the reachable set R(T ) is equal to the entire unitary group U (N ) of dynamical propagators. + +Theorem 2 [44]. Let S denote the subalgebra generated by the controls A, B1, · · · , Bm. If (A, Bi, ui(t)) is a right-invariant control system on a Lie group G and S is compact, (i) R = S; (ii) There exists a T > 0 such that R(≤ T ) = R. + +Powerful exact-time controllability results may be proven in dimension 2 because a Lie algebra isomorphism γ exists between su(2) and so(3). It follows from Lie’s third theorem [45], that ρ induces a homomorphism γ mapping SU (2) onto SO(3). It can be shown that if a system is controllable on SO(3), any element of the subgroup SO(2) can be reached in arbitrarily small time (i.e., the system is small-time controllable on SO(2)). It is then straightforward to demonstrate that on either SO(3) or SU (2), if T1 ≤ T2, then R(T1) ⊆ R(T2) and therefore R(≤ T ) = R(T ) for each T ≥ 0 [45]. In other words, if T1 < T2, all the points reachable at T1 are reachable at T2. Combining this result with Theorem 2, we obtain the the following exact-time controllability result in dimension 2: + +Theorem 3 There exists a time Tc, such that R(T ) = SU (2) for every T > Tc. The critical time Tc is the least time such that for every T > Tc, it is possible to drive the system from the identity to an arbitrary matrix in SU (2). + +26 + +An important question is whether this property can be extended to finite quantum systems of arbitrary dimension N , i.e., whether the above equivalence between the sets R and R holds in general. If this is the case, quantum control landscapes for larger systems will display a homogeneous structure for all T above a critical time, such that simulations and experiments need not sample extensively over the time T in order to obtain a landscape with simple topology. + +### B. Controllability of classical and continuous variable quantum systems + +For the noncompact Lie groups describing the evolution of classical or continuous variable quantum systems, strong controllability is not established by the above Lie algebra rank condition. This implies that the critical topology of control landscapes for such systems may change considerably as the final dynamical time T is varied, underscoring the comparative simplicity of discrete quantum control landscapes. + +For continuous variable (infinite-dimensional) quantum systems, the rank condition is also sufficient for establishing controllability on noncompact symplectic groups (i.e., those with quadratic Hamiltonians) in the common case where H0 is compact, but there is no guarantee of exact-time controllability, i.e., some particular gates may only be reachable after an extremely long time. However, exact-time controllability can be achieved at arbitrary positive times (i.e., R(T ) = R(≤ T ) = R = U (N ) for every T > 0) if we can employ two control Hamiltonians that span the whole Lie algebra of the group of dynamical propagators. Thus, the topology of control landscapes for the subset of infinite dimensional quantum gates described in section II will be largely insensitive to the final time T if two independent controls are used. + +The strong controllability of general infinitedimensional quantum systems (i.e., those with nonquadratic Hamiltonians) was studied by Wu and Tarn [46]. Such systems were shown to be associated with dynamical symmetries represented by noncompact Lie groups with infinite-dimensional unitary representations. A criterion for approximate strong controllability, called smooth controllability, was given, showing that such systems, which possess an uncountable number of levels, can be well manipulated using a finite number of control + +The effects of exact-time controllability of discrete versus continuous variable quantum systems on the + +simplicity of control field search were studied by Wu, Chakrabarti and Rabitz [24]. Fig. 12 compares the convergence of optimal searches for achieving the CV SUM logic gate (symplectic propagator) versus the computationally equivalent discrete CNOT gate (unitary propagator), using identical gradient-based algorithms. The CV gates were implemented using several models with varying degrees of controllability, including a weakly controllable system (employing ion trap interactions) and an uncontrollable system (employing photon-atomic spin interactions). As can be seen, the maximal achievable fidelity is highly sensitive to the choice of final time for uncontrollable or weakly controllable systems. Moreover, even when the gate was reachable, the search effort required for convergence was found to increase with decreasing controllability. By contrast, a randomly chosen final time was sufficient for achieving near perfect fidelity in the discrete quantum system. The weaker controllability of continuous systems can also result in control fields with more complicated Fourier power spectra, as shown in Fig. 13. + +### VII. COMPUTATIONAL COMPLEXITY OF QUANTUM CONTROL LANDSCAPES + +The scaling of the expense of quantum simulation with system dimension is of fundamental importance in quantum chemistry. Similarly, the scaling of the expense for quantum control search - either OCT or OCE - lies at the heart of the applicability of quantum control to the large molecules of practical interest in many applications. Whether the search is carried out numerically or experimentally, this scaling is referred to as the problem’s computational complexity. Compared to other central optimization problems in quantum technology and quantum information, such as state or process reconstruction [47], the complexity of quantum control problems is more difficult to assess since the optimization is carried out over an infinite-dimensional parameter space. + +We have seen that analytical solutions to quantum optimal control problems appear to exist only for lowdimensional systems, and in some cases only for restricted Hamiltonians. Nonetheless, the existence of analytical solutions is rare in classical OCT, and begs the question of how the difficulty of finding solutions to nonintegrable quantum control problems increases with system dimension. Of course, this complexity is algorithmdependent. It is also a function of the system Hamiltonian, but may display homogeneous features across + +27 + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0027-01.png) + + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0027-02.png) + + +FIG. 12: Effects of exact-time controllability on the optimal control fidelity of discrete vs. continuous quantum systems. (a) The convergence of control field searches for the 2-qunit continuous quantum SUM gate, with a weakly controllable continuous variable system, using conjugate gradient algorithms and different final times. (b) The convergence of optimal searches for a 3-qunit SUM gate with a weakly controllable system (photon model), 3-qunit SUM gate with an uncontrollable system (ion-trap model) and 3-qubit discrete quantum Controlled-CNOT gate with a standard NMR spin coupling model. See text for definitions of quantum gate terminology.(From ref [24].) + +families of Hamiltonians. Recall that in Section III, we showed that for certain classes Hamiltonians, analytical results pertaining to control mechanisms permits reduction in the dimension of the search space. + +The search effort required in OCT calculations for observable maximization is remarkably insensitive to the dimension of the quantum system. A multitude of such calculations have been reported in the literature [48, 49, 50], on systems ranging in dimension from 2 up to more than 102 . Even for the largest systems studied, the number of iterations required for convergence seldom exceeds 103 , with most calculations requiring between 102 and 103 iterations[51, 52]. Much larger systems have been subjected to OCE studies; in the common case of selective bond dissociation, a continuum of states is accessed. Nonetheless, the search effort reported in most OCE studies is of the same order of magnitude as in OCT. + +Since the majority of quantum OCT algorithms are based on the gradient of the objective function, and the gradient is relatively straightforward to implement in OCE, it is natural to ask about the complexity of optimal control when the search is carried out using these + +algorithms. + +### A. Gradient flows and search complexity + +In Section II, we showed how the critical topology of the most common objective functions in quantum optimal control can be determined analytically and display features favorable optimal search. In Section III, we established several important analytical results pertaining to the geometry of control landscapes, in particular their level sets, finding that in certain cases, control mechanisms can be exploited to reduce the dimensionality of the control search space. Here we examine another feature of the geometry of control landscapes that permits analytical investigation, namely the kinematic gradient flows of the objective function Φ. On the domain U(N ), these gradient flows themselves represent integrable dynamical systems. As a result of this feature, it is possible to identify the system-independent contribution to the scaling with system dimension of the search effort for locating quantum optimal controls. + +The gradient flow is the trajectory followed by the + +28 + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0028-01.png) + + +FIG. 13: Comparison of the optimal control fields for discrete and continuous variable quantum gates depicted in Fig. 12b. Three independent controls were employed, represented by solid, dashed, and dotted lines, respectively. (a) Optimal fields for 3-qunit continuous quantum SUM gate control in a weakly controllable system; (b) Optimal fields for 3-qubit discrete quantum Controlled-CNOT gate control in a standard NMR spin coupling model. Control field amplitudes are arbitrary. (From ref [24].) + +search algorithm when the algorithmic step is defined according to the differential equation + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0028-04.png) + + +The unitary gradient flow equations for observable maximization and gate optimization are then, respectively, + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0028-06.png) + + +In the case that ρ(0) has only one nonzero eigenvalue, corresponding to an initial pure state, it was + +shown that under the change of variables ρ(T, s) = |ψ(s)⟩⟨ψ(s)|, |ψ(s)⟩ = (c1(s), · · · , cN (s)), x(s) ≡ (|c1(s)|2 , · · · , |cN (s)|2 ), the gradient flow of Φ1 can be explicitly integrated to give [42]: + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0028-09.png) + + +where λ1, · · · , λN denote the eigenvalues of Θ. The explicit solution for the gradient trajectory of objective functional Φ2 was shown to be + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0028-11.png) + + +where the initial condition is U0 = U (0) [42]. + +29 + +Chakrabarti et al.[53] have calculated upper bounds of the convergence times of these unitary gradient flows into a ball of radius ǫ around the solution. For the class of observable maximization problems above, this bound was found to be + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0029-02.png) + + +where N is the Hilbert space dimension, k is the degeneracy of the largest eigenvalue of the observable operator θ and µ is the absolute value of the difference of the two largest eigenvalues of Θ. The upper bound on the convergence time for the unitary gradient flow of the gate fidelity function was shown to be + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0029-04.png) + + +where a = 1−sincos θ0 θ0,forsmallǫ.Sincebothofthesecrit- ical times scale logarithmically with the Hilbert space dimension, the problem of optimizing the objective functions on the domain of unitary propagators (via gradient algorithms) belongs to a logarithmic analog complexity class, referred to as CLOG within the analog complexity literature [54]. + +By contrast, the kinematic gradient flows for optimization of the corresponding objective functions for classical or continuous variable systems are not integrable. The kinematic flows for optimization of the continuous variable SUM gate and the discrete variable CNOT gate are shown in Fig. 14 [24], revealing that the kinematic contribution to control optimization is less favorable for continuous variable systems. + +The integrated flow derived above for the problem of observable maximization applies only to the case where + +ρ(0) is a pure state. For the more general problem of a mixed initial state, analytic solutions are more difficult to obtain [42]. However, insight into the scaling of the search effort for these problems may be obtained from numerical simulations, as well as consideration of the dimension of the manifold on which the gradient flow evolves. + +As such, numerical optimizations [55] of the observable expectation value function on the domain of unitary propagators were carried out for ρ(0) and Θ operators of various possible ranks and degeneracies, for Hilbert space dimensions ranging from 2 to 40. From a kinematic perspective, the optimization problem is symmetric with respect to these two operators. It was found that the scaling was completely determined by the number of nondegenerate eigenvalues of ρ(0); in particular, if ρ(0) is a pure state, the spectrum of Θ does not alter the complexity class (or vice versa). In the latter case, the scaling of effort was observed to be roughly logarithmic in the Hilbert space dimension, consistent with the analytical result above. In the limiting case where ρ(0) is a full rank matrix with nondegenerate eigenvalues, the effort was found to scale linearly with system dimension. + +The origin of the observed numerical scaling has been probed [22] by examining the dimension of the subspace on which the gradient evolves. It is found that this dimension is identical on the domain of unitary propagators and control fields, although in the latter case the basis functions change continuously along the optimization trajectory. The gradient can be expanded in terms of at most N (N − 1) linearly independent functions of the time-dependent dipole operator µ(t). Let ρ consist or r subsets of degenerate eigenvalues p1, · · · , pr with multiplicities n1, ..., nr, and write ρ(0) =�r i=1pi|i⟩⟨i|. The expression (9) for the gradient from section II can be expanded to give + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0029-11.png) + + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0029-12.png) + + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0029-13.png) + + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0029-14.png) + + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0029-15.png) + + +where ni are the degeneracies of the eigenvalues pi of + +ρ(0), s1 = 0, sk = ik =1−1ni, k=2, · · ·, r + 1, sr+1=n. + +30 + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0030-01.png) + + +FIG. 14: The convergence of the kinematic gradient flows for optimal search of the SUM gate on Sp(4, R) and CNOT gate on U(4). (From ref [24].) + +The terms in the second summation, of which there are �in i2,add to zero, from which it can be shown that the dimension of the subspace of skew-Hermitian matrices upon which the gradient flow evolves is [22] + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0030-04.png) + + +Therefore, when ρ(0) is full rank and nondegenerate, the gradient can be expressed in terms of a linear combination of N (N − 1) basis functions, irrespective of the spectrum of Θ. Increasing degeneracy in the spectrum of ρ(0) reduces the dimension of the subspace on which the gradient evolves, such that when ρ(0) has n degenerate eigenvalues, this dimension is equal to 2n(N − n). Although this result does not establish the kinematic contribution to the scaling of search effort with Hilbert space dimension, it reveals that the dimension of the subspace on which the gradient evolves for generic observable maximization problems scales less favorably with N when ρ and Θ have more nondegenerate eigenvalues. + +The existence of a low-dimensional basis set of functions upon which the gradient can be expanded is especially useful given the difficulty of implementing effective high-dimensional control field parameterizations in the experimental setting (section V). Although this basis set varies from point to point along the landscape, recent work [56] suggests that in many cases, it can be remarkably homogeneous, providing a rational means of estimating the minimal control parameterization dimensionality needed to effectively climb the landscape. + +### B. Relation between dynamic and kinematic gradient flows + +The integrated U -gradient flows of the observable maximization and gate fidelity cost functions identify Hamiltonian-independent contributions to the scaling of quantum control search effort when using gradient algorithms. In this section, we derive the Hamiltoniandependent relationship between these U -gradient flows and the ε-gradient flows that are followed by OCT and OCE algorithms. + +The ε-gradient flows are the solutions to the differential equations + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0030-10.png) + + +where s is a continuous variable parametrizing the algorithmic time evolution of the search trajectory, and α is an arbitrary scalar that we will set to 1. The gradient on ε(t) is related to the gradient on U(N ) through + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0030-12.png) + + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0030-13.png) + + +Now suppose that we have the gradient flow of ε(s, t) that follows (68) and let U (s), the system propagator at time T driven by ε(s, t), be the projected trajectory on the unitary group U(N ). The (algorithmic) time derivative of U (s) is then + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0030-15.png) + + +31 + +which, combined with (68) and (69), gives + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0031-02.png) + + +It is convenient to write this equation in vector form, replacing the N × N matrix U (s) with the N2 dimensional vector u(s): + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0031-04.png) + + +where the superscript T denotes the transpose. This relation implies that the variation of the propagator in U(N ) caused by the natural gradient flow in the space of control field is Hamiltonian-dependent, where the influence of the Hamiltonian is contained in the N2 - dimensional symmetric matrix G[ε(s, t)]. + +Thus, although the convergence times for the U - gradient flows above scale favorably with system size, the ε-gradient flows do not generally follow the same paths, and Hamiltonian-dependent effects may dominate the scaling when following the gradient on the domain of control fields. OCT calculations suggest a difference in the scaling of observable maximization and gate control search effort when using local gradient-based algorithms [6], although the U -gradient flow scalings are similar for these two problems. Systematic dynamical OCT studies have been carried out for unitary gate optimization on systems of dimension ranging from 2 to 32, using iterative algorithms. The computational effort was found to scale exponentially in the Hilbert space dimension. + +A natural question is whether the Hamiltoniandependent unfavorable scaling of local OCT or OCE algorithms can be mitigated by employing global algorithms whose optimization trajectories are less sensitive to the system Hamiltonian. In the next section, we discuss such global search algorithms and the properties of quantum control landscapes that render these algorithms + +### VIII. GLOBAL SEARCH ALGORITHMS FOR QUANTUM CONTROL + +### A. Scalar and matrix tracking algorithms + +As shown above, the convergence time of gradient OCT or OCE algorithms is dependent on the system + +Hamiltonian. By contrast, it is possible to employ algorithms that follow a predetermined track of observable expectation values, independent of the system Hamiltonian. An efficient algorithm for following a predetermined track + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0031-12.png) + + +for the expectation values of a target observable operator Θ at final time T may be derived from the diffeomorphic homotopy formalism described in section IV, originally developed for level set exploration. By making the subT stitution �0a0(s, t, T ) ∂ε ∂s(s,t) dt = b(s, T ) +d⟨Θ( ds,T s)⟩ , in equation (45), we obtain the following expression for the algorithmic step for the control field (Appendix A 5): + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0031-14.png) + + +If the Hamiltonian is kept fixed during tracking, b(s, T ) = 0. As mentioned in section IV, such algorithms may also be implemented experimentally, but require a precise measurement of the gradient. Analogous equations can be derived for following a predetermined track of gate fidelity function values. + +At each step in this approach, the observable expectation value is specified, but the unitary propagator is not; many unitary propagators will map to the same expectation value. The tracking errors that occur in this approach will be system-specific, depending on the system dimension and Hamiltonian. The method was applied to a five-level quantum system initially in state | 1⟩, with the goal of transferring population to state | 5⟩. Figure 15 depicts the changes in the control field along the track for P (s) =| ⟨5 | ψ(s = 0, T )⟩|2 + sin(2πs). In this case, the dipole operator was also morphed along the track, in order to demonstrate the feasibility of simultaneous Hamiltonian variation. + +It is natural to consider the prospects of tracking paths in the space of dynamical propagators, rather than observable expectation values. Doing so may permit a more system-independent definition of quantum control complexity. Although this may be difficult to achieve in OCE using current technology, it can be easily implemented in simulations. Indeed, diffeomorphic homotopy provides a natural means of tracking paths U (s, T ), 0 ≤ s ≤ 1 in the group of quantum dynamical propagators. Consider the problem of tracking the unitary gradient flow for observable expectation value maximization, integrated in section VII. + +32 + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0032-01.png) + + +FIG. 15: (a) Population surface of state | 5⟩ as a function of s and t for the tracking example described in the text (P (s) =| ⟨5 | ψ(s = 0, T )⟩|2 + sin(2πs)). The elements µ34 and µ14 of the dipole operator were simultaneously modulated according to µ34(s) = s, µ14(s) = 0.5(1 − s), with other elements kept fixed. At s = 0, the only available direct transition pathway to | 5⟩ is | 1⟩→| 4⟩→| 5⟩, whereas at s = 1 the only possible pathway is ladder climbing through all states. The change of the temporal dynamics with s reflects the use of different dynamical pathways throughout the s interval. (b) Cross section of a) at time t = T . The calculated observable population follows the imposed track very well. (From ref [32].) + +In order for the projected flow from ε(t) onto U (T ) to match the integrated gradient flow on U (T ), the quantity∂ε ∂s(s,t) that corresponds to movement in each step must satisfy a generalized differential equation: + +following matrix integral equation: + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0032-05.png) + + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0032-06.png) + + +In the dipole approximation, this relation becomes the + +where µ(s, t) ≡ U (s, t)µU (s, t). When Φ is the observable expectation value objective function, we have + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0032-09.png) + + +33 + +On the basis of eigenstates, the matrix integral equation is written + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0033-02.png) + + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0033-03.png) + + +To solve this equation, we first note that the flexibility in the choice of the representation of the variation in ε(s, t) allows us to expand it on the basis of functions µij (s, t), as + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0033-05.png) + + +Inserting this expansion into the above equation produces + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0033-07.png) + + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0033-08.png) + + +If we denote the correlation matrix G(s) as + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0033-10.png) + + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0033-11.png) + + +(as in eqn (72) above, but now specifically in the case of the dipole approximation) and define + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0033-13.png) + + +it can be shown [42, 57] that the matrix integral equation (78) can be converted into the following N2 -dimensional algebraic (nonsingular) differential equation (through a procedure analogous to that described in Appendix A 5 for scalar tracking): + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0033-15.png) + + +where fs = fs(t) is a ”free” function resulting from the solution of the homogeneous differential equation (analogous to fs in Appendix A 5), the operator v vectorizes its matrix argument (as in eqn (72)) and α ≡ T �0v(µ(t))fsdt. This equation is similar to equation A3 in Appendix A 5, with the notable distinctions that the scalar functions a0(s, T ), a1(s, T ), a2(s, T ), γ and Γ are now replaced by N2 -dimensional vector and N4 - dimensional matrix counterparts, respectively. + +The computational overhead required for implementing such unitary matrix tracking, compared to scalar tracking, scales (roughly) as N4 , the expense of inverting the matrix G. However, if this track can faithfully be followed, the scaling of the path length and convergence time to the optimal control solution could mirror that of the gradient flow of the objective function on the domain of unitary propagators. In the case of observable maximization, the analysis in the previous section suggests that for mixed initial states, the optimization trajectory followed by the unitary gradient flow will scale unfavorably for highly nondegenerate ρ(0). Importantly, it is possible to choose a global path that is even more favorable than the integrated unitary gradient flow of the gate objective function. Indeed, unitary matrix tracking algorithms have recently been developed [57] that follow the shortest path between the initial guess and the target matrix, namely the geodesic between these points in the unitary group. Across a wide variety of target gates and system dimensions, these algorithms were capable of tracking geodesic paths in U (N ) with almost negligible error. As the geodesic is the globally optimal path in U (N ), this indicates that it may be possible to define a system-independent search complexity for quantum optimal control problems in terms of the scaling of the length of the near-geodesic path with Hilbert space dimension. + +The ability to track globally optimal paths in U (N ), originating in the favorable properties of the input-state map for discrete quantum systems (below), may also be useful for design of more efficient experimental quantum control algorithms. In particular, it is possible to specify the observable expectation value path that corresponds to the geodesic path in U (N ) for a given ρ(0) and Θ. Tracking this path experimentally may cause the control system to follow a path in U (N ) that is globally more optimal, and more system-independent, than the projected path of the ε-gradient, permitting a more system-invariant definition of landscape complexity for OCE. + +In this approach [42], the unitary propagator W that maximizes the observable expectation value is first determined numerically. This can be achieved at minimal computational cost if ρ(0) and Θ are known. The observable track that corresponds to the geodesic U (s) = exp (i log (W U0)s) that connects U0 and W , i.e. ⟨Θ(s)⟩ = Tr �U (s)ρ(0)U (s) Θ� , is compatible with an infinite number of paths V (s) in U(N ), and the set of these + +34 + +paths can be written + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0034-02.png) + + +In order to implement a global OCE search along the desired observable track, computational overhead must be paid in order to reconstruct the initial density matrix of the system. Research in quantum statistical inference [41] has demonstrated that this reconstruction can generally be achieved with high statistical certainty at comparatively low cost. As in the case of gradient measurements, n repeated observations are made on identically prepared quantum systems. Each quantum measurement is described by a positive operator-valued measure (POVM) [58]. Denoting by Fi the POVM corresponding to the i-th observation, the likelihood functional + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0034-04.png) + + +describes the probability of obtaining the set of observed outcomes for a given density matrix ρ(t). The method of maximal likelihood estimation (MLE) maximizes this function over the set of density matrices [58]. An effective parameterization of ρ(t) is ρ(t) = T T , which guarantees positivity and Hermiticity, and the condition of unit trace is imposed via a Lagrange multiplier λ, to give + +track U (s) is approximated to progressively greater accuracy, for only incrementally greater cost. Moreover, it can be shown [42] that the k gradientsδ⟨Θ δεi (( tT ))⟩ are closely related, such that the overhead required to statistically sampleδ⟨Θ δε1 (( tT ))⟩ assists in the determination of δ⟨Θδε2((tT) )⟩ , ...,δ⟨Θ δεk (( tT ))⟩ . + +The accuracy with which globally optimal paths in U (N ) can be tracked depends on the properties of the input-state map M : ε(t) → U (T ). In particular, if the matrix G is singular or close-to-singular, the tracking errors will be greater and the performance of these globally optimal algorithms will be compromised. It can be shown that the requirement that G is nonsingular amounts to a more demanding form of controllability than full controllability, i.e., from any given unitary propagator U (T ), it must be possible to track to any neighboring U (T ) to first order in ε(t). An important advantage of (orthogonal) observable tracking compared to unitary matrix tracking is that these algorithms place less stringent demands on controllability while reaping many of the benefits of of following a globally direct path to the solution [42]. + +In the next section, we review properties of discrete quantum control systems that render them less likely to encounter tracking problems, compared to classical control systems. + +### B. Extremals of the input-state map + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0034-10.png) + + +Standard numerical techniques, such as NewtonRaphson or downhill simplex algorithms, may be used to search for the maximum over the N2 parameters of the matrix T . Note that other methods for state reconstruction, such as the maximum entropy method or Bayesian quantum state identification [41], can alternatively be used. + +It is possible (and in many cases imperative for good results) to constrain the optimization trajectory to follow still more precise paths in U(N ), by specifying observable tracks ⟨Θi(s)⟩ corresponding to the desired unitary track U (s) for multiple observable operators Θi. In the limit that the set {Θi} constitutes a complete tomographic set of observables (N2 − 1 orthogonal observables Θi), and ρ(0) is nondegenerate, the observable tracking algorithm becomes effectively identical to the unitary matrix tracking algorithm described above. For 1 ≤ i ≤ N2 − 1 observables, the geodesic + +In section II, we saw that the critical points of quantum optimal control variational problems that are not critical points of the map between control fields and unitary propagators can be identified analytically. These critical points are called normal (or regular) extremals in the terminology of control theory. In this section, we examine the properties of the so-called input-state map ε(t) → U (T ) between control fields and associated dynamical propagators, comparing to the classical case. The critical points of this map are called abnormal (or singular) extremals of the control problem (see Appendix A 2 for a formal definition). + +Given the simple critical topology of normal extremals for quantum optimal control problems, and the integrability of the gradient flows of the objective functions on the domain of dynamical propagators, the properties of the input-state map play a dominant role in determining the effort required for locating optimal controls. As such, the behavior (and design) of advanced search algorithms for quantum optimal control are largely governed + +35 + +by the properties of this map. + +It can be shown that abnormal extremals correspond to control fields ε(t) which produce a singular matrix G [57]. Because singular extremals correspond to places where algorithms that track paths in the space of dynamical propagators break down, their abundance plays a central role in determining the maximum possible efficiency of optimal control search. + +For general problems of sub-Riemannian geometry [59], abnormal extremals exist in abundance. In particular, a dynamical system can be strongly controllable and still possess abnormal extremals. For discrete quantum systems, however, it is possible to prove certain analytical results pertaining to the existence of abnormal extremals that suggest that these extremals are particularly rare. + +D’Alessandro and Dahleh [30] gave a complete classification of normal and abnormal extremals for the (singleinput) optimal gate control problem on SU (2), with field fluence as the cost. It was shown that the only abnormal extremal in this case is the control ε(t) = −Tr (Hdµ) /Tr (µµ). For two control fields, if we write + +A := a1µ1 + a2µ2 + a3 [µ1, µ2], the only abnormal extremal is ε1 = −a1, ε2 = −a2. For three controls, there are no abnormal extremals. + +We sketch the proof of this result for a single control field. Recall that in this case, the PMP Hamiltonian function for the maximum principle can be written + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0035-07.png) + + +as above, where M again plays the role of the conjugate momentum corresponding to the dynamical propagator state variable. We first demonstrate that all optimal controls except ε(t) = −Tr (Hdµ) /Tr (µµ) are normal. It is useful to define [[Hd, µ] , µ] = c1Hd + c2µ [[Hd, µ] , Hd] = d1Hd + d2µ where c2 = −d1, d1/c1 = Tr (Hdµ) /Tr (µµ). + +Now, if λ0 = 0 (i.e., if the control is an abnormal extremal), the maximizer of the PMP-Hamiltonian must satisfy Tr (M, U0(t)µU0(t)) = 0. Differentiating twice, we obtain + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0035-10.png) + + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0035-11.png) + + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0035-12.png) + + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0035-13.png) + + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0035-14.png) + + +which implies + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0035-16.png) + + +It can be shown that ε(t) = Tr (Hdµ) /Tr (µµ) implies c1 + ε(t)d1 = 0. Thus, Tr (MU0∗(t)HdU0(t))≡0. It follows that M must also equal zero, which contradicts the supposition that the minimizer is abnormal. Thus, all optimal controls except ε(t) = Tr (Hdµ) /Tr (µµ) are normal. Conversely, it can be verified that ε = −Tr (Hd, µ) /Tr (µ, µ) satisfies the maximum principle with λ0 = 0 and M chosen such that Tr (Mµ) = Tr (M, [Hd, µ]) = 0. This implies that it is an abnormal extremal, and since all extremals that are not of this form are normal, this is the only abnormal extremal. Note that the only abnormal extremal in this case is a constant control. For higher-dimensional gate control problems using one control field, it can be shown that there also exist constant controls that are abnormal extremals, although it is not yet clear whether additional abnormal extremals exist. + +For the important problem of laser control of popula- + +tion transfer examined in section III B, where the control Hamiltonian couples only neighboring energy levels (and the internal Hamiltonian can be eliminated), the existence of abnormal extremals has been studied for discrete quantum systems of arbitrary dimension; in particular, for any time t on the domain of a given solution to any such state control problem, there exists an interval [t1, t2] around t where the solution is not strictly abnormal [29]. This property might be termed ”weak normality”, by analogy to the phenomenon of weak resonance discussed above. + +The need to partition the time domain of the solutions in this way arises for the same reason as in section III B, i.e. because the dynamics has singularities whenever a state vector coefficient is zero. For example, if ψ1 = ψ2 = 0, the control V1,2 has no effect on dynamics, i.e., V1,2ψ2 = V2,1ψ1 = 0. In order to prove the property of weak normality, one can define a subspace of Sn−1 on which the corresponding control problem does not encounter singularities. An auxiliary control problem can then be framed on this subspace, which can be shown + +36 + +straightforwardly to possess no abnormal extremals. Solutions to that control problem can be lifted to Sn−1 to produce normal solutions to the original control problem. + +We can accomplish these steps by first defining a partition I J of {1, ..., n} satisfying the following condition: + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0036-03.png) + + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0036-04.png) + + +In addition, it is necessary to subdivide J into subspaces connected by controls, because only in these subspaces are the vector fields Fj,k(ψ) = Vj,kψk corresponding to the controls identically nonzero. Two indices j, k of J are connected (j ∼ k) if there exists a sequence j1, ..., js of indices of J such that j = j1, k = js, and ∀r < s, Vjr ,jr +1 is a control, i.e. the two energy levels Ej and Ek are connected if there exists path through state space where successive states are coupled by control matrix elements. Denote by K1, ..., Kr the equivalence classes defined by ∼. Let m1, ..., mr denote their respective cardinalities, and define M0 = 0, Ml = k≤lmk.Forconvenience, we reorder the indices in the partition such that ∀l ≤ r, Kl = {Ml−1 + 1, ..., Ml} and I = {Mr + 1, ..., n}. This simply shifts the indices responsible for singularities to the upper end of the spectrum. + +The essential property of discrete quantum state control systems that permits proof of weak normality is that it is possible to frame this associated auxiliary control problem on an analytical submanifold of the domain of the original problem, Sn−1 . Because j∈Kl|ψˆj(t) |2is constant on [t1, t2], we can define the analytic submanifold on which ψ(t) evolves for t ∈ [t1, t2] in terms of the ψj(t1)s as: + +It is then straightforward to show [29] that the cardinality of each subspace ∆l generated by the control elements Vj,k with j, k ∈ Kl is equal to the cardinality of the Hilbert sphere Sml−1 (Cl), and hence that the cardinality of ∆= ⊕l∆l is equal to the cardinality of J. As such, no singularities can exist on this domain and the auxiliary control problem has no abnormal extremals. We refer the reader to ref [29] for details and for a discussion of how the normality of solutions to the auxiliary control problem is preserved upon lifting to Sn−1 . Note that the conditions for normality are equivalent whether the problem is formulated on the space of quantum states or unitary propagators. Extending these results to the whole domain of the solution is as of yet an unsettled question, but this work represents the first step in that direction. + +Note that the proofs of these analytical results pertaining to the sparseness of abnormal extremals for discrete quantum control problems make use of the compactness of the discrete unitary group of dynamical propagators. Since this property is not shared by the noncompact classical or continuous variable quantum dynamical propagators, the more general circumstance of abundant abnormal extremals most likely holds for those cases. + +Although critical points of the input-state map are unlikely to be encountered directly during the search for optimal controls, unitary propagators within a certain distance of these critical points will be associated with nearly singular matrices G, thus possibly compromising global tracking efficiency. Current numerical work focuses on identifying the radius in U(N ) within which such ill-conditioned matrices occur, for various families of homologous Hamiltonians. + +### IX. OPEN QUANTUM SYSTEMS + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0036-11.png) + + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0036-12.png) + + +where Cl = ��j∈Kl| ψj(t1) |2. In other words, because the subspaces labeled by l are connected by matrix elements of the control Hamiltonian, the corresponding components of the state vector undergo unitary (state vector norm-preserving) evolution on those subspaces. It follows that any extremal ψˆ of the original control problem is also an extremal of the auxiliary control problem on X, since ψˆ remains the same if the controls Vˆ j,k, where at least one of the indices j, k is in I, are set to zero. + +Quantum systems of practical interest in chemistry or physics are always exposed to some kind of environment, which can render the dynamics nonunitary and irreversible. Intuitively, environmentally induced irreversible quantum dynamics would seem to downgrade the quality of the control outcome. It is therefore imperative to determine whether the favorable features of quantum control landscapes, derived in the context of ideal closed systems, are preserved in the presence of environmental decoherence. In this section, we examine the effects of strong decoherence on the critical topology of open quantum system control problems. + +The composite of the system and environment obeys + +37 + +the Schrodinger equation: + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0037-02.png) + + +The composite Hilbert space is H = HS ⊗HE, where HS and HE are the Hilbert spaces of the system and environment, respectively, and the initial state of the total system is ρtot(0) = ρS ⊗ ρE. The problem of maximizing the expectation value of an observable of the system can be expressed in terms of the Kraus dynamical propagators Kmn of the quantum system in the presence of the environment (see Appendix A 7): + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0037-04.png) + + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0037-05.png) + + +Using the terminology K = FE(U ) = U (IN ⊗ ρ1 E/2), where U is a unitary propagator in U (λN ), we can lift the landscape topology problem onto the composite Hilbert space as + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0037-07.png) + + +where P = ρS ⊗ ρE and θ = Θ ⊗ Iλ. The composite system is assumed to be controllable over U (λN ). Under this assumption, Wu et al. [60] showed that no suboptimal traps exist in the control landscape for maximization of observable expectation values. Moreover, the enhanced controllability attainable with open dynamics actually broadens the range of attainable expectation values. + +Because K [ρ, N ] is homeomorphic to the homogeneous space of U (λN ), a landscape mapping satisfying the conditions of Theorem 4 can be built from U (λN ) to K [ρ, N ]. This theorem allows one to extract the critical topology of the Kraus landscape in terms of the associated unitary landscape. In particular, since the observable maximization landscape for unitary evolution was demonstrated to have no suboptimal traps (section II), we can immediately conclude that the solution sets to open quantum system observable maximization problems also have no (normal) traps, assuming the environment is controllable. Although the latter condition may appear difficult to achieve in practice, in most cases the + +interaction of a system with its environment is dominated by local interactions, which may be straightforward to control. + +We note that the global observable and matrix tracking algorithms described in section VIII can be applied to open quantum systems as well. In this case, a complete tomographic set of observables {Θi} is composed of N4 rather than N2 operators [47], thus increasing the expense of tracking, but not rendering it prohibitive. + +### X. CONCLUSION AND FUTURE CHALLENGES + +As we have seen, there are stark differences between the optimal control landscapes for quantum and classical systems. In particular, the geometric properties of the compact Lie group of finite-dimensional quantum propagators endows the corresponding control landscapes with remarkable properties that are considerably simpler than those of classical systems3 . Although quantization of a finite-dimensional classical system generally produces an infinite-dimensional quantum system, the Hilbert spaces of most quantum systems of practical interest that possess an infinite number of levels can be effectively truncated to finite dimensions. Thus, counterintuitively, locating optimal quantum controls becomes in many ways easier than locating corresponding classical controls. Given the apparent favorable scaling of landscape search complexity with Hilbert space dimension, even if distant energy levels play a role in the dynamics, the effort involved in locating controls may still be minimal. + +This is most important for the practical feasibility of quantum control simulations and experiments on large molecules. The scaling of the expense of quantum dynamical simulation suggests that the computational problems inherent in quantum chemistry - highprecision electronic structure calculations become prohibitively expensive for most systems of practical interest - should be exacerbated for optimal control of such + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0037-15.png) + + +> 3 Formally, the reduction in the dimensionality (hence complexity) of the search space for discrete quantum controls originates in quantum symmetries, as shown in section III. Future work may aim to frame this statement within the context of Noether’s theorem for optimal control, which assigns so-called conserved currents to solutions to the maximum principle, based on such symmetries. + +38 + +systems. However, the simple features of quantum control landscapes described above indicate that optimal control search need not add additional complexity to these problems. + +The simple topology and geometry of quantum control landscapes, moreover, can be exploited to develop both numerical and experimental search algorithms that may outperform local or adaptive algorithms. The further development of global experimental algorithms is of particular interest, as these would take advantage of landscape structure without suffering from the exponentially unfavorable scaling of the cost of quantum simulation with Hilbert space dimension. + +Although the landscapes for control of finitedimensional quantum systems are thus simpler than those for classical systems, the need for statistical inference of quantum observable expectation values, states, or gradients thereof adds additional overhead to the cost of identifying optimal controls in an experimental context. This overhead is exacerbated when applying global algorithms that attempt to take advantage of information regarding the quantum state or dynamical propagator at each step along the landscape search trajectory. An especially noteworthy challenge, therefore, is the characterization of how the emerging methodologies of quantum statistical inference may be employed to further reduce the search complexity of quantum control problems. + +The other feature of quantum dynamics that might be considered prohibitive to their effective control, namely quantum decoherence, was shown to not have a significant effect on some of the most important properties of optimal control landscapes, in particular their critical topology. Future work should more thoroughly explore how the geometry of the control landscapes and the effectiveness of global search algorithms are affected by noise, the nonunitary evolution of incoherent quantum dynamics, and measurement. + +### APPENDIX A: MATHEMATICAL APPENDICES + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0038-06.png) + + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0038-07.png) + + +Theorem 4 Suppose the function x = f (y) is locally surjective near some point y0 ∈ Y , i.e., the Jacobian + +has full rank: + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0038-10.png) + + +in some local coordinate system. Then y0 is critical for L ◦ f in Y if and only if x0 = f (y0) is critical for L in X, and they have identical numbers of positive and negative Hessian eigenvalues at y0 and x0, respectively. Moreover, if the inverse image f−1 (x0) of every critical point x0 is connected, then the connected components of their critical manifolds are one-to-one between the two landscapes [60]. + +### b. Hessian quadratic form: observable maximization + +On the domain of unitary propagators, the Hessian quadratic form (HQF) for observable maximization can be written + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0038-14.png) + + +at a unitary matrix U , expanded along an arbitrary direction A in the Lie algebra of U (N ). Consider a particular solution Ul that generates orderings λj → πl(λj ) of the eigenvalues of Θ, where the array πl specifies an N-index permutation mapping. If we define the matrix elements of A as Aij = αij + iβij, we obtain after some straightforward calculations [17] + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0038-16.png) + + +from which the counting results presented in section II can be derived. The number of positive principal axis directions equals the number of (j, k) pairs for which (λj − λk)(ǫj − ǫk) ≥ 0, and the number of negative principal axis directions equals the number of (j, k) pairs for which (λj − λk)(ǫj − ǫk) ≤ 0. + +### 2. Maximum principle and adjoint control systems + +Theorem 5 (Pontryagin maximum principle) Consider the problem of steering the control system + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0038-20.png) + + +where M is the state space of the system, from some initial state x(0) = x0 to some final state x1 while + +39 + +minimizingmaximum principlea cost ofstatesthe thatformif the0Tf 0 couple(x, u)dt. (t), ¯xThe(t) is optimal, there exists an absolutely continuous vector λ(t) ∈ Rn and a constant λ ≤ 0, such that the PMP-Hamiltonian function h(x(t), λ(t), u(t)) = ⟨λ(t), f (x(t), u(t))⟩ + λ0f0 (x(t), u(t)) satisfies + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0039-02.png) + + +and + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0039-04.png) + + +Moreover, denoting the tangent space to the manifold M at state x(t ) by Tx(t′)M , we have ⟨λ(0), Tx(0)M ⟩ = ⟨λ(T ), Tx(T )M ⟩ (transversality condition) [27]. If the final time T is fixed, h(¯x(t), λ(t), ¯u(t)) is constant, whereas if T is allowed to vary, h(¯x(t), λ(t), ¯u(t)) = 0. + +If the control objective is to minimize the final time T instead of a cost of the form above, the optimal trajectory on [0, T ] is associated with the Hamiltonian −λ0 + ⟨λ(t), f (x(t), u(t)). In this case, maxuh(¯x(t), λ(t), u) = 0 in [0, T ], and we have the additional condition that if λ0 = 0, then λ(t) = 0 for any t [27]. + +Definition 3 (Normal, abnormal extremals) A trajectory x¯(t) satisfying the above condition is called an extremal. If λ0 = 0, it is called an abnormal extremal; if λ0 < 0, it is called a normal extremal. If an extremal is abnormal but not normal, it is called a strictly abnormal extremal. + +Definition 4 (Adjoint control system) Consider the following control system F on a Lie group G: + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0039-09.png) + + +Let K denote the subgroup spanned by the control Hamiltonians µj, and denote the adjoint orbit of −iHd (the internal, or drift Hamiltonian) under the action of the subgroup K by AdK(−iHd), i.e. AdK (−iHd) = {k1†(−iHd)k1|k1∈K}.Thentheadjoint ˙controlsys- tem of F is defined as the system P = HP, H ∈ AdK (−iHd), P ∈ G, which evolves on the coset space G/K [8]. + +Definition 5 (Infimizing time) For the control system F above, let R(I, t) denote the reachable set from the identity in time t. Then t (UF ) = inf{t ≥ 0|UF ∈ R(I, t)} is called the infimizing time for producing the propagator UF . + +Theorem 6 (Equivalence theorem) The infimizing time t (UF ) for steering the system + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0039-13.png) + + +from U (0) = I to UF is the same as the minimum coset time L (KUF ) for steering the adjoint system + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0039-15.png) + + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0039-16.png) + + +Theorem 7 (Adjoint maximum principle) For the above adjoint control system, denote the time-optimal control law by H¯ (t) and the corresponding optimal trajectory by P¯ (t). Define an adjoint auxiliary cost function, f (P ) = Tr(λ HP ), Pλ ∈ p. The corresponding adjoint PMP-Hamiltonian is h(P (t), λ(t), H(t)) = Tr(λ (t)H(t)P (t)) ≡ Tr(N (t)H(t)). The optimal adjoint control-trajectory pairs are then the solutions to the Hamiltonian equations dλd(tt) = − ∂P∂h= Hˆ(t)λ(t). The adjoint maximum principle [9] demands that there exists a N (t) ∈ p (directions in G/K space) such that + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0039-18.png) + + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0039-19.png) + + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0039-20.png) + + +### 3. Rotating wave approximation + +The rotating wave approximation (RWA) consists of a unitary change of coordinates (and controls) by which the internal (drift) Hamiltonian can be eliminated in problems involving atom-electromagnetic wave interactions, by virtue of the electromagnetic radiation being nearly resonant, or where the interaction Hamiltonian couples only neighboring states [28]. We consider the latter case. Let ψ(t) = U (t)ψ (t). Then the state vector in the rotated coordinate system satisfies the Schrodinger equation: + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0039-23.png) + + +40 + +where the Hamiltonian in the rotated coordinate system is + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0040-02.png) + + +In order to eliminate the internal Hamiltonian, we choose U (t) = exp(−iDt); since H = D + V (t), H = iUDU + U (D + V (t))U = exp(iDt)V (t) exp(−iDt). Redefining ψ → ψ and H := −iH , we have + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0040-04.png) + + +where H is skew-Hermitian. The elements of this Hamiltonian are either zero or are controls; as such, the drift is eliminated. Assuming that the control Hamiltonian V is off-diagonal (i.e., Vi,j = 0 only if i = j ± 1), the relation between the original and ”new” controls Hj,k(t) is: + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0040-06.png) + + +In the more general case where the control Hamiltonian is not off-diagonal but the control fields are assumed to be roughly in resonance with the system transition fre- + +quencies, the transformed Hamiltonian takes on a similarly simple form under the approximation that rapidly oscillating terms average to zero. + +### 4. Analytical solutions to state and gate control problems + +### a. Low-dimensional gate control problems + +Consider the right-invariant control system described in Definition 4. Following section III C, let G denote the special unitary group SU (N ). Call the subalgebra generated by the controls {µ1, ..., µm} l, and the corresponding subgroup K. If we decompose G = p ⊕ l such that p is orthogonal to l, then p represents all possible directions to move in G/K space. Denote by h ⊂ p a subspace of maximally commuting directions or generators in G/K. + +Specifically, in the case of two-qubit systems, G/K = SU (4)/SU (2) ⊗ SU (2), g = su(4), and K = SU (2) ⊗ SU (2). In this case, it can be shown that the Lie algebras l, p, and h are + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0040-13.png) + + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0040-14.png) + + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0040-15.png) + + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0040-16.png) + + +Decomposing the target unitary propagator as UF = k2 exp(Y )k1, (section III C), we have + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0040-18.png) + + +where the sub-Riemannian problem consists of generating exp(Y ) in the fastest possible way. If we define ky = exp(−iπ/2Iy) exp(−iπ/2Sy) and ky+ = exp(iπ/2Iy) exp(−iπ/2Sy), we can verify that + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0040-20.png) + + +and similarly for kx, showing we can generate any element of the Cartan subalgebra h. + +In the case of three spins coupled by local interactions such that J12 = J23, J13 = 0, G/K is a nonsymmetric space, but is still a finite-dimensional Riemannian manifold. Khaneja and coworkers [9] considered the generation of unitary propagators of the form + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0040-23.png) + + +which are hard to produce as they involve trlinear terms in the effective Hamiltonian (trilinear propagators). Applying the decomposition UF = k2 exp(Y )k1, it is sufficient to produce + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0040-25.png) + + +41 + +because all other propagators belonging to the set exp(−iθI1αI2βI3γ|α, β, γ ∈{x, y, z}} of trilinear propagators can be produced from UF in arbitrarily small time by selective hard pulses. + +The corresponding adjoint control problem (Definition 4) has H ∈ AdK (−i2πJ(I1zI2z + I2zI3z)). For adjoint control problems, an equivalent version of the Pontryagin maximum principle exists (Appendix A 2). It can be verified [9] that the following analytical time-dependent control satisfies this principle: + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0041-03.png) + + +and steers the adjoint system from P (0) = I to P (T ) ∈ KUF in minimal time. The minimum time t (UF ) required to produce a propagator of the form UF = exp(−iθI1zI2zI3z), θ ∈ [0, 4π] is then given by + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0041-05.png) + + +where κ = θ/2π. + +### b. Low-dimensional state control problems + +Analytical solutions to problems of state-to-state population transfer can be obtained for two- and three-level quantum systems, for off-diagonal control Hamiltonians (i.e., Vjk = 0 if j = k ± 1), under the rotating wave approximation (Appendix A 3). We summarize the results for the optimal control of population transfer in three-level systems using fluence as the cost, with two controls that span the control Lie algebra [28], since this provides an example for how objective function symmetry can endow integrability to quantum control systems and more generally simplify the search for optimal controls. Let us consider the problem of transferring the population from pure state | 1⟩ to pure state | 3⟩, with µ1,12 = µ2,21 = 1, µ2,12 = µ2,21 = 1. The Schrodinger equation for the Hamiltonian in this case can be written + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0041-09.png) + + +where ci denote the coefficients of the wavefunction eigenstates xi. If we set c1 = x1 +ix2, c2 = x4−ix3, c3 = + +˙ x5+ix+6, we can write this concisely as x = ε1F1+ε2F2, where x = (x1, ..., x6) and F1 = (−x3, −x4, x1, x2, 0, 0) and F2 = (0, 0, x5, x6, −x3, −x4) denote the action of the control Hamiltonians µ1 and µ2 on the state x. This is a problem on the 5-dimensional Hilbert sphere, S5 . The initial condition for this problem is a point on the circle Sin1≡{x∈S5|x2 1+ x2 2=1},whereasthetarget is a point on the circle Sfin1≡{x∈S5|x2 5+ x= 61}. However, the dimensionality of this problem can be reduced if we assume that the controls are resonant (section III B). In this case, for each x0 ∈ Sin1,theorbit O(x0) (the reachable set of states) is a two-dimensional submanifold of S5 , and hence the system is not fully controllable (i.e. not all superpositions of states can be reached from arbitrary initial conditions). Nonetheless, arbitrary eigenstate-eigenstate transitions can be controlled. Let us define x0(α) as the initial condition x1(0) = cos(α), x2(0) = sin(α), α ∈ [0, 2π]. Then this submanifold is the two-dimensional sphere defined by the equation x 12+ x′ 32+ x′ 52= 1,wherex′= R ⊗I3x. In other words, due to the isometry, all the points in Sin can be considered equivalently. Therefore, we can study the optimal control problem on the orbit O(x0). Let us consider the case where x0 is defined by x1 = 1; in this case, O(x0) is the sphere defined by x2 1+ x2 3+ x2 5= 1. + +We can then execute a change of variables to y1 = x1,y2 = x3, y3 = −x5, such that the control system can be rewritten as + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0041-13.png) + + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0041-14.png) + + +The control system can then be written + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0041-16.png) + + +where G1 = ∂θ, G2 = tan(θ)∂φ. In spherical coordinates, the Hamiltonian associated with the maximum principle is + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0041-18.png) + + +42 + +The maximum principle demands ∂v∂h 1= 0, ∂v∂h2= 0; as such, v1 = Pθ, v2 = Pφ tan(θ). The Hamiltonian corresponding to these controls is hˆ =1 2(P θ2+(tan(θ)Pφ)2). The Hamiltonian equations of motion following from the maximum principle are then: + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0042-02.png) + + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0042-03.png) + + +This Hamiltonian system is Liouville integrable, since there are two independent and commuting constants of the motion hˆ and Pφ = a. The solution for minimal fluence, with fixed transfer time T can then be shown to be [28]: + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0042-05.png) + + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0042-06.png) + + +In the following, we use the subscript + to refer to a point infinitesimally close to the system at point s. The interaction dynamical propagator in the interaction picture can then be written UI (t, 0) = U (t, 0)U+(t, 0). To first order, the interaction picture propagator at the final time T is: + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0042-08.png) + + +If we Taylor expand the Hamiltonian to first order at algorithmic time s, i.e., + +H+(s, t) = H(s + ds, t) = H(s, t) + ds∂H(s, t) , ∂s the expectation value of the observable of interest at the point s + ds in Hamiltonian space can be expressed as: + +### 5. Diffeomorphic homotopy on control landscapes + +We sketch the derivation of the general diffeomorphic homotopy procedure for Hamiltonian morphing and observable tracking [31, 32, 33]. The condition for remaining on a designated level set of an observable control landscape is + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0042-13.png) + + +It can then be shown that the condition for remaining on the level set can be written + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0042-15.png) + + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0042-16.png) + + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0042-17.png) + + +where + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0042-19.png) + + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0042-20.png) + + +A natural way to solve eqn (A2) is to transform it into an initial value problem for the laser field ε(s, t). Equa- + +43 + +tion (A2) can be reexpressed as the differential equation + +a0(s, t, T )∂ε(s, t) +a1(s, t, T )ε(s, t)+a2(s, t, T ) = f (s, t) ∂s + +where f (s, t) is an arbitrary function satisfying the constraint + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0043-04.png) + + +In general the coefficient a0(s, t, T ) may vanish at some values of s and t. Since there are a priori no restrictions on a0(s, t, T ), we cannot preclude the possibility of singular behavior with the class of D-MORPH controls admitted by equation (A2). Singular behavior is unattractive because it implies the possible existence of similar undesirable behavior in the control field ε. Regardless of the behavior of a0, a nonsingular class of D-MORPH solutions may be generated. Defining the integral + +The freedom to choose f (s, t) corresponds to the multiplicity of control field solutions on the level set, and arises naturally as a consequence of the underspecified nature of the integral form of the original D-MORPH equation. In particular, in the case of fluence minimization, f (s, t) = fm(s, t) satisfies the condition + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0043-07.png) + + +It can be shown [31] that with the choice + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0043-09.png) + + +the algorithm seeks to minimize the total field fluence at each step. Analogous free functions that correspond to other auxiliary costs (such as minimal time) in the Pontryagin maximum principle can be constructed. + +### 6. Controllability on compact Lie groups + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0043-12.png) + + +we have + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0043-14.png) + + +which is a Fredholm integral equation of the first kind. More generally, in the case that following a track of objective function values ⟨Θ(s)⟩ is desired, instead of remaining on a level set, we have + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0043-16.png) + + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0043-17.png) + + +Then, it can be shown [31] that this equation can be transformed into the equivalent (nonsingular) differential equation + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0043-19.png) + + +T where Γ(s) = �0[a0(s, t, T )]2 dt and γ(s) = T �0a0(s, t, T )f(s, t)dtistheprojectionofthearbitrary function f (s, t) onto a0(s, t, T ) (the above expression can be multiplied by an arbitrary shape coefficient S(t)). + +Definition 6 (Reachable sets and controllability) Consider a control system F defined on a manifold M . For each T > 0, and each x0 in M , the set of points reachable from x0 at time T , denoted by A(x0, T ), is equal to the set of terminal points x(T ) of integral curves of F that originate at x0. The union of A(x0, T ), for T ≥ 0, is called the reachable set[27] from x0. The set of points reachable in T or fewer units of time, defined as the union of A(x0, t), t ≤ T , is denoted A(x0, ≤ T ). A control system F is controllable if any point of M is reachable from any other point of M , at any time T > 0. + +Definition 7 (Invariant control system on a Lie group) A control system on a Lie group G, where G is the Lie group associated with a Lie algebra h, is defined by the equations + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0043-23.png) + + +where A and the Bi, i = 1, · · · , m belong to h, U (t) belongs to G, and the ui(t) are scalar functions of time which play the role of the external controls. The control system is said to be right-invariant if the following condition holds: If U (t) is a solution corresponding to the initial condition equal to the identity matrix, the solution corresponding to the initial condition F is given by U (t)F . + +44 + +### 7. Kraus superoperator formalism + +obeys the Schr¨odinger equation + +Consider a composite of system and environment whose Hamiltonian Htotal consists of the Hamiltonians of the system, environment, and their interaction. The total system evolution operator is Utotal(t) on the total Hilbert space H = HS ⊗HE, where HS and HE are the Hilbert spaces of the system (of dimension N ) and environment, respectively. The initial state of the total system is ρtot(0) = ρS ⊗ ρE. We can obtain an expression for the system dynamics ρS(t) by tracing ρtotal(t) = Utotal(t)ρtotal(0)Utotal†(t)overtheenviron- ment [60]: + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0044-04.png) + + +Define the λN -dimensional matrix K(t) = TrE{Utotal(t)(IN ρ1 E/2). Divide K into λ2 N × N matrices Kαβ(t) = |β⟩⟨α|)}, (α, β = 1, · · · , λ), where α, β constitute an arbitrary basis for HE. These matrices form the Kraus representation of the dynamical map as + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0044-06.png) + + +### 8. Symplectic propagators + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0044-08.png) + + +where the parameter is the time. The evolution propagator transforms the quadrature vector of position and ˆ momentum operators z = (ˆq1, · · · , ˆqN ; ˆp1, · · · , ˆpN )T linearly through + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0044-10.png) + + +where the 2N × 2N matrix S(t) is an element of the symplectic group Sp(2N, R) that satisfies ST JS = J, with + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0044-12.png) + + +Thus, the matrix S captures the Heisenberg equations of motion for the operators zˆi, and the unitary propagator U forms the metaplectic unitary representation of S in Sp(2N, R) [61]. Like the infinite-dimensional unitary group (but unlike the finite-dimensional unitary group), Sp(2N, R) is noncompact. + +Whereas a logical operation on N discrete quantum bits (qubits) is represented by a 2N -dimensional unitary matrix, the corresponding operation on N continuous quantum bits (qunits) can be represented by a 2N - dimensional symplectic matrix. + +Consider a continuous variable quantum system with a quadratic Hamiltonian H(t). H(t) induces a Hamiltonian vector field, which generates a one-parameter family of transformations U (t) on the Hilbert space H that + +### ACKNOWLEDGEMENTS + +The authors acknowledge support from DARPA. + + +![](.figures/arxiv__0710.0684/0710.0684.pdf-0044-18.png) + + +- [1] M. Demiralp and H. A. Rabitz, Phys. Rev. A 47, 809 (1993). + +- [2] A. Assion, T. Baumert, M. Bergt, T. Brixner, and B. Kiefer, Science 282, 5390 (1998). + +- [3] T. Baumert, T. Brixner, V. Seyfried, M. Strehle, and G. Gerber, Appl. Phys. B 65, 779782 (1997). + +- [4] J. L. Herek, J. Photochem. Photobiol. 180, 225 (2006). + +- [5] R. Bartels, S. Backus, E. Zeek, L. Misoguti, G. Vdovin, I. P. Christov, M. M. Murnane, and H. C. Kapteyn, Nature 406, 164 (2000). + +- [6] J. Palao and R. Kosloff, Phys. Rev. Lett. 89, 188301 (2002). + +- [7] M. Grace, C. Brif, H. A. Rabitz, I. Walmsley, R. L. + +Kosut, and D. A. Lidar, In press (2007), eprint arXiv:quant-ph/0611189. + +- [8] N. Khaneja, R. W. Brockett, and S. J. Glaser, Phys. Rev. A 63, 032308 (2001). + +- [9] N. Khaneja, S. J. Glaser, and R. W. Brockett, Phys. Rev. A 65, 032301 (2002). + +- [10] R. Wu and H. A. Rabitz, in preparation (2007). + +- [11] J. von Neumann, Tomsk. Univ. Rev. 1, 286 (1937). + +- [12] J. von Neumann, Unpublished works, Institute for Advanced Study Archives (1937). + +- [13] H. A. Rabitz, M. M. Hsieh, and C. M. Rosenthal, Science 303, 1998 (2004). + +- [14] M. D. Girardeau, S. G. Schirmer, J. V. Leahy, and R. M. + +45 + +Koch, Phys. Rev. A 58, 2684 (1998). + +- [15] T. S. Ho and H. A. Rabitz, J. Photochem. Photobiol. A 180, 226 (2006). + +- [16] R. Wu, M. M. Hsieh, and H. A. Rabitz, submitted (2007). + +- [17] H. A. Rabitz, M. M. Hsieh, and C. M. Rosenthal, J. Chem. Phys. 51, 204107 (2006). + +- [18] Z. Shen, M. M. Hsieh, and H. A. Rabitz, J. Chem. Phys. 124, 204106 (2006). + +- [19] G. Dirr, U. Helmke, K. Huper, M. Kleinsteuber, and Y. Liu, J. Global Opt. 35, 443 (2006). + +- [20] S. J. Glaser, T. Schulte-Herbruggen, M. Sieveking, O. Scheletzky, N. C. Nielsen, O. W. Sorensen, and C. Griesinger, Science 280, 421 (1998). + +- [21] H. A. Rabitz, M. M. Hsieh, and C. M. Rosenthal, Phys. Rev. A 72, 052337 (2005). + +- [22] T. S. Ho and H. A. Rabitz, To be submitted (2007). + +- [23] T. Frankel, in Differential and Combinatorial Topology: a symposium in honor of Marston Morse, edited by S. S. Cairns (Princeton University Press, Princeton, NJ, 1962), pp. 37–53. + +- [24] R. Wu, R. Chakrabarti, and H. Rabitz, Submitted (2007), eprint arXiv:0708.2118 [quant-ph]. + +- [25] S. Lloyd and S. L. Braunstein, Phys. Rev. Lett. 82, 1784 (1999). + +- [26] R. Wu, R. Chakrabarti, and H. A. Rabitz, To be submitted (2007), eprint arXiv:0708.3822 [math-ph]. + +- [27] V. Jurdjevic, Geometric control theory (Cambridge University Press, Cambridge, 1997). + +- [28] U. Boscain, G. Charlot, J. P. Gauthier, S. Guerin, and H. R. Jauslin, J. Math. Phys. 43, 2017 (2002). + +- [29] U. Boscain and G. Charlot, To be submitted (2007). + +- [30] D. D’Alessandro and M. Dahleh, IEEE Trans. Autom. Control 46, 866 (2001). + +- [31] A. Rothman, T. S. Ho, and H. A. Rabitz, Phys. Rev. A 72, 023416 (2005). + +- [32] A. Rothman, T. S. Ho, and H. A. Rabitz, J. Chem. Phys. 123, 134104 (2006). + +- [33] A. Rothman, T. S. Ho, and H. A. Rabitz, Phys. Rev. A 73, 053401 (2006). + +- [34] V. Beltrani, J. Dominy, and H. A. Rabitz, Phys. Rev. A 74, 043414 (2007). + +- [35] J. Roslund, M. Roth, and H. A. Rabitz, Phys. Rev. A 74, 043414 (2006). + +- [36] J. Roslund and H. A. Rabitz, To be submitted (2007). + +- [37] D. Cardoza, C. Trallero-Herrero, F. Langhojer, H. A. Rabitz, and T. Weinacht, J. Chem. Phys. 122, 124306 (2005). + +- [38] E. Wells, K. J. Betsch, C. W. S. Conover, M. J. DeWitt, D. Pinkham, and R. R. Jones, Phys. Rev. A 72, 063406 (2005). + +- [39] M. Wollenhaupt, A. Prakelt, C. Sarpe-Tudoran, D. Liese, and R. Baumert, J. Mod. Opt. 52, 2187 (2005). + +- [40] J. Roslund and H. A. Rabitz, To be submitted (2007). + +- [41] J. Malley and J. Hornstein, Stat. Sci. 8, 433 (1993). + +- [42] R. Chakrabarti, R. Wu, and H. A. Rabitz, To be submitted(2007), eprint arXiv:0708.3384 [quant-ph]. + +- [43] V. Ramakrishna, M. V. Salapaka, M. Dahleh, and H. A. Rabitz, Phys. Rev. A 51, 960 (1995). + +- [44] V. Jurdjevic and J. Sussmann, J. Diff. Equat. 12, 313 (1972). + +- [45] D. D’Alessandro, Syst. Control Lett. 41, 213 (2000). + +- [46] R. Wu, T. J. Tarn, and C. W. Li, Phys. Rev. A 73, 012719 (2006). + +- [47] M. Jezek, J. Fiurasek, and Z. Hradil, Phys. Rev. A 68, 012305 (2003). + +- [48] G. Maday and G. Turinici, J. Chem. Phys. 118, 8191 (2003). + +- [49] G. G. Balint-Kurti, F. R. Manby, Q. Ren, M. Artamonov, T. S. Ho, and H. A. Rabitz, J. Chem. Phys. 122, 084110 (2005). + +- [50] B. Amstrup, G. J. Toth, G. Szabo, H. A. Rabitz, and A. Loerincz, J. Phys. Chem. 99, 5206 (1995). + +- [51] T. Brixner, M. H. Damrauer, B. Kiefer, and G. Gerber, J. Chem. Phys 118, 3692 (2003). + +- [52] R. Levis, G. Menkir, and H. A. Rabitz, Science 292, 709 (2001). + +- [53] R. Chakrabarti, R. Wu, and H. A. Rabitz, To be submitted (2007), eprint arXiv:0708.3513 [quant-ph]. + +- [54] H. T. Siegelmann, A. B. Hur, and S. Fishman, Phys. Rev. Lett. 83, 1463 (1999). + +- [55] G. T. Riviello, K. A. Moore, and H. A. Rabitz, In preparation (2007). + +- [56] K. A. Moore, M. M. Hsieh, and H. A. Rabitz, To be submitted (2007). + +- [57] J. Dominy and H. A. Rabitz, In preparation (2007). + +- [58] J. Rehacek, Z. Hradil, E. Knill, and A. I. Lvovsky, Phys. Rev. A 75, 042108 (2007). + +- [59] A. A. Agrachev and A. V. Sarychev, Journal of Math. Syst. Est. Control 5, 1 (1995). + +- [60] R. Wu, A. Pechen, H. A. Rabitz, M. M. Hsieh, and B. Tsou, Submitted (2007), eprint arXiv:0708.2119 [quant-ph]. + +- [61] Arvind, B. Dutta, N. Mukunda, and R. Simon, Phys. Rev. A 52, 1609 (1995). diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/references/rendered/10-1016-j-jmr-2004-11-004.md b/tracks/qcs/solutions/frustration-free/challenge-113/references/rendered/10-1016-j-jmr-2004-11-004.md new file mode 100644 index 000000000..43713a9aa --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/references/rendered/10-1016-j-jmr-2004-11-004.md @@ -0,0 +1,586 @@ +--- +source: "https://doi.org/10.1016/J.JMR.2004.11.004" +type: "doi" +canonical_id: "10.1016/J.JMR.2004.11.004" +title: "Optimal control of coupled spin dynamics: design of NMR pulse sequences by gradient ascent algorithms." +authors: "N. Khaneja, Timo O. Reiss, C. Kehlet, T. Schulte-Herbrüggen, S. Glaser" +year: "2005" +venue: "Journal of magnetic resonance (San Diego, Calif. 1997 : Print)" +doi: "10.1016/J.JMR.2004.11.004" +full_text: yes +--- + +# Optimal control of coupled spin dynamics: design of NMR pulse sequences by gradient ascent algorithms. + +**Authors:** N. Khaneja, Timo O. Reiss, C. Kehlet, T. Schulte-Herbrüggen, S. Glaser + +**Citation:** Journal of magnetic resonance (San Diego, Calif. 1997 : Print), vol. 172 2, pp. + 296-305 + , 2005 + +**DOI:** [10.1016/J.JMR.2004.11.004](https://doi.org/10.1016/J.JMR.2004.11.004) + +## Abstract + +_(abstract unavailable)_ + +## Full Text + +Optimal Control of Coupled Spin Dynamics : Design of N M R Pulse Sequences by Gradient Ascent Algorithms + +Preprint of “GRAPE” article: + +N. Khaneja, T. Reiss, C. Kehlet, T. Schulte-Herbrüggen, S. J. Glaser, + +" **Optimal Control of Coupled Spin Dynamics: Design of NMR Pulse Sequences by Gradient Ascent Algorithms** ", + +Navin K hanejaa, # , Timo Reissb , Cindie K ehletb , Thomas Schulte-Herbr¨uggenb , Ste en J. Glaserb,# + +.J. Magn. Reson. 172, 296-305 (2005) + +> a Div ision of Applied Sciences, Harv ard Univ ersity, Cambridge, MA 02138, USA + +> b Chemistry Department, Technische Univ ersit¨at M¨unchen, 85747 Garching, Germany + +### Abstract + +In this paper, we introduce optimal control-related gradient-based numerical algorithms for the design of pulse sequences in NMR spectroscopy. This methodology is used for designing pulse sequences that maximize the coherence transfer between coupled spins in a given spe ed time, minimize the relaxation e ects in a given coherencetransfer step or minimize the time required to produce a given unitar y propagator, as desired. T he application of these gradient ascent pulse engineering (GRAPE) methods to design pulse sequences that are robust to experimentally important parameter variations, such as chemical shift dispersion or rf variations due to imperfections such as rf-inhomogeneity is also explained. + +> # Corresponding authors. + +E-mail addresses: navin@hrl. harvard.edu (N. K haneja), glaser@ch.tum.de (S. J. Glaser) + +1 + +# 1 Introduction + +In applications of NMR spectroscopy it is desirable to have optimized pulse sequences tailored to specific applications. For example, in multi-dimensional NMR experiments one is often interested in pulse sequences which maximize the coherence transfer between coupled spins in a given specified time, minimize the relaxation effects in a given coherence transfer step or minimize the time required to produce a given unitary propagator. From an engineering perspective all these problems are challenges in optimal control [1, 2] where one is interested in tailoring the excitation to a dynamical system to maximize some performance criterion. In this paper we present gradient ascent algorithms for optimizing pulse sequences (control laws) for steering the dynamics of coupled nuclear spins. Similar methods and their variants have been applied in Laser spectroscopy [3, 4, 5, 7]. In NMR, this approach has been used to design band-selective pulses [8, 9, 10], robust broadband excitation and inversion pulses [11, 12, 13]. However, previous studies in NMR were limited to uncoupled spin systems whose dynamics is governed by the Bloch equations. It is important to note that the optimal control principles are standard text book material in applied optimal control [1, 2]. The focus of this paper is the application of these methods for some important problems in NMR. Previously, gradientbased optimizations of NMR pulse sequences for coupled spin systems have almost exclusively relied on gradients computed by the difference method. One important exception are analytical derivatives introduced by Levante et al. [14] for pulse sequence optimizations, where the performance can be expressed in terms of the eigenvalues and eigenfunctions of the total propagator. + +The paper is organized as follows. In section 2, we present the basic theoretical ideas and numerical optimization algorithms directly applicable to the problem of pulse design. To illustrate the method, we present three simple but non-trivial applications to coupled spin systems both in the presence and in the absence of relaxation. In section 3.1, we look at the problem of finding maximum coherence transfer achievable in a given time and the design of pulse sequences that achieve this transfer. In section 3.2, the algorithm is used to find relaxation optimized pulse sequences that perform desired coherence transfer operations with minimum losses. In section 3.3, we design pulse sequences that produce a desired unitary propagator in a network of coupled spins in minimal time. In all examples, we compare the results obtained by the numerical optimization algorithm with optimal solutions obtained by analytical arguments based on geometric optimal control theory. In the conclusion section, we discuss the convergence properties of the proposed algorithm and possible extensions. + +2 + +# 2 Theory + +## 2.1 Transfer between hermitian operators in the absence of relaxation + +To ideas, we consider the problem of pulse design for polarization or coherenece transfer in the absence of relaxation. The state of the spin system is characterized by the density operator _ρ_ ( _t_ ), and its equation of motion is the Liouville-von Neuman equation [15] + + +![](.figures/doi__10.1016-j.jmr.2004.11.004/10.1016-j.jmr.2004.11.004.pdf-0003-03.png) + + +where _H_ o is the free evolution Hamiltonian, _Hk_ are the radio-frequency (rf) Hamiltonians corresponding to the available control fields and _u_ ( _t_ ) = ( _u_ 1( _t_ ) _, u_ 2( _t_ ) _, . . . , um_ ( _t_ )) represents the vector of amplitudes that can be changed and which is referred to as control vector. The problem is to find the optimal amplitudes _uk_ ( _t_ ) of the rf fields that steer a given initial density operator _ρ_ (0) = _ρ_ o in a specified time _T_ to a density operator _ρ_ ( _T_ ) with maximum overlap to some desired target operator _C_ . For hermitian operators _ρ_ o and _C_ , this overlap may be measured by the standard inner product + + +![](.figures/doi__10.1016-j.jmr.2004.11.004/10.1016-j.jmr.2004.11.004.pdf-0003-05.png) + + +(For the more general case of non-hermitian operators, see section 2.2). Hence, the performance index Φo of the transfer process can be defined as + + +![](.figures/doi__10.1016-j.jmr.2004.11.004/10.1016-j.jmr.2004.11.004.pdf-0003-07.png) + + +In the following, we will assume for simplicity that the chosen transfer time _T_ is discretized in _N_ equal steps of duration ∆ _t_ = _T/N_ and during each step, the control amplitudes _uk_ are constant, i.e. during the _j_th step the amplitude _uk_ ( _t_ ) of the _k_th control Hamiltonian is given by _uk_ ( _j_ ) (c.f. Fig. 1). The time-evolution of the spin system during a time step _j_ is given by the propagator + + +![](.figures/doi__10.1016-j.jmr.2004.11.004/10.1016-j.jmr.2004.11.004.pdf-0003-09.png) + + +The density operator at time _t_ = _T_ is + + +![](.figures/doi__10.1016-j.jmr.2004.11.004/10.1016-j.jmr.2004.11.004.pdf-0003-11.png) + + +and the performance function Φo (Eq. 3) to be maximized can be expressed as + + +![](.figures/doi__10.1016-j.jmr.2004.11.004/10.1016-j.jmr.2004.11.004.pdf-0003-13.png) + + +3 + +Using the definition of the inner product (c.f. Eq. 2) and the fact that the trace of a product is invariant under cyclic permutations of the factors, this can be rewritten as + + +![](.figures/doi__10.1016-j.jmr.2004.11.004/10.1016-j.jmr.2004.11.004.pdf-0004-01.png) + + +where _ρj_ is the density operator _ρ_ ( _t_ ) at time _t_ = _j_ ∆ _t_ and _λj_ is the backward propagated target operator _C_ at the same time _t_ = _j_ ∆ _t_ . Let us see how the performance Φo changes when we perturb the control amplitude _uk_ ( _j_ ) at time step _j_ to _uk_ ( _j_ ) + _δuk_ ( _j_ ). From Eq. (4) the change in _Uj_ to first order in _δuk_ ( _j_ ) is given by + + +![](.figures/doi__10.1016-j.jmr.2004.11.004/10.1016-j.jmr.2004.11.004.pdf-0004-03.png) + + +with + + +![](.figures/doi__10.1016-j.jmr.2004.11.004/10.1016-j.jmr.2004.11.004.pdf-0004-05.png) + + +and + + +![](.figures/doi__10.1016-j.jmr.2004.11.004/10.1016-j.jmr.2004.11.004.pdf-0004-07.png) + + +This follows from the standard formula + + +![](.figures/doi__10.1016-j.jmr.2004.11.004/10.1016-j.jmr.2004.11.004.pdf-0004-09.png) + + +For small ∆ _t_ (when ∆ _t ≪||H_ o +_m_ _k_ =1_uk_(_j_)_Hk||−_1), _H k ≈ Hk_ and using Eqs. (7) and (8) we find to first order in ∆ _t_ + + +![](.figures/doi__10.1016-j.jmr.2004.11.004/10.1016-j.jmr.2004.11.004.pdf-0004-11.png) + + +Observe we increase the performance function Φo if we choose + + +![](.figures/doi__10.1016-j.jmr.2004.11.004/10.1016-j.jmr.2004.11.004.pdf-0004-13.png) + + +where _ϵ_ is a small step size. This forms the basis of the following algorithm, which we denote GRAPE (gradient ascent pulse engineering) in order to distinguish it from conventional gradient approaches used in NMR based on difference methods. + +### Basic GRAPE algorithm + +1) Guess initial controls _uk_ ( _j_ ). + + +![](.figures/doi__10.1016-j.jmr.2004.11.004/10.1016-j.jmr.2004.11.004.pdf-0004-17.png) + + +4) Evaluate _δ_ Φo _/δuk_ ( _j_ ) and update the _m × N_ control amplitudes _uk_ ( _j_ ) according to Eq. (13). + +4 + +5) With this as the new controls, go to step 2). + +The algorithm is terminated if the change in the performance index Φo is smaller than a chosen threshold value. + +In principle, the choice of starting _uk_ ( _j_ ) can be completely random. However, an educated guess might lead to faster convergence. Clearly, since the algorithm is based on a gradient ascent procedure, there is no guarantee that it will converge to a global minimum. However at each step the algorithm moves in the direction of increasing performance (c.f. Fig. 1), so we can be assured that it converges to control amplitudes that are extremal points of the desired performance function. To expedite the process of this convergence, we can adopt standard conjugate gradient methods [2]. + +The important advantages of the optimal control related approach are best highlighted by comparing the GRAPE algorithm to conventionally used numerical difference methods to calculate the gradient _δ_ Φo _/δuk_ ( _j_ ) by computing Φo for the given pulse sequence _uk_ ( _j_ ) as well as for small variations of all _m × N_ control amplitudes. For example, for _N_ = 500 and _m_ = 4, the conventional approach would require to calculate 2001 full time evolutions of the density operator from _t_ = 0 to _T_ . In contrast, the GRAPE approach to calculate the same gradient _δ_ Φo _/δuk_ ( _j_ ) only requires two full time evolutions (one to propagate _ρ_ o from _t_ = 0 to _T_ and one to back-propagate _λN_ from _t_ = _T_ to 0), i.e. it is orders of magnitude faster. This makes it possible to efficiently optimizes NMR pulse sequences in much larger parameter spaces. As conventional approaches were typically limited to a few dozens of control variables, a typical strategy was to restrict the optimization to certain pulse families, such as composite pulses with a limited number of flip and phase angles [16, 17], Gaussian pulse cascades [18], spline functions [19] or Fourier expansions [20]. In contrast, the GRAPE algorithm allows for much higher flexibility as the number of pulse parameters to be optimized can be orders of magnitude larger compared to conventional approaches. + +## 2.2 Transfer between non-hermitian states in the absence of relaxation + +For non-hermitian operators _ρ_ o and _C_ (e.g. _ρ_ o = _S__−_ = _Sx −_ i _Sy_ and _C_ = _I__−_ = _Ix −_ i _Iy_ , c.f. section 3.1), Φo as defined in Eq. (3) cannot be used directly as a perfomance index for the optimization, because in general it is not real valued. Depending on the application [21], suitable performance functions for non-hermitian operators are the real part of Φo or the absolute value of Φo: + +5 + + +![](.figures/doi__10.1016-j.jmr.2004.11.004/10.1016-j.jmr.2004.11.004.pdf-0006-00.png) + + +or + + +![](.figures/doi__10.1016-j.jmr.2004.11.004/10.1016-j.jmr.2004.11.004.pdf-0006-02.png) + + +where _C__x_ and i _C__y_ are the hermitian and skew-hermitian parts of the target operator _C_ and _ρ__x_ oand i _ρ__y_ oarethehermitianandskew-hermitianpartsof_ρ_o. + +For the performance function Φ1 we the gradient to order in ∆ _t_ + + +![](.figures/doi__10.1016-j.jmr.2004.11.004/10.1016-j.jmr.2004.11.004.pdf-0006-05.png) + + +where _ρ__x_ _j_and_ρy_ _j_arethehermitianandskew-hermitianpartsof_ρj_=_ρx_ _j_+ i_ρy_ _j_andsimilarly_λx_ _j_and _λ__y_ _j_arethehermitianandskew-hermitianpartsof_λj_=_λx_ _j_+ i_λy_ _j_. + +For the performance function Φ2 the gradient to order in ∆ _t_ is given by + + +![](.figures/doi__10.1016-j.jmr.2004.11.004/10.1016-j.jmr.2004.11.004.pdf-0006-08.png) + + +Using the gradient _δ_ Φ1 _/δuk_ ( _j_ ) or _δ_ Φ2 _/δuk_ ( _j_ ) instead of _δ_ Φo _/δuk_ ( _j_ ) in step 4, the basic GRAPE algorithm described in section 2.1 can also be applied to optimize the transfer between non-hermitian operators. + +## 2.3 Relaxation-optimized coherence transfer + +In Liouville space [15], the equation of motion for the density operator in the presence of relaxation can be written as + + +![](.figures/doi__10.1016-j.jmr.2004.11.004/10.1016-j.jmr.2004.11.004.pdf-0006-12.png) + + +where _L_ˆ = _−_ i _H_ˆ + Γˆ is the Liouville superoperator, _H_ˆ is the Hamilton superoperator and Γˆ is the relaxation superoperator (including thermal correction [22] if appropriate). For simplicity, here we consider the transfer between hermitian operators _ρ_ o and _C_ , but the results can be easily generalized to non-hermition operators (c.f. section 2.2). According to Eq. (3), a suitable performance function is + + +![](.figures/doi__10.1016-j.jmr.2004.11.004/10.1016-j.jmr.2004.11.004.pdf-0006-14.png) + + +6 + +where now the final density operator _ρ_ ( _T_ ) is given by + + +![](.figures/doi__10.1016-j.jmr.2004.11.004/10.1016-j.jmr.2004.11.004.pdf-0007-01.png) + + +with + + +![](.figures/doi__10.1016-j.jmr.2004.11.004/10.1016-j.jmr.2004.11.004.pdf-0007-03.png) + + +Hence, the performance function can be expressed as + + +![](.figures/doi__10.1016-j.jmr.2004.11.004/10.1016-j.jmr.2004.11.004.pdf-0007-05.png) + + +and as in Eq. (12) to first order in ∆ _t_ + + +![](.figures/doi__10.1016-j.jmr.2004.11.004/10.1016-j.jmr.2004.11.004.pdf-0007-07.png) + + +where in the presence of relaxation, _λj_ and _ρj_ are defined in Eq. (23). + +## 2.4 Synthesis of unitary transformations + +Now we consider the problem to create in a given time _T_ a desired unitary propagator. The equation of motion for the propagator of a closed quantum system is + + +![](.figures/doi__10.1016-j.jmr.2004.11.004/10.1016-j.jmr.2004.11.004.pdf-0007-11.png) + + +At _t_ = 0, the initial propagator is _U_ (0) = 1. + +First we consider the problem to approach a desired propagator _UF_ by applying a pulse sequence _uj_ ( _t_ ) such that at the final time + + +![](.figures/doi__10.1016-j.jmr.2004.11.004/10.1016-j.jmr.2004.11.004.pdf-0007-14.png) + + +is minimized, which is equivalent to maximizing Re _⟨UF |U_ ( _T_ ) _⟩_ . Hence we can define the performance function to be optimized by the pulse sequence as + + +![](.figures/doi__10.1016-j.jmr.2004.11.004/10.1016-j.jmr.2004.11.004.pdf-0007-16.png) + + +and the corresponding gradient _δ_ Φ3 _/δuk_ ( _j_ ) to first order in ∆ _t_ is given by + + +![](.figures/doi__10.1016-j.jmr.2004.11.004/10.1016-j.jmr.2004.11.004.pdf-0008-01.png) + + +While the performance index Φ3 may be of theoretical interest, for practical applications, it is sufficient to approach the target propagator _UF_ only up to an arbitrary phase factor exp _{_ i _ϕ}_ and + + +![](.figures/doi__10.1016-j.jmr.2004.11.004/10.1016-j.jmr.2004.11.004.pdf-0008-03.png) + + +is to be minimized for choice of _ϕ_ , which is equivalent to maximizing the performance function + + +![](.figures/doi__10.1016-j.jmr.2004.11.004/10.1016-j.jmr.2004.11.004.pdf-0008-05.png) + + +with the operators _Xj_ and _Pj_ as defined in Eq. (27). The corresponding gradient _δ_ Φ4 _/δuk_ ( _j_ ) to first order in ∆ _t_ is given by + + +![](.figures/doi__10.1016-j.jmr.2004.11.004/10.1016-j.jmr.2004.11.004.pdf-0008-07.png) + + +## 2.5 Reduction of rf power and limited rf amplitudes + +In the given formulation of the optimization problem, it is also straight-forward to add to any of the previously defined performance functions Φ _i_ , a penalty + + +![](.figures/doi__10.1016-j.jmr.2004.11.004/10.1016-j.jmr.2004.11.004.pdf-0008-10.png) + + +for the total rf power applied during the pulse sequence to minimize sample heating, where _α_ is a weight of the penalty imposed for excessive rf-power. Hence, the gradient simply contains an additional term + + +![](.figures/doi__10.1016-j.jmr.2004.11.004/10.1016-j.jmr.2004.11.004.pdf-0008-12.png) + + +If the maximum rf amplitude is limited, this can be taken into account in the algorithm described in section 2.1 by resetting the amplitude to the maximum amplitude if it is exceeded after step 4 (see e.g. [12]). + +8 + +## 2.6 Robustness + +For practical applications, it is often desirable to achieve the optimum performance for a range of parameters _ω_ , such as a given range of chemical shifts and/or a given range of rf amplitudes to take into account the effects of rf inhomogeneity or rf miscalibration. If the range of parameters is sampled at discrete values _ωp_ , the total performance Φ _tot_ can be measured by summing over the performance of systems parameterized by _ωp_ : + + +![](.figures/doi__10.1016-j.jmr.2004.11.004/10.1016-j.jmr.2004.11.004.pdf-0009-02.png) + + +For example, for the case of hermitian transfer, with Φ( _ωp_ ) = Φo( _ωp_ ) (c.f. section 2.1) + + +![](.figures/doi__10.1016-j.jmr.2004.11.004/10.1016-j.jmr.2004.11.004.pdf-0009-04.png) + + +and + + +![](.figures/doi__10.1016-j.jmr.2004.11.004/10.1016-j.jmr.2004.11.004.pdf-0009-06.png) + + +# 3 Examples + +## 3.1 Time-optimal coherence-order selective in-phase transfer + +As a practical example, we consider coherence-order selective in-phase transfer ( _I__−_ _→ S__−_ ) [23] in a heteronuclear two-spin system in the absence of relaxation. Here we are interested in the following question: What is the minimum time to achieve a specified amount of coherence transfer, or conversely, what is the maximum possible coherence transfer amplitude in any given time _T_ in the absence of relaxation, i.e. under unitary evolution? This is a simple, but non-trivial example, which has only recently been solved analytically based on principles of geometric control [24, 25]. Hence, this constitutes an ideal test case for the presented GRAPE algorithm because numerically optimized transfer amplitudes can be directly compared to the theoretical benchmark provided by the analytical result. We assume that both spins _S_ and _I_ are on-resonance in the doubly rotating frame. The free evolution Hamiltonian of the spin system is + + +![](.figures/doi__10.1016-j.jmr.2004.11.004/10.1016-j.jmr.2004.11.004.pdf-0009-10.png) + + +9 + +where _J_ is the heteronuclear coupling constant. The initial density operator term of interest is _ρ_ o = _I__−_ = _Ix −_ i _Iy_ and the target operator is _C_ = _S__−_ = _Sx −_ i _Sy_ . In most practical applications of coherence-order-selective coherence transfer [21, 23, 26], the goal is to maximize _|⟨I__−_ _|ρ_ ( _T_ ) _⟩|_ . Hence, the appropriate performance function is Φ2 (c.f. Eq. (16)). The normalized absolute value of the transfer amplitude for a given mixing period _T_ is defined as [21] + + +![](.figures/doi__10.1016-j.jmr.2004.11.004/10.1016-j.jmr.2004.11.004.pdf-0010-01.png) + + +In our numerical optimizations based on the GRAPE algorithm, the heteronuclear coupling _J_ was chosen to be 1 Hz. 30 pulses were optimized with total durations _T_ in the range between 0 and 1.5 s (c.f. Fig. 2), each pulse was digitized in steps ∆ _t_ = 0.002 s. For each time step ∆ _t_ , the x and y rf amplitudes irradiated at spins _I_ and _S_ were optimized: _u_ 1( _j_ ) = _νx__I_(_j_),_u_2=_ν_ _y__I_(_j_),_u_3=_ν_ _x__S_(_j_), _u_ 4 = _νy__S_(_j_)and_H_1=2_πIx_,_H_2=2_πIy_,_H_3=2_πSx_,_H_4=2_πSy_(c.f.Eq.1).Forexamplefor _T_ = 1 _._ 5 s, this resulted in a total number of 6000 optimization parameters. For each value of _T_ , the gradient flow algorithm was started with initial sequences _uk_ ( _j_ ) which were created by assigning a random value to every tenth point and using a cubic spline fit to fill in the amplitudes _uk_ ( _j_ ) of the intermediate time points. This resulted in random but relatively smooth initial pulse amplitudes. In these optimizations, the maximum rf amplitude was not limited and we also did not include a penalty for increased rf power (c.f. section 2.5). In Fig. 2, the numerically optimized transfer efficiencies _η_ ( _T_ ) (circles) are superimposed with the analytical curve (solid line), representing timeoptimal pulses [24, 25]. For all chosen total durations _T_ , the maximum transfer efficiency _η_ ( _T_ ) found by the gradient algorithm converged to the analytically derived optimum values. The minimum time to reach full transfer ( _η_ = 1) ist _τ__∗_ = 3 _/_ (2 _J_ ) [24]. For _T < T__∗_ , the optimal transfer amplitude is up to 12.5% larger compared to the transfer amplitude of heteronuclear isotropic mixing [27, 28, 29]. For example, Fig. 3 shows an optimized pulse sequence found by the GRAPE algorithm for _T_ = 0 _._ 5 s. In order to simplify the comparison of the pulses applied to spins _I_ and _S_ , the arbitrary relative phase of the pulse sequence applied to spin _S_ was shifted by 200_◦_ . The figure shows that up to this relative phase shift of the _S_ pulse, the sequences are almost identical, as expected in order to create the required effective Hamiltonian [24]. Note that there is an infinite number of possible pulse sequences, which create the optimal average Hamiltonian and hence for each value of _T_ , many optimal solutions exist and the pulse sequences found by the GRAPE algorithm depend strongly on the initial random sequence. + +10 + +## 3.2 Relaxation-optimized pulse elements (ROPE) + +As a second example, we consider the problem to achieve optimal coherence transfer in the presence of relaxation. For an isolated two-spin system in the spin-diffusion limit, it has recently been demonstrated that currently used pulse sequence elements such as INEPT [30] are far from optimal. For example, if dipolar relaxation between an isolated pair of spins is the dominant relaxation mechanism, the in-phase to anti-phase transfer ( _Ix_ to 2 _IzSx_ ) via analytically derived relaxation optimized pulse sequence elements (ROPE) [31, 32] is up to a factor of _e/_ 2 = 1 _._ 36 more efficient than the traditional INEPT transfer. Here, we demonstrate the application of the GRAPE algorithm to the numerical optimization of ROPE-type sequences and compare the results to the analytical solutions. + +We consider a system, consisting of two coupled heteronuclear spins 1/2, denoted _I_ and _S_ , with a coupling constant of _J_ = 194 Hz. In the spin diffusion limit only the transverse relaxation rate _k_ is nonzero, assuming pure dipole-dipole relaxation (without CSA/DD cross-correlation effects) [31]. We consider the case where the transverse relaxation rate _k_ as defined in [31] is equal to the coupling constant, i.e. _k/J_ =1 (Here, a thermal correction of the relaxation superoperator need not be included if the transfer element is used as a mixing step [22]). For the transfer _Iz →_ 2 _IzSz_ , the initial desity operator is _ρ_ (0) = _Iz_ and the desired target operator: _C_ = 2 _IzSz_ . Both spins are assumed to be on-resonance in a doubly rotating frame. Pulse shapes consisting of _N_ = 75 discrete time steps were optimized for various pulse durations _T_ , using the gradient _δ_ Φo _/δuk_ ( _j_ ) given in Eq. (24). As in the previous example, a random initial sequence was created for each value of _T_ by assigning a random rf amplitudes to every tenth time point and using a cubic spline fit to interpolate the amplitudes _uk_ ( _j_ ) of the intermediate time points. + +Fig. 4 shows the transfer efficiency of the numerically optimized sequences (black circles). For comparison, the figure also shows the analytical curve representing the theoretical limit [31] of the transfer efficiency as a function of _T_ . E.g., for _T_ = 2 _._ 11 ms, (i.e. _T/J__−_1 = 0 _._ 408), the numerically optimized pulse sequence is shown in Fig. 5 A. This pulse shape is very close to the analytically derived ROPE pulse [31]. It is interesting to note that the numerically optimized pulse closely approaches the theoretical limit with a finite maximum rf amplitude. In the center of the pulse, _x_ and _y_ rf amplitudes are slightly overlapping, whereas this is not the case in the analytical solution, which has a short delay in the center of the sequence. However, the characteristic ROPE transfer mechanism (c.f. Figs. 4 and 6 in Ref. [31]) is evident in Fig. 5 B, which shows the trajectories of + +11 + +the non-vanishing terms of the density operator under the action of the pulse shape shown in Fig. 5 A. In contrast to INEPT, a large portion of _Ix_ is immediately transformed to _Iz_ , which is protected from relaxation in the present model. Consequently, _Iz_ is brought in an optimal trajectory back to the transverse plain in the first phase of the transfer. In the last phase, 2 _IySz_ is lifted in a an optimal way to 2 _IzSz_ , which is again protected against relaxation [31]. Although for simplicity, CSA relaxation was not considered in this example, it is straight-forward to include CSA relaxation as well as the effects of cross-correlation in the relaxation matrix and to numerically optimize corresponding pulses (data not shown) [33]. Furthermore, the algorithm is not limited to two coupled spins and more complicated relaxation networks can be taken into account. + +## 3.3 Time-optimal implementation of unitary transformations + +This example illustrates the use of the GRAPE algorithm in the development of pulse sequences that implement a desired unitary propagator in minimum time. We consider a chain of three heteronuclear spins with coupling constants _J_ 12 = _J_ 23 = _J_ , _J_ 13 = 0. In a multiple-rotating frame, in which the three heteronuclear spins are on resonance, the free evolution Hamiltonian _H_ o is + + +![](.figures/doi__10.1016-j.jmr.2004.11.004/10.1016-j.jmr.2004.11.004.pdf-0012-03.png) + + +Many applications in NMR spectroscopy [34, 35] and NMR quantum computing [36, 37, 38] require unitary transformations of the form + + +![](.figures/doi__10.1016-j.jmr.2004.11.004/10.1016-j.jmr.2004.11.004.pdf-0012-05.png) + + +We recently derived analytically the minimum time _T__∗_ ( _α_ ) to create _Uzzz_ ( _α_ ). The corresponding pulse sequences [39, 40] are considerably shorter than conventional implementations of these unitary propagators [34, 35, 41, 42]. For 0 _≤ α ≤ π/_ 2, the minimum time _T__∗_ is given by [39, 40] + + +![](.figures/doi__10.1016-j.jmr.2004.11.004/10.1016-j.jmr.2004.11.004.pdf-0012-07.png) + + +and _T__∗_ ( _n π ± α_ ) = _T__∗_ ( _α_ ), where _n_ is an arbitrary integer. + +Here, we used the gradient _δ_ Φ3 _/δuk_ ( _j_ ) defined in Eq. (28), where the initial unitary propagator is the identity matrix ( _U_ (0) = 1) and the target operator is _U_ ( _T_ ) = _Uzzz_ ( _α_ ) for six values of _α_ between 0 and _π/_ 2 (c.f. Fig. 6). The heteronuclear couplings _J_ were chosen to be 1 Hz and each pulse was digitized in steps ∆ _t_ = 0.0025 s. For each time step ∆ _t_ , the x and y rf amplitudes irradiated at spins _I_ 1 and _I_ 2 and _I_ 3 were optimized. For each value of _α_ , random initial pulse sequences + +12 + +were numerically optimized for various pulse durations _T_ , in order to determine the minimum _T_ , for which the numerical algorithm finds a performance index of Φ3 _/_ Tr _{_ 1 _}_ = 1. In this series of optimizations, _T_ was incremented in steps of 0.05 s for each value of _α_ . The shortest durations _T_ , for which a numerical value of 1.0 was found for Φ3 _/_ Tr _{_ 1 _}_ are indicated by circles in Fig. 6. These durations represent upper numerical limits for the minimum time _T__∗_ . Fig. 7 shows the numerically optimized pulse sequence for _α_ = _π/_ 8 and _T_ = 0 _._ 5 s. It is qualitatively similar to the analytically derived sequence, which consists only of rf pulses irradiated at spin _I_ 2 [39]. The curve representing the analytical solution of _T__∗_ (c.f. Eq. 41) is also shown in Fig. 6 for comparison. The asterisks represent the longest durations _T_ with Φ3 _/_ Tr _{_ 1 _} <_ 1 _._ 0. For _α_ = _π/_ 40, _π/_ 16, _π/_ 8, _π/_ 4, 3 _π/_ 8, and _π/_ 2, the numerical values of Φ3 _/_ Tr _{_ 1 _}_ at the times _T_ indicated by asterisks in Fig. 6 were 0.99995, 0.99998, 0.9987, 0.9997, 0.9985, and 0.9994, respectively. + +# 4 Conclusions + +In this paper we have presented a streamlined derivation of analytical gradients for the design of pulse sequences in NMR spectroscopy. We applied these optimal control related algorithms to the design of pulse shapes for problems involving transfer of coherence between coupled spins and synthesis of unitary propagators in a network of coupled spins. Although the theory and numerical principles are textbook material in the area of optimal control, its application to the control of coupled spin dynamics is new and promising. It should be noted that the proposed gradient ascent algorithms are not guaranteed to converge to a globally optimal pulse shape. All that can be said is the proposed algorithms will converge to a stationary point of the performance function. To speed up convergence, the algorithm can be further modified by using adaptive step sizes for updating the control amplitudes as well as by using conjugate gradients instead of ordinary ones. Yet, all these issues are technicalities of implementation that have not been addressed here, where we highlighted the basic ideas. In future extensions, we plan to test these type of variations in order to speed up algorithms. Note that variations of similar ideas have appeared in other fields of coherent control [3, 6, 43], where iterative modifications of controls yielded improved pulse shapes. All these methods only guarantee convergence to some critical point that does not have to be the global optimum. A standard modification to the gradient ascent adds some noise when updating the control amplitudes in order to avoid getting trapped in local minima. + +In the work introduced here, we have not only improved upon pulse sequences, but the GRAPE al- + +13 + +gorithms have lead us to novel coherence transfer pathways. Further investigation has even triggered anlytical solutions to optimal pulse shapes as well as optimal pulse sequences. We have also used special instantiations of the GRAPE algorithm for designing broadband excitation pulses in uncoupled spin systems [11], which are examples of robust control for a range of spin system parameters, such as chemical shift and rf amplitude. With the given gradients _δ_ Φ _i/δuk_ ( _j_ ), it is also straight forward to suppress undesired coherence transfers while simultaneously optimizing desired transfers. For example, this can be achieved by defining the overall quality factor as a (weighted) sum of e.g. Φ2 for the desired transfer and _−_ Φ2 for the undesired transfer. A practical problem is the choice of the number of time steps for the discretization of pulse shapes for a given control problem. This is directly related to the number of pulse parameters to be optimized. The discretization should be chosen to ensure that the condition given for Eq. (12) is approximately satisfied. In the presented examples, the chosen number of pulse parameters was sufficiently large to achieve the previously known theoretical performance limits, but we have not explored in detail the minimal number of pulse sequence parameters necessary to achieve the theoretical bounds. In practice, this may be done by increasing the number of pulse sequence parameters until convergence of the performance index is reached. In a recent paper [44], first applications of the GRAPE algorithm to polarization transfer in solid state NMR have been presented. This forms a further example demonstrating the optimization of robust pulse sequences for a large range of parameters, e.g. due to the powder average of dipolar couplings and the possibility to include a time-varying free-evolution Hamiltonian as in magic angle sample spinning. The algorithm introduced here is expected to be a very useful tool for developing superior pulse sequences in multiple spin systems. + +### Acknowledgments + +N.K. would like to acknowledge DARPA QUIST grant 496020-01-1-0556, NSF 0218411 and NSF 0133673. S.G. thanks the Deutsche Forschungsgemeinschaft for grants Gl 203/3-1 and Gl 203/4-2 and the Fonds der Chemischen Industrie. + +# References + +- [1] V. F. Krotov, Global methods in optimal control, Marcel Decker, New York (1996). + +- [2] A. Bryson, Jr., Y.-C. Ho, Applied optimal control, Hemisphere, Washington, D.C. (1975). + +14 + +- [3] D. J. Tannor, S. A. Rice, Control of selectivity of chemical reaction via control of wave packet evolution, _J. Chem. Phys._ 83, 5013-5018 (1985). + +- [4] S. Shi, H. Rabitz, Quantum mechanical optimal control of physical observables in microsystemss, _J. Chem. Phys._ 92 364- 376 (1990). + +- [5] W. Jakubetz, E. Kades, J. Manz, State-selective excitation of molecules by means of optimized ultrashort infrared laser pulsess, _J. Phys. Chem._ 97, 12609-12619 (1993). + +- [6] Y. Ohtsuki, G. Turinici, H. Rabitz, Generalized monotonically convergent algorithms for solving quantum optimal control problems, _J. Chem. Phys._ 120, 5509-5517 (2004). + +- [7] H. Rabitz, R. de Vivie-Riedle, M. Motzkus, K. Kompa, Whither the Future of Controlling Quantum Phenomena?, _Science_ 288, 824-828 (2000). + +- [8] S. Conolly, D. Nishimura, A.Macovski, Optimal control solutions to the magnetic resonance selective excitation problem, _IEEE Trans. Med. Imag._ MI-5, 106115 (1986). + +- [9] J. Mao, T. H. Mareci, K. N. Scott, E. R. Andrew, Selective inversion radiofrequency pulses by optimal control, _J. Magn. Reson._ 70 ,310318 (1986). + +- [10] D. Rosenfeld, Y. Zur, Design of adiabatic selective pulses using optimal control theory, _Magn. Reson. Med._ 36, 401409 (1996). + +- [11] T. E. Skinner, T. O. Reiss, B. Luy, N. Khaneja, S. J. Glaser, Application of optimal control theory to the design of broadband excitation pulses for high resolution NMR, _J. Magn. Reson._ 163, 8-15 (2003). + +- [12] T. E. Skinner, T. O. Reiss, B. Luy, N. Khaneja, S. J. Glaser, Reducing the duration of broadband excitation pulses using optimal control with limited rf amplitude, _J. Magn. Reson._ 167, 68-74 (2004). + +- [13] K. Kobzar, T. E. Skinner, N. Khaneja, S. J. Glaser, B. Luy, Exploring the limits of broadband excitation and inversion pulses, _J. Magn. Reson._ 170, 236-243 (2004). + +- [14] T. O. Levante, T. Bremi, R. R. Ernst, Pulse-sequence optimizations with analytical derivatives. Application to deuterium decoupling in oriented phases, _J. Magn. Reson. A_ 121, 167-177 (1966). + +- [15] R. R. Ernst, G. Bodenhausen, A. Wokaun, Principles of nuclear magnetic resonance in one and two dimensions, Clarendon Press, Oxford (1987). + +15 + +- [16] M. H. Levitt, Composite pulses, _Prog. NMR Spectrosc._ 18, 61-122 (1986). + +- [17] M. H. Levitt, Composite pulses, in Encyclopedia of Nuclear Magnetic Resonance, vol. 2, p.1396, (Eds.-in-Chief D.M.Grant and R.K.Harris), John Wiley & Sons, Chichester-NewYork-BrisbaneSingapore, 1996. + +- [18] L. Emsley, G. Bodenhausen, Gaussian pulse cascades: new analytical functions for rectangular selective inversion and in-phase excitation in NMR, _Chem. Phys. Lett._ 165, 469-476 (1990). + +- [19] B. Ewing, S. J. Glaser and G. P. Drobny, Development and optimization of shaped NMR pulses for the study of coupled spin systems” _Chem. Phys._ 147, 121-129 (1990). + +- [20] D. B. Zax, G. Goelman, S. Vega, Amplitude-modulated composite pulses, _J. Magn. Reson._ 80, 375-382 (1988). + +- [21] S. J. Glaser, T. Schulte-Herbr¨uggen, M. Sieveking, O. Schedletzky, N. C. Nielsen, O. W. Sørensen, C. Griesinger, Unitary control in quantum ensembles, Maximizing signal intensity in coherent spectroscopy, _Science_ 208, 421-424 (1998). + +- [22] M. H. Levitt, L. Di Bari, The homogeneous master equation and the manipulation of relaxation networks, _Bull. Magn. Reson._ 16, 94-114 (1994). + +- [23] M. Sattler, P. Schmidt, J. Schleucher, O. Schedletzky, S. J. Glaser, C. Griesinger, Novel pulse sequences with sensitivity enhancement for in-phase coherence transfer employing pulsed field gradients, _J. Magn. Reson. B_ 108, 235-242 (1995). + +- [24] N. Khaneja, R. W. Brockett, S. J. Glaser, Time-optimal control in spin systems, _Phys. Rev. A_ 63, 03208 (2001). + +- [25] T. O. Reiss, N. Khaneja, S. J. Glaser, Time-optimal coherence-order-selective transfer of inphase coherence in heteronuclear IS spin systems, _J. Magn. Reson._ , 154, 192-195 (2002). + +- [26] A. G. Palmer, J. Cavanagh, , P. E. Wright, M. Rance, Sensitivity improvement in protondetected heteronuclear correlation spectroscopy, _J. Magn. Reson._ 93, 151-170 (1991). + +- [27] P. Caravatti, L. Braunschweiler, and R. R. Ernst, Heteronuclear correlation spectroscopy in rotating solids, _Chem. Phys. Lett._ 100, 305-310 (1983). + +- [28] D. P. Weitekamp, J. R. Garbow, and A. Pines, Determination of dipole coupling constants using heteronuclear multiple quantum NMR, _J. Chem. Phys._ 77, 2870-2883 (1982). + +16 + +- [29] S. J. Glaser and J. J. Quant, Homonuclear and heteronuclear Hartmann-Hahn transfer in isotropic liquids, _in_ ”Advances in magnetic and optical resonance” (W. S. Warren, Ed.), Vol. 19, pp. 59 - 252, Academic Press, San Diego (1996). + +- [30] G. A. Morris, R. Freeman, Enhancement of nuclear magnetic resonance signals by polarization transfer, _J. Am. Chem. Soc._ 101, 760-762 (1979). + +- [31] N. Khaneja, T. Reiss, B. Luy, S. J. Glaser, Optimal control of spin dynamics in the presence of relaxation, _J. Magn. Reson._ 162, 311-319 (2003). + +- [32] D. Stefanatos, N. Khaneja, S. J. Glaser, Optimal control of coupled spins in presence of longitudinal relaxation, _Phys. Rev. A_ 69, 022319 (2004). + +- [33] N. Khaneja, B. Luy, S. J. Glaser, Boundary of quantum evolution under decoherence, _Proc. Natl. Acad. Sci. USA_ 100, 13162-13166 (2003). + +- [34] O. W. Sørensen, Polarization transfer experiments in high-resolution NMR spectroscopy, _Prog. NMR Spectrosc._ 21, 503-569 (1989). + +- [35] A. Meissner, O. W. Sørensen, _I_ -spin _n_ -quantum coherences in _InS_ spin systems employed for E.COSY-type measurement of heteronuclear long-range coupling constants in NMR, _Chem. Phys. Lett._ 276, 97-102 (1997). + +- [36] D. G. Cory, A. F. Fahmy, T. F. Havel, Ensemble quantum computing by NMR spectroscopy, _Proc. Natl. Acad. Sci_ 94, 1634-1639 (1997). + +- [37] N. Gershenfeld, I, L. Chuang, Bulk spin-resonance quantum computation, _Science_ 275, 350-356 (1997). + +- [38] C. H. Bennet, D. P. Di Vinvenzo, Quantum information and computation, _Nature_ 404, 247-255 (2000). + +- [39] N. Khaneja, S. J. Glaser, R. W. Brockett, Sub-Riemannian geometry and time optimal control of three spin systems: Quantum gates and coherence transfer, _Phys. Rev. A_ , 65, 032301 (2002). + +- [40] T. O. Reiss, N. Khaneja, S. J. Glaser, Broadband geodesic pulses for three spin systems: Timeoptimal realization of effective trilinear coupling terms and indirect SWAP Gates, _J. Magn. Reson._ 165, 95-101 (2003). + +17 + +- [41] C. H. Tseng, S. Somaroo, Y. Sharf, E. Knill, R. Laflamme, T. F. Havel, D. G. Cory, Quantum simulation of a three-body interaction Hamiltonian on an NMR quantum computer, _Phys. Rev. A_ , 61, 012302 (2000). + +- [42] J. Kim, J.-S. Lee, S. Lee, Implementing unitary operators in quantum computation, _Phys. Rev. A_ , 61, 032312 (2000). + +- [43] C. M. Tesch, L. Kurtz, R. de Vivie-Riedle, Applying optimal control theory for elements of quantum computation in molecular systems, _Chem. Phys. Lett._ 343, 633-641 (2001). + +- [44] C. Kehlet, A. C. Sivertsen, M. Bjerring, T. O. Reiss, N. Khaneja, S. J. Glaser, N. C. Nielsen, Improving solid-state NMR dipolar recoupling by optimal control, _J. Am. Chem. Soc._ 126, 10202-10203 (2004) + +18 + +### Figure Captions + +Figure 1: Schematic representation of a control amplitude _uk_ ( _t_ ), consisting of _N_ steps of duration ∆ _t_ = _T/N_ . During each step _j_ , the control amplitude _uk_ ( _j_ ) is constant. The vertical arrows represent gradients _δuδ_ Φ _k_ (o _j_ ),indicatinghoweachamplitude_uk_(_j_)shouldbemodifiedinthenext iteration in order to improve the performance function Φo. + +Figure 2: For the coherence order-selective coherence transfer _S__−_ _→ I__−_ in a system consisting of two heteronuclear spins 1/2, the numerically optimized transfer efficiencies _η_ ( _T_ ) (circles) and the analytically derive time-optimal transfer efficiency (solid line) [24, 25] are shown. + +Figure 3: Example of a numerically optimized pulse shape for coherence order-selective coherence transfer _I__−_ _→ S__−_ found by the GRAPE algorithm for a given total transfer time of _T_ = 1 _/_ (2 _J_ ) (c.f. Fig. 2). Panel A shows the x amplitude (solid curve) and y amplitude (dashed curve) of the rf field irradiated at spin _I_ and panel B shows the x amplitude (solid curve) and y amplitude (dashed curve) of the rf field irradiated at spin _S_ . + +Figure 4: _η_ of the transfer from _Iz_ to 2 _IzSz_ in the presence of dipole-dipole relaxation in the spin diffusion limit as a function of the sequence duration _T_ (details, see text). The circles show the efficiencies of five numerically optimized sequences of different durations _T_ and the curve represents the theoretical limit [31]. + +Figure 5: Example of a numerically optimized pulse sequence (A) of duration _T_ = 0 _._ 408 _J__−_1 for the transfer of _Iz_ to 2 _IzSz_ in the presence of dipole-dipole relaxation (c.f. Fig. 4). _νx_ and _νy_ correspond to the x (solid curve) and y (dashed curve) rf amplitude irradiated at spin _I_ . Panel (B) shows the corresponding trajectories of the non-vanishing density operator terms during the relaxation-optimized pulse sequence. + +Figure 6: The solid curve shows the analytical solution of the minimum time _T__∗_ ( _α_ ) [39] for the creation of a propagator _Uzzz_ ( _α_ ) = exp _{−_ i _α_ 4 _I_ 1 _zI_ 2 _zI_ 3 _z}_ in a spin system consisting of three heteronuclear spins with couplings _J_ 12 = _J_ 23 = _J_ and _J_ 13 = 0. For six values of _α_ , pulse sequences with various durations _T_ were optimized using the gradient _δ_ Φ3 _/δuk_ ( _j_ ) (c.f. Eq. 28) in steps ∆ _T_ =0.05 _J__−_1 . For each _α_ , the largest value of _T_ , for which the optimized numerical value Φ3 _/_ Tr _{_ 1 _}_ was found to be smaller than 1.0 is indicated by an asterisk. The shortest value of _T_ , for which the optimized numerical value Φ3 _/_ Tr _{_ 1 _}_ was found to be 1.0 is indicated by a circle. + +19 + +Figure 7: Example of a numerically optimized pulse sequence for the creation of the propagator _Uzzz_ ( _α_ ) corresponding to the data point represented by a circle at _α_ = _π/_ 8 in Fig. 6. The _x_ and _y_ amplitudes of the rf pulse irradiated at spin _I_ 2 are shown. In comparison, the numerically optimized rf amplitudes irradiated at spins _I_ 1 and _I_ 3 are less than 0.5 % at each point during the sequence (data not shown) and can be neglected. + +20 + + +![](.figures/doi__10.1016-j.jmr.2004.11.004/10.1016-j.jmr.2004.11.004.pdf-0021-00.png) + + + +u
k
1 j N
t
0 Δ t T
+ +Figure 1 + + +![](.figures/doi__10.1016-j.jmr.2004.11.004/10.1016-j.jmr.2004.11.004.pdf-0022-00.png) + + + +1
η
0.8
0.6
0.4
0.2
0
0 0.5 1 1.5
T / J -1
+ +Figure 2 + + +![](.figures/doi__10.1016-j.jmr.2004.11.004/10.1016-j.jmr.2004.11.004.pdf-0023-00.png) + + + +15
I I A
I
ν / J νx νy
x,y
0
-15
0 0.1 0.2 0.3 0.4 0.5
T / J -1
15
B
S S S
νx,y / J νy νx
0
-15
0 0.1 0.2 0.3 0.4 0.5
T / J -1
+ +Figure 3 + + +![](.figures/doi__10.1016-j.jmr.2004.11.004/10.1016-j.jmr.2004.11.004.pdf-0024-00.png) + + + +0.4
η
0.2
0
0 0.2 0.4
-1
T/ J
+ +Figure 4 + + +![](.figures/doi__10.1016-j.jmr.2004.11.004/10.1016-j.jmr.2004.11.004.pdf-0025-00.png) + + + +6
A
ν / J
x,y
4
2 νy νx
0
-2
-4
-6
0 0.1 0.2 0.3 0.4
T / J -1
1
B
η
0.8
< > Iz
0.6
< > Ix
0.4
0.2 < > 2IySz
2IzSz
< >
0
0 0.1 0.2 0.3 0.4
T / J -1
+ +Figure 5 + + +![](.figures/doi__10.1016-j.jmr.2004.11.004/10.1016-j.jmr.2004.11.004.pdf-0026-00.png) + + + +1
-1
T/ J
0.5
0
0 π π
4 2
α
+ +Figure 6 + + +![](.figures/doi__10.1016-j.jmr.2004.11.004/10.1016-j.jmr.2004.11.004.pdf-0027-00.png) + + + +40
νrf / J
20
ν
y
0
νx
-20
-40
-60
0 0.1 0.2 0.3 0.4 0.5
T / J -1
+ +Figure 7 diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/references/rendered/10-1063-1-2198836.md b/tracks/qcs/solutions/frustration-free/challenge-113/references/rendered/10-1063-1-2198836.md new file mode 100644 index 000000000..5dcc36c10 --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/references/rendered/10-1063-1-2198836.md @@ -0,0 +1,27 @@ +--- +source: "https://doi.org/10.1063/1.2198836" +type: "doi" +canonical_id: "10.1063/1.2198836" +title: "Quantum optimal control: Hessian analysis of the control landscape." +authors: "Zhenwen Shen, Michael Hsieh, Herschel Rabitz" +year: "2006" +venue: "Journal of Chemical Physics" +doi: "10.1063/1.2198836" +full_text: no +--- + +# Quantum optimal control: Hessian analysis of the control landscape. + +**Authors:** Zhenwen Shen, Michael Hsieh, Herschel Rabitz + +**Citation:** Journal of Chemical Physics, vol. 124 20, pp. + 204106 + , 2006 + +**DOI:** [10.1063/1.2198836](https://doi.org/10.1063/1.2198836) + +## Abstract + +_(abstract unavailable)_ + +_Full text not retrieved — abstract-only entry._ diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/references/rendered/10-1103-physrevlett-112-143001.md b/tracks/qcs/solutions/frustration-free/challenge-113/references/rendered/10-1103-physrevlett-112-143001.md new file mode 100644 index 000000000..50eb5fa31 --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/references/rendered/10-1103-physrevlett-112-143001.md @@ -0,0 +1,27 @@ +--- +source: "https://doi.org/10.1103/PHYSREVLETT.112.143001" +type: "doi" +canonical_id: "10.1103/PHYSREVLETT.112.143001" +title: "Dynamic dimensionality identification for quantum control." +authors: "J. Roslund, H. Rabitz" +year: "2014" +venue: "Physical Review Letters" +doi: "10.1103/PHYSREVLETT.112.143001" +full_text: no +--- + +# Dynamic dimensionality identification for quantum control. + +**Authors:** J. Roslund, H. Rabitz + +**Citation:** Physical Review Letters, vol. 112 14, pp. + 143001 + , 2014 + +**DOI:** [10.1103/PHYSREVLETT.112.143001](https://doi.org/10.1103/PHYSREVLETT.112.143001) + +## Abstract + +_(abstract unavailable)_ + +_Full text not retrieved — abstract-only entry._ diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/references/rendered/10-1103-physrevlett-68-1500.md b/tracks/qcs/solutions/frustration-free/challenge-113/references/rendered/10-1103-physrevlett-68-1500.md new file mode 100644 index 000000000..a56641e1f --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/references/rendered/10-1103-physrevlett-68-1500.md @@ -0,0 +1,27 @@ +--- +source: "https://doi.org/10.1103/PHYSREVLETT.68.1500" +type: "doi" +canonical_id: "10.1103/PHYSREVLETT.68.1500" +title: "Teaching lasers to control molecules." +authors: "R. Judson, H. Rabitz" +year: "1992" +venue: "Physical Review Letters" +doi: "10.1103/PHYSREVLETT.68.1500" +full_text: no +--- + +# Teaching lasers to control molecules. + +**Authors:** R. Judson, H. Rabitz + +**Citation:** Physical Review Letters, vol. 68 10, pp. + 1500-1503 + , 1992 + +**DOI:** [10.1103/PHYSREVLETT.68.1500](https://doi.org/10.1103/PHYSREVLETT.68.1500) + +## Abstract + +_(abstract unavailable)_ + +_Full text not retrieved — abstract-only entry._ diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/references/rendered/10-1126-science-1093649.md b/tracks/qcs/solutions/frustration-free/challenge-113/references/rendered/10-1126-science-1093649.md new file mode 100644 index 000000000..5f9dd2e08 --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/references/rendered/10-1126-science-1093649.md @@ -0,0 +1,25 @@ +--- +source: "https://doi.org/10.1126/SCIENCE.1093649" +type: "doi" +canonical_id: "10.1126/SCIENCE.1093649" +title: "Quantum Optimally Controlled Transition Landscapes" +authors: "H. Rabitz, Michael M. Hsieh, C. Rosenthal" +year: "2004" +venue: "Science" +doi: "10.1126/SCIENCE.1093649" +full_text: no +--- + +# Quantum Optimally Controlled Transition Landscapes + +**Authors:** H. Rabitz, Michael M. Hsieh, C. Rosenthal + +**Citation:** Science, vol. 303, pp. 1998 - 2001, 2004 + +**DOI:** [10.1126/SCIENCE.1093649](https://doi.org/10.1126/SCIENCE.1093649) + +## Abstract + +_(abstract unavailable)_ + +_Full text not retrieved — abstract-only entry._ diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/references/rendered/1103.0855_chopped-random-basis-quantum-optimization.md b/tracks/qcs/solutions/frustration-free/challenge-113/references/rendered/1103.0855_chopped-random-basis-quantum-optimization.md new file mode 100644 index 000000000..484df188b --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/references/rendered/1103.0855_chopped-random-basis-quantum-optimization.md @@ -0,0 +1,458 @@ +--- +source: "https://arxiv.org/abs/1103.0855" +type: "arxiv" +canonical_id: "1103.0855" +title: "Chopped random-basis quantum optimization" +authors: "Tommaso Caneva, Tommaso Calarco, Simone Montangero" +year: "2011" +venue: "Physical Review A" +arxiv_id: "1103.0855" +doi: "10.1103/PhysRevA.84.022326" +full_text: yes +--- + +# Chopped random-basis quantum optimization + +**Authors:** Tommaso Caneva, Tommaso Calarco, Simone Montangero + +**Citation:** Physical Review A, vol. 84, pp. 022326, 2011 + +**arXiv:** [1103.0855](https://arxiv.org/abs/1103.0855) + +**DOI:** [10.1103/PhysRevA.84.022326](https://doi.org/10.1103/PhysRevA.84.022326) + +## Abstract + +In this work, we describe in detail the chopped random basis (CRAB) optimal control technique recently introduced to optimize time-dependent density matrix renormalization group simulations [P. Doria, T. Calarco, and S. Montangero, Phys. Rev. Lett. 106, 190501 (2011)]. Here, we study the efficiency of this control technique in optimizing different quantum processes and we show that in the considered cases we obtain results equivalent to those obtained via different optimal control methods while using less resources. We propose the CRAB optimization as a general and versatile optimal control technique. + +## Full Text + +# Chopped random basis quantum optimization + +Tommaso Caneva, Tommaso Calarco, and Simone Montangero + +Institut f¨ur Quanteninformationsverarbeitung, Universit¨at Ulm, D-89069 Ulm, Germany + +(Dated: November 26, 2024) + +In this work we describe in detail the Chopped RAndom Basis (CRAB) optimal control technique recently introduced to optimize t-DMRG simulations [1]. Here we study the efficiency of this control technique in optimizing different quantum processes and we show that in the considered cases we obtain results equivalent to those obtained via different optimal control methods while using less resources. We propose the CRAB optimization as a general and versatile optimal control technique. + +### PACS numbers: + +Realizing artificial, controllable quantum systems has represented one of the most promising challenge in physics for the last thirty years [2]. On one side such systems could unveil unexplored features of Nature, when employed as universal quantum simulators [3]; on the other side this technology could be exploited to realize a new generation of extremely powerful devices, like quantum computers [4]. Along with the impressive progress marked recently in the construction of tunable quantum systems [5, 6], there is a renewed and increasing interest in quantum optimal control (OC) theory, the study of the optimization techniques aimed at improving the outcome of a quantum process [2]. Indeed OC can prove to be crucial under several respects for the development of quantum devices: first, it can be generally employed to speed up a quantum process to make it less prone to decoherence or noise effects induced by the unavoidable interaction with the external environment. Second, considering a realistic experimental setup in which just few parameters are tunable or, in the most difficult situations, only partially tunable, OC can provide an answer about the optimal use of the available resources. + +Traditionally OC has been exploited in atomic and molecular physics [7–9]. More recently, with the advent of quantum information, the requirement of accurate control of quantum systems has become unavoidable to build quantum information processors [10–16]. However, the above mentioned methods often result in optimal driving fields that require a level of tunability incompatible with current experimental capabilities and in general, the calculation of the optimal fields requires an exact description of the system (either analytical or numerical). The field of application of these methods is severely limited also by the need to have access to huge amount of information about the system, e.g. computing gradients of the control fields, expectation values of observables as a function of time. Moreover, standard OC algorithms define a set of Euler-Lagrange equations that have to be solved to find the optimal control pulse [2], where the equation for the correction to the driving field is highly dependent on the constraints imposed on the system and on the figures of merit considered. This implies that considering different figures of merit and/or constraints on the system needs a redefinition of the corresponding Euler-Lagrange equations, hindering a straightforward adaptation of the + +optimization procedure to different situations. + +In this work we discuss in detail the Chopped RAndom Basis (CRAB) technique, an optimization method directed to overcome these difficulties and already introduced in [1]. The CRAB optimization is based on the definition of a truncated randomized basis of functions for the control fields that recast the problem from a functional minimization to a multi-variable function minimization that can be performed, for example, via a direct-search method. As shown in the following, the CRAB optimization flexibility allows to construct OC pulses just exploiting the available resources. Indeed, different figures of merit and constraints can be easily considered without any complications. Another appealing characteristic of CRAB is its compatibility with t- DMRG techniques: this feature indeed significantly enlarges the class of controllable systems [1], from few-body or exactly solvable to general many-body quantum systems with “moderate” degree of entanglement generated during the dynamics [17]. This is, to the best of our knowledge, the unique OC algorithm that can be applied in such vast setting. Finally, it can be straightforward applied also in a closed-loop optimization experiment, where the simulation of the system under study is replaced with the experiments itself. + +Here we analyze the CRAB optimization as a possible general OC algorithm to be used also in a standard context (solvable and/or few body systems) as a valid alternative tool with respect to standard OC methods to find optimal control fields. Indeed, recently optimization methods based on the expansion over a particular function basis have shown to be effective [18–21]. In particular, a similar approach has been proved to be mathematically convergent and consistent [22, 23]. On top of that, some theoretical analysis over control landscapes suggests that, at least in the absence of constraints, the figure of merit landscape might be smooth enough to allow for simple optimization procedures to work [24, 25]. Here, we show that indeed a convenient choice of the function basis driven by physical or geometrical arguments is enough to obtain optimal driving fields. However, in the cases where no physical intuition drives the choice of the function basis, the CRAB algorithm allows to find the optimal driving fields where a simple ansatz would fail. Moreover, a comparison between the results + +2 + +of CRAB with and without a physically driven choice of the basis, as well as previous results obtained using different optimal control algorithms (Krotov’s algorithm), show comparable performances [26]. + +The structure of the paper is the following: in Sec. I the CRAB optimization is described; in Sec. II it is applied to a paradigmatic quantum control problem, the state transformation of two coupled qubits, to show its potential. Then we compare the results obtained via CRAB optimization in more complex cases already present in literature [26, 27]: in Sec. III the method is employed to control the quantum phase transition evolution of the Lipkin-Meshkov-Glick (LMG) model; and in Sec. IV we optimize the transfer of a state along a spin chain. Finally, in Sec. V the optimization is exploited to maximize the final entanglement entropy of the final state in the LMG model; and in Sec. VI a comparison between adiabatic and optimized processes is proposed. + +## I. CRAB OPTIMIZATION + +The optimization problem we are dealing with is defined as follows: given a Hamiltonian H acting on a Hilbert space H = CN , depending on a set of timedependent driving fields Γ(t), we search for the optimal transformation to drive, in time T , an initial state |ψ0⟩∈H into a different one (target state) |ψG⟩∈H with some desired properties expressed by a cost function f (|ψG⟩) we want to minimize [55]. In addition, constraints might be present on the driving fields, e.g. to match experimental conditions: They can be expressed usually as a function of the driving fields Ci(Γ(t)). Typical scenarios and corresponding cost functions and constraints are: + +1. The goal is the preparation of a well-defined quantum state |ψG⟩ with high accuracy for which a convenient cost function is the infidelity, + + +![](.figures/arxiv__1103.0855/1103.0855.pdf-0002-06.png) + + +2. The target state is the unknown ground state of a Hamiltonian Hp. The cost function is then given by the final system energy, + + +![](.figures/arxiv__1103.0855/1103.0855.pdf-0002-08.png) + + +3. The target is some property or condition that many states can satisfy, like for example, in the production of highly entangled states. In this case the cost function is simply defined as + + +![](.figures/arxiv__1103.0855/1103.0855.pdf-0002-10.png) + + +where S(|ψ⟩) is a convenient measure of the entanglement of the state |ψ⟩. + +4. A constraint is present on the power of the driving fields, that is, the solution should minimize also the + + +![](.figures/arxiv__1103.0855/1103.0855.pdf-0002-13.png) + + +5. A limited bandwidth is allowed for the driving fields: below we show how this is already embedded in the algorithm and is not necessary to consider it as an additional explicit constraint. + +6. The initial state or the driving fields are known within a given uncertainty ǫ. In this case, the cost function can be defined as an average other all possible outcomes compatible with that uncertainty, as for example: + + +![](.figures/arxiv__1103.0855/1103.0855.pdf-0002-16.png) + + +All of the aforementioned optimization problems are then recast in the problem of solving the Sch¨odinger equation (from now on we assume ℏ = 1) + + +![](.figures/arxiv__1103.0855/1103.0855.pdf-0002-18.png) + + +with boundary condition |ψi⟩ = |ψ(0)⟩, while minimizing the cost function + + +![](.figures/arxiv__1103.0855/1103.0855.pdf-0002-20.png) + + +where the coefficients α and βi allow for a proper weighting of the different contributions (the βs play the role of Lagrange multipliers) and f is the chosen cost function. + +To perform such an optimization, the CRAB algorithm starts from an initial pulse guess Γ0 j(t) and then looks for the best correction of the form + + +![](.figures/arxiv__1103.0855/1103.0855.pdf-0002-23.png) + + +The functions gj(t) are expanded in a simple form in some function basis characterized by some parameters Ωj (Fourier space, Lagrange polynomials, etc.): gj = �kck j jk(Ωk j). The two key ingredients of the CRAB optimization are that the function space is truncated to some finite number of components Nc (k = 1, . . . , Nc) and that the corresponding basis functions are “randomized” to enhance the algorithm convergence, i.e. gˆjk→ gˆjk(Ωk j(1+r jk)) where r jkis a random number.Indeed, this last choice breaks the orthonormalization of the functions gjk,howeverasweshowinthefollowing,itallowsforan improved convergence of the algorithm as it enlarge the subspace of functions explored by the algorithm while keeping constant the number of optimization parameters. + +The optimization problem is then reformulated as the extremization of the multivariable cost function F (T,⃗cj), which can be numerically approached with a suitable + +3 + + +![](.figures/arxiv__1103.0855/1103.0855.pdf-0003-01.png) + + + +0
10
f
1
-1
10
-2
10
0 50 100 150
N
f
+ +FIG. 1: Infidelity f1 of the final state as a function of number of calls to the optimization algorithm Nf for two capacitively Josephson charge qubits with principal harmonics (dark grey [blue] line) and randomized frequencies (light grey [green] line), for the goal state |ψG1⟩andNc=2forthirtydifferent random instances. + +method, e.g., steepest descent, conjugate gradient or direct search methods [28]. Hereafter we use the last option, which is the simplest one and easily compatible with any technique employed to solve the dynamics induced by H[Γ(t)] (either exact solution of the Eq. (6) or approximate solution with time dependent DMRG [17]). This choice also gives another advantage with respect to other OC methods where gradients and functional derivatives have to be computed, increasing the complexity of the optimization procedure. + +As an example, in the following problems, we focus on the case of a single control parameter Γ(t) and we choose to work in the Fourier basis. The optimal pulse can then be written as + + +![](.figures/arxiv__1103.0855/1103.0855.pdf-0003-05.png) + + +where λ(t) is a time dependent function enforcing the boundary conditions (i.e. λ(t) →∞ for t → 0 and for t → T ). The function ΓCRAB (t) is fixed by selecting the optimization parameters⃗A,⃗B and⃗ω, with Nc the dimension of each vector. In conclusion, given a fixed total evolution time T , the cost function is clearly just a function of the control parameters, + + +![](.figures/arxiv__1103.0855/1103.0855.pdf-0003-07.png) + + +The optimization problem is reduced to the minimization of FCRAB (⃗A,⃗B,⃗ω) as a function of 3 × Nc variables. As mentioned before, however, the space of the variables can be reduced even more: although in principle the frequencies⃗ω can be considered free variables it is often convenient to keep them fixed and to perform the minimization just with respect to⃗A and⃗B. Indeed as + + +![](.figures/arxiv__1103.0855/1103.0855.pdf-0003-09.png) + + + +1-F
-1
10
-2
10
-3
10
2 4 6 8 10 12
N
c
+ +FIG. 2: Optimized infidelity f as a function of the number of optimization parameters Nc with principal harmonics (dark grey [blue], full symbols) and randomized frequencies (light grey [green], empty symbols) for different target states |ψG1⟩ (circle), |ψG2⟩(squares),|ψ G3⟩(diamonds). + +shown in our analysis this is sufficient to obtain good results. In this approach we need then a criterion to select the⃗ω’s. When we have no available information about the typical energy scales of the system under consideration, the frequencies are picked randomly around principal harmonics: ωk = 2πk(1 + rk)/T , with rk random numbers with flat distribution in the interval [−0.5, 0.5] and k = 1, ..., Nc. Viceversa when the physical details of the model are known, clearly one can exploit this information to select the relevant frequencies, as shown in the following sections. + +## II. TWO-QUBITS OPTIMIZATION + +In this section we apply the CRAB optimization to a paradigmatic problem in quantum information theory and control: we search for the optimal way to perform a state transformation of a two-qubit system, in particular we consider two capacitively coupled Josephson charge qubits, even though the following analysis can be easily adapted to different qubit implementations. The Hamiltonian of the i-th qubit is defined as [29, 30] + + +![](.figures/arxiv__1103.0855/1103.0855.pdf-0003-14.png) + + +where the σs are Pauli matrices, EC is the charging energy and EJ is the Josephson energy and i = 1, 2. For capacitive coupled qubits, the interaction Hamiltonian reads + + +![](.figures/arxiv__1103.0855/1103.0855.pdf-0003-16.png) + + +where Ecc is the charging energy associated to the Coulomb interaction between the qubits. Hereafter we set EJ /EC = −1, while the coupling will be the driving field Ecc(t)/EC = Γ(t) we use to optimize the transformation. We will consider as initial state the state with + +4 + + +![](.figures/arxiv__1103.0855/1103.0855.pdf-0004-01.png) + + + +f1 1
-2
10 LMG model: T/T =2
QSL
-4 Initial
10
Optimized
-6
10
-8
10
-10
10
20 40 60 80 100
N
+ +FIG. 3: (Color online) Infidelity as a function of the size in the LMG model. Squares represents the data before the optimization, circles the data after the optimization with CRAB. + +no excess Cooper pairs |ψ0⟩ = |00⟩, and our goal states will be three different state with different properties: the reversed separable state |ψG1⟩=|11⟩,thehomogeneous superposition state |ψG2⟩= 21 �i,j|i, j⟩,andthemaxi- mally entangled Bell state |ψG3⟩= √12(|00⟩+ |11⟩).Note that due to the fact that only the coupling is controlled, all three states are not trivial to achieve. We set the total time of the transformation to the somehow arbitrary time scale T = π/EJ and we perform a CRAB optimization using the truncated expansion of the function g(t) given in Eq. (9), with a constant initial guess for the driving field Γ0 (t) = Γ(0) = 1. We considered an additional constraint on the fluence of the control field, thus the resulting cost function is defined as + + +![](.figures/arxiv__1103.0855/1103.0855.pdf-0004-04.png) + + +where f1 and C1 are given by equations (1) and (4) respectively. Here we are interested in studying the effect of the randomness introduced in the frequencies of the expansion (9), thus we optimize both in the case of random rk and with rk = 0. To perform a fair comparison, we ran the optimization in both cases with the same maximum number of calls Nf ∼ 30.000 to the function F , which fixes the simulation complexity. Indeed, in the first case we repeated the optimization for thirty different rk random configurations (with a single Ak, Bk random starting point), while in the second case the optimization was repeated over thirty initial random Ak, Bk configurations. A typical result is shown in Fig. 1 for Nc = 2 and |ψG1⟩:itclearlyshowsthatforthecaseofrandomized ωk the optimization is highly improved (notice the logarithmic scale). A more systematic comparison is shown in Fig. 2 where the best results are plotted against the number of optimization parameters Nc for the three target states |ψGi⟩:inallcasestherandomizationofthe frequencies improves the convergences to higher fidelities up to the simulation error. In particular, in one case, the final result without randomization is very far from being + + +![](.figures/arxiv__1103.0855/1103.0855.pdf-0004-06.png) + + + +f1 1 LMG model: T/TQSL=2 1
-2
10
10-2 10-4
-6
10
-8
-4 10
10
0 4 8 12 16
-6 N=10
10 N=32
N=100
-8
10
-10
10
0 2 4 6 8 10 12 14 16
2 N
c
+ +FIG. 4: (Color online) Infidelity as a function of the number of control parameters for different sizes in the LMG model. The total evolution time is T = 2TQSL = 2π/∆. Inset: infidelity as a function of the number of parameters for a single size N = 32: comparison between data optimized using as cost function the infidelity (empty circles) and the final energy (full circles). Green squares represent the results with randomized frequencies. + +satisfactory as the final fidelity is of the order of ten percent, resulting in a very poor state transformation. On the contrary, using the randomized frequencies we were able to find optimal pulses to obtain fidelities below one percent – values that are comparable, in most cases, with experimental errors. + +## III. LIPKIN-MESHKOV-GLICK MODEL + + +![](.figures/arxiv__1103.0855/1103.0855.pdf-0004-10.png) + + +where J is the uniform spin-spin interaction (we set J = 1 in the following), N is the number of spins in the system, Γ is the transverse field and σiαarethePaulimatrices. By introducing the total spin operator Sα = iσ iα/2, Eq. (12) can be rewritten, apart from an additive constant, as H = − N1[S x2+γS y2]−ΓSz. The Hamiltonian hence commutes with S2 and does not couple states having a different parity in the number of spins pointing in the magnetic field direction: [H, S2 ] = 0 and [H, iσ iz]=0. In the isotropic case γ = 1, also the z-component of S is conserved, [H, Sz] = 0. In the thermodynamical limit the LMG model undergoes a second order quantum phase transition at Γc = 1 from a paramagnet (Γ > 1) to a ferromagnet (Γ < 1). The phase transition is characterized by mean-field critical exponents [32]. The phase transitions dramatically affects + +5 + + +![](.figures/arxiv__1103.0855/1103.0855.pdf-0005-01.png) + + + +0
10
f
1
-1
10
-2 Information transfer Initial
10 Optimized
T/T =2
QSL
-3
10
-4
10
-5
10
0 20 40 60 80
N
+ +FIG. 5: (Color online) Infidelity as a function of the size in the transfer state problem. Squares represents the data before the optimization, circles the data after the optimization with CRAB. + +the dynamical behavior of quantum systems: As discussed in more detail in Sec. VI, the gap closure at the critical point promotes dynamical excitations, preventing adiabatic evolutions whenever the adiabaticity condition T ≫ ∆−1 is not fulfilled, where T is the total evolution time and ∆the minimum spectral gap [33–42]. Following Ref. [26], we employ the CRAB optimization to drastically reduce the residual density of defects present in the system in a strongly non adiabatic dynamics, drastically reducing the time needed to connect the ground state in one phase with the ground state of the other phase with respect to adiabatic non-optimized strategies. We chose as initial state the ground state (gs) of H[Γ(t)] at Γi ≫ 1, i.e. the state in which all the spins are polarized along the positive z-axis (paramagnetic phase). As target state we chose the gs of H[Γ = 0] (ferromagnetic phase). We focused our attention on the case γ = 0, representative of the class γ < 1 (for γ = 1 the dynamics is trivial due to the symmetry of H) [43]. For this model indeed a lot of physical information is available: the gap between the ground state and the first excited state closes polynomially with the size at the critical point [32], ∆ ∼ N−1/3 . Furthermore it has been recently demonstrated that the minimum time required to obtain a perfect conversion between the initial and the final state here considered, the so called quantum speed limit, is given by TQSL = π/∆[26, 44]. In order to test the performance of CRAB, we fixed the total evolution time above this threshold, at T = 2TQSL, in a regime in which in principle it is possible to produce an arbitrarily small infidelity with optimized evolutions. + +The results of our simulations for the LMG model are summarized in Fig. 3 and Fig. 4; the data shown in the two pictures (with the only exception of the inset of Fig. 4 as explained in the following) have been produced assuming Eq. (9) as control field and the infidelity as cost function to minimize. In Fig. 3 we plotted the infidelity as a function of the size N , before the optimization for + + +![](.figures/arxiv__1103.0855/1103.0855.pdf-0005-05.png) + + + +f1 1 Information transfer: T/TQSL=2
f
1 1
-1 N=10
10-1 10 N=30
-2 N=50
10
-3
-2 10
10
-4
10
0 0.5 1 1.5 2 2.5 3
-3 2 N
10 c/N
-4
10
0 20 40 60 80 100 120 140 160 180
2 N
c
+ +FIG. 6: (Color online) Infidelity as a function of the number of control parameters for different sizes in the in the state transfer model. The total evolution time T = 2TQSL. Inset: infidelity as a function of the number of parameters divided by the size. + +a linear driving field Γ0 (t) ∝ t/T (squares), and after the optimization with CRAB (circles): for each size we have been able to produce an infidelity below 10−6 starting from an infidelity of order O(1). In particular the data have been produced by minimizing Eq. (10) with respect to⃗A and⃗B, while keeping⃗ω fixed, for a total of 2 × Nc = 16 parameters. In this case the frequencies⃗ω have been chosen by exploiting the physical information available. We chose the frequencies equal to the minimum spectral gap ω1 = 2π/T = 2π/2TQSL = ∆and we considered the main harmonics ωk = kω1 for k up to Nc. In Fig. 4 we plot the infidelity as a function of number of parameters employed to build the optimal field of Eq. (9) – adding a frequency ωk corresponds to add two parameters, Ak and Bk. First it can be noticed that 5 harmonics are sufficient to reach the best optimization result, I ∼ 10−6 ; however with only 3 harmonics the infidelity is already of order 10−4 , of the order of the required threshold for fault-tolerant quantum computation. Considering the implementation of an optimal pulse in an NMR or quantum optics experiment, the gain with respect to other OC methods providing a totally arbitrary Γopt(t) is evident. The second interesting feature is that the behavior of the infidelity in Fig. 4 is approximately independent of the size (for the smallest system considered, N = 10, finite size effects are more evident): this confirms the intuition that the most relevant energy scale for the LMG model is given by the minimum spectral gap. + +Finally, in order to verify the independence of the optimization from the knowledge of the target state, we repeated the simulations assuming as a cost function the final energy Ef (T ) of Eq. (2). In the inset of Fig. 4 we compare the infidelity of the data optimized using as cost function the infidelity itself (empty circles) and the final energy (full circles), for a specific size of the system + +6 + + +![](.figures/arxiv__1103.0855/1103.0855.pdf-0006-01.png) + + + +10
S
opt
1
N=10
N=32
N=100
1.12 T
0.1
1 10 100
T
+ +FIG. 7: (Color online) Final entanglement entropy as a function of the total evolution time T for N = 10, 32, 100 of one of two equal bipartitions of the system. The time is measured in units of J−1 . + +N = 32 and for different number of control parameters: as shown in the picture the agreement is very good. We also repeated the optimization using randomized frequencies, obtaining the same results as before. Thus, also in the case where the chosen frequencies are optimal, introducing randomness does not prevent the optimization to work. On the contrary, if one has no access to any information on the system, the randomization does not prevent to reach the same optimal result. + +## IV. STATE TRANSFER ALONG A SPIN CHAIN + +In this section we study the optimization of a model representing a possible implementation of a quantum bus. The model consists in a chain of spins coupled via uniform nearest-neighbor (n.n.) interaction; by acting with an external, parabolic magnetic field it is possible to transfer a quantum state along the chain [10, 27, 45]. In particular we follow the lines of Ref. [45, 46]. The Hamiltonian of the system is + + +![](.figures/arxiv__1103.0855/1103.0855.pdf-0006-06.png) + + +where N is the number of spins in the chain,⃗σn represents the Pauli n−th-spin operator, J is the uniform n.n. interaction (we set J = 1 in our simulations), and Bn(t) is the tunable magnetic field along the z-direction. In particular we considered a parabolic magnetic field tunable in position and strength [45], + + +![](.figures/arxiv__1103.0855/1103.0855.pdf-0006-08.png) + + +where d(t) is the position of the potential minimum along the chain, xn is the position of the n−th spin, and C(t) is the instantaneous curvature of the field. Far from the minimum, the spins are forced by the magnetic field to be + + +![](.figures/arxiv__1103.0855/1103.0855.pdf-0006-10.png) + + + +6
S
opt
5
4
3
0.947 S
max
2
1
10 100
N
+ +FIG. 8: (Color online) Entanglement entropy saturation value as a function of the size (red circles) and the function A log2(N/2 + 1) (dashed line). A fit gives A = 0.947. + +aligned along the z-axis irrespective of their mutual interaction; instead close to the minimum, the n.n. coupling prevails and can be exploited to transfer the information (i.e. the state) from one site to the next. The Hamiltonian commutes with the total magnetic field along the z-direction, [H(t),�N n=1σ nz]=0,sothatthedynamics occurs in a subspace whose dimension grows just linearly with the size N of the system. We chose to work in the subspace ⟨�N n=1σ nz⟩=1;inparticularweaimedat transferring a spin-up state from one end of the chain to the opposite end, or in other words to transform the state |ψi⟩ = |10...0⟩ into the state |ψG⟩ = |0...01⟩, with 0 (1) corresponding to the nth spin pointing in the down (up) direction along the z-axis. We employed CRAB to optimize the two control parameters, Γ1(t) = d(t) and Γ2(t) = C(t); as in the previous section, we set the total evolution time above the quantum speed limit threshold at the value T = 2TQSL, where for the latter we used the estimate made in Refs [27, 45]. The optimization has been performed by keeping⃗ω1,⃗ω2 fixed (in particular ω1k = ω2k = 2kπ/T for k = 1, ..., Nc) and minimizing the infidelity with respect to⃗A1,⃗B1,⃗A2,⃗B2, where the index 1 and 2 refer to d(t) and C(t) respectively. + +The results of our simulations for the state transfer along the chain are summarized in Fig. 5 and Fig. 6. In Fig. 5 we show the infidelity as a function of the size before the optimization (squares), for a constant C(t) and d(t) = t/T , and after the optimization with CRAB (circles): for each size considered we were able to reach an infidelity below the value 10−4 starting from an initial infidelity of order 1. In Fig. 6 we plot the infidelity as a function of the number of parameters employed in the minimization procedure; in this case, unlike for the LMG model in Fig 4, the data show a strong dependence on the size. We interpreted this behavior as a consequence of the structure of the problem. Considering the particular transfer mechanism, in which the information moves step by step from one site to the next one, we ex- + +7 + +pect the optimal pulse to be able to modulate the magnetic field around each spin; this occurs only when the spectrum of the pulse involves frequencies of the order of the inverse of the time spent on a generic site n, i.e. ω ∼ 2π/(T/N ) = Nω1. As a test, in the inset of Fig 4 we plotted the infidelity as a function of the number of parameters divided by the size; the good agreement of the rescaled data confirms our expectation. + +## V. ENTANGLEMENT ENTROPY MAXIMIZATION + +Among its various applications, OC can be exploited for entanglement production [47, 48]. Here we employ the CRAB technique in the LMG model to maximize the von Neumann entropy SL,N = −Tr(ρL,N log2 ρL,N ) associated to the reduced density matrix ρL,N of a block of L spins out of the total number N at a given final time T , which gives a measure of the entanglement present between two bipartitions of a quantum systems. As seen in Sec. III due to the symmetry of the Hamiltonian [H, S2 ] = 0, the dynamics is restricted to subspaces with fixed total angular momentum; in particular assuming as initial state the ground state of the system, we have S = N/2. The Dicke states |S = N/2, Sz ⟩ with Sz = −N/2, ..., N/2 provide a convenient basis spanning the subspace accessible through the dynamics. Indeed the entanglement entropy SL,N can be easily evaluated noticing that, since the maximum value of the total spin can be achieved only with maximum value of the spin in each bipartition, the following decomposition holds [43, 49]: + + +![](.figures/arxiv__1103.0855/1103.0855.pdf-0007-04.png) + + +where n and l correspond respectively to the number of up spins in the whole system and in the block of size L, and pl,n = L!(N − L)!n!(N − n)!/[l!(L − l)!(n − l)!(NL − n + l)!N !]. Expressing the evolved state |ψ(T )⟩ in the Dicke state basis and using the previous decomposition, it is immediate to evaluate SL,N (T ). + +In our simulations we considered a system equally bipartite, i.e. L = N/2, and we took as starting state the ground state of the LMG Hamiltonian at Γ ≫ 1, in which all the spins are polarized along the positive z direction, so that the state factorizes and the entanglement entropy vanishes, see Fig. 7. Then we performed the optimization with CRAB, modulating the field according to Eq. (9) and using as a cost function Eq. (3). The behavior of entanglement entropy after the optimization Sopt(T ) for different values of the total evolution time T is shown in Fig. 7: after a short transient of linear growth, Sopt(T ) reaches a saturation value growing with the size, as expected. It is interesting to notice that such + + +![](.figures/arxiv__1103.0855/1103.0855.pdf-0007-07.png) + + + +1
P
-1
10
-2
10
-3
10
-4
10
-20 -15 -10 -5 0
t
+ +FIG. 9: (Color online) Instantaneous excitation probabilities Pi of the i−th excited level (P1 continuous, P2 dotted, P3 dotdashed, P4 dot-dot-dashed, P25 dash-dash-dotted line) and total excitation probability Ptot =�N i=1Pi(dashedpurple line) in the LMG model with N = 50 for an evolution induced with a driving field linear in time, Γ(t) ∝−t/T , T/TQSL = 2, N = 50. The thick red dashed line signals the crossing of the critical point. The time is measured in units of J−1 . + +a behavior closely resembles the features observed in onedimensional systems after a sudden quench [50], although here we are dealing with a fully connected model [51, 52]. In Fig. 8 we plotted the saturation value reached with the optimization as a function of the size N ; comparing our data with the maximum possible value of the von Neumann entropy for a subsystem of L = N/2 spins (described by a Hilbert space of dimension N/2 + 1) Smax = log2(N/2 + 1), we obtain almost the maximal possible amount of entanglement, Sopt/Smax ∼ 0.95. + +## VI. LINEAR VS OPTIMAL DRIVING + +In this section we analyze in more detail the features characterizing the optimal dynamics induced by CRAB. In order to better understand the matter, we draw a comparison with a simpler non-optimized dynamics, in which the driving field is linearly dependent on time; in particular we focus the attention on the LMG model. An important point in the study of the dynamics of a quantum system is usually represented by the adiabatic theorem [33]. The latter establishes that a system initially prepared in its ground state can be driven by a time dependent Hamiltonian adiabatically (i.e. without introducing excitations), if the time scale of the evolution is much larger than the minimum spectral gap, i.e. T ≫ ∆−1 . In critical systems the spectral gap closes at the phase transition, so that the system gets excited from the instantaneous gs while crossing the critical point for any finite-time evolution [34]. For finite-size systems, the critical gap is not completely closed, but it presents a pronounced minimum where the excitation appears, + +8 + +as shown in Fig 9: an estimate of the excitations induced by a linear driving can be obtained by KibbleZurek theory [35, 37, 41, 43, 53]. In the picture we monitored the instantaneous total excitation probability Ptot (dashed line), and the populations of lowest levels (different style [color] lines) during the dynamics. The evolution starts at large negative times (left) and ends at the time t = 0 (right); the critical point is crossed around the time t = 11 when Γ(t) ∼ 1, see section III. Far from the critical point the system evolves adiabatically as demonstrated by the low total instantaneous excitation probability; notice that before reaching the critical point the total excitation probability coincides with the small excitation of only the first level (red continuous line). In a restricted region around the critical point (−15 < t < −10) the total excitation probability jumps to values of order 1 and does not change significantly any more. Notice that in the final part of the evolution more levels get populated, as shown by the difference between the instantaneous infidelity and the excitation probability of the first level. At the final time t = T the excitation probability is equal to the infidelity of the process, i.e. Ptot(T ) = f1. We then optimize the final infidelity, and the correspondent plot for the optimal evolution is reported in Fig 10. The scenario in this case is completely different: the system is excited at the very beginning of the dynamics and remains excited for the most part of the evolution until close to the end, when the infidelity drops abruptly to zero. It is interesting to notice that just a few levels are excited, as demonstrated by the small difference between the total excitation probability (dashed line) and the excitation probability of the first level (red continuous line). This result is in agreement with previous findings where the authors showed that this kind of dynamics can be approximated by a two-level system dynamics [26]. The abrupt jump in the probabilities around the time 20 is due to an abrupt (double) change of sign in ΓCRAB(t), reversing suddenly the order of the levels and transforming the gs in the most excited state (dash-dash-dotted [cyan] line), thus this signature is not due to a collective involvement of all the levels but simply to a reshuffling of their order. Indeed as shown in the picture for −18 < t < 0, with the subsequent change of sign the previous order is reestablished. We can then summarize the main features of the optimal evolution induced by CRAB in three points: it is strongly non adiabatic; it involves just a restricted number of levels although not necessarily close to the nominal instantaneous ground state and it is such that at the very end all populations constructively interfere to obtain the desired goal state. + +## VII. CONCLUSIONS + +In this paper we studied in detail the performance of quantum optimal control through the CRAB optimization [1]. In particular we focused the attention on three different systems and different figures of merit, in order + + +![](.figures/arxiv__1103.0855/1103.0855.pdf-0008-04.png) + + + +1
P
-1
10
-2
10
-3
10
-4
10
-20 -15 -10 -5 0
t
+ +FIG. 10: (Color online) Optimal instantaneous excitation probabilities Pi in the LMG model for T/TQSL = 2,N = 50. Codes are the same as for Fig. 9. The time is measured in units of J−1 . + +to outline the versatility of the method. We first studied the optimization of state transformations of two qubits via a controlled coupling. We have shown that the CRAB optimization is very effective already using only a few optimization parameters and the fundamental role that the randomization of the function basis plays in increasing the process convergence. We then analyzed two manybody quantum systems: the first one, the LMG model, is the prototype of many-body system with long range interaction undergoing a quantum phase transition. The success of CRAB in this context confirms the possibility of controlling complex systems typically studied in condensed matter, with relatively small resources: due to CRAB unique features, only few parameters (3 frequencies) are indeed sufficient to obtain excellent results. The second many-body quantum system studied, the transfer of information along a spin chain, is a typical problem studied in quantum information theory: the high accuracy achievable through CRAB optimization makes it a valuable tool for this kind of applications. Moreover, due to the simple structure of the optimal pulses, they may be used to extract information on the typical timescales involved on the system dynamics, as we did for the information transfer in spin chains. We stress also that the exponential dependence of the figures of merit as a function of the number of parameters found in all cases (see Figs. 2, 4, 6) suggests that in general already a moderate number of optimization parameters will be sufficient to get huge improvements in the desired processes. + +Finally, we have shown that with a simple change of the cost function, the CRAB optimization can be used to optimize the search of the unknown ground state of a Hamiltonian or to generate quantum states satisfying desired properties, i.e. high entangled states. Monitoring the instantaneous excitation probabilities generated by the optimized process, we have demonstrated the highly non adiabatic character of the dynamics and the fact + +9 + +that, despite the complexity of the system under study, just a restricted number of excited levels are really populated during the evolution. The latter fact justifies the compatibility of CRAB with DMRG-like techniques. We mention that the CRAB optimization has been applied also to open quantum systems obtaining interesting results and thus increasing its possible applications [54]. + +In conclusion, the main features of the CRAB optimization –versatility (different constraints, compatibility with approximate simulation methods and experiments), fast convergence (the final error scales exponentially with the number of optimization parameters while the num- + +ber of algorithm iterations linearly) and simplicity (small modification to existing numerical codes for quantum system simulations)– demonstrate that the CRAB optimization is not only an unique solution for many body quantum systems optimal control but it is a valid alternative also in many different settings where other optimal control tools exist [2]. + +We acknowledge discussions with R. Fazio, V. Giovannetti, M. Murphy, and G. Santoro, and support from the EU projects AQUTE, PICC, the SFB/TRR21 and the BWgrid for computational resources. + + +![](.figures/arxiv__1103.0855/1103.0855.pdf-0009-05.png) + + +- [1] P. Doria, T. Calarco, and S. Montangero Phys. Rev. Lett. 106, 190501 (2011). + +- [2] C. Brif, R. Chakrabarti, and H. Rabitz, New J. Phys. 12, 075008 (2010). + +- [3] R. Feynman, Int. J. Theor. Phys. 21, 467 (1982). + +- [4] S. Lloyd, Nature 406, 1047 (2000). + +- [5] I. Bloch, J. Dalibard, and W. Zwerger, Rev. Mod. Phys. 80, 885 (2008). + +- [6] G. Roati, C. D’Errico, L. Fallani, M. Fattori, C. Fort, M. Zaccanti, G. Modugno, M. Modugno, and M. Inguscio, Nature 453, 895 (2008). + +- [7] A. P. Peirce, M. A. Dahleh, and H. Rabitz, Phys. Rev. A 37, 4950 (1988). + +- [8] T. Calarco, U. Dorner, P. Julienne, C. J. Williams, and P. Zoller, Phys. Rev. A 70, 012306 (2004). + +- [9] N. Khaneja, T. Reiss, C. Kehlet, T. Schulte-Herbruggen, and S. G. Glaser, J. Magn. Res. 172, 296 (2005). + +- [10] S. Bose, Phys. Rev. Lett. 91, 207901 (2003). + +- [11] S. Montangero, T. Calarco, and R. Fazio, Phys. Rev. Lett. 99, 170501 (2007). + +- [12] V. F. Krotov, Global Methods in Optimal Control Theory (Marcel Dekker, New York, 1996). + +- [13] A. Carlini, A. Hosoya, T. Koike, and Y. Okudaira, Phys. Rev. Lett. 96, 060503 (2006). + +- [14] A. T. Rezakhani, W.-J. Kuo, A. Hamma, D. A. Lidar, and P. Zanardi, Phys. Rev. Lett. 103, 080502 (2009). + +- [15] F. Verstraete, M. M. Wolf, and J. I. Cirac, Nat. Phys. 5, 633 (2009). + +- [16] A. Sp¨orl, T. Schulte-Herbr¨uggen, S. J. Glaser, V. Bergholm, M. J. Storcz, J. Ferber, and F. K. Wilhelm, Phys. Rev. A 75, 012302 (2007). + +- [17] U. Schollw¨ock, Rev. Mod. Phys. 77, 259 (2005). + +- [18] O. Romero-Isart and J. J. Garcia-Ripoll, Phys. Rev. A 76, 052304 (2007). + +- [19] L. Jiang, A. M. Rey, O. Romero-Isart, J. J. Garcia-Ripoll, A. Sanpera, and M. D. Lukin, Phys. Rev. A 79, 022309 (2009). + +- [20] F. Galve, D. Zueco, G. Reuther, and S. Kohler, Eur. Phys. Journ. Special Topics 180, 237 (2010). + +- [21] C. DiFranco, M. Paternostro, and M. S. Kim, Phys. Rev. A 81, 022319 (2010). + +- [22] J.-S. Li, J. Ruths, and D. Stefanatos, J. Chem. Phys. 131, 164110 (2009). + +- [23] J. Ruths and J.-S. Li (2011), arXiv:1102.3713. + +- [24] H. Rabitz, M. Hsieh, and C. Rosenthal, Science 303, 1998 (2004). + +- [25] H. Rabitz, T. S. Ho, M. Hsieh, R. Kosut, and M. Demiralp, Phys. Rev. A 74, 012721 (2006). + +- [26] T. Caneva, T. Calarco, R. Fazio, G. E. Santoro, and S. Montangero (2010), arXiv:1011.6634. + +- [27] T. Caneva, M. Murphy, T. Calarco, R. Fazio, S. Montangero, V. Giovannetti, and G. E. Santoro, Phys. Rev. Lett. 103, 240501 (2009). + +- [28] W. H. Press, S. A. Teukolsky, W. T. Vetterling, and B. P. Flannery, Numerical recipes in C: the art of scientific computing (Cambridge University Press, 1992), 2nd ed. + +- [29] Y. Makhlin, G. Sch¨on, and A. Shnirman, Rev. Mod. Phys. 73, 357 (2001). + +- [30] G. Wendin and V. Shumeiko, in Handbook of Theoretical and Computational Nanotechnology, edited by M. Rieth and W. Schommers (American Scientific Publishers, 2006), p. Ch. 12, cond-mat/0508729. + +- [31] H. J. Lipkin, N. Meshkov, and A. J. Glick, Nucl. Phys. 62, 188 (1965). + +- [32] R. Botet and R. Jullien, Phys. Rev. B 28, 3955 (1983). + +- [33] A. Messiah, Quantum mechanics, vol. 2 (North-Holland, Amsterdam, 1962). + +- [34] S. Sachdev, Quantum Phase Transition (Cambridge University Press, 1999). + +- [35] W. H. Zurek, U. Dorner, and P. Zoller, Phys. Rev. Lett. 95, 105701 (2005). + +- [36] A. Polkovnikov and V. Gritsev, Nature Physics 4, 477 (2008). + +- [37] F. Pellegrini, S. Montangero, G. E. Santoro, and R. Fazio, Phys. Rev. B 77, 140404(R) (2008). + +- [38] S. Deng, G. Ortiz, and L. Viola, Europhys. Lett. 84, 67008 (2008). + +- [39] C. D. Grandi and A. Polkovnikov (2009), arXiv:0910.2236. + +- [40] U. Divakaran, V. Mukherjee, A. Dutta, and D. Sen (2009), arXiv:0908.4004. + +- [41] J. Dziarmaga, Adv. Phys. 59, 1063 (2010). + +- [42] A. Polkovnikov, K. Sengupta, A. Silva, and M. Vengalattore (2010), arXiv:1007.5331. + +- [43] T. Caneva, R. Fazio, and G. E. Santoro, Phys. Rev. B 78, 104426 (2008). + +- [44] V. Giovannetti, S. Lloyd, and L. Maccone, Phys. Rev. A 67, 052109 (2003). + +- [45] M. Murphy, S. Montangero, V. Giovannetti, and T. Calarco, Phys. Rev. A 82, 022318 (2010). + +- [46] V. Balachandran and J. Gong, Phys. Rev. A 77, 012303 (2008). + +10 + +- [47] X. Wang, A. Bayat, S. G. Schirmer, and S. Bose, Phys. Rev. A 81, 032312 (2010). + +- [48] F. Platzer, F. Mintert, and A. Buchleitner, Phys. Rev. Lett. 105, 020501 (2010). + +- [49] J. I. Latorre, R. Orus, E. Rico, and J. Vidal, Phys. Rev. A 71, 064101 (2005). + +- [50] P. Calabrese and J. Cardy, J. Stat. Mech. p. P04010 (2005). + +- [51] W. Dur, L. Hartmann, M. Hein, M. Lewenstein, and H.J. Briegel, Phys. Rev. Lett. 94, 097203 (2005). + +- [52] J. I. Latorre and A. Riera, J. Phys. A: Math. Theor. 42, 504002 (2009). + +- [53] C. Zener, Proc. Royal Soc. A 137, 696 (1932). + +- [54] F. Caruso, S. Montangero, T. Calarco, S.F. Huelga, and M.B. Plenio, arXiv:1103.0929. + +- [55] The generalization of the problem to the optimization of an overall unitary transformation is straightforward, averaging over the contributions of a complete set of basis of the Hilbert space H. diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/references/rendered/1402.7193_adaptive-hybrid-optimal-quantum-control-for-imprecisely-char.md b/tracks/qcs/solutions/frustration-free/challenge-113/references/rendered/1402.7193_adaptive-hybrid-optimal-quantum-control-for-imprecisely-char.md new file mode 100644 index 000000000..d5942bee3 --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/references/rendered/1402.7193_adaptive-hybrid-optimal-quantum-control-for-imprecisely-char.md @@ -0,0 +1,465 @@ +--- +source: "https://arxiv.org/abs/1402.7193" +type: "arxiv" +canonical_id: "1402.7193" +title: "Adaptive hybrid optimal quantum control for imprecisely characterized systems." +authors: "Daniel J. Egger, Frank K. Wilhelm" +year: "2014" +venue: "Physical Review Letters" +arxiv_id: "1402.7193" +doi: "10.1103/PhysRevLett.112.240503" +full_text: yes +--- + +# Adaptive hybrid optimal quantum control for imprecisely characterized systems. + +**Authors:** Daniel J. Egger, Frank K. Wilhelm + +**Citation:** Physical Review Letters, vol. 112 24, pp. + 240503 + , 2014 + +**arXiv:** [1402.7193](https://arxiv.org/abs/1402.7193) + +**DOI:** [10.1103/PhysRevLett.112.240503](https://doi.org/10.1103/PhysRevLett.112.240503) + +## Abstract + +Optimal quantum control theory carries a huge promise for quantum technology. Its experimental application, however, is often hindered by imprecise knowledge of the input variables, the quantum system's parameters. We show how to overcome this by adaptive hybrid optimal control, using a protocol named Ad-HOC. This protocol combines open- and closed-loop optimal control by first performing a gradient search towards a near-optimal control pulse and then an experimental fidelity estimation with a gradient-free method. For typical settings in solid-state quantum information processing, adaptive hybrid optimal control enhances gate fidelities by an order of magnitude, making optimal control theory applicable and useful. + +## Full Text + +# Adaptive hybrid optimal quantum control for imprecisely characterized systems + +D. J. Egger and F. K. Wilhelm + +Theoretical Physics, Universit¨at des Saarlandes, D-66123 Saarbr¨ucken, Germany + +Optimal quantum control theory carries a huge promise for quantum technology. Its experimental application, however, is often hindered by imprecise knowledge of the input variables, the quantum system’s parameters. We show how to overcome this by Adaptive Hybrid Optimal Control (AdHOC). This protocol combines open- and closed-loop optimal control by first performing a gradient search towards a near-optimal control pulse and then an experimental fidelity estimation with a gradient-free method. For typical settings in solid-state quantum information processing, Ad-HOC enhances gate fidelities by an order of magnitude, making optimal control theory applicable and useful. + +The last decades have seen the transformation of quantum theory from a mere description of nature to a tool in research and applications, prominently in quantum information processing [1], spectroscopy, sensing, and metrology [2]. Quantum control describes the science of shaping the time evolution of quantum systems in a potentially useful way [3, 4]. Control parameters typically are parameters of an external field parameterized in a technologically appropriate way, e.g., into a quantum logic gate [5], into a higher coherence in NMR [6–8], or into states important for sensing [9]. While analytically accessible only in highly specialized cases, these pulse shapes can in many cases be found using the powerful mathematical technique of Optimal Control Theory (OCT); by solving a Schr¨odinger or master equation iteratively, a pulse shape producing the desired time evolution can be found [6]. This results in complex pulses that are used in a wide variety of cases such as controlling the cooperative effects of driving and dissipation [10], to control non-integrable quantum many-body [11] and many electron [12] systems, generating matter-wave entanglement [13, 14] and quantum information devices [15–17]. These pulses are designed based on the best available knowledge of the system. This can be insufficient for two reasons i) In many cases, the underlying model cannot be solved with sufficient precision as in the case of many-body systems [12] ii) in quantum systems that are engineered or when a human-made apparatus is a key part of the setup, parameters need to be measured with precision compatible with the control task at hand [18], which is often not possible. This necessity to precisely know the underlying model strongly limits harvesting the benefits of optimal control in complex quantum systems. + +In this Letter we solve this problem with a hybrid open/closed-loop optimal control method called Adaptation by hybrid optimal control (Ad-HOC). It combines a model based gradient search and the model free NelderMead (NM) algorithm [19]. Ad-HOC is designed to overcome shortcomings of the assumed physical model [20], errors on the controls and inaccurate knowledge of the parameters. We demonstrate this approach along two tasks: We first show that pulses can be optimized using only feedback from the experiment. We then show the + +efficiency of the hybrid method for the example of two superconducting qubits [21]. + +Model-free calibration was pioneered for state transfers in chemical reactions [22] using genetic algorithms and was implemented for state transfer in optical lattices in [23]. The many successes of this method as well as improvements can be found in [24]. We in turn optimize gates, i.e. transfers of a full basis of Hilbert space over a short distance in the control landscape, a task for which we found NM to be 1.5 orders of magnitude faster. The NM algorithm has been used in tuning dynamical decoupling sequences in [25] and is part of the Chopped RAndom Basis (CRAB) optimal control scheme [11] without initial gradient search. The closed-loop part of Ad-HOC has been experimentally demonstrated on a CZ gate between two coupled superconducting qubits [26] and enabled the high gate fidelities in [27]. + +Problem setting: Delicate engineering of controlled quantum systems, in particular the need to isolate quantum systems from their environments, makes quantum control setups very complex. Such an experiment, sketched in Fig. 1 is made of the system to be controlled and the unit (the AWG) producing the control pulses. The pulses are brought from the latter to the former by a chain of electronic or optical components referred to as control transfer chain. We assume that this chain and the AWG have a sufficiently large bandwidth to manipulate the system in the required way. In this setup, four different mechanisms will degrade the fidelity of an OCT designed pulse. i) Parameter estimation: The quantum system to be controlled is modeled by a drift and control Hamiltonians Hˆ = Hˆ d + iui(t) ˆHc,iwithu(t)thecon- trol fields to be shaped. Imprecise characterization of parameters entering the drift Hˆ d and controls Hˆ c,i will degrade fidelity. ii) Improper characterization of the control transfer chain’s distortion of the pulses [14, 28]. iii) Signal calibration: in practice the control unit generates an electrical signal or laser impulse which is related to u(t). Imprecisions in this relation, e.g. a constant offset, generate errors on the controls. iv) Effects that are not taken into account in Hˆ . Among many examples are other idling components of a complex quantum system such as a quantum processor, spurious two level systems (TLS) + +2 + + +![](.figures/arxiv__1402.7193/1402.7193.pdf-0002-01.png) + + + +Chip Fabrication Errors
a) 10 0
Electronics chain with
response function H(ω) g1+∆g1 10 -1 b)
Arbitrary
waveform 10 -2
generator Qubit Bus
10 -3
10-4
-15 -10 -5 0 5 10 15
Qubit 1 - Bus coupling error ∆ g1/g1 [%]
Chip
Φ
Error 1-
+ +FIG. 1. (color online). a) Sketch of quantum control experiment. The unit generating the control pulses, typically an arbitrary waveform generator (AWG), at room temperature generates the control pulses that are sent through the control transfer chain (sketched as the chain of cylinders) to finally reach the quantum system, often cooled to less than a Kelvin. Error sources are in the parameters modeling the “chip”, the electronics and the calibration of the control signals. b) Rapid degradation of a 99.99% fidelity CZ gate between two qubits coupled via a bus assuming only an error on the g1. + +in Josephson Junctions, as well as slow non-Markovian noise. Errors in parameters and controls could be addressed in viewing the experiment as part of an ensemble and then using broadband control [29, 30]. This typically leads to cumbersome pulses since high-order Lie brackets have to be generated by the compensating pulse [31]. Instead with Ad-HOC the pulses are suited to the single yet uncertain physical system at hand, thus avoiding complexity based on a simpler task. + + +![](.figures/arxiv__1402.7193/1402.7193.pdf-0002-04.png) + + + +System System
design characterisation
Open- loop
Model based
pulse design
Measure Close d-l oop Pulse
F calibrpu lseat ion update
Experiment
+ +FIG. 2. (color online). Ad-HOC protocol: The physical system and surrounding control and measurement apparatus are designed taking control problems into consideration. For instance the AWG has to have a sufficient bandwidth for the desired control task. The system is then characterized with the best possible precision. Using the resulting parameters the control pulses are created. These are then fine tuned to the system using closed-loop OCT. The pulses are then ready to be used in the experiment and can be recalibrated at a later time to account for drift. + +Proposed method: In order to address imperfections of the model, the control loop can be closed by using the experiment as feed-back to calibrate the control pulses. An initial gradient search [32], e.g. done with the the GRadient Ascent Pulse Engineering (GRAPE) algorithm [6], of the optimal pulse, taking into account constraints on the controls as well as robustness is performed with the best reasonably achievable (to be quantified) model of the system. This gives control pulses that yield high fidelity on the model but perform sub-optimally in the real system. As long as the model is a reasonably good approximation of the physical system, these pulses will still lie close to + +the optimal point in the control landscape. A set of similar pulses (with model parameters drawn from the error bars of the initial characterization of the system) are sent to the experiment and their performance measured. The pulses are then updated and the procedure is iterated until either a target performance is reached or convergence halts. Measuring pulse performance is time consuming, thus we chose the NM algorithm [19]. It is robust and typically only evaluates 1-2 pulses per iteration. Once the calibration is done, the pulses can be used. At a latter time a few pulse calibration iterations correct for drifts in parameter values and experiments can resume. The Ad-HOC protocol is illustrated in Fig. 2. Note that the precise experimental parameters are never identified. Ad-HOC hinges on an efficient method to experimentally estimate the performance index. Here, the performance index is the process fidelity which can be estimated using Randomized Benchmarking (RB) [33–35]. Other than standard process tomography, it is significantly faster to obtain and minimizes the impact of state preparation and measurement errors. RB yields the average fidelity + + +![](.figures/arxiv__1402.7193/1402.7193.pdf-0002-08.png) + + +estimating how well the channel Λ implements the target Clifford gate Uˆ t. As shown in [26], RB is well-adapted to fast experimentation and catches a variety of practical errors of scales. + +In summary, the gradient search approaches a favorable control over a large distance based on theory and simulation whilst the closed-loop design, done on the experiment, takes into account all experimental details [22]. + +Closed loop optimization: To show that a pulse can be optimized based only on its performance index we consider random gate synthesis. Inside a black box is a TLS in which the drift and control Hamiltonians are both random Hermitian matrices. The black box input is a pulse and the output its fidelity. The target is a random unitary matrix. Figure 3 shows the mean and median error as function of iteration for 100 different realizations of the TLS (see supplementary material for details [36]). The + +3 + +convergence is consistent with an exponential decrease of the error as a function of the number of steps. It is important to recognize in Fig. 3 that while demonstrating the power of the closed-loop part of Ad-HOC it also highlights that closed-loop control alone needs a large number of steps for a rather elementary control task. Going down this convergence curve with gradient search drastically reduces the number of steps to about 50 per order of magnitude error reduction. + + +![](.figures/arxiv__1402.7193/1402.7193.pdf-0003-02.png) + + + +10 0
Mean
Median
10 -1
10 -2
10 -3
10 -4
10 -5
0 50 100 150 200
Iteration Number
Φ ]
Gate Error [1-
+ +FIG. 3. (color online). Convergence during optimization of random gates. 100 pulses were optimized each for a different realization of the random TLS. The target fidelity of 1 − 10−5 is reached rapidly as indicated by the median. The shaded area includes 68% of all runs centered around the median. + +Numerical demonstration for a realistic setting: To demonstrate hybrid optimal control in a more complicated yet realistic and genuine system, we choose to create a CZ gate between two superconducting qubits in the qubit-bus-qubit system [37, 38]. These systems are well described by the typical setup of Fig. 1. The qubit-busqubit Hamiltonian is modeled by + + +![](.figures/arxiv__1402.7193/1402.7193.pdf-0003-05.png) + + +The control δi(t) is the ith qubit-bus detuning. Their coupling strength is gi. ∆i is the qubit’s non-linearity. σˆi+and ˆa†respectively create an excitation in qubit i and the bus. This system is particularly vulnerable to errors on the controls and parameters [18]. For instance Fig. 1b shows the fidelity loss due to a small error on g1. 5% imprecision increases the error by two orders of magnitude. In fact, albeit the initial numerical optimization leading to a pulse that is first-order insensitive to errors, the second derivative is large, making this an example that is specifically unforgiving to model uncertainty and the ideal case for showing Ad-HOC’s performance. + +First, a gradient search optimizes down to machine precision the error of a CZ gate using the quantum process fidelity Φ = |Tr{Uˆ cz†Uˆ[δ1, δ2]}|2/d2. Φ measures the overlap between the ideal CZ gate Uˆ cz and the gate implemented by the controls δi. d is the dimension of the Hilbert space. GRAPE optimizes Φ by slicing time into intervals across which the controls are constant, i.e. δi(t) →{δij}. It then searches in the direction of steepest + + +![](.figures/arxiv__1402.7193/1402.7193.pdf-0003-08.png) + + + +100
(a) After ModelInitialFree GRAPEGate TunePulseUp
100
80 Average Error 1 − F
QPT Error 1 − ΦQPT
10−1
60
10−2
40 b)
10 −3
0 200 400 600 800
Iteration Number
20
0 −3 −2.5 −2 −1.5 −1 −0.5
10 10 10 10 10 10
Average Gate Error 1 - F
30 c)
20
10
0
500 1000 1500 2000 2500 3000 3500
Number of evaluations of F
Φ
1 −
Counts Error
Counts
+ +FIG. 4. (color online). Debugging procedure for parameter errors, control transfer chain errors and control DC offset errors. The error of the initial pulse was minimized using a gradient search down to machine precision. The pulses are then calibrated to a specific realization of the system. a) Histograms for 300 system realizations. The red histograms show the fidelity of the initial uncalibrated numerical pulse. The blue histograms show the improvement in average gate fidelity after running Ad-HOC. b) Gate errors as function of the calibration algorithms iteration number. c) Histogram of the number evaluations of F needed to calibrate the pulse, i.e. to take the red histograms to the blue ones. + +∂Φ/∂δij which can be computed analytically [39]. Next, the model parameters gi and ∆i, as well as the standard deviation of the transfer chain’s impulse response are promoted to random variables following Gaussian statistics with variances reflecting the precision of actual parameter estimations [40]. Additionally, random calibration offsets are introduced on the pulses. The difference between the new and old optimal controls is five times smaller than between the initial GRAPE guess and the resulting optimal control (see supplementary material [36]). We then compute the average gate fidelity F for many different realizations of the system, see the red histograms in Fig. 4a. As expected the fidelities are nowhere close to optimal ranging between 99% and 68%, clearly insufficient for quantum computing [41]. Finally each instance is reoptimized using the closed loop part of Ad-HOC, i.e., a pulse for that specific parameter set is found. For each realization, Ad-HOC increased the fidelity by more than an order of magnitude, as seen by the blue histograms in Fig. 4a. Fig. 4b shows a typical decrease in error during the closed loop optimization. As F is being maximized, Φ, computed for comparison, also increases. The corresponding number of required evaluations of F for each realization is shown in Fig. 4c. + + +![](.figures/arxiv__1402.7193/1402.7193.pdf-0003-11.png) + + +Robustness: Unlike pure open-loop techniques, the ro- + +4 + +bustness of Ad-Hoc is limited by the reliability of fidelity estimation. In the previous examples the sampling of the integral in Eq. (1) introduces noise into the fidelity estimation. Noise would also be present in an experiment but for different reasons. Here is further investigated the effect of noise on convergence. We consider the fidelity Φ which can be computed without introducing noise. A noiseless run of closed-loop optimization is compared to one with noise artificially added by a depolarizing channel [1]. Both optimizations are shown in Fig. 5, they converge at the same speed until the noisy case halts. This termination results from the increase in fidelity, averaged over several iterations, being smaller than the noise threshold ∆Φth. (see supplementary material [36]). The calibration protocol can no longer determine if the changes made to the pulses improve Φ and halts. This is illustrated in Fig. 5b showing the difference between successive iterations of fidelity of the worst pulse Φw. in the NM simplex. + + +![](.figures/arxiv__1402.7193/1402.7193.pdf-0004-02.png) + + + +0 500 1000 1500 2000
a)
10 -2
Best Pulse, no Noise
10 -3 Worst Pulse, with Noise
Best Pulse, with Noise
10 -2
10 -6 ∆Φ th .
b)
10 -10
No Averaging
10 -14
Average over 20 Iterations
0 500 1000 1500 2000
Iteration Number
Φ
Error 1 -
���
)(n w.
Φ

+1)
(n w.
Φ
���
+ + +![](.figures/arxiv__1402.7193/1402.7193.pdf-0004-03.png) + + + +![](.figures/arxiv__1402.7193/1402.7193.pdf-0004-04.png) + + +FIG. 5. (color online). a) Convergence speed of a single optimization comparing the cases when a depolarizing channel adds noise and when the optimization is noiseless. b) Difference in fidelity of the worst point in the simplex between subsequent iterations in a noisy optimization. As long as, on average, this difference is greater than the noise level, the optimization continues. + +In conclusion we have proposed Adaptive Hybrid Optimal Control (Ad-HOC), a protocol for overcoming model imperfection and incompleteness afflicting the design of control pulses for quantum systems. The protocol is efficient and can be applied to almost arbitrary quantum control experiments as it can be used with any fidelity measure that captures the essence of the desired time evolution. We showed that noise does not affect convergence speed but rather the terminal fidelity. Therefore higher fidelity can be gained by increasing the estimation precision. The closed-loop part of Ad-HOC has been demonstrated in [26]. + +We thank J.M. Martinis for insisting that optimal control will not be applied without calibration, M. Bier- + +cuk for pointing us to the NM algorithm and J. Kelly and R. Barends for pointing out the speediness of Randomized Benchmarking. This work was supported by the EU through SCALEQIT and QUAINT as well as funded by the Office of the Director of National Intelligence (ODNI), Intelligence Advanced Research Projects Activity (IARPA), through the Army Research Office. + + +![](.figures/arxiv__1402.7193/1402.7193.pdf-0004-09.png) + + +- [1] M. A. Nielsen and I. L. Chuang, Quantum Computing and Quantum Information (Cambridge University Press, 2000). + +- [2] A. N. Boto, P. Kok, D. S. Abrams, S. L. Braunstein, C. P. Williams, and J. P. Dowling, Phys. Rev. Lett. 85, 2733 (2000). + +- [3] S. Rice and M. Zhao, Optical Conrol of Molecular Dynamics (Wiley, 2000). + +- [4] P. Brumer and M. Shapiro, Principles of the Quantum Control of Molecular Processes (Wiley, 2003). + +- [5] A. Sp¨orl, T. Schulte-Herbruggen, S. J. Glaser, V. Bergholm, M. J. Storcz, J. Ferber, and F. K. Wilhelm, Phys. Rev. A 75, 012302 (2007). + +- [6] N. Khaneja, T. Reiss, C. Kehlet, T. Schulte-Herbr¨uggen, and S. J. Glaser, J. Magn. Reson. 172, 296 (2005). + +- [7] S. Glaser, T. Schulte-Herbr¨uggen, M. Sieveking, O. Schedletzky, N. Nielsen, O. Sorensen, and C. Griesinger, Science 280, 421 (1998). + +- [8] T. Boreneman, M. Hurliman, and D. Cory, J. Magn. Res. 207, 220 (2010). + +- [9] M. Lapert, G. Ferrini, and D. Sugny, Phys. Rev. A 85, 023611 (2012). + +- [10] R. Schmidt, A. Negretti, J. Ankerhold, T. Calarco, and J. T. Stockburger, Phys. Rev. Lett. 107, 130404 (2011). + +- [11] P. Doria, T. Calarco, and S. Montangero, Phys. Rev. Lett. 106, 190501 (2011). + +- [12] A. Castro, J. Werschnik, and E. K. U. Gross, Phys. Rev. Lett. 109, 153603 (2012). + +- [13] R. B¨ucker, J. Grond, S. Manz, T. Berrada, T. Betz, C. Koller, U. Hohenester, T. Schumm, A. Perrin, and J. Schmiedmayer, Nat. Phys. 7, 608 (2011). + +- [14] G. Jaeger and U. Hohenester, Phys. Rev. A 88, 035601 (2013). + +- [15] R. Nigmatullin and S. G. Schirmer, New J. Phys. 11, 105032 (2009). + +- [16] S. Montangero, T. Calarco, and R. Fazio, Phys. Rev. Lett. 99, 170501 (2007). + +- [17] F. Motzoi, J. M. Gambetta, P. Rebentrost, and F. K. Wilhelm, Phys. Rev. Lett. 103, 110501 (2009). + +- [18] D. Egger and F. K. Wilhelm, Supercond. Sci. Technol. 27, 014001 (2014). + +- [19] J. A. Nelder and R. Mead, Comput. J. 7, 308 (1967). + +- [20] M. Hellgren, E. R¨as¨anen, and E. K. U. Gross, Phys. Rev. A 88, 013414 (2013). + +- [21] J. Clarke and F. K. Wilhelm, Nature 453, 1031 (2008). + +- [22] R. S. Judson and H. Rabitz, Phys. Rev. Lett. 68, 1500 (1992). + +- [23] S. Rosi, A. Bernard, N. Fabbri, L. Fallani, C. Fort, M. Inguscio, T. Calarco, and S. Montangero, Phys. Rev. A 88, 021601 (2013). + +- [24] C. Brif, R. Chakrabarti, and H. Rabitz, New J. Phys. + +5 + +12, 075008 (2010). + +- [25] M. J. Biercuk, H. Uys, A. P. VanDevender, N. Shiga, W. M. Itano, and J. J. Bollinger, Nature 458, 996 (2009). + +- [26] J. Kelly, R. Barends, B. Campbell, Y. Chen, Z. Chen, B. Chiaro, A. Dunsworth, A. G. Fowler, I.-C. Hoi, E. Jeffrey, A. Megrant, J. Mutus, C. Neill, P. J. J. O’Malley, C. Quintana, P. Roushan, D. Sank, A. Vainsencher, J. Wenner, T. C. White, A. N. Cleland, and J. M. Martinis, Phys. Rev. Lett. 112, 240504 (2014). + +- [27] R. Barends, J. Kelly, A. Megrant, A. Veitia, D. Sank, E. Jeffrey, T. C. White, J. Mutus, A. G. Fowler, B. Campbell, Y. Chen, Z. Chen, B. Chiaro, A. Dunsworth, C. Neill, P. O’Malley, P. Roushan, A. Vainsencher, J. Wenner, A. N. Korotkov, A. N. Cleland, and J. M. Martinis, Nature 508, 500 (2014). + +- [28] F. Motzoi, J. M. Gambetta, S. T. Merkel, and F. K. Wilhelm, Phys. Rev. A 84, 022307 (2011). + +- [29] P. Owrutsky and N. Khaneja, Phys. Rev. A 86, 022315 (2012). + +- [30] B. Khani, S. T. Merkel, F. Motzoi, J. M. Gambetta, and F. K. Wilhelm, Phys. Rev. A 85, 022306 (2012). + +- [31] J.-S. Li and N. Khaneja, Phys. Rev. A 73, 030302(R) (2006). + +- [32] F. F. Floether, P. de Fouquieres, and S. G. Schirmer, New J. Phys. 14, 073023 (2012). + +- [33] E. Magesan, J. M. Gambetta, B. R. Johnson, C. A. Ryan, J. M. Chow, S. T. Merkel, M. P. da Silva, G. A. Keefe, M. B. Rothwell, T. A. Ohki, M. B. Ketchen, and M. Stef- + +fen, Phys. Rev. Lett. 109, 080505 (2012). + +- [34] J. M. Chow, J. M. Gambetta, L. Tornberg, J. Koch, L. S. Bishop, A. A. Houck, B. R. Johnson, L. Frunzio, S. M. Girvin, and R. J. Schoelkopf, Phys. Rev. Lett. 102, 090502 (2009). + +- [35] E. Magesan, J. M. Gambetta, and J. Emerson, Phys. Rev. Lett. 106, 180504 (2011). + +- [36] See Supplemental Material, which includes Refs. [42, 43]. + +- [37] M. Mariantoni, H. Wang, T. Yamamoto, M. Neeley, R. C. Bialczak, Y. Chen, M. Lenander, E. Lucero, A. D. O’Connell, D. Sank, M. Weides, J. Wenner, Y. Yin, J. Zhao, A. N. Korotkov, A. N. Cleland, and J. M. Martinis, Science 334, 61 (2011). + +- [38] A. Galiautdinov, A. N. Korotkov, and J. M. Martinis, Phys. Rev. A 85, 042321 (2012). + +- [39] S. Machnes, U. Sander, S. J. Glaser, P. de Fouqui`eres, A. Gruslys, S. Schirmer, and T. Schulte-Herbr¨uggen, Phys. Rev. A 84, 022305 (2011). + +- [40] E. Lucero, R. Barends, Y. Chen, J. Kelly, M. Mariantoni, A. Megrant, P. O’Malley, D. Sank, A. Vainsencher, J.Wenner, Y. Y. T. White, A. N. Cleland, and J. M. Martinis, Nature Physics 8, 719 (2012). + +- [41] A. G. Fowler, M. Mariantoni, J. M. Martinis, and A. N. Cleland, Phys. Rev. A 86, 032324 (2012). + +- [42] T. E. Skinner, T. O. Reiss, B. Luy, N. Khaneja, and S. J. Glaser, J. Magn. Res. 163, 8 (2003). + +- [43] J. M. Gambetta, F. Motzoi, S. T. Merkel, , and F. K. Wilhelm, Physical Review A 83, 012308 (2011). + +## Supplementary material + +In this supplementary material we give some details on the optimization of control pulses for random two level systems. We also show that a few parameter pulse can be calibrated extremely quickly. A few extra details on the qubit-bus-qubit system are given. Finally we show that the calibration does not have to be constrained to the use of randomized benchmarking. To do this we calibrate a CZ pulse using a fidelity measure tailored to the CZ gate. + +## Modeling of the Control Transfer Chain + +The transfer chain between the quantum system and the arbitrary waveform generator (AWG) can be taken into account in the optimization [14, 28]. However, improper characterization of it will degrade pulse performance. Here we describe how control transfer chains can be modeled and how output signals from the AWG relate to the control fields u(t) used in the Hamiltonian. The voltages V (t) produced by the AWG are not identical to the functions u(t). Instead they are related through a calibration curve C. Furthermore the impulse response of the transfer chain h between the AWG and the experiment can distort the pulses. Thus whilst the AWG produces V (t) the quantum system actually receives + + +![](.figures/arxiv__1402.7193/1402.7193.pdf-0005-26.png) + + +This can be taken into account using the methodology of Ref. [28] if C and h are precisely known. Practically, these functions as well as the linearity of the signal transfer stipulated in Eq. (2) are hard to verify with the needed precision. Whereas errors in parameters of the system can be addressed using broadband control [29, 30, 42], we know of no such approach for uncertain transfer functions. + +6 + +Control of Random Two Level Systems + +To investigate the performance of the model free calibration we apply it to the control of random two level systems. The Hamiltonians are + + +![](.figures/arxiv__1402.7193/1402.7193.pdf-0006-03.png) + + +The random variable Hi(x) ∈ `R` are uniformly distributed in [−0.5, 0.5]. For each realization of the drift Hˆ(d) and control Hˆ(c) a target unitary matrix Uˆ rand, chosen randomly is given. For each realization we seek a different control u(t) to optimize the fidelity Φ = |Tr{Uˆ rand†Uˆ}|2/4.Ahistogramofthenumberofrunsrequiredtoreach1 −10−5 fidelity is shown in Fig. 6. The median and mean fidelity as function of number of iterations is shown in the main text. Instances that converged poorly can be attributed to realizations that are hard to control in the given time, as the commutator between Hˆd and Hˆc turns out to be too small. To confirm this statement we plot the number of times Φ was evaluated as function of the smallest relevant matrix norm, defined as + + +![](.figures/arxiv__1402.7193/1402.7193.pdf-0006-05.png) + + +∥· ∥ is the max norm. The smaller η is, the harder the system is to control. This is reflected in Fig. 6. Overall for controllable systems the number of evaluations of Φ is low especially since the starting point for the optimization was the null control u(t) = 0 ∀ t. At very small values of η the system tends to be uncontrollable and some target gates cannot be reached. Two bad instances were removed from the data. These had very small commutator norms and would have required a much longer gate time. + + +![](.figures/arxiv__1402.7193/1402.7193.pdf-0006-07.png) + + + +500 500
450 450
400 400
350 350
300 300
20 15 10 5 0 0.1 0.2 0.3 0.4 0.5 0.6 0.7
Counts η
Φ Φ
Number Of Evaluations of Number of Evaluations of
+ +FIG. 6. Number of evaluations of the fidelity as function of the biggest relevant norm η defined in Eq. (3). In this data two points with particularly small η failed to converge and were excluded from the data. + +## Few Parameter Pulse Closed-Loop Optimization Example: Finding DRAG + +In a two level system an Xˆ gate can be implemented by applying a pulse of area π on the Ωx component of the driving field. However, when a third level is present this is no longer sufficient and DRAG pulses are needed [17, 43]. Generally, full characterization of the third level - its anharmonicity and coupling ratio, is an extra characterization step that can be avoided using Ad-HOC. The Hamiltonian for an anharmonic three level system, driven on resonance and in the frame rotating at the frequency of the drive field is + + +![](.figures/arxiv__1402.7193/1402.7193.pdf-0006-11.png) + + +7 + +∆is the anharmonicity also called qubit non-linearity. To drive the 0 ↔ 1 transition without driving 1 ↔ 2 the Ωy quadrature has to be set to the derivative of Ωx(t) scaled by −1/2∆. To show that few parameter pulses can be quickly calibrated, we assume that the anharmonicity is not known and that the initial pulse is a Gaussian with the wrong area + + +![](.figures/arxiv__1402.7193/1402.7193.pdf-0007-02.png) + + +Here A and σ are chosen at random. The calibration protocol has to find the correct values for A, σ and ∆such that the time evolution is + + +![](.figures/arxiv__1402.7193/1402.7193.pdf-0007-04.png) + + +An example of the pulses are shown in Fig. 7. Figures 7(a) and 7(c) respectively show the pulses before and after the optimization which took only 76 evaluations of the fidelity function Φ = |Tr{Xˆ† ˆ U }|2 /9. As can be seen by Fig. 7(b) the initial pulse is unable to drive any transitions since the amplitude of the pulse is too weak. The fidelity as function of iteration number for these pulses is shown in Fig. 8. Closed-loop optimization quickly finds the optimal pulse. + + +![](.figures/arxiv__1402.7193/1402.7193.pdf-0007-06.png) + + + + 2
1.4
Ω x(t) Ω y(t) 1.2 ρ (t=0)=|1><1|
1.5
1
0.8 <0|U + ρ U|0>
1
0.6 <1|U + ρ U|1>
0.5 0.4 <2|U + ρ U|2>
0.2
0 0
0 5 10 15 20 25 30 35 40 0 5 10 15 20 25 30 35 40
Time [ns] Time [ns]
(a) Initial pulse sequence (b) Evolution of |1⟩⟨1| before optimization.
40
1.4
30 Ω x(t) Ω y(t) 1.2 ρ (t=0)=|1><1|
1
20 0.8
0.6
10
0.4
0.2
0
0
0 5 10 15 20 25 30 35 40 0 5 10 15 20 25 30 35 40
Time [ns] Time [ns]
(c) Final pulse sequence (d) Evolution of |1⟩⟨1| after optimization.
Population
Drive Amplitude [MHz]
Population
Drive Amplitude [MHz]
+ +FIG. 7. Control pulse of a weakly non-linear three level system. The target time evolution is an Xˆ gate. A bad initial pulse fails to produce the desired time evolution. (a) and (b) show the initial pulse and the corresponding population evolution when starting with |1⟩⟨1|. The final pulse, found after few iterations, produces an Xˆ gate while minimizing leakage, see (c) and (d) respectively for pulse and population as function of time. + +8 + + +![](.figures/arxiv__1402.7193/1402.7193.pdf-0008-01.png) + + + + 1
Smallest Error
0.1
0.01
0.001
0.0001
5 10 15 20 25 30 35 40
Iteration Number
Φ
Error 1 -
+ +FIG. 8. Improvement of the fidelity as function of iteration number for the pulses of Fig. 7. + +## Application of Ad-HOC to the Qubit-Bus-Qubit Architecture + +Using the rotating wave approximation, the Hamiltonian of the qubit-bus-qubit architecture in the frame rotating at the frequency of the bus is + + +![](.figures/arxiv__1402.7193/1402.7193.pdf-0008-05.png) + + +The qubits are modeled as anharmonic three level systems with creation operators σˆi+and non-linearity ∆i.They are ˆ coupled to the bus with strength gi. The bus creation operator is a . The parameters gi and ∆i need to be measured resulting in parameter errors. We approximate the control transfer chain by convoluting the pulse with a Gaussian function of standard deviation σfilt. The open loop optimization is done by gradient search with the quasi-Newton method BFGS [32]. In experiments the effect of the electronics is not as simple but can be taken into account by deconvoluting the controls with a measured transfer function. Imprecisions in this measurement further introduce errors. We model this by promoting the standard deviation of our Gaussian convolution function to a random variable following a Gaussian distribution. Lastly calibration errors between the output of the AWG and the qubit frequency can cause the qubit to over or under estimate the qubit-bus resonance point [18]. Since it is this resonance point which is most crucial to the gate we model it by a DC offset of ∆ωb,i on the controls. This introduces the error term + + +![](.figures/arxiv__1402.7193/1402.7193.pdf-0008-07.png) + + +in the Hamiltonian (4), it now reads Hˆ′ = Hˆ + Hˆ err. Furthermore, in practice ∆ωb,i is not perfectly known and is therefore promoted to a random variable. In summary the parameters used in the model are given in Tab. I. The imprecision reflects current experiments [40]. + +## Control Distance Characterization + +The gradient search done on the model allows for a large distance to be covered in the control landscape. Generally the initial guess δi,init(t) is far away from the model optimal controls δi,mod(t). The closer the model is to the physical system, the less iterations are needed by the closed-loop part of Ad-HOC to reach the physical system optimal controls + +9 + +TABLE I. Parameters used in the model. The coupling strength g is given as function of the Qubit-Bus swap time by (2Tswap)−1 . The imprecision is given relative to the parameter it refers to. When promoting the parameters to random variables this imprecision serves as standard deviation. When performing the closed loop optimization, the AWG voltage calibration ∆ωb,i is chosen randomly with mean zero and standard deviation of 0.1% of the bus frequency. System realizations with unphysical parameters are discarded, e.g. σfilt cannot be smaller than zero. + +|Name|Symbol|Qubit|1 Qubit|2 unit|Imprecision| +|---|---|---|---|---|---| +|Qubit-Bus Swap Time|Tswap,i|12.6|9.2|ns|-| +|Qubit-Bus Coupling Strength|gi|40|54|MHz|4.0%| +|Qubit non-linearity|∆i|-59|-71|MHz|4.0%| +|Bus Frequency|ωb||6.1|GHz|0.1%| +|Convolution function error|σflt||1|ns|10.0%| + + + + +![](.figures/arxiv__1402.7193/1402.7193.pdf-0009-03.png) + + + +![](.figures/arxiv__1402.7193/1402.7193.pdf-0009-04.png) + + + +![](.figures/arxiv__1402.7193/1402.7193.pdf-0009-05.png) + + + +![](.figures/arxiv__1402.7193/1402.7193.pdf-0009-06.png) + + + +0.6
0.5
0.4
0.3
0.2
0.1
0
0 0.2 0.4 0.6 0.8 1 1.2 1.4
Error Factor ξ
θ
Normalized Control Distance
+ +FIG. 9. Study of the distance between the model optimal controls and physical system optimal controls. The distance θ, defined in Eq. 5 is measured as function of the size of the error, controlled by the scaling parameter ξ. This plot shows that the model and system optimal controls still lie close to one another. + +δi,sys(t). We quantify the difference in distance between the model optimal and system optimal controls by + + +![](.figures/arxiv__1402.7193/1402.7193.pdf-0009-09.png) + + +ξ is a parameter that we use to control the size of the errors. Note that here only the system optimal controls δi,sys depend on ξ. When generating the physical system realizations, according to the parameters in Tab. I, the standard deviation, i.e. the imprecision, is scaled by ξ. This allows us to control the difference between the model and system. When ξ = 0 the model and the system are identical. The results for the qubit-bus-qubit system are shown in Fig. 9. The vertical dashed line at ξ = 1 corresponds to achievable precisions in current experiments [40]. The fact that θ(ξ = 1) ≃ 0.2, indicates that the system and model optimal controls are a lot closer to each other than the model optimal controls and the GRAPE initial guess. The latter is an educated guess that brings both qubits on resonance with the bus. + +## Additional Example + +Additionally to the example in the main text, a further illustration of Ad-HOC’s performance is shown in Fig. 10. In this case only the control DC offset error ∆ωb,i was present. As can be seen in Fig. 10 the fidelity has been + +10 + +increased over a wide range of possible ∆ωb,i’s. This shows how successful Ad-HOC is in dealing with errors on the controls introduced by Hˆ err. + + +![](.figures/arxiv__1402.7193/1402.7193.pdf-0010-02.png) + + + +![](.figures/arxiv__1402.7193/1402.7193.pdf-0010-03.png) + + + +![](.figures/arxiv__1402.7193/1402.7193.pdf-0010-04.png) + + + +![](.figures/arxiv__1402.7193/1402.7193.pdf-0010-05.png) + + + +Error 1 − F Error 1 − F
0.1 0.1
-2
10 10 -2
0.05 0.05
-3
0 10 0 10 -3
-0.05 -0.05
-4
10 10 -4
-0.1 -0.1
-0.1 -0.05 0 0.05 0.1 -0.1 -0.05 0 0.05 0.1
δ 1 DC Offset Error ∆ω b,1/ ω b [%] Calibration Error ∆ω b,1/ ω b [%]
(a) Before closed-loop part of Ad-HOC (b) After closed-loop part of Ad-HOC
99.9%
99.9%
99%
[%]
b [%]
/ ω b,2 / ω b
b,2
∆ω
∆ω
DC Offset Error
2 Calibration Error
δ
+ +FIG. 10. Average error for the CZ gate as function of the DC offset error ∆ωb,i introduced by miscalibrating the AWG’s output to the qubit frequency. Ad-HOC greatly improves the fidelity of the pulse as can be seen by comparing Fig. a) and b). The central region of high fidelity does not change since the target fidelity for the calibration protocol was 99.9%. + +## Optimization with Gate-Taylored Quality Parameters + +The main text emphasizes randomized benchmarking as the fidelity measure. This fidelity is applicable when the desired gate is a Clifford gate. Here is shown that a gate specific fidelity can also be used to calibrate the pulse. We illustrate this with the optimization of a CZ gate. We define the following fidelity measure + + +![](.figures/arxiv__1402.7193/1402.7193.pdf-0010-09.png) + + +Here Uij,ij is the element of the time evolution operator mapping the state |ij⟩ onto itself. The terms |Uij,ij|2 are the qubit populations after the pulse sequence for a specific input state. The argument of these terms can be found using Ramesy measurements. A gate that is unitary and optimizes Φcz has to be a good CZ gate. An example of this fidelity as function of the iteration number is shown in Fig. 11. The initial pulse was optimized by GRAPE up to 80% fidelity using Φ = |Tr{Uˆ cz†Uˆ}|2/d2.TheremainingcalibrationwasdonewiththemodelfreepartofAd-HOC. The target fidelity was set to be Φcz = 99.9%. It can be seen that the intrinsic gate fidelity follows closely. + + +![](.figures/arxiv__1402.7193/1402.7193.pdf-0010-11.png) + + + +10 0
1 - Φ cz
10 -1 1 - Φ
10 -2
10 -3
0 2 4 6 8 10 12 14
Iteration Number x 10 3
Error
+ +FIG. 11. Model free calibration with a gate tailored fidelity. At each iteration the gate overlap fidelity is also computed and, as can be seen, optimizing Φcz also optimizes Φ. + +11 + +## Noise Threshold + +Fig. 5 of the main text shows that the closed-loop optimization stops when it can no longer tell, on average, if the changes in fidelity are due to noise or changes in the pulse parameters. The noisy fidelity used was + + +![](.figures/arxiv__1402.7193/1402.7193.pdf-0011-03.png) + + +where n is the number of times the channel depolarized out of a total of m trials. d is the Hilbert space dimension. Therefore the uniform probability of depolarization p is estimated by n/m. In order for the closed loop part of Ad-HOC to converge it must be able to distinguish if a new pulse sequence is better or worse. This sets bounds on the amount of noise tolerated. Thus when, on average, an operation on the simplex improves Φ of the worst pulse by less than a threshold difference ∆Φth. the optimization will not be able to improve the fidelity any longer because the experiment cannot distinguish fidelities sufficiently well. For this case, the threshold difference is + + +![](.figures/arxiv__1402.7193/1402.7193.pdf-0011-05.png) + + + +![](.figures/arxiv__1402.7193/1402.7193.pdf-0011-06.png) + + +The estimation of p is p¯ ± σp = n/m ± 1/12 m. Here the factor 12 comes from the uniform distribution. diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/references/rendered/1403.0035_optimal-quantum-control-using-randomized-benchmarking.md b/tracks/qcs/solutions/frustration-free/challenge-113/references/rendered/1403.0035_optimal-quantum-control-using-randomized-benchmarking.md new file mode 100644 index 000000000..bc51c85d6 --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/references/rendered/1403.0035_optimal-quantum-control-using-randomized-benchmarking.md @@ -0,0 +1,264 @@ +--- +source: "https://arxiv.org/abs/1403.0035" +type: "arxiv" +canonical_id: "1403.0035" +title: "Optimal quantum control using randomized benchmarking." +authors: "Julian Kelly, Rami Barends, Austin G. Fowler, Anthony Megrant, Evan Jeffrey, Theodore C. White, Daniel Sank, Josh Y. Mutus, Brooks Campbell, Yu Chen, Zijun Chen, Ben Chiaro, Andrew Dunsworth, I.-C. Hoi, Charles Neill, Peter J. J. O'Malley, Chris Quintana, Peter Roushan, Amit Vainsencher, John Wenner, Alexey N. Korotkov, Andrew N. Cleland, John M. Martinis" +year: "2014" +venue: "Physical Review Letters" +arxiv_id: "1403.0035" +doi: "10.1103/PhysRevLett.112.240504" +full_text: yes +--- + +# Optimal quantum control using randomized benchmarking. + +**Authors:** Julian Kelly, Rami Barends, Austin G. Fowler, Anthony Megrant, Evan Jeffrey, Theodore C. White, Daniel Sank, Josh Y. Mutus, Brooks Campbell, Yu Chen, Zijun Chen, Ben Chiaro, Andrew Dunsworth, I.-C. Hoi, Charles Neill, Peter J. J. O'Malley, Chris Quintana, Peter Roushan, Amit Vainsencher, John Wenner, Alexey N. Korotkov, Andrew N. Cleland, John M. Martinis + +**Citation:** Physical Review Letters, vol. 112 24, pp. + 240504 + , 2014 + +**arXiv:** [1403.0035](https://arxiv.org/abs/1403.0035) + +**DOI:** [10.1103/PhysRevLett.112.240504](https://doi.org/10.1103/PhysRevLett.112.240504) + +## Abstract + +We present a method for optimizing quantum control in experimental systems, using a subset of randomized benchmarking measurements to rapidly infer error. This is demonstrated to improve single- and two-qubit gates, minimize gate bleedthrough, where a gate mechanism can cause errors on subsequent gates, and identify control crosstalk in superconducting qubits. This method is able to correct parameters so that control errors no longer dominate and is suitable for automated and closed-loop optimization of experimental systems. + +## Full Text + +# **Optimal quantum control using randomized benchmarking** + +J. Kelly,1,_∗_ R. Barends,1,_∗_ B. Campbell,1 Y. Chen,1 Z. Chen,1 B. Chiaro,1 A. Dunsworth,1 A. G. Fowler,1, 2 I. Hoi,1 E. Jeffrey,1 A. Megrant,1 J. Mutus,1 C. Neill,1 P.J.J. O’Malley,1 C. Quintana,1 P. Roushan,1 D. Sank,1 A. Vainsencher,1 J. Wenner,1 T. C. White,1 A. N. Cleland,1 and John M. Martinis1 + +> 1 _Department of Physics, University of California, Santa Barbara, CA 93106, USA_ + +> 2 _Centre for Quantum Computation and Communication Technology, School of Physics, The University of Melbourne, Victoria 3010, Australia_ + +We present a method for optimizing quantum control in experimental systems, using a subset of randomized benchmarking measurements to rapidly infer error. This is demonstrated to improve single- and two-qubit gates, minimize gate bleedthrough, where a gate mechanism can cause errors on subsequent gates, and identify control crosstalk in superconducting qubits. This method is able to correct parameters to where control errors no longer dominate, and is suitable for automated and closed-loop optimization of experimental systems. + +Quantum control is inherently analog [1], so gate control parameters have to be set precisely to enable fault-tolerant quantum computing [2, 3]. With gate fidelities approaching the fault-tolerant threshold [4–6], characterizing and reducing the remnant errors become increasingly challenging. Quantum process tomography can completely characterize a gate, decomposing a process into Pauli or Kraus operators [7, 8]. However, improving gates is complicated: gate parameters map non-intuitively onto the process matrix, and state preparation and measurement errors (SPAM) can be confused with process errors. + +Here, we present a different approach to achieve high fidelity gates. We use Clifford-based randomized benchmarking (RB) [9, 10] to map gate errors onto control parameters. We show how the data can be directly fed back to optimize gates. The method is fast and scales to arbitrary precision as the sensitivity to fractional error is independent of gate fidelity. We experimentally show that it works well for several real problems. We apply it to general quantum control problems, such as gate optimization, gate bleedthrough [11] and crosstalk. In particular we demonstrate closed-loop optimization with nonorthogonal parameters in a real, noisy quantum system. As RB is platform-independent, our approach is in principle applicable to a variety of quantum systems. + +In standard RB, gate are characterized by measuring the fidelities of sequences with varying lengths. We experimentally show that optimizing the _sequence_ fidelity at fixed length improves the _gate_ fidelity. We call this approach optimized randomized benchmarking for immediate tune-up (ORBIT). + +As a testbed, we use a five qubit superconducting system based on the Xmon transmon design [12]. Here, XY control is achieved with microwave pulses and Z control with DC current pulses which modulate the qubit frequency. Qubits are coupled capacitively. Qubit frequencies lie between _f_ 10 = 4 and 6 GHz, and qubit nonlinearities ∆ _/_ 2 _π_ are around 220 MHz. This device is an ideal platform for optimizing for small errors, as we have obtained high fidelity single- and two-qubit gates (see Ref. [5]). + +We start with a simple test case where we optimize a singlequbit 90 degree rotation about the X-axis in the Bloch sphere representation (X/2 gate). This gate is implemented by a mi- + +crowave pulse with a cosine envelope (Fig. 1a inset) centered around frequency _f_ with amplitude _A_ . As the Xmon transmon qubit is a multilevel system, we must apply a quadrature correction term with coefficient _α_ to minimize leakage to higher levels [13, 14]. First, we determine the gate fidelity using RB, then measure how control errors affect fidelity of sequences. + +In Clifford-based RB, random Clifford rotations are inserted between the gate under test to ensure that it is applied to a representative set of states. The single-qubit Clifford gates are the group of rotations that preserve the two polar and four equally spaced equator states on the Bloch sphere, and are able to generate a representative set of states to remove bias from gate error. To quantify the X/2 fidelity, we first measure a reference curve by applying many sequences of ran- + + +![](.figures/arxiv__1403.0035/1403.0035.pdf-0001-11.png) + + + +(a) m
0.9 ( ) C1 Cr
m
( ) C1 X/2 Cr
0.8
Cosine envelop e
0.7 X/2
0.999
Quadrature correction
0.6
0 100 200 300
m - Number of Cliffords C1
(b) 1.0
0.5
0 0.05 0 2•10 -4 0 0.5
Δ A / A Δ f/f Δ α/α
reference
Sequence Fidelity
+ +FIG. 1. (Color online) (a) Single qubit randomized benchmarking ( _k_ = 40). The reference experiment uses sequences of random Cliffords only (black hexagons); a X/2 gate is tested by interleaving it with random Cliffords (red stars). The X/2 gate has a cosine envelope pulse shape and quadrature correction (inset). (b) Sequence fidelities versus parameters: the pulse amplitude _A_ , frequency _f_ , and coefficient _α_ ( _k_ = 20). The sequences are measured at _m_ =1 (square), 50 (circle), 100 (triangle), and 300 (diamond). + +2 + + + + + + + +dom Cliffords, appended by recovery Cliffords _Cr_ that make the ideal operation the identity. As we initialize the qubit in the ground state, the ground state population becomes the sequence fidelity. Randomization makes the sequence fidelity follow an exponential decay from the accumulation of gateaspecific errors as _Ap__m_ ref+_B_,withgateerrorscapturedin the characteristic scale _p_ ref ( see Fig. 1a). The SPAM errors affect _A_ and _B_ , but not the rate of the decay. Individual gate fidelities are evaluated by interleaving a specific gate between Cliffords, generating a decay curve with scale _p_ gate. By subtracting away the reference curve, we get the gate error _r_ gate = (1 _− p_ gate _/p_ ref )( _d −_ 1) _/d_ [15], with _d_ = 2_n_ a function of the number of qubits _n_ ; here _n_ = 1. Each point in _m_ is an average of the fidelity of _k_ different random sequences. We find the fidelity of this X/2 gate to be 0.9995 ( _k_ = 40). + +For the data in Fig. 1b, we set _m_ = 1 _,_ 50 _,_ 100 _,_ 300 and measure the sequence fidelity as we vary each of the gate parameters from their optimum. As expected, we find that longer length sequences drop more rapidly in fidelity away from the maximum, indicating an increased sensitivity to gate error with sequence length. It is this feature that opens a viable route to optimizing arbitrarily high fidelity gates: sensitivity can be maintained by doubling _m_ when the error is halved [16]. + +In the rest of this Letter, we demonstrate that ORBIT is applicable to a variety of non-trivial parameterized tune-up problems, such as entangling gate optimization with nonorthogonal parameters, improving waveform control for reducing gate bleedthrough, and minimizing crosstalk in a multi-qubit system. We emphasize that these applications are issues of prime importance to high fidelity and scaling up to larger qubit systems [17]. + +We start by applying ORBIT to a controlled-phase (CZ) entangling gate with two qubits, as described in [5, 18]. With the addition of many non-orthogonal gate parameters and larger Hilbert space, this is a significant increase in complexity compared to the X/2 gate. The CZ gate is performed by moving a qubit along an adiabatic trajectory in frequency [18] (see inset Fig. 2a) which brings the _|_ 11 _⟩_ and _|_ 02 _⟩_ avoided level crossing near resonance, generating a conditional phase. The fidelity of this gate is sensitive to the frequency trajectory, as deviations from the ideal can cause non-adiabatic leakage errors to _|_ 02 _⟩_ . The gate depends on eight parameters that follow straightforwardly from theory (see Ref. [18]). + +The direct mapping that ORBIT provides between the control parameters and gate fidelity allows for automated optimization. Here, we used the Nelder-Mead algorithm for closed-loop control. As a metric, we use sequences ( _m_ = 30) composed of gates from the two-qubit Clifford group _C_ 2, generated with an average of 1.5 CZ gates per Clifford [5]. These CZ gates dominate the error, making the reference fidelity a metric for CZ gate fidelity. Figure 2a shows the reference curves before (blue squares) and after (red circles) optimization. The average error per Clifford was reduced from _r_ = 0 _._ 0361 to _r_ = 0 _._ 0188, consistent with an improvement in gate fidelity from 0.984 to 0.993 (see Ref. [16] for interleaved + +data). Figure 2b shows the evolution of the sequence fidelity versus number of evaluations, starting with the blue square; it initially varies strongly with small parameter changes, underlining the sensitivity of this method, and eventually converges on optimal parameters (red circle). The inset of Fig. 2a shows the small change in waveform shape (5 MHz in magnitude) that improves fidelity. + +Figure 2 illustrates the advantages of this approach. First, we can identify and remedy small errors in an environment with noise; we optimize parameters to where gate errors are no longer dominated by control imperfections (see Ref. [5] for a representative error budget for a similar experiment). Second, our approach is fast: the total number of measurements is 18000 ( _k_ = 20 sequences, 900 repetitions each), which can be performed in 2 seconds with our system. Third, the optimization is model-free, which is a powerful tool as the system Hamiltonian is not always known to high precision. We be- + + +![](.figures/arxiv__1403.0035/1403.0035.pdf-0002-11.png) + + + +(a)
0.8 0.0
40 ns
F CZ=0.993
0.6 0.5
F CZ=0.984
0.4 N-M
5 MHz
o p timization
0.2
0 20 40 60 80
m - Number of Cliffords C 2
(b)
0.5
0.3
0.1
0 20 40 60 80
Nelder-Mead function evaluations
reference - before
reference - after
f (GHz)
D
Sequence Fidelity
Sequence Fidelity
+ +FIG. 2. (Color online). Optimizing the fidelity of a two-qubit CZ gate. (inset a) One qubit undergoes an effective adiabatic trajectory in frequency that brings the _|_ 11 _⟩_ and _|_ 02 _⟩_ near resonance, producing a conditional phase. (a) The sequence fidelity of the reference curve versus number of two-qubit Cliffords before (blue squares) and after (red circles) optimization ( _k_ = 50). This optimization has shifted the shoulder of the trajectory by 5 MHz (inset). (b) The change in sequence fidelity at _m_ = 30 versus Nelder-Mead function evaluations ( _k_ = 20), starting at a fidelity of 0.3 (blue square), and converging on a sequence fidelity of 0.5 (red circle). The fidelity of the CZ improved from _F_ CZ = 0.984 to _F_ CZ = 0.993, measured using interleaved RB [16]. + +3 + +lieve this will be critical to improving gates beyond current + +We have used the Nelder-Mead algorithm for automated tune-up as it is a gradient-free method, and therefore less sensitive to noise. While ORBIT was used for the “last mile” – where gate parameters are initially near the global optimum – algorithms that search for a global optimum could in principle also be used. We therefore envision possible applications in experimentally implementing model-free gates. One example is numerical optimal control [19, 20], where pulses are discretized into pixels. This technique can be used on the full Hamiltonian without approximation, can optimize for robustness against noise or experimental parameters, and can generate gates as fast as the “quantum speed limit” [21]. Typically, these gates are computed to machine precision assuming an idealized Hamiltonian. However, experimentally implementing such gates is hindered by differences between the assumed and actual system Hamiltonians. ORBIT could provide a bridge for direct, high-precision optimization of such gates to arbitrary fidelity in an experimental system, such as outlined in the Ad-HOC approach (see Ref. [22]). + +We now use ORBIT to minimize gate bleedthrough; this is a particularly harmful problem because it causes gatespecific errors on potentially many subsequent gates. Gate bleedthrough occurs when the mechanism for implementing a gate is not adequately turned off at the end. Physical mechanisms include reflections of control pulses, stray inductance in control lines, and amplifier slew rates for microwave systems. Gate bleedthrough is challenging to characterize and correct, because the entire time domain response must be optimized. Here, ORBIT has a distinct advantage by capturing all gate bleedthrough errors in the sequence fidelity. + +We reduce gate bleedthrough from a detune operation which is implemented using a square step pulse on the qubit frequency control line. The qubit is detuned for 35 ns by -0.37 GHz, acquiring a single-qubit phase _φ_ = 13 _·_ 2 _π_ . These current pulses can detune the qubit during subsequent gates if not properly leveled, as illustrated in the top inset of Fig. 3a. In the bottom inset, we measure deviations _δφ_ ( _t_ ) from the ideal acquired qubit phase before and after correction. We compensate the waveform for stray inductances and reflections in the line by applying an inverse transfer function with two poles, expressed in terms of the step response: Θ_′_ ( _t_ ) = Θ( _t_ )[1 + _i__ai_exp(_−γit_)], with Θ(_t_) the Heaviside step function, and amplitudes _ai_ and rates _γi_ . In Fig. 3a, the error of a Clifford plus step pulse is reduced from _r_ = 0 _._ 011 to _r_ = 0 _._ 003 by Nelder-Mead optimization. The sequence fidelity and evolution of the parameters _ai_ , _γi_ , and accumulated qubit phase are shown to converge in Fig. 3b and Fig. 3c. Gate bleedthrough is reduced as evidenced in the improved sequence fidelity. Additionally, the remnant qubit phase _δφ_ ( _t_ ) is markedly flatter after the detuning pulse (see the bottom inset of Fig. 3a). This demonstrates that gate bleedthrough can be minimized without the need for a full time-domain characterization. + +We also apply ORBIT to optimization problems relevant to + +large systems. One of the greatest challenges in scaling up to larger quantum systems is to maintain addressability over single qubits. A major obstacle is control crosstalk, where control pulses for one qubit affect others. In our architecture, we minimize control crosstalk between nearest neighbors by alternating the qubit frequency; next-nearest neighbors however are prone to crosstalk due to the smaller frequency dif- + + +![](.figures/arxiv__1403.0035/1403.0035.pdf-0003-07.png) + + + +(a) 1.00
ideal
non-optimized
0.75
N- M
0.0
optim ization
-0.2
0 300
time (ns)
0.50
0 100 200 300
m - Number of Cliffords C 1
(b) 0.9
0.8
0.7
0.6
(c) 0.2 Q' = Q( t )[1+ å i aie -g it ] a1
a2 f mod 2p
0.0
g1
-0.2 g2
0 20 40 60
Nelder-Mead function evaluations
reference
(rad)
Sequence Fidelity
df
Sequence Fidelity
Relative evolution
+ +FIG. 3. (Color online) Reducing gate bleedthrough. (top inset a) For rotating the state around the Z axis (Bloch sphere representation), the qubit frequency is detuned by a step pulse, with _t_ gate = 35 ns and large frequency change ∆ _f_ = _−_ 0 _._ 37 GHz which is ideally flat (solid red). Non-idealities in control and wiring bring about a non-trivial deformation of the waveform (dashed blue), causing gate bleedthrough. (a) Sequence fidelity _vs_ number of Cliffords for the reference (black squares), and interleaved with the step pulse. The error per Clifford plus step pulse is reduced using ORBIT from _r_ = 0 _._ 011 (blue squares) to _r_ = 0 _._ 003 (red circles). With this improvement, the remnant qubit phase _δφ_ ( _t_ ) after the step pulse is notably more constant (bottom inset), determined via quantum state tomography ( _k_ = 40). (b) Sequence fidelity during the Nelder-Mead algorithm ( _k_ = 30). (c) Evolution of transfer function parameters, written in terms of the step response Θ_′_ . + +4 + +ference (see Fig.˜reffig:xtalka). One of the difficulties in minimizing crosstalk lies in characterizing its effect on gate fidelity. Here, ORBIT provides an elegant solution by directly mapping the gate error onto the relevant parameters, through the comparison of isolated and simultaneous application of random single-qubit Clifford sequences [23]. + +We start by measuring the reference fidelity curve for qubit labeled _Q_ 2, shown in Fig. 4b. From the decay, we find an average error per Clifford of _rc_ = 0 _._ 001, consistent with the average single qubit gate fidelity of _F_ = 0 _._ 9995. The colored regions indicate different ranges in reference fidelity; we use this as a map to infer the gate fidelity from the sequence fidelity. Next, we monitor the sequence fidelity (with _m_ = 35) of _Q_ 2 while sending pulses for single-qubit Cliffords down the control line of _Q_ 0. We can ignore the state of _Q_ 0. We vary both the detuning _δ_ and gate length _t_ gate for pulses on the _Q_ 0 line, while keeping the product of gate length and amplitude fixed to mimic control crosstalk. The inferred gate fidelity of qubit _Q_ 2 is shown in Fig. 4c. The red regions indicate minimal added error from crosstalk ( _<_ 0 _._ 05%), while the blue regions show significant increase in error ( _>_ 1%). Clear signatures of infidelity appear when crosstalk signals are resonant with the qubit transition frequencies _f_ 10 or _f_ 21, as illustrated in blue in Fig. 4a, and fall off with detuning and gate length as expected. + +The data in Fig. 4 demonstrates that ORBIT can provide a map to visualize and optimize control crosstalk in a straightforward manner, without the need to characterize or recalibrate the pulses on qubit _Q_ 0. This technique could in principle also be used for crosstalk reduction methods that reduce spectral power at overlapping frequencies (see Ref. [24]). + +In using ORBIT, we explicitly assume that the cause of sequence decay remains unchanged: the single exponential decay, and SPAM errors captured in parameters _A_ and _B_ , must be consistent. We experimentally find that behavior remains consistent, by comparing standard RB before and after optimization (Fig. 2 and Fig. 3). In addition, leakage into the larger Hilbert space outside of the qubit subspace is assumed to penalize sequence fidelity [25]. The results show that small leakage errors to higher levels penalize fidelity for singlequbit gates (Fig. 1b), and can be minimized for two-qubit gates, (Fig. 2). We underline that the reference and interleaved RB data should be verified for self-consistency [5, 16]. As a final note, randomized benchmarking and hence ORBIT work best when a gate has sufficient fidelity to construct a decay curve. + +We have experimentally tested a new approach for optimizing quantum control using randomized benchmarking. This has been shown to be effective for improving single- and twoqubit gates, minimizing gate bleedthrough, and identifying control crosstalk. These experiments are a representative set of control problems for realizing high fidelity gates on large quantum systems. We believe ORBIT can be a generic tool for implementing closed-loop optimization in experimental systems, due to its speed, accuracy and platform independence. + +We thank F. Wilhelm and D. Egger for helpful discussions on gate optimization and the Nelder-Mead algorithm. We also + + +![](.figures/arxiv__1403.0035/1403.0035.pdf-0004-07.png) + + + +(a)
Q0
Detuning δ Q2 f10
f21
Distance
(b) 1.0
0.9995
0.8
0.999
0.998
0.6 0.995
0.990
50 100 150 200
m - Number of Cliffords C1
(c)
Fidelity
60 0.999
0.998
0.995
40
0.990
20
f21 f10
-200 -100 0 100
Detuning δ [MHz]
reference
gate1/t
Frequency
Sequence Fidelity
[ns]
gate
Pulse length t
+ +FIG. 4. (Color online) Mapping control crosstalk. (a) Energy level diagram of the qubits _Q_ 2 and _Q_ 0. Control pulses are applied to the XY lines of _Q_ 2 and _Q_ 0; the latter is swept in detuning _δ_ and gate length _t_ gate. Control crosstalk can be exacerbated by small detunings or fast gates. (b) Single qubit benchmarking of _Q_ 2. The colored regions indicate different ranges of reference fidelity. The vertical cut indicates the _m_ value used to discriminate between regions. (c) The inferred gate fidelity ( _m_ = 35, _k_ = 20) versus detuning and gate length [16]. + +thank A. N. Korotkov and A. Veitia for help in implementing Clifford-based RB. This work was supported by the Office of the Director of National Intelligence (ODNI), Intelligence Advanced Research Projects Activity (IARPA), through the Army Research Office grants W911NF-09-1-0375 and W911NF-10-1-0334. All statements of fact, opinion or conclusions contained herein are those of the authors and should not be construed as representing the official views or policies of IARPA, the ODNI, or the U.S. Government. Devices were made at the UC Santa Barbara Nanofabrication Facility, a part of the NSF-funded National Nanotechnology Infrastructure Network, and at the NanoStructures Cleanroom Facility. J. Kelly and R. Barends contributed equally to this work. + +- _∗_ These authors contributed equally to this work + +- [1] M. Shapiro and P. Brumer, Principles of the Quantum Control of Molecular Processes, by Moshe Shapiro, Paul Brumer, pp. 250. ISBN 0-471-24184-9. Wiley-VCH, February 2003. **1** + +5 + +(2003). + +- [2] M. A. Nielsen and I. L. Chuang, _Quantum computation and quantum information_ (Cambridge university press, 2010). + +- [3] A. G. Fowler, M. Mariantoni, J. M. Martinis, and A. N. Cleland, Phys. Rev. A **86** , 032324 (2012). + +- [4] J. Benhelm, G. Kirchmair, C. F. Roos, and R. Blatt, Nature Physics **4** , 463 (2008). + +- [5] R. Barends, J. Kelly, A. Megrant, A. Veitia, D. Sank, E. Jeffrey, T. C. White, J. Mutus, A. G. Fowler, B. Campbell, et al., arXiv:1402.4848 (2014). + +- [6] K. Brown, A. Wilson, Y. Colombe, C. Ospelkaus, A. Meier, E. Knill, D. Leibfried, and D. Wineland, Phys. Rev. A **84** , 030303 (2011). + +- [7] J. L. O’Brien, G. Pryde, A. Gilchrist, D. James, N. Langford, T. Ralph, and A. White, Phys. Rev. Lett. **93** , 080502 (2004). + +- [8] T. Yamamoto, M. Neeley, E. Lucero, R. Bialczak, J. Kelly, M. Lenander, M. Mariantoni, A. OConnell, D. Sank, H. Wang, et al., Phys. Rev. B **82** , 184515 (2010). + +- [9] E. Magesan, J. Gambetta, and J. Emerson, Phys. Rev. Lett. **106** , 180504 (2011). + +- [10] A. C´orcoles, J. M. Gambetta, J. M. Chow, J. A. Smolin, M. Ware, J. Strand, B. Plourde, and M. Steffen, Phys. Rev. A **87** , 030301 (2013). + +- [11] S. Gustavsson, O. Zwier, J. Bylander, F. Yan, F. Yoshihara, Y. Nakamura, T. P. Orlando, and W. D. Oliver, Phys. Rev. Lett. **110** , 040502 (2013). + +- [12] R. Barends, J. Kelly, A. Megrant, D. Sank, E. Jeffrey, Y. Chen, Y. Yin, B. Chiaro, J. Mutus, C. Neill, et al., Phys. Rev. Lett. **111** , 080502 (2013). + +- [13] F. Motzoi, J. Gambetta, P. Rebentrost, and F. K. Wilhelm, Phys. Rev. Lett. **103** , 110501 (2009). + +- [14] E. Lucero, J. Kelly, R. C. Bialczak, M. Lenander, M. Mariantoni, M. Neeley, A. OConnell, D. Sank, H. Wang, M. Weides, et al., Phys. Rev. A **82** , 042339 (2010). + +- [15] E. Magesan, J. M. Gambetta, B. Johnson, C. A. Ryan, J. M. Chow, S. T. Merkel, M. P. da Silva, G. A. Keefe, M. B. Rothwell, T. A. Ohki, et al., Phys. Rev. Lett. **109** , 080505 (2012). + +- [16] See supplementary information for discussion of ORBIT scaling with fidelity and additional data for Figures 3 and 4. + +- [17] A. G. Fowler, arXiv:1401.2466 (2014). + +- [18] J. M. Martinis and M. R. Geller, arXiv:1402.5467 (2014). + +- [19] N. Khaneja, T. Reiss, C. Kehlet, T. Schulte-Herbr¨uggen, and S. J. Glaser, Journal of Magnetic Resonance **172** , 296 (2005). + +- [20] D. Egger and F. Wilhelm, Superconductor Science and Technology **27** , 014001 (2014). + +- [21] T. Caneva, M. Murphy, T. Calarco, R. Fazio, S. Montangero, V. Giovannetti, and G. E. Santoro, Phys. Rev. Lett. **103** , 240501 (2009). + +- [22] D. Egger and F. K. Wilhelm, Submitted (2014). + +- [23] J. M. Gambetta, A. C´orcoles, S. Merkel, B. Johnson, J. A. Smolin, J. M. Chow, C. A. Ryan, C. Rigetti, S. Poletto, T. A. Ohki, et al., Phys. Rev. Lett. **109** , 240504 (2012). + +- [24] R. Schutjens, F. A. Dagga, D. Egger, and F. Wilhelm, Phys. Rev. A **88** , 052330 (2013). + +- [25] J. M. Epstein, A. W. Cross, E. Magesan, and J. M. Gambetta, arXiv:1308.2928 (2013). + +# **Supplementary Information for ‘Optimal quantum control using randomized benchmarking’** + +J. Kelly,1,_∗_ R. Barends,1,_∗_ B. Campbell,1 Y. Chen,1 Z. Chen,1 B. Chiaro,1 A. Dunsworth,1 A. G. Fowler,1, 2 I. Hoi,1 E. Jeffrey,1 A. Megrant,1 J. Mutus,1 C. Neill,1 P.J.J. O’Malley,1 C. Quintana,1 P. Roushan,1 D. Sank,1 A. Vainsencher,1 J. Wenner,1 T. C. White,1 A. N. Cleland,1 and John M. Martinis1 + +> 1 _Department of Physics, University of California, Santa Barbara, CA 93106, USA_ + +> 2 _Centre for Quantum Computation and Communication Technology, School of Physics, The University of Melbourne, Victoria 3010, Australia_ + +## **SCALING OF THE SENSITIVITY OF ORBIT WITH ERROR** + +Here, we derive the sensitivity of the sequence fidelity to gate error, and show that the sensitivity to fractional error is constant – hence ORBIT can in principle scale to arbitrarily small errors. + +The sequence fidelity decays with _m_ following _F_ = _Ap__m_ + _B_ . For the single-qubit case: _p_ = 1 _−_ 2 _r_ , with _r_ the error per Clifford. The variation in sequence fidelity with gate error is then _dF/dr_ = _−_ 2 _Am_ (1 _−_ 2 _r_ )_m−_1 . The optimal value of _m_ to operate ORBIT is at the characteristic decay of the sequence fidelity _m__′_ = _−_ 1 _/_ ln(1 _−_ 2 _r_ ) [this becomes clear when expressing the sequence fidelity as _F_ = _A_ exp( _−m/m__′_ ) + _B_ ]. To quantify the scaling of the sensitivity with gate error, we evaluate the sensitivity at _m__′_ : + + +![](.figures/arxiv__1403.0035/1403.0035.pdf-0006-07.png) + + +with the right side when expanding for small _r_ , keeping the lowest order term. + +Importantly, the sensitivity to fractional error ( _dr/r_ ) is con- + + +![](.figures/arxiv__1403.0035/1403.0035.pdf-0006-10.png) + + + + r . 0
r . 0
- N r o f C l i f f o s
/ d
i t y d
i t i v
+ +stant, + + +![](.figures/arxiv__1403.0035/1403.0035.pdf-0006-12.png) + + +This is a crucial result, as it implies that ORBIT scales to arbitrarily small error: the sensitivity is the same when improving the fidelity of a 99.0% gate to 99.9%, or a 99.99% gate to 99.999%; only the choice for _m_ is different. + +As an example, Eq. S1 is plotted in Fig. S1 for two cases: _r_ = 0 _._ 001 and 0 _._ 0005 ( _A_ = 0 _._ 5). These cases reach a maximum sensitivity at _m__′_ = 500 and _m__′_ = 1000 respectively. When halving the error the optimal _m_ and sensitivity double, as expected. We note that the sensitivity is retained for a wide range of _m_ around the optimum, therefore the choice of _m_ need not be exact. This is useful for improving gates, as we generally operate at a fixed _m_ , and changes in _r_ will affect _m__′_ . + +## **CZ GATE FIDELITY BEFORE AND AFTER NELDER-MEAD OPTIMIZATION** + +The reference and interleaved randomized benchmarking data for the CZ gate, for Fig. 2 in the main text, are shown in Fig. S2. Figure S2a is before improvement, Fig. S2b after. + +As a self-consistency check, we can calculate the expected error per Clifford using the derivation in Ref. [1]. We assume that gate errors are small and uncorrelated, such that adding errors is a good approximation. The expected error per Clifford is _r_ ref _,_ expected = 8 _._ 25 _r_ SQ +1 _._ 5 _r_ CZ with _r_ SQ the average single-qubit gate error and _r_ CZ the CZ gate error. Assuming _r_ SQ = 0 _._ 001, we compute _r_ ref _,_ expected _,_ before = 0 _._ 0318 and _r_ ref _,_ expected _,_ after = 0 _._ 0185 which are close to the experimental values of _r_ ref _,_ before = 0 _._ 0361 and _r_ ref _,_ after = 0 _._ 0188. + +## **CONTROL CROSSTALK DATA** + +The sequence fidelity data, for Fig. 4 in the main text, are shown in Fig. S3. + +- _∗_ These authors contributed equally to this work + +FIG. S1. The sensitivity of sequence fidelity to gate error (Eq. S1) for error per Clifford _r_ = 0 _._ 001 and _r_ = 0 _._ 0005. + +- [1] R. Barends, J. Kelly, A. Megrant, A. Veitia, D. Sank, E. Jeffrey, T. C. White, J. Mutus, A. G. Fowler, B. Campbell, et al., arXiv:1402.4848 (2014). + + +![](.figures/arxiv__1403.0035/1403.0035.pdf-0007-00.png) + + + +0.8 0.8
Before N-M Optmization: After N-M Optmization:
Fcz = 0.984 Fcz = 0.993
0.6 0.6
Reference Reference
0.4 0.4
CZ CZ
0.2 0.2
0 20 40 60 80 0 20 40 60 80
m - Number of Cliffords C2 m - Number of Cliffords C2
FIG. S2. Two-qubit randomized benchmarking data for Fig. 2 in the main text, showing the decay of the sequence fidelity of the reference
and when interleaved with the CZ gate ( k = 50).). (a) Before Nelder-Mead optimization. Reference error: r ref = 0 . 0361,, interleaved error:
r ref+CZ = 0 . 0511,, extracted CZ error: r CZ = 0 . 0157.. (b) After Nelder-Mead optimization. Reference error: r ref = 0 . 0188,, interleaved
error: r ref+CZ = 0 . 0254, extracted CZ error:, extracted CZ error: r CZ = 0 . 0068..
0.9
60
40 0.65
20
0.4
5.26 5.4 5.53 5.66
Frequency [GHz]
Sequence Fidelity Sequence Fidelity
[ns]
gate
Sequence Fidelity
Gate Length t
+ +FIG. S2. Two-qubit randomized benchmarking data for Fig. 2 in the main text, showing the decay of the sequence fidelity of the reference and when interleaved with the CZ gate ( _k_ = 50).). (a) Before Nelder-Mead optimization. Reference error: _r_ ref = 0 _._ 0361,, interleaved error: _r_ ref+CZ = 0 _._ 0511,, extracted CZ error: _r_ CZ = 0 _._ 0157.. (b) After Nelder-Mead optimization. Reference error: _r_ ref = 0 _._ 0188,, interleaved error: _r_ ref+CZ = 0 _._ 0254, extracted CZ error:, extracted CZ error: _r_ CZ = 0 _._ 0068.. + +FIG. S3. Sequence fidelity data for Fig. 4 in the main text at _m_ = 35 ( _k_ = 20). diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/references/rendered/1706.04454_empirical-analysis-of-the-hessian-of-over-parametrized-neura.md b/tracks/qcs/solutions/frustration-free/challenge-113/references/rendered/1706.04454_empirical-analysis-of-the-hessian-of-over-parametrized-neura.md new file mode 100644 index 000000000..ce213e47d --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/references/rendered/1706.04454_empirical-analysis-of-the-hessian-of-over-parametrized-neura.md @@ -0,0 +1,474 @@ +--- +source: "https://arxiv.org/abs/1706.04454" +type: "arxiv" +canonical_id: "1706.04454" +title: "Empirical Analysis of the Hessian of Over-Parametrized Neural Networks" +authors: "Levent Sagun, Utku Evci, V. Ugur Guney, Yann Dauphin, Leon Bottou" +year: "2017" +venue: "International Conference on Learning Representations" +arxiv_id: "1706.04454" +full_text: yes +--- + +# Empirical Analysis of the Hessian of Over-Parametrized Neural Networks + +**Authors:** Levent Sagun, Utku Evci, V. Ugur Guney, Yann Dauphin, Leon Bottou + +**Citation:** International Conference on Learning Representations, vol. abs/1706.04454, 2017 + +**arXiv:** [1706.04454](https://arxiv.org/abs/1706.04454) + +## Abstract + +We study the properties of common loss surfaces through their Hessian matrix. In particular, in the context of deep learning, we empirically show that the spectrum of the Hessian is composed of two parts: (1) the bulk centered near zero, (2) and outliers away from the bulk. We present numerical evidence and mathematical justifications to the following conjectures laid out by Sagun et al. (2016): Fixing data, increasing the number of parameters merely scales the bulk of the spectrum; fixing the dimension and changing the data (for instance adding more clusters or making the data less separable) only affects the outliers. We believe that our observations have striking implications for non-convex optimization in high dimensions. First, the flatness of such landscapes (which can be measured by the singularity of the Hessian) implies that classical notions of basins of attraction may be quite misleading. And that the discussion of wide/narrow basins may be in need of a new perspective around over-parametrization and redundancy that are able to create large connected components at the bottom of the landscape. Second, the dependence of small number of large eigenvalues to the data distribution can be linked to the spectrum of the covariance matrix of gradients of model outputs. With this in mind, we may reevaluate the connections within the data-architecture-algorithm framework of a model, hoping that it would shed light into the geometry of high-dimensional and non-convex spaces in modern applications. In particular, we present a case that links the two observations: small and large batch gradient descent appear to converge to different basins of attraction but we show that they are in fact connected through their flat region and so belong to the same basin. + +## Full Text + +Workshop track - ICLR 2018 + +# - EMPIRICAL ANALYSIS OF THE HESSIAN OF OVER PARAMETRIZED NEURAL NETWORKS + +**Levent Sagun**1 **, Utku Evci**2 **, V. U˘gur Güney**3 **, Yann Dauphin**4 **, Léon Bottou**4 + +1 Institut de Physique Théorique, Université Paris Saclay, CEA + +2 Computer Science Department, NYU + +3 Data Engineer at Facebook, New York + +4 Facebook AI Research, New York + +Email: levent.sagun@ipht.fr, ue225@nyu.edu, vug@fb.com, yann@dauphin.io, leonb@fb.com + +## ABSTRACT + +We study the properties of common loss surfaces through their Hessian matrix. In particular, in the context of deep learning, we empirically show that the spectrum of the Hessian is composed of two parts: (1) the bulk centered near zero, (2) and outliers away from the bulk. We present numerical evidence and mathematical justifications to the following conjectures laid out by Sagun et al. (2016): Fixing data, increasing the number of parameters merely scales the bulk of the spectrum; fixing the dimension and changing the data (for instance adding more clusters or making the data less separable) only affects the outliers. We believe that our observations have striking implications for non-convex optimization in high dimensions. First, the _flatness_ of such landscapes (which can be measured by the singularity of the Hessian) implies that classical notions of basins of attraction may be quite misleading. And that the discussion of wide/narrow basins may be in need of a new perspective around over-parametrization and redundancy that are able to create _large_ connected components at the bottom of the landscape. Second, the dependence of a small number of large eigenvalues to the data distribution can be linked to the spectrum of the covariance matrix of gradients of model outputs. With this in mind, we may reevaluate the connections within the data-architecturealgorithm framework of a model, hoping that it would shed light on the geometry of high-dimensional and non-convex spaces in modern applications. In particular, we present a case that links the two observations: small and large batch gradient descent appear to converge to different basins of attraction but we show that they are in fact connected through their flat region and so belong to the same basin. + +## 1 INTRODUCTION + +In this paper, we study the geometry of the loss surface of supervised learning problems through the lens of their second order properties. To introduce the framework, suppose we are given data in the form of input-label pairs, _D_ = _{_ ( _x__i_ _, y__i_ ) _}__N_ _i_ =1where_x ∈_R_d_and_y∈_R that are sampled i.i.d.from a possibly unknown distribution _ν_ , a model that is parametrized by _w ∈_ R_M_ ; so that the number of examples is _N_ and the number of parameters of the system is _M_ . Suppose also that there is a predictor _f_ ( _w, x_ ). The supervised learning process aims to solve for _w_ so that _f_ ( _w, x_ ) _≈ y_ . To make the ‘ _≈_ ’ precise, we use a non-negative loss function that measures how close the predictor is to the true label, _ℓ_ ( _f_ ( _w, x_ ) _, y_ ). We wish to find a parameter _w__∗_ such that _w__∗_ = arg min _L_ ( _w_ ) where, + + +![](.figures/arxiv__1706.04454/1706.04454.pdf-0001-13.png) + + +In particular, one is curious about the relationship between _L_ ( _w_ ) and _L_ˆ ( _w_ ) := � _ℓd_ ( _ν_ ). By the law of large numbers, at a given point _w_ , _Lw → L_ˆ _w_ almost surely as _N →∞_ for fixed _M_ . However in modern applications, especially in deep learning, the number of parameters _M_ is comparable to the number of examples _N_ ( _if not much larger_ ). And the behaviour of the two quantities may be drastically different (for a recent analysis on provable estimates see (Mei et al., 2016)). + +1 + +Workshop track - ICLR 2018 + +A classical algorithm to find _w__∗_ is gradient descent (GD), in which the optimization process is carried out using the gradient of _L_ . A new parameter is found iteratively by taking a step in the direction of the negative gradient whose size is scaled with a constant step size _η_ that is chosen from line-search minimization. Two problems emerge: (1) Gradient computation can be expensive, (2) Line-search can be expensive. More involved algorithms, such as Newton-type methods, make use of second-order information (Nocedal & Wright, 2006). Under sufficient regularity conditions we may observe: _L_ ( _w_ + ∆ _w_ ) _≈L_ ( _w_ ) + ∆ _w∇L_ ( _w_ ) + ∆ _w__T_ _∇_2 _L_ ( _w_ )∆ _w_ . A third problem emerges beyond an even more expansive computational cost of the Hessian: (3) Most methods require the Hessian to be non-degenerate to a certain extent. + +When the gradients are computationally expensive, one can alternatively use its stochastic version (SGD) that replaces the above gradient with the gradient of averages of losses over _subsets_ (such a subset will be called the _mini-batch_ ) of _D_ (see (Bottou, 2010) for a classical reference). The benefit of SGD on real-life time limits is obvious, and GD may be impractical for practical purposes in many problems. In any case, the stochastic gradient can be seen as an approximation to the true gradient, and hence it is important to understand how the two directions are related to one another. Therefore, the discussion around the geometry of the loss surface can be enlightening in the comparison of the two algorithms: Does SGD locate solutions of a different nature than GD? Do they follow different paths? If so, which one is better in terms of generalization performance? + +For the second problem of expensive line-search, there are two classical solutions: using a small, constant step size, or scheduling the step size according to a certain rule. In practice, in the context of deep learning, the values for both approaches are determined heuristically, by trial and error. More involved optimal step size choices involve some kind of second-order information that can be obtained from the Hessian of the loss function (Schaul et al., 2013). From a computational point of view, obtaining the Hessian is extremely expensive, however obtaining some of its largest and smallest eigenvalues and eigenvectors are not that expensive. Is it enough to know only those eigenvalues and eigenvectors that are large in magnitude? How do they change through training? Would such a method work in SGD as well as it would on GD? + +For the third problem, let’s look at the Hessian a little closer. A critical point is defined by _w_ such that _||∇L_ ( _w_ ) _||_ = 0 and the nature of it can be determined by looking at the _signs_ of its Hessian matrix. If all eigenvalues are positive the point is called a local minimum, if _r_ of them are negative and the rest are positive, then it is called a saddle point with index _r_ . At the critical point, the eigenvectors indicate the directions in which the value of the function locally changes. Moreover, the changes are proportional to the corresponding -signed- eigenvalue. Under sufficient regularity conditions, it is rather straightforward to show that gradient-based methods converge to points where the gradient is zero. Recently Lee et al. (2016) showed that they indeed converge to minimizers. However, a significant and untested assumption to establish these convergence results is that the Hessian of the loss is non-degenerate. A relaxation of the above convergence to the case of non-isolated critical points can be found in (Panageas & Piliouras, 2016). What about the critical points of machine learning loss functions? Do they satisfy the non-degeneracy assumptions? If they don’t, can we still apply the results of provable theorems to gain intuition? + +### 1.1 A HISTORICAL OVERVIEW + +One of the first instances of the comparison of GD and SGD in the context of neural networks dates back to the late eighties and early nineties. Bottou (1991) points out that large eigenvalues of the Hessian of the loss can create the illusion of the existence of local minima and GD can get stuck there, it further claims that the help of the inherent noise in SGD may help to get out of this obstacle. The origin of this observation is due to Bourrely (1989), as well as numerical justifications. However, given the computational limits of the time, these experiments relied on low-dimensional neural networks with few hidden units. The picture may be drastically different in higher dimensions. In fact, provable results in statistical physics tell us that, in certain real-valued non-convex functions, the local minima concentrate at an error level near that of the global minima. A theoretical review on this can be found in (Auffinger et al., 2013), while Sagun et al. (2014) and Ballard et al. (2017) provide an experimental simulation as well as a numerical study for neural networks. They notably find that high error local minima traps do not appear when the model is _over-parametrized_ . + +2 + +Workshop track - ICLR 2018 + +These concentration results can help explain why we find that the solutions attained by different optimizers like GD and SGD often have comparable training accuracies. However, while these methods find comparable solutions in terms of training error there is no guarantee they generalize equally. A recent work in this direction compares the generalization performance of _small batch_ and _large batch_ methods (Keskar et al., 2016). They demonstrate that the _large batch_ methods always generalize a little bit worse even when they have similar training accuracies. The paper further makes the observation that the basins found by _small batch_ methods are wider, thereby contributing to the claim that wide basins, as opposed to narrow ones, generalize better. + +Attempts on deliberately targeting wider basins also date back quite a bit. Non-convex optimization in the nineties seem to have aimed to find the global minimum through modifying the original energy landscape. Piela et al. (1989) aims to find the global minimum through the diffusion equation. Modern attempts on the same idea can be found in Mobahi & Fisher III (2015b), Mobahi & Fisher III (2015a), Mobahi (2016) and Hazan et al. (2016). Convolving the original landscape is another similar idea to transform the energy landscape in such a way that the sharp minima disappear and the wider ones are emphasized. Somewhat earlier accounts on this can be found in Pardalos et al. (1994) which aims to find the global minimum through spatial averaging. In the context of deep learning, early attempts have been pointed out in Bengio et al. (2009), which is later followed by a proposed algorithm in Gulcehre et al. (2016). The key difficulty in the approach of modifying the energy landscape is the intractability of the integral transformation that is required to actually modify the landscape. To alleviate this difficulty, Baldassi et al. (2016) propose a _K_ -replica approach which performs optimization on _K_ -copies of the same model and letting them interact in such a way that their _average_ is actually moving on the convoluted surface. + +The final part of the historical account is devoted to the observation of flatness of the landscape in neural networks and its consequences through the lens of the Hessian. In the early nineties, Hochreiter & Schmidhuber (1997) remarks that there are parts of the landscape in which the weights can be perturbed without significantly changing the loss value. Such regions at the bottom of the landscape are called _the flat minima_ , which can be considered as another way of saying a _very wide minima_ . It is further noted that such minima have better generalization properties and a new loss function that makes use of the Hessian of the loss function has been proposed that targets the _flat minima_ . The computational complexity issues have been attempted to be resolved using the _R_ -operator of Pearlmutter (1994). However, the new loss requires all the entries of the Hessian, and even with the _R_ -operator, it is unimaginably slow for today’s large networks. More recently, an _exact_ numerical calculation of the Hessian has been carried out by Sagun et al. (2016). It turns out that the Hessian can have near zero eigenvalues even at a given random initial point, and that the spectrum of it is composed of two parts: (1) the bulk, and (2) the outliers. The bulk is mostly full of zero eigenvalues with a fast decaying tail, and the outliers are only a handful which appears to depend on the data. This implies that, locally, most directions in the weight space are flat, and leads to little or no change in the loss value, except for the directions of eigenvectors that correspond to the large eigenvalues of the Hessian. Based on exactly this observation combined with the _K_ -replica method of the previous paragraph, a recent work produced promising practical results (Chaudhari et al., 2016). + +### 1.2 OVERVIEW OF RESULTS + +In this work, we present a phenomenological study in which we provide various observations on the local geometry at the bottom of the landscape and discuss their implications on certain features of solution spaces: Connectedness of basins found by large and small batch methods. + +1. **Flatness at the bottom of the landscape:** At the bottom, most of the eigenvalues in the spectrum of the Hessian are near zero, except for a small number of relatively larger ones. + +2. **A possible explanation through over-parametrization:** The decomposition of the Hessian as a sum of two matrices, where the first one is the sample covariance matrix of the gradients of model outputs and the second one is the Hessian of the function that describes the model outputs. We argue that the second term can be ignored as training progress which leaves us with the covariance term which leads to degeneracy in the Hessian when there are more parameters than samples. + +3. **Dependence of eigenvalues to model-data-algorithm:** We empirically examine the spectrum to uncover intricate dependencies within the data-architecture-algorithm triangle: (1) + +3 + +Workshop track - ICLR 2018 + +more complex data produce more outliers, (2) increasing the network size doesn’t affect the density of large eigenvalues, (3) large batch methods produce the same number of outliers that are larger in magnitude, and finally (4) there are negative eigenvalues even after the training process appears to show no further progress but their magnitude is much smaller than the outliers. + +4. **A new interpretation of basins:** It has been a common practice to discuss certain features of basins found by various algorithms. Some recent examples, such as Keskar et al. (2016); Chaudhari et al. (2016); Jastrz˛ebski et al. (2017), appear to draw the big-picture of isolated basins at the bottom of the landscape. One tool that is commonly used to demonstrate such claims is to evaluate the loss on a line that connects two solutions found by different methods. First of all, the notion of the basin itself can be misleading given the negative eigenvalues pointed out in the previous item. Moreover, we claim that this idea of isolated basins may be misleading based on the above observations of the dramatic level of flatness of the local geometry. In particular, we show that two solutions with different qualities can be shown to be in the same basin even with a loss evaluation on a straight line connecting the two. + +**Remark 1:** Throughout this paper we use the notions of data complexity and over-parametrization vaguely. The complexity of data can be defined in various ways and further research is required to determine the precise notion that is required that would link complexity to the spectrum. Overparametrization, similarly, can be defined in various ways: _M >> N_ , _M →∞_ for fixed _N_ , or _M/N → c_ for a certain constant, etc... However, more realistic notions of both complexity of the data and the over-parametrization should take the architecture into account, and detailed treatment of this should follow another work. + +**Remark 2:** The notion of the basin, also, can be defined precisely. However, it is unclear whether any algorithm used in practice actually locates the bottom of a basin described in classical ways. For instance, the norm of the gradients are small but not at the machine precision, and the eigenvalues of the Hessian still has a negative part even after SGD continues a long while without a meaningful decrease in the loss value. This is presumably the fault of the algorithm itself, however, it requires a further study, and hence such notions of sharp vs. wide minima in various recent work should be taken with a grain of salt. + +**Remark 3:** Even when one has a way to measure the width of a ‘basin’, such ways of measuring the approximate width are all relative. In a recent study, Dinh et al. (2017) shows how ‘sharp minima’ can still generalize with proper modifications to the loss function. We note that it takes a non-linear transformation to deform relative widths of basins. And, in this work, we focus on relative values as opposed to absolute values to get a consistent comparison across different setups. + +## 2 SOME PROPERTIES OF THE HESSIAN + +### 2.1 A FIRST LOOK AT THE SPECTRUM + +We begin by an exact calculation of the spectrum of the Hessian at a random initial point, and at the end of training. Note that the plots are arranged in a way that they show the eigenvalues in the _y_ -axis, and indicate the order of the eigenvalue in the _x_ -axis. This choice is necessary to indicate the scale of the degeneracy while still showing all the eigenvalues in the same plot. Figure 1 shows the full spectrum of the Hessian at the random initial point of training and after the final training point. The model of this example is a two hidden layer network with a total of 5 _K_ parameters that is trained using gradient descent. Also note that, throughout the paper, the exact full Hessian is computed via the Hessian-vector products (Pearlmutter, 1994) up to the machine precision. + +### 2.2 GENERALIZED GAUSS-NEWTON DECOMPOSITION OF THE HESSIAN + +In order to study its spectrum, we will describe how the Hessian can be decomposed into two meaningful matrices (LeCun et al., 1998; Martens, 2010; 2014). Suppose the loss function is given as a composition of two functions, the model function _f•_ : R_M_ _−→_ R is the real-valued output of a network that depends on the parameters; and the loss function _ℓ•_ : R _−→_ R+ is a convex function. Here, _•_ refers to the given example. Examples include the regression: the mean-square loss composed + +4 + +Workshop track - ICLR 2018 + + +![](.figures/arxiv__1706.04454/1706.04454.pdf-0005-01.png) + + + +1e 1 Full spectrum at large scale
At initial point
1.5
At final point
1.0
0.5
0.0
0.5
0 1000 2000 3000 4000 5000
Order of eigenvalues
Eigenvalues
+ +Figure 1: Ordered eigenvalues at a random initial point, and at the final point of GD. _x_ -axis is the rank of the eigenvalue, and _y_ -axis the value of the eigenvalue. + +with a real-valued output function, and classification: the negative log-likelihood loss composed with the dot product of the output of a softmax layer with the label vector. + +For ease of reading, we indicate the dependencies of functions _ℓ_ and _f_ to data by the index of the example, or omit it altogether in case it is not necessary, and unless noted otherwise the gradients are taken with respect to _w_ . The gradient and the Hessian of the loss for a given example are given by + + +![](.figures/arxiv__1706.04454/1706.04454.pdf-0005-05.png) + + +where _•__T_ denotes the transpose operation (here the gradient is a column-vector). Note that since _ℓ_ is convex _ℓ__′′_ ( _s_ ) _≥_ 0 and we can take its square root which allows us to rewrite the Hessian of the loss as follows: + + +![](.figures/arxiv__1706.04454/1706.04454.pdf-0005-07.png) + + +In general, it isn’t straightforward to discuss the spectrum of the sums of matrices by looking at the individual ones. Nevertheless, looking at the decomposition, we can still infer what we should expect. At a point close to a local minimum, the average gradient is close to zero. However, this doesn’t necessarily imply that the gradients for individual samples are also zero. However, if _ℓ__′_ ( _f_ ( ˆ _w_ )) and _∇_2 _f_ ( ˆ _w_ ) are not correlated, then we can ignore the second term. And so the Hessian can be approximated by the first term: + + +![](.figures/arxiv__1706.04454/1706.04454.pdf-0005-09.png) + + +Here, Equation 5 is the sum of rank one matrices (via the outer products of gradients of _f_ multiplied by some non-negative number), therefore, the sum can be written as a product of an _M × N_ matrix with its transpose where the columns of the matrix are formed by the scaled gradients of _f_ . Immediately, this implies that there are at least _M − N_ many trivial eigenvalues of the right-hand side in Equation 5. + +From a theoretical point of view, the tool that is required for the above problem should be a mapping of eigenvalues of the population matrix to the sample covariance matrix. Recent provable results on this can be found in Bloemendal et al. (2016) (please refer to the appendix for a review of this approach). We emphasize that this result require independent inputs, and extensions to correlated data appear to be unavailable to the best of our knowledge. + +5 + +Workshop track - ICLR 2018 + +## 3 IMPLICATIONS OF FLATNESS FOR THE GEOMETRY OF THE ENERGY LANDSCAPE + +In this section, we leave the decomposition behind and focus on experimental results of the spectrum of the full Hessian through the exact Hessian-vector products. We discuss how data, model, and algorithm affect the spectrum of the Hessian of the loss. + +### 3.1 THE RELATION BETWEEN DATA AND EIGENVALUES + +In many cases of practical interest, the data contains redundancies. In such cases, the number of non-trivial eigenvalues could be even smaller than _N_ . For instance, if one deals with a classification problem where the training data has _k_ classes with relatively small deviation among each of them, it is reasonable to expect that there will be an order of _k_ many non-trivial eigenvalues of the first term of the above decomposition of the Hessian in Equation 5. Then, if the second term is small (for instance when all the gradients per example are zero), we would expect to see _k_ many outliers in the spectrum of the Hessian of the loss. To test this idea, we used a feed-forward neural network with a 100dimensional input layer, two hidden layers each of which with 30 hidden units, and a _k_ dimensional output layer that is combined with softmax for _k_ -class classification. We randomly sampled from _k_ Gaussian clusters in the input space and normalized the data globally. Then we carried out the training using SGD on ReLU network for the following number of clusters: _k_ : _{_ 2 _,_ 5 _,_ 10 _,_ 20 _,_ 50 _}_ . + + +![](.figures/arxiv__1706.04454/1706.04454.pdf-0006-05.png) + + + +1e 2 Gaps in eigenvalue distribution - ReLU
1.6 Heuristic threshold
2
1.4 5
10
1.2 20
50
1.0
0.8
0.6
0.4
0.2
0.0
120 100 80 60 40 20 0
Order of largest eigenvalues
Eigenvalues
+ +Figure 2: Ordered plot of eigenvalues with a visible gap. _x_ -axis is the rank of the eigenvalue, and _y_ -axis the value of the eigenvalue. + +The number of large eigenvalues that are above the gap in Figure 2 match exactly the number of classes in the dataset. This experiment is repeated for various different setups. Please refer to Table 1 in the appendix for more experiments on this. + +### 3.2 THE RELATION BETWEEN NUMBER OF PARAMETERS AND EIGENVALUES + +We test the effect of growing the size of the network when data, architecture, and algorithm are fixed. In some sense, we make the system more and more over-parametrized. Based on the intutions developed above we should not observe a change in the size and number of the large eigenvalues of the Hessian at the bottom. To test this, we sample 1K examples from the MNIST dataset and fix them as the training set. Then we form four different networks each of which has a different number of nodes in its hidden layer ( _n_ hidden _∈{_ 10 _,_ 30 _,_ 50 _,_ 70 _}_ ). All four networks are trained with the same step size and the same number of iterations and the exact Hessian is computed at the end. Figure 3 shows the largest 120 eigenvalues of each of the four Hessians ranked in an increasing order. For the right edge of the spectrum (that is, for the large positive eigenvalues), the shape of the plot remains invariant as the number of parameters increase (Figure 3). + +6 + +Workshop track - ICLR 2018 + + +![](.figures/arxiv__1706.04454/1706.04454.pdf-0007-01.png) + + + +1e 1 Right edge of the spectrum
7960
23860
0.8 39760
55660
0.6
0.4
0.2
0.0
120 100 80 60 40 20
Order of large eigenvalues
Eigenvalues
+ +Figure 3: Right edge of the spectrum of the Hessian of the loss for a fully connected network on MNIST with increasing dimensions. _x_ -axis is the rank of the eigenvalue, and _y_ -axis the value of the eigenvalue. + +### 3.3 THE RELATION BETWEEN THE ALGORITHM AND EIGENVALUES + +Finally, we turn to describing the nature of solutions found by the large and small batch methods for the training landscape. We train a convnet that is composed of 2 convolution layers with relu-maxpool followed by two fully-connected layers. The training set is a subsampled MNIST with 1K training examples. The small batch method uses a mini-batch size of 10, and the large-batch one uses 512. A learning rate for which both algorithms converge is fixed for both LB and SB. Note that the stochastic gradients are averaged over the mini-batch. Therefore, fixing the learning rate allows the algorithms to take steps whose lengths are proportional to the norms of the corresponding stochastic gradients averaged over the mini-batch. This way we ensure that both methods are compared fairly when we look at them after a fixed number of iterations. We train until the same number of iterations have been reached. Then, we calculate the Hessian and the spectrum of the Hessian in Figure 4. The large batch method locates points with larger positive eigenvalues. + + +![](.figures/arxiv__1706.04454/1706.04454.pdf-0007-05.png) + + + +1e1 Right eigenvalue distribution
Heuristic threshold
2.00
Small batch
1.75 Large batch
1.50
1.25
1.00
0.75
0.50
0.25
0.00
40 35 30 25 20 15 10 5
Order of largest eigenvalues
Eigenvalues
+ +Figure 4: Outlier eigenvalues of LB ( _bs_ = 512) vs SB ( _bs_ = 10). _x_ -axis is the rank of the eigenvalue, and _y_ -axis the value of the eigenvalue. + +This observation is consistent with Keskar et al. (2016) as the way they measure flatness takes local rates of increase in a neighborhood of the solution into account which is intimately linked to the size of the large eigenvalues. + +7 + +Workshop track - ICLR 2018 + +### 3.4 TRAILING NEGATIVE EIGENVALUES AFTER TRAINING + +Lastly, we observe that the negative eigenvalues at the end of the training are orders of magnitude smaller than the large ones. The very existence of the negative eigenvalues indicates that the algorithm didn’t locate a local minimum, yet. Note that the stopping criterion in most practical cases is arbitrary. Training is stopped after there is no meaningful decrease in the loss value or increase in the test accuracy. In our experiments, the training ran well beyond the point of this saturation. In this time-scale, the loss decays in much smaller values. And we may expect convergence to a localminimum at large (possibly exponentially long) time-scales. However, from a practical point of view, it appears that the properties of the landscape at this fine-grained scale is less relevant in terms of its test performance. Anyhow, we observe that there are a number of negative eigenvalues but their magnitude is much smaller compared to the positive eigenvalues. + + +![](.figures/arxiv__1706.04454/1706.04454.pdf-0008-03.png) + + + +1e 5 Left edge of the spectrum
0
1
2
3
4
5
7960
23860
6
39760
55660
7
0.000 0.002 0.004 0.006 0.008 0.010 0.012 0.014
Percentage of small eigenvalues
Eigenvalues
+ +Figure 5: Negative eigenvalues at the bottom when increasing the number of hidden nodes (compare with Figure 3). _x_ -axis indicates the order of the eigenvalue in percentages. + + +![](.figures/arxiv__1706.04454/1706.04454.pdf-0008-05.png) + + + +1e 2 Left eigenvalue distribution
0.0
0.5
1.0
1.5
2.0
2.5
3.0
Small batch
3.5 Large batch
0.0000 0.0025 0.0050 0.0075 0.0100 0.0125 0.0150 0.0175 0.0200
Percentage of largest eigenvalues
Eigenvalues
+ +Figure 6: Negative eigenvalues at the bottom for LB-SB experiment on MNIST (compare with Figure 4). _x_ -axis indicates the order of the eigenvalue in percentages. + +The reason we look at the ranked negative eigenvalues in percentages rather than the order is the result of the experiment in Section 3.2. Adding more weights to the system scale the small scale eigenvalues proportionally, the _number_ of outliers remain unchanged whereas the _ratio_ of negative eigenvalues remain the same. Moreover, they appear to be converging to have the same shape (Figure 6). Also, note that the negative eigenvalues can only come from the second term of the decomposition in Equation 5. Unless the second term contributes to the spectrum in an asymmetrical way, the observation that the negative eigenvalues are small confirms our previous suspicions that the effect of the ignored term in the decomposition is small. + +8 + +Workshop track - ICLR 2018 + +## 4 DISCUSSION ON BASINS OF SOLUTIONS AND GENERALIZATION + +Finally, we revisit the issue through the lens of the following question: What does overparametrization imply on the discussion around GD vs. SGD (or large batch vs small batch) especially for their generalization properties? In this final section, we will argue that, contrary to what is believed in Keskar et al. (2016) and Chaudhari et al. (2016) the two algorithms do _not_ have to be falling into different basins. + +As noted in the introduction, for a while the common sense explanation on why SGD works well (in fact better) than GD (or large batch methods) was that the non-convex landscape had local minima at high energies which would trap large-batch or full-batch methods. Something that SGD with small batch shouldn’t suffer due to the inherent noise in the algorithm. However, there are various experiments that have been carried out in the past that show that, for reasonable large systems, this is not the case. For instance, Sagun et al. (2014) demonstrate that a two hidden layer fully connected network on MNIST can be trained by GD to reach at the same level of loss values as SGD1 . In fact, when the step size is fixed to the same value for both of the algorithms, they reach the same loss value at the same number of iterations. The training accuracy for both algorithms are the same, and the gap between test accuracies diminish as the size of the network increase with GD falling ever so slightly behind. It is also shown in Keskar et al. (2016) that training accuracies for both large and small batch methods are comparably good. Furthermore, Zhang et al. (2016) demonstrates that training landscape is easy to optimize even when there is no clear notion of generalization. Such observations are consistent with our observations: over-parametrization (due to the architecture of the model) leads to flatness at the bottom of the landscape which is easy to optimize. + +When we turn our attention to generalization, Keskar et al. (2016) note that LB methods find a basin that is different than the one found by SB methods, and they are characterized by how wide the basin is. As noted in Figure 4, indeed the large eigenvalues are larger in LB than in SB, but is it enough to justify that they are in different basins, especially given the fact that the number of flat directions are enormous. In the next experiment, we revisit the LB SB training on CIFAR10 with a twist to test the idea that they may be in the same basin. + +### 4.1 ARE THEY REALLY DIFFERENT BASINS? CASE OF CIFAR10 WITH LB VS SB + +The observation that LB converges to sharper basins that are separated by wells from the wider basins found by SB has been an observation that triggered attention. In this section, we present two solutions with different qualities as measured by the generalization error and we show that they are in fact in the same ‘basin’ by showing that the evaluation of the loss doesn’t go through a barrier between the two solutions. We start by two common pitfalls that one may fall into in testing this: + +**The problem with epoch based time scales:** A common way to plot training profiles in larger scale neural networks is to stop every epoch to reserve extra computational power to calculate various statistics of the model at its current position. This becomes problematic when one compares training with different batch sizes, primarily because the larger batch model takes fewer steps in a given epoch. Recall that the overall loss is averaged, therefore, for a fixed point in the weight space, the empirical average of the gradients is an unbiased estimator for the expected gradient. Hence it is reasonable to expect that the norms of the large batch methods match to the ones of the small batch. And for a fair comparison, one should use the same learning rate for both training procedures. This suggests that a better comparison between GD and SGD (or LB and SB) should be scaled with the number of steps, so that, on average both algorithms are able to take similar number of steps of comparable sizes. The experiments we present use the number of iterations as the time-scale. + +**The problem with line interpolations in the weight space:** The architecture of typical neural networks have many internal symmetries, one of which is the flip symmetry: when one swaps two nodes (along with the weights connected to it) at a given layer, the resulting network is identical to the original one. Therefore, when one trains two systems to compare, it may well be possible that + +> 1Another important observation in this work that will be useful for our purposes is that constant rate GD (as well as SGD) do not stop as training progresses, the loss decreases ever so slightly. This means that even in the flat region, there is a small amount of signal coming from the total gradients that allow the algorithm to keep moving on the loss surface. We emphasize that this doesn’t contradict with the theory, but this implies that the convergence may be achieved at time scales much larger than the ones we observe in practice. + +9 + +Workshop track - ICLR 2018 + +the two fall into different flip symmetrical configurations that may look more similar when they are reordered. Therefore, training two systems with levels of randomness (seed, batch-size, choice of the initial point, etc.) may result in two points in the weight space that present a barrier only because of such symmetries. In an attempt to partially alleviate this problem we switch dynamics of an already trained system. + +1. _Part I:_ Train full CIFAR10 data for a bare AlexNet (bare meaning: no momentum, no dropout, and no batch normalization) with a batch-size of 1 _,_ 000. Record every 100 steps for 250 times. + +2. _Part II:_ Continue training from the endpoint of the previous step with a smaller batch-size of 32. Everything else, including the constant learning rate is kept the same. And train another 250 periods each of which with 100 steps. + + +![](.figures/arxiv__1706.04454/1706.04454.pdf-0010-04.png) + + + +Continuous training in two phases
1.0
Train acc
2.0 Test acc
0.8
1.5
0.6
1.0
0.4
0.5
0.2
Train loss
Test loss
0.0
0.0
0 10000 20000 30000 40000 50000
Number of steps (measurements every 100)
Loss value Accuracy
+ +Figure 7: Large batch training immediately followed by small batch training on the full dataset of CIFAR10 with a _raw_ version of AlexNet. The accuracy increases by about 1%: _Part I_ and _Part II_ locate solutions with different generalization properties. + +The key observation is the jump in the training and test losses, and a drop in the corresponding accuracies (Figure 7). Toward the end of _Part II_ the small batch reaches to a slightly better accuracy (about _∼_ 1%). And this looks in line with the observations in Keskar et al. (2016), in that, it appears that the LB solution and SB solutions are separated by a barrier and that the latter of which generalizes better. Moreover, the line interpolations extending away from either endpoint appear to be confirming the sharpness of LB solution. However, we find the _straight line_ interpolation connecting the endpoints of _Part I_ and _Part II_ turns out to _not_ contain any barriers (Figure 8). This suggests that while the _Part I_ and _Part II_ converge to two solutions with different properties, these solutions have been in the same basin all along. This raises the striking possibility that those other seemingly different solutions may be similarly connected by a flat region to form a larger basin (modulo internal symmetries). + +Another interpretation of this experiment, also, goes through the Gauss-Newton decomposition introduced in Equation 5. When we decrease the batch size, we increase the noise in the covariance of the gradients, and hence the first term starts to dominate. Even when the weight space has large flat regions, the fluctuations of the stochastic noise should be precisely in the directions of the large eigenvalues. Therefore, at the beginning of Part II, the loss increases because the point fluctuates in the directions corresponding to the large eigenvalues, and eventually settles at a point that lies at the interior of the same level set, essentially staying in the same basin. + +### 4.2 FURTHER DISCUSSIONS + +One of the most striking implications of flatness may be the connected structure of the solution space. We may wonder whether two given solutions can be connected by a continuous path of solutions. + +10 + +Workshop track - ICLR 2018 + + +![](.figures/arxiv__1706.04454/1706.04454.pdf-0011-01.png) + + + +Line interpolation between end points of the two phases
7 1.0
Train accuracy
6 Test accuracy
0.8
5
4 0.6
3
0.4
2
Train loss
1 Test loss 0.2
0
0.0
1.0 0.5 0.0 0.5 1.0 1.5 2.0
Interpolation coefficient
Loss value Accuracy
+ +Figure 8: Loss and accuracy evaluation on the _straight_ line that contains LB and SB solutions. The accuracy of the SB solution is _∼_ 1% better than the LB solution, but there is no barrier between the two points. + +This question has been explored in a recent work: in Freeman & Bruna (2016) it is shown that for one hidden layer rectified neural networks the solution space is connected which is consistent with the flatness of the landscape. The classical notion of basins of attractions may not be the suitable objects to study for neural networks. Rather, we may look at the exploration of interiors of level sets of the landscape. We may be tempted to speculate that such an exploration may indeed result in point that generalizes better. However, the flat space itself is very high dimensional which comes with its own computational issues. + +The training curve can be seen as composed of two parts: (1) high gain part where the norm of the gradients are large, (2) noise of the gradients is larger relative to the size of the stochastic gradients (see (Shwartz-Ziv & Tishby, 2017) for a recent reference). We speculate that the first part is relatively easy and even a large batch method can locate a large level set that contains points that generalize better than what’s initially found. From a practical point of view, using larger batches with larger step sizes can, in fact, accelerate training. An example of this can be found in Goyal et al. (2017), where training Imagenet with a minibatch size of 8192 can match small batch performance. On a final note for further consideration, we remark that we used standard pre-processing and initialization methods that are commonly used in practice. Fixing these two aspects, we modified the data, model, and algorithm in order to study their relative effects. However, the effects of pre-processing and initialization on the Hessian is highly non-trivial and deserves a separate attention. + +## 5 CONCLUSION + +We have shown that the level of the singularity of the Hessian cannot be ignored from theoretical considerations. Furthermore, we use the generalized Gauss-Newton decomposition of the Hessian to argue the cluster of zero eigenvalues are to be expected in practical applications. This allows us to reconsider the division between initial fast decay and final slow progress of training. We see that even large batch methods are able to get to the same basin where small batch methods go. As opposed to the common intuition, the observed generalization gap between the two is not due to small batch finding a different, better, wider basin. Instead, the two solutions appear to be in the same basin. This lack of a barrier between solutions is demonstrated by finding paths between the two points that lie in the same level set. To conclude, we propose a major shift in perspective on considerations of the energy landscape in deep learning problems. + +11 + +Workshop track - ICLR 2018 + +### ACKNOWLEDGMENTS + +We thank Yann Ollivier, Afonso Bandeira, Yann LeCun, and Mihai Nica for their valuable comments. We also thank the reviewers for their suggestions. The first author was partially supported by the grant from the Simons Foundation ( _♯_ 454935, Giulio Biroli). + +## REFERENCES + +Antonio Auffinger, Gérard Ben Arous, and Jiˇrí Cernˇ y.` Random matrices and complexity of spin glasses. _Communications on Pure and Applied Mathematics_ , 66(2):165–201, 2013. + +- Jinho Baik, Gérard Ben Arous, Sandrine Péché, et al. Phase transition of the largest eigenvalue for nonnull complex sample covariance matrices. _The Annals of Probability_ , 33(5):1643–1697, 2005. + +- Carlo Baldassi, Christian Borgs, Jennifer T. Chayes, Alessandro Ingrosso, Carlo Lucibello, Luca Saglietti, and Riccardo Zecchina. Unreasonable effectiveness of learning neural networks: From accessible states and robust ensembles to basic algorithmic schemes. _Proceedings of the National Academy of Sciences_ , 113(48):E7655–E7662, November 2016. ISSN 0027-8424, 1091-6490. doi: 10.1073/pnas.1608103113. + +- Andrew J Ballard, Ritankar Das, Stefano Martiniani, Dhagash Mehta, Levent Sagun, Jacob D Stevenson, and David J Wales. Energy landscapes for machine learning. _Physical Chemistry Chemical Physics_ , 2017. + +- Yoshua Bengio et al. Learning deep architectures for ai. _Foundations and trends⃝_ R _in Machine Learning_ , 2(1):1–127, 2009. + +- Alex Bloemendal, Antti Knowles, Horng-Tzer Yau, and Jun Yin. On the principal components of sample covariance matrices. _Probability Theory and Related Fields_ , 164(1-2):459–552, 2016. + +- Léon Bottou. Stochastic gradient learning in neural networks. _Proceedings of Neuro-Nımes_ , 91(8), 1991. + +- Léon Bottou. Large-scale machine learning with stochastic gradient descent. In _Proceedings of COMPSTAT’2010_ , pp. 177–186. Physica-Verlag HD, 2010. + +- J Bourrely. Parallelization of a neural network learning algorithm on a hypercube. _Hypercube and distributed computers. Elsiever Science Publishing_ , 1989. + +- Pratik Chaudhari, Anna Choromanska, Stefano Soatto, Yann LeCun, Carlo Baldassi, Christian Borgs, Jennifer Chayes, Levent Sagun, and Riccardo Zecchina. Entropy-sgd: Biasing gradient descent into wide valleys. _arXiv preprint arXiv:1611.01838_ , 2016. + +- Laurent Dinh, Razvan Pascanu, Samy Bengio, and Yoshua Bengio. Sharp minima can generalize for deep nets. _arXiv preprint arXiv:1703.04933_ , 2017. + +- C Daniel Freeman and Joan Bruna. Topology and geometry of deep rectified network optimization landscapes. _arXiv preprint arXiv:1611.01540_ , 2016. + +- Priya Goyal, Piotr Dollár, Ross Girshick, Pieter Noordhuis, Lukasz Wesolowski, Aapo Kyrola, Andrew Tulloch, Yangqing Jia, and Kaiming He. Accurate, large minibatch sgd: Training imagenet in 1 hour. _arXiv preprint arXiv:1706.02677_ , 2017. + +- Caglar Gulcehre, Marcin Moczulski, Francesco Visin, and Yoshua Bengio. Mollifying networks. _arXiv preprint arXiv:1608.04980_ , 2016. + +Elad Hazan, Kfir Yehuda Levy, and Shai Shalev-Shwartz. On graduated optimization for stochastic non-convex problems. In _International Conference on Machine Learning_ , pp. 1833–1841, 2016. + +Sepp Hochreiter and Jürgen Schmidhuber. Flat minima. _Neural Computation_ , 9(1):1–42, 1997. + +Stanisław Jastrz˛ebski, Zachary Kenton, Devansh Arpit, Nicolas Ballas, Asja Fischer, Yoshua Bengio, and Amos Storkey. Three factors influencing minima in sgd. _arXiv preprint arXiv:1711.04623_ , 2017. + +12 + +Workshop track - ICLR 2018 + +Nitish Shirish Keskar, Dheevatsa Mudigere, Jorge Nocedal, Mikhail Smelyanskiy, and Ping Tak Peter Tang. On large-batch training for deep learning: Generalization gap and sharp minima. _arXiv preprint arXiv:1609.04836_ , 2016. + +- Yann LeCun, Léon Bottou, GB Orr, and K-R Müller. Efficient backprop. _Lecture notes in computer science_ , pp. 9–50, 1998. + +- Jason D Lee, Max Simchowitz, Michael I Jordan, and Benjamin Recht. Gradient descent converges to minimizers. _University of California, Berkeley_ , 1050:16, 2016. + +- Vladimir A Marˇcenko and Leonid Andreevich Pastur. Distribution of eigenvalues for some sets of random matrices. _Mathematics of the USSR-Sbornik_ , 1(4):457, 1967. + +- James Martens. Deep learning via hessian-free optimization. In _Proceedings of the 27th International Conference on Machine Learning (ICML-10)_ , pp. 735–742, 2010. + +- James Martens. New insights and perspectives on the natural gradient method. _arXiv preprint arXiv:1412.1193_ , 2014. + +- Song Mei, Yu Bai, and Andrea Montanari. The landscape of empirical risk for non-convex losses. _arXiv preprint arXiv:1607.06534_ , 2016. + +- Hossein Mobahi. Training recurrent neural networks by diffusion. _arXiv preprint arXiv:1601.04114_ , 2016. + +- Hossein Mobahi and John W Fisher III. On the link between gaussian homotopy continuation and convex envelopes. In _International Workshop on Energy Minimization Methods in Computer Vision and Pattern Recognition_ , pp. 43–56. Springer, 2015a. + +- Hossein Mobahi and John W Fisher III. A theoretical analysis of optimization by gaussian continuation. In _AAAI_ , pp. 1205–1211. Citeseer, 2015b. + +- Jorge Nocedal and Stephen J Wright. Numerical optimization, second edition. _Numerical optimization_ , pp. 497–528, 2006. + +- Ioannis Panageas and Georgios Piliouras. Gradient descent only converges to minimizers: Nonisolated critical points and invariant regions. _arXiv preprint arXiv:1605.00405_ , 2016. + +- Panos M Pardalos, David Shalloway, and Guoliang Xue. Optimization methods for computing global minima of nonconvex potential energy functions. _Journal of Global Optimization_ , 4(2):117–133, 1994. + +- Barak A Pearlmutter. Fast exact multiplication by the hessian. _Neural computation_ , 6(1):147–160, 1994. + +- Lucjan Piela, Jaroslaw Kostrowicki, and Harold A Scheraga. On the multiple-minima problem in the conformational analysis of molecules: deformation of the potential energy hypersurface by the diffusion equation method. _The Journal of Physical Chemistry_ , 93(8):3339–3346, 1989. + +- Levent Sagun, V. U˘gur Güney, Gérard Ben Arous, and Yann LeCun. Explorations on high dimensional landscapes. _ICLR 2015 Workshop Contribution, arXiv:1412.6615_ , 2014. + +- Levent Sagun, Léon Bottou, and Yann LeCun. Singularity of the hessian in deep learning. _arXiv preprint arXiv:1611.07476_ , 2016. + +- Tom Schaul, Sixin Zhang, and Yann LeCun. No more pesky learning rates. _ICML (3)_ , 28:343–351, 2013. + +- Ravid Shwartz-Ziv and Naftali Tishby. Opening the black box of deep neural networks via information. _arXiv preprint arXiv:1703.00810_ , 2017. + +- Chiyuan Zhang, Samy Bengio, Moritz Hardt, Benjamin Recht, and Oriol Vinyals. Understanding deep learning requires rethinking generalization. _arXiv preprint arXiv:1611.03530_ , 2016. + +13 + +Workshop track - ICLR 2018 + +## A OUTLIER EIGENVALUES + +In the subsequent experiments, we used a feed-forward neural network with a 100 dimensional input layer, two hidden layers each of which with 30 hidden units, and a _k_ dimensional output layer that is combined with softmax for _k_ -class classification. We sampled random _k_ Gaussian clusters in the input space and normalized the data globally. Then we carried out the training for the following sets of parameters: _k_ : _{_ 2 _,_ 5 _,_ 10 _,_ 20 _,_ 50 _}_ , algorithm: {GD, SGD}, non-linearity: {tanh, ReLU}, initial multipler for the covariance of the input distribution: _{_ 1 _,_ 10 _}_ . Then we counted the number of large eigenvalues according to three different cutoff methods: largest consecutive gap, largest consecutive ratio, and a heuristic method of determining the threshold by searching for the elbow in the scree plot (see Figure 2). In Table 1 we marked the ones that are off by _±_ 1. + +|alg-size|cov|mean&std|nc|gap|ratio|heur|cutoff| +|---|---|---|---|---|---|---|---| +|GD tanh 4022|1.0|1.19e-05, 1.19e-05|2|**2**|**3**|N/A|N/A| +|GD tanh 4115|1.0|2.52e-05, 2.52e-05|5|2|17|N/A|N/A| +|GD tanh 4270|1.0|3.75e-05, 3.75e-05|10|2|4|N/A|N/A| +|GD tanh 4580|1.0|5.37e-05,5.37e-05|20|3|4|N/A|N/A| +|GD ReLU 4022|1.0|7.13e-06, 7.13e-06|2|**3**|4|**2**|0.003| +|GD ReLU 4115|1.0|1.55e-05, 1.55e-05|5|**6**|7|**5**|0.003| +|GD ReLU 4270|1.0|3.04e-05, 3.04e-05|10|**11**|12|**10**|0.003| +|GD ReLU 4580|1.0|5.65e-05, 5.65e-05|20|4|**21**|**21**|0.003| +|GD ReLU 5510|1.0|1.16e-04,1.16e-04|50|**50**|**51**|**49**|0.003| +|SGD ReLU 4022|1.0|8.96e-06, 8.96e-06|2|**3**|4|**2**|0.002| +|SGD ReLU 4115|1.0|1.62e-05, 1.62e-05|5|**6**|7|7|0.002| +|SGD ReLU 4270|1.0|2.97e-05, 2.97e-05|10|2|14|15|0.002| +|SGD ReLU 4580|1.0|4.53e-05, 4.53e-05|20|2|3|**21**|0.002| +|SGD ReLU 5510|1.0|6.78e-05,6.78e-05|50|**50**|**51**|**49**|0.002| +|SGD ReLU 4022|10.0|9.49e-05, 9.49e-05|2|**2**|4|**2**|0.015| +|SGD ReLU 4115|10.0|1.68e-04, 1.68e-04|5|**5**|**6**|**5**|0.015| +|SGD ReLU 4270|10.0|1.92e-04, 1.92e-04|10|2|**11**|**9**|0.015| +|SGD ReLU 4580|10.0|3.10e-04, 3.10e-04|20|**20**|**21**|**19**|0.015| +|SGD ReLU 5510|10.0|1.67e-04, 1.67e-04|50|**50**|**51**|**49**|0.005| + + + +Table 1: Counting outliers for matching the number of blobs. Dictionary of table elements: {cov: scale of covariance for inputs, mean&std: of the eigenvalues, nc: number of clusters, gap: largest consecutive gaps, ratio: largest consecutive ratios, heur: heuristic threshold, cutoff: the value of the heur.} + +## B THE SPECTRUM OF THE GENERALIZED GAUSS-NEWTON MATRIX + +In this section, we will show that the spectrum of the Generalized Gauss-Newton matrix can be characterized theoretically under some conditions. Suppose that we can express the scaled gradient (� _ℓ__′′_ _i_(_fi_( ˆ_w_))_∇fi_( ˆ_w_) from Equation 5) as_g_=_Tx_with the matrix_T∈M× d_depending only on 1 the parameters _w_ - which is the case for linear models. Then we can write: _G_ = _N_ � _i∈D__gig_ _i__T_= 1=_{x_1_, . . . , xN}_is an_d×N_matrix.Furthermore, without loss of generality, _N__TXXT T T_, where_X_ we assume that the examples are normalized such that the entries of _X_ are independent with zero mean and unit variance. One of the first steps in studying _G_ goes through understanding its principle components. In particular, we would like to understand how the eigenvalues and eigenvectors of _G_ are related to the ones of Σ where Σ := E( _G_ ) = _N_1_TXXT T T_=_TT T_. + +In the simplest case, we have Σ = _Id_ so that the gradients are uncorrelated and the eigenvalues of _G_ are distributed according to the Marˇcenko-Pastur law in the limit where _N, M →∞_ and _α_ :=_M_The result dates back to sixties and can be found in (Marˇcenko & Pastur, 1967).Note that _N_. if _M > N_ then there are _M − N_ trivial eigenvalues of _G_ at zero. Also, the width of the nontrivial distribution essentially depends on the ratio _α_ . Clearly, setting the expected covariance to identity is very limiting. One of the earliest relaxations appear in (Baik et al., 2005). They prove a phase transition for the largest eigenvalues of the sample covariance matrix which has been known as the _BBP phase transition_ . A case that may be useful for our setup is as follows: + +14 + +Workshop track - ICLR 2018 + +**Theorem 1** (Baik, Arous, Péché, et al., 2005) **.** _If_ Σ = _diag_ ( _ℓ,_ 1 _, . . . ,_ 1) _, ℓ>_ 1 _, and M, N →∞ with__M_ _N_=_α ≥_1_.Let c_= 1 +_√_ _α, and let’s call the top eigenvalue of the sample covariance matrix as λmax then:_ + +- _If_ 1 _≤ ℓ< c then λmax is at the right edge of the spectrum with Tracy-Widom fluctuations._ + +- _• If c < ℓ then λmax is an outlier that is away from bulk centered at ℓ_ (1+ _ℓ−α_ 1)_with Gaussian_ _fluctuations._ + +Typically, due to the correlations in the problem we don’t have Σ to be the identity matrix or a diagonal matrix with spikes. This makes the analysis of their spectrum a lot more difficult. A solution for this slightly more general case with non-trivial correlations has been provided only recently by Bloemendal et al. (2016). We will briefly review these results here see how they are related to the first term of the above decomposition. + +**Theorem 2** (Bloemendal, Knowles, Yau, and Yin, 2016) **.** _If d_ = _M ,_ Σ _− Id has bounded rank,_ log _N is comparable to_ log _M , and entries of X are independent with mean zero and variance one, then the spectrum of_ Σ _can be precisely mapped to the one of G as M, N →∞ for fixed α_ =_M_ _N__._ _Let K_ = _min{M, N }, and the decomposition of the spectrum can be described as follows:_ + +- **_Zeros:_** _M − K many eigenvalues located at zero (if M > N )._ + +- **_Bulk:_** _Order K many eigenvalues are distributed according to Marˇcenko-Pastur law._ + +- **_Right outliers:_** _All eigenvalues of_ Σ _that exceed a certain value produce large-positive outlier eigenvalues to the right of the spectrum of G._ + +- **_Left outliers:_** _All eigenvalues of_ Σ _that are close to zero produce small outlier eigenvalues between 0 and the left edge of the bulk of G._ + +_Moreover, the eigenvectors of outliers of G are close to the corresponding ones of_ Σ _._ + +This theorem essentially describes the way in which one obtains outlier eigenvalues in the sample covariance matrix assuming the population covariance is known. Here is an example: + + +![](.figures/arxiv__1706.04454/1706.04454.pdf-0015-11.png) + + + +2 . 00 35
alpha = 0.2 alpha = 1
1 . 75 alpha = 1 30 alpha = 2
alpha = 0.02
1 . 50 25
1 . 25
20
1 . 00
15
0 . 75
10
0 . 50
0 . 25 5
0 . 00 0
0 . 0 0 . 5 1 . 0 1 . 5 2 . 0 2 . 5 3 . 0 3 . 5 4 . 0 0.0 0.1 0.2 0.3 0.4 0.5
Eigenvalue Eigenvalues
Density Density
+ +Figure 9: Spectrum of the logistic regression loss with tanh unit: when data has a single Gaussian blob (left), when data has two Gaussian blobs (right). In the latter case, the spectrum has outlier eigenvalues at 454.4, 819.5, and 92.7 for _alpha_ = 1 _,_ 2 _,_ 0 _._ 02, respectively. + +1 **Example 1** (Logistic regression) **.** Consider the log-loss _ℓ_ ( _s, y_ ) = _−y_ log 1+ _e__−s−_(1_−y_) log(1_−_ 1+1 _e__−s_) and a single neuron with the sigmoid non-linearity.Note that_ℓ_(_s, y_) is convex in_s_for fixed _y_ , and we can apply the decomposition using _ℓ_ and _f_ ( _w, x_ ) = _⟨w, x⟩_ . In this case we have _M_ = _d_ , also, note that the second part of the Hessian in Equation 4 is zero since _∇_2 _f_ ( _w, x_ ) = 0. So the Hessian of the loss is just the first term. It is straightforward to calculate that the gradient per sample is of the form _g_ = _c_ ( _w, x_ ) _IdM x_ for a positive constant _c_ = _c_ ( _w, x_ ) that doesn’t depend on _y_ . This case falls into the classical Marˇchenko-Pastur law (left pane of Figure 9). + +**Example 2.** Once we have more than one class this picture fails to hold. For, _ℓ_ ( _s_ ) = _−_ log( _s_ ), and exp_−⟨wy,x⟩_ _f_ ( _w_ ; ( _x, y_ )) = _k_exp _−⟨wyk ,x⟩_the spectrum changes.It turns out that in that case the weights have one large outlier eigenvalue, and a bulk that’s close to zero (right pane of Figure 9). + +15 diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/references/rendered/1803.10856_glassy-phase-of-optimal-quantum-control.md b/tracks/qcs/solutions/frustration-free/challenge-113/references/rendered/1803.10856_glassy-phase-of-optimal-quantum-control.md new file mode 100644 index 000000000..a2a865fbf --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/references/rendered/1803.10856_glassy-phase-of-optimal-quantum-control.md @@ -0,0 +1,726 @@ +--- +source: "https://arxiv.org/abs/1803.10856" +type: "arxiv" +canonical_id: "1803.10856" +title: "Glassy Phase of Optimal Quantum Control." +authors: "Andrew G. R. Day, Marin Bukov, Phillip Weinberg, Pankaj Mehta, Dries Sels" +year: "2019" +venue: "Physical Review Letters" +arxiv_id: "1803.10856" +doi: "10.1103/PhysRevLett.122.020601" +full_text: yes +--- + +# Glassy Phase of Optimal Quantum Control. + +**Authors:** Andrew G. R. Day, Marin Bukov, Phillip Weinberg, Pankaj Mehta, Dries Sels + +**Citation:** Physical Review Letters, vol. 122 2, pp. + 020601 + , 2019 + +**arXiv:** [1803.10856](https://arxiv.org/abs/1803.10856) + +**DOI:** [10.1103/PhysRevLett.122.020601](https://doi.org/10.1103/PhysRevLett.122.020601) + +## Abstract + +We study the problem of preparing a quantum many-body system from an initial to a target state by optimizing the fidelity over the family of bang-bang protocols. We present compelling numerical evidence for a universal spin-glasslike transition controlled by the protocol time duration. The glassy critical point is marked by a proliferation of protocols with close-to-optimal fidelity and with a true optimum that appears exponentially difficult to locate. Using a machine learning (ML) inspired framework based on the manifold learning algorithm t-distributed stochastic neighbor embedding, we are able to visualize the geometry of the high-dimensional control landscape in an effective low-dimensional representation. Across the transition, the control landscape features an exponential number of clusters separated by extensive barriers, which bears a strong resemblance with replica symmetry breaking in spin glasses and random satisfiability problems. We further show that the quantum control landscape maps onto a disorder-free classical Ising model with frustrated nonlocal, multibody interactions. Our work highlights an intricate but unexpected connection between optimal quantum control and spin glass physics, and shows how tools from ML can be used to visualize and understand glassy optimization landscapes. + +## Full Text + +# **Glassy Phase of Optimal Quantum Control** + +Alexandre G.R. Day,1,_∗_ Marin Bukov,2,_†_ Phillip Weinberg,1 Pankaj Mehta,1 and Dries Sels1, 3, 4 + +> _1Department of Physics, Boston University, 590 Commonwealth Ave., Boston, MA 02215, USA_ + +> _2Department of Physics, University of California, Berkeley, CA 94720, USA_ + +> _3Department of Physics, Harvard University, 17 Oxford St., Cambridge, MA 02138, USA_ + +> _4Theory of quantum and complex systems, Universiteit Antwerpen, B-2610 Antwerpen, Belgium_ + +(Dated: February 12, 2019) + +We study the problem of preparing a quantum many-body system from an initial to a target state by optimizing the fidelity over the family of bang-bang protocols. We present compelling numerical evidence for a universal spin-glass-like transition controlled by the protocol time duration. The glassy critical point is marked by a proliferation of protocols with close-to-optimal fidelity and with a true optimum that appears exponentially difficult to locate. Using a machine learning (ML) inspired framework based on the manifold learning algorithm _t_ -SNE, we are able to visualize the geometry of the high-dimensional control landscape in an effective low-dimensional representation. Across the transition, the control landscape features an exponential number of clusters separated by extensive barriers, which bears a strong resemblance with replica symmetry breaking in spin glasses and random satisfiability problems. We further show that the quantum control landscape maps onto a disorder-free classical Ising model with frustrated nonlocal, multibody interactions. Our work highlights an intricate but unexpected connection between optimal quantum control and spin glass physics, and shows how tools from ML can be used to visualize and understand glassy optimization landscapes. + +State preparation plays a quintessential role in presentday studies of quantum physics. The ability to reliably manipulate and control quantum states has proven crucial to many physical systems, from quantum mechanical emulators ultracold atoms [1–3] and trapped ions [4–6], through solid-state systems like superconducting qubits [7], to nitrogen-vacancy centres [8]. The non-equilibrium character of quantum state manipulation makes it a difficult and not well-understood problem of ever-increasing importance to building a large-scale quantum computer [9]. + +Analytically, state preparation has been studied using both adiabatic perturbation theory [10] and shortcuts to adiabaticity [11–15]. Unfortunately, these theories have limited application in non-integrable manybody systems, for which no exact closed-form expressions can be obtained. This has motivated the development of efficient numerical algorithms, such as GRAPE [16, 17], CRAB [18], and Machine learning based approaches [19– 31]. State preparation can be formulated as an optimal control problem for which the objective is to find the set of controls that extremize a cost function, i.e. determine the optimal fidelity to prepare a target state, subject to physical and dynamical constraints. However, cost functions are usually defined on a high-dimensional space and are typically non-convex. For this reason, sophisticated algorithms must be devised to guarantee finding the global optimum. Moreover, optimality does not automatically imply stability and robustness of the solution, which are required for experimental applications. + +> _∗_ Electronic address: agrday@bu.edu + +> _†_ Electronic address: mgbukov@berkeley.edu + + +![](.figures/arxiv__1803.10856/1803.10856.pdf-0001-13.png) + + + +effective
classical
model
log-fidelity
log-fidelity
+ +FIG. 1: Bang-bang protocols _h_ ( _jδt_ ) to control a quantum system with high fidelity (a) are equivalent to classical spin configurations _hj_ with log-fidelity playing the role of energy. (b) Using _k_ -flip Stochastic Descent, we explore the log-fidelity landscape (c), and find a glass-like transition in the control landscape described by the effective classical model _H_ eff (d). + +Establishing the general limitations and constraints of quantum control is crucial for guiding the field forward. + +Recently, it was shown that the quantum state preparation paradigm supports a number of _control phase transitions_ by varying the protocol duration _T_ [22, 32, 33], exhibiting _overconstrained_ , _controllable_ , _correlated_ , and _glassy_ phases. Glass-like systems are expected to feature slow equilibration time scales related to an underlying + +2 + +extremely rugged free-energy landscape. Such features have been extensively discussed in the context of spinglass physics [34–37] and in hard combinatorial [38–41] and random satisfiability [42–48] problems. + +In this work we provide evidence for the existence of a generic glass-like control phase transition observed in the manipulation of generic nonintegrable spin chains with a single global control field. By sampling the optimization landscape for this state preparation problem, we discover the existence of a glass-like critical point marked by an extremely rugged landscape with an exponential number local extrema. This transition in the control landscape is visualized using the manifold learning method known as _t_ -distributed stochastic neighbor-embedding ( _t_ -SNE) [49], which reveals the clustering of minima near the glass transition. We further present a mapping of this dynamical optimal control problem to a _static_ frustrated classical spin model with all-to-all multi-body interactions, the energy landscape of which is in one-to-one correspondence with the original optimization landscape. Similar to the problem of finding the ground-state of spin-glasses, we find strong evidence for an exponential algorithmic complexity scaling in the number of _control_ degrees of freedom for the task of locating the optimal protocol, suggesting that quantum state preparation is NP-hard in the glassy phase. + +_Problem Setup.—_ Consider a periodic chain of _L_ interacting qubits (Pauli operator _Si__µ_),controlledbyaglobal time-dependent transverse-field: + + +![](.figures/arxiv__1803.10856/1803.10856.pdf-0002-04.png) + + +with interaction strength _J_ = 1 (sets the energy scale), and an external magnetic field of a static _z_ -component _g_ = 1 and a time-varying _x_ -component _h_ ( _t_ ). The presence of the longitudinal _z_ -field renders the model nonintegrable at any fixed time _t_ , with no known closedform expression for the exact instantaneous eigenstates and eigenenergies. We work in a non-perturbative regime with all couplings of similar magnitude, and choose a bounded control _|h_ ( _t_ ) _|≤_ 4 reflecting the experimental infeasibility to inject unlimited amounts of energy in the system. + +The system is prepared at _t_ = 0 in the paramagnetic ground state (GS) _|ψi⟩_ of _H_ [ _h_ = _−_ 2]. Our goal is to find a protocol _h__∗_ ( _t_ ) which, following Schr¨odinger evolution for a fixed short duration _T ∈_ [0 _,_ 4], brings the initial state _|ψi⟩_ as close as possible to the target state – the paramagnetic GS _|ψ∗⟩_ of _H_ [ _h_ =+2], as measured by the many-body fidelity _Fh_ ( _T_ ) = _|⟨ψ∗|ψ_ ( _T_ ) _⟩|_2 . The specific values of the field for the initial and target states, _h_ = _±_ 2, were chosen to be of similar magnitude as the interaction strength _J_ =1. We checked that the conclusions we draw in this work are insensitive to this choice. + +Whether preparing the target state with unit fidelity is feasible in the thermodynamic (TD) limit _L →∞_ , is currently an open question related to the existence of + +a finite quantum speed limit [15, 50, 51]. Let us formulate this objective as a minimization problem, and choose as a cost function the (negative) log-fidelity _Ch_ ( _T_ ) = _−_ log _Fh_ ( _T_ ) _/L_ . _Ch_ ( _T_ ) remains intensive in the TD limit, and we verified that our results do not change qualitatively starting from _L ≥_ 6 [52]. Thus, the emerging _logfidelity landscape h_ ( _t_ ) _�→ Ch_ ( _T_ ) corresponds to the control landscape for quantum state preparation [16, 53, 54] (Fig. 1c). The optimal protocol _h__∗_ ( _t_ ) is defined as the global minimum of the log-fidelity landscape. We divide the protocol duration _T_ = _δtNT_ into _NT_ steps of size _δt_ . We are interested in the properties of the control landscape in the large _NT_ limit. Motivated by Pontryagin’s maximum principle and the optimal control literature, we restrict the discussion to bang-bang protocols (Fig. 1.a) where the control field can take only the maximum allowed values _h_ ( _t_ ) _∈{±_ 4 _}_ at each time step [55, 56]. + +_Control landscape & sampling method.—_ In general, the control landscape _Ch_ ( _T_ ) is a non-convex functional of _h_ ( _t_ ): local minima obtained using a greedy optimization approach depend on the initial starting points of the algorithm. Using Stochastic Descent (SD) [52], we start from a random protocol and flip the sign of _h_ ( _jδt_ ) at _k_ different time steps _j_ 1 _, · · · , jk_ chosen uniformly at random (Fig.1b). A set of flips is accepted only if it decreases _Ch_ ( _T_ ). We repeat this process until a _local minimum_ is reached (see SI for psuedocode). A protocol _h_ ( _t_ ) is a SD _k_ local minimum if _all_ possible _k_ -flip updates increase the log-fidelity. We use SD _k_ algorithms with _k_ = 1, _k_ = 2 and _k_ =4 flips per local update. The best found fidelity _Fh_ ( _T_ ) as a function of protocol duration is presented in Fig. 2 (black line). + +_Order parameters measured.—_ The structure of the control landscape can be understood by measuring the protocol correlator and the number of unique local minima which we now define. Consider the set _S_ = _{h__α_ ( _t_ ) _}_ of all local log-fidelity minima. We sample _M_ protocols from _S_ using SD _k_ and denote _h_ ( _t_ ) _≡ M__−_1 �_M_ _α_ =1_hα_(_t_)as the sample average. Let us define the _protocol correlator_ : + + +![](.figures/arxiv__1803.10856/1803.10856.pdf-0002-11.png) + + +which is related to the Edwards-Anderson order parameter for replica symmetry breaking in spin-glasses [57– 59]. If the landscape is convex (unique minimum): _q_ =0, while if all the sampled local minima are uncorrelated: _q_ = 1. In collecting _M_ samples, we denote _M__⋆_ _≤ M_ as the number of distinct protocols. We further define the fraction of distinct local minima as + + +![](.figures/arxiv__1803.10856/1803.10856.pdf-0002-13.png) + + +For a fixed number of samples, this fraction is sensitive to drastic changes in the number of distinct local minima in _S_ . + +_Overconstrained and correlated phases.—_ The correlator _q_ SD1 as a function of the protocol duration _T_ is + +3 + + +![](.figures/arxiv__1803.10856/1803.10856.pdf-0003-01.png) + + + +Tc (1) Tc (2)
1 . 0
0 . 8
0 . 6
q SD1
0 . 4 f SD1
q SD2
0 . 2
f SD2
Fh
0 . 0
0 . 0 0 . 5 1 . 0 1 . 5 2 . 0 2 . 5 3 . 0 3 . 5 4 . 0
Protocol duration ( T )
+ + +![](.figures/arxiv__1803.10856/1803.10856.pdf-0003-02.png) + + +FIG. 3: (a)-(c) _t_ -SNE visualization of the control landscape above the SD2 glass critical point _Tc_(2) _≈_ 2 _._ 3. Each data point represents a local _Ch_ ( _T_ )-minimum – a bang-bang protocol embedded in a two-dimensional _t_ -SNE space. Embedded protocols are colored by their fidelity in the interval [ _F_ min, _F_ max] with intervals [0 _._ 919 _,_ 0 _._ 920], [0 _._ 958 _,_ 0 _._ 959], [0 _._ 992 _,_ 0 _._ 997] from (a) to (c). (a) & (b): The local minima cluster are separated by extensive barriers as seen in (d) & (e), the Hamming distance matrix for the local-minima protocols. distmax = 0.5, 0.52, 0.61 for (d), (e), (f) respectively. The protocols in the Hamming matrix are grouped by their cluster index found using density clustering (see SI). (c) At larger protocol duration ( _T_ = 3 _._ 4) large clusters fracture in an exponential number of small clusters. The small clusters are separated by extensive barriers (f). We used SD2 with _NT_ = 200, _L_ = 6 and sampled 5000 unique protocols. + +FIG. 2: Preparing states in a chain of qubits with optimal many-body fidelity _Fh_ ( _T_ ) (black) features transitions from an overconstrained phase (red region) to a correlated phase (blue region) to a glass-like phase (purple region) at protocol durations _Tc_(1) and _Tc_(2) . This is revealed by the non-zero fraction _f_ SD _k_ ( _T_ ) order parameter. We used _k_ -flip stochastic descent (SD _k_ ) on the family of bang-bang protocols with _NT_ = 200, _L_ = 6 and _M_ = 105 . + +shown in Fig. 2. For _T < Tc_(1) _≈_ 0 _._ 35, _f_ SD1 = 1 _/M_ , and the log-fidelity landscape is convex. While the maximum attainable fidelity is small, there exist a unique optimal protocol which is easy to find using SD1. At _T_ = _Tc_(1) , the control landscape undergoes a phase transition from an overconstrained phase ( _q_ SD1 =0, red region) to a correlated phase ( _q_ SD1 _>_ 0, blue region). This transition is characterized by a rapid increase of the number of quasidegenerate SD1 local minima as shown by _f_ SD1 reaching unity for _T > Tc_(1) . However, these local minima are all separated by barriers of width 2 in Hamming distance (number of sign flips required to connect them). This is revealed by using SD2 just above _Tc_(1) , for which _f_ SD2 = 1 _/M_ and _q_ SD2 = 0. At _T ≈_ 1 _._ 2, _q_ SD2 becomes non-zero, indicating the appearance of multiple SD2 local minima. However, the unique fraction of those minima, _f_ SD2, remains nearly zero. Remarkably, the control landscape undergoes another transition at _Tc_(2) _≈_ 2 _._ 3, characterized by a proliferation of SD2 local minima, where _f_ SD2 _∼O_ (1). + +_within_ a cluster are similar and connected by small barrier widths, protocols _between_ clusters are separated by barriers of width _extensive_ in _NT_ [52]. At longer protocol durations _T_ ≳ 3 _._ 0 (Fig. 3.c-f), the number of clusters appears to be exponential in _NT_ and all protocols are separated by extensive barriers (Fig. 3.f and see SI). The number of SD _k_ local minima is large, _f_ SD _k →_ 1, and we find that it scales exponentially with _NT_ [52]. Therefore, we expect that any local-flip algorithm (e.g. SD _k_ with _k_ subextensive in _NT_ ) will have exponential run-time for finding the global optimum. Having a landscape with an exponential number of minima separated by extensive barriers (in height and width) in the number of degrees of freedom is one of the landmarks of spin glasses, and leads to extremely slow mixing times [35]. + +This glassy control transition is analogous to replica symmetry breaking in spin glasses and random satisfiability problems [60, 61]. We verified that applying higherorder SD _k_ ( _k >_ 2) only slightly shifts the glass critical point to larger _T_ , as expected due to the presence of large and numerous barriers [52]. + +_Glassy phase.—_ To better understand the physics behind this SD2 glassy transition, we visualize the log-fidelity landscape using the nonlinear-manifold machine learning method _t_ -distributed stochastic neighborembedding ( _t_ -SNE) [49] (Fig. 3). _t_ -SNE embeddings preserve local ordination of data, and hence allow to understand the geometry of the control landscape. At _Tc_(2) , the geometry of the control landscape undergoes a drastic transition with the appearance of distinct clusters in the space of near-optimal protocols (Fig. 3 and SI for clustering procedure). Each cluster corresponds to a distinct region of closely related SD2 minima. While protocols + +_Effective Classical Model.—_ To further evidence the glassy character of the phase, we map the control problem to an effective classical Ising model _H_ eff ( _T_ ), which governs the control landscape phase transitions. By studying its properties, we establish a closer connection with spinglasses. Similar to classical Ising-type models, in which each spin configuration comes with its energy, we assign + +4 + + +![](.figures/arxiv__1803.10856/1803.10856.pdf-0004-01.png) + + + +15
to every bang-bang protocol the log-fidelity Chh ( T ) of be-
in the target state (Fig. 1.d)..d). From the set of all 10 0 T = 2 . 00
h ( T ) values, which we refer to as the log-fidelity ‘spec-
trum’, we reconstruct an effective classical spin model: 10 − 1 10
Mh = 0
NT 1 NT 10 − 2 M h = 2
H eff ( T ) = C 0( T ) + � j =1 Gj ( T ) hj + NT � i = j Jij ( T ) hihj 10 − 3 5
NT 10 − 4
+ N 1 T 2 i =� j = k Kijk ( T ) hjhjhk + . . . . (4) 10 1 0
T = 2 . 80 4
Here the couplings Gjj , Jijij , Kijkijk , which can be uniquely 10 0
computed by tracing over all 2 NTT possible protocol con- 3
figurations [52], encode all the information about the con-52], encode all the information about the con-], encode all the information about the con- 10 − 1 M h = 0
trol landscape [52].52].]. 10 − 2 Mh = 2 2
For T > T 1 ( c ), we find that the effective two-body inter-, we find that the effective two-body inter-
action Jijij (which is non-local and antiferromagnetic) and 10 − 3 1
one-body interaction compete, resulting in H eff ( T ) 10 − 4
0
being highly frustrated, i.e. a large fraction of the Jijij 0 . 0 0 . 2 0 . 4 0 . 6 0 . 8 1 . 0
bonds are unsatisfied in the ground-state[52].52].]. For larger 1 − Fh ( T )
protocols)(all (excitations)
DOS DOS
protocols)(all (excitations)
DOS DOS
+ +to every bang-bang protocol the log-fidelity _Chh_ ( _T_ ) of being in the target state (Fig. 1.d)..d). From the set of all _Ch_ ( _T_ ) values, which we refer to as the log-fidelity ‘spectrum’, we reconstruct an effective classical spin model: + +Here the couplings _Gjj_ , _Jijij_ , _Kijkijk_ , which can be uniquely computed by tracing over all 2_NTT_ possible protocol configurations [52], encode all the information about the con-52], encode all the information about the con-], encode all the information about the control landscape [52].52].]. + +For _T > T_ 1(_c_), we find that the effective two-body inter-, we find that the effective two-body inter- action _Jijij_ (which is non-local and antiferromagnetic) and the one-body interaction compete, resulting in _H_ eff ( _T_ ) being highly frustrated, i.e. a large fraction of the _Jijij_ bonds are unsatisfied in the ground-state[52].52].]. For larger times, higher-order (and possibly all) nonlocal multibody spin interactions in _H_ eff ( _T_ ) are required to reliably capture the behaviour of the system in the glassy phase. We present further evidence for these claims using an independent procedure for learning couplings based on the RIDGE algorithm for sparse linear regression [52, 62, 63]. The long-range and multi-body nature of the couplings is related to the dynamic origin of the state preparation problem: causality imposes that the value of the low- _Ch_ ( _T_ ) protocols at time _t_ is correlated with the values at all previous times _t__′_ _< t_ in the bang-bang sequence. + +FIG. 4: Normalized density of states (DOS) of _H_ eff (black line, left _y_ -axis), and the distribution of the _Mh_ = 0 and _Mh_ = 2-magnetized excitations (shaded, right _y_ -axis) on both sides of the glass critical point _Tc_(2) _≈_ 2 _._ 3 for _NT_ = 80 _, L_ = 6. The position of the best obtained fidelity using SD4 is marked by the vertical dashed line. + +We explicitly verified that this behavior holds using exact numerical computation of all protocol fidelities up to _NT ≤_ 28 [52]; + +_Density of states.—_ In order to understand the underlying causes for the glassy phase, we examine the density of states [i.e. protocols] of _H_ eff ( _T_ ) (DOS), obtained by counting protocols in a small fidelity window [Fig. 4, black line, left axis]. Starting from a protocol _h__∗_ with near-optimal fidelity (i.e. a low-energy local minimum of _H_ eff ( _T_ )), we analyze the behaviour of elementary excitations (Fig. 1d), by computing the fidelity of all possible protocols obtained after flipping 1, 2 and 4 bangs in _h__∗_ . These excitations can be classified by their ‘magnetization’ _Mh_ = _j_(_hj −h_ _j__∗_)relativetothenear-optimal protocol. Below the SD2 glass transition, _T (2) _≈_ 2 _._ 3, the bulk of the excitations (shaded area, right axis) is located in a region where the DOS is much smaller than the typical DOS. Therefore, when searching for the optimal protocol, starting from an initial protocol with large log-fidelity, finding one of the elementary excitations is relatively easy since most of these excitations are in a region of extremely small DOS (w.r.t to the typical DOS). In contrast, for _T >Tc_(2) in the glassy phase, the bulk of the excitations moves to a region where the DOS is large. This implies that if we miss one of the elementary excitations in the search for a better protocol, it becomes infeasible to reach _h__∗_ . From an algorithmic perspective, this suggests a transition from a sub-exponential complexity to an at least exponential complexity in _NT_ . + +_Outlook/Discussion.—_ Studying the properties of the control landscape, we provided compelling evidence for the existence of a glass-like phase in optimal ground state manipulation of constrained quantum systems. Using _t_ - SNE we were able to reveal the complex geometry of the high-dimensional control landscape, which features multiple clusters separated by extensive barriers. We mapped this out-of-equilibrium problem to an effective classical Ising model with non-local and frustrated multibody interactions, resulting in a complicated optimal protocol configuration. Further, applying ideas from condensed matter physics to reveal the microscopic origin of the putative glassy control phase, we analyzed the behaviour of the DOS in protocol space of the distribution of local elementary excitations above the low log-fidelity manifold. Our analysis suggest that the state preparation paradigm in nonintegrable many-body systems belongs to the class of NP-hard problems, with the optimal protocol becoming exponentially hard to find in the glass phase. + +The approach outlined in this work has the potential to further the understanding of quantum dynamics away from equilibrium. It generalizes to control problems beyond state preparation, for instance minimizing work fluctuations [64], and highlights the application of machine learning and glass-physics methods to quantum + +5 + +control tasks. + +_Acknowlegements.—_ We thank A. Polkovnikov, C. Laumann and C. Baldwin for illuminating discussions. AD was supported by a NSERC PGS-D scholarship. AD and PM acknowledge support from Simon’s Foundation through the MMLS Fellow program. M.B. was supported by the Emergent Phenomena in Quantum Systems initiative of the Gordon and Betty Moore Foundation, the ERC synergy grant UQUAM, and the U.S. Department of Energy, Office of Science, Office of Advanced Scientific Computing Research, Quantum Algorithm Teams Pro- + +- [1] M. G. Bason, M. Viteau, N. Malossi, P. Huillery, E. Arimondo, D. Ciampini, R. Fazio, V. Giovannetti, R. Mannella, and O. Morsch, Nature Physics **8** , 147 (2012). + +- [2] S. van Frank, M. Bonneau, J. Schmiedmayer, S. Hild, C. Gross, M. Cheneau, I. Bloch, T. Pichler, A. Negretti, T. Calarco, _et al._ , Scientific reports **6** (2016). + +- [3] P. B. Wigley, P. J. Everitt, A. van den Hengel, J. Bastian, M. A. Sooriyabandara, G. D. McDonald, K. S. Hardman, C. Quinlivan, P. Manju, C. C. Kuhn, _et al._ , Scientific reports **6** (2016). + +- [4] R. Islam, E. E. Edwards, K. Kim, S. Korenblit, C. Noh, H. Carmichael, G.-D. Lin, L.-M. Duan, C.-C. Joseph Wang, J. K. Freericks, and C. Monroe, Nature Communications **2** , 377 EP (2011), article. + +- [5] C. Senko, P. Richerme, J. Smith, A. Lee, I. Cohen, A. Retzker, and C. Monroe, Phys. Rev. X **5** , 021026 (2015). + +- [6] P. Jurcevic, B. P. Lanyon, P. Hauke, C. Hempel, P. Zoller, R. Blatt, and C. F. Roos, Nature **511** , 202 (2014), letter. + +- [7] R. Barends, A. Shabani, L. Lamata, J. Kelly, A. Mezzacapo, U. Las Heras, R. Babbush, A. Fowler, B. Campbell, Y. Chen, _et al._ , Nature **534** , 222 (2016). + +- [8] B. B. Zhou, A. Baksic, H. Ribeiro, C. G. Yale, F. J. Heremans, P. C. Jerger, A. Auer, G. Burkard, A. A. Clerk, and D. D. Awschalom, Nat Phys **13** , 330 (2017), letter. + +- [9] M. A. Nielsen and I. Chuang, _Quantum computation and quantum information_ (AAPT, 2002). + +- [10] M. Kolodrubetz, D. Sels, P. Mehta, and A. Polkovnikov, Physics Reports , (2017). + +- [11] M. Demirplak and S. A. Rice, The Journal of Physical Chemistry A **107** , 9937 (2003). + +- [12] A. del Campo, Phys. Rev. Lett. **111** , 100502 (2013). + +- [13] C. Jarzynski, Phys. Rev. A **88** , 040101 (2013). + +- [14] D. Sels and A. Polkovnikov, Proceedings of the National Academy of Sciences **114** , E3909 (2017). + +- [15] M. Bukov, D. Sels, and A. Polkovnikov, arXiv:1804.05399 (2018). + +- [16] S. J. Glaser, T. Schulte-Herbr¨uggen, M. Sieveking, O. Schedletzky, N. C. Nielsen, O. W. Sørensen, and C. Griesinger, Science **280** , 421 (1998). + +- [17] N. Khaneja, T. Reiss, C. Kehlet, T. Schulte-Herbrueggen, and S. J. Glaser, Journal of Magnetic Resonance **172** , 296 (2005). + +- [18] T. Caneva, T. Calarco, and S. Montangero, Phys. Rev. A **84** , 022326 (2011). + +- [19] R. S. Judson and H. Rabitz, Phys. Rev. Lett. **68** , 1500 (1992). + +gram. DS acknowledges support of the FWO as postdoctoral fellow of the Research Foundation - Flanders. This research was supported in part by the National Science Foundation under Grant No. NSF PHY-1748958. We used Quspin for simulating the dynamics of the qubit system [65, 66]. The authors are pleased to acknowledge that the computational work reported on in this paper was performed on the Shared Computing Cluster which is administered by Boston University’s Research Computing Services. + +- [20] C. Chen, D. Dong, R. Long, I. R. Petersen, and H. A. Rabitz, Phys. Rev. A **89** , 023402 (2014). + +- [21] C. Chen, D. Dong, H.-X. Li, J. Chu, and T.-J. Tarn, IEEE transactions on neural networks and learning systems **25** , 920 (2014). + +- [22] M. Bukov, A. G. R. Day, D. Sels, P. Weinberg, A. Polkovnikov, and P. Mehta, Phys. Rev. X **8** , 031086 (2018). + +- [23] X.-C. Yang, M.-H. Yung, and X. Wang, arXiv preprint arXiv:1708.00238 (2017). + +- [24] V. Dunjko and H. J. Briegel, arXiv preprint arXiv:1709.02779 (2017). + +- [25] M. August and J. M. Hern´andez-Lobato, “Taking gradients through experiments: Lstms and memory proximal policy optimization for black-box quantum control,” (2018), arXiv:1802.04063 . + +- [26] T. F¨osel, P. Tighineanu, T. Weiss, and F. Marquardt, arXiv:1802.05267 (2018). + +- [27] J. J. Sorensen, M. Aranburu, T. Heinzel, and J. Sherson, arXiv:1802.07521 (2018). + +- [28] X.-M. Zhang, Z.-W. Cui, X. Wang, and M.-H. Yung, arXiv preprint arXiv:1802.09248 (2018). + +- [29] M. Y. Niu, S. Boixo, V. Smelyanskiy, and H. Neven, arXiv preprint arXiv:1803.01857 (2018). + +- [30] F. Albarran-Arriagada, J. C. Retamal, E. Solano, and L. Lamata, arXiv:1803.05340 (2018). + +- [31] M. Bukov, arXiv preprint arXiv:1808.08910 (2018). + +- [32] M. Bukov, A. G. R. Day, P. Weinberg, A. Polkovnikov, P. Mehta, and D. Sels, Phys. Rev. A **97** , 052114 (2018). + +- [33] M. Larocca, P. Poggi, and D. Wisniacki, arXiv:1802.05683 (2018). + +- [34] M. M´ezard, G. Parisi, and M. Virasoro, _Spin glass theory and beyond: An Introduction to the Replica Method and Its Applications_ , Vol. 9 (World Scientific Publishing Company, 1987). + +- [35] H. Nishimori, _Statistical physics of spin glasses and information processing: an introduction_ , Vol. 111 (Clarendon Press, 2001). + +- [36] D. Hu, P. Ronhovde, and Z. Nussinov, Philosophical Magazine **92** , 406 (2012). + +- [37] V. Ros, G. Biroli, and C. Cammarota, arXiv preprint arXiv:1809.05440 (2018). + +- [38] R. Monasson and R. Zecchina, Phys. Rev. E **56** , 1357 (1997). + +- [39] M. Leone, F. Ricci-Tersenghi, and R. Zecchina, Journal of Physics A: Mathematical and General **34** , 4615 (2001). + +- [40] L. Zdeborov´a, Acta Physica Slovaca **59** , 169 (2009). + +6 + +- [41] K. Klemm, A. Mehta, and P. F. Stadler, PloS one **7** , e34780 (2012). + +- [42] F. Ricci-Tersenghi, M. Weigt, and R. Zecchina, Phys. Rev. E **63** , 026702 (2001). + +- [43] M. M´ezard, G. Parisi, and R. Zecchina, Science **297** , 812 (2002). + +- [44] M. M´ezard, in _International Conference on Theoretical Physics_ (Springer, 2003) pp. 475–488. + +- [45] D. Battaglia, M. Kol´aˇr, and R. Zecchina, Phys. Rev. E **70** , 036107 (2004). + +- [46] M. M´ezard, M. Palassini, and O. Rivoire, Phys. Rev. Lett. **95** , 200202 (2005). + +- [47] D. Hu, P. Ronhovde, and Z. Nussinov, Physical Review E **86** , 066106 (2012). + +- [48] S. C. Morampudi, B. Hsu, S. L. Sondhi, R. Moessner, and C. R. Laumann, Phys. Rev. A **96** , 042303 (2017). + +- [49] L. v. d. Maaten and G. Hinton, Journal of machine learning research **9** , 2579 (2008). + +- [50] V. Jurdjevic and H. J. Sussmann, Journal of Differential equations **12** , 313 (1972). + +- [51] T. Caneva, A. Silva, R. Fazio, S. Lloyd, T. Calarco, and S. Montangero, Phys. Rev. A **89** , 042322 (2014). + +- [52] See Supplemental Material. + +- [53] H. A. Rabitz, M. M. Hsieh, and C. M. Rosenthal, Science **303** , 1998 (2004). + +- [54] K. W. Moore and H. Rabitz, The Journal of chemical physics **137** , 134113 (2012). + +- [55] Z.-C. Yang, A. Rahmani, A. Shabani, H. Neven, and C. Chamon, Phys. Rev. X **7** , 021027 (2017). + +A **97** , 062343 (2018). + + - [57] G. Parisi, Physical Review Letters **50** , 1946 (1983). + + - [58] T. Castellani and A. Cavagna, Journal of Statistical Mechanics: Theory and Experiment **2005** , P05012 (2005). + + - [59] L. O. Hedges, R. L. Jack, J. P. Garrahan, and D. Chandler, Science **323** , 1309 (2009). + + - [60] M. Mezard and A. Montanari, _Information, physics, and computation_ (Oxford University Press, 2009). + + - [61] C. Moore and S. Mertens, _The nature of computation_ (OUP Oxford, 2011). + + - [62] P. Mehta, M. Bukov, C. H. Wang, A. G. R. Day, C. Richardson, C. K. Fisher, and D. J. Schwab, arXiv:1803.08823 (2018). + + - [63] J. Friedman, T. Hastie, and R. Tibshirani, _The elements of statistical learning_ , Vol. 1 (Springer series in statistics New York, 2001). + + - [64] A. P. Solon and J. M. Horowitz, arXiv preprint arXiv:1712.05816 (2017). + + - [65] P. Weinberg and M. Bukov, SciPost Phys. **2** , 003 (2017). + + - [66] P. Weinberg and M. Bukov, arXiv preprint arXiv:1804.06782 (2018). + + - [67] A. Rodriguez and A. Laio, Science **344** , 1492 (2014). + + - [68] M. Bukov, A. G. R. Day, D. Sels, P. Weinberg, A. Polkovnikov, and P. Mehta, Phys. Rev. X **8** , 031086 (2018). + + - [69] H. C. Nguyen, R. Zecchina, and J. Berg, Advances in Physics **66** , 197 (2017). + + - [70] H. Fujita, Y. O. Nakagawa, S. Sugiura, and M. Oshikawa, Phys. Rev. B **97** , 075114 (2018). + +- [56] S. Bao, S. Kleer, R. Wang, and A. Rahmani, Phys. Rev. + +7 + +# **Supplemental Material** + +### **I. STOCHASTIC DESCENT** + +Here we provide more details on the stochastic descent algorithms used to sample the local minima of the control landscape (see Algorithm 1 for a pseudocode of SD _k_ ). As mentioned in the main text, SD _k_ is a _k_ -flip stochastic descent where, starting from an initial random protocol, the algorithm proposes a new protocol chosen uniformly at random that differs by at most _k_ -flips from the previous protocol. If the proposed protocol fidelity is higher than that of the previous protocol, the update is accepted. The SD _k_ algorithm halts when all possible updates with at most _k_ -flips decrease the fidelity. The obtained protocol is said to be a local minimum of the (negative) log-fidelity landscape: all _k_ -flip perturbations from that minimum will increase the log-fidelity. For _NT_ bangs and _k_ = 4 (the most computationally intensive algorithm we ran), the number of fidelity evaluations needed to certify that a protocol is a local minimum is � _N_ 4 _T_ �+� _N_ 3 _T_ �+� _N_ 2 _T_ �+� _N_ 1 _T_ �. For _NT_ = 80 (the largest _NT_ we ran for SD4), sampling a _single_ local minimum required computing the fidelity of _O_ (107 ) protocols (see Fig. 5). Note that evaluating the fidelity of a single bang-bang protocol with _NT_ time steps requires the multiplication of _NT_ unitaries of size 2_L_ . Since this is the bottleneck in the algorithm run-time we optimized the fidelity evaluation by taking into account symmetries of the qubit Hamiltonian (Eq. 1 of main text), such as translation and reflection (parity), and precomputing and storing in memory a subset of the products of unitaries. + +**Algorithm 1** Stochastic descent (SD _k_ ) + +- 1: **Input:** _NT_ , _T_ , _k_ + +2: **Routines:** FindAllUpdate, RandomShuffle, UpdateProtocol, Fid + +3: _initialize:_ 4: **h** _old ←{h_ (1) _, h_ ( _δt_ ) _, · · · h_ ( _NT δt_ ) _} ∼{−_ 4 _,_ 4 _}__NT_ _▷_ Initialize protocol at random 5: ListOfAllUpdates _←_ FindAllUpdate( _NT_ , _k_ ) _▷_ Finds the list of updates with at most _k_ -flips 6: _shuffle:_ 7: ListOfAllUpdates _←_ RandomShuffle(ListOfAllUpdates) _▷_ Shuffle updates in a random order 8: **for** _update_ **in** ListOfAllUpdates **do** _▷_ Iterate over all possible update 9: **h** _new ←_ UpdateProtocol( **h** _old, update_ ) _▷_ Update protocol given the specified update 10: **if** Fid( **h** _new_ ) _>_ Fid( **h** _old_ ) **then** _▷_ Evaluates the fidelity of each protocol and compares them 11: **h** _old ←_ **h** _new_ 12: **goto** _shuffle ▷_ If update accepted, then restart for loop **return h** _old_ + + +![](.figures/arxiv__1803.10856/1803.10856.pdf-0007-08.png) + + + +10 7
10 6
10 5
10 4
10 3
10 2 SDSD12
SD4
10 1
0 1 2 3 4
T

eval
n

+ +FIG. 5: Average number of protocol fidelity evaluation per stochastic descent run required to reach a local minimum of the log-fidelity landscape as a function of the protocol duration _T_ . This is computed for _NT_ = 80 and _L_ = 6 with _O_ (105 ) samples for each time and SD _k_ . + +8 + +### **II. ALGORITHMIC COMPLEXITY AND SCALING OF THE NUMBER OF LOCAL MINIMA** + +In order to verify the exponential nature of the glassy phase (i.e. verify whether there are exponentially many local minima in the glassy phase), we measure the algorithmic complexity scaling of the stochastic descent algorithms used (SD _k_ , _k_ = 1 _,_ 2 _,_ 4), see Fig. 6. Specifically, we are interested in the computational effort required by SD _k_ to find the optimal protocol as a function of _NT_ . In order to certify the optimal fidelity protocol, we performed a brute-force search over all possible 2_NT_ protocols. For this reason (but also because the complexity (see below) is exponentially hard to measure in the glassy phase) we were limited to effective system sizes of _NT ≤_ 28. + +The algorithmic complexity of stochastic descent is measured by estimating the number fidelity evaluations required on _average_ to locate the optimal protocol with high-probability (w.h.p.). Thus, we define the complexity as the expected number of fidelity evaluations per stochastic descent run, _⟨n_ eval _⟩_ , _multiplied_ by the number of random initializations needed until on average one stochastic descent local minimum corresponds to the global minimum w.h.p.: + + +![](.figures/arxiv__1803.10856/1803.10856.pdf-0008-04.png) + + +Here, _p_ ( _h_ ( _t_ ) = _h__∗_ ( _t_ )) is the probability that the local minimum found for a _single_ stochastic descent run corresponds to the global minimum. Importantly, we expect that the scaling of the complexity _for stochastic descent_ also reveals whether the number of local minima is exponential or sub-exponential in _NT_ (see Fig. 6). The results are shown Fig. 6) and were computed using sampling (hence the error bars) due to computational limitations. Note that the “ruggedness” exhibited by the curves in Fig. 6) is mostly due to finite size effects (as opposed to sampling noise). The ”ruggedness” increases with _k_ but also with _T_ . + + +![](.figures/arxiv__1803.10856/1803.10856.pdf-0008-06.png) + + + +4.00
20 a) 17 . 5 b) 16 c) 3.60
3.20
14
15 . 0
2.80
15
12 . 5 12 2.40
2.00
10 10 . 0 10 1.60
1.20
7 . 5
8
0.80
5
5 . 0 0.40
6
10 14 18 22 26 30 10 14 18 22 26 30 1 0 14 18 22 26 30 0.00
NT NT NT
)
( Clog T
+ +FIG. 6: The log-complexity (see Eq. (5)) of finding the optimal fidelity protocol as a function of the effective system size _NT_ for various protocol durations _T ∈_ [0 _._ 1 _,_ 0 _._ 3 _, · · · ,_ 3 _._ 9] (see color bar). Error bars are shown for every data point. (a), (b), (c) : complexity for SD1, SD2, SD4 respectively. The value _Tc_(_k_) at which the logarithm of the complexity goes from a logarithmic to a linear scaling in _NT_ indicates the transition to a rugged landscape with exponentially many local minima. In the main text, the _Tc_(_k_) was defined as the point where _f_ SD _k ≈_ 0 _._ 5: we found _Tc_(1) _≈_ 0 _._ 35, _Tc_(2) _≈_ 2 _._ 3, _Tc_(4) _≈_ 2 _._ 5. + +### **III.** _t_ **-SNE: T-DISTRIBUTED STOCHASTIC NEIGHBOR EMBEDDING** + +_t_ -SNE [49] is a non-parametric dimensional reduction method that can be used to effectively embed high-dimensional data in a two-dimensional space. The _t_ -SNE embedding is obtained by minimizing a cost function which emphasizes the conservation of local ordination (short-distance information) of the data while downplaying long-distance information. In this work, we used _t_ -SNE with a perplexity of 60 and a Barnes-Hut angle of 0.5. We computed multiple _t_ -SNE runs to make sure that the results were insensitive (up to global rotations) to the random seed. + +From the _t_ -SNE results, we used density clustering (similar to DBSCAN) in order to accurately identify the clusters. Density clustering does not require to specify the number of clusters as would be the case using _K_ -means for instance. The explicit clusters found using density clustering are shown in Fig. 7. In order to further corroborate the presence of clusters, we computed the mean inter-distance (in the original space of protocols) for every pair of protocols in between the clusters found (see Fig. 8). + +9 + +We verified that the clusters found by _t_ -SNE are indeed separated by extensive barriers by computing the Hamming distance matrix of the sampled protocols (see Fig. 3 of the main text and Fig. 9). Note that the scales (axis values) in _t_ -SNE maps can sometimes be misleading and one should NOT compare the scales _in between t_ -SNE plots. In the main text, Fig. 3.c, while it may seem that all protocols (local minima) are closeby, computing the exact pairwise distances (see Fig. 3.d-f) reveals that in fact all protocols are almost maximally distant (the distribution is peaked at 0.5). Indeed, if two protocols are drawn at uniformly at random, they will on average be separated by a ( _NT_ normalized) Hamming distance of 0.5. This can be seen by inspecting the Hamming distance matrix in Fig. 3.f. In Fig. 9 we plot the distribution of the pairwise distances for the distance matrices presented in the main text. + +Last, note that many software packages that implement _t_ -SNE in different programming languages have been written and we refer the enthusiastic reader to consult `https://lvdmaaten.github.io` for more information and for the version that we used. We implemented an easy-to-use Python wrapper available on `https://github.com/ alexandreday/tsne_visual` . For a more detailed discussion about the use of _t_ -SNE we encourage the reader to consult Ref. [62]. + + +![](.figures/arxiv__1803.10856/1803.10856.pdf-0009-03.png) + + + +![](.figures/arxiv__1803.10856/1803.10856.pdf-0009-04.png) + + + +![](.figures/arxiv__1803.10856/1803.10856.pdf-0009-05.png) + + + +![](.figures/arxiv__1803.10856/1803.10856.pdf-0009-06.png) + + + +![](.figures/arxiv__1803.10856/1803.10856.pdf-0009-07.png) + + + +![](.figures/arxiv__1803.10856/1803.10856.pdf-0009-08.png) + + + +![](.figures/arxiv__1803.10856/1803.10856.pdf-0009-09.png) + + + +![](.figures/arxiv__1803.10856/1803.10856.pdf-0009-10.png) + + + +![](.figures/arxiv__1803.10856/1803.10856.pdf-0009-11.png) + + + +![](.figures/arxiv__1803.10856/1803.10856.pdf-0009-12.png) + + + +![](.figures/arxiv__1803.10856/1803.10856.pdf-0009-13.png) + + + +![](.figures/arxiv__1803.10856/1803.10856.pdf-0009-14.png) + + + +![](.figures/arxiv__1803.10856/1803.10856.pdf-0009-15.png) + + + +![](.figures/arxiv__1803.10856/1803.10856.pdf-0009-16.png) + + + +![](.figures/arxiv__1803.10856/1803.10856.pdf-0009-17.png) + + +FIG. 7: Density clustering of the _t_ -SNE embedding. We used a modern density clustering based approach based on [67] and available on https://github.com/alexandreday/fast ~~d~~ ensity ~~c~~ lustering. Each protocol corresponds to a point on the 2D _t_ -SNE embedding and is assigned a cluster label by the clustering algorithm. Protocols belonging to the same cluster were grouped together in Fig. 3 of the main text. _NT_ = 200 _, L_ = 6 and we sampled 5000 unique protocols using SD2. + + +![](.figures/arxiv__1803.10856/1803.10856.pdf-0009-19.png) + + + +T= 2.3 T= 2.5
0 0
1 0 . 4 1
2 0 . 35
2
3
4 0 . 3 3 0 . 30
5 4
6 5
7 0 . 2 6 0 . 25
8
7
9 0 . 20
10 8
0 . 1
11 9
0 1 2 3 4 5 6 7 8 9 1011 0 1 2 3 4 5 6 7 8 9
Cluster label Cluster label
T T
N N
/ /
label label
Cluster inter-distance Cluster inter-distance
Mean Mean
+ +FIG. 8: Mean inter-distance for each pair of clusters found and labelled in Fig. 7: for every pair of protocols within two distinct clusters, we compute the Hamming distance between. The mean inter-distance is then obtained by averaging over all protocol pairs. _NT_ = 200 _, L_ = 6 and we sampled 5000 unique protocols using SD2. + +### **IV. FINITE-SIZE SCALING OF THE DENSITY OF STATES AND ELEMENTARY EXCITATIONS** + +Phase transitions appear in the TD limit, which we can also define for _H_ eff ( _T_ ): to add more degrees of freedom to the classical model, we increase the number of bangs _NT →∞_ , which requires sending _δt →_ 0 to keep the protocol duration _T_ = _NT δt_ fixed. We refer to the thermodynamic limit of the effective model _H_ eff ( _T_ ) as the ‘continuum limit’, to distinguish it from the TD limit of the physical quantum many-body system _L →∞_ . + +10 + + +![](.figures/arxiv__1803.10856/1803.10856.pdf-0010-01.png) + + + +T = 2 . 30 T = 2 . 50 T = 2 . 80 T = 3 . 40
6
5 6 8
5
4
4 6
4
3
3
4
2
2 2
1 1 2
0 0 0 0
0 . 0 0 . 2 0 . 4 0 . 6 0 . 8 1 . 0 0 . 0 0 . 2 0 . 4 0 . 6 0 . 8 1 . 0 0 . 0 0 . 2 0 . 4 0 . 6 0 . 8 1 . 0 0 . 0 0 . 2 0 . 4 0 . 6 0 . 8 1 . 0
Hamming distance / NT Hamming distance / NT Hamming distance / NT Hamming distance / NT
density
Probability
+ +FIG. 9: Distribution of the pairwise Hamming distances for _M >_ 10000 sampled local minima protocols using SD2 at protocol durations _T_ = 2 _._ 3 _,_ 2 _._ 5 _,_ 2 _._ 8 _,_ 3 _._ 4 with _NT_ = 200 and _L_ = 6. At 2 _._ 3 _≈ Tc_(2) ≲ _T_ ≲ 2 _._ 55, the distribution is bi-modal, indicating the presence of well-separated clusters : there is a small number of clusters with high entropy of protocols that are connected by small barriers. At longer protocol duration times, tightly concentrated clusters fracture into an exponential number of clusters (with a low-entropy) separated by extensive barriers. The latter is seen from the fact that the mode of the distribution approaches 0.5. + +Figure 10 shows the finite-size scaling of the normalized density of states and the elementary excitations on top of the optimal protocol _h__∗_ _j_as we vary the physical system size_L_.Note that the log-fidelity log_Fh_(_T_)_∼L_is an extensive quantity. Therefore, to carry out the scaling, we consider + + +![](.figures/arxiv__1803.10856/1803.10856.pdf-0010-04.png) + + +which remains finite in the limit _L →∞_ . The plots were generated by computing the fidelity for all 2_NT_ protocols for _NT_ = 28. The collapse of the curves suggests that the physics of the optimization problem is close to the TD limit. This is consistent with similar results obtained in Ref. [68]. + + +![](.figures/arxiv__1803.10856/1803.10856.pdf-0010-06.png) + + + +(a) (b) (c)
3 L = 6 L = 6 1 . 0 L = 6
L = 8 L = 8 0 . 8 L = 8
1 . 0
2 L = 10 L = 10 L = 10
L = 12 L = 12 0 . 6 L = 12
0 . 5 0 . 4
1
T = 2 . 0 T = 3 . 0 0 . 2 T = 4 . 0
0 0 . 0 0 . 0
0 1 2 3 0 1 2 3 4 0 1 2 3 4
−L − 1 log Fh ( T ) −L − 1 log Fh ( T ) −L − 1 log Fh ( T )
P P P
+ +FIG. 10: Finite-size scaling of the normalized DOS as a function of the number of qubits _L_ , shows the system is close to the thermodynamic limit _L →∞_ . The positions of the local elementary excitations on top of the optimal protocol w.r.t. the fidelity axis are marked in circles for 1-flip excitations (lower batch) and vertical bars for 2-flip excitations (upper batch), respectively. The position of the excitations along the _y_ -axis carries no meaning. All plots are based on the exact computation of the fidelity for all 2_NT_ protocols for _NT_ = 28. + +Figure 11 shows the scaling of the normalized density of states against the number of bangs _NT_ , i.e. the system size of the effective model _H_ eff ( _T_ ). These results are computed using the full set of 2_NT_ protocols for _NT ≤_ 28. While the results presented are exact for these value of _NT_ , because we are limited by computational bottlenecks to _NT ≤_ 28, the results display some finite-size effects in the log-fidelity of the elementary excitations. In order to access greater system sizes (in _NT_ ), we used stochastic descent (to find good fidelity protocols) along with uniform sampling of the protocols (to compute the black curve in Fig. 4 of the main text). Finally, we remark that the shape of the DOS responds only weakly to increasing _NT_ . + +11 + + +![](.figures/arxiv__1803.10856/1803.10856.pdf-0011-01.png) + + + +(a) (b) (c)
10 2 10 2 10 2
T = 1 . 5 T = 2 . 1 T = 2 . 7
10 0 10 0 10 0
10 − 2 10 − 2 10 − 2
NT = 20 NT = 26 NT = 20 NT = 26 NT = 20 NT = 26
10 − 4 NT = 22 NT = 28 10 − 4 NT = 22 NT = 28 10 − 4 NT = 22 NT = 28
NT = 24 NT = 24 NT = 24
0 . 6 0 . 7 0 . 8 0 . 9 1 . 0 0 . 4 0 . 6 0 . 8 1 . 0 0 . 25 0 . 50 0 . 75 1 . 00
1 − Fh ( T ) 1 − Fh ( T ) 1 − Fh ( T )
P P P
+ +FIG. 11: Finite-size scaling of the normalized DOS as a function of the number of bangs _NT_ . The positions of the local elementary excitations on top of the optimal protocol w.r.t. the fidelity axis are marked in circles (1-flip) and vertical bars (2-flip), respectively. The position of the excitations along the _y_ -axis carries no meaning. The DOS data is obtained from fits of histograms, hence the noise. + + +![](.figures/arxiv__1803.10856/1803.10856.pdf-0011-03.png) + + + +1 . 0 1 . 0 1 . 0
a) b) c)
0 . 8 0 . 8 0 . 8
0 . 6 0 . 6 0 . 6
0 . 4 0 . 4 0 . 4
NT = 40 NT = 40 NT = 40
0 . 2 N T = 50 0 . 2 NT = 50 0 . 2 NT = 50
NT = 60 NT = 60 NT = 60
NT = 70 NT = 70 NT = 70
0 . 0 NT = 80 0 . 0 NT = 80 0 . 0 NT = 80
0 1 2 3 4 0 1 2 3 4 0 1 2 3 4
T T T
SD1 SD2 SD4
q q q
+ +FIG. 12: Correlator ( _q_ ( _T_ )) scaling with the effective system size _NT ∈{_ 40 _,_ 50 _,_ 60 _,_ 70 _,_ 80 _}_ as a function of the protocol duration _T_ . (a)-(b)-(c) : the correlator for SD1, SD2 and SD4 respectively. Here we used _L_ = 6. The correlation is computed from the sampled protocols that have a fidelity greater than 95% of the best encountered protocol fidelity over the whole sampling at a fixed _T_ . We sampled at least 10000 protocols for every _NT_ and protocol duration _T_ . + + +![](.figures/arxiv__1803.10856/1803.10856.pdf-0011-05.png) + + + +1 . 0 a) 1 . 0 b) 1 . 0 c)
0 . 8 0 . 8 0 . 8
0 . 6 0 . 6 0 . 6
0 . 4 0 . 4 0 . 4
NT = 40 NT = 40 NT = 40
0 . 2 NT = 50 0 . 2 NT = 50 0 . 2 NT = 50
NT = 60 NT = 60 NT = 60
NT = 70 NT = 70 NT = 70
0 . 0 NT = 80 0 . 0 NT = 80 0 . 0 NT = 80
0 1 2 3 4 0 1 2 3 4 0 1 2 3 4
T T T
SD1 SD2 SD4
f f f
+ +FIG. 13: Local minima fraction ( _f_ ( _T_ )) scaling with the effective system size _NT ∈{_ 40 _,_ 50 _,_ 60 _,_ 70 _,_ 80 _}_ as a function of the protocol duration _T_ . (a)-(b)-(c) : the correlator for SD1, SD2 and SD4 respectively. Here we used _L_ = 6. The correlation is computed from the sampled protocols that have a fidelity greater than 95% of the best encountered protocol fidelity over the whole sampling at a fixed _T_ . We sampled at least 10000 protocols for every _NT_ and protocol duration _T_ + +### **V. FINITE-SIZE SCALING OF THE ORDER PARAMETERS** _q_ ( _T_ ) **AND** _f_ ( _T_ ) **.** + +### **VI. THE EFFECTIVE CLASSICAL SPIN MODEL** + +The quantum state preparation problem is an optimisation task which, over the space of bang-bang protocols, is equivalent to finding the ground state of a classical spin model. As we discussed in the main text, each bang-bang protocol can be thought of as a classical Ising spin state, while the discrete time points are mapped onto lattice sites. The TD limit of the effective classical spin model coincides with the continuum limit of the discrete time evolution: + +12 + + +![](.figures/arxiv__1803.10856/1803.10856.pdf-0012-01.png) + + + +(a) 0 . 004 T = 0 . 400 (b) T = 0 . 800 (c) 0 . 006 T = 1 . 600
0 . 004
0 . 005
0 . 002
0 . 002
0 . 000 0 . 000 0 . 000
− 0 . 002 − 0 . 002
− 0 . 005 − 0 . 004
− 0 . 004
− 0 . 006
0 5 10 15 20 25 0 5 10 15 20 25 0 5 10 15 20 25
time lattice site j time lattice site j time lattice site j
(d) 0 . 003 T = 2 . 000 (e) T = 2 . 600 (f) 0 . 002 T = 4 . 000
0 . 004
0 . 002
0 . 001
0 . 001 0 . 002
0 . 000 0 . 000 0 . 000
− 0 . 001 − 0 . 002 − 0 . 001
− 0 . 002
− 0 . 004
− 0 . 003 − 0 . 002
0 5 10 15 20 25 0 5 10 15 20 25 0 5 10 15 20 25
time lattice site j time lattice site j time lattice site j
Gj Gj Gj
Gj Gj Gj
+ +FIG. 14: Spatial dependence of the single-spin (‘onsite magnetic field’) term in the exact effective Hamiltonian _H_ eff ( _T_ ). The time lattice sites correspond to the bangs of the bang-bang protocols used to prepare the state. The parameters are _NT_ = 28, _L_ = 6. + +## _NT →∞_ , _δt →_ 0 with _δtNT_ = _T_ = _const_ . + +An intriguing and natural question arises as to what the underlying classical spin model _H_ eff ( _T_ ) actually looks like. This classical spin energy function governs the phase transitions of the quantum control problem, and our interpretation of the latter in terms of spin phases can potentially benefit if one is able to extract some useful information about the properties of the different terms in _H_ eff ( _T_ ). For instance, information about the form of its couplings is contained in the (negative) log-fidelity spectrum, i.e. the log-fidelities corresponding to all possible bang-bang configurations. In this section, we work with the log-fidelity _Ch_ ( _T_ ) = _−L__−_1 log _Fh_ ( _T_ ), which depends only weakly on the system size _L_ . + +When it comes to studying control phases, of particular interest is to determine the locality properties of the dominant spin couplings. For this purpose, we make the following ansatz for the most general form of the energy function of a two-state classical spin degree of freedom _hj ∈{±_ 4 _}_ : + + +![](.figures/arxiv__1803.10856/1803.10856.pdf-0012-06.png) + + +### **A. Exact Coupling Strengths** + +Consider first a generic _k_ -body interaction term. If _k_ = 0, this is just the constant log-fidelity offset _C_ 0; for _k_ = 1, we have an effective magnetic field _Gj_ , while the _k_ = 2 case can be interpreted as a two-body interaction _Jij_ , and so forth. If we consider a protocol of _NT_ time steps (bangs), there are a total of _{hj_ ; _s}_2 _s_ =1_NT_differentprotocolconfigurations, each of which comes with its own log-fidelity _Cs_ ( _T_ ). Here the index _s_ runs over all 2_NT_ protocol configurations. Suppose we know the entire exact log-fidelity spectrum, but not the underlying effective energy function _H_ eff . One can compute all protocols and their log-fidelities numerically for up to _NT_ = 28 bangs. Then, one can convince oneself + +13 + + +![](.figures/arxiv__1803.10856/1803.10856.pdf-0013-01.png) + + + +(a) 0 5 10 15 20 25 ×Jij 10 − 3 (b) 0 5 10 15 20 25 Jij× 10 − 3 (c) 0 5 10 15 20 25 ×Jij 10 − 2
0 0 0 2 . 0
1 . 5 6
1 . 5
5 1 . 0 5 4 5 1 . 0
T = 0 . 4 T = 0 . 8 T = 1 . 6
10 0 . 5 10 2 10 0 . 5
0 . 0 0 0 . 0
15 15 15
− 0 . 5 − 2 − 0 . 5
20 − 1 . 0 20 − 4 20 − 1 . 0
− 1 . 5
25 − 1 . 5 25 − 6 25
− 2 . 0
time lattice site i time lattice site i time lattice site i
(d) 0 5 10 15 20 25 ×Jij 10 − 2 (e) 0 5 10 15 20 25 ×Jij 10 − 2 (f) 0 5 10 15 20 25 Jij× 10 − 3
0 2 . 0 0 0
1 . 5 6
1 . 5
5 5 1 . 0 5 4
1 . 0
10 T = 2 . 0 0 . 5 10 T = 2 . 6 0 . 5 10 T = 4 . 0 2
0 . 0 0 . 0 0
15 15 15
− 0 . 5 − 0 . 5 − 2
20 − 1 . 0 20 − 1 . 0 20 − 4
− 1 . 5
25 25 − 1 . 5 25 − 6
− 2 . 0
time lattice site i time lattice site i time lattice site i
FIG. 15: Spatial dependence of the two-body interaction term in the exact effective Hamiltonian H eff ( T ). The time lattice
sites correspond to the bangs of the bang-bang protocols used to prepare the state. The parameters are NT = 28, L = 6.
(a) × 10 − 3 (b) × 10 − 3 (c) × 10 − 2
1 . 75 T = 0 . 400 T = 0 . 800 2 . 0 T = 1 . 600
6
1 . 50 1 . 5
1 . 25 4 1 . 0
1 . 00 2 0 . 5
0 . 75
0 0 . 0
0 . 50
− 0 . 5
0 5 10 15 20 25 0 5 10 15 20 25 0 5 10 15 20 25
time lattice site j time lattice site j time lattice site j
(d) × 10 − 2 (e) × 10 − 2 (f) × 10 − 3
2 . 0 T = 2 . 000 T = 2 . 600 T = 4 . 000
1 . 5
6
1 . 5
1 . 0
1 . 0 4
0 . 5
0 . 5
2
0 . 0
0 . 0
− 0 . 5 0
− 0 . 5
0 5 10 15 20 25 0 5 10 15 20 25 0 5 10 15 20 25
time lattice site j time lattice site j time lattice site j
j j j
site site site
lattice lattice lattice
time time time
j j j
site site site
lattice lattice lattice
time time time
j,NjT− j,NjT− j,NjT−
J J J
j,NjT− j,NjT− j,NjT−
J J J
+ +FIG. 16: Decay of the two-body interaction terms _Jij_ along the anti-diagonal, c.f. Fig. 15. The time lattice sites correspond to the bangs of the bang-bang protocols used to prepare the state. The parameters are _NT_ = 28, _L_ = 6. + +14 + + +![](.figures/arxiv__1803.10856/1803.10856.pdf-0014-01.png) + + + +(a) × 10 − 5 (b) × 10 − 4 (c) × 10 − 3
T = 0 . 4 T = 0 . 8 T = 1 . 6
3 . 0 4 . 5 3
2 . 5
4 . 0
2 . 0 2
3 . 5
1 . 5
1
1 . 0 3 . 0
0 . 5
2 . 5 0
10 20 30 40 50 10 20 30 40 50 10 20 30 40 50
|i − j| + |j − k| + |k − i| |i − j| + |j − k| + |k − i| |i − j| + |j − k| + |k − i|
(d) × 10 − 3 (e) × 10 − 3 (f) × 10 − 3
5
7 5
4 6
3 5 4
4
2 3
3
1 2
T = 2 . 0 T = 2 . 6 2 T = 4 . 0
10 20 30 40 50 10 20 30 40 50 10 20 30 40 50
|i − j| + |j − k| + |k − i| |i − j| + |j − k| + |k − i| |i − j| + |j − k| + |k − i|
| ) ijk | ) ijk | ) ijk
| mean ( K | mean ( K | mean ( K
| ) ijk | ) ijk | ) ijk
| mean ( K | mean ( K | mean ( K
+ +FIG. 17: Non-locality of the three-body interaction term in the exact effective Hamiltonian _H_ eff ( _T_ ). The parameters are _NT_ = 28, _L_ = 6. + +that all couplings of the effective spin model _H_ eff ( _T_ ) can be uniquely determined from the following expressions: + + +![](.figures/arxiv__1803.10856/1803.10856.pdf-0014-04.png) + + +and analogously for the higher-order terms. To derive these expressions, we note that in the set of all protocols _hj/_ 4 = _±_ 1 an equal number of times. For this reason, _s__hj_;_s_=0atanyfixedtimestep(time-latticesite)_j_. Combining this observation with the fact _h_2 _j__/_16 = 1yieldstheexpressionsabove. + +For the sake of simplicity and tractability, we can truncate the effective spin model, keeping all possible _n_ -body interactions ( _n_ = 1 _,_ 2 _,_ 3), and neglect any higher-order ones. This leads to an approximate classical spin model which + +15 + +we denote by _H_ approx(_n_)(_T_).Forinstance: + + +![](.figures/arxiv__1803.10856/1803.10856.pdf-0015-02.png) + + +and so forth. We emphasize that _Gj_ , _Jij_ and _Kijk_ are the exact coupling strengths which depend parametrically on the protocol duration _T_ but are independent of the truncation order _n_ , see Eq. (8). To quantify each of these approximate Hamiltonians, we define the mean error + + +![](.figures/arxiv__1803.10856/1803.10856.pdf-0015-04.png) + + +where _Cs_ denotes the exact and _Cs,_(_n_ approx)–theapproximatelog-fidelitytoorder_n_,respectively. In the following we always restrict the total number of bangs to _NT_ = 28, and vary the total protocol duration _T_ . Figure 14 shows the exact effective on-site field strength _Gj_ ( _T_ ) for six protocol durations _T_ . It follows that the optimal protocol (i.e. the lowest energy configuration) of _H_ approx(1)for_T< T_ _c_is a single step at time_T/_2.Interestingly, this is precisely the form of the optimal protocol in the overconstrained phase [68]. This is backed up by Fig. 18a (blue line), which shows that the mean error generated by _H_ approx(1)isindeedsmallestintheoverconstrainedcontrol phase. In the glassy phase for _T > Tc_ , however, the form of the _Gj_ field changes gradually, and the higher-order terms become more important. + +Figures 15 and 16 show the exact effective spin-spin interaction _Jij_ ( _T_ ). First, we notice that it is not sparse, but features finite all-to-all couplings on all the bonds. Moreover, _Jij_ ( _T_ ) keeps the same sign over large portions of neighbouring spin bonds ( _i, j_ ) for all _T_ we consider. Therefore, we can anticipate that it would hardly be possible for the optimal protocol to satisfy all two-body couplings simultaneously. To quantify the bond satisfiability of the optimal protocol, let us define the frustration parameter inspired by the _k_ -SAT problem and spin-glass physics: + + +![](.figures/arxiv__1803.10856/1803.10856.pdf-0015-07.png) + + +where _J_˜ _i_ 1 _...in_ denotes all couplings up to and including order _n_ , and _|·|_ denotes the absolute value. The tilde means that the couplings _J_˜ _i_ 1 _...in_ are first normalized such that _hj ∈{±_ 1 _}_ . With this definition, the frustration parameter Φ( _T_ ) is normalized between zero and unity with zero signaling no frustration and unity – maximum possible frustration, respectively. To gain intuition for this quantity, notice that the energy function _H_ eff is a sum of terms, each with its own coupling _J_˜ _i_ 1 _...in_ . In the absence of frustration, we can find a global minimum of the energy by finding the minimum for each term in _H_ eff , and _i_ 1 _,...,in__|J_˜_i_1_...in_(_T_)_|_willbeequaltothenegativeminimumenergy.Sincethe first term in the numerator of Φ( _T_ ) is just the energy of the lowest spin configuration, in the absence of frustration the two terms in the numerator will cancel and Φ( _T_ ) = 0. In general, we can measure frustration by asking how different minimizing the individual terms of _H_ eff is, from the true minimum of the sum of terms. This is what the quantity Φ( _T_ ) measures. Figure 18b demonstrates the high degree of frustration in the effective model. + +Including the all-to-all three-body interaction term _Kijk_ ( _T_ ) improves the error _E_ in the log-fidelity spectrum only marginally, cf. Fig. 18a. This means that even more complicated higher-order multi-body terms are needed in order to fully capture the underlying physics of the effective model. To show the non-locality of the mean three-body interactions mean( _|Kijk|_ ), we adopt the following measure: (i) we fix the perimeter of the triangle spanned by three interacting spins _|i − j|_ + _|j − k|_ + _|k − i|_ , (ii) we categorize all three-spin interactions according to this perimeter, and (iii) we compute the mean of their absolute value. The result is shown in Fig. 17. We attribute this nonlocal behaviour to the original quantum state preparation problem being causal: in other words, the value of the protocol at a later time depends on all possible values taken at previous times (up to symmetries). The complexity of the effective classical spin model is also sustained by the frustration parameter, which remains high upon adding the higher-order terms, see Fig. 18b. + +16 + + +![](.figures/arxiv__1803.10856/1803.10856.pdf-0016-01.png) + + + +1 . 0 0 . 8
0 . 30 H approx (1)
a) 0 . 8 b) c)
0 . 25 0 . 6 H approx (2)
0 . 20 0 . 6 H approx (3)
0 . 15 HH approxapprox (1)(2) 0 . 4 0 . 4 HH approxapprox (1+2)(1+2+3)
0 . 10 H approx (3) H approx (1) H approx (1+2)
0 . 2
0 . 05 H approx (1+2) 0 . 2 H approx (2) H approx (1+2+3)
0 . 00 H approx (1+2+3) H approx (3)
0 . 0 0 . 0
1 2 3 4
T 1 2 3 4 1 2 3 4
T T
)
( T
)( T ) T
E Φ( infidelity

low
E
+ +FIG. 18: a) Average difference between the spectrum of the truncated effective models and the exact fidelity spectrum as a function of the protocol duration _T_ . b) Frustration order parameter for the truncated effective models as a function of the protocol duration _T_ . c) Same as a), but with the effective model computed only on the 200000 lowest log-fidelity protocols. The legends shows which terms are kept in the effective Hamiltonian (see text). The parameters used are _NT_ = 28, _L_ = 6. + +Finally, in Fig. 18c, we computed the effective couplings by summing only over the lowest 200000 log-fidelity protocols. As expected, the computed error increases w.r.t. to Fig. 18a. + +### **B. Better-Fidelity (Low log-Fidelity) Coupling Strengths** + +Many properties of the phases of classical spin systems are usually determined by their low-energy states. Therefore, one might wonder if, despite the results presented above, a _local_ effective spin model _H_ ML(_n_)(_T_) still exists, which captures only the physics of the better-fidelity states [corresponding to the low negative log-fidelity part of the spectrum of the exact _H_ eff ( _T_ )]. In other words, at least in principle, there exists the possibility that the completely non-local character of the exact effective couplings discussed above originates from bad-fidelity states, and we want to rule that out. This check is important, since the number of the bad-fidelity states is exponentially large in _NT_ , compared to that of the good-fidelity states, as a consequence of any two randomly chosen quantum states being with high probability orthogonal in the high-dimensional Hilbert space of the quantum many-body system. + +To address this concern, we order the protocols [classical spin states] according to their fidelities, and impose a cut-off, keeping only the better fidelities [in practice, we keep the best 2 _×_ 105 out of a total of 228 protocols for _NT_ = 28 bangs]. In the following, we shall refer to this set as the low log-fidelity manifold (in analogy with the low-energy manifold of classical spin models). Based on this data, we employ ideas from Machine Learning to learn only those properties of the coupling of the exact effective spin-energy model _H_ eff ( _T_ )[62, 69, 70], which influence the low log-fidelity manifold. The learning problem being linear in the coupling strengths, we can employ Ridge and Lasso regression to fit an effective spin-energy model to the log-fidelity data. While Ridge regression assumes that the resulting learned coupling strengths are all-to-all, Lasso is particularly suited for finding sparse couplings. The ML model _H_ ML(_n_)(_T_)differsfromtheeffectiveoneintheprevioussection,inthatitshouldapproximatelyreproduce only the better fidelities, while all information about the bad protocols is discarded. + +Starting with the set of better fidelities and the corresponding states, we divide it into a training and a test data set in proportion 5:3. We train our ML models _H_ ML(_n_)(_T_)usingan_L_2-costfunction,tryingoutdifferentregularisation strength hyperparameters, and select the one which results in the best performance [62]. The model is trained by only using the training data, while we measure its performance on both the training and test data sets. We denote the exact log-fidelities by _Cs_ , and the predicted ones – by _Cs,_(_n_ ML),where_s_hererunsuptothedatasetsize_N_.The measure of performance is the quantity _R_2 , defined as + + +![](.figures/arxiv__1803.10856/1803.10856.pdf-0016-08.png) + + +This quantity is unity if the predicted data matches the true data, while its deviation from unity quantifies how good the ML model is. We evaluate _R_2 for the Ridge and Lasso regression on both the training and the test data, see Fig. 19. In general, one can expect two types of behavior: (i) if the training (solid line) and the test (dashed line) values of _R_2 are not on top of each other, it means the model failed to learn the correct properties of the data which determine the physics, most likely due to overfitting. (ii) sometimes the train and test curves are indeed very close, + +17 + + +![](.figures/arxiv__1803.10856/1803.10856.pdf-0017-01.png) + + + +1 . 0 1 . 0
H ML (1) H ML (1)
0 . 8 H ML (2) 0 . 8 H ML (2)
H ML (3) H ML (3)
0 . 6 0 . 6
0 . 4 0 . 4
0 . 2 0 . 2
0 . 0 0 . 0
1 2 3 4 1 2 3 4
T T
2 2
R R
+ +FIG. 19: _R_2 of model performance vs. the protocol duration _T_ for the three model classes considered for Lasso (left) and Ridge regression (right). The solid and dashed lines correspond to the training and test data, respectively. The similarity between the test and training _R_2 -curves shows the low degree of overfitting. The parameters are _NT_ = 28, _L_ = 6. + +but the value of _R_2 deviates from unity. This means that the true model which generated the data points lies outside the model class we assumed to look for a solution in. In other words, the model learned everything there is to learn about the data _within the model class under consideration_ . In our problem, since the true model (7) is linear in the coupling strengths, this suggests that we have to include higher-order multi-body interaction terms in _H_ ML(_n_)(_T_), i.e. consider _n >_ 3. + +We apply Machine Learning with three different model types: (i) a non-interacting spin model _H_ ML(1)(_T_)wherewe only learn the on-site local field values _Gj_ ( _T_ ), (ii) a more sophisticated interacting model _H_ ML(1+2) ( _T_ ) where we learn both the on-site _Gj_ ( _T_ ) and the two-body interaction _Jij_ ( _T_ ), and (iii) a more general spin energy function _H_ ML(1+2+3) ( _T_ ) containing all possible one, two and three-body interactions _Gj_ ( _T_ ), _Jij_ ( _T_ ) and _Kijk_ ( _T_ ). Figure 19 shows the _R_2 ( _T_ ) as a function of the protocol duration _T_ in the three cases for the optimal regularization strength for Ridge and Lasso regression. First, note that all training and test curves are on top of each other, which means that our model learned the underlying correlations reliably. Second, notice that Ridge regression always outperforms the Lasso regression. Recalling that the Lasso regularization tries to enforce sparse couplings, this results backs up our conclusion that the effective classical spin model is non-local even when it comes to the low log-fidelity (i.e. better-fidelity) states. Last, observe how, at a fix protocol duration _T_ in the glassy phase, enlarging the model type leads to a better performance, yet there is always a protocol duration at which the _R_2 deviates significantly from unity. If we extrapolate this behavior, deeper in the glassy phase all multi-body interactions will most likely be required to keep the ML model performance close to unity. This means that, even for the set of better fidelities, it is insufficient to consider only local one and two-body terms in _H_ ML(_n_)(_T_)for_T_intheglassphase.Wethusconcludethatthenon-localityofthe effective classical spin model is a property featured by the entire log-fidelity spectrum, and is not inflicted solely by the majority of bad protocols. + +Figures 20, 21 and 22 display the better-fidelity coupling strengths obtained using ML with Ridge Regression for six protocol durations _T_ . One can compare these to the exact couplings from Figs. 14, 15 and 17. Unlike the exact coupling strengths, they depend on the model class. + +18 + + +![](.figures/arxiv__1803.10856/1803.10856.pdf-0018-01.png) + + + +(a) 0 . 004 R test 2 = 1 . 000 (b) R test 2 = 1 . 000 (c) 0 . 0075 R test 2 = 0 . 998
T = 0 . 400 T = 0 . 800 0 . 0050 T = 1 . 600
0 . 002
0 . 002
0 . 0025
0 . 000 0 . 000 0 . 0000
− 0 . 0025
− 0 . 002
− 0 . 002 − 0 . 0050
− 0 . 004 − 0 . 0075
0 5 10 15 20 25 0 5 10 15 20 25 0 5 10 15 20 25
time lattice site j time lattice site j time lattice site j
(d) R test 2 = 0 . 986 (e) R test 2 = 0 . 883 (f) 0 . 0003 R test 2 = 0 . 109
T = 2 . 000 T = 2 . 600 T = 4 . 000
0 . 005 0 . 01 0 . 0002
0 . 0001
0 . 000 0 . 00 0 . 0000
− 0 . 0001
− 0 . 005 − 0 . 01 − 0 . 0002
− 0 . 0003
0 5 10 15 20 25 0 5 10 15 20 25 0 5 10 15 20 25
time lattice site j time lattice site j time lattice site j
Gj Gj Gj
Gj Gj Gj
+ +FIG. 20: Spatial dependence of the single-spin (‘onsite magnetic field’) term in the ML Hamiltonian _H_ ML(1+2+3) ( _T_ ). The time lattice sites correspond to the bangs of the bang-bang protocols used to prepare the state. The parameters are _NT_ = 28, _L_ = 6. The inverse Ridge regression regularisation strength is _λ_ = 10_−_9 . + + +![](.figures/arxiv__1803.10856/1803.10856.pdf-0018-03.png) + + + +T = 0 . 4
+ + +![](.figures/arxiv__1803.10856/1803.10856.pdf-0018-04.png) + + + +![](.figures/arxiv__1803.10856/1803.10856.pdf-0018-05.png) + + + +T = 0 . 8
+ + +![](.figures/arxiv__1803.10856/1803.10856.pdf-0018-06.png) + + + +![](.figures/arxiv__1803.10856/1803.10856.pdf-0018-07.png) + + + +T = 1 . 6
+ + +![](.figures/arxiv__1803.10856/1803.10856.pdf-0018-08.png) + + + +![](.figures/arxiv__1803.10856/1803.10856.pdf-0018-09.png) + + + +T = 2 . 0
+ + +![](.figures/arxiv__1803.10856/1803.10856.pdf-0018-10.png) + + + +![](.figures/arxiv__1803.10856/1803.10856.pdf-0018-11.png) + + + +T = 2 . 6
+ + +![](.figures/arxiv__1803.10856/1803.10856.pdf-0018-12.png) + + + +![](.figures/arxiv__1803.10856/1803.10856.pdf-0018-13.png) + + + +T = 4 . 0
+ + +![](.figures/arxiv__1803.10856/1803.10856.pdf-0018-14.png) + + +FIG. 21: Spatial dependence of the two-body interaction term in the ML Hamiltonian _H_ ML(1+2+3) ( _T_ ). The time lattice sites correspond to the bangs of the bang-bang protocols used to prepare the state. The parameters are _NT_ = 28, _L_ = 6. The inverse Ridge regression regularisation strength is _λ_ = 10_−_9 . + +19 + + +![](.figures/arxiv__1803.10856/1803.10856.pdf-0019-01.png) + + + +(a) × 10 − 5 (b) 2 . 0 × 10 − 4 (c) × 10 − 3
1 . 2 T = 0 . 4 T = 0 . 8 T = 1 . 6
3
1 . 5
1 . 0
2
0 . 8 1 . 0
0 . 6 0 . 5 1
0 . 4 R test 2 = 1 . 000 0 . 0 R test 2 = 1 . 000 0 R test 2 = 0 . 998
10 20 30 40 50 10 20 30 40 50 10 20 30 40 50
|i − j| + |j − k| + |k − i| |i − j| + |j − k| + |k − i| |i − j| + |j − k| + |k − i|
(d) × 10 − 3 T = 2 . 0 (e) 1 . 75 × 10 − 2 T = 2 . 6 (f) 4 . 0 × 10 − 4 T = 4 . 0
6 1 . 50 3 . 5
1 . 25 3 . 0
4 1 . 00 2 . 5
0 . 75 2 . 0
2
0 . 50 1 . 5
0 R test 2 = 0 . 986 0 . 25 R test 2 = 0 . 883 1 . 0 R test 2 = 0 . 109
10 20 30 40 50 10 20 30 40 50 10 20 30 40 50
|i − j| + |j − k| + |k − i| |i − j| + |j − k| + |k − i| |i − j| + |j − k| + |k − i|
| ) ijk | ) ijk | ) ijk
| mean ( K | mean ( K | mean ( K
| ) ijk | ) ijk | ) ijk
| mean ( K | mean ( K | mean ( K
+ +FIG. 22: Non-locality of the tree-body term in the ML Hamiltonian _H_ ML(1+2+3) ( _T_ ). The time lattice sites correspond to the bangs of the bang-bang protocols used to prepare the state. The parameters are _NT_ = 28, _L_ = 6. The inverse Ridge regression regularisation strength is _λ_ = 10_−_9 . diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/references/rendered/2001.01013_discrete-adjoints-for-accurate-numerical-optimization-with-a.md b/tracks/qcs/solutions/frustration-free/challenge-113/references/rendered/2001.01013_discrete-adjoints-for-accurate-numerical-optimization-with-a.md new file mode 100644 index 000000000..21b472605 --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/references/rendered/2001.01013_discrete-adjoints-for-accurate-numerical-optimization-with-a.md @@ -0,0 +1,1142 @@ +--- +source: "https://arxiv.org/abs/2001.01013" +type: "arxiv" +canonical_id: "2001.01013" +title: "Discrete Adjoints for Accurate Numerical Optimization with Application to Quantum Control" +authors: "N. Anders Petersson, Fortino M. Garcia, Anil R. Bhat, Daniel M. Copeland, Marissa D. Grace, Jason L. DuBois" +year: "2020" +venue: "arXiv: Quantum Physics" +arxiv_id: "2001.01013" +full_text: yes +--- + +# Discrete Adjoints for Accurate Numerical Optimization with Application to Quantum Control + +**Authors:** N. Anders Petersson, Fortino M. Garcia, Anil R. Bhat, Daniel M. Copeland, Marissa D. Grace, Jason L. DuBois + +**Citation:** arXiv: Quantum Physics, 2020 + +**arXiv:** [2001.01013](https://arxiv.org/abs/2001.01013) + +## Abstract + +We consider optimizing control functions for realizing logical gates in a closed quantum systems, where the evolution of the state vector is governed by the time dependent Schrodinger equation. This is an optimal control problem where the objective function consists of two parts: the infidelity of the quantum gate and an integral in time over highly energetic states. We write Schrodingers equation as a Hamiltonian system in terms of the real and imaginary parts of the state vector and discretize the system with the Stromer-Verlet scheme, which is a partitioned Runge-Kutta method. We develop a compatible scheme for the adjoint differential equation, which allows the gradient of the discretized objective function to be calculated exactly. This allows all components of the gradient to be calculated at the cost of solving two Schrodinger systems, independently of the number of parameters in the control functions. The control function are expanded in a series of B-spline basis functions with built-in carrier waves. The interior point L-BFGS algorithm from the IPOPT package is used to minimize the objective function, subject to amplitude constraints on the parameter vector. The method is applied to Hamiltonians that model the dynamics of a super-conducting qudit. By calculating the spectrum of the Hessian at the optima we find that many eigenvalues are small and that a handful are negative, indicating that the optimization problem is non-convex. + +## Full Text + +# Discrete Adjoints for Accurate Numerical Optimization with Application to Quantum Control + +N. Anders Peterssona,_∗_ , Fortino M. Garciab , Austin E. Copelandc , Ylva L. Rydind , Jonathan L. DuBoise + +> _aCenter for Applied Scientific Computing, LLNL, Livermore, CA 94550, USA._ + +> _bDepartment of Applied Mathematics, CU, Boulder, CO 80309, USA_ + +> _cDepartment of Mathematics, SMU, Dallas, TX 75205, USA_ + +> _dDepartment of Information Technology, UU, 751 05 Uppsala, SWEDEN_ + +> _eQuantum Coherent Device Physics Group, LLNL, Livermore, CA 94550, USA_ + +## **Abstract** + +This paper considers the optimal control problem for realizing logical gates in a closed quantum system. The quantum state is governed by Schr¨odinger’s equation, which we formulate as a time-dependent Hamiltonian system in terms of the real and imaginary parts of the state vector. The system is discretized with the St¨ormer-Verlet scheme, which is a symplectic partitioned Runge-Kutta method. Our main theoretical contribution is the derivation of a compatible time-discretization of the adjoint state equation, such that the gradient of the discrete objective function can be calculated exactly, at a computational cost of solving two Schr¨odinger systems, independently of the number of parameters in the control functions. + +A parameterization of the control functions based on B-splines with builtin carrier waves is also introduced. The carrier waves are used to specify the frequency spectra of the control functions, while the B-spline functions specify their envelope and phase. This approach allows the number of control parameters to be independent of, and significantly smaller than, the number of time steps for integrating Schr¨odinger’s equation. + +We consider Hamiltonians that model the dynamics of a superconducting multi-level qudit and present numerical examples of how the proposed technique can be combined with the interior point L-BFGS algorithm from the IPOPT package for realizing quantum gates. In a set of test cases, the proposed algorithm is shown to compare favorably with QuTiP/pulse ~~o~~ ptim and Grape-Tensorflow. + +_Keywords:_ Optimal control, Partitioned Runge-Kutta method, Discrete adjoint, Quantum computing + +> _∗_ Corresponding author _Email addresses:_ `petersson1@llnl.gov` (N. Anders Petersson), `fortino.garcia@colorado.edu` (Fortino M. Garcia), `acopeland@mail.smu.edu` (Austin E. Copeland), `ylva.rydin@it.uu.se` (Ylva L. Rydin), `dubois9@llnl.gov` (Jonathan L. DuBois) + +_Preprint submitted to Elsevier_ + +_November 20, 2020_ + +## **1. Introduction** + +A key challenge for realizing the potential of quantum computing lies in determining the most efficient and accurate route to controlling the quantum states in a quantum device. This challenge stems from the fact that current quantum computing systems, unlike classical computers, do not have a fixed set of logical gates predetermined in hardware. Instead, the execution of a quantum algorithm is carried out by first devising a set of classical control functions that are then applied to the quantum computing hardware to guide the quantum states through a series of quantum logical operations [16]. Reducing the time required for a quantum gate to be realized is critical for near-term quantum computing because it enables the computation to finish before the quantum state collapses to a classical state, rendering the results meaningless. To mitigate this problem, quantum optimal control techniques have been developed to produce customized control pulses that minimize the execution time for complicated gates that directly map onto a physical system [20]. + +Optimizing the control functions for realizing quantum gates is a optimal control problem where the objective function measures the infidelity of the gate transformation, constrained by Schr¨odinger’s equation governing the evolution of the quantum states. For superconducting circuits it is also important to suppress leakage into highly energetic states [13], leading to an optimal control problem in Mayer-Lagrange form. Our approach builds upon the works of Hager [9], Sanz-Serna [19] and Ober-Bl¨obaum [18]. Hager [9] first showed how the Hamiltonian structure in an optimization problem can be utilized to calculate the gradient of the objective function. Hager considered the case in which the state equation is discretized by one Runge-Kutta scheme, with the adjoint state equation discretized by another Runge-Kutta scheme. It was found that the discrete gradient can be calculated exactly if the pair of Runge-Kutta methods satisfy the requirements of a symplectic partitioned Runge-Kutta method. Further details and generalizations are described in the review paper by SanzSerna [19]. Ober-Bl¨obaum [18] extended Hager’s approach to the case where the state equation itself is a Hamiltonian system that is discretized by a partitioned Runge-Kutta scheme. For autonomous state equations, it was shown that the compatible discretization of the adjoint state equation is another partitioned Runge-Kutta scheme. + +In the quantum optimal control problem, the Schr¨odinger (state) equation is a time-dependent Hamiltonian system. To ensure long-time numerical accuracy it is appropriate to discretize it using a symplectic time-integration method [10]. For this purpose we use the St¨ormer-Verlet method, which can be written as a partitioned Runge-Kutta scheme, based on the trapezoidal and implicit midpoint rules. Our main theoretical contribution is the generalization of OberBl¨obaum’s [18] work to the case of a time-dependent Hamiltonian system. We show that the compatible method for the adjoint state equation resembles a partitioned Runge-Kutta scheme, except that the time-dependent matrices must be + +2 + +evaluated at time levels. + +Logical gates in a closed quantum system can be viewed as linear reversible mappings, _|_ **_ψ_****_′′_** _⟩_ = _Vg|_ **_ψ_****_′_** _⟩_ , from an initial state _|_ **_ψ_****_′_** _⟩_ to a final state _|_ **_ψ_****_′′_** _⟩_ , where the reversibility implies that the mapping _Vg_ must be unitary, _Vg__†Vg_=_I_.To introduce the quantum control problem, we start by discussing the case where the unitary transformation is defined in the entire _N_ -dimensional state space, such that it can be represented by a unitary matrix _Vg ∈_ C_N×N_ ; a more general case is described in Section 2. + += In the following, we will replace the ket-notation [16] of the state vector _|_ **_ψ_** _⟩ ψ_(0) _|_ 0 _⟩_ + _ψ_(1) _|_ 1 _⟩_ + _. . ._ + _ψ_(_N−_1) _|N −_ 1 _⟩_ by the vector notation **_ψ_** = _ψ_(0) **_e_** 0+ _ψ_(1) **_e_** 1+ _. . ._ + _ψ_(_N−_1) **_e_** _N −_ 1, which is more common in the computational mathematics literature1 . The elements in the state vector are complex probability amplitudes and the squared magnitude of the amplitudes sum to unity, i.e., _∥_ **_ψ_** _∥_ 22= 1. + +To account for all admissible initial data in the Hilbert space C_N_ , we consider the evolutions from the canonical basis vectors **_e_** _j_ , for _j_ = 0 _,_ 1 _, . . . , N −_ 1. The time-dependent control functions are expanded in terms of a finite number of basis functions, such that the control functions are determined by the finitedimensional parameter vector **_α_** _∈_ R_D_ . This leads to Schr¨odinger’s equation in matrix form for the _N × N_ complex-valued solution operator matrix _U_ ( _t,_ **_α_** ): + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0003-04.png) + + +Here, _IN_ is the _N × N_ identity matrix and _H_ ( _t,_ **_α_** ) is the Hamiltonian matrix, in which the time-dependence is parameterized by **_α_** . As a result, the solution operator matrix depends implicitly on **_α_** through Schr¨odinger’s equation. Due to linearity, the solution for general initial conditions satisfies **_ψ_** ( _t,_ **_α_** ) = _U_ ( _t,_ **_α_** ) **_ψ_** (0 _,_ **_α_** ). + +The goal of the quantum control problem is to determine the parameter vector **_α_** such that the time-dependence in the Hamiltonian matrix leads to a solution of Schr¨odinger’s equation that minimizes the difference between the target gate matrix _Vg_ and _U_ ( _T,_ **_α_** ). Here, we measure the difference by the commonly used target gate infidelity [11, 13, 14, 15, 20], + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0003-07.png) + + +Because _UT_ and _Vg_ are unitary, _|_ Tr( _UT__†Vg_)_|≤N_and_J_0_≥_0. Note that the target gate infidelity is sensitive to relative phase differences between the columns of _UT_ and _Vg_ , but is invariant to global phase differences between _UT_ and _Vg_ . + +The quantum control problem is a constrained optimization problem where, in the basic setting, the gate infidelity (2) is minimized under the constraints that the solution operator matrix satisfies Schr¨odinger’s equation (1) and the + +> 1Here, **_e_** _j_ represents the _j_ th canonical basis vector, in which the _j_ th element is one and all other elements are zero. + +3 + +amplitudes of the control functions (determined by the parameter vector **_α_** ) do not exceed prescribed limits. For a discussion of the solvability of the quantum control problem, see for example Borzi et al. [3]. + +While not a restriction of our approach, we exemplify our technique on Hamiltonians that model the dynamics of a superconducting qudit (a qubit with more than two energy levels). We represent the state vector in the energy basis in which the system Hamiltonian matrix is diagonal. In the laboratory frame of reference, the Hamiltonian matrix is modeled by + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0004-02.png) + + +Here, _a_ and _a__†_ are the lowering and raising matrices (see Appendix A), _ωa >_ 0 is the fundamental resonance frequency, _ξa >_ 0 is the self-Kerr coefficient and _f_ ( _t,_ **_α_** ) is a real-valued control function that depends on the parameter vector **_α_** . + +To slow down the time scales in the state vector, the problem is transformed to a rotating frame of reference (see Appendix A) in which the Hamiltonian matrix + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0004-05.png) + + +where _p_ ( _t,_ **_α_** ) and _q_ ( _t,_ **_α_** ) are the real-valued control functions in the rotating frame of reference. The control functions in the two frames are related by + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0004-07.png) + + +Several numerical methods for the quantum control problem are based on the GRAPE algorithm [12]. In this case, Schr¨odinger’s equation is discretized in time using the second order accurate Magnus scheme [10], in which the Hamiltonian matrix is evaluated at the midpoint of each time step. A stair-step approximation of the control functions is imposed such that each control function is constant within each time step. Thus, the time step determines both the numerical accuracy of the dynamics of the quantum state _and_ the number of control parameters. With _Q_ control functions, _M_ time steps of size _h_ , the control functions are thus described by _M_ times _Q_ parameters _αj,k_ . The propagator in the Magnus method during the _j__th_ time step is of the form exp( _−ih_ ( _H_ 0 + _k__αk,jHk_)).Ingeneral,thematrices_H_0and_Hk_donotcom- mute, leading to an integral expression for the derivative of the propagator with respect to the parameters, which is needed for computing the gradient of the objective function. In the original GRAPE method, this integral expression is approximated by the first term in its Taylor series expansion, leading to an approximate gradient that is polluted by an _O_ ( _h_2 ) error. As the gradient becomes smaller during the optimization, the approximation error will eventually dominate the numerical gradient, which may hamper the convergence of the optimization algorithm. A more accurate way of numerically evaluating the + +4 + +derivative of the time-step propagator can be obtained by retaining more terms in the Taylor series expansion, or by using a matrix commutator expansion [5]. More recently, the GRAPE algorithm has been generalized to optimize objective functions that include a combination of the target gate infidelity, integrals penalizing occupation of “forbidden states” (see Section 2) and terms for imposing smoothness and amplitude constraints on the control functions. Here, automatic differentiation is used for computing the gradient of the objective function [13]. However, the number of control parameters is still proportional to the number of time steps, which may become very large when the duration of the gate is long, or the quantum state is highly oscillatory. + +As an alternative to calculating the gradient of the objective function by solving an adjoint equation backwards in time, the gradient can be calculated by differentiating Schr¨odinger’s equation with respect to each parameter in the control function, leading to a differential equation for each component of the gradient of the state vector. This approach, implemented in the GOAT algorithm [15], allows the gradient of the objective function to be calculated exactly, but requires ( _D_ +1) Schr¨odinger systems to be solved when the control functions depend on _D_ parameters. This makes the method computationally expensive when the number of parameters is large. + +Using the stair-stepped approximation of the control functions often leads to a large number of control parameters, which may hamper the convergence of the GRAPE algorithm. The total number of parameters can be reduced by instead expanding the control functions in terms of basis functions. By using the chain rule, the gradient from the GRAPE algorithm can then be used to calculate the gradient with respect to the coefficients in the basis function expansion. This approach is implemented in the GRAFS algorithm [14], where the control functions are expanded in terms of Slepian sequences. + +Gradient-free optimization methods can also be applied to quantum optimal control problems. These methods do not rely on the gradient to be evaluated and are therefore significantly easier to implement. However, the convergence of these methods is usually much slower than for gradient-based techniques, unless the number of control parameters is very small. One example of a gradient-free methods for quantum optimal control is the CRAB algorithm [4]. + +Many parameterizations of quantum control functions have been proposed in the literature, for example cubic splines [7], Gaussian pulse cascades [6], Fourier expansions [22] and Slepian sequences [14]. This paper presents a different approach, based on parameterizing the control functions by B-spline basis functions with carrier waves, see Figure 1. Our approach relies on the observation that transitions between the energy levels in a quantum system are triggered by resonance, at frequencies which often can be determined by inspection of the system Hamiltonian. The carrier waves are used to specify the frequency spectra of the control functions, while the B-spline functions specify their envelope and phase. We find that this approach allows the number of control parameters to be independent of, and significantly smaller than, the number of time steps for integrating Schr¨odinger’s equation. + +The remainder of the paper is organized as follows. In Section 2, we gen- + +5 + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0006-00.png) + + +Figure 1: An example of three quadratic B-spline basis functions with carrier wave frequencies (0 _, ξ,_ 2 _ξ_ ). + +eralize the optimization problem to the case of target gates that are defined in a subspace of the entire state space. In Section 3, we first introduce the real-valued formulation of Schr¨odinger’s equation, followed by a presentation of the symplectic St¨ormer-Verlet time-stepping method, written as a partitioned Runge-Kutta scheme. To achieve an exact gradient of the discrete objective function, in Section 4 we derive the discrete adjoint time integration method. This method resembles a partitioned Runge-Kutta scheme, except that the timedependent matrices are evaluated at modified time-levels. The solution of the discrete adjoint equation is used to efficiently calculate all components of the gradient of the discrete objective function. The parameterization of the control functions using B-splines with carrier waves is presented in Section 5. Section 6 presents a numerical example of how the proposed technique can be combined with the interior point L-BFGS algorithm [17] from the IPOPT package [21] to realize multi-level qudit gates. Important properties of the optima are exposed by analyzing the eigenvalues of the Hessian. The proposed algorithm has been implemented in the JuQBox package, written in the Julia [2] programming language. In Section 7, we compare its performance to two variants of the GRAPE algorithm. Concluding remarks are given in Section 8. + +## **2. Generalized gates** + +In quantum computing applications it is common to define gate transformations in a subspace of the entire (possibly infinite dimensional) state space, in which the evolution of higher energy states is not relevant for the gate transformation, but if left uncontrolled, may lead to leakage of probability. In the following, let the subspace of interest contain _E >_ 0 “essential” states and let _G_ = _N − E ≥_ 0 denote the number of “guard” states. The guard states that correspond to the highest energy levels in the model are often called “forbidden” states [13]. + +In the case of one qudit oscillator, we can always order the elements in the state vector such that they correspond to increasing energy levels. The + +6 + +Schr¨odinger equation governs the evolution of all energy levels in the state vector, including the guard levels, but the unitary gate transformation is only defined in the subspace of the essential states. This requirement leads us to define the target gate transformation matrix according to + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0007-01.png) + + +Let the state vector **_ψ_** _j_ ( _t,_ **_α_** ) _∈_ C_N_ satisfy the Schr¨odinger equation, + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0007-03.png) + + +for _j_ = 0 _,_ 1 _, . . . , E −_ 1. The solution operator matrix _U_ ( _t,_ **_α_** ) and the target gate matrix _V_ are rectangular with _N_ rows and _E_ columns, + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0007-05.png) + + +The decomposition (6) implies that the last _G_ rows of **_d_** _j_ must be zero. + +The matrix overlap function _RV_ ( _UT_ ) in (2) generalizes in a straightforward way to unitary gates that are defined in the subspace, resulting in the target gate infidelity function + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0007-08.png) + + +where _⟨·, ·⟩_ 2 is the _ℓ_ 2 vector scalar product. The population of the guard states can be measured by the objective function + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0007-10.png) + + +Here, _W_ is a diagonal _N ×N_ positive semi-definite weight matrix. The elements in _W_ are zero for all essential states and are positive for the guard states. The elements of _W_ are typically larger for higher energy levels in the model. + +For the quantum control problem with guard states, we formulate the optimization problem as + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0007-13.png) + + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0007-14.png) + + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0007-15.png) + + +In the special case of zero guard states, _J_ 2( _U_ ) = 0 because _W_ = 0. Thus, the above formulation applies to both the cases with and without guard states, i.e., when _G_ = _N − E ≥_ 0. + +7 + +## **3. Real-valued formulation** + +A real-valued formulation of Schr¨odinger’s equation (7) is given by + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0008-02.png) + + +where, + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0008-04.png) + + +Because the matrix _H_ is Hermitian, _K__T_ = _K_ and _S__T_ = _−S_ (note that the matrix _S_ is unrelated to the matrix overlap function _SV_ ). The real-valued formulation of Schr¨odinger’s equation is a time-dependent Hamiltonian system corresponding to the Hamiltonian functional, + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0008-06.png) + + +In general, _S_ ( _t_ ) = 0, which makes the Hamiltonian system non-separable. + +In terms of the real-valued formulation, the columns of the solution operator matrix in (8) satisfy _U_ = [ **_u_** 1 _− i_ **_v_** 1 _,_ **_u_** 2 _− i_ **_v_** 2 _, . . . ,_ **_u_** _E − i_ **_v_** _E_ ]. Here, ( **_u_** _j,_ **_v_** _j_ ) satisfy (14) subject to the initial conditions **_g_** _j__u_=**_e_**_j_and**_g_** _j__v_=**0**.Thecolumns in the target gate matrix _V_ correspond to + +_V_ = [ **_d_**_u_ 1_−i_**_d_** 1_v,_**_d_**_u_ 2_−i_**_d_** 2_v,. . . ,_**_d_**_u_ _E__−i_**_d_** _E__v_]_,_ **_d_**_u_ _j_= Re(**_d_**_j_)_,_ **_d_**_v_ _j_=_−_Im(**_d_**_j_)_._ + +Using the real-valued notation, the objective function (11) can be written + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0008-11.png) + + +where, + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0008-13.png) + + +## _3.1. Time integration_ + +Let _tn_ = _nh_ , for _n_ = 0 _,_ 1 _, . . . , M_ , be a uniform grid in time where _h_ = _T/M_ is the time step. Also let **_u_**_n_ _≈_ **_u_** ( _tn_ ) and **_v_**_n_ _≈_ **_v_** ( _tn_ ) denote the numerical + +8 + +solution on the grid. We use a partitioned Runge-Kutta (PRK) scheme [10] to discretize the real-valued formulation of Schr¨odinger’s equation, + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0009-01.png) + + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0009-02.png) + + +Here, _s ≥_ 1 is the number of stages. The stage variables **_U_**_n,i_ and **_V_**_n,i_ are set in a bold font to indicate that they are unrelated to the solution operator matrix _U_ ( _t,_ **_α_** ) and the target gate matrix _V_ . + +The St¨ormer-Verlet scheme is a two-stage PRK method ( _s_ = 2) that is symplectic, time-reversible and second order accurate [10]. It combines the trapezoidal and the implicit midpoint rules, with Butcher coefficients: + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0009-05.png) + + +## _3.2. Time step restrictions for accuracy and stability_ + +The accuracy in the numerical solution of Schr¨odinger’s equation is essentially determined by how well the fastest time scale in the state vector is resolved on the grid in time. The analysis of the time scales in the solution of Schr¨odinger’s equation is most straightforward to perform in the complex-valued formulation (7). + +There are two fundamental time scales that must be resolved in the solution of Schr¨odinger’s equation. The first corresponds to how quickly the control functions must vary in time to trigger the desired transitions between the energy levels in the quantum system. This time scale is determined by the transition frequencies in the system Hamiltonian, which follow as the difference between its consecutive eigenvalues. In the Hamiltonian model (4), the angular transition frequencies between the essential energy levels are + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0009-09.png) + + +The second time scale is due to the harmonic oscillation of the phase in the state vector. It can be estimated by freezing the time-dependent coefficients in the Hamiltonian matrix at some time _t_ = _t∗_ and considering Schr¨odinger’s equation with the time-independent Hamiltonian matrix _H∗_ = _H_ ( _t∗_ ). The _N × N_ matrix _H∗_ is Hermitian and can be diagonalized by a unitary transformation, + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0009-11.png) + + +9 + +where the eigenvalues _γk_ are real. By the change of variables **_ψ_** = _X__†_ **_ψ_** , the solution of the diagonalized system follows as + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0010-01.png) + + +corresponding to the period _τk_ = 2 _π/|γk|_ . The shortest period thus follows from the spectral radius of _H∗_ , _ρ_ ( _H∗_ ) = max _k |γk|_ . + +To estimate the time step for the St¨ormer-Verlet method, we require that the shortest period in the solution of Schr¨odinger’s equation must be resolved by at least _CP_ time steps. Taking both time scales into account leads to the time step restriction + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0010-04.png) + + +The value of _CP_ that is needed to obtain a given accuracy in the numerical solution depends on the order of accuracy, the duration of the time integration, as well as the details of the time-stepping scheme. For second order accurate methods such as the St¨ormer-Verlet method, acceptable accuracy for engineering applications can often achieved with _CP ≈_ 40. With the St¨ormer-Verlet method, we note that the time-stepping can become unstable if _CP ≤_ 2, corresponding to a sampling rate below the Nyquist limit. + +After freezing the coefficients, the Hamiltonian (4) becomes + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0010-07.png) + + +We can estimate the spectral radius of _H∗ ∈_ C_N×N_ using the Gershgorin circle theorem [8]. Because _H∗_ is Hermitian, all its eigenvalues are real. As a result, its spectral radius can be bounded by + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0010-09.png) + + +Hence, it is the largest value of ( _|p∗|_ + _|q∗|_ ) that determines the time step. + +Given the parameter vector **_α_** , the control functions are bounded by _p∞_ = max _t |p_ ( _t,_ **_α_** ) _|_ and _q∞_ = max _t |q_ ( _t,_ **_α_** ) _|_ , where the maximum is evaluated for times 0 _≤ t ≤ T_ . Thus, using the estimate + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0010-12.png) + + +in (25) guarantees that the time-dependent phase in the state vector is resolved by at at least _CP_ time steps per shortest period. + +If the optimization imposes amplitude constraints on the parameter vector, _|_ **_α_** _|∞ ≤ αmax_ , those constraints can be used to estimate the time step before the optimization starts. This allows the same time step to be used throughout the iteration and eliminates the need to recalculate the spectral radius of _H∗_ when **_α_** changes. + +10 + +Our implementation of the St¨ormer-Verlet scheme was verified to be second order accurate. It was also found to give approximately the same accuracy as the second order Magnus integrator [10] when the same time step was used in both methods (data not shown to conserve space). + +## **4. Discretizing the objective function and its gradient** + +In this section, we develop a “discretize before optimize” approach in which we first discretize the objective function and then derive a compatible scheme for discretizing the adjoint state equation, which is used for computing the gradient of the objective function. As was outlined in the introduction, our approach builds upon the works of Hager [9], Sanz-Serna [19] and Ober-Bl¨obaum [18]. + +## _4.1. Discretizing the objective function_ + +The St¨ormer-Verlet scheme can be written in terms of the stage variables ( **_U_**_n,i_ _,_ **_V_**_n,i_ ) by substituting ( **_κ_**_n,i_ _,_ **_ℓ_**_n,i_ ) from (20) into (19), + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0011-05.png) + + +and into (21), + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0011-07.png) + + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0011-08.png) + + +Here, _Sn_ = _S_ ( _tn_ ), _Sn_ +1 _/_ 2 = _S_ ( _tn_ + 0 _._ 5 _h_ ), etc. Because _S_ ( _t_ ) = 0, the scheme is block implicit. Note that **_u_**_n_+1 = **_U_**_n,_2 and **_V_**_n,_1 = **_V_**_n,_2 = **_v_** ( _tn_ +1 _/_ 2) + _O_ ( _h_2 ). + +The numerical solution at the final time step provides a second order accurate approximation of the continuous solution operator matrix _UT_ , which we denote _UT h_ . It is used to approximate the matrix overlap function _SV_ ( _UT_ ) in (17), + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0011-11.png) + + +which is then used as the first part of the discrete objective function, + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0011-13.png) + + +11 + +The integral in the objective function (16) can be discretized to second order accuracy by using the Runge-Kutta stage variables, + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0012-01.png) + + +Based on the above formulas we discretize the objective function (16) according to + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0012-03.png) + + +Here, _UT h__α_,**_U_**_α_and**_V_**_α_representthetime-discretesolutionoftheSt¨ormer- Verlet scheme for a given parameter vector **_α_** . We note that _Gh_ ( **_α_** ) can be evaluated by accumulation during the time-stepping of the St¨ormer-Verlet scheme. + +## _4.2. The discrete adjoint approach_ + +The gradient of the discretized objective function can be derived from first order optimality conditions of the corresponding discrete Lagrangian. In this approach, let ( **_µ_**_n_ _j__,_**_ν_** _j__n_) be the adjoint variables and let (**_M_** _j__n,i_ _,_ **_N_** _j__n,i_ ) be Lagrange multipliers. We define the discrete Lagrangian by + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0012-07.png) + + +The first two terms in the sum enforce the initial conditions (27). The terms _Tj_1and_T_2 _j_enforcethetime-steppingupdateformulas(28)-(29)intheSt¨ormer- Verlet scheme, + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0012-09.png) + + +The terms _Tj_3to_T_6 _j_enforcetherelationsbetweenthestagevariables(30)-(33) using the Lagrange multipliers ( **_M_** _j__n,i_ and **_N_** _j__n,i_ ), see Appendix B for details. + +12 + +To derive the discrete adjoint scheme, we note that the discrete Lagrangian (38) has a saddle point if + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0013-01.png) + + +for _n_ = 0 _,_ 1 _, . . . , M_ , _i_ = 1 _,_ 2 and _j_ = 0 _,_ 1 _, . . . , E −_ 1. Here, the set of conditions in (41) result in the St¨ormer-Verlet scheme (27)-(33) for evolving ( **_u_**_n_ _j__,_**_v_** _j__n,_**_U_**_n,i_ _j ,_ **_V_** _j__n,i_ ) forwards in time. The set of conditions in (42) result in a time-stepping scheme for evolving the adjoint variables ( **_µ_**_n_ _j__,_**_ν_** _j__n_) backwards in time, as is made precise in the following lemma. + +**Lemma 1.** _Let Lh be the discrete Lagrangian defined by_ (38) _. Furthermore, let_ ( **_u_**_n_ _j__,_**_v_** _j__n,_**_U_**_n,i_ _j ,_ **_V_** _j__n,i_ ) _satisfy the St¨ormer-Verlet scheme_ (27) _-_ (33) _for a given parameter vector_ **_α_** _. Then, the set of saddle-point conditions_ (42) _are satisfied if the Lagrange multipliers_ ( **_µ_**_n_ _j__,_**_ν_** _j__n_)_arecalculatedaccordingtothereversedtime-_ _stepping scheme,_ + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0013-04.png) + + +_for n_ = _M −_ 1 _, M −_ 2 _, . . ._ 0 _. Because S__T_ = _−S and K__T_ = _K, the slopes satisfy_ + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0013-06.png) + + +_where the stage variables are given by_ + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0013-08.png) + + +13 + +_Proof._ The lemma follows after a somewhat tedious but straightforward calculation shown in detail in Appendix B. + +Corresponding to the continuous Schr¨odinger equation (14), the adjoint state equation (without forcing) is + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0014-02.png) + + +where we used that _S__T_ = _−S_ and _K__T_ = _K_ . + +**Corollary 1.** _The time-stepping scheme_ (44) _-_ (52) _(without forcing) is a consistent approximation of the continuous adjoint state equation_ (53) _. It can be written as a modified partitioned Runge-Kutta method, where the Butcher coefficients are_ + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0014-05.png) + + +_corresponding to the implicit midpoint rule for the_ **_µ_** _-equation and the trapezoidal rule for the_ **_ν_** _-equation in_ (53) _. The modifications to the partitioned RungeKutta scheme concerns the formulae for the slopes,_ (46) _-_ (49) _. Because of the time-levels at which the matrices K and S are evaluated, it is_ not _possible to define Butcher coefficients c__µ_ _i__andcν_ _i__suchthat_ + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0014-07.png) + + +_Proof._ See Appendix C. + +Only the matrices _K_ and _S_ depend explicitly on **_α_** in the discrete Lagrangian. When the saddle point conditions (41) and (42) are satisfied, we can therefore calculate the gradient of _Gh_ by differentiating (38), + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0014-10.png) + + +This relation leads to the following lemma. + +**Lemma 2.** _Let Lh be the discrete Lagrangian defined by_ (38) _. Assume that_ ( **_u_**_n_ _j__,_**_v_** _j__n,_**_U_**_n,i_ _j ,_ **_V_** _j__n,i_ ) _are calculated according to the St¨ormer-Verlet scheme for a given parameter vector_ **_α_** _. Furthermore, assume that_ ( **_µ_**_n_ _j__,_**_ν_** _j__n,_**_X_** _j__n,_**_Y_** _j__n,i_ ) _satisfy the adjoint time-stepping scheme in Lemma 1, subject to the terminal conditions_ + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0014-13.png) + + +14 + +_and the forcing functions_ + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0015-01.png) + + +_Then, the saddle-point conditions_ (41) _and_ (42) _are satisfied and the gradient of the objective function_ (37) _is given by_ + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0015-03.png) + + +_where Sn__′_=_∂S/∂αr_(_tn_)_,K_ _n__′_ +1 _/_ 2=_∂K/∂αr_(_tn_+1_/_2)_,etc._ + +_Proof._ See Appendix D. + +As a result of Lemma 2, all components of the gradient can be calculated from ( **_u_**_n_ _j__,_**_v_** _j__n,_**_U_**_n,i_ _j ,_ **_V_** _j__n,_1 ) and the adjoint variables ( **_µ_**_n_ _j__,_**_ν_** _j__n,_**_X_** _j__n,_**_Y_** _j__n,i_ ). The first set of variables are obtained from time-stepping the St¨ormer-Verlet scheme forward in time, while the second set of variables follow from time-stepping the adjoint scheme backward in time. + +We can avoid storing the time-history of ( **_u_**_n_ _j__,_**_v_** _j__n,_**_U_**_n,i_ _j ,_ **_V_** _j__n,_1 ) by using the time-reversibility of the St¨ormer-Verlet scheme. However, in order to do so, we must first calculate the terminal conditions ( **_u_**_M_ _j__,_**_v_** _j__M_)byevolving(27)-(33) forwards in time. The time-stepping can then be reversed and the gradient of the objective function (56) can be accumulated by simultaneously time-stepping the adjoint system (44)-(52) backwards in time. + +## **5. Quadratic B-splines with carrier waves** + +Let _A_ ( _t_ ) and _φ_ ( _t_ ) be real-valued amplitude and phase functions of time. By taking the control functions in the rotating frame Hamiltonian (4) to be + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0015-10.png) + + +the relation (5) results in the laboratory frame control function + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0015-12.png) + + +We expand the amplitude function in a set of basis functions _{Bk}__D_ _k_ =11and start by considering the case of one carrier wave. We make the ansatz, + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0015-14.png) + + +15 + +where _βk_ are real coefficients. By defining the phase as _φk_ ( _t_ ) = Ω _t_ + _θk_ , + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0016-01.png) + + +where + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0016-03.png) + + +In the laboratory frame, the resulting control function becomes + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0016-05.png) + + +The case with one carrier wave is straightforward to generalize to multiple frequencies, _{_ Ω _ℓ}__N_ _ℓ_ =1_f_.Thisleadstoalaboratoryframecontrolfunctionwitha spectrum that can be precisely specified to match the transition frequencies of the system, + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0016-07.png) + + +The total number of control parameters becomes _D_ = 2 _Nf D_ 1, which equals the size of the parameter vector **_α_** . Here, _Nf_ is the number of frequencies and _D_ 1 _≥_ 1 is the number of basis functions per frequency. + +In this paper we use the quadratic B-spline basis (see Figure 1) to represent the amplitude and phase of the control functions. Here, each basis function is a piecewise quadratic polynomial in time. It is the lowest order B-spline function that has at least one continuous derivative. We define the basis functions on a uniform grid in time, + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0016-10.png) + + +Each basis function _Bm_ ( _t_ ) is centered around _t_ = _tm_ and is easily expressed in terms of the scaled time parameter _τm_ ( _t_ ) = ( _t − tm_ ) _/_ 3 _δ_ , + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0016-12.png) + + +Note that _Bm_ ( _t_ ) is only non-zero in the interval _t ∈_ [ _tm −_ 1 _._ 5 _δ, tm_ +1 _._ 5 _δ_ ]. Thus, for any fixed time _t_ , a control function will only get contributions from at most three B-spline basis functions. This property allows the control functions to be evaluated very efficiently. + +16 + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0017-00.png) + + +Figure 2: A B-spline control function _p_ ( _t,_ **_α_** ) without carrier wave (Ω1 = 0 and _Nf_ = 1). Here, the black dashed line is the control function and the solid colored lines are the individual B- spline basis functions, scaled by _α_1 _m,_ 1.Inthiscase,_D_1= 6. + +## **6. Numerical optimization** + +Our numerical solution of the optimal control problem is based on the general purpose interior-point optimization package IPOPT [21]. This open-source library implements a primal-dual barrier approach for solving large-scale nonlinear programming problems, i.e., it minimizes an objective function subject to inequality (barrier) constraints on the parameter vector. Because the Hessian of the objective function is costly to calculate, we use the L-BFGS algorithm [17] in IPOPT, which only relies on the objective function and its gradient to be evaluated. Inequality constraints that limit the amplitude of the parameter vector **_α_** are enforced internally by IPOPT. + +The routines for evaluating the objective function and its gradient are implemented in the Julia programming language [2], which provides a convenient interface to IPOPT. Given a parameter vector **_α_** , the routine for evaluating the objective function solves the Schr¨odinger equation with the St¨ormer-Verlet scheme and evaluates _Gh_ ( **_α_** ) by accumulation. The routine for evaluating the gradient first applies the St¨ormer-Verlet scheme to calculate terminal conditions for the state variables. It then proceeds by accumulating the gradient _∇αGh_ by simultaneous reversed time-stepping of the discrete adjoint scheme and the St¨ormer-Verlet scheme. These two fundamental routines, together with functions for setting up the Hamiltonians, estimating the time step, setting up constraints on the parameter vector, post-processing and plotting of the results have been implemented in the software package JuQBox, which was used to generate the numerical results below. + +The adjoint gradient implementation has been verified against a centered finite difference approximation of the discrete objective function by perturbing each component of the parameter vector. To further verify our implementation, we also calculated the discrete gradient by differentiating the St¨ormer-Verlet + +17 + +scheme with respect to each component of the parameter vector. This gradient agreed with the adjoint gradient to within 11-12 digits. (Data not shown to conserve space.) + +## _6.1. A CNOT gate on a single qudit with guard levels_ + +To test our methods on a quantum optimal control problem, we consider realizing a CNOT gate on a single qudit with four essential energy levels and two guard levels. The qudit is modeled in the rotating frame of reference using the Hamiltonian (4) with fundamental frequency _ωa/_ 2 _π_ = 4 _._ 10336 GHz and selfKerr coefficient _ξa/_ 2 _π_ = 0 _._ 2198 GHz. We parameterize the two control functions using B-splines with carrier waves and choose the frequencies to be Ω1 = 0, Ω2 = _−ξa_ and Ω3 = _−_ 2 _ξa_ . In the rotating frame, these frequencies correspond to transitions between the ground state and the first exited state, the first and second excited states and the second and third excited states. We discourage population of the fourth and fifth excited states using the weight matrix _W_ = diag[0 _,_ 0 _,_ 0 _,_ 0 _,_ 0 _._ 1 _,_ 1 _._ 0] in _J_ 2 _h_ , see (10). We use _D_ 1 = 10 basis functions per frequency and control function, resulting in a total of _D_ = 60 parameters. The amplitudes of the control functions are limited by the constraint + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0018-03.png) + + +We set the gate duration to _T_ = 100 ns and estimate the time step using the technique in Section 3.2. To guarantee at least _CP_ = 40 time steps per period, we use _M_ = 8 _,_ 796 time steps, corresponding to _h ≈_ 1 _._ 136 _·_ 10_−_2 ns. + +As initial guess for the elements of the parameter vector, we use a random number generator with a uniform distribution in [ _−_ 0 _._ 01 _,_ 0 _._ 01]. In Figure 3 we present the convergence history with the two parameter thresholds _αmax/_ 2 _π_ = 4 MHz and 3 MHz, respectively. We show the objective function _G_ , decomposed into _J_ 1 _h_ and _J_ 2 _h_ , together with the norm of the dual infeasibility, _∥∇αG − z∥∞_ , that IPOPT uses to monitor convergence, see [21] for details. For the case with _αmax/_ 2 _π_ = 3 MHz, IPOPT converges well and needs 126 iteration to reduce the dual infeasibility to 10_−_5 , which was used as convergence criteria. However, when the parameter constraint is relaxed to _αmax/_ 2 _π_ = 4 MHz, the convergence of IPOPT stalls after about 100 iterations and is terminated after 200 iterations. + +For the converged solution with parameter constraint _αmax/_ 2 _π_ = 3 MHz, the two parts of the objective function are _J_ 1 _h ≈_ 1 _._ 47 _·_ 10_−_4 and _J_ 2 _h ≈_ 4 _._ 72 _·_ 10_−_5 , corresponding to a trace fidelity greater than 0 _._ 9998. The population of the guard states remains small for all times and initial conditions. In particular, the “forbidden” state _|_ 5 _⟩_ has a population that remains below 4 _._ 04 _·_ 10_−_7 , see Figure 4. The optimized control functions are shown in Figure 5 and the population of the essential states, corresponding to the four initial conditions of the CNOT gate, are presented in Figure 6. + +Even though the dual infidelity does not reach the convergence criteria with the parameter threshold _αmax/_ 2 _π_ = 4 MHz, the resulting control functions give a very small objective function. Here, _J_ 1 _h ≈_ 8 _._ 56 _·_ 10_−_5 and _J_ 2 _h ≈_ 4 _._ 15 _·_ 10_−_5 , + +18 + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0019-00.png) + + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0019-01.png) + + +Figure 3: Convergence of the IPOPT iteration for the CNOT gate with the parameter constraint _∥_ **_α_** _∥∞ ≤ αmax_ . Here, _αmax/_ 2 _π_ = 4 MHz (left) and _αmax/_ 2 _π_ = 3 MHz (right). + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0019-03.png) + + +Figure 4: The population of the “forbidden” state _|_ 5 _⟩_ as function of time for the four initial conditions of the CNOT gate. Here, _αmax/_ 2 _π_ = 3 MHz. + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0019-05.png) + + +Figure 5: The rotating frame control functions _p_ ( _t_ ) (blue) and _q_ ( _t_ ) (orange) for realizing a CNOT gate with _D_ 1 = 10 basis function per carrier wave and three carrier wave frequencies. Here, _αmax/_ 2 _π_ = 3 MHz. + +19 + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0020-00.png) + + +Figure 6: The population of the states _|_ 0 _⟩_ (blue), _|_ 1 _⟩_ (orange), _|_ 2 _⟩_ (green) and _|_ 3 _⟩_ (purple), as function of time, for each initial condition of the CNOT gate. Here, _αmax/_ 2 _π_ = 3 MHz. + +corresponding to a trace fidelity greater than 0 _._ 9999. The population of the “forbidden” state _|_ 5 _⟩_ has a population that remains below 3 _._ 39 _·_ 10_−_7 . + +## _6.2. The Hessian of the objective function_ + +The numerical results shown in Figure 3 illustrate that the convergence properties of the optimization algorithm depend on the parameter constraints. To gain clarity into the local landscape of the optima we study the Hessian of the objective function. Let the optima correspond to the parameter vector **_α_**_∗_ . Based on the adjoint scheme for calculating the gradient, we can approximate the elements of the Hessian matrix using a centered finite difference approximation, + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0020-05.png) + + +for _j, k_ = 1 _,_ 2 _, . . . , D_ . To perform this calculation, the gradient must be evaluated for the 2 _D_ parameter vectors ( **_α_**_∗_ _± ε_ **_e_** _k_ ). Because the objective function and the parameter vector are real-valued, the gradient and the Hessian are also real-valued. Due to the finite difference approximation, the matrix _L_ is only approximately equal to the Hessian. The accuracy in _L_ is estimated in Table 1 by studying the norm of its asymmetric part, which is zero for the Hessian. Based on this experiment we infer that _ε_ = 10_−_6 is appropriate to use for approximating the Hessian in (61). To eliminate spurious effects from the asymmetry in the _L_ matrix, we study the spectrum of its symmetric part, _Ls_ = 0 _._ 5( _L_ + _L__T_ ). Because it is real and symmetric, it has a complete set of eigenvectors and all eigenvalues are real. + +20 + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0021-00.png) + + +Table 1: The Frobenius norm of the symmetric and asymmetric parts of the approximate Hessian, _L_ , for the case _αmax/_ 2 _π_ = 3 _._ 0 MHz. + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0021-02.png) + + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0021-03.png) + + +Figure 7: The eigenvalues of the symmetric part of the approximate Hessian, 0 _._ 5( _L_ + _L__T_ ), evaluated at the optima for the parameter thresholds _αmax/_ 2 _π_ = 4 MHz (blue triangles) and _αmax/_ 2 _π_ = 3 MHz (orange circles). The positive eigenvalues are shown on a log-scale on the left and the small eigenvalues are shown on a linear scale on the right. + +The eigenvalues of the Hessian are shown in Figure 7 for both values of the parameter threshold, _αmax_ . Two properties of the spectra are noteworthy. First, a few eigenvalues are negative. This may be an artifact related to the elements of the parameter vector that are close to their bounds. As a result the landscape of the objective function may not be accurately represented by the corresponding components of the Hessian. The second interesting property is that the 15 largest eigenvalues are significantly larger than the rest. This indicates that the control functions are essentially described by the 15 eigenvectors associated with those eigenvalues. As a result, the objective function varies much faster in those directions than in the directions of the remaining 45 eigenvectors and this may hamper the convergence of the optimization algorithm in that subspace. However, most of those 45 eigenvalues become larger when the parameter threshold is reduced from _αmax/_ 2 _π_ = 4 MHz to _αmax/_ 2 _π_ = 3 MHz. This indicates that the constraints on the parameter vector have a regularizing effect on the optimization problem and may explain why the latter case converges better (see Figure 3). + +## **7. Comparing JuQBox with QuTiP/pulse optim and Grape-TF** + +The QuTiP/pulse ~~o~~ ptim package is part of the QuTiP [11] framework and implements the GRAPE algorithm in the Python language. The Grape-TF code + +21 + +(TF is short for Tensorflow [1]) is also implemented in Python and provides an enhanced implementation of the GRAPE algorithm, as described by Leung et al. [13]. It is callable from QuTiP and shares a similar problem setup with the pulse ~~o~~ ptim function. + +To compare the JuQBox code with pulse ~~o~~ ptim and Grape-TF, we consider a set of SWAP gates. These gates transform the ground state _|_ 0 _⟩_ to excited state _|d⟩_ , and vice versa. The transformation can be described by the unitary matrix + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0022-02.png) + + +which involves _E_ = _d_ + 1 essential states. To evaluate how much leakage occurs to higher energy levels, we add one guard (forbidden) level ( _G_ = 1) and evolve a total of _N_ = _d_ +2 states in Schr¨odinger’s equation. As before, the guard level is left unspecified in the target gate transformation. We consider implementing the SWAP gates on a multi-level qudit that can be described by the fundamental frequency _ωa/_ 2 _π_ = 4 _._ 8 GHz and the self-Kerr coefficient _ξa/_ 2 _π_ = 0 _._ 22 GHz. We apply the rotating wave approximation, where the angular frequency of the rotation is _ωa_ , resulting in the Hamiltonian model (4). As a realistic model for current superconducting quantum devices, we impose the control amplitude restrictions + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0022-04.png) + + +in the rotating frame of reference. + +## _7.1. Setup of simulation codes_ + +QuTiP/pulse optim can minimize the target gate fidelity, _G_ 1, but does not suppress occupation of higher energy states. Thus, it does _not_ minimize terms of the type _G_ 2. As a proxy for _G_ 2, we append one additional energy level to the simulation and measure its occupation as an estimate of leakage to higher energy states. In pulse ~~o~~ ptim, the control functions are discretized on the same grid in time as Schr¨odinger’s equation and no smoothness conditions are imposed. In our tests, we use a random initial guess for the parameter vector. + +Grape-TF discretizes the control functions on the same grid in time as Schr¨odinger’s equation. It minimizes an objective function that consists of a number of user-configurable parts. In our test, we minimize the gate infidelity ( _G_ 1) and the occupation of one guard (forbidden) energy level (similar to _G_ 2). To smooth the control functions in time, the objective function also contains additional terms to minimize their and second time derivatives. The various + +22 + +parts of the objective function are weighted together by user-specified coefficients. The gradient of the objective function is calculated using the automatic differentiation (AD) technique, as implemented in the Tensorflow package. In our tests, we use a random initial guess for the control vector. + +In JuQBox, we trigger the first _d_ transition frequencies in the Hamiltonian by using _d_ carrier waves in the control functions, with angular frequencies + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0023-02.png) + + +Similar to pulse ~~o~~ ptim and Grape-TF, a pseudo-random number generator is used to construct the initial guess for the parameter vector. + +The pulse ~~o~~ ptim and JuQBox simulations were run on a Macbook Pro with a 2.6 GHz Intel iCore-7 processor. To utilize the GPU acceleration in Tensorflow, the Grape-TF simulations were run on one node of the Pascal machine at Livermore Computing, where each node has an Intel XEON E5-2695 v4 processor with two NVIDIA P-100 GPUs. + +## _7.2. Numerical results_ + +A SWAP gate where the control functions meet the control amplitude bounds (63) can only be realized if the gate duration is sufficiently long. Furthermore, the minimum gate duration increases with _d_ . For each value of _d_ , we used numerical experiments to determine a duration _Td_ such that at least two of the three simulation codes could find a solution with a small gate infidelity. For JuQBox, we used the technique in Section 3.2 with _CP_ = 80 to obtain the number of time steps. The number of control parameters follow from _D_ = 2 _Nf D_ 1, where _Nf_ = _d_ equals the number of carrier wave frequencies and _D_ 1 is the number of B-splines per control functions. Here, _D_ 1 = 10 for _d_ = 3 _,_ 4 _,_ 5 and _D_ 1 = 20 for _d_ = 6. For pulse ~~o~~ ptim and Grape-TF, we calculate the number of time steps based on the shortest transition period, corresponding to the highest transition frequency in the system. We then use 40 time steps per shortest transition period to resolve the control functions. For both GRAPE methods there are 2 control parameters per time step. The main simulation parameters are given in Table 2. + +Optimization results for the pulse ~~o~~ ptim, Grape-TF and JuQBox codes are presented in Tables 3, 4 and 5. The pulse ~~o~~ ptim code generates piecewise constant control functions that are very noisy and may therefore be hard to realize experimentally. To obtain a realistic estimate of the resulting dynamics, we interpolate the optimized control functions on a grid with 20 times smaller time step and use the `mesolve()` function in QuTiP to calculate the evolution of the system from each initial state. We then evaluate the gate infidelity using the evolved states at the final time, denoted by _G_ 1_∗_inTable3.Sincethecontrol functions from Grape-TF and JuQBox are significantly smoother, we report the target gate fidelities as calculated by those codes. + +For the _|_ 0 _⟩↔|_ 3 _⟩_ , _|_ 0 _⟩↔|_ 4 _⟩_ and _|_ 0 _⟩↔|_ 5 _⟩_ SWAP gates, all three codes produce control functions with very small gate infidelities. We note that the population of the guard level, _|ψ_(_d_+1) _|_2 , is about an order of magnitude larger + +23 + +|||# tim|e steps|# para|meters| +|---|---|---|---|---|---| +|_d_|_Td_ [ns]|JuQBox|GRAPE|JuQBox|GRAPE| +|3|140|14,787|4,480|60|8,960| +|4|215|37,843|7,568|80|15,136| +|5|265|69,962|11,661|100|23,322| +|6|425|157,082|22,441|240|44,882| + + + +Table 2: Gate duration, number of time steps ( _M_ ) and total number of control parameters ( _D_ ) in the _|_ 0 _⟩↔|d⟩_ SWAP gate simulations. The number of time steps and control parameters are the same for pulse ~~o~~ ptim and Grape-TF. + +|_d_|_G__∗_
1|_|ψ_(_d_+1)_|_2
_∞_|_|p|∞_[MHz]|_|q|∞_[MHz]|# iter|CPU [s]| +|---|---|---|---|---|---|---| +|3|4.35e-6|9.41e-3|9.00|9.00|38|30| +|4|3.91e-5|1.20e-2|9.00|9.00|93|108| +|5|1.57e-4|8.77e-3|9.00|9.00|215|385| +|6|**1.76e-3**|**4.48e-2**|9.00|9.00|246|894| + + + +Table 3: QuTiP/pulse ~~o~~ ptim results for _|_ 0 _⟩↔|d⟩_ SWAP gates. Note the larger infidelity and guard state population for _d_ = 6. + +|_d_|_G_1|_|ψ_(_d_+1)_|_2
_∞_|_|p|∞_[MHz]|_|q|∞_[MHz]|# iter|CPU [s]| +|---|---|---|---|---|---|---| +|3|8.76e-6|4.03e-3|6.98|8.83|78|2,062| +|4|1.52e-5|3.39e-3|6.87|6.54|128|10,601| +|5|2.80e-5|1.78e-3|7.21|7.62|161|28,366| +|6|**4.89e-1**|2.33e-5|0.73|0.74|93|81,765| + + + +Table 4: Grape-TF results for _|_ 0 _⟩↔|d⟩_ SWAP gates. Note the very large infidelity for _d_ = 6. These simulations used two NVIDIA P-100 GPUs to accelerate Tensorflow. + +|_d_|_G_1|_|ψ_(_d_+1)_|_2
_∞_|_|p|∞_[MHz]|_|q|∞_[MHz]|# iter|CPU| +|---|---|---|---|---|---|---| +|3|2.71e-5|1.92e-3|7.59|8.99|177|55| +|4|4.91e-5|1.23e-3|7.78|5.33|166|151| +|5|4.95e-5|1.25e-3|7.42|7.24|173|291| +|6|7.41e-6|4.41e-3|4.55|5.39|229|1255| + + + +Table 5: JuQBox results for _|_ 0 _⟩↔|d⟩_ SWAP gates. + +24 + +with pulse ~~o~~ ptim than with JuQBox; the guard level population from Grape-TF are somewhere in between. The most significant difference between the results occur for the _d_ = 6 SWAP gate. Here, the Grape-TF code fails to produce a small gate infidelity after running for almost 23 hours and the pulse ~~o~~ ptim code results in a gate fidelity that is about 2 orders of magnitude larger than JuQBox. + +While pulse ~~o~~ ptim and JuQBox require comparable amounts of CPU time to converge, the Grape-TF code is between 50-100 times slower, despite the GPU acceleration. + +We proceed by analyzing the optimized control functions and take the _|_ 0 _⟩↔ |_ 5 _⟩_ SWAP gate as a representative example. In this case, the relevant transition frequencies in the laboratory frame of reference are + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0025-03.png) + + +To compare the optimized control functions, we evaluate the corresponding laboratory frame control function using (5) and study its Fourier spectrum. Results from the pulse ~~o~~ ptim, Grape-TF and JuQBox simulations are presented in Figure 8. We first note that pulse ~~o~~ ptim produces a significantly noisier control function compared to the other two codes. The control function from Grape-TF is significantly smoother, even though its spectrum includes some noticeable peaks at frequencies that do not correspond to transition frequencies in the system. The JuQBox simulation results in a laboratory frame control function where each peak in the spectrum corresponds to a transition frequency in the Hamiltonian. + +## **8. Conclusions** + +In this paper we have developed numerical methods for optimizing control functions for realizing logical gates in a closed quantum system. The quantum state is governed by Schr¨odinger’s equation, which is a time-dependent Hamiltonian system. To ensure long-time numerical accuracy we discretize it using the symplectic St¨ormer-Verlet method, which can be written as a partitioned Runge-Kutta scheme. Our main theoretical contribution is the derivation of a compatible time-discretization of the adjoint state equation, such that the gradient of the discrete objective function can be calculated exactly. This scheme generalizes Ober-Bl¨obaum’s [18] methods to the case of a time-dependent Hamiltonian system. + +We have also introduced a parameterization of the control functions based on B-splines with built-in carrier waves. The carrier waves are used to specify the frequency spectra of the control functions, while the B-spline functions specify their envelope and phase. This approach allows the number of control parameters to be independent of, and significantly smaller than, the number of time steps for integrating Schr¨odinger’s equation. Our numerical solution of the optimal control problem is based on the general purpose interior-point optimization package IPOPT [21], which implements a primal-dual barrier approach + +25 + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0026-00.png) + + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0026-01.png) + + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0026-02.png) + + +Figure 8: Magnitude of the Fourier spectrum of the laboratory frame control function for the _|_ 0 _⟩↔|_ 5 _⟩_ SWAP gate. + +26 + +for minimizing the objective function subject to amplitude constraints on the parameter vector. We optimized the control functions for a CNOT gate with two guard states, resulting in a gate trace fidelity greater than 0.9999. Having a moderate number of control parameters enabled us to study the spectrum of the Hessian of the objective function at an optima. We found that imposing tighter bounds on the parameter vector results in a Hessian with larger eigenvalues and thus improves the convergence of the optimization algorithm. + +The performance of the proposed algorithm, implemented in a code called JuQBox, was compared with two implementations of the GRAPE algorithm: QuTiP/pulse optim [11] and Grape-Tensorflow [13]. JuQBox was found to produce significantly smoother control functions than QuTiP/pulse ~~o~~ ptim, while using about the same computational resources. JuQBox was also found to run about 50-100 times faster than Grape-Tensorflow. + +In future work, it would be interesting to study if the convergence properties of the optimization algorithm can be improved by modifying the objective function. We also intend to generalize our approach to solve optimal control problem for open quantum systems. + +## **Acknowledgment** + +We would like to thank Prof. Daniel Appel¨o for bringing the St¨ormer-Verlet method to our attention. + +This work was supported in part by LLNL laboratory directed research and development project 20-ERD-028 and in part by DOE office of advanced scientific computing research (OASCR) under the Advanced Research in Quantum Computing (ARQC) program, award 2019-LLNL-SCW-1683. + +This work performed under the auspices of the U.S. Department of Energy by Lawrence Livermore National Laboratory under Contract DE-AC5207NA27344. This is contribution LLNL-JRNL-800457. + +## **Appendix A. The Hamiltonian in a rotating frame of reference** + +In the laboratory frame of reference, the Hamiltonian matrix for a single superconducting qudit can be modeled by + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0027-09.png) + + +Here, _ωa >_ 0 and _ξa_ are given real constants and _f_ ( _t,_ **_α_** ) is a real-valued function of time that depend on the parameter vector **_α_** . Furthermore, _a_ is the lowering matrix, + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0027-11.png) + + +27 + +and the raising matrix _a__†_ is its adjoint (conjugate transpose). + +To derive the rotating frame transformation, we consider the time-dependent change of variable + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0028-02.png) + + +We have + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0028-04.png) + + +After some algebra, the Schr¨odinger equation (7) and the identity _RR_˙_†_ = _−RR_˙_†_ gives: + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0028-06.png) + + +The rotating frame of reference is introduced by taking the unitary transformation to be + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0028-08.png) + + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0028-09.png) + + +From (A.2) and (A.3), the first term in the Hamiltonian (A.1) is canceled by the term _iR_˙ ( _t_ ) _R_ ( _t_ )_†_ . Furthermore, _a__†_ _a__†_ _aa_ = ( _a__†_ _a_ )2 _− a__†_ _a_ and both _a__†_ _a_ and ( _a__†_ _a_ )2 commute with _R_ ( _t_ ). After noting that _Ra__†_ _R__†_ = _e__iωat_ _a__†_ , the transformed Hamiltonian can be written + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0028-11.png) + + +To slow down the time scales in the control function, we want to absorb the highly oscillatory factors exp( _±iωat_ ) into _f_ ( _t_ ). Because the control function _f_ ( _t_ ) is real-valued, this can only be done in an approximate fashion. We make the ansatz, + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0028-13.png) + + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0028-14.png) + + +where _p_ ( _t_ ) and _q_ ( _t_ ) are real-valued functions. After some algebra, the transformed Hamiltonian (A.4) becomes + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0028-16.png) + + +The rotating frame approximation follows by ignoring the terms that oscillate with twice the frequency, exp( _±_ 2 _iωat_ ), resulting in the transformed Schr¨odinger + +28 + +system, + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0029-01.png) + + +Here, _Hd_ is called the drift Hamiltonian. When _ξa ≪ ωa_ , the state vector varies on a significantly slower time scale in the rotating frame than in the laboratory frame. + +In the remainder of the paper, the Schr¨odinger equation is always solved under the rotating frame approximation and we drop the tildes on the state vector and the Hamiltonian matrices. + +## **Appendix B. Derivation of the discrete adjoint scheme** + +We seek to determine a scheme for evolving the Lagrange multiplier (adjoint) variables to satisfy the first order optimality conditions (42). In the following, let _δr,s_ denote the usual Kronecker delta function. The terms _Tj_3to_T_6 _j_in (38) enforce the relations between the stage variables (30)-(33) according to + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0029-06.png) + + +Taking the derivative of (38) with respect to **_u_**_r_ + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0029-08.png) + + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0029-09.png) + + +which gives the conditions + +Similarly, differentiating (38) with respect to **_v_** _j__r_gives + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0029-12.png) + + +29 + +which leads to the conditions + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0030-01.png) + + +Next we take the derivative of (38) with respect to **_U_** _j__n,_1 , + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0030-03.png) + + +which, using the fact that _Sn__T_=_−Sn_and_K_ _n__T_=_Kn_,wemaywriteas + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0030-05.png) + + +Repeating this procedure for the derivative with respect to **_U_** _j__n,_2 gives + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0030-07.png) + + +30 + +which we may write compactly as + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0031-01.png) + + +Taking the derivative of (38) with respect to **_V_** _j__n,_1 gives the set of equations + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0031-03.png) + + +which gives the condition + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0031-05.png) + + +31 + +Similarly, taking the derivative with respect to **_V_** _j__n,_2 gives + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0032-01.png) + + +giving + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0032-03.png) + + +In summary, the first order optimality conditions (42) are satisfied if the following equations hold: + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0032-05.png) + + +32 + +We now consider the following change of variables + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0033-01.png) + + +which, upon substitution into (B.7)-(B.10), gives the set of equations + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0033-03.png) + + +By adding (B.14)-(B.15), + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0033-05.png) + + +Similarly, by adding (B.16)-(B.17), + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0033-07.png) + + +Thus, (B.5)-(B.6) can be rewritten as + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0033-09.png) + + +By combining **_X_** _j__n_=**_µ_**_n_ _j_+1 + **_M_** _j__n,_2 and (B.15), + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0033-11.png) + + +33 + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0034-00.png) + + +The time-stepping scheme is completed by the relation + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0034-02.png) + + +The scheme (B.20)-(B.24) may be written in the form of Lemma 1 by defining the slopes according to (46)-(49). This completes the proof of the lemma. + +## **Appendix C. Proof of Corollary 1** + +By rearranging (44) and (45), + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0034-06.png) + + +Hence, _b__µ_ 1=_bµ_ 2= 1_/_2and_bν_ 1=_bν_ 2= 1_/_2. To express the stage variables in standard form we substitute (C.1) into (50) and define **_X_** _j__n,_1 = **_X_** _j__n,_2 = **_X_** _j__n_.Similarly,wesubstitute(C.2)into(51)and (52), resulting in + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0034-08.png) + + +From these relations we can identify _a__µ_ 11=_aµ_ 21=1_/_2and_aµ_ 12=_aµ_ 22=0. Furthermore, _a__ν_ 11=_a_ 12_ν_= 0and_aν_ 21=_aν_ 22= 1_/_2. For the case without forcing, the formulae for the slopes, (50)-(52), become + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0034-10.png) + + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0034-11.png) + + +They are consistent approximations of the time derivatives **_µ_** ˙ ( _tn_ ) and **_ν_** ˙ ( _tn_ ), respectively. The scheme is therefore a consistent approximation of the continuous adjoint system. + +34 + +## **Appendix D. Computing the gradient of the discrete objective function** + +Given a solution that satisfies the saddle point conditions of (41) and (42), the gradient of _Lh_ ( **_α_** ) satisfies + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0035-02.png) + + +The gradient of _Lh_ with respect to **_α_** only gets a contribution from the terms in _Tj__q_thatinvolvethematrices_K_and_S_.Let_S_ _n__′_=_∂S/∂αr_(_tn_)and_K_ _n__′_= _∂K/∂αr_ ( _tn_ ). We have, + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0035-04.png) + + +We note that + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0035-06.png) + + +Let **_X_** _j__n_and**_Y_** _j__n,i_ be defined by (B.11)-(B.13). We have, + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0035-08.png) + + +Thus, + +Furthermore, from the relation (B.13), + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0036-01.png) + + +We can further simplify the expressions by recognizing that **_V_**_n,_1 = **_V_**_n,_2 . By collecting the terms, + + +![](.figures/arxiv__2001.01013/2001.01013.pdf-0036-03.png) + + +This completes the proof of the lemma. + +## **References** + +- [1] Mart´ın Abadi, Paul Barham, Jianmin Chen, Zhifeng Chen, Andy Davis, Jeffrey Dean, Matthieu Devin, Sanjay Ghemawat, Geoffrey Irving, Michael Isard, et al. Tensorflow: A system for large-scale machine learning. In _12th USENIX Symposium on Operating Systems Design and Implementation (OSDI 16)_ , pages 265–283, 2016. + +- [2] J. Bezanson, A. Edelman, S. Karpinski, and V. B. Shah. Julia: A fresh approach to numerical computing. _SIAM Review_ , 59(1):65–89, 2017. + +- [3] A. Borz`ı, G. Ciarmella, and M. Sprengel. _Formulation and Numerical Solution of Quantum Control Problems_ . Computational science and engineering. SIAM, 2017. + +- [4] T. Caneva, T. Calarco, and S. Montangero. Chopped random-basis quantum optimization. _Physical Review A_ , 84(2), Aug 2011. + +- [5] P. de Fouquieres, S.G. Schirmer, S.J. Glaser, and Ilya Kuprov. Second order gradient ascent pulse engineering. _Journal of Magnetic Resonance_ , 212(2):412–417, Oct 2011. + +- [6] L. Emsley and G. Bodenhausen. Gaussian pulse cascades: New analytical functions for rectangular selective inversion and in-phase excitation in NMR. _Chem. Phys._ , 165(6):469–476, 1989. + +- [7] B. Ewing, S. J. Glaser, and G. P. Drobny. Development and optimization of shaped NMR pulses for the study of coupled spin systems. _Chem. Phys._ , 147:121–129, 1990. + +36 + +- [8] G. H. Golub and C. F. Van Loan. _Matrix Computations_ . Johns Hopkins University Press, 1996. + +- [9] William W. Hager. Runge-Kutta methods in optimal control and the transformed adjoint system. _Numerische Mathematik_ , 87(2):247–282, Dec 2000. + +- [10] E. Hairer, C. Lubich, and G. Wanner. _Geometric Numerical Integration_ . Number 31 in Springer series in computational mathematics. SpringerVerlag, Heidelberg, 2nd edition, 2006. + +- [11] J.R. Johansson, P.D. Nation, and Franco Nori. Qutip 2: A python framework for the dynamics of open quantum systems. _Computer Physics Communications_ , 184(4):1234 – 1240, 2013. + +- [12] N. Khaneja, T. Reiss, C. Kehlet, T. Schulte-Herbruggen, and S. Glaser. Optimal control of coupled spin dynamics: design of NMR pulse sequences by gradient ascent algorithms. _J. Magnetic Resonance_ , 172:296–305, 2005. + +- [13] N. Leung, M. Abdelhafez, Jens Koch, and D. Schuster. Speedup for quantum optimal control from automatic differentiation based on graphics processing units. _Phys. Rev. A_ , 95:0432318, 2017. + +- [14] Dennis Lucarelli. Quantum optimal control via gradient ascent in function space and the time-bandwidth quantum speed limit. _Physical Review A_ , 97(6), Jun 2018. + +- [15] S. Machnes, E. Ass´emat, D. Tannor, and F. K. Wilhelm. Tunable, flexible, and efficient optimization of control pulses for practical qubits. _Physical Review Letters_ , 120(15), Apr 2018. + +- [16] M. Nielsen and I. Chuang. _Quantum computation and quantum information_ . Cambridge University Press, 2000. + +- [17] J. Nocecdal and S. J. Wright. _Numerical Optimization_ . Springer, 2nd edition, 2006. + +- [18] Sina Ober-Bl¨obaum. _Discrete mechanics and optimal control_ . PhD thesis, University of Paderborn, 2008. + +- [19] J. M. Sanz-Serna. Symplectic Runge–Kutta schemes for adjoint equations, automatic differentiation, optimal control, and more. _SIAM Review_ , 58(1):3–33, 2016. + +- [20] Yunong Shi, Nelson Leung, Pranav Gokhale, Zane Rossi, David I. Schuster, Henry Hoffmann, and Frederic T. Chong. Optimized compilation of aggregated instructions for realistic quantum computers. _Proceedings of the Twenty-Fourth International Conference on Architectural Support for Programming Languages and Operating Systems - ASPLOS ’19_ , 2019. + +37 + +- [21] A. W¨achter and L. T. Biegler. On the implementation of an interior-point filter line-search algorithm for large-scale nonlinear programming. _Mathematical Programming_ , 106(1):25–57, Mar 2006. + +- [22] D. B. Zax, G. Goelman, and S. Vega. Amplitude-modulated composite pulses. _J. Magn. Reson._ , 80(2):375–382, 1988. + +38 diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/references/rendered/2105.14377_diagnosing-barren-plateaus-with-tools-from-quantum-optimal-c.md b/tracks/qcs/solutions/frustration-free/challenge-113/references/rendered/2105.14377_diagnosing-barren-plateaus-with-tools-from-quantum-optimal-c.md new file mode 100644 index 000000000..05d518134 --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/references/rendered/2105.14377_diagnosing-barren-plateaus-with-tools-from-quantum-optimal-c.md @@ -0,0 +1,1902 @@ +--- +source: "https://arxiv.org/abs/2105.14377" +type: "arxiv" +canonical_id: "2105.14377" +title: "Diagnosing Barren Plateaus with Tools from Quantum Optimal Control" +authors: "Martin Larocca, Piotr Czarnik, Kunal Sharma, Gopikrishnan Muraleedharan, Patrick J. Coles, M. Cerezo" +year: "2022" +venue: "Quantum" +arxiv_id: "2105.14377" +doi: "10.22331/q-2022-09-29-824" +full_text: yes +--- + +# Diagnosing Barren Plateaus with Tools from Quantum Optimal Control + +**Authors:** Martin Larocca, Piotr Czarnik, Kunal Sharma, Gopikrishnan Muraleedharan, Patrick J. Coles, M. Cerezo + +**Citation:** Quantum, vol. 6, pp. 824, 2022 + +**arXiv:** [2105.14377](https://arxiv.org/abs/2105.14377) + +**DOI:** [10.22331/q-2022-09-29-824](https://doi.org/10.22331/q-2022-09-29-824) + +## Abstract + +Variational Quantum Algorithms (VQAs) have received considerable attention due to their potential for achieving near-term quantum advantage. However, more work is needed to understand their scalability. One known scaling result for VQAs is barren plateaus, where certain circumstances lead to exponentially vanishing gradients. It is common folklore that problem-inspired ansatzes avoid barren plateaus, but in fact, very little is known about their gradient scaling. In this work we employ tools from quantum optimal control to develop a framework that can diagnose the presence or absence of barren plateaus for problem-inspired ansatzes. Such ansatzes include the Quantum Alternating Operator Ansatz (QAOA), the Hamiltonian Variational Ansatz (HVA), and others. With our framework, we prove that avoiding barren plateaus for these ansatzes is not always guaranteed. Specifically, we show that the gradient scaling of the VQA depends on the degree of controllability of the system, and hence can be diagnosed through the dynamical Lie algebra g obtained from the generators of the ansatz. We analyze the existence of barren plateaus in QAOA and HVA ansatzes, and we highlight the role of the input state, as different initial states can lead to the presence or absence of barren plateaus. Taken together, our results provide a framework for trainability-aware ansatz design strategies that do not come at the cost of extra quantum resources. Moreover, we prove no-go results for obtaining ground states with variational ansatzes for controllable system such as spin glasses. Our work establishes a link between the existence of barren plateaus and the scaling of the dimension of g. + +## Full Text + +# Diagnosing barren plateaus with tools from quantum optimal control + +Martín Larocca1,2 , Piotr Czarnik2 , Kunal Sharma3,2 , Gopikrishnan Muraleedharan2 , Patrick J. Coles2 , and M. Cerezo4,5 + +> 1Departamento de Física “J. J. Giambiagi” and IFIBA, FCEyN, Universidad de Buenos Aires, 1428 Buenos Aires, Argentina + +> 2Theoretical Division, Los Alamos National Laboratory, Los Alamos, New Mexico 87545, USA + +> 3Hearne Institute for Theoretical Physics and Department of Physics and Astronomy, Louisiana State University, Baton Rouge, LA USA + +> 4Information Sciences, Los Alamos National Laboratory, Los Alamos, NM 87545, USA + +> 5Center for Nonlinear Studies, Los Alamos National Laboratory, Los Alamos, New Mexico 87545, USA + +**Variational Quantum Algorithms (VQAs) have received considerable attention due to their potential for achieving near-term quantum advantage. However, more work is needed to understand their scalability. One known scaling result for VQAs is barren plateaus, where certain circumstances lead to exponentially vanishing gradients. It is common folklore that problem-inspired ansatzes avoid barren plateaus, but in fact, very little is known about their gradient scaling. In this work we employ tools from quantum optimal control to develop a framework that can diagnose the presence or absence of barren plateaus for problem-inspired ansatzes. Such ansatzes include the Quantum Alternating Operator Ansatz (QAOA), the Hamiltonian Variational Ansatz (HVA), and others. With our framework, we prove that avoiding barren plateaus for these ansatzes is not always guaranteed. Specifically, we show that the gradient scaling of the VQA depends on the degree of controllability of the system, and hence can be diagnosed through the dynamical Lie algebra** g **obtained from the generators of the ansatz. We analyze the existence of barren plateaus in QAOA and HVA ansatzes, and we highlight the role of the input state, as different initial states can lead to the presence or absence of barren plateaus. Taken together, our results provide a framework for trainabilityaware ansatz design strategies that do not come at the cost of extra quantum resources. Moreover, we prove no-go results for obtaining ground states with variational ansatzes for controllable system such as spin glasses. Our work establishes a link between the existence of barren plateaus and the scaling of the dimension of** g **.** + +## 1 INTRODUCTION + +Quantum computers hold the promise to achieve computational speed-ups over classical supercomputers for certain tasks [1, 2, 3, 4]. However, despite recent tremendous progress in quantum technologies, present-day quantum devices (known as Noisy Intermediate-Scale Quantum (NISQ) devices) are constrained by the limited number of qubits, connectivity, and by the presence of quantum noise [5]. Hence, it becomes crucial to determine what are the capabilities and limitations of NISQ computers to achieving a quantum advantage. + +One of the most promising computational models for making use of near-term quantum computers are Variational Quantum Algorithms (VQAs) [6]. Here, a task of interest is encoded into a parametrized cost function _C_ ( **_θ_** ) that is efficiently computable on a noisy quantum computer. Part of the computational complexity is pushed onto classical computers by leveraging the power of classical optimizer that train the parameters **_θ_** and minimize the cost. VQAs have been proposed for tasks such as solving linear systems of equations [7, 8, 9] or performing dynamical quantum simulations [10, 11, 12, 13, 14, 15, 16, 17], as well as for many others relevant applications [18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30]. + +Despite the wide application range of VQAs, their widespread use is still limited by several challenges that can hinder their success. For instance, it has been shown that the optimization task associated with minimizing the VQA cost function is in general an NPhard non-convex optimization problem [31]. Moreover, despite the typical difficulties encountered in classical non-convex optimization tasks, there are new challenges that arise when training the parameters of VQAs such as hardware noise, or the limited precision arising from a limited number of shots. These difficulties have then led to several quantum-aware optimizers being developed [32, 33, 34, 10, 35, 36]. + +In addition, certain VQAs have been shown to exhibit the so-called barren plateau phenomenon, where + +Accepted in Quantum 2022-08-31, click title to verify. Published under CC-BY 4.0. + +1 + +the cost function becomes untrainable due to gradients that vanish, on average, exponentially with the system size [37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48]. Thus, barren plateaus have then been recognized as one of the main limitations to overcome in order to preserve the hope of achieving quantum advantage with VQAs. Recently, a great deal of effort has been put forward to developing methods that can mitigate the effect of barren plateaus [49, 50, 51, 52, 53, 54, 55], but ideally one would like to devise and employ VQA ansatzes which do not exhibit barren plateaus altogether. + +For instance, it is known that one should avoid problem-agnostic ansatzes such as deep hardware efficient ansatzes, as these can exhibit barren plateaus due to their high expressibility [37, 38, 47]. Hence, so-called problem-inspired ansatzes have been speculated to be able to overcome barren plateaus by encoding information about the problem at hand in the ansatz. Here, the intuition is that problem-inspired ansatzes constrain the space explored during the optimization to a space that either contains the solution to the problem, or that at least contains a good approximation to the solution, while maintaining a low expressibility. + +In this work we employ tools from Quantum Optimal Control (QOC) to diagnose the presence or absence of barren plateaus in certain families of probleminspired ansatzes with a periodic structure. QOC theory is a long standing theoretical framework developed to provide tools for the manipulation of quantum dynamical processes. As shown in Fig. 1, we here make use of the fact that periodic VQAs and QOC systems can be considered as different level formulations of a common variational problem [56], as both aim at driving a quantum system with a classical optimization loop. Most importantly, this connection allows us to understand and forecast the presence or absence of barren plateaus in problem-inspired variational ansatzes like the Quantum Alternating Operator Ansatz (QAOA) [19, 57] and the Hamiltonian Variational Ansatz (HVA) [58, 59]. We note that the procedure is perfectly suitable for other periodic ansatzes like the adaptive QAOA [57, 60] and the quantum optimal control ansatz [61]. Moreover, our results also extend to quantum neural network architectures used in the quantum machine learning literature [62]. Our results indicate that probleminspired ansatzes are not immune to barren plateaus, and hence that certain ansatz strategies in the literature need to be revised. + +Our main results are organized into propositions and theorems that show that one can diagnose the existence of barren plateaus by analyzing the controllability of the system, i.e., by studying the Dynamical Lie Algebra (DLA) of the system. The DLA is the subspace of operator space spanned by the nested commutators of the elements in the set of genera- + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0002-04.png) + + +Figure 1: **Framework for Variational Quantum Algorithms (VQA) and Quantum Optimal Control (QOC).** VQAs and QOCs can be regarded as two different levels of a theory that manipulate the evolution of a quantum system by training sets of parameters governing the system’s dynamical evolution [56]. In VQAs (QOC) one applies a series of parametrized quantum gates (control pulses) to an input state. By gathering knowledge on the evolution via measurements on the resulting evolved state, the set of parameters (controls) are trained using a classical optimizer until a given task is completed. In this work we consider VQAs and QOC systems that have periodic structure ansatzes as in Eq. (2). + +tors of the ansatz (e.g., see [63] for an introduction to quantum control theory). In an effort to give a comprehensive picture, our results follow the different controllability scenarios shown in Fig. 2. + +The manuscript is organized as follows. In Section 2 we present the theoretical framework for VQAs, which includes a description of the type of ansatz considered, as well as a basic review of concepts related to barren plateaus and ansatz expressibility. Then, in Section 3 we introduce the framework of QOC, and recall how in QOC theory the DLA of the ansatz generators is used to study the controllability of the system. Section 4 contains the main results of this work, while in Section 5 we present our numerical simulations. Finally, in Section 6 we present our discussions and conclusions. + +## 2 VARIATIONAL QUANTUM ALGORITHMS + +In this section we review the basic framework of Variational Quantum Algorithms (VQAs). In particular, we discuss a general form for ansatzes that have a periodic structure, which we consider throughout this work. Since our goal is to analyze the gradient scaling, we additionally provide an overview of the barren plateau phenomenon. + +Accepted in Quantum 2022-08-31, click title to verify. Published under CC-BY 4.0. + +2 + +### 2.1 General framework + +We consider an optimization task where the goal is to minimize a cost function of the form + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0003-02.png) + + +Here, _ρ_ is an input state on _n_ qubits in a _d_ -dimensional Hilbert space with _d_ = 2_n_ , _U_ ( **_θ_** ) a parametrized quantum circuit, and _O_ is a Hermitian operator that defines the task at hand. + +Throughout this work we consider layered parametrized quantum circuits that, as shown in Fig. 1, have a periodic structure of the form + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0003-05.png) + + +Here, the index _l_ indicates the layer, **_θ_** _l_ = ( _θl_ 1 _, . . . , θlK_ ) contains the parameters of such layer (such that **_θ_** = _{_ **_θ_** _l}__L_ _l_ =1),and_Hk_areHermitian traceless operators that generate the unitaries in the ansatz. For generality, one can also allow for certain layers to be unparametrized, in which case one would simply set certain _θlk_ to be constant. In what follows we refer to this type of ansatz as a Periodic Structure Ansatz (PSA). We refer the reader to Appendix B for a detailed discussion of several ansatzes from the literature that are PSAs. + +### 2.2 Barren plateaus + +Recently, it has been shown that the choice of ansatz can hinder the trainability of the parameters **_θ_** for large problem sizes due to the existence of the socalled barren plateau phenomenon. In this context, deep unstructured problem-agnostic ansatz are known to exhibit barren plateaus [37, 49, 47]. Hence, the design of ansatzes that overcome barren plateaus has been recognized as one of the most important challenges to guarantee the success of VQAs [6], and problem-inspired ansatzes have been proposed as one of the most promising strategies. However, despite their promise, little is known about the existence of barren plateaus in problem-inspired ansatzes. + +Let us now briefly recall that when the cost exhibits a barren plateau, the gradients are exponentially suppressed (on average) across the optimization landscape. This implies that an exponentially large precision is needed to navigate trough the flat landscape and determine a cost minimizing direction [37, 49, 42]. Hence, consider the following definition. + +**Definition 1** (Barren Plateau) **.** _A cost function C_ ( **_θ_** ) _as in Eq._ (1) _is said to have a barren plateau when training θµ ≡ θpq ∈_ **_θ_** _, if the cost function partial derivative ∂C_ ( **_θ_** ) _/∂θµ ≡ ∂µC_ ( **_θ_** ) _is such that_ + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0003-11.png) + + +_for some b >_ 1 _. Here the variance is taken with respect to the set of parameters_ **_θ_** _._ + +We refer the reader to Appendix C for additional details on barren plateaus. + +It is worth remarking that the barren plateau phenomenon has been linked to the expressibility of the ansatz, as it has been shown that circuits with large expressibility will exhibit small gradients [47]. In this context, one can quantify the expressibility of an ansatz by comparing the distribution of unitaries obtained from _U_ ( **_θ_** ) to the maximally expressive uniform (Haar) distribution _UH_ [64]. Defining the _t_ -th moment superoperator of the distribution generated by the ansatz _U_ ( **_θ_** ), + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0003-15.png) + + +we recall that its ordinary action on a given operator can be obtained by placing said operator into the center of the representation of _MU_(_t_ () **_θ_** )as[65,66] + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0003-17.png) + + +In our case, we will only be interested in second moments. For that reason, we will focus on the deviation of the second moments of the distribution generated by the ansatz _MU_(2) ( **_θ_** )from_M_(2) _UH_thesecondmoments of the Haar distribution, via the norm of the superoperator + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0003-19.png) + + +For our purposes here, we find it convenient to define the expressibility as the infinity norm, _∥A∥∞_ = _λ_ max( _A_ ), with _λ_ max( _A_ ) the largest singular value of _A__1_ . Thus, the more expressible the ansatz, the smaller the norm _∥A_(2) _U_ ( **_θ_** )_∥∞_,andthesmallerthegra- dients of the cost partial derivatives [47]. The limit _∥A_(2) _U_ ( **_θ_** )_∥∞_= 0 is reached when_U_(**_θ_**) forms a 2-design, in which case the cost exhibits a barren plateau according to Definition 1 [37, 49]. + +## 3 QUANTUM OPTIMAL CONTROL + +Quantum Optimal Control (QOC) is a theoretical framework that provides tools for the systematic manipulation of quantum dynamical systems. The connection between VQAs and QOC has been previously established showing that one can use QOC tools to + +> _1_ The expressibility can also be defined in terms of other matrix norms such as the diamond norm or the Schatten _p_ -norms. However, due to the matrix norm equivalence, if _∥A_( _U__t_ () **_θ_** )_∥_=_ϵ_ for our definition, there always exists an _ϵ__′_ for other expressibility definitions such that the expressibility is equal to _ϵ__′_ and such that _ϵ_ and _ϵ__′_ are related via a dimensionallity factor [67, 68]. + +Accepted in Quantum 2022-08-31, click title to verify. Published under CC-BY 4.0. + +3 + +specify the parameters **_θ_** at a device-level [69, 56, 70] and to analyze VQA landscapes [71]. Conversely, tools from VQAs have been employed to determine optimal control sequences [72]. In particular, Ref. [56] notes that VQAs and QOC can be unified as formulations of variational optimization at the circuit level and pulse level, respectively. In addition, the framework of QOC has been employed to analyze the computational universality of quantum circuits [73, 74, 75], as well as their reachability [76]. + +In QOC one is interested in controlling the dynamical evolution of a quantum state _|ψ⟩_ in a complex _d_ -dimensional Hilbert space _H_ = C_d_ (where _d_ = 2_n_ ) [63]. In the typical setting, one has a Hamiltonian + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0004-02.png) + + +that is tunable through some time-dependent functions _{fk_ ( _t_ ) _}_ , know as control fields or protocols. The fixed Hamiltonian _H_ 0, usually called the drift, represents the natural or free evolution of the system, whereas the control Hamiltonians _{Hi}_ are associated with interactions with external degrees of freedom (usually electromagnetic radiation). Thus, _|ψ⟩_ evolves through the parametrized propagator _U_ ( _t_ ) as _|ψ⟩_ ( _t_ ) = _|ψ_ ( _t_ ) _⟩_ . In turn, _U_ ( _t_ ) is the solution to the Schrödinger equation + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0004-04.png) + + +As shown in Appendix D, under standard assumptions, the Trotrerized QOC propagator of Eq. (8) is a PSA as in Eq. (2). + +The variety of different dynamics a quantum control system in the form of Eq. (7) can undergo, upon variation of the control fields, is well understood though group theory. Since the Hamiltonian is Hermitian and traceless, _U_ ( **_θ_** ) belongs to _SU_ ( _d_ ), the Lie group of unitary _d × d_ complex matrices that preserves the standard inner product on _H_ . Surprisingly, the set of all unitaries _U_ ( **_θ_** ) that can be accessed by such a control system forms itself a Lie group, known as the _dynamical Lie group_ G _⊆U_ ( _d_ ). Hence, a natural question which arises is: how can this group be determined? + +First, let us define the set of generators. + +**Definition 2** (Set of generators) **.** _Given a parametrized quantum circuit of the form in Eq._ (2) _we define the set of generators G_ = _{Hk}__K_ _k_ =0_asthe_ _set (of size |G|_ = _K_ + 1 _) of the (traceless) Hermitian operators that generate the unitaries in a single layer of U_ ( **_θ_** ) _._ + +Naturally, the group G depends on the set of generators _G_ , yet it is not sufficient to look at the individual elements of _G_ . Instead, one must consider the Lie algebra that emerges from their nested commutators. Hence, consider the following definition [77]. + +**Definition 3** (Dynamical Lie Algebra) **.** _Given a control system with generators G (see Definition 2), the Dynamical Lie Algebra (DLA)_ g _is the subalgebra of_ su( _d_ ) _spanned by the repeated nested commutators of the elements in G, i.e.,_ + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0004-11.png) + + +_where ⟨S⟩_ Lie _denotes the Lie closure, i.e., the set obtained by repeatedly taking the nested commutators between the elements in S._ + +Here, su( _d_ ) is the special unitary algebra of degree _d_ , the Lie algebra formed by the set of _d × d_ skewHermitian, traceless matrices. In Appendix E, we lay down the basic procedure to build DLAs (see Algorithm 1) and provide some discussion on the complexity of such construction. + +Once the DLA is obtained from the set of generators, one can determine the set of unitaries that are expressible by the control system. Specifically, one can now properly define the dynamical Lie group as follows. + +**Definition 4** (dynamical Lie group) **.** _The set unitaries_ G _that can be generated by a control system is determined by its DLA (see Definition 3) through__2_ + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0004-16.png) + + +The dynamical Lie group in turn determines the set of states _|ψ_ ( **_θ_** ) _⟩_ = _U_ ( **_θ_** ) _|ψ⟩_ that can be reached by evolving an initial state _|ψ⟩_ . Specifically, here _U_ ( **_θ_** ) can attain values in the Lie group G. In addition, Definition 4 crucially shows that one can study the expressibility of a control system (i.e., the unitaries that can be generated, or the set of states that can be reached) via the DLA obtained from the set of generators. As shown in Fig. 2, when computing g there are several cases of interest that can arise and which we here consider. For the sake of clarity, in what follows we briefly recall several key concepts that will be useful throughout the manuscript. We refer the reader to [63] for additional details. + +First and foremost, we recall the concept of _controllability_ . A control system is said to be _controllable_ if its DLA is full rank, i.e., g = su( _d_ ). This implies that G = _SU_ ( _d_ ) and hence every unitary (up to a phase) can be obtained by appropriately choosing control parameters in Eq. (45). In particular, this means that for any two states _|ψ⟩_ and _|φ⟩_ , there always exists a unitary _U_ ( **_θ_** ) _∈_ G such that _U_ ( **_θ_** ) _|ψ⟩_ = _|φ⟩_ . + +If the DLA is not full rank, then the system is said to be _uncontrollable_ . In this case g is a _proper_ subalgebra of su( _d_ ), and only a _proper_ subgroup G _⊂SU_ ( _d_ ) + +> _2_ This is grounded in the fact that every possible DLA is a subalgebra of the special unitary algebra su( _d_ ) and therefore compact. The exponential map is a function _e_ : g _→_ G. If g is compact then the exponential map is surjective: every element of G is the image of at least one element of g. + +Accepted in Quantum 2022-08-31, click title to verify. Published under CC-BY 4.0. + +4 + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0005-00.png) + + +Figure 2: **Cases of interest for the Dynamical Lie Algebra.** The Dynamical Lie Algebra (DLA) g determines the set of unitaries expressible, and concomitantly, the set of states reachable. In this figure we show different scenarios that can arise when computing g. Our main results (on gradient scaling of VQAs and QOC systems) pertain to these different scenarios. + +is available to the control system, meaning that the set of reachable states _{U_ ( **_θ_** ) _|ψ⟩, ∀ U_ ( **_θ_** ) _∈_ G _}_ is not the whole state space. As depicted in Fig. 2, there are two sources of uncontrollability [78]. + +On one hand, if the generators in _G_ share one or more common symmetries, i.e., there is at least one Hermitian operator Σ that commutes with every element in _G_ , then every _H ∈_ g is block diagonal in the eigenbasis of Σ. This causes the state space to break into subspaces that are invariant under the action of g, in which case controllability is clearly disrupted. Here, the DLA is a _reducible_ representation of some Lie algebra. On the other hand, even in the absence of symmetries, that is, when the DLA is _irreducible_ , uncontrollability can arise simply because the Lie algebra is a proper subalgebra of su( _d_ ). + +Let us finally remark that, as shown in Fig. 2, even though a reducible system cannot be controllable on the entire Hilbert space it may still be controllable on some (or all) of the invariant subspaces. Given a DLA that is a direct sum of irreducible representations, i.e., g = _j_g_j_, then the Hilbert space can be expressed as _H_ = _j__Hj_, with_Hj_being invariant under the action of g. A system is said to be _subspace controllable_ on subspace _Hj_ if g _j_ is full rank, i.e., g _j_ = u( _dj_ ), where _dj_ = dim( _Hj_ )) and u( _dj_ ) denotes the unitary algebra of degree _dj_ . + +trainability and the existence of barren plateaus in VQAs. Specifically, we organize our results in term of the different controllability settings shown in Fig. 2. In all cases, the proofs are presented in the Appendix. The main idea behind our results is that, given a PSA _U_ ( **_θ_** ) as in Eq. (2), the study of the DLA of the ansatz can diagnose the presence (or absence) of barren plateaus in the VQA landscapes. + +### 4.1 Controllable systems + +First, let us consider controllable systems. It is well known that the distribution of unitaries generated by controllable systems converges to a 2-design in the long-time (i.e., for sufficiently deep circuits) [79]. However, the rate of convergence actually depends on the specific choice of generators. Hence, our first result analyzes the depth at which the expressibility _∥A_(2) _U_ ( **_θ_** )_∥∞_ofacontrollablesystemis_ε_small. + +**Theorem 1.** _Consider a controllable system. Then, the PSA U_ ( **_θ_** ) _will form an ε-approximate_ 2 _-design, i.e. ∥A_(2) _U_ ( **_θ_** )_∥∞_=_εwithϵ>_0_,whenthenumberof_ _layers L in the circuit is_ + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0005-09.png) + + +_Here ∥A_(2) _U_ 1( **_θ_** )_∥∞denotestheexpressibilityofasin-_ _gle layer U_ 1( **_θ_** 1) _of the ansatz according to Eqs._ (2) _and_ (6) _._ + +See Appendix F for a proof of Theorem 1. + +We note that Theorem 1 arises from the following expression that connects the expressibility of an _L_ - layered PSA to the expressibility of a single layer of the ansatz to the _L_ -th power as + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0005-13.png) + + +Here we can see that _∥A_(2) _U_ ( **_θ_** )_∥∞_=0ifandonlyif _∥A_(2) _U_ 1( **_θ_** )_∥∞_= 0.Hence,asexpected,PSAsthathave more expressible layers require less depth to have an _ε_ -expressibility (to be _ε_ -approximate 2-designs). Conversely, one can also see that ansatzes with less expressible layers require more depth to become _ε_ - approximate 2-design. + +The following corollary analyzes the scaling of _L_ . + +## 4 MAIN RESULTS + +As previously discussed, VQAs and QOC can be considered as two formulations of a common variational optimization problem that optimizes parameters controlling the dynamical evolution of a quantum system. In this section we present our main results, where we basically leverage tools from QOC to analyze the + +**Corollary 1.** _Let the single layer expressibility of a controllable system be ∥A_(2) _U_ 1( **_θ_** )_∥∞_ = 1 _− δ_ ( _n_ ) _, with δ_ ( _n_ ) _being at most polynomially vanishing with n, i.e., with δ_ ( _n_ ) _∈_ Ω(1 _/_ poly( _n_ )) _. Then, if L_ ( _n_ ) _∈_ Ω( _n/δ_ ( _n_ )) _, U_ ( **_θ_** ) _will be no worse than an ε_ ( _n_ ) _-approximate_ 2 _-design (i.e., ∥A_(2) _U_ ( **_θ_** )_∥∞_⩽_ε_(_n_)_)_ _with ε_ ( _n_ ) _∈O_ (1 _/_ 2_n_ ) _, where we have added the n- dependence in L and ε for clarity._ + +Accepted in Quantum 2022-08-31, click title to verify. Published under CC-BY 4.0. + +5 + +See Appendix G for a proof of Corollary 1. + +From Corollary 1 we have that when the single layer expressibility is (at most) polynomially vanishing with _n_ , then a polynomial number of layers suffice to make the PSA _U_ ( **_θ_** ) exponentially close to being a 2-design. We note, however, that in the case where the single layer expressibility is exponentially close to 1, one requires an exponential number of layers to form an _ε_ -approximate 2-design. In all the aforementioned cases it is worth remarking that an exponential number of layers will always lead to _ε_ -approximate 2- designs with _ε ∈O_ (1 _/_ 2_n_ ), independently of the value of _∥A_(2) _U_ 1( **_θ_** )_∥∞_. + +Once the depth of the ansatz is sufficient for the controllable system to be an _ε_ -approximate 2-design, then a barren plateau will arise. Hence, one can prove the following proposition from Theorem 1 and Corollary 1. + +**Proposition 1** (Controllable) **.** _There exists a scaling of the depth for which controllable systems form ε-approximate_ 2 _-designs with ε ∈O_ (1 _/_ 2_n_ ) _, and hence the system exhibits a barren plateau according to Definition 1._ + +See Appendix H for a proof of Proposition 1. + +Proposition 1 rephrases the well known barren plateau results of [37, 47] in terms of controllability. Specifically, it has been shown that when an ansatz forms a 2-design, such randomness leads to a barren plateau. Hence, the proof of Proposition 1 simply follows the proof in [37], with the addition that the convergence to a 2-design comes from the fact that the system is controllable. + +Evidently, it becomes relevant to determine systems that are controllable as these can exhibit barren plateaus. In this work we prove that two relevant sets of generators lead to full rank DLAs, and hence to controllable systems. + +**Proposition 2.** _The following two sets of generators generate full rank DLAs, and concomitantly lead to controllable systems:_ + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0006-08.png) + + +See Appendix I for a proof of Proposition 2. + +The first case in Proposition 2 corresponds to the generators of PSA layered Hardware Efficient Ansatz [80], and hence Proposition 1 indicates that this system can exhibit barren plateaus. While it is known that the layered Hardware Efficient Ansatz converges to a 2-design for sufficient depth [81, 82, 83, 37, 49], the proof of existence of barren plateaus for this ansatz presented here is novel in that we show that the system is controllable. + +The second result in Proposition 2 pertains to determining the ground state energy of quantum spin glasses (usually configured to encode solution to combinatorial optimization problems) [84, 85] with a PSA generated by _G_ SG. Hence, since the system is controllable, according to Proposition 1, such an ansatz will also exhibit a barren plateau. This provides a no-go theorem for determining the ground state of certain spin glasses with Hamiltonians using deep PSA variational ansatzes . + +### 4.2 Subspace controllable systems + +Let us now consider the case of reducible DLAs, i.e., control systems with symmetries. Here we recall that in this case the DLA is a direct sum of the form g = _j_g_j_, such that any unitary_U_(**_θ_**) in the dynam- ical group G preserves the subspaces _U_ ( **_θ_** ) _Hj ⊂Hj_ . Then, similarly to the fully controllable case, if a system is subspace controllable in a given subspace there exists a depth at which the unitaries _U_ ( **_θ_** ) form 2- designs in that subspace. In such a case, we can derive the following theorem for the variance of the cost function partial derivative with respect to a parameter _θµ ≡ θpq_ associated to layer _p_ and generator _Hq_ (see Eq. (2) for a definition of the ansatz). In the following, will slightly abuse notation and denote _Hµ_ = _Hq_ . + +**Theorem 2** (Subspace controllable) **.** _Consider a system that is reducible, i.e. so that the Hilbert space is H_ = _j__HjwitheachHjinvariantundertheaction_ _of the dynamical Lie group_ G _(see Def. 4), and controllable on some Hk of dimension dk (i.e._ g _k_ = u( _dk_ ) _or_ su( _dk_ ) _). Consider a cost function C_ ( **_θ_** ) _in the form of Eq._ (1) _and suppose that the number of layers L in the circuit is enough to allow the distribution of unitaries U_ ( **_θ_** ) _to be ε close to a 2-design in Hk. Then, if the initial state is such that ρ ∈Hk, the variance of the cost function partial derivative with respect to parameter θµ is given by_ + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0006-15.png) + + +_where O is the operator whose expectation value is being minimized and Hµ is the generator of the corresponding gate. Here_ ∆( _A_ ) = _DHS_ � _A,_ Tr[ _A_ ]1 _d__d_ � _, with DHS_ ( _A, B_ ) = Tr[( _A − B_ )2 ] _the Hilbert-Schmidt distance, and where we defined A_(_k_) _as the reduction of operator A onto the subspace of Hk. Explicitly, A_(_k_) = _QkAQ__†_ _k__withQkamatrix(ofdimension_ _dk ×_ 2_n_ _) with columns corresponding to a basis of Hk._ + +See Appendix J for a proof of Theorem 2. + +Theorem 2 shows that the input state _ρ_ can actually play a crucial role in determining the gradient scaling of the cost function. Specifically, if _ρ_ belongs to an invariant subspace where the system is controllable, then the scaling of the cost function partial derivative variance is determined by the dimension of the invariant subspace rather than by the dimension _d_ = 2_n_ + +Accepted in Quantum 2022-08-31, click title to verify. Published under CC-BY 4.0. + +6 + +with a PSA generated by _G_ = _GXXZU_ � _{Z_ 1 _}_ . Here, the XXZ generators + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0007-01.png) + + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0007-02.png) + + +Figure 3: **Trainability analysis for a PSA used to prepare the ground state of a Heisenberg** _XXZ_ **Hamiltonian.** a) Due to the symmetries in the _XXZ_ Hamiltonian of Eq. (15), the Hilbert space spanned by the PSA can be expressed as a direct sum of invariant subspaces _Hk_ (of dimension _dk_ =_n_ _k_ �) composed of states with _k_ excitations. Hence both _HXXZ_ and _U_ ( **_θ_** ) generated by Eq. (16) will be block diagonal. Since the system is subspace controllable in each invariant subspace, the gradient scaling of the PSA can be analyzed via Theorem 2. b) The existence or absence of barren plateaus is directly determined by the dimension of the invariant subspace to which the input state _ρ_ belongs. For instance, the cost can be trainable for _ρ ∈H_ 1, but will exhibit a barren plateau if _ρ ∈Hn/_ 2, as in the latter case the dimension _dn/_ 2 is exponentially large. + +of the Hilbert space. Hence, the cost function _C_ ( **_θ_** ) might exhibit barren plateaus in some subspaces but not in others. This is formalized in the following corollary. + +**Corollary 2.** _Consider an ansatz of the form in_ (2) _giving rise to a reducible DLA, and let ρ ∈Hk, with Hk some invariant subspace that is controllable (i.e. the DLA reduced to such subspace is full rank). The following bound holds_ + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0007-06.png) + + +_That is, provided_ Tr[( _Hµ_ )4 ] _,_ Tr[ _O_4 ] _∈O_ (2_n_ ) _, the cost function will exhibit a barren plateau for any subspace such that dk ∈O_ (2_n_ ) _._ + +See Appendix K for a proof of Corollary 2. In addition, in this appendix, we also note relevant cases for which Tr[( _Hµ_ )4 ] _,_ Tr[ _O_4 ] _∈O_ (2_n_ ) holds. + +For example, let us consider the problem of preparing the ground state of a Heisenberg _XXZ_ spin chain + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0007-10.png) + + +are accompanied by a _control_ generator _Z_ 1, which is introduced precisely to make the system (subspace) controllable. We remark that we here employ a _U_ subindex in _GXXZU_ to indicate that this set of generators is uncontrollable. Since all elements in _GXXZU_ commute with _Mz_ =_n_ _i_ =1_Zi_,the Hilbert space fractures into _n_ + 1 invariant subspaces of fixed excitation_3_ _H_ =_n_ _m_ =0_Hm_,ofdimension _dm_ = dim( _Hm_ ) = � _mn_ � [86]. + +Because the example set _G_ has a DLA that is full rank on every subspace [87], we can analyze the trainability of such a PSA using Theorem 2. The implications of Corollary 2 for such a VQA are schematically shown in Fig. 3. Here we find that the presence, or absence, of barren plateaus for the PSA _U_ ( **_θ_** ) generated by Eq. (16) is completely determined by the scaling of the invariant subspace to which the input state belongs. For instance, the cost may not exhibit a barren plateau if _ρ_ has a number of excitations that does not scale with _n_ , while it will have a barren plateau for _k_ = _n/_ 2 (as in this case the dimension _dn/_ 2 scales exponentially with the number of qubits). + +### 4.3 Uncontrollable and reducible systems + +Analyzing the scaling of the gradients in the case of uncontrollable systems becomes much more intricate than in the controllable or subspace controllable cases, mainly because integrating over the Haar measure of proper subgroups of the unitary group is not so straightforward [88]. As shown in this (and the next) section, one can still obtain a few analytical results for these cases. In particular, one can derive an upper bound for the variance of partial derivatives in terms of the degree of expressibility on the invariant subspaces, in a spirit similar to that of [47]. + +Before presenting our main results for uncontrollable and reducible systems, it is convenient to introduce some notation. We will use _UB_ and _UA_ , respectively, to address the portions of the circuit that come before and after a given parameter _θµ ≡ θpq ∈_ **_θ_** . That is, + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0007-16.png) + + +and + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0007-18.png) + + +Accepted in Quantum 2022-08-31, click title to verify. Published under CC-BY 4.0. + +7 + +where we have omitted the **_θ_** dependency for simplicity. + +Then, the following theorem holds. + +**Theorem 3.** _Consider a system that is reducible and let ρ ∈Hk with Hk an invariant subspace of dimension dk. Then, the variance of the cost function partial derivative is upper bounded by_ + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0008-03.png) + + +_with_ + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0008-05.png) + + +_Here we defined X_ = [ _Hµ_(_k_)_,_(_U_ _A_(_k_))_†O U_( _A__k_)]_andY_= [ _Hµ_(_k_)_,_(_U_ _B_(_k_))_ρ_(_k_)(_U_ _B_(_k_))_†_]_.Forsimplicity,wehereem-_ _ployed the short-hand notation ⟨·⟩U_ ( _xk_ ) _(with x_ = _A, B) indicates the expectation value over the distribution of unitaries obtained from Ux_(_k_) _in the k-th subsystem. Finally, ∥M ∥_ 2 = ~~�~~ Tr[ _M__†_ _M_ ] _is the Frobenius norm, and_ ∆( _·_ ) _was defined in Theorem 2._ + +See Appendix L for a proof of Theorem 3. + +Theorem 3 generalizes the expressibility results in [47] to invariant subspaces. More specifically, Theorem 3 provides a bound on the variance of the cost function partial derivative _∂µC_ ( **_θ_** ) as a function of the ansatz expressibility on the relevant invariant subspace. Hence, similar to the results observed in [47], the more expressible an ansatz is in a subspace, the smaller the gradients will be. Moreover, ansatzes that are very expressible in subspaces with exponentially large dimensions can exhibit barren plateaus as the variance of the cost function partial derivative will vanish exponentially, according to Eq. (19). + +### 4.4 Uncontrollable and irreducible systems + +Here, we analyze a case where the DLA is an irreducible representation of some proper subalgebra of su( _d_ ). Specifically, we consider a toy model ansatz _U_ ( **_θ_** ) =_L_ _l_ =1_e−iθlxSye−iθlySx_withgenerators_G_= _{Sx, Sy}_ , where g = _{iSx, iSy, iSz}_ is the spin _S_ = ( _d −_ 1) _/_ 2 irreducible representation of su(2). That is, [ _Sj, Sk_ ] = 2 _iϵjklSl_ with _ϵ_ the Levi-Civita symbol and _j, k, l ∈{x, y, z}_ . We address the task of minimizing a cost function of the form + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0008-11.png) + + +where _|m⟩_ is an eigenstate of _Sz_ , i.e., _Sz|m⟩_ = _m|m⟩_ with _m ∈{−S, −S_ + 1 _, . . . , S −_ 1 _, S}_ . + +Let us analyze the variance of partial derivative with respect to parameter _θµ_ = _θjx_ , i.e., the one corresponding to the generator _Sx_ on the _j_ -th layer. Assuming a depth _p_ such that that the distributions _UA_ ( **_θ_** ) and _UB_ ( **_θ_** ) converge to _ε_ -approximate 2- designs on the dynamical Lie group G (which in this + +case is the _d_ -dimensional irreducible representation of _SU_ (2)), we are able to explicitly integrate over the Haar measure on G and find the following proposition to hold. + +**Proposition 3.** _Consider the cost function of Eq._ (20) _. Let θµ_ = _θj,x, and let us assume that the circuit is deep enough to allow for the distribution of unitaries UA and UB to converge to_ 2 _-designs on_ G = _SU(2). Then variance of the cost function partial derivative ∂µC_ ( **_θ_** ) = _∂C_ ( **_θ_** ) _/∂θµ is_ + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0008-16.png) + + +See Appendix M for a proof of Proposition 3. + +Proposition 3 shows that the variance of the cost function again depends on the input state _|m⟩_ , which is a similar result to the one obtained in Theorem 2. Moreover, here Var **_θ_** [ _∂µC_ ( **_θ_** )] can in fact be as large as _d_2 . This is due to the fact that the “size” (the difference between maxima and minima) of the landscape also grows with _d_ . One can get rid of this effect by considering a normalized cost instead, _C_ ( **_θ_** ) = _C_ ( **_θ_** ) _/S_ , where the _ad-hoc_ factor 1 _/S_ guarantees that the landscape is _|C_ ( **_θ_** ) _|_ ⩽ 1 for all values of _d_ . The variance of such normalized landscape is Var **_θ_** [ _∂µC_ ( **_θ_** )] = 23 _mS_22= 38 ( _dm−_ 1)22, that is, vanishes exponentially for initial states with _|m| ∈O_ (poly(log( _d_ ))). Similar to the subspace controllable results in Corollary 2, here the choice of initial state is again crucial as it can lead to the cost function exhibiting barren plateaus. + +### 4.5 General case: linking gradient scaling to the dimension of the Lie algebra + +In this section we note that the dimension of the DLA can be linked to the scaling of the variance of the cost function partial derivatives. This opens up the possibility of diagnosing the existence of barren plateaus of uncontrollable systems by analyzing the scaling of their DLAs. First, let us remark that a key aspect of the toy model in Section 4.4 is that the dimension of the DLA is dim(g) = 3. This is independent of the dimension _d_ of the Hilbert space it acts on. Moreover, as shown in Eq. (21) the variance is also independent of _d_ as it does not present the typical dimensionaldependent factor in the denominator that one usually obtains when integrating over unitary 2-designs (see Eq. (13) in Theorem 2). + +For instance, when the system is controllable, dim(g) = _d_2 _−_ 1 = 22_n_ _−_ 1, and thus the dimension of the DLA is exponentially growing with the system size _n_ . Concomitantly, one finds that Var **_θ_** [ _∂µC_ ( **_θ_** )] = ( _d_2 2 _−d_ 1)2_g_(_Hµ, O, ρ_) [37, 49],and hence the variance is exponentially vanishing with the system size. A similar result is obtained in the subspace controllable case (see Theorem 2) where the variance is of the form ( _d_2 _k_ 2_−_ _dk_1)2_g_(_H_ _µ_(_k_)_, O_(_k_)_, ρ_(_k_)). + +Accepted in Quantum 2022-08-31, click title to verify. Published under CC-BY 4.0. + +8 + +These facts have led us to conjecture that the dimension of the DLA plays a key role in determining the presence or absence of barren plateaus in the cost function landscape. More specifically, for PSAs with sufficient depth (i.e., with a depth such that the distribution of unitaries generated by _U_ ( **_θ_** ) has converged to the Haar measure in the Lie group G), we have noted that the following conjecture appears to hold. + +**Conjecture 1.** _Let the state ρ belong to a subspace Hk associated with a subspace DLA_ g _k (or sub-DLA, the subrepresentation in_ g _where ρ has support on). Then, the scaling of the variance of the cost function partial derivative is inversely proportional to the scaling of the dimension of the DLA, i.e._ + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0009-02.png) + + +The implications of Conjecture 1 are as follows. First, it means that systems with a sub-DLA g _k__4_ that is polynomially growing with the system size can exhibit gradients that vanish only polynomially, and hence may not exhibit barren plateaus. Conversely, systems with a sub-DLA that is exponentially growing with the system size would exhibit gradients that vanish exponentially with the system size, hence exhibiting barren plateaus. Here, we remark that systems with sub-DLAs that are not exponentially growing may still have barren plateaus which are not related to the dimension of the DLA. For instance, if the cost function is global, the system can still exhibit barren plateaus even with trivial ansatzes that do not a have exponentially growing dimension of the DLA [49]. + +Using Conjecture 1, one could diagnose gradient scalings by determining the size of the Lie algebra of a given ansatz _U_ ( **_θ_** ). This comes at the cost of taking the set of generators _G_ and computing the DLA. While numerical methods (as in Algorithm 1) can prove valuable insights for small system sizes, these algorithms will generally scale poorly in the number of qubits. Hence, performing a theoretical analysis of the DLA (similar to the one performed in Proposition 2) is a preferable method. + +Here we remark that there are simple (yet pathological) cases that show that Eq. (22) does not preclude the possibility that systems with algebras that grow polynomial with the system size may still exhibit barren plateaus. For instance, consider Eq. (13), where _ρ_ belongs to a subspace with polynomially growing algebra: _dk ∈O_ (poly( _n_ )). Then, note that if the input state is exponentially close to being maximally mixed on _Hk_ (i.e., if ∆( _ρ_(_k_) ) _∈O_ (1 _/_ 2_n_ )) one can easily verify that the system will exhibit a barren plateau + +> _4_ Let us note that one should look at the sub-DLA instead of the full DLA, since when there are symmetries and the initial state belongs to one or multiple invariant subspaces, the dynamics is contained in those. + +according to Definition 1 as the cost function partial derivative will be exponentially vanishing. Here, the barren plateau arises not from the dimension of the DLA being exponentially large but rather from trying to train a VQA on an input state that is exponentially close to being maximally mixed. A similar result can be found if _Hµ_(_k_) is exponentially close to the identity. Hence, we remark that Conjecture 1 does not imply that systems with polynomially growing algebras are exempt from having barren plateaus, as cases where _ρ_ ( _Hµ_(_k_))isexponentiallyclosetobeingmaximally mixed (the identity) will naturally be hard to train from the definition of the cost function in Eq. (1). + +We finally note that to further support the claim in Conjecture 1, we present in the following section results obtained from numerically computing the scaling of the variance of the cost function partial derivatives for systems with DLAs having several different dependencies on the number of qubits. As discussed in Section 5, we see that the the result in Conjecture 1 holds true for all cases considered, as in these cases the scaling of the variance of the cost function partial derivative is inversely proportional to the scaling of the dimension of the DLA. In addition, based on our conjecture one can accurately make predictions regarding whether a given modification to an ansatz (adding a new generator to _G_ by introducing a new unitary in each layer) might improve or be detrimental to the trainability of the parameters. + +## 5 NUMERICAL SIMULATIONS + +In this section we present results obtained by numerically computing the variance of the cost function partial derivatives for systems with different PSAs, and with DLAs of dimensions with different scaling. In particular, we consider systems that are controllable, subspace controllable, and subspace uncontrollable. As we show, in all cases Conjecture 1 is verified. Finally, we refer the reader to Appendix M for a numerical study of the toy model in Section 4.4, where g and G are, respectively, the _d_ -dimensional irreducible representations of su(2) and _SU_ (2). + +### 5.1 Controllable systems + +First, let us remind that when the system is controllable, _U_ ( **_θ_** ) forms a 2-design (see Proposition 1). In this case the scaling of Var **_θ_** [ _∂µC_ ( **_θ_** )] has been widely analyzed in the literature (see for instance [37, 47]). Controllable systems, as previously discussed, satisfy Conjecture 1. In Fig. 4 we show the variance of cost function partial derivatives as a function of 1 _/_ dim(g) for the cost function + +_C_ ( **_θ_** ) = _⟨_ **0** _|U__†_ ( **_θ_** )( _Z_ 1 _⊗ Z_ 2) _U_ ( **_θ_** ) _|_ **0** _⟩ ._ (23) Here, _U_ ( **_θ_** ) is a layered Hardware Efficient ansatz (see the circuit in the inset of Fig. 4) with 200 layers and + +Accepted in Quantum 2022-08-31, click title to verify. Published under CC-BY 4.0. + +9 + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0010-00.png) + + +Figure 4: **Variance of cost function partial derivatives versus inverse of the DLA dimension for a controllable system.** The layered Hardware Efficient Ansatz (shown in the inset for _n_ = 4) is a controllable system with generators given in Proposition 2. Then, as shown in Proposition 1, the cost function of Eq. (23) exhibits a barren plateau and hence Var **_θ_** [ _∂µC_ ( **_θ_** )] _∈O_ (1 _/_ 2_n_ ). Moreover, since the system is controllable one finds that dim(g) = 4_n_ _−_ 1. Hence, as shown in the plot, Conjecture 1 holds for controllable systems, since the dependence of Var **_θ_** [ _∂µC_ ( **_θ_** )] versus 1 _/_ dim(g) is linear on a log-log scale. + +where _|_ **0** _⟩_ = _|_ 0 _⟩__⊗n_ . For each value of _n_ = 2 _,_ 4 _, . . . ,_ 20, the variance was computed by randomly initializing 1000 sets of parameters. Since this system is controllable (as proved in Proposition 1), then dim(g) = _d_2 _−_ 1 = 4_n_ _−_ 1. In Fig. 4 we see that, as expected, the variance is a polynomial function of 1 _/_ dim(g) (indicated by a straight line in a log-log scale). + +### 5.2 Reducible systems + +#### 5.2.1 The XXZ model + +Let us first consider the task of finding the ground state energy of the XXZ Hamiltonian _HXXZ_ of Eq. (15). First, let us notice that _GXXZU_ , the uncontrollable set of generators of Eq. (16), has two symmetries: magnetization and parity. Hence, the DLA is reducible, i.e. a sum of irreducible sub-representations g _XXZU_ =_n_ _m_ =0_⊆_u(_dm,σ_), where the indices_m_ _σ_ = _±_g_m,σ_ and _σ_ indicate number of excitations and parity, respectively (see Appendix N for details). Notably, the system can be rendered subspace controllable (while preserving the invariant subspace structure) by introducing an additional generator consisting of local fields at the ends of the chain [89, 90] + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0010-06.png) + + +The new set _GXXZ_ generates a DLA that is full rank on each of the invariant subspaces, i.e. gXXZ = � _nm_ =0 In Figure 5(a), we sketch a single _σ_ = _±_u(_dm,σ_). layer of the ansatz generated by _GXXZ_ . Note that upon the removal of the unitary generated by _Z_ 1 + _ZN_ (indicated by a shaded area), one recovers the HVA ansatz with generators _GXXZU_ proposed in Ref. [59]. Figure 5(b) shows numerical results obtained by + +computing Var **_θ_** [ _∂µC_ ( **_θ_** )] for the cost function + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0010-09.png) + + +with _J_ = 1. Here, _U_ ( **_θ_** ) is the HVA ansatz generated by _GXXZ_ (see Eq. (24)) with _L_ = 6 _n_ layers, and _|ψm,_ + _⟩_ is an initial state with _m_ excitations and even parity _σ_ = + (see Appendix O for details). For each system system size _n_ = 2 _,_ 4 _, . . . ,_ 20, and for each value of _m_ , we computed the variance with respect to _θ L_ 2_,_2 by randomly initializing each **_θ_** _pq ∈_ [0 _,_ 2 _π_ ] and averaging over 9500 sets of parameters (for _n_ = 20 we averaged over 2700 sets of parameters). + +In Figure 5(b, left) we see that for _m_ = 1 _,_ 2 _, . . . ,_ 5 the variance of the cost function partial derivative is polynomially decreasing with _n_ , indicating that the cost function does not exhibit a barren plateau for initial states with fixed number of excitations. However, in the case _m_ = _n/_ 2 (see Figure 5(b, right)), one can observe that Var **_θ_** [ _∂µC_ ( **_θ_** )] vanishes exponentially. In addition, in Figure 5(b) we also show the curves for Var **_θ_** [ _∂µC_ ( **_θ_** )] obtained from the analytical result in Eq. (13) of Theorem 2. The agreement between theoretical and numerical results indicates that, already for the linear depths used in the experiments, the ansatz is well converged to a 2-design. Hence, the results in Theorem 2 suggest that the system will exhibit a barren plateau when initialized on any subspace where _dm ∈O_ (2_n_ ), for example, in the case of _m_ = _n/_ 2 excitations. + +In addition, Figure 5(b, right) shows the scaling of the variance for the PSA generated by _GXXZU_ , with an initial state with _m_ = _n/_ 2. As previously noted, this case is not controllable and hence Theorem 2 does not hold. However, the gradient scaling of the cost function can still be diagnosed using the expressibility result of Theorem 3. First, we note that the variance values for the uncontrollable case are larger than the ones for the controllable case. This result is in accordance with the fact that the smallest variances are reached with the higher expressibilities. Still, despite the system not being controllable, we find that the cost function still exhibits a barren plateau as the cost vanishes exponentially with _n_ . + +In Figure 5(c) we show that for all subspace controllable cases considered, Conjecture 1 holds. Specifically, we have shown Var **_θ_** [ _∂µC_ ( **_θ_** )] as a function of 1 _/_ dim(g), and we see a linear dependence in a log-log scale. This is true both for the exponentially growing algebras ( _m_ = _n/_ 2) as well as for the polynomially growing algebras ( _m_ = 1 _,_ 2 _, . . . ,_ 5). Moreover, we see that the Conjecture is verified on the subspace uncontrollable case of _GXXZU_ (pink stars), where the dimension of the DLA is exponentially growing, and concomitantly, the variance of the cost function partial derivative is exponentially suppressed. + +Accepted in Quantum 2022-08-31, click title to verify. Published under CC-BY 4.0. + +10 + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0011-00.png) + + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0011-01.png) + + +Figure 5: **Numerical results for the** _XXZ_ **model.** a) Schematic illustration of a single layer of the subspace controllable periodic structure ansatz for the set of generators _GXXZ_ in Eq. (24) for _n_ = 4 qubits. Removing the unitary in the shaded area leads to the uncontrollable periodic structure HVA generated by _GXXZU_ . b) Left panel: Variance of the partial derivative of the cost in Eq. (25) versus the number of qubits _n_ . The different markers correspond to initial states with a number of excitations _m_ = 1 _,_ 2 _, . . . ,_ 5 (left) with an ansatz generated by _GXXZ_ , and to _m_ = _n/_ 2 (right) with an ansatz generated by _GXXZ_ and by _GXXZU_ . Here we recall that a state _|ψ⟩_ has _m_ excitations if it can be expressed as a linear combination of computational basis states with Hamming weight _m_ . The dashed lines represent the theoretical prediction of Eq. (13). In both cases the plot is shown in a log-linear scale. For _m_ = 1 _,_ 2 _, . . . ,_ 5 the variance is polynomially vanishing with _n_ , while for _m_ = _n/_ 2 the variance is exponentially vanishing with _n_ . c) Variance of the cost function partial derivative versus 1 _/_ dim(g). The plot is shown in a log-log scale. For each value of _m_ we see a linear dependence, which verifies Conjecture 1. + +#### 5.2.2 The Ising Model + +In this section we present results obtained for numerically simulating the use of a PSA to find the ground state of the Ising model. Specifically, consider the Hamiltonian of the one-dimensional Transverse Field Ising Model (TFIM) + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0011-05.png) + + +where _nf_ = _n −_ 1 in the case of open boundary conditions, and _nf_ = _n_ in the periodic boundary conditions case (where _Zn_ +1 _≡ Z_ 1). Then, as shown in Figure 6(a, left), the ansatz is generated by the set + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0011-07.png) + + +Note that the PSA generated by _G_ TFIM is in fact the QAOA employed for solving the MAXCUT problem on a 2-regular graph [19, 57]. + +As discussed in Appendix N, the generators in _G_ TFIM (with open boundary conditions) have two symmetries: parity symmetry Π, and the so-called Z2 symmetry ΠZ2 (representing an invariance under a global flip in the qubits). The Hilbert space is broken into four invariant subspaces, _H_ = _σ,σ__′ Hσ,σ′_, where _σ, σ__′_ = _±_ 1 respectively spanning the eigenvalues of Π and ΠZ2, and where dim( _Hσ,σ′_ ) is exponentially growing, i.e., dim( _Hσ,σ′_ ) _∈O_ (2_n_ ). In turn, the DLA decomposes as gTFIM = _σ,σ__′_g_σ,σ′⊆_u(_dσ,σ′_). However, employing Algorithm 1 we computed the dimension of the DLA generated by _G_ TFIM and we found that it only grows polynomially with _n_ . That is, we obtain that + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0011-10.png) + + +Clearly, this implies that dim(g _σ,σ′_ ) ⩽ _n_2 for all _σ, σ__′_ . Note that the set _{_ 1 _,_ Π _}_ constitutes a representation of _S_ 2, the symmetric group of two elements, under which the open-boundary-condition TFIM generators are invariant. Instead, the TFIM generators with closed boundary conditions are invariant under a representation of _Cn_ , the cyclic group of _n_ elements. As discussed in Appendix N, the dimension of the DLA now grows linearly instead of quadratically. + +Similarly to what happened in the _XXZ_ case, we can turn the TFIM model subspace controllable upon the introduction of an extra generator. Consider the set + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0011-13.png) + + +leading to the PSA in Figure 6(a, right). The set _G_ LTFIM can also be regarded as being constituted by the individual terms in the one-dimensional Longitudinal and Transverse Field Ising Model (LTFIM) Hamiltonian + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0011-15.png) + + +In addition, the ansatz generated by _G_ LTFIM is also a QAOA-type ansatz where an additional mixer has been added. + +In the case of open boundary conditions, the � _ni_ =1_Zi_termbreakstheZ2symmetry,andthus the set _G_ LTFIM only conserves the parity symmetry, gLTFIM = _σ_g_σ_.Using Algorithm 1 we find that the DLA is full rank on both _σ_ = _±_ 1 parity subspaces, and hence + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0011-18.png) + + +Similarly, in the closed boundary condition case, one can also find that the dimension of the DLA grows + +Accepted in Quantum 2022-08-31, click title to verify. Published under CC-BY 4.0. + +11 + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0012-00.png) + + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0012-01.png) + + +Figure 6: **Numerical results for the TFIM and LTFIM models.** a) Schematic illustration of a single layer of the PSA for the sets of generators _G_ TFIM in Eq. (27) (left), and _G_ LTFIM in Eq. (29) (right) for _n_ = 4 qubits. By adding (removing) the gates in the shaded one obtains the ansatz in Eq. (27) with periodic (open) boundary conditions. b) Variance of the cost function partial derivative of the cost function in Eq. (32) versus the number of qubits _n_ for each ansatz. The dashed (dotted) lines indicate the best polynomial (exponential) fit. The plot is shown in a log-linear scale. c) Variance of the cost function partial derivative versus 1 _/_ dim(g). The plot is shown in a log-log scale. + +exponentially with _n_ . This is an example where we show how a simple modification to the ansatz (adding a layer generated by_n_ _i_ =1_Zi_)cangreatlychangethe dimension of the DLA, and, as discussed below, such a small change can greatly affect the trainability of the cost function. + +In Figure 6(b) we show results for numerically computing Var **_θ_** [ _∂µC_ ( **_θ_** )] for the cost function + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0012-05.png) + + +where _U_ ( **_θ_** ) is the PSA generated by the set _G_ TFIM of Eq. (27) with _L_ = 12 _n_ layers for open boundary conditions, and _L_ = 6 _n_ for closed boundary conditions. For each value of _n_ = 4 _,_ 6 _, . . . ,_ 18 we computed the variance by picking 4400 random sets of parameters, while for _n_ = 20 we picked 1000 random intializations. In all cases the partial derivative was taken with respect to _θ_ _L_We see from Figure 6(b) that the 2_,_2. variance of the cost partial derivative vanishes polynomially with _n_ for both open and closed boundary conditions, and hence the system does not exhibit a barren plateau. Then, as shown in Figure 6(c), once again, Conjecture 1 holds for both open and closed boundary conditions: Var **_θ_** [ _∂µC_ ( **_θ_** )] and dim(gTFIM) respectively vanish, and grow, polynomially with _n_ . + +Moreover, in Figure 6(b) we also depict results obtained by computing Var **_θ_** [ _∂µC_ ( **_θ_** )] for the LTFIM ansatz, using the same cost function of Eq. (32). Now, _U_ ( **_θ_** ) is the PSA generated by the set _G_ LTFIM in Eq. (27) with _L_ = 6 _n_ layers. Using the same number of samples than for the TFIM case, we find + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0012-08.png) + + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0012-09.png) + + +Figure 7: **Numerical results for the Erdös–Rényi model.** a) Variance of the cost function partial derivative of the cost function in Eq. (35) versus the number of qubits _n_ . The dashed line indicates the medians across graphs computed for each value of _n_ . The plot is shown in a log-linear scale. b) Variance of the cost function partial derivative versus 1 _/_ dim(g). The plot is shown in a log-log scale. + +that Var **_θ_** [ _∂µC_ ( **_θ_** )] vanishes exponentially with _n_ for both open and closed boundary conditions, and hence the cost exhibits a barren plateau. We see in Figure 6(c) that Conjecture 1 also holds for the LTFIM ansatzes with open and closed boundary conditions, as this time Var **_θ_** [ _∂µC_ ( **_θ_** )] vanishes exponentially with _n_ , while dim(gTFIM) grows exponentially with _n_ . + +It is worth noting that, as discussed before, and as shown in Figure 6(a), the difference between the TFIM and the LTFIM ansatz is given by an additional unitary in each layer (parametrized by a single angle). However, despite this simple difference, we find the variance of the cost function have different scaling, as one cost exhibits a barren plateau while the other one does not exhibit a barren plateau. + +#### 5.2.3 Erdös–Rényi model + +Let us now consider the task of solving MAXCUT problems with a QAOA ansatz. Here, we recall that MAXCUT is specified by a graph _G_ = ( _V, E_ ) of nodes _V_ and edges _E_ , such that one seeks to determine a partition of the nodes of _G_ into two sets that maximize the number of edges connecting nodes between sets. The MAXCUT Hamiltonian is given by + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0012-15.png) + + +and we consider the standard QAOA ansatz generated by + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0012-17.png) + + +Let us analyze the variance of the partial derivative of the cost + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0012-19.png) + + +where we use _|E|_ (the number of edges in the graph) to normalize the cost function. For each value of _n_ = 2 _,_ 3 _, . . . ,_ 9 we generated 90 graphs according to the Erdös–Rényi model [91]. That is, each graph _G_ + +Accepted in Quantum 2022-08-31, click title to verify. Published under CC-BY 4.0. + +12 + +was chosen uniformly at random from the set of all graphs of _n_ nodes. Then, for each graph we sampled 3000 random initializations with _L_ = 12 _n_ layers and we took the partial derivative with respect to the angle in the _L/_ 2-th layer associated to the (mixer) Hamiltonian_n_ _i_ =1_Xi_.InFig.7(a)weshowresults of Var **_θ_** [ _∂µC_ ( **_θ_** )] versus the number of qubits. Here we can see that, as expected, even for fixed _n_ different graphs will have different value of the variance. However, by computing the median variance for each system size we found that the scaling of the median is exponentially decaying with the system size. While this result does not preclude the possibility of generating graphs that will not have a barren plateau, it suggests that uniform sampling of graphs from the Erdös–Rényi model will lead to the landscape for a typical graph having a barren plateau. Then, as shown in Fig. 7(b), we compute the dimension of the DLA for each graph, and find that Conjecture 1 is confirmed, as the relation between Var **_θ_** [ _∂µC_ ( **_θ_** )] and dim(g) is linear in a log-log-scale. + +## 6 DISCUSSION + +In this work, we have explored a fundamental connection between VQAs and the theory of QOC with the purpose of analyzing the existence of barren plateaus in a family of periodic-structured ansatzes which contain, as special cases, the QAOA and the HVA, among other widely used ansatzes in variational quantum algorithms and quantum machine learning. Our results show that one can diagnose the presence of barren plateaus in the cost function landscape by analyzing the degree of controllability of the system, characterized by the dimension of the dynamical Lie algebra (DLA) obtained from the set of generators of the ansatz. + +Our main results are the following. First, we show that if the DLA is full rank, i.e. if the system is controllable, then the cost function exhibits a barren plateau. This follows from the fact that, as we show, controllable systems converge to 2-designs. Here, we also derive an expression relating the depth required for a given ansatz to become an _ε_ -approximate twodesign with the expressibility of one of its layers. + +We then consider systems with symmetries, where the Hilbert space partitions into invariant subspaces associated with the different eigenspaces. In this context, we show that when the system is subspace controllable, the existence of barren plateaus crucially depends on the input state to the VQA. For example, the cost might be trainable for certain input states, but might exhibit a barren plateau for others. Specifically, our results connect the scaling of the variance of cost function partial derivatives to that of the dimension of the subspace in which the input state has support on. Instead, when the system is subspace uncontrollable, we show that one can still upper bound the variance of + +the cost function partial derivative using the expressibility of the ansatz in the relevant subspace. This indicates that larger subspace expressibilities leads to smaller gradients. + +Finally, we present an conjecture that shows that one can directly study the scaling of the cost function partial derivative variance by computing the dimension of the subspace DLA to which the input state belongs. This conjecture implies that ansatzes with polynomially growing DLAs can exhibit polynomially vanishing gradients, while ansatzes with exponentially growing DLAs should exhibit exponentially vanishing gradients. + +In addition, we performed numerical simulations of VQAs with the hardware efficient ansatz, QAOA, and HVA, for problems such as preparing ground states of the _XXZ_ model and of the Ising model, or solving MAXCUT problems on graphs generated from the Erdös–Rényi model. The numerical results match our theoretical predictions and hence verify our analytical results for controllable and subspace controllable systems. Moreover, in all cases considered we verify that our conjecture holds, further providing evidence that the scaling of the cost function partial derivative variance may be directly linked to the dimension of the subspace DLA. + +#### Implications of our results to ansatz design + +The broader implication of our results is that the framework introduced here can be used to design ansatzes, as one could potentially predict if an ansatz, or a modification to the ansatz, will lead to the cost function exhibiting a barren plateau. Hence, our work can be considered as paving the way towards trainability-aware ansatz design. + +For instance, we have shown how a simple change in the ansatz structure, such as adding an additional parametrized unitary per layer, can greatly affect the gradient scaling of the cost by changing the controllability of the system. This means that one should be careful when employing schemes such as the Adaptive QAOA or quantum optimal control ansatz as the addition of an operator _H_ to the set _G_ of generators of the ansatz can lead to barren plateaus if the system becomes controllable (or subspace controllable in an exponentially growing subspace). In particular, if _H_ does not commute with the elements in _G_ , one should analyze how the DLA changes by such addition before proceeding to change the ansatz. + +Here, we crucially remark that one of the main advantages of the aforementioned theoretical analysis is that it can be performed classically (either analytically or numerically) as it just requires the evaluation of the DLA. Hence, our methods save precious quantum resources as one does not need to run the quantum algorithm, or even access a quantum computer, to test the trainability of the ansatzes. + +Accepted in Quantum 2022-08-31, click title to verify. Published under CC-BY 4.0. + +13 + +Finally, we remark that if our conjecture holds more generally, then one can use this additional tool to directly study the trainability of an ansatz by estimating the scaling of the variance of the cost function partial derivative through the scaling of the dimension of the DLA. For example, such results can be used to show that certain ansatzes might not have exponentially vanishing gradients. For instance, when considering a QAOA ansatz for solving MAXCUT on 2- regular graphs, a straightforward computation of the DLA reveals its scaling is only linear in _n_ . Hence, we expect (and we find) no barren plateaus. Similarly, one can use our conjecture to analyze ansatz proposals in the literature. For example, Ref. [71] recently proposed an ansatz generated by the set of products up to _K_ -body Pauli _X_ operators, i.e., _G_ = _{Xi}i ∪{XiXj}i>j ∪{XiXjXk}i>j>k ∪· · ·_ . Since the ansatz is abelian, the dimension of DLA is just the number of generators. Thus, we expect that when using a poly number of layers the ansatz should be rid barren plateaus. + +#### Outlook + +In the present work, we have established a novel framework for diagnosing the presence of barren plateaus in VQAs. While here we mainly focus on the trainability of ansatzes for near-term quantum computing, our results should also be considered as useful in the broader context of QOC. For instance, while the barren plateau phenomenon has been recently widely studied in VQAs, it is clear from our manuscript that barren plateaus can (and will) also arise in QOC schemes (see also [92]). Hence, we leave for future work to study how some of the results derived for the trainability of VQAs be used to analyze the trainability of QOC control pulses. + +In addition, we note that since our work studies the trainability of certain families of ansatzes, we also leave for future work to show how the tools here presented can be employed to study more general ansatzes (e.g., ansatzes for quantum machine learning applications) which do not necessarily have a periodic structure. In addition, we leave as an open question how the results in our conjecture can be generalized and formally proved. + +## 7 ACKNOWLEDGMENTS + +We thank Marco Farinati and Robert Zeier for useful discussions on Lie-algebras, and we also thank Zoe Holmes and Pablo Poggi for helpful discussions. ML acknowledges partial support by CONICET (PIP 112201 50100493CO), UBACyT (20020130100406BA) and ANPCyT (PICT2016-1056)). ML was also supported by the U.S. Department of Energy (DOE), Office of Science, Office of Advanced Scientific Computing Research, under + +the Quantum Computing Applications Team (QCAT) program and also under the Accelerated Research in Quantum Computing (ARQC) program. Piotr C. was supported by the Laboratory Directed Research and Development (LDRD) program of Los Alamos National Laboratory (LANL) under project numbers 20190659PRD4. KS acknowledges support from the National Science Foundation under Grant No. 2014010. GM was supported by the US Department of Energy, Office of Advanced Scientific Computing Research, under the ARQC program and by the LANL ASC Beyond Moore’s Law project. PJC acknowledges initial support from the LANL ASC Beyond Moore’s Law project. MC acknowledges initial support from the Center for Nonlinear Studies at LANL. KS, PJC, and MC also acknowledge support from LDRD program of LANL under project number 20190065DR. + +## References + +- [1] Peter W Shor. Algorithms for quantum computation: discrete logarithms and factoring. In _Proceedings 35th annual symposium on foundations of computer science_ , pages 124–134. Ieee, 1994. DOI: 10.1109/SFCS.1994.365700. URL `https:// ieeexplore.ieee.org/document/365700` . + +- [2] Aram W Harrow, Avinatan Hassidim, and Seth Lloyd. Quantum algorithm for linear systems of equations. _Physical Review Letters_ , 103(15):150502, 2009. DOI: 10.1103/PhysRevLett.103.150502. URL `https://journals.aps.org/prl/abstract/ 10.1103/PhysRevLett.103.150502` . + +- [3] Dominic W Berry, Andrew M Childs, Richard Cleve, Robin Kothari, and Rolando D Somma. Simulating hamiltonian dynamics with a truncated taylor series. _Physical Review Letters_ , 114(9):090502, 2015. DOI: 10.1103/PhysRevLett.114.090502. URL `https://journals.aps.org/prl/abstract/ 10.1103/PhysRevLett.114.090502` . + +- [4] Iulia M Georgescu, Sahel Ashhab, and Franco Nori. Quantum simulation. _Reviews of Modern Physics_ , 86(1):153, 2014. DOI: 10.1103/RevModPhys.86.153. URL `https://journals.aps.org/rmp/abstract/ 10.1103/RevModPhys.86.153` . + +- [5] John Preskill. Quantum computing in the nisq era and beyond. _Quantum_ , 2: 79, 2018. DOI: 10.22331/q-2018-08-0679. URL `https://quantum-journal.org/ papers/q-2018-08-06-79/` . + +- [6] M. Cerezo, Andrew Arrasmith, Ryan Babbush, Simon C Benjamin, Suguru Endo, Keisuke Fujii, Jarrod R McClean, Kosuke Mitarai, Xiao Yuan, Lukasz Cincio, and Patrick J. Coles. Variational + +Accepted in Quantum 2022-08-31, click title to verify. Published under CC-BY 4.0. + +14 + +quantum algorithms. _Nature Reviews Physics_ , 3(1):625–644, 2021. DOI: 10.1038/s42254021-00348-9. URL `https://www.nature.com/ articles/s42254-021-00348-9` . + +- [7] Carlos Bravo-Prieto, Ryan LaRose, M. Cerezo, Yigit Subasi, Lukasz Cincio, and Patrick Coles. Variational quantum linear solver. _arXiv preprint arXiv:1909.05820_ , 2019. URL `https: //arxiv.org/abs/1909.05820` . + +- [8] Hsin-Yuan Huang, Kishor Bharti, and Patrick Rebentrost. Near-term quantum algorithms for linear systems of equations. _arXiv preprint arXiv:1909.07344_ , 2019. URL `https://arxiv. org/abs/1909.07344` . + +- [9] Xiaosi Xu, Jinzhao Sun, Suguru Endo, Ying Li, Simon C Benjamin, and Xiao Yuan. Variational algorithms for linear algebra. _Science Bulletin_ , 66(21):2181–2188, 2021. DOI: 10.1016/j.scib.2021.06.023. URL `https://www.sciencedirect.com/science/ article/pii/S2095927321004631` . + +- [10] Sam McArdle, Tyson Jones, Suguru Endo, Ying Li, Simon C Benjamin, and Xiao Yuan. Variational ansatz-based quantum simulation of imaginary time evolution. _npj Quantum Information_ , 5(1):1–6, 2019. DOI: 10.1038/s41534019-0187-2. URL `https://www.nature.com/ articles/s41534-019-0187-2` . + +- [11] Harper R Grimsley, Sophia E Economou, Edwin Barnes, and Nicholas J Mayhall. An adaptive variational algorithm for exact molecular simulations on a quantum computer. _Nature Communications_ , 10(1):1– 9, 2019. DOI: 10.1038/s41467-019-109882. URL `https://www.nature.com/articles/ s41467-019-10988-2` . + +- [12] Cristina Cirstoiu, Zoe Holmes, Joseph Iosue, Lukasz Cincio, Patrick J. Coles, and Andrew Sornborger. Variational fast forwarding for quantum simulation beyond the coherence time. _npj Quantum Information_ , 6(1): 1–10, 2020. DOI: 10.1038/s41534-020-003020. URL `https://www.nature.com/articles/ s41534-020-00302-0` . + +- [13] Benjamin Commeau, M. Cerezo, Zoë Holmes, Lukasz Cincio, Patrick J. Coles, and Andrew Sornborger. Variational hamiltonian diagonalization for dynamical quantum simulation. _arXiv preprint arXiv:2009.02559_ , 2020. URL `https://arxiv.org/abs/2009.02559` . + +- [14] Joe Gibbs, Kaitlin Gili, Zoë Holmes, Benjamin Commeau, Andrew Arrasmith, Lukasz Cincio, Patrick J. Coles, and Andrew Sornborger. Longtime simulations with high fidelity on quantum hardware. _arXiv preprint arXiv:2102.04313_ , 2021. URL `https://arxiv.org/abs/2102. 04313` . + +- [15] Yong-Xin Yao, Niladri Gomes, Feng Zhang, + +Cai-Zhuang Wang, Kai-Ming Ho, Thomas Iadecola, and Peter P Orth. Adaptive variational quantum dynamics simulations. _PRX Quantum_ , 2(3):030307, 2021. DOI: 10.1103/PRXQuantum.2.030307. URL `https://journals.aps.org/prxquantum/ abstract/10.1103/PRXQuantum.2.030307` . + +- [16] Suguru Endo, Jinzhao Sun, Ying Li, Simon C Benjamin, and Xiao Yuan. Variational quantum simulation of general processes. _Physical Review Letters_ , 125(1):010501, 2020. DOI: 10.1103/PhysRevLett.125.010501. URL `https://journals.aps.org/prl/abstract/ 10.1103/PhysRevLett.125.010501` . + +- [17] Jonathan Wei Zhong Lau, Kishor Bharti, Tobias Haug, and Leong Chuan Kwek. Quantum assisted simulation of time dependent hamiltonians. _arXiv preprint arXiv:2101.07677_ , 2021. URL `https://arxiv.org/abs/2101.07677` . + +- [18] Alberto Peruzzo, Jarrod McClean, Peter Shadbolt, Man-Hong Yung, Xiao-Qi Zhou, Peter J Love, Alán Aspuru-Guzik, and Jeremy L O’brien. A variational eigenvalue solver on a photonic quantum processor. _Nature Communications_ , 5(1):1–7, 2014. DOI: doi.org/10.1038/ncomms5213. URL `https://www.nature.com/articles/ ncomms5213#citeas` . + +- [19] Edward Farhi, Jeffrey Goldstone, and Sam Gutmann. A quantum approximate optimization algorithm. _arXiv preprint arXiv:1411.4028_ , 2014. URL `https://arxiv.org/abs/1411.4028` . + +- [20] Jarrod R McClean, Jonathan Romero, Ryan Babbush, and Alán Aspuru-Guzik. The theory of variational hybrid quantum-classical algorithms. _New Journal of Physics_ , 18(2): 023023, 2016. DOI: 10.1007/978-94-015-83305_4. URL `https://iopscience.iop.org/ article/10.1088/1367-2630/18/2/023023` . + +- [21] Sumeet Khatri, Ryan LaRose, Alexander Poremba, Lukasz Cincio, Andrew T Sornborger, and Patrick J Coles. Quantumassisted quantum compiling. _Quantum_ , 3: 140, 2019. DOI: 10.22331/q-2019-05-13140. URL `https://quantum-journal.org/ papers/q-2019-05-13-140/` . + +- [22] Jonathan Romero, Jonathan P Olson, and Alan Aspuru-Guzik. Quantum autoencoders for efficient compression of quantum data. _Quantum Science and Technology_ , 2(4):045001, 2017. DOI: 10.1088/20589565/aa8072. URL `https://iopscience.iop. org/article/10.1088/2058-9565/aa8072` . + +- [23] Ryan LaRose, Arkin Tikku, Étude O’NeelJudy, Lukasz Cincio, and Patrick J Coles. Variational quantum state diagonalization. _npj Quantum Information_ , 5(1):1– 10, 2019. DOI: 10.1038/s41534-019-0167-6. + +Accepted in Quantum 2022-08-31, click title to verify. Published under CC-BY 4.0. + +15 + +URL `https://www.nature.com/articles/ s41534-019-0167-6` . + +- [24] Andrew Arrasmith, Lukasz Cincio, Andrew T Sornborger, Wojciech H Zurek, and Patrick J Coles. Variational consistent histories as a hybrid algorithm for quantum foundations. _Nature Communications_ , 10(1):1– 7, 2019. DOI: 10.1038/s41467-019-114170. URL `https://www.nature.com/articles/ s41467-019-11417-0` . + +- [25] M. Cerezo, Alexander Poremba, Lukasz Cincio, and Patrick J Coles. Variational quantum fidelity estimation. _Quantum_ , 4: 248, 2020. DOI: 10.22331/q-2020-03-26248. URL `https://quantum-journal.org/ papers/q-2020-03-26-248/` . + +- [26] Y. Li and S. C. Benjamin. Efficient variational quantum simulator incorporating active error minimization. _Phys. Rev. X_ , 7:021050, Jun 2017. DOI: 10.1103/PhysRevX.7.021050. URL `https://link.aps.org/ doi/10.1103/PhysRevX.7.021050` . + +- [27] Kentaro Heya, Ken M Nakanishi, Kosuke Mitarai, and Keisuke Fujii. Subspace variational quantum simulator. _arXiv preprint arXiv:1904.08566_ , 2019. URL `https://arxiv. org/abs/1904.08566` . + +- [28] Kishor Bharti and Tobias Haug. Quantum-assisted simulator. _Physical Review A_ , 104(4):042418, 2021. DOI: 10.1103/PhysRevA.104.042418. URL `https://journals.aps.org/pra/abstract/ 10.1103/PhysRevA.104.042418` . + +- [29] M. Cerezo, Kunal Sharma, Andrew Arrasmith, and Patrick J Coles. Variational quantum state eigensolver. _npj Quantum Information_ , 8(1):1–11, 2022. DOI: 10.1038/s41534-02200611-6. URL `https://doi.org/10.1038/` + + - `s41534-022-00611-6` . + +- [30] Jacob L Beckey, M. Cerezo, Akira Sone, and Patrick J Coles. Variational quantum algorithm for estimating the quantum Fisher information. _Physical Review Research_ , 4(1):013083, 2022. DOI: 10.1103/PhysRevResearch.4.013083. URL `https: //journals.aps.org/prresearch/abstract/ 10.1103/PhysRevResearch.4.013083` . + +- [31] Lennart Bittel and Martin Kliesch. Training variational quantum algorithms is nphard. _Phys. Rev. Lett._ , 127:120502, Sep 2021. DOI: 10.1103/PhysRevLett.127.120502. URL `https://link.aps.org/doi/10.1103/ PhysRevLett.127.120502` . + +- [32] Kosuke Mitarai, Makoto Negoro, Masahiro Kitagawa, and Keisuke Fujii. Quantum circuit learning. _Physical Review A_ , 98(3):032309, 2018. DOI: 10.1103/PhysRevA.98.032309. URL + +`https://journals.aps.org/pra/abstract/ 10.1103/PhysRevA.98.032309` . + +- [33] Maria Schuld, Ville Bergholm, Christian Gogolin, Josh Izaac, and Nathan Killoran. Evaluating analytic gradients on quantum hardware. _Physical Review A_ , 99(3):032331, 2019. DOI: 10.1103/PhysRevA.99.032331. URL `https://journals.aps.org/pra/abstract/ 10.1103/PhysRevA.99.032331` . + +- [34] Jonas M Kübler, Andrew Arrasmith, Lukasz Cincio, and Patrick J Coles. An adaptive optimizer for measurement-frugal variational algorithms. _Quantum_ , 4:263, 2020. DOI: 10.22331/q-2020-05-11-263. URL `https://quantum-journal.org/papers/ q-2020-05-11-263/` . + +- [35] James Stokes, Josh Izaac, Nathan Killoran, and Giuseppe Carleo. Quantum natural gradient. _Quantum_ , 4:269, 2020. DOI: 10.22331/q-202005-25-269. URL `https://quantum-journal. org/papers/q-2020-05-25-269/` . + +- [36] Andrew Arrasmith, Lukasz Cincio, Rolando D Somma, and Patrick J Coles. Operator sampling for shot-frugal optimization in variational algorithms. _arXiv preprint arXiv:2004.06252_ , 2020. URL `https://arxiv.org/abs/2004. 06252` . + +- [37] Jarrod R McClean, Sergio Boixo, Vadim N Smelyanskiy, Ryan Babbush, and Hartmut Neven. Barren plateaus in quantum neural network training landscapes. _Nature Communications_ , 9(1):1–6, 2018. DOI: 10.1038/s41467018-07090-4. URL `https://www.nature.com/ articles/s41467-018-07090-4` . + +- [38] M. Cerezo, Akira Sone, Tyler Volkoff, Lukasz Cincio, and Patrick J Coles. Cost function dependent barren plateaus in shallow parametrized quantum circuits. _Nature Communications_ , 12(1):1–12, 2021. DOI: 10.1038/s41467-021-21728-w. URL `https://www.nature.com/articles/ s41467-021-21728-w` . + +- [39] Samson Wang, Enrico Fontana, M. Cerezo, Kunal Sharma, Akira Sone, Lukasz Cincio, and Patrick J Coles. Noise-induced barren plateaus in variational quantum algorithms. _Nature Communications_ , 12(1):1– 11, 2021. DOI: 10.1038/s41467-021-270456. URL `https://www.nature.com/articles/ s41467-021-27045-6` . + +- [40] M. Cerezo and Patrick J Coles. Higher order derivatives of quantum neural networks with barren plateaus. _Quantum Science and Technology_ , 6(2):035006, 2021. DOI: 10.1088/20589565/abf51a. URL `https://iopscience.iop. org/article/10.1088/2058-9565/abf51a` . + +- [41] Kunal Sharma, M. Cerezo, Lukasz Cincio, and Patrick J Coles. Trainability of dissi- + +Accepted in Quantum 2022-08-31, click title to verify. Published under CC-BY 4.0. + +16 + +pative perceptron-based quantum neural networks. _Physical Review Letters_ , 128(18):180505, 2022. DOI: 10.1103/PhysRevLett.128.180505. + +- [42] Andrew Arrasmith, M. Cerezo, Piotr Czarnik, Lukasz Cincio, and Patrick J Coles. Effect of barren plateaus on gradient-free optimization. _Quantum_ , 5:558, 2021. DOI: 10.22331/q-202110-05-558. URL `https://quantum-journal. org/papers/q-2021-10-05-558/` . + +- [43] Zoë Holmes, Andrew Arrasmith, Bin Yan, Patrick J. Coles, Andreas Albrecht, and Andrew T Sornborger. Barren plateaus preclude learning scramblers. _Physical Review Letters_ , 126(19):190501, 2021. DOI: 10.1103/PhysRevLett.126.190501. URL `https://journals.aps.org/prl/abstract/ 10.1103/PhysRevLett.126.190501` . + +- [44] Carlos Ortiz Marrero, Mária Kieferová, and Nathan Wiebe. Entanglement-induced barren plateaus. _PRX Quantum_ , 2(4):040316, 2021. DOI: 10.1103/PRXQuantum.2.040316. URL `https://journals.aps.org/prxquantum/ abstract/10.1103/PRXQuantum.2.040316` . + +- [45] Taylor L Patti, Khadijeh Najafi, Xun Gao, and Susanne F Yelin. Entanglement devised barren plateau mitigation. _Physical Review Research_ , 3(3):033090, 2021. DOI: 10.1103/PhysRevResearch.3.033090. URL `https://par.nsf.gov/ servlets/purl/10328786` . + +- [46] Arthur Pesah, M. Cerezo, Samson Wang, Tyler Volkoff, Andrew T Sornborger, and Patrick J Coles. Absence of barren plateaus in quantum convolutional neural networks. _Physical Review X_ , 11(4):041011, 2021. DOI: 10.1103/PhysRevX.11.041011. URL `https://journals.aps.org/prx/abstract/ 10.1103/PhysRevX.11.041011` . + +- [47] Zoë Holmes, Kunal Sharma, M. Cerezo, and Patrick J Coles. Connecting ansatz expressibility to gradient magnitudes and barren plateaus. _PRX Quantum_ , 3:010313, Jan 2022. DOI: 10.1103/PRXQuantum.3.010313. URL `https://link.aps.org/doi/10.1103/ PRXQuantum.3.010313` . + +- [48] Andrew Arrasmith, Zoë Holmes, Marco Cerezo, and Patrick J Coles. Equivalence of quantum barren plateaus to cost concentration and narrow gorges. _Quantum Science and Technology_ , 7(4):045015, 2022. DOI: 10.1088/20589565/ac7d06. URL `https://iopscience.iop. org/article/10.1088/2058-9565/ac7d06` . + +- [49] M. Cerezo, Akira Sone, Tyler Volkoff, Lukasz Cincio, and Patrick J Coles. Cost function dependent barren plateaus in shallow parametrized quantum circuits. _Nature Communications_ , 12(1):1–12, 2021. DOI: 10.1038/s41467-021-21728-w. URL + +`https://www.nature.com/articles/ s41467-021-21728-w` . + +- [50] AV Uvarov and Jacob D Biamonte. On barren plateaus and cost function locality in variational quantum algorithms. _Journal of Physics A: Mathematical and Theoretical_ , 54(24):245301, 2021. DOI: 10.1088/17518121/abfac7. URL `https://doi.org/10. 1088/1751-8121/abfac7` . + +- [51] Tyler Volkoff and Patrick J Coles. Large gradients via correlation in random parameterized quantum circuits. _Quantum Science and Technology_ , 6(2):025008, 2021. DOI: 10.1088/20589565/abd89. URL `https://iopscience.iop. org/article/10.1088/2058-9565/abd891` . + +- [52] Guillaume Verdon, Michael Broughton, Jarrod R McClean, Kevin J Sung, Ryan Babbush, Zhang Jiang, Hartmut Neven, and Masoud Mohseni. Learning to learn with quantum neural networks via classical neural networks. _arXiv preprint arXiv:1907.05415_ , 2019. URL `https://arxiv.org/abs/1907.05415` . + +- [53] Edward Grant, Leonard Wossnig, Mateusz Ostaszewski, and Marcello Benedetti. An initialization strategy for addressing barren plateaus in parametrized quantum circuits. _Quantum_ , 3:214, 2019. DOI: 10.22331/q-2019-1209-214. URL `https://quantum-journal.org/ papers/q-2019-12-09-214/` . + +- [54] Andrea Skolik, Jarrod R McClean, Masoud Mohseni, Patrick van der Smagt, and Martin Leib. Layerwise learning for quantum neural networks. _Quantum Machine Intelligence_ , 3(1):1–11, 2021. DOI: 10.1007/s42484-02000036-4. URL `https://doi.org/10.1007/ s42484-020-00036-4` . + +- [55] M Bilkis, M. Cerezo, Guillaume Verdon, Patrick J. Coles, and Lukasz Cincio. A semiagnostic ansatz with variable structure for quantum machine learning. _arXiv preprint arXiv:2103.06712_ , 2021. URL `https://arxiv. org/abs/2103.06712` . + +- [56] Alicia B Magann, Christian Arenz, Matthew D Grace, Tak-San Ho, Robert L Kosut, Jarrod R McClean, Herschel A Rabitz, and Mohan Sarovar. From pulses to circuits and back again: A quantum optimal control perspective on variational quantum algorithms. _PRX Quantum_ , 2(1):010101, 2021. DOI: https://doi.org/10.1103/PRXQuantum.2.010101. URL `https://journals.aps.org/ prxquantum/abstract/10.1103/PRXQuantum. 2.010101` . + +- [57] Stuart Hadfield, Zhihui Wang, Bryan O’Gorman, Eleanor G Rieffel, Davide Venturelli, and Rupak Biswas. From the quantum approximate optimization algorithm to a quantum alternating operator ansatz. _Algorithms_ , + +Accepted in Quantum 2022-08-31, click title to verify. Published under CC-BY 4.0. + +17 + + - 12(2):34, 2019. DOI: 10.3390/a12020034. URL `https://www.mdpi.com/1999-4893/12/2/34` . + +- [58] Dave Wecker, Matthew B. Hastings, and Matthias Troyer. Progress towards practical quantum variational algorithms. _Physical Review A_ , 92:042303, Oct 2015. DOI: 10.1103/PhysRevA.92.042303. URL `https://link.aps.org/doi/10.1103/ PhysRevA.92.042303` . + +- [59] Roeland Wiersema, Cunlu Zhou, Yvette de Sereville, Juan Felipe Carrasquilla, Yong Baek Kim, and Henry Yuen. Exploring entanglement and optimization within the hamiltonian variational ansatz. _PRX Quantum_ , 1(2):020319, 2020. DOI: 10.1103/PRXQuantum.1.020319. URL `https://journals.aps.org/prxquantum/ abstract/10.1103/PRXQuantum.1.020319` . + +- [60] Linghua Zhu, Ho Lun Tang, George S Barron, Nicholas J Mayhall, Edwin Barnes, and Sophia E Economou. Adaptive quantum approximate optimization algorithm for solving combinatorial problems on a quantum computer. _Physical Review Research_ , 4(3):033029, 2022. DOI: 10.1103/PhysRevResearch.4.033029. URL `https: //journals.aps.org/prresearch/abstract/ 10.1103/PhysRevResearch.4.033029` . + +- [61] Alexandre Choquette, Agustin Di Paolo, Panagiotis Kl Barkoutsos, David Sénéchal, Ivano Tavernelli, and Alexandre Blais. Quantumoptimal-control-inspired ansatz for variational quantum algorithms. _Physical Review Research_ , 3(2):023092, 2021. DOI: 10.1103/PhysRevResearch.3.023092. URL `https: //journals.aps.org/prresearch/abstract/ 10.1103/PhysRevResearch.3.023092` . + +- [62] Supanut Thanasilp, Samson Wang, Nhat A Nghiem, Patrick J. Coles, and M. Cerezo. Subtleties in the trainability of quantum machine learning models. _arXiv preprint arXiv:2110.14753_ , 2021. URL `https://arxiv. org/abs/2110.14753` . + +- [63] D. D’Alessandro. _Introduction to Quantum Control and Dynamics_ . Chapman & Hall/CRC Applied Mathematics & Nonlinear Science. Taylor & Francis, 2007. ISBN 9781584888840. URL `https://books.google. sm/books?id=HbMYmAEACAAJ` . + +- [64] Sukin Sim, Peter D Johnson, and Alán Aspuru-Guzik. Expressibility and entangling capability of parameterized quantum circuits for hybrid quantum-classical algorithms. _Advanced Quantum Technologies_ , 2(12):1900070, 2019. DOI: 10.1002/qute.201900070. URL `https://onlinelibrary.wiley.com/doi/ full/10.1002/qute.201900070` . + +- [65] Carlton M Caves. Quantum error correction + + - and reversible operations. _Journal of Superconductivity_ , 12(6):707–718, 1999. DOI: 10.1023/A:1007720606911. URL `https: //link.springer.com/article/10.1023/A: 1007720606911` . + +- [66] P Rungta, WJ Munro, K Nemoto, P Deuar, Gerard J Milburn, and CM Caves. Qudit entanglement. In _Directions in Quantum Optics_ , pages 149–164. Springer, 2001. DOI: 10.1007/3-540-40894-0_14. URL `https://link.springer.com/chapter/ 10.1007%2F3-540-40894-0_14` . + +- [67] Nicholas Hunter-Jones. Unitary designs from statistical mechanics in random quantum circuits. _arXiv preprint arXiv:1905.12053_ , 2019. URL `https://arxiv.org/abs/1905.12053` . + +- [68] Yoshifumi Nakata, Masato Koashi, and Mio Murao. Generating a state t-design by diagonal quantum circuits. _New Journal of Physics_ , 16(5):053043, 2014. DOI: 10.1088/1367-2630/16/5/053043. URL `https://iopscience.iop.org/article/ 10.1088/1367-2630/16/5/053043` . + +- [69] Zhi-Cheng Yang, Armin Rahmani, Alireza Shabani, Hartmut Neven, and Claudio Chamon. Optimizing variational quantum algorithms using pontryagin’s minimum principle. _Physical Review X_ , 7(2):021027, 2017. DOI: 10.1103/PhysRevX.7.021027. URL `https://journals.aps.org/prx/abstract/ 10.1103/PhysRevX.7.021027` . + +- [70] Oinam Romesh Meitei, Bryan T Gard, George S Barron, David P Pappas, Sophia E Economou, Edwin Barnes, and Nicholas J Mayhall. Gatefree state preparation for fast variational quantum eigensolver simulations: ctrl-vqe. _arXiv preprint arXiv:2008.04302_ , 2020. URL `https: //arxiv.org/abs/2008.04302` . + +- [71] Juneseo Lee, Alicia B Magann, Herschel A Rabitz, and Christian Arenz. Progress toward favorable landscapes in quantum combinatorial optimization. _Physical Review A_ , 104(3):032401, 2021. DOI: 10.1103/PhysRevA.104.032401. URL `https://link.aps. org/doi/10.1103/PhysRevA.104.032401` . + +- [72] Jun Li, Xiaodong Yang, Xinhua Peng, and Chang-Pu Sun. Hybrid quantum-classical approach to quantum optimal control. _Physical Review Letters_ , 118(15):150503, 2017. DOI: 10.1103/PhysRevLett.118.150503. URL `https://journals.aps.org/prl/abstract/ 10.1103/PhysRevLett.118.150503` . + +- [73] Viswanath Ramakrishna and Herschel Rabitz. Relation between quantum computing and quantum controllability. _Physical Review A_ , 54(2):1715, 1996. DOI: 10.1103/PhysRevA.54.1715. URL + +Accepted in Quantum 2022-08-31, click title to verify. Published under CC-BY 4.0. + +18 + +`https://journals.aps.org/pra/abstract/ 10.1103/PhysRevA.54.1715` . + +- [74] Seth Lloyd. Quantum approximate optimization is computationally universal. _arXiv preprint arXiv:1812.11075_ , 2018. URL `https: //arxiv.org/abs/1812.11075` . + +- [75] Mauro ES Morales, JD Biamonte, and Zoltán Zimborás. On the universality of the quantum approximate optimization algorithm. _Quantum Information Processing_ , 19(9): 1–26, 2020. DOI: 10.1007/s11128-020-02748-9. URL `https://link.springer.com/article/ 10.1007/s11128-020-02748-9` . + +- [76] V Akshay, H Philathong, Mauro ES Morales, and Jacob D Biamonte. Reachability deficits in quantum approximate optimization. _Physical Review Letters_ , 124(9):090504, 2020. DOI: 10.1103/PhysRevLett.124.090504. URL `https://journals.aps.org/prl/abstract/ 10.1103/PhysRevLett.124.090504` . + +- [77] Robert Zeier and Thomas Schulte-Herbrüggen. Symmetry principles in quantum systems theory. _Journal of mathematical physics_ , 52(11):113510, 2011. DOI: https://doi.org/10.1063/1.3657939. URL `https://aip.scitation.org/doi/pdf/10. 1063/1.3657939` . + +- [78] Thomas Polack, Haim Suchowski, and David J Tannor. Uncontrollable quantum systems: A classification scheme based on lie subalgebras. _Physical Review A_ , 79(5):053403, 2009. DOI: https://doi.org/10.1103/PhysRevA.79.053403. URL `https://journals.aps.org/pra/ abstract/10.1103/PhysRevA.79.053403` . + +- [79] Leonardo Banchi, Daniel Burgarth, and Michael J Kastoryano. Driven quantum dynamics: will it blend? _Physical Review X_ , 7(4):041015, 2017. DOI: 10.1103/PhysRevX.7.041015. URL `https://journals.aps.org/prx/abstract/ 10.1103/PhysRevX.7.041015` . + +- [80] Abhinav Kandala, Antonio Mezzacapo, Kristan Temme, Maika Takita, Markus Brink, Jerry M. Chow, and Jay M. Gambetta. Hardwareefficient variational quantum eigensolver for small molecules and quantum magnets. _Nature_ , 549(7671):242–246, Sep 2017. ISSN 14764687. DOI: 10.1038/nature23879. URL `https: //doi.org/10.1038/nature23879` . + +- [81] Aram W Harrow and Richard A Low. Random quantum circuits are approximate 2-designs. _Communications in Mathematical Physics_ , 291 (1):257–302, 2009. DOI: 10.1007/s00220-0090873-6. URL `https://link.springer.com/ article/10.1007%2Fs00220-009-0873-6` . + +- [82] Fernando GSL Brandao, Aram W Harrow, and Michał Horodecki. Local random quantum circuits are approximate polynomial-designs. + +_Communications in Mathematical Physics_ , 346 (2):397–434, 2016. DOI: 10.1007/s00220-0162706-8. URL `https://link.springer.com/ article/10.1007%2Fs00220-016-2706-8` . + +- [83] Aram Harrow and Saeed Mehraban. Approximate unitary _t_ -designs by short random quantum circuits using nearest-neighbor and longrange gates. _arXiv preprint arXiv:1809.06957_ , 2018. URL `https://arxiv.org/abs/1809. 06957` . + +- [84] Andrew Lucas. Ising formulations of many np problems. _Frontiers in Physics_ , 2:5, 2014. DOI: 10.3389/fphy.2014.00005. URL `https://www.frontiersin.org/articles/ 10.3389/fphy.2014.00005/full` . + +- [85] Michael Streif and Martin Leib. Training the quantum approximate optimization algorithm without access to a quantum processing unit. _Quantum Science and Technology_ , 5(3):034008, 2020. DOI: 10.1088/20589565/ab8c2b. URL `https://iopscience.iop. org/article/10.1088/2058-9565/ab8c2b` . + +- [86] M. Cerezo, Raúl Rossignoli, N Canosa, and E Ríos. Factorization and criticality in finite _xxz_ systems of arbitrary spin. _Physical Review Letters_ , 119(22):220605, 2017. DOI: 10.1103/PhysRevLett.119.220605. URL `https://journals.aps.org/prl/abstract/ 10.1103/PhysRevLett.119.220605` . + +- [87] Xiaoting Wang, Daniel Burgarth, and S Schirmer. Subspace controllability of spin-1 2 chains with symmetries. _Physical Review A_ , 94(5):052319, 2016. DOI: https://doi.org/10.1103/PhysRevA.94.052319. URL `https://journals.aps.org/pra/ abstract/10.1103/PhysRevA.94.052319` . + +- [88] Benoît Collins and Piotr Śniady. Integration with respect to the haar measure on unitary, orthogonal and symplectic group. _Communications in Mathematical Physics_ , 264(3):773– 795, 2006. DOI: 10.1007/s00220-006-1554-3. URL `https://link.springer.com/article/ 10.1007%2Fs00220-006-1554-3` . + +- [89] PM Poggi and Diego Ariel Wisniacki. Optimal control of many-body quantum dynamics: Chaos and complexity. _Physical Review A_ , 94(3):033406, 2016. DOI: 10.1103/PhysRevA.94.033406. URL `https://journals.aps.org/pra/abstract/ 10.1103/PhysRevA.94.033406` . + +- [90] Martín Larocca and Diego Wisniacki. Krylovsubspace approach for the efficient control of quantum many-body dynamics. _Physical Review A_ , 103(2):023107, 2021. DOI: 10.1103/PhysRevA.103.023107. URL `https://journals.aps.org/pra/abstract/ 10.1103/PhysRevA.103.023107` . + +- [91] P Erdos and A Renyi. On random graphs + +Accepted in Quantum 2022-08-31, click title to verify. Published under CC-BY 4.0. + +19 + + - i. _Publ. math. debrecen_ , 6(290-297):18, 1959. URL `http://snap.stanford.edu/class/ cs224w-readings/erdos59random.pdf` . + +- [92] Christian Arenz and Herschel Rabitz. Drawing together control landscape and tomography principles. _Physical Review A_ , 102(4):042207, 2020. DOI: 10.1103/PhysRevA.102.042207. URL `https://journals.aps.org/pra/ abstract/10.1103/PhysRevA.102.042207` . + +- [93] Zbigniew Puchala and Jaroslaw Adam Miszczak. Symbolic integration with respect to the haar measure on the unitary groups. _Bulletin of the Polish Academy of Sciences Technical Sciences_ , 65(1):21–27, 2017. DOI: 10.1515/bpasts-2017-0003. URL `http: //journals.pan.pl/dlibra/publication/ 121307/edition/105697/content` . + +- [94] Bryan T Gard, Linghua Zhu, George S Barron, Nicholas J Mayhall, Sophia E Economou, and Edwin Barnes. Efficient symmetrypreserving state preparation circuits for the variational quantum eigensolver algorithm. _npj Quantum Information_ , 6(1): 1–9, 2020. DOI: 10.1038/s41534-019-0240-1. URL `https://www.nature.com/articles/ s41534-019-0240-1` . + +- [95] Christian Kokail, Christine Maier, Rick van Bijnen, Tiff Brydges, Manoj K Joshi, Petar Jurcevic, Christine A Muschik, Pietro Silvi, Rainer Blatt, Christian F Roos, et al. Self-verifying variational quantum simulation of lattice models. _Nature_ , 569(7756):355– 360, 2019. DOI: 10.1038/s41586-019-11774. URL `https://www.nature.com/articles/ s41586-019-1177-4` . + +- [96] Kunal Sharma, Sumeet Khatri, M. Cerezo, and Patrick J Coles. Noise resilience of variational quantum compiling. _New Journal of Physics_ , 22(4):043006, 2020. DOI: 10.1088/13672630/ab784c. URL `https://iopscience.iop. org/article/10.1088/1367-2630/ab784c` . + +- [97] Nikolay V Tkachenko, James Sud, Yu Zhang, Sergei Tretiak, Petr M Anisimov, Andrew T Arrasmith, Patrick J. Coles, Lukasz Cincio, and Pavel A Dub. Correlation-informed permutation of qubits for reducing ansatz depth in vqe. _PRX Quantum_ , 2(2):020337, 2021. DOI: 10.1103/PRXQuantum.2.020337. URL `https://journals.aps.org/prxquantum/ abstract/10.1103/PRXQuantum.2.020337` . + +- [98] Bobak Toussi Kiani, Seth Lloyd, and Reevu Maity. Learning unitaries by gradient descent. _arXiv preprint arXiv:2001.11897_ , 2020. URL `https://arxiv.org/abs/2001.11897` . + +- [99] Zhihui Wang, Nicholas C Rubin, Jason M Dominy, and Eleanor G Rieffel. _XY_ mixers: Analytical and numerical results for the quantum alternating operator ansatz. + + - _Physical Review A_ , 101(1):012320, 2020. DOI: 10.1103/PhysRevA.101.012320. URL `https://journals.aps.org/pra/abstract/ 10.1103/PhysRevA.101.012320` . + +- [100] Andreas Bärtschi and Stephan Eidenbenz. Grover mixers for qaoa: Shifting complexity from mixer design to state preparation. In _2020 IEEE International Conference on Quantum Computing and Engineering (QCE)_ , pages 72–82. IEEE, 2020. DOI: 10.1109/QCE49297.2020.00020. URL `https://www.computer.org/ csdl/proceedings-article/qce/2020/ 896900a072/1p2VnUCmpYA` . + +- [101] Wen Wei Ho and Timothy H. Hsieh. Efficient variational simulation of non-trivial quantum states. _SciPost Phys._ , 6:29, 2019. DOI: 10.21468/SciPostPhys.6.3.029. URL `https://scipost.org/10.21468/ SciPostPhys.6.3.029` . + +- [102] Chris Cade, Lana Mineh, Ashley Montanaro, and Stasja Stanisic. Strategies for solving the fermi-hubbard model on near-term quantum computers. _Physical Review B_ , 102(23):235122, 2020. DOI: 10.1103/PhysRevB.102.235122. URL `https://journals.aps.org/prb/ abstract/10.1103/PhysRevB.102.235122` . + +- [103] Chen Zhao and Xiao-Shan Gao. Analyzing the barren plateau phenomenon in training quantum neural networks with the ZX-calculus. _Quantum_ , 5:466, June 2021. ISSN 2521-327X. DOI: 10.22331/q-2021-06-04-466. URL `https: //doi.org/10.22331/q-2021-06-04-466` . + +- [104] Kaining Zhang, Min-Hsiu Hsieh, Liu Liu, and Dacheng Tao. Toward trainability of quantum neural networks. _arXiv preprint arXiv:2011.06258_ , 2020. URL `https://arxiv. org/abs/2011.06258` . + +- [105] Frederic Sauvage, Sukin Sim, Alexander A Kunitsa, William A Simon, Marta Mauri, and Alejandro Perdomo-Ortiz. Flip: A flexible initializer for arbitrarily-sized parametrized quantum circuits. _arXiv preprint arXiv:2103.08572_ , 2021. URL `https://arxiv.org/abs/2103.08572` . + +- [106] Yidong Liao, Min-Hsiu Hsieh, and Chris Ferrie. Quantum optimization for training quantum neural networks. _arXiv preprint arXiv:2103.17047_ , 2021. URL `https://arxiv. org/abs/2103.17047` . + +- [107] Raj Chakrabarti and Herschel Rabitz. Quantum control landscapes. _International Reviews in Physical Chemistry_ , 26(4):671–735, 2007. DOI: 10.1080/01442350701633300. URL `https://www.tandfonline.com/doi/abs/10. 1080/01442350701633300` . + +- [108] Martín Larocca, Pablo M Poggi, and Diego A Wisniacki. Quantum control landscape for a two-level system near the quantum speed + +Accepted in Quantum 2022-08-31, click title to verify. Published under CC-BY 4.0. + +20 + + - limit. _Journal of Physics A: Mathematical and Theoretical_ , 51(38):385305, aug 2018. DOI: 10.1088/1751-8121/aad657. URL `https:// doi.org/10.1088/1751-8121/aad657` . + +- [109] Martín Larocca, Esteban Calzetta, and Diego A. Wisniacki. Exploiting landscape geometry to enhance quantum optimal control. _Physical Review A_ , 101:023410, Feb 2020. DOI: 10.1103/PhysRevA.101.023410. URL `https://link.aps.org/doi/10.1103/ PhysRevA.101.023410` . + +- [110] Winton G. Brown and Lorenza Viola. Convergence rates for arbitrary statistical moments of random quantum circuits. _Phys. Rev. Lett._ , 104:250501, Jun 2010. DOI: 10.1103/PhysRevLett.104.250501. URL `https://link.aps. org/doi/10.1103/PhysRevLett.104.250501` . + +- [111] Domenico D’Alessandro and Jonas T Hartwig. Dynamical decomposition of bilinear control systems subject to symmetries. _Journal of Dynamical and Control Systems_ , 27(1):1–30, 2021. DOI: https://doi.org/10.1007/s10883020-09488-0. + +Accepted in Quantum 2022-08-31, click title to verify. Published under CC-BY 4.0. + +21 + +## **Appendices** + +In the following appendices we present additional information and derive proofs for the main results in the manuscript. In Appendix A we introduce preliminary notation and definitions that will be relevant for the rest of the appendices. Then, in Appendix B we provide additional details on different widely known ansatzes that are Periodic Structure Ansatz (PSA). In Appendix C we provide a brief review of barren plateaus. Appendices F–M contain the proofs of our main Theorems, Corollaries and Propositions. Finally, in Appendix N we discuss the symmetries in the _XXZ_ and Ising spin models considered in the main text, and in Appendix O we provide additional details on the initial state used for the numerical simulations of the _XXZ_ model. + +## A Preliminaries + +Let us first review some definitions and prior results that will be relevant for the rest of the appendices. + +**Properties of the Haar measure.** Let _U_ ( _d_ ) denote the group of _d × d_ unitary matrices. Let _dµH_ ( _V_ ) = _dµ_ ( _V_ ) be the volume element of the Haar measure, where _V ∈U_ ( _d_ ). Then, the Haar measure has the following properties: (1) The volume of the Haar measure is finite: � _U_ ( _d_ )_dµ_(_V_)_< ∞_.(2)TheHaarmeasureisuniquely defined up to a multiplicative constant factor. (3) Let _dζ_ ( _V_ ) be an invariant measure. Then there exists a constant _c_ such that _dζ_ ( _V_ ) = _c · dµ_ ( _V_ ). (4) The Haar measure is left- and right-invariant under the action of the unitary group of degree _d_ , i.e., for any integrable function _g_ ( _V_ ), the following holds: + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0022-05.png) + + +where _W ∈U_ ( _d_ ). + +**Symbolic integration.** Let us present formulas that allow for the symbolical integration with respect to the Haar measure on a unitary group [93]. For any _V ∈U_ ( _d_ ) the following expressions are valid for the first two moments: + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0022-08.png) + + +where _u_ **_ij_** are the matrix elements of _U_ . Assuming _d_ = 2_n_ , we use the notation **_i_** = ( _i_ 1 _, . . . in_ ) to denote a bitstring of length _n_ such that _i_ 1 _, i_ 2 _, . . . , in ∈{_ 0 _,_ 1 _}_ . + +**Useful Identities.** We introduce the following identities, which can be derived using Eq. (37) (see [49] for a review): + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0022-11.png) + + +#### where _A, B, C_ , and _D_ are linear operators on a _d_ -dimensional Hilbert space. + +**Integration over parameter space** : In the next sections we will derive analytical expressions for the variance of the partial derivatives of cost functions _C_ ( **_θ_** ) over parametrized circuits _U_ ( **_θ_** ). In such derivations, we will have to deal with integration over the parameter space. A key step in the following analysis will be to relate the integration over parameters with integration over the ensemble of unitaries arising from different parameter choices. In this sense, we recall that given a set of parameters _{_ **_θ_** _}_ one can obtain an associated set of unitaries generated by the quantum circuit _{U_ ( **_θ_** ) _}_ . Then, consider the integration of some function _f_ ( _U_ ( **_θ_** )) over **_θ_** . Defining U as the distribution of unitaries generated by _U_ ( **_θ_** ), the following identity holds + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0022-14.png) + + +In addition, if the distribution of unitaries U can be shown to converge to a 2-design, the integration over the distibution can be further converted into an integration over the Haar measure + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0023-01.png) + + +allowing the use of identities (38), (39), and (40). + +## B Ansatzes + +In general, ansatzes for parametrized quantum circuits can be divided into two primary categories: problemagnostic and problem-inspired ansatzes. In a problem agnostic ansatz one does not have any information about the problem, or its solution, that one can encode in the ansatz. Such is the case for instance in a task of estimating the spectrum of an unknown density operator [29]. On the other hand, problem-inspired ansatzes employ prior information about a given problem or task. For example, for the problem of estimating the ground state energy of a particular Hamiltonian, one can design ansatzes that preserve the symmetry of the problem Hamiltonian [94]. + +Here we remark that several well known problem-agnostic and problem-inspired ansatzes in the literature are PSAs of the form in Eq. (2). In particular, our framework allows us to study the hardware-efficient ansatz (HEA) [80], quantum alternating operator ansatz (QAOA) [19], Adaptive QAOA [57, 60], Hamiltonian variational ansatz (HVA) [58], and Quantum Optimal Control Ansatz (QAOC) [61]. + +Below we provide several examples of problem-inspired and problem-agnostic PSA and highlight their advantages in different problems. + +**Hardware efficient ansatz.** The Hardware Efficient Ansatz (HEA) is a problem-agnostic ansatz, which relies on gates native to a quantum hardware. In particular, an ansatz can be designed based on a gate alphabet, which depends on the architecture and the connectivity of a given quantum hardware. This procedure helps in avoiding the overhead associated with transpiling an arbitrary unitary into a sequence of native gates. For example, one can consider native gates, such as single qubit rotations _e__−iθ/_2_Z_ _e__−iγ/_2_Y_ and CNOTs, where _Y_ and _Z_ denote Pauli matrices, and a CNOT between the control qubit _i_ and the target qubit _j_ is given by: _e__−iπ/_2(_|_1_⟩⟨_1_|i⊗_(_Xj−_1_j_)) . Then an ansatz of the form in (2) can be generated as follows: one layer consists of parametrized single qubit rotations on each qubit, followed by unparametrized CNOTs acting on neighboring qubits. + +The HEA has been employed to prepare the ground state of molecules [80], to study Hamiltonians that are similar to the device’s interactions [95], and in several other variational quantum algorithms [21, 96, 97, 49]. The HEA is also suitable in the near-term implementations of VQAs due to its low-depth structure which results into a lower-noise circuit in comparison to other ansatze [97, 39]. + +**Quantum alternating operator ansatz.** The Quantum Alternating Operator Ansatz (QAOA) is a probleminspired ansatz that simulates the discretized adiabatic transformations [19]. Consider a goal of preparing the ground state of a problem Hamiltonian _HP_ . Let _HM_ denote a mixer Hamiltonian, with corresponding ground state _|ψ⟩_ . Then the QAOA maps _|ψ⟩_ to the ground state of _HP_ by sequentially applying the problem unitary _e__−iγlHP_ , followed by the mixer unitary _e__−iβlHM_ . Let **_θ_** = ( **_γ_** _,_ **_β_** ). Then the QAOA is given by _U_ ( **_θ_** ) = � _Ll_ =1_e−iβlHM e−iγlHP_,whichfollowsthegeneralformoftheansatzdefinedin(2). Here, _p_ is the order of the discretized adiabatic transformation and it determines the precision of the solution [19]. The QAOA was originally introduced for finding approximate solutions to combinatorial optimization problems [19]. The QAOA has been generalized as a standalone ansatz [57] and its performance has been investigated in several tasks, including the task of learning a unitary [98]. Moreover, the QAOA has been shown to be computationally universal [74, 75], and the choice of optimal mixer is still an open debate [99, 100]. + +**Adaptive QAOA.** As a consequence of the adiabatic theorem, the QAOA should lead to good solutions for high values of _p_ [19]. However, for small values of _p_ , the QAOA is an _ad-hoc_ ansatz, which is not necessarily an optimal strategy to approximate the ground state of the problem Hamiltonian. A way to improve such an _ad-hoc_ ansatz is to employ a variable mixer instead of a fixed mixer at each layer [57]. Let _{Gk}__q_ _k_ =1denotea set of mixer Hamiltonians. Then an adaptive QAOA can be defined as follows: _U_ ( **_θ_** ) =_L_ _l_ =1_e−iβlGle−iγlHp_, where each _Gl_ can be adaptively picked from _{Gk}__q_ _k_ =1. + +One particular adaptive approach was introduced in [60], where at each layer, _Gl_ is picked based on the largest gradient of the cost function among all _{Gl}_ . Moreover, [60] observed that adaptive entangling mixers can improve performance and reduce the number of parameters and CNOTs to achieve a desired accuracy in comparison to the non-adaptive QAOA. We note that the adaptive QAOA follows the form in + +Accepted in Quantum 2022-08-31, click title to verify. Published under CC-BY 4.0. + +23 + +(2) if adaptive mixers are learned up to a fixed layer and then the whole structure is repeated. That is, _r U_ ( **_θ_** ) =_p_ _m_ =1 � _l_ =1_e−iβl,mGl,me−iγl,mHP_,where_Gl,_1arelearnedadaptivelyforeach_l∈{_1_, . . . , r}_,and _Gl,_ 1 = _Gl,m_ for all _m ∈{_ 1 _, . . . , m}_ . + +**Hamiltonian variational ansatz.** The Hamiltonian variational ansatz is another problem-inspired ansatz, which implements time evolution under problem Hamiltonian via Trotterization [58]. It can be understood as a generalization of the QAOA to more than two non-commuting Hamiltonians. Let _HP_ = _l__Hl_denotea problem Hamiltonian, such that [ _Hl, Hl__′_ ] = 0. Then the HVA of order _p_ is given by _U_ ( **_θ_** ) =_p_ _k_ =1 � _l__e−iθl,kHl_, which is in the form of (2). The HVA has been investigated in studying one- and two-dimensional quantum many-body models [101, 102]. + +A simple example where the HVA can be employed is the XXZ model to study magnetism. For a onedimensional chain, the Hamiltonian for the XXZ model is given by _HXXZ_ = _−__n_ _l_ =1_XlXl_+1+_YlYl_+1+_gZlZl_+1, where _g_ determines the phase of magnetisation. Let _HA_ =_n_ _l_ =1_AlAl_+1,where_A ∈{X, Y, Z}_.Then,oneway to parametrize a HVA of order _p_ is as follows: _U_ ( **_θ_** ) =_p_ _l_ =1_e−iβlHXe−iγlHY e−iδlHZ_for_g_=1,andwhere **_θ_** = ( **_β_** _,_ **_γ_** _,_ **_δ_** ). Another way to parametrize a HVA is as follows: _U_ ( **_θ_** ) =_p_ _l_ =1_e−iβl_(_HX_+_HY_)_e−iδlHZ_,wherewe redefined **_θ_** = ( **_β_** _,_ **_δ_** ). + +**Quantum optimal control ansatz.** The HVA discussed above helps constraining the variational search to a relevant symmetric subspace of the the total Hilbert space. In general, this approach might require high values of _p_ to achieve a desired accuracy in approximating the ground state of many-body Hamiltonians. One way to avoid high values of _p_ is to introduce drive terms in addition to the problem Hamiltonian, which break the symmetry of the problem Hamiltonian _Hp_ . This approach falls under the framework of quantum optimal control [61]. In particular, let _{H_ˆ _k}_ denote a set of drive terms. Then the update time-dependent Hamiltonian is given by _H_¯ ( _t_ ) = _HP_ + _k__ck_(_t_)_Hk_,wheredriveterms_Hk_arepickedsuchthat[_HP , Hk_] = 0forall_k_.Here,_ck_(_t_)are time-dependent control parameters. Let _HP_ = _q__Hq_andlet**_θ_**= (**_γ_**_,_**_β_**).ThentheQuantumOptimalControl Ansatz (QOCA) of order _L_ is given by _U_ ( **_θ_** ) =_L_ _l_ =1 � _q__e−iβl,qHq_� _k__e−iγl,kHk_,where_γl,k_denotethediscrete drive amplitudes of the control parameter _ck_ ( _t_ ). + +In general, finding an optimal drive Hamiltonian terms _{Hk}_ is a computationally challenging problem. One can employ an adaptive approach to pick drive Hamiltonians from a fixed set of Hamiltonian, similar to the adaptive QAOA [60]. In [61], the QOCA was shown to outperform other ansatze, including the HEA and the HVA for the task of preparation of the ground state of the half-filled Fermi Hubbard model. + +## C Barren Plateaus + +As mentioned in the main text, the barren plateau phenomenon has been recognized as one of the most important challenges to overcome to guarantee the success of VQAs. When a cost function exhibits a barren plateau, its gradients are exponentially suppressed (in average) across the optimization landscape. Consider the following mathematical definition. + +**Definition 1** (Barren Plateau) **.** _A cost function C_ ( **_θ_** ) _as in Eq._ (1) _is said to have a barren plateau when training θµ ≡ θpq ∈_ **_θ_** _, if the cost function partial derivative ∂C_ ( **_θ_** ) _/∂θµ ≡ ∂µC_ ( **_θ_** ) _is such that_ + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0024-08.png) + + +_for some b >_ 1 _. Here the variance is taken with respect to the set of parameters_ **_θ_** _._ + +From Chebyshev inequality we know that Var **_θ_** [ _∂µC_ ( **_θ_** )] bounds the probability that _∂µC_ ( **_θ_** ) diverges from its average (of zero) as _P_ ( _|_ ( _∂µC_ ( **_θ_** ) _| > c_ ) ⩽ Var **_θ_** [ _∂µC_ ( **_θ_** )] _/c_2 for any _c >_ 0. + +Equation (43) implies that one requires a precision (i.e., a number of shots) that grows exponentially with _n_ to navigate trough the flat landscape and determine a cost minimizing direction when optimizing the cost function. Moreover, as shown in [40, 42], barren plateaus affect both gradient-based and gradient-free methods meaning that simply changing the optimization strategy does not mitigate or solve the barren plateau issues. Since the goal of VQAs is to have computational complexities that scale polynomial with _n_ , such exponential scaling in the required precision destroys the hope of achieving a computational advantage with the VQA over classical methods (which usually scale exponentially with _n_ ). + +The first result for barren plateaus was obtained in [37], where it was shown that deep unstructured ansatz that form 2-designs have barren plateaus. This phenomenon was then generalized to layered Hardware Efficient Ansatzes in [49] were it was proven that the locality of the cost function is connected to the existence of barren plateaus. That is, global cost functions (i.e., cost functions where _O_ in (1) acts non-trivially in all qubits) exhibit + +Accepted in Quantum 2022-08-31, click title to verify. Published under CC-BY 4.0. + +24 + +barren plateaus even for shallow depths, whereas local cost functions (i.e., cost functions where _O_ in (1) acts non-trivially in a small number of neighboring qubits) do not exhibit barren plateaus for short-depth ansatzes. + +The barren plateaus phenomenon has also been studied in the context of quantum neural networks [41, 46, 103], and to the problem of learning scramblers [43]. In addition, it has been shown that circuits that generate large amounts of entanglement [41, 45, 44] are prone to suffer from barren plateaus. To circumvent or mitigate the effect of barren plateaus, several strategies have been developed [52, 51, 54, 53, 46, 104, 28, 29, 105, 106]. + +## D Quantum Optimal Control + +Here we recall for convenience that in a standard QOC setting one is interested in controlling the dynamical evolution of a quantum state _|ψ⟩_ in a _d_ -dimensional Hilbert space _H_ = C_d_ (where _d_ = 2_n_ ) [63]. Here, the system dynamics are determined by a Hamiltonian + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0025-04.png) + + +that is tunable through some time-dependent control fields functions _{fk_ ( _t_ ) _}_ . + +At its core, the problem in QOC is to determine how to shape the control fields such that the system evolves in a desired manner. A specific set of optimal fields is usually constructed by imposing a parametrization on the functions and applying standard numerical optimization routines. The success of such optimization process depends on the structure of the underling optimization spaces, the so-called quantum control landscapes [107, 108, 109]. + +For instance, a common choice is to consider piece-wise constant fields where the protocol duration _T_ is divided in _L_ intervals ∆ _tj_ = _tj − tj−_ 1 (such that _T_ =_L_ _j_ =1_tj_)ateachofwhichthefieldstakeaconstant value, e.g., _fk_ ( _t_ ) = _fk,j_ if _tj−_ 1 _< t < tj_ . In this case, the propagator factorizes into a product of individual sub-propagators, each of which is generated by a constant Hamiltonian and thus leads to the simple matrix exponential form + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0025-08.png) + + +By Trotterizing Eq. (45) one finds + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0025-10.png) + + +where _fl,_ 0 = 1 for all _l_ . Note that (46) is a PSA of the form of Eq. (2), through the identification + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0025-12.png) + + +We remark that in the limit ∆ _tl −→_ 0, Eq. (46) becomes exact. In the general case, the exact and Trotterized ansatzes approximately coincide, and a nontrivial correction of Eq. (47) is needed to make the correspondence exact. In any case, Eq. (47) allows us to henceforth use the notation _U_ ( _{fk_ ( _t_ ) _}_ ) = _U_ ( **_θ_** ) and indicate with **_θ_** the trainable parameters in a QOC setting. + +## E Dynamical Lie Algebra computation + +First, let us recall that a Lie algebra is a vector space g together with an operation [ _·, ·_ ] : g _→_ g called _Lie bracket_ that is bilinear, _alternating_ (the output is zero if the inputs are linearly dependent) and satisfies the identity [ _x,_ [ _y, z_ ]] + [ _y,_ [ _z, x_ ]] + [ _z,_ [ _x, y_ ]] = 0, known as the Jacobi identity. Lie algebras are vector spaces that are _closed_ under such Lie bracket, i.e. [ _x, y_ ] _∈_ g for all _x, y ∈_ g. Note that this operation is not necessarily associative, i.e. [ _x_ [ _y, z_ ] = [[ _x, y_ ] _, z_ ]. In fact, this is precisely what the Jacobi identity captures: how the order of evaluation affects the result of the operation. + +In our quantum context, Lie algebras manifest as matrix Lie algebras. For example, the space of quantum observables u( _d_ ) is a subspace of the vector space of _d × d_ complex matrices that is closed under matrix commutator (playing the role of a Lie bracket). More generally, we will encounter ourselves with Lie algebras that form Lie subalgebras of u( _d_ ). A subalgebra is a subspace of an algebra that is itself closed under the Lie bracket. For example, in a _n_ qubit quantum system, the subspace Ω= span _{X, Y, Z}_ , where _X_ = _i__Xi_, + +Accepted in Quantum 2022-08-31, click title to verify. Published under CC-BY 4.0. + +25 + +**Algorithm 1:** Basis for the Dynamical Lie Algebra (DLA). + +**Input:** Set of generators _G_ of the ansatz. **Output:** Basis _S_ of the algebra g obtained from _G_ . **Init:** _S ←G_ , _Sprev ←G_ , _Snew ←{}_ , _C ←_ 1 , `new` _←_ 1. **1 while** _`new` >0_ **do 2** _Snew ←{}_ . **3 for** _h_ 0 _∈G_ **do 4 for** _h ∈ Sprev_ **do 5** _C ←_ [ _h_ 0 _, h_ ] **6 if** _S ∪{C} is linearly independent_ **7 then 8** _S ← S ∪{C}_ . **9** _Snew ← Snew ∪{C}_ . **10** `new` _←|Snew|_ . **11** _Sprev ← Snew_ . **12 return** _S_ + +_Y_ = _i__Yi_and_Z_= � _i__Zi_isclosedundercommutationandthusconstitutesa3-dimensionalsubalgebraofthe 4_n_ -dimensional operator space. + +Specifically, we will be interested in the so-called _dynamical Lie algebra_ (DLA). This Lie algebra is the subspace of u( _d_ ) generated by the Lie closure of the generators of a paramterized quantum circuit (see Definition 3). In general, computing the DLA is a highly nontrivial task. One possible approach to the DLA is direct construction, i.e. start with a set of generators defining a subspace of operator space (but not a subalgebra), and start commuting them, finding new elements until one obtains a basis of the DLA (see Algorithm 1). The complexity of such approach is, in general, _O_ (poly(d)) with _d_ = 2_n_ , that is, exponential in _n_ the number of qubits. For example, a naive approach (representing operators as dense _d × d_ matrices) yields roughly _O_ ( _d_2 _d_6 ), since, in general, one has to check linear independence _O_ ( _d_2 ), for example by implementing LU or QR decompositions (whose cost is _O_ ( _N_3 ) for _N × N_ matrices) on such _d × d_ matrices. Although such complexity can be reduced, for example, using more intelligent representations of operators, in essence direct construction is attempting to build a basis for a subalgebra of su( _d_2 ), i.e. a basis with potentially as many as _d_2 elements, and therefore it cannot generally avoid exponentiality. + +Despite being exponential, direct construction of the DLA (either numerically or analitically) on small system sizes can constitute a remarkably useful tool to later extrapolate or prove the scaling (e.g. by induction) of the DLA beyond those small ’afforable’ system sizes. Moreover, note that in many cases one may only be interested in checking whether the dimension of the DLA is above a certain threshold, a task with complexity linear in the size of such threshold. Of course, this is neglecting the complexity of computing new DLA elements, which, as mentioned above, can be substantially diminished by choosing efficient representations for those operators. + +## F Proof of Theorem 1: Convergence of controllable systems to 2-designs + +In the following we provide a proof for Theorem 1, which we recall for convenience. + +**Theorem 1.** _Consider a controllable system. Then, the PSA U_ ( **_θ_** ) _will form an ε-approximate_ 2 _-design, i.e. ∥A_(2) _U_ ( **_θ_** )_∥∞_=_εwithϵ >_0_,whenthenumberoflayersLinthecircuitis_ + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0026-08.png) + + +_Here ∥A_(2) _U_ 1( **_θ_** )_∥∞denotestheexpressibilityofasinglelayerU_1(**_θ_**1)_oftheansatzaccordingtoEqs._(2)_and_(6)_._ + +_Proof._ To study the convergence of the PSA _U_ ( **_θ_** ) to an approximate 2-design we employ the tools of Harmonic analysis. The following arguments are based on Ref. [110]. This is similar to using Fourier analysis to study the convergence of a probability distribution on a real line to the normal distribution, which is also known as the central limit theorem. + +Accepted in Quantum 2022-08-31, click title to verify. Published under CC-BY 4.0. + +26 + +The second moment operator corresponding to the distribution U over unitaries _U_ can be defined as follows + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0027-01.png) + + +Let _MU_(2) _H_denotethesecondmomentoperatorcorrespondingtotheHaardistribution.Ourgoalistoevaluate the difference between _MU_(2) and _MU_(2) _H_.Letusreviewsomepropertiesof_M_(2) _UH_beforewecalculatethisdistance operator. An important property of _MU_(2) _H_isthatitisaprojectorontoatwo-dimensionalsubspace,thatis, _M_(2)eigenvalues0or1.Weshowthisbynotingthatthefollowingequationshold: _UH_has + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0027-03.png) + + +and + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0027-05.png) + + +In Eq. (50), we used the left invariance of the Haar measure, and in (52) we used the Weingarten function to explicitly evaluate the integral. The first property puts in evidence that _MU_(2) _H_isaprojectorandthesecond property shows that the eigenspace with eigenvalue 1 is a two-dimensional subspace. + +Let _V_ = _UL · · · U_ 2 _U_ 1, be an _L_ -layered PSA, where each unitary _Uj_ is sampled from the same distribution _dµ_ = _P_ ( _U_ ) _dU_ . Then, the probability distribution and moment operator of _V_ are respectively given by + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0027-08.png) + + +Equation (55) shows that the moment operator of an _L_ -layered ansatz is equal to the _L_ -th power of the moment _K_ operator of a single layer. We can also calculate this formally. In our case each _Ul_ is given by _Ul_ = � _e__−iθlkHk_ , _k_ =1 + +Accepted in Quantum 2022-08-31, click title to verify. Published under CC-BY 4.0. + +27 + +where _G_ = _{Hk}__K_ _k_ =1arethesetofgenerators,andwherethe_θlk_aresampledfromtheuniformdistribution. Then, let us note that + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0028-01.png) + + +Here _Hk|li,k⟩_ = _li,k|li,k⟩_ , and _Wk_ is the unitary matrix that diagonalizes _Hk_ . To calculate the distance to a 2-design we need to prove some properties of eigenvalues and eigenvectors of _MU_(2) 1.Let_|φ⟩_beaneigenvectorof _M_(2)associatedeigenvalue_λ_.Then, _U_ 1with + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0028-03.png) + + +The equality holds if and only if _|φ⟩_ is an eigenvector of _U ⊗ U_ 1 _⊗ U_ 1_∗⊗U_ 1_∗∀U_1,suchthat_P_(_U_1)=0.For the specific case of Haar measure, this means that _|φ⟩_ is an eigenvector of _U ⊗ U ⊗ U__∗_ _⊗ U__∗_ _∀U ∈SU_ ( _d_ ). We already showed that there are two such eigenvectors for Haar measure with eigenvalue 1. Now, using the following argument we hope to show that those two are also the only eigenvectors of _MU_(2) 1 with eigenvalue 1, given that the set _G_ is controllable. Let _|φ⟩_ be an eigenvector of _MU_(2) 1witheigenvalue1. One can now also see that _|φ⟩_ is also an eigenvector of _MV_(2) (since _MV_(2) = ( _MU_(2) 1)_L_)_∀L∈_Z+.Then,_G_being _L K_ a controllable set implies that for all _U ∈SU_ ( _d_ ), there exists an _L_ for which _V_ = � � _e__−iθlkHk_ = _U_ so that _l_ =1 _k_ =1 + +_P_ ( _U_ ) = 0. That, is one can obtain any unitary in _U_ ( _d_ ) by tuning the parameters in _V_ . It also implies that _|φ⟩_ has to be an eigenvector of _U ⊗ U ⊗ U__∗_ _⊗ U__∗_ with eigenvalue 1. But this means that _|φ⟩_ is an eigenvector of _U ⊗ U ⊗ U__∗_ _⊗ U__∗_ with eigenvalue 1 _∀U ∈SU_ ( _d_ ). So _|φ⟩_ is also an eigenvector of _M_ 2[ _µH_ ] with eigenvalue 1, and there are two such eigenvectors. Let us call them _|φ_ 1 _⟩_ , and _|φ_ 2 _⟩_ . Hence, we have + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0028-06.png) + + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0028-07.png) + + +Here _{λi}_ is the set of the remaining eigenvalues such that 0 ⩽ _|λi| <_ 1. Let _λ_ max be the eigenvalue with _N_4 _−_ 2 maximum modulus. Thus, we can now show that _∥A_(2) _U_ ( **_θ_** )_∥∞_=_∥M_(2) _UH__−M_(2) _V__∥∞_=_∥_ � _λ__k_ _i__|ψi⟩⟨ψi |∥∞_=_|λ_ max_L|_. _i_ =1 Then, recalling that _|λ_ max _|_ = _∥A_(2) _U_ 1( **_θ_** )_∥∞_isalsotheexpressibilityofonelayer,onecanfindthat + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0028-09.png) + + +Accepted in Quantum 2022-08-31, click title to verify. Published under CC-BY 4.0. + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0028-11.png) + + +Solving for _L_ and denoting _ε_ = _∥A_(2) _U_ ( **_θ_** )_∥∞_leadsto + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0029-01.png) + + +## G Proof of Corollary 1: Rate of convergence of controllable systems to 2-designs + +Here we prove Corollary 1. + +**Corollary 1.** _Let the single layer expressibility of a controllable system be ∥A_(2) _U_ 1( **_θ_** )_∥∞_=1_−δ_(_n_)_,withδ_(_n_) _being at most polynomially vanishing with n, i.e., with δ_ ( _n_ ) _∈_ Ω(1 _/_ poly( _n_ )) _. Then, if L_ ( _n_ ) _∈_ Ω( _n/δ_ ( _n_ )) _, U_ ( **_θ_** ) _will be no worse than an ε_ ( _n_ ) _-approximate_ 2 _-design (i.e., ∥A_(2) _U_ ( **_θ_** )_∥∞_⩽_ε_(_n_)_)withε_(_n_)_∈O_(1_/_2_n_)_,wherewe_ _have added the n-dependence in L and ε for clarity._ + +_Proof._ Let us recall from the main text, and from Eq. (63), that + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0029-06.png) + + +Replacing _∥A_(2) _U_ 1( **_θ_** )_∥∞_= 1_−δ_(_n_),onefinds + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0029-08.png) + + +Then, assuming that _L_ ( _n_ ) _∈_ Ω( _n/δ_ ( _n_ )) we have, by definition, that there exists _c_ ⩾ 0 and _n_ 0 such that _L_ ( _n_ ) ⩾ _cn/δ_ ( _n_ ) for all _n_ ⩾ _n_ 0. From the previous, we find that for _n_ ⩾ _n_ 0 + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0029-10.png) + + +where we have used the fact that (1 _− δ_ ( _n_ )) ⩽ 1. Recalling that _δ_ ( _n_ ) _∈_ Ω(1 _/_ poly( _n_ )), i.e., _δ_ ( _n_ ) vanishes no faster than a polynomial function of _n_ , we find to first order that + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0029-12.png) + + +_cn_ Then, defining _ε_ ( _n_ ) = (1 _− δ_ ( _n_ )) _δ_ ( _n_ ) we prove the result in Corollary 1 as + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0029-14.png) + + +## H Proof of Proposition 1: Controllability leads to barren plateaus + +Let us now provide a proof for Proposition 1. + +**Proposition 1** (Controllable) **.** _There exists a scaling of the depth for which controllable systems form ε- approximate_ 2 _-designs with ε ∈O_ (1 _/_ 2_n_ ) _, and hence the system exhibits a barren plateau according to Definition 1._ + +_Proof._ Let us start by noting that for all controllable systems one can form a _ε_ -approximate 2-designs with _ε ∈O_ (1 _/_ 2_n_ ) with a depth scaling obtained from Eq. (64). Then, let us recall that we have defined the expressibility superoperator as + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0029-19.png) + + +with its ordinary action given by + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0029-21.png) + + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0029-22.png) + + +Accepted in Quantum 2022-08-31, click title to verify. Published under CC-BY 4.0. + +We then have that for any quantum state _ρ_ + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0030-01.png) + + +which follows from the normalization of _ρ_ . Hence, if _∥A_( _U__t_) ( **_θ_** )_∥∞_⩽_ε_wefindfromEq.(72)thatforanyquantum state _ρ_ , the inequality _∥A_( _U__t_) ( **_θ_** )(_ρ_)_∥∞_⩽_ε_holds,whichispreciselythedefinitionofan_ε_-approximatestate2- design. Finally, we can use the results from [37], which imply that since _ε ∈O_ (1 _/_ 2_n_ ), then the variance of the cost function partial derivative is given by + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0030-03.png) + + +Thus, the cost function exhibit a barren plateau. + +## I Proof of Proposition 2: Controllability of the HEA and the Spin Glass model + +#### Let us now prove Proposition 2. + +**Proposition 2.** _The following two sets of generators generate full rank DLAs, and concomitantly lead to controllable systems:_ + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0030-08.png) + + +In the following we find that by repeated nested commutators between the elements of the sets _G_ in Proposition 2. one can obtain all 22_n_ _−_ 1 Pauli strings, and hence, that the DLA g obtained is full rank. + +_Proof._ We divide the proof into HEA and GS models. + +#### I.0.1 Generators of the Hardware Efficient Ansatz (HEA) + +We first start with the set _G_ HEA corresponding to the set of generators of a HEA. + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0030-13.png) + + +First, let us note that from the commutation of _Xi_ with _Yi_ , we get every _Zi_ . Meaning that one can already obtain all single qubit Pauli operators. Then, the commutation of _Xi_ and _Yi_ with_n_ _i_ =1_−_1_ZiZi_+1,respectively, gives + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0030-15.png) + + +It then follows that the commutator of _Xi_ +1 with _Bi_ is + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0030-17.png) + + +Then by computing the commutators of _{Xi}__n_ _i_ =1,_{Yi}n_ _i_ =1,and_{Zi}n_ _i_ =1with_{Ci}_ _i__n_ =1_−_1wegetallnearest- neighbour two-body Pauli operators + +_Di_ = _{XiXi_ +1 _, YiYi_ +1 _, ZiZi_ +1 _, XiYi_ +1 _, YiXi_ +1 _, XiZi_ +1 _, ZiXi_ +1 _, YiZi_ +1 _, ZiYi_ +1 _}, ∀i ∈{_ 1 _, . . . , n −_ 1 _} ._ (77) + +Similarly, it can be readily verified that the commutators between _{Xi}__n_ _i_ =1,_{Yi}n_ _i_ =1,_{Zi}n_ _i_ =1,andoperatorsin _{Di}__n_ _i_ =1_−_1yieldall“nearest-neighbour”three-bodyPaulioperators. Now, let us show that g also contains the remaining non-nearest-neighbour two body operators. Consider the commutator between the three-body nearest-neighbour operators + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0030-21.png) + + +where _N, M, O, P, Q ∈{X, Y, Z}_ . Clearly, the different choices of _M, O, P_ and _Q_ will generate all next-nearestneighbour two-body operators. Iterating this procedure we obtain all 9� _n_ 2� two-body terms. Then, once we have all two-bodies we can use one-bodies _{Xi}__n_ _i_ =1,_{Yi}n_ _i_ =1and_{Zi}n_ _i_ =1togetallthree-bodies.Three-bodieswith one-bodies will give four-bodies, and so on. We will get all n-body operators. Thus the DLA of the HEA is full rank, which implies that the HEA is a controllable ansatz. + +Accepted in Quantum 2022-08-31, click title to verify. Published under CC-BY 4.0. + +30 + +#### I.0.2 Generators of the Spin Glass (SG) + +The set of generators for a spin glass system is given by + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0031-02.png) + + +with _hi, Jij ∈_ R. For convenience, we define the following two operators + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0031-04.png) + + +The commutator of _Hp_ and _Hm_ gives + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0031-06.png) + + +We then compute the commutator of _A_ 0 and _Hm_ : + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0031-08.png) + + +Combining _Hp_ and [ _Hm, A_ 0] gives + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0031-10.png) + + +We now compute the commutator between _A_ 1 and _Hm_ as follows: + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0031-12.png) + + +Combining _A_ 0 and _A_ 3, we get + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0031-14.png) + + +Similarly, combining _Hm_ and _A_ 4 gives + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0031-16.png) + + +Finally, combining _A_ 5 and _Hp_ gives + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0031-18.png) + + +From the commutator of _A_ 4 and _A_ 5 we get + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0031-20.png) + + +Moreover, the commutators of _A_ 7 with _A_ 4 and _A_ 5 lead to + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0031-22.png) + + +By repeating this procedure, we get that the set + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0031-24.png) + + +also belongs to the Lie algebra. Now, because the _h__′_ _i_saresampledfromaGaussiandistribution,wecansafely assume them to be non-zero and different from each other. Then, using a Vandermonde determinant type of argument one can show that the 3 _n_ elements in _S_¯ are linearly independent and span the same subspace as _S_ = _{Xi, Yi, Zi}__n_ _i_ =1.Thus_S_belongs to gSG.Combining this with_A_6,we essentially get the generators of_G_HEA and hence one can again generate all _n_ -body Pauli operators. Thus the DLA of the spin-glass system is also of full rank, which implies that the spin-glass system is controllable. + +Accepted in Quantum 2022-08-31, click title to verify. Published under CC-BY 4.0. + +31 + +## J Proof of Theorem 2: Variance in subspace controllable systems + +In the following, we provide a proof for Theorem 2 by explicitly computing the variance of the cost function partial derivative in a subspace controllable setting. Consider a set of generators that share a symmetry (for simplicity we assume only one symmetry, although generalization to multiple symmetries is straightforward), i.e., there is a Hermitian operator Σ such that [Σ _, g_ ] = 0 _∀g ∈_ g. Assuming Σ has _N_ distinct eigenvalues, the DLA has the form g =_N_ _m_ =1g_m_.Thisimposesapartitionof_H_=�_N_ _m_ =1_Hm_whereeachsubspace_Hm_of dimension _dm_ is invariant under g. + +Let us introduce some notation. Consider the _d × dm_ matrix that results from horizontally stacking the eigenvectors of Σ associated with the _m_ -th eigenvalue (of degeneracy _gm_ ) + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0032-03.png) + + +such that _Qm_ maps vectors from _H_ to _Hm_ . These satisfy + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0032-05.png) + + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0032-06.png) + + +to denote the _dm_ -dimensional reduced states and operators, respectively. Recall that, since any unitary _U ∈_ G produced by such a system is block diagonal, we can write _U_ = _m_P_mU_P_m_.Also,letusnotethatif_A_=_A†_ then ( _A_(_k_) )_†_ = _A_(_k_) . + +We are ready to prove of Theorem 2, which we here recall for convenience. + +**Theorem 2** (Subspace controllable) **.** _Consider a system that is reducible (so that the Hilbert space is H_ = _j__Hj_ _with each Hj invariant under_ G _), and controllable on some Hk of dimension dk. Then, if the initial state is such that ρ ∈Hk, the variance of the cost function partial derivative is given by_ + +_Here_ + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0032-11.png) + + +_is the Hilbert-Schmidt distance, and A_(_k_) _the reduction of operator A onto the subspace of Hk as defined in Eq_ (93) _._ + +_Proof._ Consider the partial derivative of the cost function _C_ ( **_θ_** ) with respect to the parameter _θpq_ (= _θµ_ ), i.e. the one associated with layer _p_ and generator _Hq_ (= _Hµ_ ). We have + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0032-14.png) + + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0032-15.png) + + +where in the second line we have expanded _U_ ( **_θ_** ) = _UAUB_ , with + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0032-17.png) + + +corresponding to the unitaries before and after the parameter _θpq_ , and _OA_ = _UA__†OUA_.Then,thevarianceof the partial derivative is + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0032-19.png) + + +Accepted in Quantum 2022-08-31, click title to verify. Published under CC-BY 4.0. + +where U _A_ and U _B_ denote the distribution of before and after unitaries, respectively, and _F_ ( _UB, UA_ ) = Tr[ _UBρUB__†_[_Hq, U_ _A__†OUA_]]. Now, expanding the block-diagonal unitaries in this equation and assuming that the initial state belongs to a particular invariant subspace, i.e., _ρ_(_k_) = P _kρ_ P _k_ = _ρ_ , we find + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0033-01.png) + + +where we defined _X_ = [ _Hq_(_k_) _,_ ( _UA_(_k_))_†O_(_k_)_U_ _A_(_k_)],andthus + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0033-03.png) + + +Note that here we have used the fact that, owing to Eq. (100), _F_ ( _UB, UA_ ) actually depends only on the action of _UB_(_k_),i.e.,ontheactionof_UB_projectedontothe_k_-invariantsubspace,andnotontheactionoftheentire unitary _UB_ ,. Hence, the integration over U _B_ can be replaced by an integration over U( _B__k_). + +At this point, we introduce the assumption of subspace controllability on _Hk_ . By virtue of Theorem 1 and Corollary 1, we know that the distribution of unitaries produced by a subspace controllable PSA constitute a _ε_ -approximate 2-design in the subspace. Therefore, we can use Eq. (39) to integrate and get + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0033-06.png) + + +where ∆( _A_ ) is the Hilbert-Schmidt distance defined in Theorem 2. Here, we used that Tr[ _X_ ] = 0. This is grounded in the fact that, because the generator _V_ shares the symmetry, the commutator _X_ in the subspace is still a commutator, i.e., _X_ = [ _Hq_(_k_) _, O_˜(_k_) ] with _O_˜ = _UA__†OUA_.If the initial state was spread across two (ormore) subspaces, neither _ρ_(_k_) would be a density matrix, nor _X_ would be a commutator and one would have to be more careful in the derivation. + +Finally, we proceed to integrate Eq. (102) over U _A_ . Notice again that _O_˜(_k_) = P _kO_˜ P _k_ = _UA__†_(_k_) _O_(_k_) _UA_(_k_) so _X_ is actually only a function of _UA_(_k_) (and not of the entire _UA_ ). Consequently we can integrate over the reduced distribution U( _A__k_),that,accordingtothesubspacecontrollabilityassumption,formsa_ε_-approximate2-design over _U_ ( _dk_ ). This leads to + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0033-09.png) + + +Here, we used the notation Var **_θ_** _−→_ Var _A,B_ to make explicit the assumption that both U( _A__k_) and U( _B__k_) form 2-designs in _U_ ( _dk_ ). Note that we first expanded Tr[ _XX_ ] in Eq. (104), and then used identities (39) and (40) on each integrand respectively to arrive at Eq. (105). Note that, even though Tr[ _Hq_ ] = 0 (and, in most applications, Tr[ _O_ ] = 0), this is not necessarily true for their reduced analogs _Hq_(_k_) and _O_(_k_) . + +## K Proof of Corollary 2: Exponentially growing subspaces have barren plateaus + +Let us prove Corollary 2, which we here recall. + +Accepted in Quantum 2022-08-31, click title to verify. Published under CC-BY 4.0. + +33 + +**Corollary 2.** _Consider a PSA of the form in_ (2) _giving rise to a reducible DLA, and let ρ ∈Hk, with Hk some invariant subspace that is controllable (i.e. the DLA reduced to such subspace is full rank). If,_ Tr[( _Hµ_ )4 ] _,_ Tr[ _O_4 ] _∈O_ (2_n_ ) _, the cost function will exhibit a barren plateau for any subspace such that dk ∈O_ (2_n_ ) _._ + +_Proof._ We consider the situation in which we have a reducible system and an initial state _ρ_ that belongs to an invariant subspace _Hk_ which, by assumption, is controllable. Notice that, using Eqs. (J) and (93), we find + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0034-02.png) + + +Then, since the operator _M_ = _A_ P _kA_ is a positive semi-definite operator, and since P _k_ ⩽ 1 , one can write + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0034-04.png) + + +Using this expression, the following bound on the Hilbert-Schmidt distance holds + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0034-06.png) + + +Using Eq. (112) and the equation for the variance in (106) we find + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0034-08.png) + + +where we have additionally used the fact that ∆( _ρ_(_k_) ) ⩽ 2 _∀ρ_ . Recalling that we are interested in the case when _dk ∈O_ (2_n_ ) we find that, assuming Tr[ _V_4 ] _,_ Tr[ _O_4 ] _∈O_ (2_n_ ), then the function + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0034-10.png) + + +is such that + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0034-12.png) + + +Hence, we finally find that Var **_θ_** [ _∂µC_ ( **_θ_** )] ⩽ _T_ ( _n_ ), and the cost exhibits a barren plateau from the fact that the variance of the cost function partial derivative is upper bounded by a function that vanishes exponentially with _n_ . + +Let us finally denote that the proof of Corollary 2 follow from the fact that Tr[( _Hµ_ )4 ] _,_ Tr[ _O_4 ] _∈O_ (2_n_ ). We here note that the following relevant cases satisfy this assumption: + +- _Hµ_ , _O_ are projectors of arbitrary rank. + +- _Hµ_ , _O_ have a decomposition in the Pauli string basis of the form _i__ci_**_σ_**_i_(with_ci_realcoefficients,and **_σ_** _i ∈{_ 1 _, X, Y, Z}__⊗n_ ) with up to _O_ (poly( _n_ )) terms such that _i__c_ _i_4_∈O_(poly(_n_)). + +## L Proof of Theorem 3: Expressibility in the subspace + +Here we prove Theorem 3, which we now recall. + +**Theorem 3.** _Consider a system that is reducible and let ρ ∈Hk with Hk an invariant subspace of dimension dk. Then, the variance of the cost function partial derivative is upper bounded by_ + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0034-20.png) + + +_with_ + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0034-22.png) + + +Accepted in Quantum 2022-08-31, click title to verify. Published under CC-BY 4.0. + +34 + +_Here we define X_ = [ _Hµ_(_k_)_,_(_U_ _A_(_k_))_†O U_( _A__k_)]_and Y_= [_H_ _µ_(_k_)_, U_ _B_(_k_)_ρ_(_k_)(_U_ _B_(_k_))_†_]_.For simplicity,we here employed the_ _short-hand notation ⟨·⟩U_ ( _xk_ ) _(with x_ = _A, B) to indicate the expectation value over the distribution of unitaries obtained from Ux_(_k_) _in the k-th subsystem. Finally, ∥M ∥_ 2 = ~~�~~ Tr[ _M__†_ _M_ ] _is the Frobenius norm, and_ ∆( _·_ ) _was defined in Theorem 2._ + +_Proof._ Let us first note that the variance of the cost function partial derivative can be expressed as + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0035-02.png) + + +where we + +_X_ = [ _Hµ_(_k_)_,_(_U_ _A_(_k_))_†O_(_k_)_U_ _A_(_k_)]_,_ and _Y_ = [ _Hµ_(_k_)_,_(_U_ _B_(_k_))_ρ_(_k_)(_U_ _B_(_k_))_†_]_._ (121) + +Then, we recall that _Ax_ ( _·_ ) denotes the expressibility superoperator for the second moment in the _k_ -th subspace + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0035-06.png) + + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0035-07.png) + + +Which leads to + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0035-09.png) + + +Using the triangle inequality and then the Cauchy-Schwarz inequality one finds + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0035-11.png) + + +Then, using the fact that + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0035-13.png) + + +one + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0035-15.png) + + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0035-16.png) + + +Similarly, one can derive Eq. (118) by replacing _AA_ (( _O_(_k_) )_⊗_2 ) into (119), which leads to + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0035-18.png) + + +Following a similar derivation to the one used in obtaining (125), one finds + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0035-20.png) + + +Accepted in Quantum 2022-08-31, click title to verify. Published under CC-BY 4.0. 35 + +## M Proof of Proposition 3: Variance on the irreducible representations of _SU_ (2) + +In this section we derive a proof for Proposition 3 for the variance of the irreducible representations of _SU_ (2). Proposition 3 reads. + +**Proposition 3.** _Consider the cost function of Eq._ (20) _. Let θµ_ = _θj,x, and let us assume that the circuit is deep enough to allow for the distribution of unitaries UA and UB to converge to_ 2 _-designs on_ G = _SU(2). Then variance of the cost function partial derivative ∂µC_ ( **_θ_** ) = _∂C_ ( **_θ_** ) _/∂θµ is_ + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0036-03.png) + + +_Proof._ First, let us bring the reader into context. We consider a toy model ansatz _U_ ( **_θ_** ) =_L_ _l_ =1_e−iθlxSye−iθlySx_ with generators _G_ = _{Sx, Sy}_ , where _{iSx, iSy, iSz}_ , with _Sν ∈_ C_d×d_ ( _ν_ = _x, y, z_ ), form a basis of the spin _S_ = ( _d −_ 1) _/_ 2 irreducible representation of su(2). Here, it is convenient to use as basis of the Hilbert space the set _{|m⟩}_ , _m_ = _−S, −S_ + 1 _, . . . , S −_ 1 _, S_ , of eigenvectors of _Sz_ . That is, we have + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0036-05.png) + + +with _λ±_ = � _S_ ( _S_ + 1) _− m_ ( _m ±_ 1), and where _S_ + and _S−_ are the spin ladder operators such that _Sx_ = 1and_S−_=1 2(_S_+ +_S−_) 2 _i_(_S_+_−S−_). The dynamical group G is the _d_ -dimensional representation of _SU_ (2) and we will be interested in the partial derivative w.r.t _θµ_ = _θj,x_ , i.e., the parameter associated with generator _V_ = _Sx_ on the _j_ -th layer of the circuit. We will assume that the circuit is deep enough to allow for the distribution of unitaries _UA_ and _UB_ to converge to 2-designs on G = _SU_ (2). In consequence, when computing the variance of the different gradient components, we will be allowed to replace the integration over the angles in the PSA with an integration over the Haar measure on the group _SU_ (2). Moreover, because irreducible representations of _SU_ (2) and _SO_ (3) are isomorphic, we can choose to integrate over the latter + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0036-07.png) + + +That is, we choose a parameterization of _SO_ (3), e.g. in terms of Euler angles + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0036-09.png) + + +and compute the mean value of a function of the form _FM_ ( **_θ_** ) = Tr[ _U_ ( **_θ_** ) _ρU_ ( **_θ_** )_†_ _M_ ], with _M_ Hermitian, over the cost landscape as + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0036-11.png) + + +where 8 _π_ 12_dα dβ_sin_β dγ_isthenormalizedHaarmeasureforthisparametrizationof_SO_(3).Consideringan initial state that is an eigenstate of _Jz_ , i.e., _ρ_ = _|m⟩⟨m|_ , its evolution can be conveniently expressed in terms of Wigner’s small _d_ -matrices + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0036-13.png) + + +and using that _d__j_ _rk_(_β_) =_dj_ _kr_(_−β_)andtheorthonormalityrelations + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0036-15.png) + + +we arrive at + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0036-17.png) + + +Accepted in Quantum 2022-08-31, click title to verify. Published under CC-BY 4.0. 36 + +#### M.0.1 An unbiased gradient + +Now, the computation of the mean value of _∂µC_ ( **_θ_** ) amounts to making the choice _M_ = _X_ , with _X_ = [ _Hµ, UA__†OUA_].Wereadilyfindthat + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0037-02.png) + + +since + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0037-04.png) + + +which follows from the fact that commutators are traceless. + +#### M.0.2 The variance + +Let us now compute the variance of _∂µC_ ( **_θ_** ). Having shown that _⟨∂µC⟩_ = 0, the variance is + +where + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0037-09.png) + + +and + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0037-11.png) + + +Here and henceforth, _dm,m′ ≡ d__S_ _m,m__′_.Now,again,integrationover_γ_istrivial,andintegratingover_α_weget _δn′′,m′_ + _n′−m′′_ , so that + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0037-13.png) + + +In the following we will consider that the cost function minimize the expectation value of an operator _O_ that is an element of the dynamical algebra, i.e., _O ∈_ g. It is easy to verify that any unitary _U_ ( _α, β, γ_ ) of the form of Eq. (136), associated with some 3-dimensional Euler rotation matrix _R_ ( _α, β, γ_ ), transforms an arbitrary ˆ element _Sn_ ˆ = ( _Sx, Sy, Sz_ ) _·_ ˆ _n_ (with _n_ a unit vector _∈_ R3 ) of the algebra in the following manner + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0037-15.png) + + +Using the shorthand sin( _x_ ) _−→ sx_ and cos( _x_ ) _−→ cx_ , the rotation matrix reads + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0037-17.png) + + +Hence, considering _O_ = _Sx_ + _Sy_ + _Sz_ and, calling _X_(_ι_) = [ _Jx, USνU__†_ ], we get + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0037-19.png) + + +Thus, denoting _X_ = _ν_ = _{x,y,z}__X_(_ν_)= (_ax, ay, az_)_·_(_Sx, Sy, Sz_)_T_wefind + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0037-21.png) + + +Accepted in Quantum 2022-08-31, click title to verify. Published under CC-BY 4.0. 37 + +For the moment, let us neglect the explicit dependence of _ai_ on the Euler angles and let us compute the matrix elements of _X_ + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0038-01.png) + + +where _a±_ =_ax∓_ 2_iay_ , and where we here define _S±|m⟩_ = _λ±_ ( _m_ ) _|m ±_ 1 _⟩_ . Similarly, + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0038-03.png) + + +Now, out of the nine terms in the product _⟨m__′′_ _|X|m__′_ _⟩⟨m__′_ + _n__′_ _− m__′′_ _|X|n__′_ _⟩_ appearing in Eq. (146), only three terms are non-zero. That is, one only gets + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0038-05.png) + + +which give rise to three terms on _Y_ , namely _Y_ 1, _Y_ 2 and _Y_ 3. Consider the first of these + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0038-07.png) + + +where, we recall that the coefficients _ai_ = _ai_ ( _UA_ ) depend on _UA_ . Now, we can use the identity + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0038-09.png) + + +where we henceforth employ the more general notation _|m⟩→|S, m⟩_ . In our context, _S_ = _S__′_ , and we will reference the Clebsh-Gordan coefficients as _gr,k_ = _⟨SrSk |J, r_ + _k⟩_ . We get + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0038-11.png) + + +_× ⟨Sm__′_ _Sn__′_ _|J, m__′_ + _n__′_ _⟩⟨S, m__′_ + 1 _, S, n__′_ _−_ 1 _|J, m__′_ + _n__′_ _⟩⟨SmSn |J, m_ + _n⟩⟨SmSn |J__′_ _, m_ + _n⟩,_ + +and using the orthogonality relations yields + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0038-14.png) + + +Similarly + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0038-16.png) + + +These can be restated as _Y_ 1 = _Y_ 2 = _c_ 1 _a_ + _a−_ and _Y_ 3 = _c_ 3 _a_2 _z_with + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0038-18.png) + + +Accepted in Quantum 2022-08-31, click title to verify. Published under CC-BY 4.0. + +38 + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0039-00.png) + + +Figure 1: **Numerical results for the** _SU_ **(2) toy model algebra.** Variance of the cost function partial derivative of the cost function in (164) as a function of _d_ = dim( _H_ ). Dashed lines indicate the theoretical prediction of (162). + +We can proceed to integrate over _UA_ , assuming again we can replace U _A_ with an integration over the Haar measure on G. Let us call _vi_ each contribution to the variance, i.e. Var **_θ_** [ _∂µC_ ( **_θ_** )] =�3 _i_ =1_vi_.First,onefinds by explicit integration that let us consider + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0039-03.png) + + +Similarly, it is easy to see that + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0039-05.png) + + +Altogether, we finally find + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0039-07.png) + + +where we have introduced Var _A,B_ to make explicit the assumption made, that is, that the before and after distributions of unitaries form 2-designs. + +M.0.3 Numerical simulations of the _SU_ (2) toy model algebra. Here we numerically simulate the model employing a PSA of the form + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0039-10.png) + + +for minimizing the normalized cost function + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0039-12.png) + + +We compute Var **_θ_** [ _∂µC_ ( **_θ_** )] for the initial states _|m⟩_ = _|S⟩_ and _|m⟩_ = _|_ 1 _/_ 2 _⟩_ for _L_ = 100 using 1200 random initialization. As shown in Figure 1, the theoretical prediction of Eq. (162) matches the numerical results, indicating that an initial state _|m⟩_ = _|_ 1 _/_ 2 _⟩_ is trainable, while an initial state _|m⟩_ = _|S⟩_ leads to a barren plateau in the cost function. + +## N Symmetries in the _XXZ_ and TFIM systems + +### N.1 The _XXZ_ model + +Consider the generators for an HVA ansatz to the _XXZ_ model in Eq. (16) of section 5.2.1. The elements in _GXXZU_ , and thus in its associated DLA, share two common symmetries: they commute both with the + +Accepted in Quantum 2022-08-31, click title to verify. Published under CC-BY 4.0. + +39 + +magnetization operator _M_ =_n_ _i_ =1_Zi_andwiththeparityoperator + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0040-01.png) + + +Here, Π is the unitary _d_ -dimensional irreducible representation of the element of the symmetric group _Sn_ that corresponds to a reflection over the central constituent. As a result of these two symmetries, the state space is broken into invariant subspaces with well defined parity and excitation number + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0040-03.png) + + +_n_ The dimension of each excitation subspace is dim( _Hm_ ) = � _m_ �, whereas, the joint parity-excitation subspaces have dimension dim( _Hm,σ_ ) _≈_1 2dim(_Hm_).TheDLAis,accordingly,adirectsumofsimplealgebrasoneach invariant subspace + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0040-05.png) + + +Notably, upon the addition of a generator consisting of local fields on either end of the chain, _GXXZ_ = _GXXZU ∪ {_ Z1 + Z _N }_ , the DLA can be shown to become full rank on each subspace gXXZ =_n_ _m_ =0[87]. _σ_ = _±_u(_dm,σ_) + +### N.2 The TFIM and LTFIM models + +In this section we review the symmetries of the different variants of the Transverse Field Ising Model (TFIM) presented in Section 5.2.2. + +#### N.2.1 Open boundary condition + +Let us first consider open boundary conditions on the TFIM model. In this case, the generators _G_ TFIM = _ni_ =1 _−_ 1_ZiZi_+1_,_�_n_ _i_ =1_Xi_ have two symmetries. On one hand, they commute with the parity symmetry Π �� � defined in Eq. (165). On the other hand, they commute with the so-called Z2 operator + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0040-11.png) + + +that amounts to a global flip of the qubits. Consequently, _H_ is broken into four subspaces _Hσ,σ′_ with _σ, σ__′_ _∈ {_ 1 _, −_ 1 _}_ . Because the initial state _|_ + _⟩__⊗n_ for the cost function in Eq. (32) is an eigenstate of both symmetry operators with eigenvalues _σ_ = _σ__′_ = +1, the dynamics under such a PQC is constrained to the _H_ +1 _,_ +1 subspace. We find, using Algorithm 1, that the dimension of the DLA scales polynomially, dim(g) = _n_2 (and so does the restriction to the _σ_ = _σ__′_ = +1 subspace. + +In turn, consider open boundary conditions on the _Longitudinal_ Transverse Field Ising Model (LTFIM), given _n_ by generators _G_ LTFIM = _G_ TFIM � _{_ � _i_ =1_Zi}_.Whileparitysymmetryisconserved,theintroductionofthisnew global longitudinal field breaks the Z2 symmetry. Thus, we are left with only two subspaces, _H_ = _σ_ = _±_ 1_Hσ_, of dimensions dim( _Hσ_ ) _≈_2 2_n_.Asexpected,theDLAbreaksintotwocorrespondingsubspaceDLAs,eachof which we find to be full rank on the corresponding subspaces, i.e., both subspaces are controllable. + +#### N.2.2 Closed boundary conditions + +Let us now consider closed boundary conditions. This case is slightly more involved since the parity symmetry is replaced by _Cn_ , the cyclic group of _n_ elements. Hereafter, we follow Ref. [111]. Consider the operator _R_ whose action is to cycle the qubits in a state, i.e., _R|a_ 1 _, . . . , an⟩_ = _|an, a_ 1 _, . . . , an−_ 1 _⟩_ . Clearly, _R__n_ = 1 . Now, as a consequence of the invariance of the generators under the action of this group of symmetries, the state space is broken into _n_ invariant subspaces _H_ =_n_ _k_ =0_−_1_Hk_,wheretheprojectorontoeachsubspaceisgivenby + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0040-16.png) + + +Accepted in Quantum 2022-08-31, click title to verify. Published under CC-BY 4.0. + +40 + +In particular, the state _|_ + _⟩__⊗n_ _∈H_ 0 and for that reason we will only focus on this subspace. Let us note that a general formula for the dimension of this subspace is, as far as we know, not known, but it is possible to derive a closed expression in the case of _n_ prime [111] + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0041-01.png) + + +which makes evident that the subspace is exponentially large. Furthermore, we compute the DLA reduced to this subspace in the LTFIM case and see an exponential behaviour. In contrast, for the TFIM model we find a DLA that reduced to the _k_ = 0 subspace and the _σ__′_ = +1 (also has Z2 symmetry), and that has a linear scaling, i.e., dim(g0 _,_ +) _∈O_ ( _n_ ). + +## O Initial state for the numerical simulations of the _XXZ_ spin chain model + +The initial states _|ψm,_ + _⟩_ in the cost function of Eq. (25) are chosen to be eigenstates of the symmetries of the generators, namely _M_ =_n_ _i_ =1_Zi_andΠ(thereflectivespatialsymmetryw.r.t.thechain’scenter)definedin Eq. (165). That is, the initial state is + + +![](.figures/arxiv__2105.14377/2105.14377.pdf-0041-05.png) + + +Here _m_ denotes the number of excitations in the state, and + the fact that it is an even parity eigenstate. + +Accepted in Quantum 2022-08-31, click title to verify. Published under CC-BY 4.0. + +41 diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/references/rendered/2109.11676_theory-of-overparametrization-in-quantum-neural-networks.md b/tracks/qcs/solutions/frustration-free/challenge-113/references/rendered/2109.11676_theory-of-overparametrization-in-quantum-neural-networks.md new file mode 100644 index 000000000..a83390a56 --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/references/rendered/2109.11676_theory-of-overparametrization-in-quantum-neural-networks.md @@ -0,0 +1,1519 @@ +--- +source: "https://arxiv.org/abs/2109.11676" +type: "arxiv" +canonical_id: "2109.11676" +title: "Theory of overparametrization in quantum neural networks" +authors: "Martin Larocca, Nathan Ju, Diego Garcia-Martin, Patrick J. Coles, M. Cerezo" +year: "2023" +venue: "Nature Computational Science" +arxiv_id: "2109.11676" +doi: "10.1038/s43588-023-00467-6" +full_text: yes +--- + +# Theory of overparametrization in quantum neural networks + +**Authors:** Martin Larocca, Nathan Ju, Diego Garcia-Martin, Patrick J. Coles, M. Cerezo + +**Citation:** Nature Computational Science, vol. 3, pp. 542 - 551, 2023 + +**arXiv:** [2109.11676](https://arxiv.org/abs/2109.11676) + +**DOI:** [10.1038/s43588-023-00467-6](https://doi.org/10.1038/s43588-023-00467-6) + +## Abstract + +The prospect of achieving quantum advantage with quantum neural networks (QNNs) is exciting. Understanding how QNN properties (for example, the number of parameters M) affect the loss landscape is crucial to designing scalable QNN architectures. Here we rigorously analyze the overparametrization phenomenon in QNNs, defining overparametrization as the regime where the QNN has more than a critical number of parameters Mc allowing it to explore all relevant directions in state space. Our main results show that the dimension of the Lie algebra obtained from the generators of the QNN is an upper bound for Mc, and for the maximal rank that the quantum Fisher information and Hessian matrices can reach. Underparametrized QNNs have spurious local minima in the loss landscape that start disappearing when M ≥ Mc. Thus, the overparametrization onset corresponds to a computational phase transition where the QNN trainability is greatly improved. We then connect the notion of overparametrization to the QNN capacity, so that when a QNN is overparametrized, its capacity achieves its maximum possible value. A theoretical framework for quantum neural network (QNN) overparametrization, a phase transition in loss landscape complexity, is established. The precise characterization of the critical number of parameters offered is expected to impact QNN design. + +## Full Text + +# **Theory of overparametrization in quantum neural networks** + +Martín Larocca,1, 2,_∗_ Nathan Ju,1,_∗_ Diego García-Martín,1, 3, 4 Patrick J. Coles,1 and M. Cerezo5, 1, 6 + +> 1 _Theoretical Division, Los Alamos National Laboratory, Los Alamos, New Mexico 87545, USA_ + +> 2 _Departamento de Física “J. J. Giambiagi” and IFIBA, FCEyN, Universidad de Buenos Aires, 1428 Buenos Aires, Argentina_ + +> 3 _Barcelona Supercomputing Center, Barcelona 08034, Spain_ + +> 4 _Instituto de Física Teórica, UAM-CSIC, Madrid 28049, Spain_ + +> 5 _Information Sciences, Los Alamos National Laboratory, Los Alamos, NM 87545, USA_ + +> 6 _Center for Nonlinear Studies, Los Alamos National Laboratory, Los Alamos, New Mexico 87545, USA_ + +The prospect of achieving quantum advantage with Quantum Neural Networks (QNNs) is exciting. Understanding how QNN properties (e.g., the number of parameters _M_ ) affect the loss landscape is crucial to the design of scalable QNN architectures. Here, we rigorously analyze the overparametrization phenomenon in QNNs with periodic structure. We define overparametrization as the regime where the QNN has more than a critical number of parameters _Mc_ that allows it to explore all relevant directions in state space. Our main results show that the dimension of the Lie algebra obtained from the generators of the QNN is an upper bound for _Mc_ , and for the maximal rank that the quantum Fisher information and Hessian matrices can reach. Underparametrized QNNs have spurious local minima in the loss landscape that start disappearing when _M_ ⩾ _Mc_ . Thus, the overparametrization onset corresponds to a computational phase transition where the QNN trainability is greatly improved by a more favorable landscape. We then connect the notion of overparametrization to the QNN capacity, so that when a QNN is overparametrized, its capacity achieves its maximum possible value. We run numerical simulations for eigensolver, compilation, and autoencoding applications to showcase the overparametrization computational phase transition. We note that our results also apply to variational quantum algorithms and quantum optimal control. + +### **I. INTRODUCTION** + +The development of Neural Networks (NNs) and Machine Learning (ML) is one of the greatest scientific revolutions of the twentieth century. Traditionally, computers were explicitly programmed to solve a task, so that a usercreated code would take an input and produce a desired output. In ML, however, one follows a fundamentally different approach. Here, a computer is trained to learn from data, with the goal that it can accurately solve the problem when presented with new and previously unseen cases [1]. Currently, ML is used in virtually all areas of science, with applications such as drug discovery [2], new materials exploration [3], and self-driving cars [4]. + +Despite their tremendous success, training NNs is a difficult task that has even been shown to be NP-hard [5–7]. Thus, finding ways to improve the NNs trainability and generalization capacity has always been a coveted goal. Towards this end, one of the most surprising phenomena in ML is that of overparametrization. Here, one trains a NN with a capacity larger than that which is necessary to represent the distribution of the training data [8]. Usually, this implies having a number of parameters in the NN + +that is much larger than the number of training points [9]. Naively, one could expect that a model with a large capacity would have training difficulties and also have overfitting (poor generalization). However, overparametrizing a NN can improve its performance and reduce its training and generalization errors [9–13], and even lead to provable convergence results [14, 15]. + +The advent of quantum computers [16, 17] has brought a tremendous interest in using these devices for data science. Here, researchers have embedded ML into the framework of quantum mechanics, with the new, generalized theory being called Quantum Machine Learning (QML) [18–20]. With QML, the end goal is not formal generalization but rather to exploit entanglement and superposition to achieve a quantum advantage [21–24], that is, to solve the problem more efficiently than any classical algorithm run on a classical supercomputer. + +Naturally, as a generalized theory, QML has the potential to exhibit many of the issues and phenomena exhibited by (classical) ML. For instance, like the classical case, it has been shown that training QML models is NPhard [25]. Since a QML model may consist of a data embedding followed by a parametrized quantum circuit that is often called a Quantum Neural Network (QNN), its training requires optimizing the QNN’s parameters [20, 26–29]. Recently, much effort has gone towards developing so-called Quantum Landscape Theory [30], which studies the prop- + +> _∗_ The first two authors contributed equally to this work. + +2 + +erties of QML loss function landscapes. Indeed, there are results analyzing the presence of sub-optimal local minima [31, 32], the existence of barren plateaus [33–44], and how quantum noise affects the loss landscape [45–49]. + +Similar to classical NNs, some examples of QNNs that exhibit overparametrization have been constructed [32, 50– 55]. Some of these works have heuristically shown that increasing the number of parameters in the QNN can improve its trainability and lead to faster convergence. However, there is still need for a detailed theoretical analysis of this overparametrization phenomenon. Understanding overparametrization is crucial for Quantum Landscape Theory and for engineering QNNs to enhance their trainability. + +In this work we provide a theoretical framework for the overparametrization of QNNs. Our main results indicate that, for a general type of periodic-structured QNNs, one can reach an overparametrized regime by increasing the number of parameters past some threshold critical value _Mc_ (see Fig. 1(a)). Moreover, we prove that _Mc_ is related to the dimension of the Dynamical Lie Algebra (DLA) [56, 57] associated with the generators of the QNN. + +We here define overparametrization as the QNN having enough parameters so that the quantum Fisher information matrix saturates its achievable rank. In this case, one can explore all relevant directions in the state space by varying the QNN parameters. We then relate this notion of overparametrization to different measures of the model’s capacity [24, 58], so that a model is overparametrized when its capacity is saturated. Then, as shown in Fig. 1(b), our results have direct implications in understanding why overparametrization can improve the model’s trainability, as the overparametrization onset corresponds to a computational phase transition [52]. We verify our theoretical results by performing numerical simulations. In all cases, we find the predicted computational phase transition, where the success probability of solving the optimization problem is greatly increased after a critical number of parameters. + +These results provide theoretical grounds for recent observations of the overparametrization phenomenon in QML [50, 52, 59]. Moreover, our theorems have direct consequences for the field of quantum optimal control [60–63]. + +### **II. RESULTS** + +### **A. Quantum Neural Networks** + +Quantum Neural Networks (QNNs) [18–20] employ parametrized quantum circuits to allow for task-oriented programming of quantum computers. Here, one encodes the problem of interest in a loss function _L_ ( **_θ_** ), whose min- + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0002-09.png) + + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0002-10.png) + + +Figure 1. **Overparametrization in quantum neural networks (QNNs).** a) Quantum circuit description of the QNN. By having a low (high) number of parameters one is not able (is able) to explore all relevant directions in the Hilbert space, and thus the QNN is underparametrized (overparametrized). b) The gray surface corresponds to the unconstrained loss function landscape. An underparametrized QNN explores a low dimensional cut of the loss function (1D cut over the red lines). Here, the optimizer can get trapped in spurious local minima (blue segment) that negatively impact the parameter optimization. By increasing the number of parameters past some threshold _Mc_ , one can explore a higher dimensional cut of the landscape (2D cut over the green region). As shown, some previous spurious local minima correspond to saddle points (blue segment), and the optimizer can escape the false trap. + +ima correspond to the task’s solution. Using data from a training dataset _S_ composed of quantum states _|ψµ⟩∈S_ , one optimizes the QNN parameters to solve the problem + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0002-13.png) + + +Measurements on a quantum computer assist in estimating the loss function (or its gradients), while a classical optimizer is used to update the parameters and solve Eq. (1). This hybrid scheme allows the QML model to access the exponentially large dimension of the Hilbert space, with the hope that if the whole process is hard to classically simulate, then a quantum advantage could be achieved [22, 64, 65]. + +We consider the case when the QNN is a parametrized + +3 + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0003-01.png) + + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0003-02.png) + + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0003-03.png) + + +Figure 2. **Relevant mathematical spaces for QNNs.** QNNs employ a set of _M_ trainable parameters **_θ_** _∈_ R_M_ , which live in parameter space. The QNN itself is represented by a _d_ -dimensional unitary _U_ ( **_θ_** ), which lives in unitary space. An exemplary form of _U_ ( **_θ_** ) is that of Eq. (2), where the set of unitaries depends on the dynamical Lie algebra g, which in turn is obtained from the set of generators _G_ in Definition 1. In most QML applications, the QNN acts on an input state _|ψµ⟩_ from a training set. Thus, the set of reachable unitaries of the ansatz translates into a set of reachable states in the Hilbert space _H_ . Finally, by performing measurements on a quantum computer one estimates the loss function or its gradient. This information is then used to navigate through the loss function landscape _L_ ( **_θ_** ). Understanding the connections between these mathematical spaces is fundamental for the theory of quantum landscapes. + +quantum circuit _U_ ( **_θ_** ) that acts on the quantum states in the training set as _U_ ( **_θ_** ) _|ψµ⟩_ . Here, _U_ ( **_θ_** ) has an _L_ -layered periodic structure of the form + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0003-06.png) + + +where the index _l_ indicates the layer, and the index _k_ spans the traceless Hermitian operators _Hk_ that generate the unitaries in the ansatz. Moreover, **_θ_** _l_ = ( _θl_ 1 _, . . . θlK_ ) are the parameters in a single layer, and **_θ_** = _{_ **_θ_** 1 _, . . . ,_ **_θ_** _L}_ denotes the set of _M_ = _K · L_ trainable parameters in the QNN. + +As discussed in [37], Eq. (2) contains as special cases the hardware-efficient ansatz [66], quantum alternating operator ansatz (QAOA) [67, 68], Adaptive QAOA [69], Hamiltonian Variational Ansatz (HVA) [70], and Quantum Optimal Control Ansatz [71], among others [54]. As we discuss in the Methods section, due to the close connection between training a parametrized quantum circuit and the control pulses used to evolved a quantum state in a quantum optimal control protocol, all the results derived hereon can be directly applied to the field of quantum optimal control. + +### **B. Quantum Landscape Theory** + +The usefulness of a QNN for a given task hinges on several factors. First and foremost, it is crucial that a solution (or a good approximation to it) actually exists within the ansatz. Then, even if that solution exists, one must be + +able to find the associated optimal parameters. The goals of Quantum Landscape Theory are to study properties of the QML loss landscape, how they emerge, and how they affect the optimization process. Here we recall the basic theoretical framework of Quantum Landscape Theory. + +First, we note that there are several aspects of the problem that play a key role in how the loss function landscape arises. Specifically, as shown in Fig. 2, **_θ_** is a vector in R_M_ , and each set of parameters corresponds to a unitary _U_ ( **_θ_** ) in the unitary group _U_ ( _d_ ) of degree _d_ . Then, one applies the unitary _U_ ( **_θ_** ) to an _n_ -qubit input state _|ψµ⟩_ (from the dataset _S_ ) in a Hilbert space _H_ of dimension _d_ = 2_n_ . Finally, the loss function value _L_ ( **_θ_** ) _∈_ R is determined by performing measurements over the states _U_ ( **_θ_** ) _|ψµ⟩_ . In this sense, the action of the QML model arises from the composition of the following three maps: + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0003-13.png) + + +Since the landscape is essentially the collection of values obtained at the end of the maps in Eq. (3), understanding each step of this process is crucial to understanding the properties of the landscape. + +Let us consider the first map in Eq. (3), i.e., the map between the space of parameters and the unitary group. It has been shown that the unitaries generated by the ansatz in Eq. (2) are characterized via the so-called Dynamical Lie Algebra (DLA) [56, 57]. Specifically, consider the following + +**Definition 1** (Set of generators _G_ ) **.** _Consider a parametrized quantum circuit of the form_ (2) _. The set_ + +4 + +_of generators G_ = _{Hk}__K_ _k_ =1_isdefinedastheset(ofsize_ _|G|_ = _K) of the Hermitian operators that generate the unitaries in a single layer of U_ ( **_θ_** ) _._ + +Then, the DLA is defined as follows. + +**Definition 2** (Dynamical Lie Algebra (DLA)) **.** _Consider a set of generators G according to Definition 1. The DLA_ g _is generated by repeated nested commutators of the operators in G. That is,_ + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0004-04.png) + + +_where ⟨S⟩Lie denotes the Lie closure, i.e., the set obtained by repeatedly taking the commutator of the elements in S._ + +Recall that the set of reachable unitaries _{U_ ( **_θ_** ) _}_ **_θ_** _⊆_ G _⊆ SU_ ( _d_ ) obtained from arbitrary choices of **_θ_** forms itself a Lie group, known as the dynamical Lie group G. Then, we note that G is fully obtained from the DLA as G = _e_g [37, 72]. We refer the reader to the Methods section for some intuitive understanding on the role of the DLA. + +Here, we should remark that the optimal choice of ansatz (or equivalently, the best choice of generators) for a given task is still an open question. While a natural choice would be to use a QNN that is as expressible as possible [73], it has been shown that such choice can lead to trainability issues such as barren plateaus [33, 37, 38]. + +We can now analyze the second map in Eq. (3), i.e., the map leading to quantum states in a Hilbert space. Given the fact that g determines the set of reachable unitaries, and recalling that the QNN acts on the states _|ψµ⟩_ in the training set _S_ as _U_ ( **_θ_** ) _|ψµ⟩_ , then the set of reachable states (i.e., the orbit) is, in turn, also directly determined by the DLA. We note that in many cases the set of generators can have symmetries, in which case the DLA is of the form g = � _ν_g_ν_.Here,_ν_isanindexovertheinvariantsubspaces. The states in the training set need not respect some, or any, of the symmetries of the QNN. In this work, we consider the case where the states in the training set respect some of the symmetries, and we denote as g _S_ the DLA associated with the symmetries preserved by the states in _S_ . The limiting case when the states in _S_ break all symmetries in the ansatz (or when the ansatz has no symmetries) corresponds to g _S_ = g. + +Here, one can study the set of reachable states through the action of _U_ ( **_θ_** ) on _|ψµ⟩_ as follows. Given a set of parameters **_θ_** and an infinitesimal perturbation **_δ_** (possibly obtained from some update rule), it is useful to quantify the distance _D_ between the quantum states _|ψµ_ ( **_θ_** ) _⟩_ = _U_ ( **_θ_** ) _|ψµ⟩_ and _|ψµ_ ( **_θ_** + **_δ_** ) _⟩_ = _U_ ( **_θ_** + **_δ_** ) _|ψµ⟩_ . The secondorder Taylor expansion of _D_ is given by the Fubini-Study metric [74, 75] as + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0004-10.png) + + +Here, _Fµ_ ( **_θ_** ) is the Quantum Fisher Information Matrix (QFIM) for the state _|ψµ⟩_ . The QFIM is an _M × M_ matrix whose elements are [76] + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0004-12.png) + + +where _|∂iψµ_ ( **_θ_** ) _⟩_ = _∂ |ψµ_ ( **_θ_** ) _⟩ /∂θi_ = _∂i |ψµ_ ( **_θ_** ) _⟩_ for _θi ∈_ **_θ_** . The QFIM plays a crucial role in imaginary time evolution algorithms [77], and in quantum-aware optimizers such as the quantum natural gradient descent [78–81]. Moreover, we recall that the rank of the QFIM quantifies the number of independent directions in state space that can be explored by making an infinitesimal change in **_θ_** . + +Finally, consider the third map in Eq. (3), i.e., the map leading to the loss function value. Similar to how the QFIM is related to the changes in state space arising by a change in the parameters, one can also quantify how much the loss function value changes by a small parameter update. In this case, one can study the curvature of the loss landscape via the Hessian matrix _∇_2 _L_ ( **_θ_** ), an _M × M_ matrix whose elements are as + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0004-15.png) + + +Evaluating the gradient and the Hessian at a given point allows one to construct a quadratic model of the loss function, with the Hessian eigenvectors associated with positive (negative) eigenvalues determining directions of positive (negative) curvature. Thus, the rank of _∇_2 _L_ ( **_θ_** ) is related to the number of directions that lead to (second order) changes in the loss, as a zero-valued eigenvalue indicates a zero-curvature flat direction. We finally note that the Hessian has been used to characterize the loss landscapes of variational quantum algorithms [42, 82–84]. + +### **C. Theoretical Results** + +Here we present our main results, where we rigorously analyze the overparametrization phenomenon in QNNs. Our results prove that: 1) there exists a critical number of parameters _Mc_ needed to overparametrize a QNN, and 2) that _Mc_ , and the onset of overparametrization, can be related to the dimension of the associated DLA. The proofs of our main results are sketched in the Methods section and formally derived in the Supplementary Information. For our main results in Theorem 1 and Theorem 2, we make no assumption on the loss function other than the QNN acting on the states _|ψµ⟩_ in the training set _S_ as _U_ ( **_θ_** ) _|ψµ⟩_ and that the loss is estimated via measurements on these evolved states. Then, for Theorem 3 we consider special cases of such loss functions. + +5 + +## First, consider the following definition. + +**Definition 3** (Overparametrization) **.** _A QNN is said to be overparametrized if the number of parameters M is such that the QFI matrices, for all the states in the training set, simultaneously saturate their achievable rank Rµ at least in one point of the loss landscape. That is, if increasing the number of parameters past some minimal (critical) value Mc does not further increase the rank of any QFIM:_ + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0005-03.png) + + +In the Methods section we give additional motivation for this definition, as well as present an equivalent definition that further highlights the geometrical nature of the overparametrization phenomenon. + +According to Definition 3, when the QNN is overparametrized, one can explore all relevant and independent directions in the state space by changing the parameters of the ansatz. Evidently, since the rank of the QFIM is at most equal to _M_ , then Definition 3 implies that _Mc_ must be such that _Mc_ ⩾ max _µ Rµ_ . We also remark that the overparametrization is here defined for the QFIM ranks to be equal to _Rµ_ on a single point in the landscape. In principle, the QFIM could achieve its maximum rank in a given point, and not in others. However, as we numerically verify (see Supplementary Information), at the overparametrization onset the QFIM saturates its rank almost everywhere in the landscape simultaneously. Here, increasing the number of parameters will not further increase the number of accessible directions in state space. However, it can still be beneficial to add more parameters as this will lead to global minima with higher degeneracy [46, 62, 63, 85]. + +In light of Definition 3, overparametrization has implications for the trainability of the QNN parameters. If the QNN is underparametrized, the loss landscape can exhibit spurious, or false, local minima [86–89]. However, by increasing the number of parameters and overparametrizing the QNN, one can explore more directions in state space, and hence the optimizer is able to escape these false minima. As such, crossing the overparametrization threshold can be considered as a computational phase transition [52] where a more favorable landscape ameliorates the optimization. + +Then, our first main result to understand how overparametrization can improve the trainability is as follows. + +**Theorem 1.** _For each state |ψµ⟩ in the training set S, the maximum rank Rµ of its associated QFIM (defined in Eq._ (6) _) is upper bounded as_ + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0005-09.png) + + +We remark that dim(g _S_ ) ⩽ dim(g), and hence dim(g) also upper bounds _Rµ_ . Theorem 1 shows that, at most, the QNN can explore dim(g _S_ ) relevant and independent directions in the state space. And thus, a sufficient condition for overparametrization is that + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0005-11.png) + + +Note here that the number of parameters for overparametrization depends on the data in _S_ and on the set of generators _G_ . The latter implies that: 1) Different ansatzes for the QNN can be overparametrized for different depths even when using the same dataset, 2) The same QNN ansatz can reach overparametrization for different depths when used for two datasets. + +Then, as shown in the numerical results below, in many cases the QNN is found to be overparametrized when + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0005-14.png) + + +Evidently, _M_ in Eq. (11) can be intractable for ansatzes where dim(g _S_ ) _∈O_ ( _b__n_ ) with _b >_ 1 (e.g. controllable systems [37]). More promising, however, are QNNs where dim(g _S_ ) _∈O_ (poly(n)), as here the QNN can be overparametrized for a number of parameters _M ∈O_ (poly( _n_ )). Below we show examples of ansatzes that can achieve overparametrization with polynomially deep circuits. + +Here we note that Definition 3 allows us to connect the notion of overparametrization to that of the QNN’s capacity. We recall that the capacity (or power) of a QNN quantifies the breadth of functions that it can capture [90]. While there is no unique definition of capacity, we here consider two definitions for the so-called effective quantum dimension, which measures the power of the QNN. First, following [58], we can define the average effective quantum dimension of a QNN: + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0005-17.png) + + +where _λ__i_aretheeigenvaluesoftheQFIMforthestate _µ_(**_θ_**) _|ψµ⟩_ , and where _I_ ( _x_ ) = 0 for _x_ = 0, and _I_ ( _x_ ) = 1 for _x_ = 1. Here the expectation value is taken over the probability distribution that samples input states from the dataset. + +The second definition follows from [24]. In the _n →∞_ limit, the effective quantum dimension of [24] converges to + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0005-20.png) + + +where _F_ ( **_θ_** ) is the classical Fisher Information matrix obtained as + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0005-22.png) + + +6 + +Here, _p_ ( _|ψ⟩ , y_ ; **_θ_** ), describes the joint relationship between an input _|ψ⟩_ and an output _y_ of the QNN. In addition, the expectation value is taken over the probability distribution that samples input states from the dataset. + +Then, the following theorem holds. + +**Theorem 2.** _The model capacity, as quantified by the effective dimensions of Eqs._ (12) _or_ (13) _, is upper bounded as_ + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0006-04.png) + + +_Moreover, when the QNN is overparametrized according to Definition 3, D_ 1( **_θ_** ) _achieves its maximum value on at least one point of the landscape._ + +Theorem 2 provides an operational meaning to the overparametrization definition in terms of the model’s capacity. Specifically, the onset of the overparametrization arises when the model’s capacity in Eq. (12) can get saturated. Moreover, we here see that increasing the number of parameters can never increase the model capacity beyond dim(g _S_ ). + +Note that Definition 3 relates the overparametrization phenomenon with the rank of the QFIM and the possibility of exploring all relevant directions in the state space. One can also relate the notion of overparametrization with the rank of the Hessian and the relevant directions in the loss function landscape. Consider the case when the loss function is of the form + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0006-08.png) + + +where _cµ_ are real coefficients associated with each state _|ψµ⟩_ in _S_ , and where _O_ is a Hermitian operator. Such loss functions arise for supervised quantum machine learning [43, 44, 91], autoencoding [92], principal component analysis [93–95], dynamical simulation [96–99], and, more generally, for variational quantum algorithms [20, 100]. Then, the following theorem holds. + +**Theorem 3.** _Let ∇_2 _L_ ( **_θ_** _∗_ ) _be the Hessian for a loss function of the form of Eq._ (16) _evaluated at the optimal set of parameters_ **_θ_** _∗. Then, its rank is upper bounded as_ + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0006-11.png) + + +_where r_ = min _{_ rank[ _µ__cµ |ψµ⟩⟨ψµ|_]_,_rank[_O_]_},anddis_ _the Hilbert space dimension._ + +Theorem 3 shows that the maximum number of relevant directions around the global minima of the optimization problem is always smaller than dim(g _S_ ). Here, we again + +numerically find that in the overparametrization regime adding more parameters only adds zero-valued eigenvalues to the Hessian. We finally remark that Theorem 3 imposes a maximal rank on the Hessian when evaluated at the solution, but in general the Hessian can have a rank larger than dim(g _S_ ) at other points in the landscape. + +Note that in principle one can define overparametrization as the rank of the Hessian being saturated at the solution. However, as discussed in the Methods, this definition could have potential issues. + +### **D. Numerical Results** + +Here we numerically illustrate the overparametrization phenomenon and the associated computational phase transition. We consider three different optimization tasks: the Variational Quantum Eigensolver (VQE), unitary compilation, and quantum autoencoding. We note that the overparametrization phenomenon has been empirically observed for the first two tasks respectively in [50, 59] and [52]. The simulations were performed with the opensource library `Qibo` [101, 102], and the details can be found in the Supplemental Information. + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0006-18.png) + + +First, we use the VQE algorithm [103–105] to minimize the loss function + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0006-20.png) + + +and find the ground state of the Hamiltonian of the transverse field Ising model _H_ TFIM. Here, _|ψ_ ( **_θ_** ) _⟩_ = _U_ ( **_θ_** ) _|_ + _⟩__⊗n_ and _H_ TFIM = _−__n_ _i_ =1_fσ_ _i__zσ_ _i__z_ +1_−h_�_n_ _i_ =1_σ_ _i__x_,where_σ_ _i__µ_de- notes the _µ_ -Pauli matrix (with _µ_ = _x, z_ ) acting on qubit _i_ , and _h_ is the strength of the transverse field. We set _h_ = 1 and consider both open ( _nf_ = _n −_ 1) and closed ( _nf_ = _n_ ) boundary conditions. In the latter, _σn__µ_ +1=_σ_ 1_µ_.Weem- ploy a Hamiltonian variational ansatz for the QNN [50, 70]. This ansatz has two parameters per layer and is precisely of the form in (2) (see Methods for a detailed description of the ansatz). + +As shown in [37], the dimension of the DLA associated with the ansatz is given by [106] + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0006-23.png) + + +where the superscripts indicate closed and open boundary conditions in the ansatz and in _H_ TFIM. Hence, from our theoretical results, we expect that both of these ansatzes + +7 + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0007-01.png) + + + +QFIM
success probability QFIM
+ + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0007-02.png) + + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0007-03.png) + + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0007-04.png) + + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0007-05.png) + + +Figure 4. **Overparametrization threshold for VQE implementation.** a) The success probability (i.e., fraction of instances that converged to the global optimum within an error of 10_−_7 ) is plotted versus number of parameters. Results are obtained from 50 randomly-initialized instances, for _n_ = 4 _−_ 10 qubits and for the Hamiltonian variational ansatz with open boundary conditions. The vertical black lines indicate the dimension of the DLA. b) The rank of the QFIM and Hessian is plotted versus number of qubits. The rank of the QFIM was evaluated at the optima and at random points in the landscape. The rank of the Hessian was evaluated at the optima. Dashed lines indicate the functional dependence of the computed ranks. Here, the ansatz had a number of parameters for which overparametrization had been fully achieved. + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0007-07.png) + + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0007-08.png) + + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0007-09.png) + + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0007-10.png) + + +the loss function decreases exponentially with each optimization step when the number of layers is large enough. + +To analyze the number of parameters for which the overparametrization occurs, Figure 4(a) shows the success probability, i.e., the fraction of randomly-initialized instances that converged within 10_−_7 of the true solution. Here, one can see the phase transition at the onset of overparametrization. Indeed, at _M ∼_ dim(g _S_ ), the success probability rapidly goes to one. This is due to the fact that the optimization hypersurface becomes more favorable by the removal of false local minima, and thus one can obtain higher-quality solutions with less iterations. Figure 4(a) also shows that further increasing the number of parameters past dim(g _S_ ) can in fact lead to the QNN having a higher probability of converging to the solution. There exists a point, however, for which the overparametrization saturates and there is no visible improvement in convergence speed or quality of the solution found. We found that the saturation number of parameters grows linearly for closed boundary conditions and quadratically for open boundary conditions, and thus these saturation numbers have the same scaling as their corresponding dim(g _S_ ). + +Figure 3. **Training curves for VQE implementation.** The loss function value minus the exact ground-state energy ( _Egs_ ) is plotted versus iteration. We used a Hamiltonian variational ansatz with open (left) and closed (right) boundary conditions to solve the VQE task in Eq. (18) for a) _n_ = 4, b) _n_ = 6, c) _n_ = 8, and d) _n_ = 10 qubits. Solid lines represent the average over 50 random initialized runs while the shaded regions correspond to the standard deviation. + +can be overparametrized with only a polynomial number of parameters. + +Figure 3 shows the results of minimizing the loss in Eq. (18), for problem sizes of _n_ = 4 _,_ 6 _,_ 8 _,_ 10 qubits and for ansatzes with different depths _L_ (i.e., 2 _L_ parameters), with both open and closed boundary conditions. In all cases, we averaged over 50 random parameter initializations. First, we note that one can always observe the onset of overparametrization through a computational phase transition whereby the convergence of the optimization dramatically increases when increasing the number of parameters past some threshold. That is, for a small number of layers, the algorithm is unable to accurately find the ground state, while for a large number of layers the algorithm always rapidly converges to the solution. In fact, we observe that + +Finally, Fig. 4(b) shows the computations of the ranks of the QFIM and Hessian at the overparametrization threshold for Hamiltonians and ansatzes with open and closed boundary conditions. The rank of the QFIM was computed at the global optima, and also at random points in the landscape, and it was found to be the same in all cases. The rank of the Hessian was computed at the global + +8 + +optima. First, let us note that these results show that Theorem 1 and Theorem 3 hold, as the dimension of the associated DLA is always an upper bound for the ranks. As shown in the figure by the dashed lines, we can find the explicit dependence for the ranks as a function of the system size. In the Supplementary Information we present additional plots for the ranks of the QFIM and Hessian. + +### _2. Unitary compilation_ + +Let us now consider a unitary compilation task. Unitary compilation refers to decomposing a target unitary into a sequence of control pulses or quantum gates that can be directly implemented on quantum hardware [107–111]. + +In variational unitary compiling [110, 111], one trains a parametrized quantum circuit _U_ ( **_θ_** ) so that its action matches that of a target unitary _V_ (up to a global phase). Thus, one minimizes the loss function + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0008-05.png) + + + +success probability
+ + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0008-06.png) + + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0008-07.png) + + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0008-08.png) + + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0008-09.png) + + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0008-10.png) + + +Here, _L_ ( **_θ_** ) can be efficiently evaluated on a quantum computer with the Hilbert-Schmidt test [110]. While _L_ ( **_θ_** ) is not exactly of the form in (16), we also prove in the Methods section a theorem showing that the rank of its Hessian is also upper bounded by dim(g _S_ ) at the global optima. + +We employ a hardware efficient ansatz [66] for _U_ ( **_θ_** ) composed of alternating layers of single qubit rotations and entangling gates. The number of parameters is therefore _M_ = 2 _n_ + _L_ (4 _n −_ 4) (see Methods for a detailed description of the ansatz). We sample the target unitary _V_ from the Haar measure in the unitary group of degree _d_ . As shown in [37], the dimension of the DLA associated with this ansatz is + +Figure 5. **Unitary compilation implementation.** a) The loss function is plotted versus iteration. The results are obtained for problem sizes of _n_ = 2 _,_ 3 _,_ 4 _,_ 5 qubits and for an _L_ -layered hardware efficient ansatz. For each point, we averaged the results over 50 randomly-initialized problem instances. b) The success probability (i.e., fraction of instances that converged to the global optimum within an error of 10_−_7 ) is plotted versus number of parameters. The vertical lines indicate the dimension of the DLA. c) The rank of the QFIM and Hessian is plotted versus number of qubits. The rank of the QFIM was evaluated at the optimum and at random points in the landscape. The rank of the Hessian was evaluated at the optimum. Here, the ansatz had a number of parameters for which overparametrization had been fully achieved. + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0008-14.png) + + +and thus grows exponentially with the number of qubits. + +Figure 5(a) shows the results of minimizing the loss function in Eq. (20), for problem sizes of _n_ = 2 _,_ 3 _,_ 4 _,_ 5 qubits, and for ansatzes with different depths _L_ . In all cases we averaged over 50 random parameter initializations. Here we can again observe that as the depth of the circuit increases, the convergence towards the global optimum improves dramatically until reaching a saturation point. Figure 5(b) plots the success probability for randomly-initialized instances. Similar to the VQE implementation, one finds that around dim(g _S_ ) parameters are required to consistently find high-quality solutions, and that the probability of convergence to the global optimum undergoes a drastic phase transition when the number of parameters is around dim(g _S_ ). This result again implies a simplification of the + +optimization landscape, where local traps disappear. We also numerically verify Theorem 1 and Theorem 3. Namely, Fig. 5(c) plots the rank of the QFIM and the Hessian. The QFIM was evaluated at the global optima and at random points in the landscape, while the Hessian was evaluated at the global optima. For all cases we found that the ranks are equal to dim(g) _−_ 1. + +### _3. Quantum autoencoding_ + +Finally, we present results for the archetypal QML task of quantum autoencoding [92, 112]. A quantum autoencoder is a special type of QNN that can be used to com- + +9 + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0009-01.png) + + +Figure 6. **Quantum autoencoder implementation.** The loss function value is plotted versus the number of iterations. Results are obtained for a _n_ = 4 qubit problem when using a layered hardware efficient ansatz with _L_ = 5 and _L_ = 35 layers. + +press quantum information. Analogously to classical autoencoders, the idea is to reduce the dimensionality of the states in a dataset through the action of an encoder _U_ ( **_θ_** ). Once compressed, the states belong to a smaller dimensional Hilbert space known as the latent space. The states compressed into this space can subsequently be recovered with high fidelity at a later time by a decoder _U__†_ ( **_θ_** ). + +Consider a bipartite quantum system _AB_ of _nA_ and _nB_ qubits, respectively, and let _|ψµ⟩_ be states from the training set _S_ . The goal of the quantum autoencoder is to train an encoding parametrized quantum circuit _U_ ( **_θ_** ) to compress the states in _S_ onto subsystem _A_ , so that one can discard the qubits in subsystem _B_ without losing information. A possible loss function here is given by [92] + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0009-05.png) + + +with _O_ = _|_ 0 _⟩⟨_ 0 _|__⊗nb_ _⊗_ 11 _A_ , and where 11 _A_ denotes the identity on subsystem _A_ . We note that an alternative local version of this loss function was proposed in [34] to avoid barren plateaus issues. However, since we here consider small problem sizes, we use the loss in (22). + +Our results were obtained for a system of _n_ = 4 qubits (with _nB_ =2) and for the same hardware efficient ansatz used for unitary compilation. The dataset consisted of four states drawn from the NTangled dataset [113], a quantum dataset composed of states with different amounts and types of multipartite entanglement. As shown in Fig. 6, we can again see that an overparametrized QNN is able to accurately reach the global optima in few iterations. When computing the rank of the QFIM at random points of the landscape, we found that the rank is always 30. This is in contrast to the dimension of the DLA, which is dim(g _S_ ) = 256, and thus the latter leads to some hope + +that overparametrization can be achieved with a number of parameters that is much smaller than dim(g _S_ ). + +Let us here note a crucial difference between the results obtained for the Hamiltonian variational ansatz and for the hardware efficient ansatz. Namely, for the Hamiltonian variational ansatz the dimension of the DLA scaled polynomially with _n_ , whereas for the hardware efficient ansatz the dimension of the DLA grows exponentially with the system size. Thus, in the first case the system becomes overparametrized at a polynomial number of parameters, while the latter case one can require an exponentially large one. This makes it so that overparametrization can be unachievable in practice for large problem sizes when using an ansatz with an exponentially large DLA. In addition, it has been shown that the ansatzes with exponentially large DLAs can exhibit barren plateaus [37], thus further preventing their practical use. + +### **III. DISCUSSION** + +Quantum Machine Learning (QML) is an emerging field that aims to analyze (either classical or quantum) data with significant speedup over classical Machine Learning (ML). However, like classical ML, QML also has trainability issues associated with non-convex landscapes, local minima, and the overall NP-hardness of the optimization. + +Classical ML has benefited from the discovery of the overparameterization phenomenon, whereby increasing the number of parameters beyond some threshold causes many local minima to disappear (e.g., as in Fig. 1). Similarly, preliminary evidence of overparameterization in QML has been discovered for specific constructions of Quantum Neural Networks (QNNs). However, prior to our work, no general theory existed for the precise properties of QNNs that lead to overparameterization. + +In this work, we provide the first general analysis of overparameterization for a broad class of QNNs (i.e., those with periodic structure). We find that the Dynamical Lie Algebra (DLA) obtained from the set of generators of the QNN plays a crucial role in determining properties of the QNN and the ensuing landscape. To our knowledge, our work is the first _algebraic_ theory of overparameterization. This represents an important contribution to Quantum Landscape Theory, i.e., the understanding of QML loss function landscapes and how to engineer them. + +We defined overparametrization as the QNN having more than a critical number of parameters that allow it to explore all independent and relevant directions in the state space. This translates to the Quantum Fisher Information Matrices (QFIMs) having reached their maximum achievable rank. This definition has direct implications for + +10 + +the loss functions of under- and over-parametrized QNNs. Underparametrized QNNs can exhibit spurious, or false, local minima that disappear when one increases the number of parameters and reaches the overparametrization regime. Since the existence of false local minima negatively affect the QNN’s trainability, the overparametrization onset corresponds to a computational phase transition where the QNN parameter optimization improves due to a more favorable landscape. + +We found that the critical number of parameters needed to overparametrize the QNN is directly linked to the dimension of the associated DLA g _S_ . Our theorems showed that the rank of the QFIM (across the whole landscape) and the rank of the Hessian (evaluated at the optima) are upper bounded by dim(g _S_ ). Thus, one can potentially reach overparametrization if the QNN has dim(g _S_ ) parameters. This result is particularly interesting for QNN constructions where dim(g _S_ ) _∈O_ (poly( _n_ )). Thus, our results show that there can exists QNNs that are overparametrized for a polynomial number of parameters. + +We verified our theoretical results by performing numerical simulations of problems where the overparametrization had been heuristically observed [50, 52]. Here, our theoretical framework allowed us to shed new light and explain some of the observations in these prior works. + +We note that most ansatzes used for QNNs in the literature are ultimately hardware efficient ansatzes. These are known to exhibit barren plateaus, and in view of our recent results, they may require an exponential number of parameters to be overparametrized (e.g., see Fig. 5). These results indicate that the search for scalable and trainable ansatzes should be a priority for the field. + +In this sense, our results provide additional guidance to develop QNN architectures with extremely favorable landscapes: overparametrization and absence of barren plateaus. In this context, good candidates are architectures with polynomially large DLAs. + +### **IV. METHODS** + +In this section, we provide additional details and intuition for the results in the main text, as well as a sketch of the proofs for our main theorems. More detailed proofs of our theorems are given in the Supplementary Information. + +### **Intuition behind the dynamical Lie algebra** + +According to Definition 2, the DLA g is obtained from the nested commutators of the elements in the set of generators. To understand why this is the case, let us + +consider a single-layered unitary _U_ ( **_θ_** ) generated by two Hermitian operators, so that _G_ = _{H_ 1 _, H_ 2 _}_ . From the Baker–Campbell–Hausdorff formula, we have that + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0010-11.png) + + +where + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0010-13.png) + + +In Eq. (24) we can see that by combining _e__iθ_1_H_1 and _e__iθ_2_H_2 into a single term, the new evolution is generated by an operator _K_ 1( **_θ_** ) that depends on both _θ_ 1 and _θ_ 2, and which contains the nested commutators between _H_ 1 and _H_ 2. Here, it is also worth noting that the set formed by the operators _{iH_ 1 _, iH_ 2 _, i_ [ _H_ 1 _,_ [ _H_ 1 _, H_ 2]] _, . . .}_ will eventually be closed under the commutation operation in the sense that not all elements will be linearly independent, but rather there will be a finite basis. This is precisely what the DLA is. It is the space spanned by the dim(g) operators that form a basis of the nested commutators. + +When the QNN has multiple layers, that is, when _U_ ( **_θ_** ) =_L_ _l_ =1_eiθl_1_H_1_eiθl_2_H_2,onecanrecursivelyapplythe Baker–Campbell–Hausdorff formula to express the action of the QNN as being generated by a single parametrized operator _KL_ ( **_θ_** ). That is, to have _U_ ( **_θ_** ) = _e__KL_(**_θ_**) . Evidently, both _K_ 1 and _KL_ are obtained from the nested commutators of _H_ 1 and _H_ 2, and thus both operators are elements of g. However, while _K_ 1 depends on only two parameters _θ_ 1 and _θ_ 2, _KL_ is parametrized by all 2 _L_ elements in the vector **_θ_** = _{θl_ 1 _, θl_ 2 _}__L_ _l_ =1.Havingtheseadditionalparameters allows for a more fine-tuned control of the action of _U_ ( **_θ_** ). Intuitively, to hope for a locally surjective map between parameter space and g, we need to place at least dim(g) parameters. Here, there will come a point where further adding parameters does not further increase one’s control of the action of _U_ ( **_θ_** ). + +We finally note that the analysis for a QNN with more than two unitaries in _G_ follows readily. + +### **Motivation for the definition of overparametrization** + +Let us here motivate our definition of overparametrization. First, we recall that we are considering the case where the QNN _U_ ( **_θ_** ) acts on the states of the training set as _U_ ( **_θ_** ) _|ψµ⟩_ , and that the loss function is estimated via measurement outcomes on such evolved states. + +In Definition 3, we defined overparametrization as a property of the QNN (independently of how the loss function is defined). More specifically, we consider a QNN to + +11 + +be overparametrized if the QNN can explore all relevant directions in the state space. This definition is justified from the fact that, irrespective of how the loss function is estimated via measurements on _U_ ( **_θ_** ) _|ψµ⟩_ , the accessible space in the Hilbert space is ultimately defined by the action of the QNN in the states of the training set. + +Here, one could also potentially define overparametrization in terms of exploring all relevant directions in the loss landscape. However, this could have some issues. For instance, consider a QML model where one measures the evolved states _U_ ( **_θ_** ) _|ψµ⟩_ in the computational basis and evaluates the loss function as _L_ ( **_θ_** ) = _µ,_ **_z_**_p_(**_z_**_|ψµ_)_/|S|_, where _p_ ( **_z_** _|ψµ_ ) is the probability of measuring the bitstring **_z_** at the output of the QNN when sending the state _|ψµ⟩_ as input. Evidently, here _L_ ( **_θ_** ) = 1 for all **_θ_** , and independently of how the QNN is defined. Thus, the loss landscape is always flat, and the Hessian is trivially given by the zero matrix. + +The previous example shows that while a QNN can be considered as overparametrized in the state space, this might not be relevant in the loss landscape space. In view of this issue, we have opted to define overparametrization in the state space, as the map leading to states in the Hilbert space (third map in Eq. (3) and Fig. 2) is more fundamental than the map leading to the loss landscape (fourth map). Evidently, we also expect that arguments can be made in favor of defining overparametrization in terms of the loss landscape, or even the unitary space. However, for the setting presently analyzed, Definition 3 can be considered as a first step toward better understanding the overparametrization phenomenon. + +### **Sketch of the Proof of Theorem 1** + +Let us here consider for simplicity the case when the states in the training set do not respect any symmetries in the QNN (i.e., g _S_ = g). From Eq. (2) we have that + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0011-06.png) + + +where we defined _H_˜ _j_ = _U_ 1_†· · · U_ _j__†HjUj · · · U_1.Notethat here the explicit dependence of _H_˜ _j_ in the parameters **_θ_** is omitted. Replacing (25) in Eq. (6) we find that the elements of the QFIM can be written as + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0011-08.png) + + +From here, one finds that the QFIM can be expressed as + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0011-10.png) + + +where we have introduced the vectors **_R_** _mn_ and **_I_** _mn_ with components + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0011-12.png) + + +Here we recall that, by definition, _Hj_ are elements in the DLA g. Then, since the unitaries _U_ are elements of the dynamical Lie group G generated by g, conjugating _Hj_ by any unitary _U_ results in another element in g. That is: _∀U ∈_ G, and _∀Hi ∈_ g we have _UHjU__†_ _∈_ g. Then, by repeating this argument _j_ times, we find that _H_˜ _j ∈_ g. Letting _{Sν}_dim( _ν_ =1g) be a basis of g, we can express + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0011-14.png) + + +where _aν_ ( _j_ ) are real coefficients. From Eq. (29) we can find + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0011-16.png) + + +Equations (30), and (31) show that the vectors **_R_** _mn_ and **_I_** _mn_ can be expressed as a linear combination of dim(g _S_ ) other vectors _{_ **_a_** _ν}_ . Then, while the **_R_** _mn_ and **_I_** _mn_ generate the 2 _d−_ 2 rank-one matrices in the QFIM, we have that _Fµ_ ( **_θ_** ) has a support on a subspace with a basis that has, at most, dim(g) elements. Thus, we find rank[ _Fµ_ ( **_θ_** )] ⩽ dim(g). The latter hence proves Theorem 1. + +Here we note that Eqs. (30), and (31) do not take into consideration what the state _|ψµ⟩_ is. However, from (27), the QFIM is actually expressed in terms of **_Rmψµ_** and **_Imψµ_** . Then, from the definitions in Eq. (28), one can see closerthat theinspection,state playsonea canrole seein thethatterms _H_ ˜ _i |ψHµ_˜ _i⟩ |ψ_ is _µ⟩_ the. Fromactiona of some elements of the Lie algebra over the state _|ψµ⟩_ . Thus, since _H_˜ _i_ are directions in the Lie group, we have that _H_˜ _i |ψµ⟩_ are directions in the state space. + +In fact, the expressible states obtained by acting with the QNN on _|ψµ⟩_ form a submanifold of the Hilbert space known as the state space orbit, which is defined by G _|ψµ⟩_ = _{U |ψµ⟩ , ∀U ∈_ G _}_ [56]. The latter has a very important implications. Since the rank of the QFIM quantifies the number of independent directions in the state space that are accessible via arbitrary infinitesimal variations of the parameter vector **_θ_** , it cannot be larger than the dimension of the state space orbit. Thus, one can tighten + +12 + +the bound in Theorem 1 as + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0012-02.png) + + +where we recall that dim(G _|ψµ⟩_ ) is upper bounded by dim(g). Thus, one can define the overparametrization as + +**Definition 4** (Overparametrization) **.** _A QNN is said to be overparametrized if the number of parameters M is such that the QFIM has rank equal to the dimension of the orbits given by the action of_ G _on the states in the training set._ + +Evidently, a sufficient condition for the QNN to be overparametrized is that _M_ ⩾ max _µ_ dim(G _|ψµ⟩_ ). For example, we have numerically verified that this occurs in the VQE implementation, where the overparametrization onset occurs when _M_ = max _µ_ dim(G _|ψµ⟩_ ). + +### **Sketch of the Proof of Theorem 3** + +The proof of Theorem 3 follows similarly to that of Theorem 1. Specifically, one can show that the Hessian evaluated at **_θ_** _∗_ can be expressed as + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0012-08.png) + + +where _κmn_ are real coefficients, and where now the vectors **_R_**_′_ _mn_and**_I_**_mn_havecomponents + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0012-10.png) + + +Where _Q_ is the matrix that diagonalizes the operator _σ_ = � _µ__cµ |ψµ⟩⟨ψµ|_. + +Then following a similar argument as the one previously used for Theorem 1, we can again show that the rank of the Hessian is such that rank[ _∇_2 _L_ ( **_θ_** _∗_ )] ⩽ min _{_ dim(g _S_ ) _}_ , we leave for the Supplementary Information the rest of the proof, where the quantity _r_ = min _{_ rank[ _σ,_ rank[ _O_ ] _}_ comes into play. + +In the Supplemental Information we also provide a proof for the following theorem + +**Theorem 4.** _Consider the loss functions for a unitary compilation task_ + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0012-15.png) + + +_where T_ ( **_θ_** ) = Tr� _V__†_ _U_ ( **_θ_** )� _for a target unitary V . Then, let H_ 1( **_θ_** _∗_ ) _and H_ 2( **_θ_** _∗_ ) _be the Hessian for the loss functions L_ 1( **_θ_** ) _and L_ 1( **_θ_** ) _, respectively evaluated at their solutions U_ ( **_θ_** _∗_ ) = _V and U_ ( **_θ_** _∗_ ) = _e__iφ_ _V . Then, the maximal rank of ∇_2 _L_ 1( **_θ_** _∗_ ) _and ∇_2 _L_ 1( **_θ_** _∗_ ) _is such that_ rank[ _∇_2 _L_ 1( **_θ_** _∗_ )] _,_ rank[ _∇_2 _L_ 2( **_θ_** _∗_ )] ⩽ dim(g _S_ ) + +### **Sketch of the Proof of Theorem 2** + +Let us here consider for simplicity the case when the dataset contains a single state _|ψ⟩_ which does not respect the symmetries of the ansatz (g _S_ = g). The more general proof is presented in the Supplementary Information. + +Now, the capacities of Eq. (12) and (13) are + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0012-20.png) + + +where _λ__i_ ( **_θ_** ) are the eigenvalues of the QFIM for the state _|ψ⟩_ , and + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0012-22.png) + + +for _F_ ( **_θ_** ) the classical Fisher information for the input state _|ψ⟩_ . + +First, we note that, by definition, _D_ 1( **_θ_** ) = rank[ _F_ ( **_θ_** )], so that that the inequality _D_ 1( **_θ_** ) ⩽ dim(g) follows readily from Theorem 1. Moreover, by the definition of overparametrization in Definition 3, the capacity _D_ 1( **_θ_** ) is saturated on at least one point of the landscape. + +Now we need to show that _D_ 2( **_θ_** ) ⩽ dim(g). Here we recall that the quantum and classical Fisher information matrices are such that [75] + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0012-26.png) + + +for all **_θ_** . Then, using that fact that if _A_ and _B_ are two Hermitian matrices such that _A_ ⩽ _B_ , then _A__q_ ⩽ _B__q_ for all _q ∈_ [0 _,_ 1] [114]. Thus, we have that _F__q_ ( **_θ_** ) ⩽ _F__q_ ( **_θ_** ), and choosing _q_ = 0 leads to + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0012-28.png) + + +_·_ where here supp( ) denotes the support of a matrix. Taking the trace on both sides allows us to obtain + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0012-30.png) + + +Finally, combining Theorem 1 with the definition of overparametrization in Definition 3 and the definition of the capacity _D_ 2( **_θ_** ) in Eq. (35), if follows that _D_ 2( **_θ_** ) ⩽ dim(g). + +13 + +### **Implications for Quantum Optimal Control** + +In Quantum Optimal Control (QOC) [115–126] one is typically interested in controlling the dynamics of a quantum state _|ψ⟩_ evolving through a functional timedependent Hamiltonian + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0013-03.png) + + +that defines the continuous-in-time equation of motion + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0013-05.png) + + +Here, the idea is that the functions _{θk_ ( _t_ ) _}_ , known as control fields, can be trained to pursue some desired evolution. + +Interestingly, it has been shown some that QOC and the field of variational quantum algorithms can unified into a single framework where the evolution of a quantum system is controlled at the pulse level (QOC), or at the gate level (QNN) [37, 127]. Most importantly, irregardless of the choice of controls, the unitaries that are expressible by a QOC ansatz _U_ ( _t_ ) are, like in the QNN case, contained in the group generated by the DLA g (see Definition 2) that is determined by the set of generators _G_ = _{Hk}__K_ _k_ =0.Since all of the results presented in this manuscript are stated in terms of the DLA of a given QNN, they can be straightforwardly adapted to the QOC setting. For example, the maximum rank achievable by some QFIM associated with an ansatz of the form in Eq. (40) will be upper bounded by the dimension of g (equivalent to Theorem 1). That is, a QOC and a QNN ansatz that share the same set of generators _G_ can be expected to have the same saturation value for their respective QFIM matrices. + +Similarly, in analogy with the results in Theorems 3 and 4, the Hessian under a QOC ansatz can be expected to be upper bounded by dim(g) when evaluated at a solution. While the existence of bounds on the rank of the Hessian at solutions is well known in the control literature [129, 130], these results analyze the case when the ansatz is controllable (i.e., when g = su( _d_ )) and thus the bounds found are exponentially large. For example, the rank of the Hessian for unitary compilation tasks (see Eq.(20)), has been shown to be upper bounded by dim(g) = dim(su( _d_ ))) = _d_2 _−_ 1. Hence, the results in this work generalize these previous studies to the case of general g (i.e. uncontrollable systems). Let us note that the existence of a fundamental bound on the rank of the Hessian at the global minima is directly connected to another interesting phenomenon: the arisal of continuous submanifolds of degenerate solutions [62, 63, 85]. + +Although historically the quantum control community has mainly focused on controllable systems, the importance of studying uncontrollable ones, in particular those with dim(g) = _O_ ( _poly_ ( _n_ )), has been evidenced in [37]. Here, it has been ascertained that control systems with exponentially large DLAs may encounter scalability issues, like the prescence of barren plateaus in their optimization landscapes. Conversely, systems with polynomially large DLAs can avoid barren plateaus issues and be scalable. Thus, the results in the present manuscript should also be considered as an additional motivation for QOC systems with polynomially sized algebras, as these will achieve overparametrization with _O_ (poly( _n_ )) parameters. + +Finally, we remark that our results also provide a new insight into the existence of false traps in the control landscape [83, 86–89]. In QOC, false traps are usually analyzed through the rank of the Jacobian matrix of the map _{θk_ ( _t_ ) _} → U_ ( _t_ ). Here, false traps are critical points in the landscape that are not related to local minima of the loss function itself, but to points where this map is not locally not surjective. In our context, this is precisely what a rankdeficient QFIM means: points in parameter space where all possible variations of parameters do not translate into all possible directions in the state space orbit. + +### **Ansatzes for the numerical simulations** + +In this section we present the details of the two QNN ansatzes used in our numeric simulations. Let us remark that both ansatzes are of the form in Eq. (18), i.e. a periodic structured parametrized circuit defined by a given set of generators _G_ . + +Let us first describe the so-called Hamiltonian variational ansatz (HVA) [50, 70]. Consider a VQE task where one wants to minimize a Hamiltonian of the form + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0013-14.png) + + +where _Ak_ are Hermitian operators and _ak_ real numbers. The basic idea in the HVA ansatz is to use, as generators, the individual terms in the Hamiltonian that is being minimized, i.e. _G_ = _{Ak}__N_ _k_ =1.Forinstance,weshow in Fig. 7(a) the ansatz used to find the ground-state of the transverse field Ising model with open boundary conditions. Here, the the generators are _A_ 0 =1 2 � _i__σ_ _i__x_and _A_ 1 =1 2 � _i__σ_ _i__zσ_ _i__z_ +1. + +As a second choice of ansatz, let us introduce the hardware efficient ansatz [66] used in the unitary compilation and autoencoding tasks. As shown in Fig. 7(b), this ansatz is composed of single qubit rotations followed by + +14 + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0014-01.png) + + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0014-02.png) + + +Figure 7. **QNN ansatzes for the numerical simulations.** a) Hamiltonian variational ansatz for the VQE task. Here we show a single layer of the ansatz for _n_ = 4 qubits. For closed boundary conditions, there is an extra _ZZ_ ( _β_ ) gate acting on the uppermost and lowermost qubits. A _ZZ_ ( _β_ ) gate on qubits _i, j_ corresponds to the operator _e__−i__β_ 2_σ_ _i__zσ_ _j__z_ , and it may be decomposed into two CNOTs and one _Rz_ rotation [128]. The input state was _|_ + _⟩__⊗n_ . b) Hardware efficient ansatz for the unitary compilation and autoencoding tasks. Here we show a single layer of the ansatz for _n_ = 5 qubits. Notice that there is an extra _Ry_ and _Rx_ rotation on each qubit at the beginning of the circuit. + +CZ gates acting on alternating pairs of qubits. Here we can see that the number of parameters in the ansatz is _M_ = 2 _n_ + _L_ (4 _n −_ 4). + +the Center for Nonlinear Studies at LANL. This work was supported by the U.S. DOE, Office of Science, Office of Advanced Scientific Computing Research, under the Accelerated Research in Quantum Computing (ARQC) program. + +### **ACKNOWLEDGEMENTS** + +### **AUTHOR CONTRIBUTIONS** + +We thank Patrick deNiverville, Julia Nakhleh, Stavros Efthymiou, Louis Schatzki and Marco Farinati for useful conversations. NJ and DGM were supported by the U.S. DOE through a quantum computing program sponsored by the Los Alamos National Laboratory (LANL) Information Science & Technology Institute. DGM acknowledges partial financial support from project QuantumCAT (ref. 001- P-001644), co-funded by the Generalitat de Catalunya and the European Union Regional Development Fund within the ERDF Operational Program of Catalunya, and from the European Union’s Horizon 2020 research and innovation programme under grant agreement No 951911 (AI4Media). PJC and MC were initially supported by Laboratory Directed Research and Development (LDRD) program of LANL under project number 20190065DR. PJC also acknowledges support from the LANL ASC Beyond Moore’s Law project. MC also acknowledges support from + +The project was conceived by ML, PJC and MC. The manuscript was written by NJ, ML, DGM, PJC, and MC. Theoretical results were proved by NJ, ML, PJC, and MC. Numerical implementations were performed by DGM. + +### **DATA AVAILABILITY** + +Data generated and analyzed during current study are available from the corresponding author upon reasonable request. + +### **COMPETING INTERESTS** + +The authors declare no competing interests. + +- [1] M. Mohri, A. Rostamizadeh, and A. Talwalkar, _Foundations of Machine Learning_ (MIT Press, 2018). + +- [2] J. Vamathevan, D. Clark, P. Czodrowski, I. Dunham, E. Ferran, G. Lee, B. Li, A. Madabhushi, P. Shah, M. Spitzer, _et al._ , Applications of machine learning in drug discovery and development, Nature Reviews Drug Discovery **18** , 463 (2019). + +- [3] J. Schmidt, M. R. Marques, S. Botti, and M. A. Marques, Recent advances and applications of machine learning in solid-state materials science, npj Computational Materials **5** , 1 (2019). + +- [4] S. Grigorescu, B. Trasnea, T. Cocias, and G. Macesanu, A survey of deep learning techniques for autonomous driving, Journal of Field Robotics **37** , 362 (2020). + +15 + +- [5] A. L. Blum and R. L. Rivest, Training a 3-node neural network is np-complete, Neural Networks **5** , 117 (1992). + +- [6] A. Daniely, Complexity theoretic limitations on learning halfspaces, in _Proceedings of the forty-eighth annual ACM symposium on Theory of Computing_ (2016) pp. 105–117. + +- [7] D. Boob, S. S. Dey, and G. Lan, Complexity of training relu neural network, Discrete Optimization , 100620 (2020). + +- [8] B. Neyshabur, Z. Li, S. Bhojanapalli, Y. LeCun, and N. Srebro, The role of over-parametrization in generalization of neural networks, in _International Conference on Learning Representations_ (2018). + +- [9] C. Zhang, S. Bengio, M. Hardt, B. Recht, and O. Vinyals, Understanding deep learning (still) requires rethinking generalization, Communications of the ACM **64** , 107 (2021). + +- [10] Z. Allen-Zhu, Y. Li, and Z. Song, A convergence theory for deep learning via over-parameterization, in _International Conference on Machine Learning_ (PMLR, 2019) pp. 242– 252. + +- [11] Z. Allen-Zhu, Y. Li, and Y. Liang, Learning and generalization in overparameterized neural networks, going beyond two layers, Advances in neural information processing systems (2019). + +- [12] S. Du, J. Lee, H. Li, L. Wang, and X. Zhai, Gradient descent finds global minima of deep neural networks, in _International Conference on Machine Learning_ (PMLR, 2019) pp. 1675–1685. + +- [13] R.-D. Buhai, Y. Halpern, Y. Kim, A. Risteski, and D. Sontag, Empirical study of the benefits of overparameterization in learning latent variable models, in _International Conference on Machine Learning_ (PMLR, 2020) pp. 1211–1219. + +- [14] S. S. Du, X. Zhai, B. Poczos, and A. Singh, Gradient descent provably optimizes over-parameterized neural networks, in _International Conference on Learning Representations_ (2019). + +- [15] A. Brutzkus, A. Globerson, E. Malach, and S. ShalevShwartz, SGD learns over-parameterized networks that provably generalize on linearly separable data, in _International Conference on Learning Representations_ (2018). + +- [16] M. A. Nielsen and I. L. Chuang, _Quantum Computation and Quantum Information_ (Cambridge University Press, 2000). + +- [17] J. Preskill, Quantum computing in the nisq era and beyond, Quantum **2** , 79 (2018). + +- [18] M. Schuld, I. Sinayskiy, and F. Petruccione, An introduction to quantum machine learning, Contemporary Physics **56** , 172 (2015). + +- [19] J. Biamonte, P. Wittek, N. Pancotti, P. Rebentrost, N. Wiebe, and S. Lloyd, Quantum machine learning, Nature **549** , 195 (2017). + +- [20] M. Cerezo, A. Arrasmith, R. Babbush, S. C. Benjamin, S. Endo, K. Fujii, J. R. McClean, K. Mitarai, X. Yuan, L. Cincio, and P. J. Coles, Variational quantum algorithms, Nature Reviews Physics **1** , 19 (2021). + +- [21] H.-Y. Huang, R. Kueng, and J. Preskill, Informationtheoretic bounds on quantum advantage in machine learning, Phys. Rev. Lett. **126** , 190505 (2021). + +- [22] H.-Y. Huang, M. Broughton, M. Mohseni, R. Babbush, S. Boixo, H. Neven, and J. R. McClean, Power of data in quantum machine learning, Nature Communications **12** , 1 (2021). + +- [23] J. M. Kübler, S. Buchholz, and B. Schölkopf, The inductive bias of quantum kernels, arXiv preprint arXiv:2106.03747 (2021). + +- [24] A. Abbas, D. Sutter, C. Zoufal, A. Lucchi, A. Figalli, and S. Woerner, The power of quantum neural networks, Nature Computational Science **1** , 403 (2021). + +- [25] L. Bittel and M. Kliesch, Training variational quantum algorithms is np-hard, Phys. Rev. Lett. **127** , 120502 (2021). + +- [26] M. Benedetti, E. Lloyd, S. Sack, and M. Fiorentini, Parameterized quantum circuits as machine learning models, Quantum Science and Technology **4** , 043001 (2019). + +- [27] K. Beer, D. Bondarenko, T. Farrelly, T. J. Osborne, R. Salzmann, D. Scheiermann, and R. Wolf, Training deep quantum neural networks, Nature Communications **11** , 808 (2020). + +- [28] I. Cong, S. Choi, and M. D. Lukin, Quantum convolutional neural networks, Nature Physics **15** , 1273 (2019). + +- [29] E. Farhi and H. Neven, Classification with quantum neural networks on near term processors, arXiv preprint arXiv:1802.06002 (2018). + +- [30] A. Arrasmith, Z. Holmes, M. Cerezo, and P. J. Coles, Equivalence of quantum barren plateaus to cost concentration and narrow gorges, arXiv preprint arXiv:2104.05868 (2021). + +- [31] J. Rivera-Dean, P. Huembeli, A. Acín, and J. Bowles, Avoiding local minima in variational quantum algorithms with neural networks, arXiv preprint arXiv:2104.02955 (2021). + +- [32] D. Wierichs, C. Gogolin, and M. Kastoryano, Avoiding local minima in variational quantum eigensolvers with the natural gradient optimizer, Physical Review Research **2** , 043246 (2020). + +- [33] J. R. McClean, S. Boixo, V. N. Smelyanskiy, R. Babbush, and H. Neven, Barren plateaus in quantum neural network training landscapes, Nature communications **9** , 1 (2018). + +- [34] M. Cerezo, A. Sone, T. Volkoff, L. Cincio, and P. J. Coles, Cost function dependent barren plateaus in shallow parametrized quantum circuits, Nature Communications **12** , 1791 (2021). + +- [35] C. O. Marrero, M. Kieferová, and N. Wiebe, Entanglement induced barren plateaus, arXiv preprint arXiv:2010.15968 (2020). + +- [36] T. L. Patti, K. Najafi, X. Gao, and S. F. Yelin, Entanglement devised barren plateau mitigation, Physical Review Research **3** , 033090 (2021). + +- [37] M. Larocca, P. Czarnik, K. Sharma, G. Muraleedharan, P. J. Coles, and M. Cerezo, Diagnosing barren plateaus with tools from quantum optimal control, arXiv preprint arXiv:2105.14377 (2021). + +16 + +- [38] Z. Holmes, K. Sharma, M. Cerezo, and P. J. Coles, Connecting ansatz expressibility to gradient magnitudes and barren plateaus, arXiv preprint arXiv:2101.02138 (2021). + +- [39] M. Cerezo and P. J. Coles, Higher order derivatives of quantum neural networks with barren plateaus, Quantum Science and Technology **6** , 035006 (2021). + +- [40] Z. Holmes, A. Arrasmith, B. Yan, P. J. Coles, A. Albrecht, and A. T. Sornborger, Barren plateaus preclude learning scramblers, Physical Review Letters **126** , 190501 (2021). + +- [41] A. Arrasmith, M. Cerezo, P. Czarnik, L. Cincio, and P. J. Coles, Effect of barren plateaus on gradient-free optimization, arXiv preprint arXiv:2011.12245 (2020). + +- [42] P. Huembeli and A. Dauphin, Characterizing the loss landscape of variational quantum circuits, Quantum Science and Technology **6** , 025011 (2021). + +- [43] A. Pesah, M. Cerezo, S. Wang, T. Volkoff, A. T. Sornborger, and P. J. Coles, Absence of barren plateaus in quantum convolutional neural networks, arXiv preprint arXiv:2011.02966 (2020). + +- [44] K. Sharma, M. Cerezo, L. Cincio, and P. J. Coles, Trainability of dissipative perceptron-based quantum neural networks, arXiv preprint arXiv:2005.12458 (2020). + +- [45] S. Wang, E. Fontana, M. Cerezo, K. Sharma, A. Sone, L. Cincio, and P. J. Coles, Noise-induced barren plateaus in variational quantum algorithms, arXiv preprint arXiv:2007.14384 (2020). + +- [46] E. Fontana, M. Cerezo, A. Arrasmith, I. Rungger, and P. J. Coles, Optimizing parametrized quantum circuits via noise-induced breaking of symmetries, arXiv preprint arXiv:2011.08763 (2020). + +- [47] S. Wang, P. Czarnik, A. Arrasmith, M. Cerezo, L. Cincio, and P. J. Coles, Can error mitigation improve trainability of noisy variational quantum algorithms?, arXiv preprint arXiv:2109.01051 (2021). + +- [48] E. Campos, D. Rabinovich, V. Akshay, and J. Biamonte, Training saturation in layerwise quantum approximate optimization, Phys. Rev. A **104** , L030401 (2021). + +- [49] D. S. Franca and R. Garcia-Patron, Limitations of optimization algorithms on noisy quantum devices, arXiv preprint arXiv:2009.05532 (2020). + +- [50] R. Wiersema, C. Zhou, Y. de Sereville, J. F. Carrasquilla, Y. B. Kim, and H. Yuen, Exploring entanglement and optimization within the hamiltonian variational ansatz, PRX Quantum **1** , 020319 (2020). + +- [51] S. Zhang and W. Cui, Overparametrization in qaoa, Written Report (2020). + +- [52] B. T. Kiani, S. Lloyd, and R. Maity, Learning unitaries by gradient descent, arXiv preprint arXiv:2001.11897 (2020). + +- [53] L. Funcke, T. Hartung, K. Jansen, S. Kühn, M. Schneider, and P. Stornati, Best-approximation error for parametric quantum circuits, arXiv preprint arXiv:2107.07378 (2021). + +- [54] J. Lee, A. B. Magann, H. A. Rabitz, and C. Arenz, Progress toward favorable landscapes in quantum combinatorial optimization, Physical Review A **104** , 032401 (2021). + +- [55] E. R. Anschuetz, Critical points in hamiltonian agnostic variational quantum algorithms, arXiv preprint arXiv:2109.06957 (2021). + +- [56] D. D’Alessandro, _Introduction to Quantum Control and Dynamics_ , Chapman & Hall/CRC Applied Mathematics & Nonlinear Science (Taylor & Francis, 2007). + +- [57] R. Zeier and T. Schulte-Herbrüggen, Symmetry principles in quantum systems theory, Journal of mathematical physics **52** , 113510 (2011). + +- [58] T. Haug, K. Bharti, and M. Kim, Capacity and quantum geometry of parametrized quantum circuits, arXiv preprint arXiv:2102.01659 (2021). + +- [59] J. Kim, J. Kim, and D. Rosa, Universal effectiveness of high-depth circuits in variational eigenproblems, Physical Review Research **3** , 023203 (2021). + +- [60] D. d’Alessandro, _Introduction to quantum control and dynamics_ (CRC press, 2007). + +- [61] R. Chakrabarti and H. Rabitz, Quantum control landscapes, International Reviews in Physical Chemistry **26** , 671 (2007). + +- [62] M. Larocca, E. Calzetta, and D. A. Wisniacki, Exploiting landscape geometry to enhance quantum optimal control, Phys. Rev. A **101** , 023410 (2020). + +- [63] M. Larocca, E. Calzetta, and D. Wisniacki, Fourier compression: A customization method for quantum control protocols, Phys. Rev. A **102** , 033108 (2020). + +- [64] S. Boixo, S. V. Isakov, V. N. Smelyanskiy, R. Babbush, N. Ding, Z. Jiang, M. J. Bremner, J. M. Martinis, and H. Neven, Characterizing quantum supremacy in nearterm devices, Nature Physics **14** , 595 (2018). + +- [65] F. Arute, K. Arya, R. Babbush, D. Bacon, _et al._ , Quantum supremacy using a programmable superconducting processor, Nature **574** , 505 (2019). + +- [66] A. Kandala, A. Mezzacapo, K. Temme, M. Takita, M. Brink, J. M. Chow, and J. M. Gambetta, Hardwareefficient variational quantum eigensolver for small molecules and quantum magnets, Nature **549** , 242 (2017). + +- [67] E. Farhi, J. Goldstone, and S. Gutmann, A quantum approximate optimization algorithm, arXiv preprint arXiv:1411.4028 (2014). + +- [68] S. Hadfield, Z. Wang, B. O’Gorman, E. G. Rieffel, D. Venturelli, and R. Biswas, From the quantum approximate optimization algorithm to a quantum alternating operator ansatz, Algorithms **12** , 34 (2019). + +- [69] L. Zhu, H. L. Tang, G. S. Barron, N. J. Mayhall, E. Barnes, and S. E. Economou, An adaptive quantum approximate optimization algorithm for solving combinatorial problems on a quantum computer, arXiv preprint arXiv:2005.10258 (2020). + +- [70] D. Wecker, M. B. Hastings, and M. Troyer, Progress towards practical quantum variational algorithms, Phys. Rev. A **92** , 042303 (2015). + +- [71] A. Choquette, A. Di Paolo, P. K. Barkoutsos, D. Sénéchal, I. Tavernelli, and A. Blais, Quantum-optimal-controlinspired ansatz for variational quantum algorithms, Physical Review Research **3** , 023092 (2021). + +17 + +- [72] M. E. Morales, J. Biamonte, and Z. Zimborás, On the universality of the quantum approximate optimization algorithm, Quantum Information Processing **19** , 1 (2020). + +- [73] S. Sim, P. D. Johnson, and A. Aspuru-Guzik, Expressibility and entangling capability of parameterized quantum circuits for hybrid quantum-classical algorithms, Advanced Quantum Technologies **2** , 1900070 (2019). + +- [74] R. Cheng, Quantum geometric tensor (fubini-study metric) in simple quantum system: A pedagogical introduction, arXiv preprint arXiv:1012.1337 (2010). + +- [75] J. J. Meyer, Fisher Information in Noisy IntermediateScale Quantum Applications, Quantum **5** , 539 (2021). + +- [76] J. Liu, H. Yuan, X.-M. Lu, and X. Wang, Quantum fisher information matrix and multiparameter estimation, Journal of Physics A: Mathematical and Theoretical **53** , 023001 (2019). + +- [77] S. McArdle, T. Jones, S. Endo, Y. Li, S. C. Benjamin, and X. Yuan, Variational ansatz-based quantum simulation of imaginary time evolution, npj Quantum Information **5** , 1 (2019). + +- [78] J. Stokes, J. Izaac, N. Killoran, and G. Carleo, Quantum natural gradient, Quantum **4** , 269 (2020). + +- [79] B. Koczor and S. C. Benjamin, Quantum natural gradient generalised to non-unitary circuits, arXiv preprint arXiv:1912.08660 (2019). + +- [80] J. Gacon, C. Zoufal, G. Carleo, and S. Woerner, Simultaneous perturbation stochastic approximation of the quantum fisher information, arXiv preprint arXiv:2103.09232 (2021). + +- [81] T. Haug and M. Kim, Natural parameterized quantum circuit, arXiv preprint arXiv:2107.14063 (2021). + +- [82] J. Kim and Y. Oz, Quantum energy landscape and vqa optimization, arXiv preprint arXiv:2107.10166 (2021). + +- [83] M. Dalgaard, J. Sherson, and F. Motzoi, Predicting quantum dynamical cost landscapes with deep learning, arXiv preprint arXiv:2107.00008 (2021). + +- [84] M. Dalgaard, F. Motzoi, J. H. M. Jensen, and J. Sherson, Hessian-based optimization of constrained quantum control, Physical Review A **102** , 042612 (2020). + +- [85] K. W. Moore and H. Rabitz, Exploring constrained quantum control landscapes, The Journal of chemical physics **137** , 134113 (2012). + +- [86] R.-B. Wu, R. Long, J. Dominy, T.-S. Ho, and H. Rabitz, Singularities of quantum control landscapes, Physical Review A **86** , 013405 (2012). + +- [87] G. Riviello, C. Brif, R. Long, R.-B. Wu, K. M. Tibbetts, T.-S. Ho, and H. Rabitz, Searching for quantum optimal control fields in the presence of singular critical points, Physical Review A **90** , 013404 (2014). + +- [88] N. Rach, M. M. Müller, T. Calarco, and S. Montangero, Dressing the chopped-random-basis optimization: A bandwidth-limited access to the trap-free landscape, Physical Review A **92** , 062343 (2015). + +- [89] M. Larocca, P. M. Poggi, and D. A. Wisniacki, Quantum control landscape for a two-level system near the quantum speed limit, Journal of Physics A: Mathematical and Theoretical **51** , 385305 (2018). + +- [90] P. J. Coles, Seeking quantum advantage for neural networks, Nature Computational Science **1** , 389 (2021). + +- [91] V. Havlíček, A. D. Córcoles, K. Temme, A. W. Harrow, A. Kandala, J. M. Chow, and J. M. Gambetta, Supervised learning with quantum-enhanced feature spaces, Nature **567** , 209 (2019). + +- [92] J. Romero, J. P. Olson, and A. Aspuru-Guzik, Quantum autoencoders for efficient compression of quantum data, Quantum Science and Technology **2** , 045001 (2017). + +- [93] R. LaRose, A. Tikku, É. O’Neel-Judy, L. Cincio, and P. J. Coles, Variational quantum state diagonalization, npj Quantum Information **5** , 1 (2019). + +- [94] C. Bravo-Prieto, D. García-Martín, and J. I. Latorre, Quantum singular value decomposer, Phys. Rev. A **101** , 062310 (2020). + +- [95] M. Cerezo, K. Sharma, A. Arrasmith, and P. J. Coles, Variational quantum state eigensolver, arXiv preprint arXiv:2004.01372 (2020). + +- [96] X. Yuan, S. Endo, Q. Zhao, Y. Li, and S. C. Benjamin, Theory of variational quantum simulation, Quantum **3** , 191 (2019). + +- [97] C. Cirstoiu, Z. Holmes, J. Iosue, L. Cincio, P. J. Coles, and A. Sornborger, Variational fast forwarding for quantum simulation beyond the coherence time, npj Quantum Information **6** , 1 (2020). + +- [98] B. Commeau, M. Cerezo, Z. Holmes, L. Cincio, P. J. Coles, and A. Sornborger, Variational hamiltonian diagonalization for dynamical quantum simulation, arXiv preprint arXiv:2009.02559 (2020). + +- [99] J. Gibbs, K. Gili, Z. Holmes, B. Commeau, A. Arrasmith, L. Cincio, P. J. Coles, and A. Sornborger, Long-time simulations with high fidelity on quantum hardware, arXiv preprint arXiv:2102.04313 (2021). + +- [100] K. Bharti, A. Cervera-Lierta, T. H. Kyaw, T. Haug, S. Alperin-Lea, A. Anand, M. Degroote, H. Heimonen, J. S. Kottmann, T. Menke, W.-K. Mok, S. Sim, L.-C. Kwek, and A. Aspuru-Guzik, Noisy intermediate-scale quantum (nisq) algorithms, arXiv preprint arXiv:2101.08448 (2021). + +- [101] S. Efthymiou, S. Ramos-Calderer, C. Bravo-Prieto, A. Pérez-Salinas, D. García-Martín, A. Garcia-Saez, J. I. Latorre, and S. Carrazza, Qibo: a framework for quantum simulation with hardware acceleration, arXiv preprint arXiv:2009.01845 (2020). + +- [102] S. Efthymiou, S. Carrazza, S. Ramos, bpcarlos, AdrianPerezSalinas, D. García-Martín, Paul, J. Serrano, and atomicprinter, qiboteam/qibo: Qibo 0.1.6-rc1 (2021). + +- [103] A. Peruzzo, J. McClean, P. Shadbolt, M.-H. Yung, X.-Q. Zhou, P. J. Love, A. Aspuru-Guzik, and J. L. O’brien, A variational eigenvalue solver on a photonic quantum processor, Nature communications **5** , 1 (2014). + +- [104] C. Bravo-Prieto, J. Lumbreras-Zarapico, L. Tagliacozzo, and J. I. Latorre, Scaling of variational quantum circuit depth for condensed matter systems, Quantum **4** , 272 (2020). + +- [105] M. Consiglio, W. J. Chetcuti, C. Bravo-Prieto, S. RamosCalderer, A. Minguzzi, J. I. Latorre, L. Amico, and + +18 + + - T. J. G. Apollaro, Variational quantum eigensolver for su( _n_ ) fermions, arXiv preprint arXiv:2106.15552 (2021). + +- [106] Equations (19) are actually upper bounds for dim(g _S_ ), however, the bounds get quickly saturated as _n_ increases. + +- [107] F. T. Chong, D. Franklin, and M. Martonosi, Programming languages and compiler design for realistic quantum hardware, Nature **549** , 180 (2017). + +- [108] T. Häner, D. S. Steiger, K. Svore, and M. Troyer, A software methodology for compiling quantum programs, Quantum Science and Technology **3** , 020501 (2018). + +- [109] D. Venturelli, M. Do, E. Rieffel, and J. Frank, Compiling quantum circuits to realistic hardware architectures using temporal planners, Quantum Science and Technology **3** , 025004 (2018). + +- [110] S. Khatri, R. LaRose, A. Poremba, L. Cincio, A. T. Sornborger, and P. J. Coles, Quantum-assisted quantum compiling, Quantum **3** , 140 (2019). + +- [111] K. Sharma, S. Khatri, M. Cerezo, and P. J. Coles, Noise resilience of variational quantum compiling, New Journal of Physics **22** , 043006 (2020). + +- [112] H. Ma, C.-J. Huang, C. Chen, D. Dong, Y. Wang, R.-B. Wu, and G.-Y. Xiang, On compression rate of quantum autoencoders: Control design, numerical and experimental realization, arXiv preprint arXiv:2005.11149 (2020). + +- [113] L. Schatzki, A. Arrasmith, P. J. Coles, and M. Cerezo, Entangled datasets for quantum machine learning, arXiv preprint arXiv:2109.03400 (2021). + +- [114] N. Chan and M. K. Kwong, Hermitian matrix inequalities and a conjecture, The American Mathematical Monthly **92** (1985). + +- [115] S. J. Glaser, U. Boscain, T. Calarco, C. P. Koch, W. Köckenberger, R. Kosloff, I. Kuprov, B. Luy, S. Schirmer, T. Schulte-Herbrüggen, _et al._ , Training schrödinger’s cat: quantum optimal control, The European Physical Journal D **69** , 1 (2015). + +- [116] A. Acín, I. Bloch, H. Buhrman, T. Calarco, C. Eichler, J. Eisert, D. Esteve, N. Gisin, S. J. Glaser, F. Jelezko, _et al._ , The quantum technologies roadmap: a european community view, New Journal of Physics **20** , 080201 (2018). + +- [117] Z.-C. Yang, A. Rahmani, A. Shabani, H. Neven, and C. Chamon, Optimizing variational quantum algorithms using pontryagin’s minimum principle, Physical Review X **7** , 021027 (2017). + +- [118] D. Lu, K. Li, J. Li, H. Katiyar, A. J. Park, G. Feng, T. Xin, H. Li, G. Long, A. Brodutch, _et al._ , Enhancing quantum control by bootstrapping a quantum processor of 12 qubits, npj Quantum Information **3** , 1 (2017). + +- [119] P. Rembold, N. Oshnik, M. M. Müller, S. Montangero, T. Calarco, and E. Neu, Introduction to quantum optimal control for quantum sensing with nitrogen-vacancy centers in diamond, AVS Quantum Science **2** , 024701 (2020). + +N. Maskara, W. W. Ho, _et al._ , Controlling quantum manybody dynamics in driven rydberg atom arrays, Science **371** , 1355 (2021). + + - [122] S. Ebadi, T. T. Wang, H. Levine, A. Keesling, G. Semeghini, A. Omran, D. Bluvstein, R. Samajdar, H. Pichler, W. W. Ho, _et al._ , Quantum phases of matter on a 256atom programmable quantum simulator, Nature **595** , 227 (2021). + + - [123] A. B. Magann, K. M. Rudinger, M. D. Grace, and M. Sarovar, Feedback-based quantum optimization, arXiv preprint arXiv:2103.08619 (2021). + + - [124] M. Larocca and D. Wisniacki, Krylov-subspace approach for the efficient control of quantum many-body dynamics, Physical Review A **103** , 023107 (2021). + + - [125] L. T. Brady, C. L. Baldwin, A. Bapat, Y. Kharkov, and A. V. Gorshkov, Optimal protocols in quantum annealing and quantum approximate optimization algorithm problems, Physical Review Letters **126** , 070505 (2021). + + - [126] N. Wittler, F. Roy, K. Pack, M. Werninghaus, A. S. Roy, D. J. Egger, S. Filipp, F. K. Wilhelm, and S. Machnes, Integrated tool set for control, calibration, and characterization of quantum devices applied to superconducting qubits, Physical Review Applied **15** , 034080 (2021). + + - [127] A. B. Magann, C. Arenz, M. D. Grace, T.-S. Ho, R. L. Kosut, J. R. McClean, H. A. Rabitz, and M. Sarovar, From pulses to circuits and back again: A quantum optimal control perspective on variational quantum algorithms, PRX Quantum **2** , 010101 (2021). + + - [128] A. Smith, M. Kim, F. Pollmann, and J. Knolle, Simulating quantum many-body dynamics on a current digital quantum computer, npj Quantum Information **5** , 1 (2019). + + - [129] M. Hsieh, R. Wu, and H. Rabitz, Topology of the quantum control landscape for observables, The Journal of chemical physics **130** , 104109 (2009). + + - [130] T.-S. Ho, J. Dominy, and H. Rabitz, Landscape of unitary transformations in controlled quantum dynamics, Physical Review A **79** , 013422 (2009). + + - [131] J. J. Meyer, Fisher Information in Noisy IntermediateScale Quantum Applications, Quantum **5** , 539 (2021). + + - [132] D. P. Kingma and J. Ba, Adam: A method for stochastic optimization, in _Proceedings of the 3rd International Conference on Learning Representations (ICLR)_ (2015). + + - [133] K. Mitarai, M. Negoro, M. Kitagawa, and K. Fujii, Quantum circuit learning, Physical Review A **98** , 032309 (2018). + + - [134] M. Schuld, V. Bergholm, C. Gogolin, J. Izaac, and N. Killoran, Evaluating analytic gradients on quantum hardware, Physical Review A **99** , 032331 (2019). + + - [135] A. Mari, T. R. Bromley, and N. Killoran, Estimating the gradient and higher-order derivatives on quantum hardware, Phys. Rev. A **103** , 012405 (2021). + +- [120] J. P. Peterson, H. Katiyar, and R. Laflamme, Fast simulation of magnetic field gradients for optimization of pulse sequences, arXiv preprint arXiv:2006.10133 (2020). + +- [121] D. Bluvstein, A. Omran, H. Levine, A. Keesling, G. Semeghini, S. Ebadi, T. T. Wang, A. A. Michailidis, + +19 + +## **SUPPLEMENTARY INFORMATION FOR “THEORY OF OVERPARAMETRIZATION IN QUANTUM NEURAL NETWORKS”** + +In this Supplementary Information, we present detailed proofs of the theorems, and corollaries presented in the manuscript “ _Theory of overparametrization in quantum neural networks_ ”. In addition, here we provide additional details and results for the numerical simulations. + +### **I. PRELIMINARIES** + +Let us start by recalling that we consider the case when the QNN _U_ ( **_θ_** ) is a parametrized quantum circuit with an _L_ -layered periodic structure of the form + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0019-05.png) + + +where the index _l_ indicates the layer, and the index _k_ spans the traceless Hermitian operators _Hk_ that generate the unitaries in the ansatz. Here, **_θ_** _l_ = ( _θl_ 1 _, . . . θlK_ ) are the parameters in a single layer, and **_θ_** = _{_ **_θ_** 1 _, . . . ,_ **_θ_** _L}_ denotes the set of _M_ = _K · L_ trainable parameters in the QNN. In this Supplementary Information, we make use of the following relabelling of the parameters _θlk_ and operators _Hk_ : + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0019-07.png) + + +For convenience we also recall the following definitions: + +**Definition 1** (Set of generators _G_ ) **.** _Consider a parametrized quantum circuit of the form_ (42) _. The set of generators G_ = _{Hk}__K_ _k_ =1_isdefinedastheset(ofsize|G|_=_K)oftheHermitianoperatorsthatgeneratetheunitariesinasingle_ _layer of U_ ( **_θ_** ) _._ + +And, the definition for the dynamical Lie Algebra: + +**Definition 2** (Dynamical Lie Algebra (DLA)) **.** _Consider a set of generators G according to Definition 1. The DLA_ g _is generated by repeated nested commutators of the operators in G. That is,_ + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0019-12.png) + + +_where ⟨S⟩Lie denotes the Lie closure, i.e., the set obtained by repeatedly taking the commutator of the elements in S._ + +**Invariant subspaces.** Consider now the case when the elements in the DLA share a symmetry (for simplicity we assume only one symmetry, although generalization to multiple symmetries is straightforward). That is, there exists a Hermitian operator Σ such that [Σ _, g_ ] = 0 for all _g ∈_ g. If Σ has _N_ distinct eigenvalues, then the DLA has the form g =_N_ _m_ =1g_m_.ThisimposesapartitionofHilbertspace_H_=�_N_ _m_ =1_Hm_whereeachsubspace_Hm_ofdimension_dm_is invariant under g. + +Let us introduce some notation. Consider the _d × dm_ matrix that results from horizontally stacking the eigenvectors of Σ associated with the _m_ -th eigenvalue (of degeneracy _gm_ ) + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0019-16.png) + + +20 + +such that _Qm_ maps vectors from _H_˜ to _H_˜ _m_ . These satisfy + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0020-02.png) + + +where P _m_ are projectors onto the _m_ -th eigenspace, such that_N_ _m_ =1P_m_=11.Letusnowusethenotation + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0020-04.png) + + +to denote the _dm_ -dimensional reduced states and operators, respectively. Recall that, since any unitary _U ∈_ G produced by such a system is block diagonal, we can write _U_ = _m_P_mU_P_m_.Also,letusnotethatif_A_isHermitian,then_A_(_k_) is also Hermitian. + +### **II. PROOF OF THEOREM 1** + +In the following we provide a proof for Theorem 1. Let us first recall the definition of the Quantum Fisher Information Matrix (QFIM). The QFIM entries are given by + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0020-08.png) + + +where _|ψ_ ( **_θ_** ) _⟩_ = _U_ ( **_θ_** ) _|ψ⟩_ . Here we also denote where _|∂iψ_ ( **_θ_** ) _⟩_ = _∂ |ψ_ ( **_θ_** ) _⟩ /∂θi_ = _∂i |ψ_ ( **_θ_** ) _⟩_ for _θi ∈_ **_θ_** . We now restate Theorem 1 for convenience. + +**Theorem 1.** _For each state |ψµ⟩ in the training set S, the maximum rank Rµ of its associated QFIM (defined in Eq._ (48) _) is upper bounded as_ + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0020-11.png) + + +_Proof._ Let us first note that the partial derivatives of the parametrized state are + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0020-13.png) + + +where + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0020-15.png) + + +Thus, _U_ ~~1~~ _~~)~~ j_isthepropagatoruptothej-thlayerinthecircuitandwearelabeling_H_ _j_modulo_|G|_,e.g._H_ _|G|_=_H_ 1,i.e. the first generator. + +Next, let us consider the case then the DLA has a symmetry (see the Preliminaries section above) and that all states in the training set belong to the _m_ -th invariant subspace of the symmetry. We denote by g _S_ the DLA associated with said symmetry respected by the training set, by _HS_ the corresponding Hilbert space, and by _|ψ⟩_(_m_) = _Qm |ψ⟩_ the projected state according to Eq. (47). Then, for any pair of states _|φ⟩ , |χ⟩∈S_ , their overlap can be described in terms of the overlap between the corresponding _dS_ -dimensional reduced states + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0020-18.png) + + +Then, it is straightforward to see that _|ψ⟩_ , _|ψ_ ( **_θ_** ) _⟩_ and _|∂jψ_ ( **_θ_** ) _⟩_ also belong in _HS_ . Hence, the overlaps in Eq. (48) can be computed in terms of their reduced counterparts �� _ψ_ ( **_θ_** )( _S_ )� and �� _∂jψ_ ( **_θ_** )( _S_ )�. In the following, we will work with everything reduced to such subspace, but to simplify the notation, we will omit the _S_ superscript everywhere. For example, whenever we write operator _O_ , we actually mean _O_(_S_) _∈_ C_dS×dS_ . + +Using the explicit expression for the partial derivatives, we find that the first term in Eq. (48) is + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0020-21.png) + + +21 + +Choosing an orthonormal basis containing _|ψ⟩_ we can rewrite this term as + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0021-02.png) + + +Proceeding similarly, we find for the second term in Eq. (48) + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0021-04.png) + + +Combining these results we can express the matrix elements of the QFIM as + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0021-06.png) + + +Note here that this equations also allows us to express the QFIM elements as [ _F_ ] _jk_ = 4Re[Cov _|ψ⟩_ ( _H_˜ _j, H_˜ _k_ )]. Then, defining the vectors **_R_** _mn_ and **_I_** _mn_ with components + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0021-08.png) + + +we can express (56) as + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0021-10.png) + + +where the second equality follows from the fact that _Rmn_ ( _j_ ) = _Rnm_ ( _j_ ), while _Imn_ ( _j_ ) = _−Inm_ ( _j_ ). Thus, one can finally express the QFIM as a sum of 2 _d −_ 2 rank-one matrices + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0021-12.png) + + +Here we recall that, by definition, _Hj_ are elements in the DLA g _S_ . Then, since the unitaries _U_ are elements of the dynamical Lie group G _S_ generated by g _S_ , conjugating _Hj_ by any unitary _U_ results in another element in g _S_ . That is: _∀H_ ˜ _Uj_ was _∈_ G _S_ defined, and _∀_ in _Hi_ Eq. _∈_ g( _S_ 51we). have _UHjU__†_ _∈_ g _S_ . Then, by repeating this argument _j_ times, we find that _H_˜ _j ∈_ g _S_ , where + +Letting _{Sν}ν_dim( =1g) be a basis of g, we can express + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0021-15.png) + + +with _aν_ real coefficients. Using this fact, we can expand **_R_** _mn_ and **_I_** _mn_ in the following ways: + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0021-17.png) + + +More succinctly, we find + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0021-19.png) + + +22 + +These equations show that the vectors **_R_** _mn_ and **_I_** _mn_ can be expressed as a linear combination of dim(g _S_ ) other vectors _{_ **_a_** _ν}_ . Then, since the **_R_** _mn_ and **_I_** _mn_ generate the 2 _d −_ 2 rank-one matrices in the QFIM, we have that _F_ ( **_θ_** ) has a support on a subspace with a basis that has, at most, dim(g _S_ ) elements. Thus, we find + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0022-02.png) + + +## where we have recovered the _µ_ dependence of the QFIM. + +Let us here note that the proof of Theorem 1 holds for all states in the training set _|ψµ⟩∈S_ . Thus, for all _|ψµ⟩_ we know that the associated QFIM _Fµ_ ( **_θ_** ) has a column space contained within some fixed dim(g _S_ ) dimensional space. More precisely, from the previous proof, we have that the following Proposition holds. + +**Proposition 1.** _There is some vector space spanned by_ dim(g _S_ ) _vectors {_ **_a_** _ν}ν_dim( =1g_S_) _, such that for any state in the training set |ψµ⟩∈S, the associated QFIM Fµ_ ( **_θ_** ) _has a column space contained within this vector space._ + +We will make use of this proposition in the following section. + +### **III. PROOF OF THEOREM 2** + +In the following, we provide a proof for Theorem 2. For convenience, we here recall the definition of overparametrization as well as the statement for Theorem 2. + +**Definition 3** (Overparametrization) **.** _A QNN is said to be overparametrized if the number of parameters M is such that the QFI matrices, for all the states in the training set, simultaneously saturate their achievable rank Rµ at least in one point of the loss landscape. That is, if increasing the number of parameters past some minimal (critical) value Mc does not further increase the rank of any QFIM:_ + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0022-10.png) + + +Let us also recall two definitions of a QNN’s effective dimension. First, following [58], we can define the average effective quantum dimension of a QNN: + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0022-12.png) + + +where _λ__i_ _µ_(**_θ_**)aretheeigenvaluesof_Fµ_(**_θ_**),andwhere_I_(_x_) = 0for_x_= 0,and_I_(_x_) = 1for_x_= 1.Heretheexpectation value is taken over the probability distribution that samples input states from the dataset. + +The second definition follows from [24]. In the _n →∞_ limit, the effective quantum dimension of [24] converges to + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0022-15.png) + + +where _F_ ( **_θ_** ) is the classical Fisher Information matrix obtained as + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0022-17.png) + + +Here, _p_ ( _|ψ⟩ , y_ ; **_θ_** ), describes the joint relationship between an input _|ψ⟩_ and an output _y_ of the QNN. In addition, the expectation value is taken over the probability distribution that samples input states from the dataset. Then, consider the following theorem in the main text. + +**Theorem 2.** _The model capacity, as quantified by the effective dimensions of Eqs._ (65) _or_ (66) _, is upper bounded as_ + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0022-20.png) + + +_Moreover, when the QNN is overparametrized according to Definition 3, D_ 1( **_θ_** ) _achieves its maximum value on at least one point of the landscape._ + +23 + +_Proof._ When the QNN is overparametrized according to Definition 3, there exists some **_θ_** such that the ranks of the QFIMs are maximized. That is, rank[ _Fµ_ ( **_θ_** )] = _Rµ_ . + +Note that the effective dimension _D_ 1( **_θ_** ) can be expressed as _D_ 1( **_θ_** ) = E [rank[ _Fµ_ ( **_θ_** )]]. Then, since the ranks are maximal at the overparametrization, so is _D_ 1( **_θ_** ). More precisely, we have _D_ 1( **_θ_** ) = E [ _Rµ_ ]. Additionally, as shown in Theorem 1, rank[ _Fµ_ ( **_θ_** )] ⩽ dim(g _S_ ) for all **_θ_** and _|ψµ⟩∈S_ , so _D_ 1( **_θ_** ) ⩽ dim(g _S_ ). + +Next we consider the effective dimension _D_ 2 (Eq. (66)). _D_ 2 specifically quantifies the maximal rank of the expectation value of the classical Fisher information matrices _F_ _µ_ ( **_θ_** ). Because the operator _Fµ_ ( **_θ_** ) _− F_ _µ_ ( **_θ_** ) is positive semidefinite ([131], Section 5), the following holds: + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0023-04.png) + + +In addition for any two symmetric matrices _A_ and _B_ , having _A_ ⩽ _B_ implies that _A_0 ⩽ _B_0 ([114], Theorem 3). Thus, + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0023-06.png) + + +implying that + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0023-08.png) + + +By applying Proposition 1 to E _µ_ [ _Fµ_ ( **_θ_** )], we arrive at the desired result: + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0023-10.png) + + +### **IV. PROOF OF THEOREM 3** + +In the following we prove Theorem 3, which bounds the rank of the Hessian for an observable minimization loss function of the form + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0023-13.png) + + +at its optimum. Here, the terms _cµ_ are real coefficients associated with each state _|ψµ⟩_ in _S_ , and where the operator _O_ is Hermitian. Let us restate the theorem for convenience. + +**Theorem 3.** _Let ∇_2 _L_ ( **_θ_** _∗_ ) _be the Hessian for a loss function of the form of Eq._ (73) _evaluated at the optimum set of parameters_ **_θ_** _∗. Then, its rank is upper bounded as_ + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0023-16.png) + + +_where r_ = min _{_ rank[ _µ__cµ |ψµ⟩⟨ψµ|_]_,_rank[_O_]_},anddistheHilbertspacedimension._ + +_Proof._ Let us define _ρ_ = _µ__cµ |ψµ⟩⟨ψµ|_.First,thegradienthasthefollowingform: + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0023-19.png) + + +where _H_˜ _j_ is defined in Eq. (51), and where we defined _Of_ = _U_ ( **_θ_** )_†_ _OU_ ( **_θ_** ) (we henceforth drop the explicit dependence on **_θ_** ). Going forward, we similarly drop a the explicit dependence on **_θ_** on terms which are not being differentiated. + +24 + +If we assume that _i_ ⩽ _j_ , and note that _∂iH_˜ _j_ = _i_ [ _H_˜ _i, H_˜ _j_ ] in this case, then we can express the matrix elements of the Hessian as + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0024-02.png) + + +We now evaluate the Hessian at the optimum **_θ_** _∗_ . Here, the propagator has the form [61] _U_ ( **_θ_** _∗_ ) = _R__†_ _Q_ for unitaries _R_ and _Q_ that respectively diagonalize _ρ_ and _O_ , i.e. _ρ_ = _Q__†_ _eQ_ and _O_ = _R__†_ _oR_ , such that _e_ ( _o_ ) is a diagonal matrix containing the eigenvalues of _ρ_ ( _O_ ) in decreasing (increasing) order. Therefore, we can rewrite Eq. (76) at the optimum as + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0024-04.png) + + +where we have used _Of_ = _Q__†_ _oQ_ at the optimum and defined _Rmn__′_(_j_) = Re[ _⟨m| QH_˜ _jQ__†_ _|n⟩_ ] and _Imn__′_(_j_) = Im[ _⟨m| QH_˜ _jQ__†_ _|n⟩_ ]. Because Eq. (77) is symmetric in indices _i_ and _j_ , we can remove the assumption that _i_ ⩽ _j_ . By following a proof similar to that in Theorem 1, we have an upper bound of dim(g _S_ ) on the rank of the Hessian _∇_2 _L_ ( **_θ_** _∗_ ) because _Rmn__′_(_·_)and_I_ _mn__′_(_·_)resideinadim(g_S_)dimensionalspace;seeEq.(62). We will now establish the additional 2 _dr − r_2 _− r_ upper bound stated in the theorem. We will use the short hand _r_ ( _o_ ) and _r_ ( _e_ ) for ranks of _o_ and _e_ , respectively. Assume that _r_ ( _e_ ) ⩽ _r_ ( _o_ ) (the case of _r_ ( _o_ ) ⩽ _r_ ( _e_ ) proceeds similarly). We would like to split Eq. (77) into disjoint summations over _m_ and _n_ . Toward that goal, let us define _tmn_ ( _i, j_ ) = _Rmn__′_(_i_)_R_ _mn__′_(_j_) +_I_ _mn__′_(_i_)_I_ _mn__′_(_j_)torewriteEq.(77): + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0024-06.png) + + +where in the first equality we have simply split the sums among _m_ = _n_ , _m > n_ , and _m < n_ . Then, in the second sum we have attached more specific subscripts to the summation and used the fact that _r_ ( _e_ ) ⩽ _r_ ( _o_ ). We now combine the sums over _m > n_ (and the same for _m < n_ , separately) to arrive at + +25 + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0025-01.png) + + +where the second summation contains the leftover terms when combining over _m > n_ and the fourth summations contains the leftover terms when combining over _m < n_ . Note that _Rmn__′_=_R_ _nm__′_and_I_ _mn__′_=_−I_ _nm__′_.Thismeansthatwecanalso combine more terms between the first and second summations, and also combine terms between the third and fourth summations. By rewriting terms so that _m > n_ and combining, we arrive at + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0025-03.png) + + +As a result, the Hessian can be expressed as + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0025-05.png) + + +where the _j_ ’th entry of **_R_** _mn_ and **_I_** _mn_ are _Rmn__′_(_j_)and_I_ _mn__′_(_j_),respectively.Noweachsummationiscompletelydisjoint _⊤ ⊤_ over ( _m, n_ ) pairs, so the remaining projectors, **_Rmn_** _·_ **_Rmn_** and **_Imn_** _·_ **_Imn_** , are those such that _m > n_ and _n_ ⩽ _r_ ( _e_ ). This gives an upper bound on the rank of the Hessian when _r_ ( _e_ ) ⩽ _r_ ( _o_ ) as + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0025-07.png) + + +A similar analysis for the case of _r_ ( _o_ ) ⩽ _r_ ( _e_ ) reveals + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0025-09.png) + + +Thus, defining _r_ = min _{r_ ( _e_ ) _, r_ ( _o_ ) _}_ , we have an upper bound on the rank of the Hessian as + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0025-11.png) + + +### **V. PROOF OF THEOREM 4** + +Here we present a proof for Theorem 4, which upper bounds the rank of the Hessian (evaluated at the solution) for a unitary compilation task. Here the goal is to train a QNN so that its action matches that of a target unitary _V_ . We + +26 + +consider two possible loss functions for this task + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0026-02.png) + + +Where _L_ 1( **_θ_** ) is minimized if _U_ ( **_θ_** ) = _V_ , while _L_ 2( **_θ_** ) is minimized if _U_ ( **_θ_** ) = _e__iφ_ _V_ , for some any phase _φ_ . We recall now the statement of Theorem 4,: + +**Theorem 4.** _Consider the loss functions for a unitary compilation task_ + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0026-05.png) + + +_where T_ ( **_θ_** ) = Tr� _V__†_ _U_ ( **_θ_** )� _for a target unitary V . Then, let ∇_2 _L_ 1( **_θ_** _∗_ ) _and ∇_2 _L_ 2( **_θ_** _∗_ ) _be the Hessians for the loss functions L_ 1( **_θ_** ) _and L_ 1( **_θ_** ) _, respectively evaluated at their solutions U_ ( **_θ_** _∗_ ) = _V and U_ ( **_θ_** _∗_ ) = _e__iφ_ _V . Then, the maximal ranks of ∇_2 _L_ 1( **_θ_** _∗_ ) _and ∇_2 _L_ 2( **_θ_** _∗_ ) _are such that_ rank[ _∇_2 _L_ 1( **_θ_** _∗_ )] _,_ rank[ _∇_2 _L_ 2( **_θ_** _∗_ )] ⩽ dim(g) _._ + +_Proof._ Let us begin with _L_ 1( **_θ_** ). The gradient of this loss function is + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0026-08.png) + + +where **_H_****˜** **(** **_θ_ )** = ( _H_˜ 1( **_θ_** ) _, · · · , H_˜ _M_ ( **_θ_** ))_⊤_ , and where _H_˜ _j_ ( **_θ_** ) was defined in Eq. (51). Similarly, assuming _i_ ⩽ _j_ , we find for the matrix elements of the Hessian + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0026-10.png) + + +Evaluating at any optimal set of parameters, that is, such that _U_ ( **_θ_** _∗_ ) = _V_ , we find that Eq. (87) is symmetric in indices _i_ and _j_ . Thus, we can remove the assumption that _i_ ⩽ _j_ and express the Hessian more succinctly: + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0026-12.png) + + +where **_R_** _mn_ = Re[ _⟨m|_ **_H_****˜** _|n⟩_ ] and **_I_** _mn_ = Im[ _⟨m|_ **_H_****˜** _|n⟩_ ]. Hence, we again find that the Hessian is a sum of _d_2 rank-one matrices. We note that from here onward, we drop the **_θ_** dependence of **_H_****˜** . + +Then, following a proof similar to the one used in proving Theorem 1, we know that each of the vectors generating the matrices **_R_** _mn_ and **_I_** _mn_ can be written as a linear combination of dim(g) other vectors _{_ **_aν_** _}_dim( _ν_ =1g) . Thus, the rank of the Hessian of _∇_2 _L_ 1( **_θ_** ) at the optimum is upper bounded be larger that dim(g). Now, let us derive the result for _L_ 2( **_θ_** ). The gradient of the loss function is + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0026-15.png) + + +and the Hessian + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0026-17.png) + + +27 + +Evaluating at a solution _U_ ( **_θ_** ) = _e__iφ_ _V_ + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0027-02.png) + + +Again, this is a sum of rank-one matrices that live in the span of _{_ **_a_** _ν}_ , and following a proof similar to that used in deriving Theorem 1, the rank of _∇_2 _L_ 2( **_θ_** _∗_ ) is at upper bounded by dim(g). + +### **VI. DETAILS OF THE NUMERICAL SIMULATIONS** + +The simulations in the main text were carried out in double precision using the open-source library `Qibo` [101, 102] (version 0.1.6). All circuits have been run on CPU because the overhead of transferring the state vector between the host and the device makes the usage of GPUs not suitable for circuits with less than 15-20 qubits. This is specially true for the case of VQAs, where back and forth communication between host and device results in a deteriorated performance. Simulations have been performed using single-thread multiprocessing to parallelize the execution of different instances of circuits with different number of qubits and depths in multiple cores. In particular, IntelCore i7-9750H, IntelCore i7-10750H and IntelCore i9-9900K cores have been employed. + +The optimization method chosen in all cases has been the Adaptive Moment Estimation (Adam) algorithm [132], which is a variant of Stochastic Gradient Descent (SGD) widely used in classical machine learning, that adaptively adjusts the learning rate for each optimization parameter based on information coming from first and second moments of the gradients. This choice has been motivated by the fact that the works that have reported overparametrization in VQAs used this algorithm [50, 52], and also because we consider that a gradient-based optimizer is an appropriate choice to probe relevant features of the optimization landscape, like the disappearance of suboptimal local minima. In order to leverage automatic differentiation for the computation of gradients, the simulation backend in `Qibo` has been set to `tensorflow` . This backend, although slower than the `qibojit` and `qibotf` custom backends, allows to seamlessly deploy `Tensorflow` ’s implementation of the Adam optimizer. The hyper-parameter values employed in all cases are: initial ˆ learning rate = 10_−_2 , _β_ 1 = 0 _._ 9, _β_ 2 = 0 _._ 999 and _ϵ_ = 10_−_7 . The optimization was stopped whenever we reached machine precision. + +The minimizations have been carried out in all cases without considering sampling noise, _i.e._ using the full state vector in the simulation to compute expectation values of observables. The main reason for this is that we are here interested in the optimization landscape itself, and not in the stochasticity introduced by finite sampling. + +Finally, we mention that parameter-shift rules [133, 134] have been employed in all cases for the computation of the quantum Fisher information and Hessian matrices, and that the simulation backend was switched to the faster `qibojit` for that. + +### **VII. FORMULAS FOR COMPUTING THE QFIM AND HESSIAN** + +We present here the explicit formulas employed in the computation of the Quantum Fisher Information Matrix, _F_ ( **_θ_** ), and the Hessian, _∇_2 _L_ ( **_θ_** ), in each of the examples in our numerical simulations. For convenience, we recall the definitions of the elements of these two matrices, + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0027-11.png) + + +where we use the notation _∂i_ = _∂θ∂ i_andwherethesubscript_µ_indicatesthequantumstate_|ψµ⟩_theQNNactson.The QFIM can be interpreted, at each point **_θ_** in the landscape (and up to a constant factor), as the Hessian matrix of a pure state transfer problem where the target state is _|ψ_ ( **_θ_** ) _⟩_ itself. This opens up the possibility of employing quantum circuits to evaluate the QFIM on quantum hardware using parameter shift rules [133, 134], which may be useful for instance when + +28 + +computing the natural gradient during the optimization of a VQA [78], or when doing variational quantum simulation of imaginary-time evolution [77]. The parameter shift rules are simple recipes to analytically compute partial derivatives of a given loss function with respect to parametrized quantum gates. In the case of the QFIM, its elements are given by [135] + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0028-02.png) + + +where + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0028-04.png) + + +We start by computing the QFIM and the Hessian for the Hamiltonian Variational Ansatz (HVA) employed in the Variational Quantum Eigensolver (VQE) implementation. We recall that a HVA is an ansatz of the form in Eq. (42) where the generators _G_ , for a given Hamiltonian _H_ =_N_ _k_ =1_akAk_(with_Ak_Hermitianoperatorsand_ak_realnumbers), are simply _G_ = _{Ak}__N_ _k_ =1.Weemployedthistypeofansatzinthemaintexttominimizethelossfunction + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0028-06.png) + + +where _H_ TFIM is the Hamiltonian of the Transverse Field Ising Model (TFIM). Making use of the fact that for a HVA applied to the TFIM Hamiltonian, all the terms commute within a given _e__−iθlkHk_ operator (where _Hk_ =1 2 � _i__σ_ _i__zσ_ _i__z_ +1or _Hk_ = 21 � _i__σ_ _i__x_),wefindthat + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0028-08.png) + + +where _Hki_ = _σi__zσ_ _i__z_ +1or_Hki_=_σ_ _i__x_.Forconvenience,wehaveadditionallyintroducedthenotation_θlki_todenotethe parameter in the _l_ -th layer, that parametrizes the _i_ -th term of the _k_ -th generator. Note that in a periodic-structured ansatz, _θlki_ = _θlk_ for all _i_ . Now, the partial derivative _∂lk e__−iθlkHki_ can be obtained by applying the parameter shift rule (since _Hki_ has only two distinct non-zero eigenvalues), and hence the partial derivative of a loss function with respect to _θlk_ is + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0028-10.png) + + +where _lki_ denotes all the indices distinct from _l, k, i_ , and _θlki__s_=_θlki_+_s_.Therefore,applyingtheparametershiftrule twice, the matrix elements of the QFIM are given by + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0028-12.png) + + +To analytically compute the Hessian matrix of the loss function _E_ ( **_θ_** ) for the HVA, we can also apply twice the parameter shift rule. The matrix elements _∇_2 _E_ ( **_θ_** ) _lk, l′k′_ = _∂lk ∂l′k′ E_ ( **_θ_** ) of the Hessian are thus given by (see _e.g._ [39, 42]) + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0028-14.png) + + +29 + +We now turn our attention to the Hardware Efficient Ansatz (HEA) that we employed for unitary compilation and quantum autoencoding in the main text. In this case, every _Rx_ or _Ry_ gate in the ansatz is a generator of the form _e__−i__θ_ 2_σk_ (where _σ__k_ _∈{σ__x_ _, σ__y_ _}_ has eigenvalues _±_ 1), with an independent angle. Hence, the QFIM elements are simply + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0029-02.png) + + +We recall that the QFIM is independent of the loss function, and hence the above formula is valid for both the unitary compilation task and the quantum autoencoder. The difference between these two is that the initial states are different, and this has an impact on the QFIM. The Hessian matrices, on the contrary, depend on the loss function and thus are different in each case, but in our simulations we only computed the Hessian for the unitary compilation case. The loss here is given by _L_ ( **_θ_** ) = 1 _− d_ 12_|_Tr � _W__†_ _U_ ( **_θ_** )� _|_2 , where _W_ is the unitary being compiled and _d_ = 2_n_ . In this case, the term _L_ ( _θ_ ) = _d_ 12_|_Tr � _W__†_ _U_ ( **_θ_** )� _|_2 can be directly evaluated on a quantum computer, and so its second partial derivative _∂lk ∂l__′_ _k__′_ _L_ ( **_θ_** ) is + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0029-04.png) + + +Then, applying the chain rule twice on _L_ ( **_θ_** ) = 1 _− L_ ( **_θ_** ) as + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0029-06.png) + + +where _L__′_ ( _L__′′_ ) is the first (second) derivative of _L_ with respect to _L_ , the expression for the Hessian matrix is found to be + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0029-08.png) + + +### **VIII. ADDITIONAL NUMERICAL RESULTS** + +We present here some additional numerical results that we obtained in simulations. In Sup. Fig. 1, we show the eigenvalues of the QFIM and Hessian computed at the global optimum for the loss function _E_ ( **_θ_** ) = _⟨ψ_ ( **_θ_** ) _|H_ TFIM _|ψ_ ( **_θ_** ) _⟩_ and the Hamiltonian variational ansatz with closed boundary conditions, for _n_ = 10 qubits. The interest in showing these plots is that therein one can better appreciate that the spectrum does not form a continuum, but rather, that there is a large gap between the non-zero and the zero eigenvalues, so that there is no ambiguity when defining the rank, stemming from numerical precision issues. Moreover, we computed the eigenvalues at random points in the landscape, where the rank of the QFIM is also bounded by dim(g _S_ ), unlike the rank of the Hessian. The spectra of the Hessian at random points in the landscape further informed us that the landscape is highly non-convex, as it contains both positive and negative eigenvalues. It is also interesting to note that in order to obtain a rank of the Hessian that is bounded by dim(g _S_ ), one needs to compute it at the global minimum; otherwise, we encountered fairly-good local minima that did not fulfill this result. We remark that all these features were found in all cases where we computed and diagonalized the QFIM and the Hessian. + +Furthermore, in Sup. Fig. 2 we computed the rank of the QFIM at 30 random points in the landscape for the HVA with open boundaries and different depths ( _i.e._ number of parameters), for _n_ = 4 _,_ 6 qubits. This figure shows that the rank quickly statures at all points once a critical number of parameters _Mc_ is reached, suggesting that, at least in this case, overparametrization largely arises simultaneously across the entire landscape. We note as well that before having _Mc_ parameters, the average rank is equal to the number of parameters, _i.e._ there is a perfectly linear relation between the two quantities. Adding more parameters beyond _Mc_ however seems to have no effect on the rank, as it only adds null eigenvalues. + +30 + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0030-01.png) + + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0030-02.png) + + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0030-03.png) + + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0030-04.png) + + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0030-05.png) + + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0030-06.png) + + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0030-07.png) + + +SUP FIG. 1. **Spectra of the QFIM and the Hessian for the VQE implementations.** Top row: QFIM spectra for the Hamiltonian variational ansatz with closed boundary conditions, for _n_ = 4 _,_ 6 _,_ 8 _,_ 10 qubits and _L_ = 6 _,_ 12 _,_ 16 _,_ 24 layers, both at the global optima and at three random points in the landscape. Bottom row: Hessian spectra for the Hamiltonian variational ansatz with closed boundary conditions, for _n_ = 4 _,_ 6 _,_ 8 _,_ 10 qubits and _L_ = 4 _,_ 6 _,_ 10 _,_ 12 _,_ 14 _,_ 16 _,_ 22 _,_ 24 layers, both at the global optima and at a random point in the landscape. + + +![](.figures/arxiv__2109.11676/2109.11676.pdf-0030-09.png) + + + +4 qubits
50 6 qubits
dim ( open )
40
30
20
10
10 20 30 40 50
number of parameters
QFIM rank
+ +SUP FIG. 2. **Average QFIM rank versus number of parameters for the VQE implementations.** Average rank of the QFIM across 30 random points in the landscape, for the Hamiltonian variational ansatz with open boundary conditions and _n_ = 4 _,_ 6 qubits. The horizontal dashed lines mark the maximal ranks that the QFIMs achieve, and the tilted dashed line is the line _⟨_ QFIM rank _⟩_ = number of parameters. The vertical black lines correspond to the respective dim(g _S_ ) (leftmost for _n_ = 4, and rightmost for _n_ = 6). We remark that the standard deviation is exactly 0 for all the points in the plot, except for the case _n_ = 4 (6) and _M_ = 12 (30) parameters, where rank[ _F_ ( **_θ_** )] = 12 (30) for 29 out of 30 points and rank[ _F_ ( **_θ_** )] = 11 (29) for the remaining one. diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/references/rendered/INDEX.md b/tracks/qcs/solutions/frustration-free/challenge-113/references/rendered/INDEX.md new file mode 100644 index 000000000..00075619b --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/references/rendered/INDEX.md @@ -0,0 +1,30 @@ +# Challenge 113 quantum-control references + +Generated 2026-07-29. Challenge-specific references for sim-to-real quantum-gate control. Raw PDFs and extracted figures are local-only and gitignored. + +Search inside this dir with `rg --hidden -g '!.raw' "term" .knowledge/` (or `rg` from inside `.knowledge/` itself). The `.raw/` subdir holds the original PDFs / clones / HTML and is gitignored. + +## arXiv (9 entries) + +| File | Title | Authors | Year | Venue | Full text | +|---|---|---|---|---|:---:| +| [2109.11676_theory-of-overparametrization-in-quantum-neural-networks.md](2109.11676_theory-of-overparametrization-in-quantum-neural-networks.md) | Theory of overparametrization in quantum neural networks | Martin Larocca et al. | 2023 | Nature Computational Science | ✅ | +| [2105.14377_diagnosing-barren-plateaus-with-tools-from-quantum-optimal-c.md](2105.14377_diagnosing-barren-plateaus-with-tools-from-quantum-optimal-c.md) | Diagnosing Barren Plateaus with Tools from Quantum Optimal Control | Martin Larocca et al. | 2022 | Quantum | ✅ | +| [2001.01013_discrete-adjoints-for-accurate-numerical-optimization-with-a.md](2001.01013_discrete-adjoints-for-accurate-numerical-optimization-with-a.md) | Discrete Adjoints for Accurate Numerical Optimization with Application to Quantum Control | N. Anders Petersson et al. | 2020 | arXiv: Quantum Physics | ✅ | +| [1803.10856_glassy-phase-of-optimal-quantum-control.md](1803.10856_glassy-phase-of-optimal-quantum-control.md) | Glassy Phase of Optimal Quantum Control. | Andrew G. R. Day et al. | 2019 | Physical Review Letters | ✅ | +| [1706.04454_empirical-analysis-of-the-hessian-of-over-parametrized-neura.md](1706.04454_empirical-analysis-of-the-hessian-of-over-parametrized-neura.md) | Empirical Analysis of the Hessian of Over-Parametrized Neural Networks | Levent Sagun et al. | 2017 | International Conference on Learning Representations | ✅ | +| [1402.7193_adaptive-hybrid-optimal-quantum-control-for-imprecisely-char.md](1402.7193_adaptive-hybrid-optimal-quantum-control-for-imprecisely-char.md) | Adaptive hybrid optimal quantum control for imprecisely characterized systems. | Daniel J. Egger, Frank K. Wilhelm | 2014 | Physical Review Letters | ✅ | +| [1403.0035_optimal-quantum-control-using-randomized-benchmarking.md](1403.0035_optimal-quantum-control-using-randomized-benchmarking.md) | Optimal quantum control using randomized benchmarking. | Julian Kelly et al. | 2014 | Physical Review Letters | ✅ | +| [1103.0855_chopped-random-basis-quantum-optimization.md](1103.0855_chopped-random-basis-quantum-optimization.md) | Chopped random-basis quantum optimization | Tommaso Caneva, Tommaso Calarco, Simone Montangero | 2011 | Physical Review A | ✅ | +| [0710.0684_quantum-control-landscapes.md](0710.0684_quantum-control-landscapes.md) | Quantum control landscapes | Herschel Rabitz, Michael Hsieh, Carey Rosenthal | 2007 | International Reviews in Physical Chemistry | ✅ | + +## DOI (5 entries) + +| File | Title | Authors | Year | Venue | Full text | +|---|---|---|---|---|:---:| +| [10-1103-physrevlett-112-143001.md](10-1103-physrevlett-112-143001.md) | Dynamic dimensionality identification for quantum control. | J. Roslund, H. Rabitz | 2014 | Physical Review Letters | — | +| [10-1063-1-2198836.md](10-1063-1-2198836.md) | Quantum optimal control: Hessian analysis of the control landscape. | Zhenwen Shen, Michael Hsieh, Herschel Rabitz | 2006 | Journal of Chemical Physics | — | +| [10-1016-j-jmr-2004-11-004.md](10-1016-j-jmr-2004-11-004.md) | Optimal control of coupled spin dynamics: design of NMR pulse sequences by gradient ascent algorithms. | N. Khaneja et al. | 2005 | Journal of magnetic resonance (San Diego, Calif. 1997 : Print) | ✅ | +| [10-1126-science-1093649.md](10-1126-science-1093649.md) | Quantum Optimally Controlled Transition Landscapes | H. Rabitz, Michael M. Hsieh, C. Rosenthal | 2004 | Science | — | +| [10-1103-physrevlett-68-1500.md](10-1103-physrevlett-68-1500.md) | Teaching lasers to control molecules. | R. Judson, H. Rabitz | 1992 | Physical Review Letters | — | + diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/run.py b/tracks/qcs/solutions/frustration-free/challenge-113/run.py new file mode 100644 index 000000000..e6d9da450 --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/run.py @@ -0,0 +1,230 @@ +from __future__ import annotations + +import argparse +import json +from pathlib import Path +import sys + +import numpy as np + +sys.path.insert(0, str(Path(__file__).resolve().parent / "src")) + +from qcontrol.artifacts import ArtifactConflict, ArtifactStore +from qcontrol.config import DeviceConfig, ExperimentConfig, SearchConfig, SystemConfig +from qcontrol.experiments import ( + default_sweep_configs, + generate_paired_trials, + read_plan, + run_sweep, + sweep_status, + validate_sweep, +) +from qcontrol.landscape import analyze_landscape +from qcontrol.open_loop import optimize_open_loop +from qcontrol.pulses import PulseSpace +from qcontrol.systems import make_system + + +def _shots(value: str) -> int | None: + if value == "exact": + return None + try: + shots = int(value) + except ValueError: + raise argparse.ArgumentTypeError("shots must be 'exact' or a positive integer") from None + if shots <= 0: + raise argparse.ArgumentTypeError("shots must be 'exact' or a positive integer") + return shots + + +def _system(name: str, segments: int | None = None) -> SystemConfig: + return SystemConfig( + name, + segments if segments is not None else (6 if name == "one_qubit" else 20), + 4.0, + ) + + +def _trial_config(args: argparse.Namespace) -> ExperimentConfig: + system = _system(args.system, args.segments) + return ExperimentConfig( + run_kind=args.kind, + system=system, + device=DeviceConfig( + gap=args.gap, + shots=args.shots, + perturbation_seed=args.perturbation_seed, + ), + search=SearchConfig( + args.method, + args.dimension, + 200 if args.kind == "development" else 2_000, + ), + trial_seed=args.seed, + model_seed=args.model_seed, + ) + + +def _print(payload: object) -> None: + print(json.dumps(payload, allow_nan=False, sort_keys=True)) + + +def _geometry(args: argparse.Namespace) -> int: + system_config = _system(args.system, args.segments) + system = make_system(system_config) + pulse_space = PulseSpace.from_system(system, system_config.segments) + result = optimize_open_loop(system, pulse_space, seed=args.seed) + landscape = analyze_landscape( + system, + pulse_space, + result, + leading_count=min(pulse_space.parameter_count, system.dimension**2 - 1), + dense_validation=pulse_space.parameter_count <= 80, + ) + payload = { + "eigenvalue_ordering": landscape.eigenvalue_ordering, + "hessian_ranks": { + str(key): value for key, value in landscape.hessian_ranks.items() + }, + "jacobian_ranks": { + str(key): value for key, value in landscape.jacobian_ranks.items() + }, + "leading_eigenvalues": [ + float(value) for value in landscape.leading_eigenvalues + ], + "open_loop": { + "evaluations": result.evaluations, + "gradient_norm": result.gradient_norm, + "loss": result.loss, + "starts": result.starts, + }, + "schema_version": 1, + "system": system_config.name, + } + store = ArtifactStore(args.output) + store.bind_provenance( + { + "command": "geometry", + "seed": args.seed, + "system": { + "amplitude_bound": system_config.amplitude_bound, + "duration": system_config.effective_duration, + "name": system_config.name, + "segments": system_config.segments, + }, + } + ) + digest = store.publish_json("geometry.json", payload, immutable=True) + _print({"artifact": "geometry.json", "sha256": digest}) + return 0 + + +def _trial(args: argparse.Namespace) -> int: + config = _trial_config(args) + store = ArtifactStore(args.output) + status = run_sweep(generate_paired_trials([config]), store) + _print(status.canonical_dict()) + return 0 + + +def _sweep(args: argparse.Namespace) -> int: + specs = generate_paired_trials(default_sweep_configs(args.kind)) + store = ArtifactStore(args.output) + status = run_sweep( + specs, + store, + stop_after=args.stop_after, + shard_index=args.shard_index, + shard_count=args.shard_count, + ) + _print(status.canonical_dict()) + return 0 + + +def _load_specs(store: ArtifactStore): + return read_plan(store) + + +def _validate(args: argparse.Namespace) -> int: + store = ArtifactStore(args.output) + if not (store.root / "plan.json").exists(): + _print({"errors": ["missing trial plan"], "valid": False}) + return 1 + try: + report = validate_sweep(_load_specs(store), store) + except ArtifactConflict as error: + _print({"errors": [str(error)], "valid": False}) + return 1 + _print(report.canonical_dict()) + return 0 if report.valid else 1 + + +def _status(args: argparse.Namespace) -> int: + store = ArtifactStore(args.output) + try: + status = sweep_status(_load_specs(store), store) + except ArtifactConflict as error: + _print({"error": str(error)}) + return 1 + _print(status.canonical_dict()) + return 0 + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser( + description="Restartable Challenge 113 experiment orchestration" + ) + subparsers = parser.add_subparsers(dest="command", required=True) + + geometry = subparsers.add_parser("geometry") + geometry.add_argument("--system", choices=("one_qubit", "two_qubit"), required=True) + geometry.add_argument("--segments", type=int) + geometry.add_argument("--seed", type=int, default=0) + geometry.add_argument("--output", type=Path, required=True) + geometry.set_defaults(handler=_geometry) + + trial = subparsers.add_parser("trial") + trial.add_argument("--kind", choices=("development", "production"), required=True) + trial.add_argument("--system", choices=("one_qubit", "two_qubit"), required=True) + trial.add_argument("--segments", type=int) + trial.add_argument("--gap", type=float, required=True) + trial.add_argument("--shots", type=_shots, default=None) + trial.add_argument("--perturbation-seed", type=int, required=True) + trial.add_argument( + "--method", + choices=("full", "model_hessian", "random", "oracle"), + required=True, + ) + trial.add_argument("--dimension", type=int, required=True) + trial.add_argument("--model-seed", type=int, default=5) + trial.add_argument("--seed", type=int, required=True) + trial.add_argument("--output", type=Path, required=True) + trial.set_defaults(handler=_trial) + + sweep = subparsers.add_parser("sweep") + sweep.add_argument("--kind", choices=("development", "production"), required=True) + sweep.add_argument("--stop-after", type=int) + sweep.add_argument("--shard-index", type=int, default=0) + sweep.add_argument("--shard-count", type=int, default=1) + sweep.add_argument("--output", type=Path, required=True) + sweep.set_defaults(handler=_sweep) + + for command, handler in (("validate", _validate), ("status", _status)): + subparser = subparsers.add_parser(command) + subparser.add_argument("--output", type=Path, required=True) + subparser.set_defaults(handler=handler) + return parser + + +def main(argv: list[str] | None = None) -> int: + parser = build_parser() + args = parser.parse_args(argv) + try: + return int(args.handler(args)) + except (ArtifactConflict, ValueError) as error: + parser.error(str(error)) + return 2 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/scripts/apptainer_job_gate.sh b/tracks/qcs/solutions/frustration-free/challenge-113/scripts/apptainer_job_gate.sh new file mode 100755 index 000000000..76033e81a --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/scripts/apptainer_job_gate.sh @@ -0,0 +1,120 @@ +#!/usr/bin/env bash + +: "${CHALLENGE113_DEPLOYMENT:?set extracted immutable source directory}" +: "${CHALLENGE113_RUN_ROOT:?set revision/run-ID output directory}" +: "${CHALLENGE113_EXPECTED_REVISION:?set canonical source revision}" +: "${CHALLENGE113_ARCHIVE_PATH:?set current source archive path}" +: "${CHALLENGE113_ARCHIVE_SHA256:?set current source archive SHA256}" +: "${CHALLENGE113_DEPLOYMENT_METADATA:?set external deployment metadata path}" +: "${CHALLENGE113_DEPLOYMENT_METADATA_SHA256:?set expected deployment metadata SHA256}" +: "${CHALLENGE113_EVIDENCE_REVISION:?set measured evidence revision}" +: "${CHALLENGE113_SIF_PATH:?set immutable Apptainer SIF path}" +: "${CHALLENGE113_SIF_SHA256:?set expected SIF SHA256}" +: "${CHALLENGE113_PYPROJECT_SHA256:?set expected pyproject.toml SHA256}" +: "${CHALLENGE113_UV_LOCK_SHA256:?set expected uv.lock SHA256}" +: "${CHALLENGE113_CLUSTER_PROFILE:?set approved cluster profile}" +if [[ -z "${CHALLENGE113_APPTAINER:-}" ]]; then + module load apptainer/1.3.4 +fi +APPTAINER="${CHALLENGE113_APPTAINER:-apptainer}" + +verify_sha256_file() { + local name="$1" + local path="$2" + local expected="${!name}" + local actual + if [[ ! "${expected}" =~ ^[0-9a-f]{64}$ ]]; then + echo "${name} must be exactly 64 lowercase hex: expected=64-lowercase-hex actual=${expected}" >&2 + return 2 + fi + if [[ ! -f "${path}" || -L "${path}" ]]; then + echo "${name} path must be a regular non-symlink file: ${path}" >&2 + return 2 + fi + actual="$(sha256sum -- "${path}" | awk '{print $1}')" + if [[ "${actual}" != "${expected}" ]]; then + echo "${name} mismatch: expected=${expected} actual=${actual} path=${path}" >&2 + return 2 + fi +} + +test "${CHALLENGE113_CLUSTER_PROFILE}" = "lasg02-cpu-v1" +ARCHIVE_BASENAME="${CHALLENGE113_ARCHIVE_PATH##*/}" +EXPECTED_ARCHIVE_BASENAME="challenge-113-${CHALLENGE113_EXPECTED_REVISION:0:7}.tar.gz" +if [[ ! "${ARCHIVE_BASENAME}" =~ ^challenge-113-[0-9a-f]{7}\.tar\.gz$ ]] \ + || [[ "${ARCHIVE_BASENAME}" != "${EXPECTED_ARCHIVE_BASENAME}" ]]; then + echo "archive basename is unsafe or does not bind the revision" >&2 + exit 2 +fi +CONTAINER_ARCHIVE="/${ARCHIVE_BASENAME}" +SOURCE_REVISION_PATH="${CHALLENGE113_DEPLOYMENT}/.source-revision" +if [[ ! -f "${SOURCE_REVISION_PATH}" || -L "${SOURCE_REVISION_PATH}" ]]; then + echo "source revision path must be a regular non-symlink file: ${SOURCE_REVISION_PATH}" >&2 + exit 2 +fi +ACTUAL_SOURCE_REVISION="$(<"${SOURCE_REVISION_PATH}")" +if [[ "${ACTUAL_SOURCE_REVISION}" != "${CHALLENGE113_EXPECTED_REVISION}" ]]; then + echo "source revision mismatch: expected=${CHALLENGE113_EXPECTED_REVISION} actual=${ACTUAL_SOURCE_REVISION}" >&2 + exit 2 +fi +verify_sha256_file CHALLENGE113_SIF_SHA256 "${CHALLENGE113_SIF_PATH}" +verify_sha256_file CHALLENGE113_ARCHIVE_SHA256 "${CHALLENGE113_ARCHIVE_PATH}" +verify_sha256_file CHALLENGE113_PYPROJECT_SHA256 "${CHALLENGE113_DEPLOYMENT}/pyproject.toml" +verify_sha256_file CHALLENGE113_UV_LOCK_SHA256 "${CHALLENGE113_DEPLOYMENT}/uv.lock" +verify_sha256_file CHALLENGE113_DEPLOYMENT_METADATA_SHA256 "${CHALLENGE113_DEPLOYMENT_METADATA}" +PYTHON_ENTRY="${CHALLENGE113_DEPLOYMENT}/.venv/bin/python" +if ! PYTHON_PARENT="$(realpath -e -- "${CHALLENGE113_DEPLOYMENT}/.venv/bin")"; then + echo "runtime Python parent is missing: ${CHALLENGE113_DEPLOYMENT}/.venv/bin" >&2 + exit 2 +fi +if [[ "${PYTHON_PARENT}" != "${CHALLENGE113_DEPLOYMENT}/.venv/bin" ]]; then + echo "runtime Python parent escapes deployment: ${PYTHON_PARENT}" >&2 + exit 2 +fi +if [[ ! -L "${PYTHON_ENTRY}" && ! -f "${PYTHON_ENTRY}" ]]; then + echo "runtime Python path entry is missing: ${PYTHON_ENTRY}" >&2 + exit 2 +fi +READY_MARKER="${CHALLENGE113_DEPLOYMENT}/.runtime/task10c-ready.json" +if [[ ! -f "${READY_MARKER}" || -L "${READY_MARKER}" ]]; then + echo "runtime readiness marker must be a regular non-symlink file: ${READY_MARKER}" >&2 + exit 2 +fi +mkdir -p "${CHALLENGE113_RUN_ROOT}" + +CONTAINER_ARGS=( + exec + --no-home + --cleanenv + --net + --network none + --bind "${CHALLENGE113_DEPLOYMENT}:/workspace" + --bind "${CHALLENGE113_RUN_ROOT}:/output" + --bind "${CHALLENGE113_ARCHIVE_PATH}:${CONTAINER_ARCHIVE}:ro" + --bind "${CHALLENGE113_DEPLOYMENT_METADATA}:/challenge113-deployment.json:ro" + --env JAX_ENABLE_X64=1 + --env JAX_PLATFORMS=cpu + --env OMP_NUM_THREADS="${SLURM_CPUS_PER_TASK}" + --env UV_OFFLINE=1 + "${CHALLENGE113_SIF_PATH}" +) + +"${APPTAINER}" "${CONTAINER_ARGS[@]}" test -x /workspace/.venv/bin/python +"${APPTAINER}" "${CONTAINER_ARGS[@]}" python3 /workspace/scripts/verify_deployment.py \ + --root /workspace \ + --archive "${CONTAINER_ARCHIVE}" \ + --deployment-metadata /challenge113-deployment.json \ + --expected-revision "${CHALLENGE113_EXPECTED_REVISION}" \ + --expected-archive-sha256 "${CHALLENGE113_ARCHIVE_SHA256}" \ + --expected-evidence-revision "${CHALLENGE113_EVIDENCE_REVISION}" \ + --expected-sif-sha256 "${CHALLENGE113_SIF_SHA256}" \ + --expected-deployment-metadata-sha256 "${CHALLENGE113_DEPLOYMENT_METADATA_SHA256}" \ + --expected-pyproject-sha256 "${CHALLENGE113_PYPROJECT_SHA256}" \ + --expected-uv-lock-sha256 "${CHALLENGE113_UV_LOCK_SHA256}" \ + --expected-cluster-profile "${CHALLENGE113_CLUSTER_PROFILE}" +"${APPTAINER}" "${CONTAINER_ARGS[@]}" /workspace/.venv/bin/python \ + /workspace/scripts/pre_submit_gate.py \ + --root /workspace \ + --deployment-metadata /challenge113-deployment.json \ + --expected-deployment-metadata-sha256 "${CHALLENGE113_DEPLOYMENT_METADATA_SHA256}" \ + --check-marker /workspace/.runtime/task10c-ready.json diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/scripts/build_task10a_evidence.py b/tracks/qcs/solutions/frustration-free/challenge-113/scripts/build_task10a_evidence.py new file mode 100644 index 000000000..eeb18389e --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/scripts/build_task10a_evidence.py @@ -0,0 +1,242 @@ +from __future__ import annotations + +import argparse +import hashlib +import importlib.metadata +import json +import os +from pathlib import Path +import platform +import re +import sys + + +sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src")) + +import jax + +def canonical_bytes(value: object) -> bytes: + return ( + json.dumps( + value, + allow_nan=False, + ensure_ascii=False, + separators=(",", ":"), + sort_keys=True, + ) + + "\n" + ).encode() + + +def digest(path: Path) -> str: + return hashlib.sha256(path.read_bytes()).hexdigest() + + +def write(path: Path, value: object) -> str: + data = canonical_bytes(value) + path.parent.mkdir(parents=True, exist_ok=True) + path.write_bytes(data) + return hashlib.sha256(data).hexdigest() + + +def document( + evidence_type: str, + revision: str, + inputs: dict[str, str], + payload: dict[str, object], +) -> dict[str, object]: + return { + "evidence_type": evidence_type, + "inputs": inputs, + "payload": payload, + "schema_version": 1, + "source_revision": revision, + } + + +def parse_time(path: Path) -> tuple[dict[str, object], str]: + values = {} + command = "" + for line in path.read_text().splitlines(): + stripped = line.strip() + if stripped.startswith("Command being timed: "): + command = stripped.removeprefix("Command being timed: ").strip('"') + elif stripped.startswith("Elapsed (wall clock) time "): + values["wall"] = stripped.rsplit(": ", 1)[1] + elif ":" in stripped: + key, value = stripped.split(":", 1) + values[key] = value.strip() + wall_parts = [float(part) for part in values["wall"].split(":")] + wall_seconds = sum( + part * multiplier + for part, multiplier in zip(reversed(wall_parts), (1.0, 60.0, 3600.0)) + ) + return { + "command_sha256": hashlib.sha256(command.encode()).hexdigest(), + "cpu_percent": int(values["Percent of CPU this job got"].rstrip("%")), + "exit_status": int(values["Exit status"]), + "peak_rss_kib": int(values["Maximum resident set size (kbytes)"]), + "system_seconds": float(values["System time (seconds)"]), + "user_seconds": float(values["User time (seconds)"]), + "wall_seconds": wall_seconds, + }, command + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--run-root", type=Path, required=True) + parser.add_argument("--time", type=Path, required=True) + parser.add_argument("--validation", type=Path, required=True) + parser.add_argument("--output", type=Path, required=True) + parser.add_argument("--revision", required=True) + parser.add_argument("--report", type=Path, required=True) + args = parser.parse_args() + if re.fullmatch(r"[0-9a-f]{40}", args.revision) is None: + raise ValueError("revision must be a full git SHA") + + root = Path(__file__).resolve().parents[1] + calibration_path = args.run_root / "calibration.raw.json" + pilot_root = args.run_root / "pilot" + calibration = json.loads(calibration_path.read_text()) + expected_platform = os.environ["JAX_PLATFORMS"] + actual_platform = jax.devices()[0].platform + if not jax.config.x64_enabled or actual_platform != expected_platform: + raise RuntimeError("evidence collection requires the requested JAX x64 runtime") + config = calibration.pop("config") + calibration.pop("schema_version") + config_sha256 = hashlib.sha256( + json.dumps( + config, + allow_nan=False, + separators=(",", ":"), + sort_keys=True, + ).encode() + ).hexdigest() + lock = root / "uv.lock" + uv_lock_sha256 = digest(lock) + trial_paths = sorted((pilot_root / "trials").glob("*.json")) + if len(trial_paths) != 1: + raise ValueError("representative pilot must contain exactly one trial") + trial_path = trial_paths[0] + trial = json.loads(trial_path.read_text()) + if trial["config"] != config: + raise ValueError("pilot is not bound to the calibration configuration") + plan_path = pilot_root / "plan.json" + ready_path = pilot_root / "ready.json" + manifest_path = pilot_root / "manifest.json" + validation = json.loads(args.validation.read_text()) + timing_payload, _ = parse_time(args.time) + artifact_bytes = sum( + path.stat().st_size for path in pilot_root.rglob("*") if path.is_file() + ) + + docs: dict[str, dict[str, object]] = {} + docs["calibration.json"] = document( + "calibration", + args.revision, + {"raw_calibration": digest(calibration_path), "uv_lock": uv_lock_sha256}, + {"config_sha256": config_sha256, **calibration}, + ) + docs["environment.json"] = document( + "environment", + args.revision, + {"uv_lock": uv_lock_sha256}, + { + "cpu_count": os.cpu_count(), + "jax": importlib.metadata.version("jax"), + "jax_platform": actual_platform, + "jaxlib": importlib.metadata.version("jaxlib"), + "numpy": importlib.metadata.version("numpy"), + "platform": platform.platform(), + "python": platform.python_version(), + "scipy": importlib.metadata.version("scipy"), + "uv_lock_sha256": uv_lock_sha256, + "x64_enabled": bool(jax.config.x64_enabled), + }, + ) + pilot_payload = { + "artifact_bytes": artifact_bytes, + "config_sha256": config_sha256, + "evaluations": trial["ledger"]["total_queries"], + "manifest_sha256": digest(manifest_path), + "plan_sha256": digest(plan_path), + "ready_sha256": digest(ready_path), + "total_queries": trial["ledger"]["total_queries"], + "trial_id": trial["trial_id"], + "trial_sha256": digest(trial_path), + } + docs["pilot.json"] = document( + "pilot", + args.revision, + { + "plan": pilot_payload["plan_sha256"], + "trial": pilot_payload["trial_sha256"], + "uv_lock": uv_lock_sha256, + }, + pilot_payload, + ) + docs["time.json"] = document( + "time", + args.revision, + {"raw_time": digest(args.time), "trial": pilot_payload["trial_sha256"]}, + timing_payload, + ) + docs["validation.json"] = document( + "validation", + args.revision, + { + "raw_validation": digest(args.validation), + "ready": pilot_payload["ready_sha256"], + "trial": pilot_payload["trial_sha256"], + }, + validation, + ) + trial_count = 9_500 + trial_hours = timing_payload["wall_seconds"] * trial_count / 3600 + projection_payload = { + "cpus_per_trial": 8, + "formula": "trial_hours=pilot_wall_seconds*trial_count/3600; core_hours=trial_hours*cpus_per_trial; storage_bytes=pilot_artifact_bytes*trial_count", + "pilot_artifact_bytes": artifact_bytes, + "pilot_wall_seconds": timing_payload["wall_seconds"], + "projected_core_hours": trial_hours * 8, + "projected_storage_bytes": artifact_bytes * trial_count, + "projected_trial_hours": trial_hours, + "provisional": True, + "trial_count": trial_count, + } + docs["report_metadata.json"] = document( + "report_metadata", + args.revision, + {"uv_lock": uv_lock_sha256}, + {"report_sha256": digest(args.report)}, + ) + + hashes = {} + for name in ( + "calibration.json", + "environment.json", + "pilot.json", + "time.json", + "validation.json", + ): + hashes[name] = write(args.output / name, docs[name]) + docs["projection.json"] = document( + "projection", + args.revision, + {"pilot": hashes["pilot.json"], "time": hashes["time.json"]}, + projection_payload, + ) + for name in ("projection.json", "report_metadata.json"): + hashes[name] = write(args.output / name, docs[name]) + write( + args.output / "index.json", + { + "documents": dict(sorted(hashes.items())), + "schema_version": 1, + "source_revision": args.revision, + }, + ) + + +if __name__ == "__main__": + main() diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/scripts/calibrate_pilot.py b/tracks/qcs/solutions/frustration-free/challenge-113/scripts/calibrate_pilot.py new file mode 100644 index 000000000..9e73a3a22 --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/scripts/calibrate_pilot.py @@ -0,0 +1,161 @@ +from __future__ import annotations + +import argparse +import json +import os +from pathlib import Path +import resource +import sys +import time + +import jax +import numpy as np + +sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src")) + +from qcontrol.closed_loop import make_search_space +from qcontrol.config import DeviceConfig, ExperimentConfig, SearchConfig, SystemConfig +from qcontrol.device import make_query_device +from qcontrol.landscape import analyze_landscape +from qcontrol.offline import ( + compute_geometry_diagnostics, + cumulative_best_exact_infidelity, + make_offline_evaluator, + optimize_restricted_noiseless_upper_bound, +) +from qcontrol.open_loop import optimize_open_loop +from qcontrol.pulses import PulseSpace +from qcontrol.systems import make_system, perturb_system + + +def timed(callable_): + started = time.perf_counter() + result = callable_() + return result, time.perf_counter() - started + + +def representative_config() -> ExperimentConfig: + return ExperimentConfig( + run_kind="production", + system=SystemConfig("two_qubit", 20, 4.0), + device=DeviceConfig(gap=0.05, shots=None, perturbation_seed=0), + search=SearchConfig("model_hessian", 4, 2_000), + trial_seed=0, + model_seed=5, + ) + + +def write_json(path: Path, payload: object) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text( + json.dumps( + payload, + allow_nan=False, + separators=(",", ":"), + sort_keys=True, + ) + + "\n", + encoding="utf-8", + ) + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--queries", type=int, default=32) + parser.add_argument("--output", required=True) + args = parser.parse_args() + if not 20 <= args.queries <= 100: + parser.error("--queries must be between 20 and 100") + + config = representative_config() + model = make_system(config.system) + pulse_space = PulseSpace.from_system(model, config.system.segments) + open_loop, open_loop_seconds = timed( + lambda: optimize_open_loop(model, pulse_space, seed=config.model_seed) + ) + landscape, landscape_seconds = timed( + lambda: analyze_landscape( + model, + pulse_space, + open_loop, + leading_count=pulse_space.parameter_count - 1, + dense_validation=True, + ) + ) + origin = np.asarray( + landscape.polishing.normalized_pulse + if landscape.polishing is not None + else open_loop.normalized_pulse, + dtype=np.float64, + ) + truth = perturb_system( + model, + config.device.gap, + config.device.perturbation_seed, + ) + search_space = make_search_space( + config.search, + origin, + model_basis=landscape.model_basis, + seed=config.trial_seed, + ) + rng = np.random.default_rng(config.trial_seed) + pulses = [ + search_space.to_pulse(rng.uniform(-0.2, 0.2, search_space.dimension)) + for _ in range(args.queries) + ] + device = make_query_device( + truth, + pulse_space, + config.device, + seed=config.trial_seed, + ) + first, compilation_seconds = timed(lambda: device.query(pulses[0])) + observations, warm_seconds = timed( + lambda: [device.query(pulse) for pulse in pulses[1:]] + ) + audited = list(zip(pulses, [first, *observations], strict=True)) + _, exact_seconds = timed( + lambda: cumulative_best_exact_infidelity( + make_offline_evaluator(truth, pulse_space), + initial_pulse=origin, + audited_queries=audited, + ) + ) + _, geometry_seconds = timed( + lambda: compute_geometry_diagnostics(model, truth, pulse_space, origin) + ) + restricted, restricted_seconds = timed( + lambda: optimize_restricted_noiseless_upper_bound( + truth, + pulse_space, + search_space, + ) + ) + warm_queries = args.queries - 1 + payload = { + "config": config.canonical_dict(), + "cpu_count": os.cpu_count(), + "exact_trajectory_seconds": exact_seconds, + "first_query_compilation_inclusive_seconds": compilation_seconds, + "geometry_seconds": geometry_seconds, + "jax_platform": jax.devices()[0].platform, + "landscape_seconds": landscape_seconds, + "open_loop_seconds": open_loop_seconds, + "parameter_count": pulse_space.parameter_count, + "peak_rss_kib": resource.getrusage(resource.RUSAGE_SELF).ru_maxrss, + "queries": args.queries, + "restricted_nfev": restricted.nfev, + "restricted_optimization_seconds": restricted_seconds, + "schema_version": 1, + "search_dimension": search_space.dimension, + "warm_queries_per_second": warm_queries / warm_seconds, + "warm_query_seconds": warm_seconds, + "x64_enabled": bool(jax.config.x64_enabled), + } + write_json(Path(args.output), payload) + print(json.dumps(payload, allow_nan=False, sort_keys=True), flush=True) + + +if __name__ == "__main__": + main() diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/scripts/lasg02_profile.env b/tracks/qcs/solutions/frustration-free/challenge-113/scripts/lasg02_profile.env new file mode 100644 index 000000000..b5a9f642a --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/scripts/lasg02_profile.env @@ -0,0 +1,4 @@ +CHALLENGE113_CLUSTER_PROFILE=lasg02-cpu-v1 +CHALLENGE113_SLURM_ACCOUNT=chenkun2025 +CHALLENGE113_SLURM_QOS=user_student090 +CHALLENGE113_SLURM_PARTITION=ihicnormal diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/scripts/pre_submit_gate.py b/tracks/qcs/solutions/frustration-free/challenge-113/scripts/pre_submit_gate.py new file mode 100644 index 000000000..52cb20a1b --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/scripts/pre_submit_gate.py @@ -0,0 +1,193 @@ +from __future__ import annotations + +import argparse +import hashlib +import importlib.metadata +import json +import math +from pathlib import Path +import re +import subprocess +import sys + +import jax +import numpy as np + + +sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src")) + +from qcontrol.config import SystemConfig +from qcontrol.objectives import normalized_infidelity +from qcontrol.propagation import propagate +from qcontrol.pulses import PulseSpace +from qcontrol.systems import make_system + + +EXPECTED_OBJECTIVE = 0.9665488081391005 +OBJECTIVE_TOLERANCE = 1e-14 + + +def canonical_bytes(value: object) -> bytes: + return ( + json.dumps(value, allow_nan=False, separators=(",", ":"), sort_keys=True) + + "\n" + ).encode() + + +def sha256(path: Path) -> str: + return hashlib.sha256(path.read_bytes()).hexdigest() + + +def read_canonical(path: Path) -> dict[str, object]: + data = path.read_bytes() + value = json.loads(data) + if not isinstance(value, dict) or data != canonical_bytes(value): + raise ValueError(f"{path} is not canonical JSON") + return value + + +def runtime_observation() -> dict[str, object]: + system = make_system(SystemConfig("one_qubit", 6, 4.0)) + pulse_space = PulseSpace.from_system(system, 6) + pulse = np.zeros(pulse_space.parameter_count, dtype=np.float64) + propagator = np.asarray( + propagate(system, pulse_space.to_physical(pulse)), + dtype=np.complex128, + ) + objective = float(normalized_infidelity(pulse, system, pulse_space)) + uv_version = subprocess.check_output(["uv", "--version"], text=True).strip() + if not uv_version.startswith("uv "): + raise RuntimeError("uv version output is not canonical") + return { + "critical_packages": { + name: importlib.metadata.version(name) + for name in ("jax", "jaxlib", "numpy", "scipy") + }, + "jax_platform": jax.devices()[0].platform, + "objective": objective, + "propagation_finite": bool( + np.all(np.isfinite(propagator.real)) + and np.all(np.isfinite(propagator.imag)) + ), + "python_version": ".".join(map(str, sys.version_info[:3])), + "uv_version": uv_version.removeprefix("uv "), + "x64_enabled": bool(jax.config.x64_enabled), + } + + +def validate_runtime( + observation: dict[str, object], + deployment: dict[str, object], +) -> None: + if observation["python_version"] != deployment["python_version"]: + raise RuntimeError("Python version does not match deployment metadata") + if observation["uv_version"] != deployment["uv_version"]: + raise RuntimeError("uv version does not match deployment metadata") + if observation["critical_packages"] != deployment["critical_packages"]: + raise RuntimeError("critical package versions do not match deployment metadata") + if observation["jax_platform"] != "cpu" or observation["x64_enabled"] is not True: + raise RuntimeError("JAX must use the CPU x64 runtime") + if observation["propagation_finite"] is not True: + raise RuntimeError("propagation smoke is nonfinite") + objective = observation["objective"] + if ( + type(objective) is not float + or not math.isfinite(objective) + or abs(objective - EXPECTED_OBJECTIVE) > OBJECTIVE_TOLERANCE + ): + raise RuntimeError("deterministic objective smoke does not match") + + +def prepared_runtime_marker( + bindings: dict[str, object], + observation: dict[str, object], +) -> dict[str, object]: + return { + **bindings, + "execution_isolation": "cleanenv-network-none", + "isolated_smoke": { + "jax_platform": observation["jax_platform"], + "objective": observation["objective"], + "propagation_finite": observation["propagation_finite"], + "x64_enabled": observation["x64_enabled"], + }, + "preparation_mode": "one-time-frozen-networked-sync", + "runtime_versions": { + "critical_packages": observation["critical_packages"], + "python_version": observation["python_version"], + "uv_version": observation["uv_version"], + }, + } + + +def marker_payload( + root: Path, + metadata_path: Path, + observation: dict[str, object], +) -> dict[str, object]: + deployment = read_canonical(metadata_path) + source_revision = (root / ".source-revision").read_text().strip() + if source_revision != deployment["revision"]: + raise RuntimeError("runtime source revision is stale") + bindings = { + "archive_sha256": deployment["archive_sha256"], + "cluster_profile": deployment["cluster_profile"], + "deployment_metadata_sha256": sha256(metadata_path), + "evidence_index_sha256": sha256(root / "evidence/task10a/index.json"), + "pyproject_sha256": sha256(root / "pyproject.toml"), + "report_sha256": sha256(root / "REPORT.md"), + "revision": source_revision, + "schema_version": 1, + "sif_sha256": deployment["sif_sha256"], + "uv_lock_sha256": sha256(root / "uv.lock"), + } + for name in ( + "evidence_index_sha256", + "pyproject_sha256", + "report_sha256", + "uv_lock_sha256", + ): + if bindings[name] != deployment[name]: + raise RuntimeError(f"runtime {name} binding is stale") + return prepared_runtime_marker(bindings, observation) + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--root", type=Path, required=True) + parser.add_argument("--deployment-metadata", type=Path, required=True) + parser.add_argument("--expected-deployment-metadata-sha256", required=True) + marker = parser.add_mutually_exclusive_group(required=True) + marker.add_argument("--write-marker", type=Path) + marker.add_argument("--check-marker", type=Path) + args = parser.parse_args() + if ( + re.fullmatch( + r"[0-9a-f]{64}", + args.expected_deployment_metadata_sha256, + re.ASCII, + ) + is None + or sha256(args.deployment_metadata) + != args.expected_deployment_metadata_sha256 + ): + raise RuntimeError("deployment metadata SHA256 mismatch") + deployment = read_canonical(args.deployment_metadata) + observation = runtime_observation() + validate_runtime(observation, deployment) + expected_marker = marker_payload( + args.root, + args.deployment_metadata, + observation, + ) + if args.check_marker is not None: + if read_canonical(args.check_marker) != expected_marker: + raise RuntimeError("prepared runtime marker is stale") + else: + args.write_marker.parent.mkdir(parents=True, exist_ok=True) + args.write_marker.write_bytes(canonical_bytes(expected_marker)) + print(canonical_bytes({"pre_submit_gate": "valid"}).decode().strip(), flush=True) + + +if __name__ == "__main__": + main() diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/scripts/prepare_apptainer_runtime.sh b/tracks/qcs/solutions/frustration-free/challenge-113/scripts/prepare_apptainer_runtime.sh new file mode 100755 index 000000000..622846871 --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/scripts/prepare_apptainer_runtime.sh @@ -0,0 +1,110 @@ +#!/usr/bin/env bash +set -euo pipefail + +: "${CHALLENGE113_DEPLOYMENT:?set extracted immutable source directory}" +: "${CHALLENGE113_EXPECTED_REVISION:?set canonical source revision}" +: "${CHALLENGE113_ARCHIVE_PATH:?set current source archive path}" +: "${CHALLENGE113_ARCHIVE_SHA256:?set current source archive SHA256}" +: "${CHALLENGE113_DEPLOYMENT_METADATA:?set external deployment metadata path}" +: "${CHALLENGE113_DEPLOYMENT_METADATA_SHA256:?set expected deployment metadata SHA256}" +: "${CHALLENGE113_EVIDENCE_REVISION:?set measured evidence revision}" +: "${CHALLENGE113_SIF_PATH:?set immutable Apptainer SIF path}" +: "${CHALLENGE113_SIF_SHA256:?set expected SIF SHA256}" +: "${CHALLENGE113_PYPROJECT_SHA256:?set expected pyproject.toml SHA256}" +: "${CHALLENGE113_UV_LOCK_SHA256:?set expected uv.lock SHA256}" +: "${CHALLENGE113_CLUSTER_PROFILE:?set approved cluster profile}" +if [[ -z "${CHALLENGE113_APPTAINER:-}" ]]; then + module load apptainer/1.3.4 +fi +APPTAINER="${CHALLENGE113_APPTAINER:-apptainer}" + +verify_sha256_file() { + local name="$1" + local path="$2" + local expected="${!name}" + local actual + if [[ ! "${expected}" =~ ^[0-9a-f]{64}$ ]]; then + echo "${name} must be exactly 64 lowercase hex: expected=64-lowercase-hex actual=${expected}" >&2 + return 2 + fi + if [[ ! -f "${path}" || -L "${path}" ]]; then + echo "${name} path must be a regular non-symlink file: ${path}" >&2 + return 2 + fi + actual="$(sha256sum -- "${path}" | awk '{print $1}')" + if [[ "${actual}" != "${expected}" ]]; then + echo "${name} mismatch: expected=${expected} actual=${actual} path=${path}" >&2 + return 2 + fi +} + +test "${CHALLENGE113_CLUSTER_PROFILE}" = "lasg02-cpu-v1" +ARCHIVE_BASENAME="${CHALLENGE113_ARCHIVE_PATH##*/}" +EXPECTED_ARCHIVE_BASENAME="challenge-113-${CHALLENGE113_EXPECTED_REVISION:0:7}.tar.gz" +if [[ ! "${ARCHIVE_BASENAME}" =~ ^challenge-113-[0-9a-f]{7}\.tar\.gz$ ]] \ + || [[ "${ARCHIVE_BASENAME}" != "${EXPECTED_ARCHIVE_BASENAME}" ]]; then + echo "archive basename is unsafe or does not bind the revision" >&2 + exit 2 +fi +CONTAINER_ARCHIVE="/${ARCHIVE_BASENAME}" +SOURCE_REVISION_PATH="${CHALLENGE113_DEPLOYMENT}/.source-revision" +if [[ ! -f "${SOURCE_REVISION_PATH}" || -L "${SOURCE_REVISION_PATH}" ]]; then + echo "source revision path must be a regular non-symlink file: ${SOURCE_REVISION_PATH}" >&2 + exit 2 +fi +ACTUAL_SOURCE_REVISION="$(<"${SOURCE_REVISION_PATH}")" +if [[ "${ACTUAL_SOURCE_REVISION}" != "${CHALLENGE113_EXPECTED_REVISION}" ]]; then + echo "source revision mismatch: expected=${CHALLENGE113_EXPECTED_REVISION} actual=${ACTUAL_SOURCE_REVISION}" >&2 + exit 2 +fi +verify_sha256_file CHALLENGE113_SIF_SHA256 "${CHALLENGE113_SIF_PATH}" +verify_sha256_file CHALLENGE113_ARCHIVE_SHA256 "${CHALLENGE113_ARCHIVE_PATH}" +verify_sha256_file CHALLENGE113_PYPROJECT_SHA256 "${CHALLENGE113_DEPLOYMENT}/pyproject.toml" +verify_sha256_file CHALLENGE113_UV_LOCK_SHA256 "${CHALLENGE113_DEPLOYMENT}/uv.lock" +verify_sha256_file CHALLENGE113_DEPLOYMENT_METADATA_SHA256 "${CHALLENGE113_DEPLOYMENT_METADATA}" +if [[ "${CHALLENGE113_ACK_NETWORKED_PREPARE:-}" != "1" ]]; then + echo "set CHALLENGE113_ACK_NETWORKED_PREPARE=1 for the one-time frozen sync" >&2 + exit 2 +fi + +ISOLATED_CONTAINER_ARGS=( + exec + --no-home + --cleanenv + --net + --network none + --bind "${CHALLENGE113_DEPLOYMENT}:/workspace" + --bind "${CHALLENGE113_ARCHIVE_PATH}:${CONTAINER_ARCHIVE}:ro" + --bind "${CHALLENGE113_DEPLOYMENT_METADATA}:/challenge113-deployment.json:ro" + --env JAX_ENABLE_X64=1 + --env JAX_PLATFORMS=cpu + "${CHALLENGE113_SIF_PATH}" +) +NETWORKED_SYNC_ARGS=( + exec + --no-home + --cleanenv + --bind "${CHALLENGE113_DEPLOYMENT}:/workspace" + "${CHALLENGE113_SIF_PATH}" +) + +"${APPTAINER}" "${ISOLATED_CONTAINER_ARGS[@]}" python3 /workspace/scripts/verify_deployment.py \ + --root /workspace \ + --archive "${CONTAINER_ARCHIVE}" \ + --deployment-metadata /challenge113-deployment.json \ + --expected-revision "${CHALLENGE113_EXPECTED_REVISION}" \ + --expected-archive-sha256 "${CHALLENGE113_ARCHIVE_SHA256}" \ + --expected-evidence-revision "${CHALLENGE113_EVIDENCE_REVISION}" \ + --expected-sif-sha256 "${CHALLENGE113_SIF_SHA256}" \ + --expected-deployment-metadata-sha256 "${CHALLENGE113_DEPLOYMENT_METADATA_SHA256}" \ + --expected-pyproject-sha256 "${CHALLENGE113_PYPROJECT_SHA256}" \ + --expected-uv-lock-sha256 "${CHALLENGE113_UV_LOCK_SHA256}" \ + --expected-cluster-profile "${CHALLENGE113_CLUSTER_PROFILE}" +"${APPTAINER}" "${NETWORKED_SYNC_ARGS[@]}" uv sync \ + --frozen --group dev --project /workspace +"${APPTAINER}" "${ISOLATED_CONTAINER_ARGS[@]}" /workspace/.venv/bin/python \ + /workspace/scripts/pre_submit_gate.py \ + --root /workspace \ + --deployment-metadata /challenge113-deployment.json \ + --expected-deployment-metadata-sha256 "${CHALLENGE113_DEPLOYMENT_METADATA_SHA256}" \ + --write-marker /workspace/.runtime/task10c-ready.json diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/scripts/run_development.sh b/tracks/qcs/solutions/frustration-free/challenge-113/scripts/run_development.sh new file mode 100755 index 000000000..ebad32cff --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/scripts/run_development.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "${ROOT}" + +export JAX_ENABLE_X64=1 +export JAX_PLATFORMS="${CHALLENGE113_JAX_PLATFORM:-cpu}" +OUTPUT="${CHALLENGE113_DEVELOPMENT_OUTPUT:-${ROOT}/results/development}" +case "${OUTPUT}" in + *"/production"|*"/production/"*) echo "development output cannot use a production path" >&2; exit 2 ;; +esac + +uv sync --frozen --group dev +uv run python -c 'import jax; expected = __import__("os").environ["JAX_PLATFORMS"]; actual = jax.devices()[0].platform; assert jax.config.x64_enabled; assert actual == expected, (actual, expected)' +/usr/bin/time -v uv run python -u run.py sweep --kind development --output "${OUTPUT}" diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/scripts/run_production.sh b/tracks/qcs/solutions/frustration-free/challenge-113/scripts/run_production.sh new file mode 100755 index 000000000..3d9420338 --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/scripts/run_production.sh @@ -0,0 +1,85 @@ +#!/usr/bin/env bash +set -euo pipefail + +: "${CHALLENGE113_ACK_PRODUCTION:?set CHALLENGE113_ACK_PRODUCTION=1}" +test "${CHALLENGE113_ACK_PRODUCTION}" = "1" +: "${CHALLENGE113_EXPECTED_REVISION:?set the deployed canonical git revision}" +: "${CHALLENGE113_ARCHIVE_PATH:?set the immutable deployment archive path}" +: "${CHALLENGE113_ARCHIVE_SHA256:?set the deployed archive SHA256}" +: "${CHALLENGE113_DEPLOYMENT_METADATA:?set external deployment metadata path}" +: "${CHALLENGE113_DEPLOYMENT_METADATA_SHA256:?set expected deployment metadata SHA256}" +: "${CHALLENGE113_EVIDENCE_REVISION:?set the measured evidence revision}" +: "${CHALLENGE113_SIF_SHA256:?set expected Apptainer SIF SHA256}" +: "${CHALLENGE113_PYPROJECT_SHA256:?set expected pyproject.toml SHA256}" +: "${CHALLENGE113_UV_LOCK_SHA256:?set expected uv.lock SHA256}" +: "${CHALLENGE113_CLUSTER_PROFILE:?set approved cluster profile}" + +require_sha256_env() { + local name="$1" + local value="${!name}" + if [[ ! "${value}" =~ ^[0-9a-f]{64}$ ]]; then + echo "${name} must be exactly 64 lowercase hex: expected=64-lowercase-hex actual=${value}" >&2 + return 2 + fi +} + +for SHA_NAME in \ + CHALLENGE113_ARCHIVE_SHA256 \ + CHALLENGE113_DEPLOYMENT_METADATA_SHA256 \ + CHALLENGE113_SIF_SHA256 \ + CHALLENGE113_PYPROJECT_SHA256 \ + CHALLENGE113_UV_LOCK_SHA256 +do + require_sha256_env "${SHA_NAME}" +done + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "${ROOT}" + +if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then + test -z "$(git status --porcelain=v1 --untracked-files=all)" + SOURCE_REVISION="$(git rev-parse HEAD)" +elif [[ -f .source-revision ]]; then + SOURCE_REVISION="$(<.source-revision)" +else + echo "cannot establish canonical source revision" >&2 + exit 2 +fi +test "${SOURCE_REVISION}" = "${CHALLENGE113_EXPECTED_REVISION}" +if [[ ! -f "${CHALLENGE113_DEPLOYMENT_METADATA}" || -L "${CHALLENGE113_DEPLOYMENT_METADATA}" ]]; then + echo "CHALLENGE113_DEPLOYMENT_METADATA_SHA256 path must be a regular non-symlink file: ${CHALLENGE113_DEPLOYMENT_METADATA}" >&2 + exit 2 +fi +ACTUAL_METADATA_SHA256="$(sha256sum -- "${CHALLENGE113_DEPLOYMENT_METADATA}" | awk '{print $1}')" +if [[ "${ACTUAL_METADATA_SHA256}" != "${CHALLENGE113_DEPLOYMENT_METADATA_SHA256}" ]]; then + echo "CHALLENGE113_DEPLOYMENT_METADATA_SHA256 mismatch: expected=${CHALLENGE113_DEPLOYMENT_METADATA_SHA256} actual=${ACTUAL_METADATA_SHA256} path=${CHALLENGE113_DEPLOYMENT_METADATA}" >&2 + exit 2 +fi + +export JAX_ENABLE_X64=1 +: "${CHALLENGE113_JAX_PLATFORM:?set CHALLENGE113_JAX_PLATFORM explicitly}" +export JAX_PLATFORMS="${CHALLENGE113_JAX_PLATFORM}" +OUTPUT="${CHALLENGE113_PRODUCTION_OUTPUT:-${ROOT}/results/production}" +case "${OUTPUT}" in + *"/development"|*"/development/"*) echo "production output cannot use a development path" >&2; exit 2 ;; +esac + +uv sync --frozen --group dev +uv run python scripts/verify_deployment.py \ + --root "${ROOT}" \ + --archive "${CHALLENGE113_ARCHIVE_PATH}" \ + --deployment-metadata "${CHALLENGE113_DEPLOYMENT_METADATA}" \ + --expected-revision "${CHALLENGE113_EXPECTED_REVISION}" \ + --expected-archive-sha256 "${CHALLENGE113_ARCHIVE_SHA256}" \ + --expected-evidence-revision "${CHALLENGE113_EVIDENCE_REVISION}" \ + --expected-sif-sha256 "${CHALLENGE113_SIF_SHA256}" \ + --expected-deployment-metadata-sha256 "${CHALLENGE113_DEPLOYMENT_METADATA_SHA256}" \ + --expected-pyproject-sha256 "${CHALLENGE113_PYPROJECT_SHA256}" \ + --expected-uv-lock-sha256 "${CHALLENGE113_UV_LOCK_SHA256}" \ + --expected-cluster-profile "${CHALLENGE113_CLUSTER_PROFILE}" +uv run python -c 'import jax; expected = __import__("os").environ["JAX_PLATFORMS"]; actual = jax.devices()[0].platform; assert jax.config.x64_enabled; assert actual == expected, (actual, expected)' +if [[ "${CHALLENGE113_CHECK_ONLY:-0}" == "1" ]]; then + printf '%s\n' '{"production_gate":"ready"}' + exit 0 +fi +/usr/bin/time -v uv run python -u run.py sweep --kind production --output "${OUTPUT}" diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/scripts/slurm_pilot.sh b/tracks/qcs/solutions/frustration-free/challenge-113/scripts/slurm_pilot.sh new file mode 100755 index 000000000..ed6c1a3e6 --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/scripts/slurm_pilot.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +#SBATCH --job-name=c113-p80-pilot +#SBATCH --account=chenkun2025 +#SBATCH --qos=user_student090 +#SBATCH --partition=ihicnormal +#SBATCH --nodes=1 +#SBATCH --ntasks=1 +#SBATCH --cpus-per-task=8 +#SBATCH --mem=24G +#SBATCH --time=12:00:00 +set -euo pipefail + +: "${CHALLENGE113_DEPLOYMENT:?set absolute canonical deployment directory}" +CANONICAL_DEPLOYMENT="$(realpath -e -- "${CHALLENGE113_DEPLOYMENT}")" +if [[ "${CHALLENGE113_DEPLOYMENT}" != "${CANONICAL_DEPLOYMENT}" ]]; then + echo "CHALLENGE113_DEPLOYMENT must be an absolute canonical directory" >&2 + exit 2 +fi +GATE="${CANONICAL_DEPLOYMENT}/scripts/apptainer_job_gate.sh" +if [[ ! -f "${GATE}" || -L "${GATE}" ]]; then + echo "deployment job gate must be a regular non-symlink file" >&2 + exit 2 +fi +source "${GATE}" + +/usr/bin/time -v "${APPTAINER}" "${CONTAINER_ARGS[@]}" \ + /workspace/.venv/bin/python -u /workspace/run.py trial \ + --kind production --system two_qubit --segments 20 --gap 0.05 --shots exact \ + --perturbation-seed 0 --method model_hessian --dimension 4 \ + --model-seed 5 --seed 0 \ + --output /output/pilot +"${APPTAINER}" "${CONTAINER_ARGS[@]}" /workspace/.venv/bin/python -u \ + /workspace/run.py validate --output /output/pilot diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/scripts/slurm_production_array.sh b/tracks/qcs/solutions/frustration-free/challenge-113/scripts/slurm_production_array.sh new file mode 100755 index 000000000..d6431bd7a --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/scripts/slurm_production_array.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +#SBATCH --job-name=c113-production +#SBATCH --account=chenkun2025 +#SBATCH --qos=user_student090 +#SBATCH --partition=ihicnormal +#SBATCH --nodes=1 +#SBATCH --ntasks=1 +#SBATCH --cpus-per-task=8 +#SBATCH --mem=24G +#SBATCH --time=12:00:00 +set -euo pipefail + +: "${SLURM_ARRAY_TASK_ID:?submit this script as a Slurm array}" +test "${CHALLENGE113_ACK_PRODUCTION:-}" = "1" + +: "${CHALLENGE113_DEPLOYMENT:?set absolute canonical deployment directory}" +CANONICAL_DEPLOYMENT="$(realpath -e -- "${CHALLENGE113_DEPLOYMENT}")" +if [[ "${CHALLENGE113_DEPLOYMENT}" != "${CANONICAL_DEPLOYMENT}" ]]; then + echo "CHALLENGE113_DEPLOYMENT must be an absolute canonical directory" >&2 + exit 2 +fi +GATE="${CANONICAL_DEPLOYMENT}/scripts/apptainer_job_gate.sh" +if [[ ! -f "${GATE}" || -L "${GATE}" ]]; then + echo "deployment job gate must be a regular non-symlink file" >&2 + exit 2 +fi +source "${GATE}" + +"${APPTAINER}" "${CONTAINER_ARGS[@]}" /workspace/.venv/bin/python -u \ + /workspace/run.py sweep \ + --kind production \ + --shard-index "${SLURM_ARRAY_TASK_ID}" \ + --shard-count 9500 \ + --output /output/production diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/scripts/verify_deployment.py b/tracks/qcs/solutions/frustration-free/challenge-113/scripts/verify_deployment.py new file mode 100644 index 000000000..2fb9d3572 --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/scripts/verify_deployment.py @@ -0,0 +1,46 @@ +from __future__ import annotations + +import argparse +from pathlib import Path +import sys + + +sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src")) + +from qcontrol.evidence import validate_deployment + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--root", type=Path, required=True) + parser.add_argument("--archive", type=Path, required=True) + parser.add_argument("--deployment-metadata", type=Path, required=True) + parser.add_argument("--expected-revision", required=True) + parser.add_argument("--expected-archive-sha256", required=True) + parser.add_argument("--expected-evidence-revision", required=True) + parser.add_argument("--expected-sif-sha256", required=True) + parser.add_argument("--expected-deployment-metadata-sha256", required=True) + parser.add_argument("--expected-pyproject-sha256", required=True) + parser.add_argument("--expected-uv-lock-sha256", required=True) + parser.add_argument("--expected-cluster-profile", required=True) + args = parser.parse_args() + validate_deployment( + args.root, + archive_path=args.archive, + deployment_metadata_path=args.deployment_metadata, + expected_revision=args.expected_revision, + expected_archive_sha256=args.expected_archive_sha256, + expected_evidence_revision=args.expected_evidence_revision, + expected_sif_sha256=args.expected_sif_sha256, + expected_deployment_metadata_sha256=( + args.expected_deployment_metadata_sha256 + ), + expected_pyproject_sha256=args.expected_pyproject_sha256, + expected_uv_lock_sha256=args.expected_uv_lock_sha256, + expected_cluster_profile=args.expected_cluster_profile, + ) + print('{"deployment_valid":true}', flush=True) + + +if __name__ == "__main__": + main() diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/scripts/write_deployment_metadata.py b/tracks/qcs/solutions/frustration-free/challenge-113/scripts/write_deployment_metadata.py new file mode 100644 index 000000000..7f9d8d5c2 --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/scripts/write_deployment_metadata.py @@ -0,0 +1,60 @@ +from __future__ import annotations + +import argparse +import hashlib +import json +from pathlib import Path +import re + + +SIF_NAME = "uv-0.9.9-python3.12-bookworm-slim.sif" +SIF_SHA256 = "2405a769d520e6d0f680c0f1dff0d9f92083724f1ffd85ea0c26b5e36defa323" +CLUSTER_PROFILE = "lasg02-cpu-v1" + + +def sha256(path: Path) -> str: + return hashlib.sha256(path.read_bytes()).hexdigest() + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--root", type=Path, required=True) + parser.add_argument("--archive", type=Path, required=True) + parser.add_argument("--revision", required=True) + parser.add_argument("--output", type=Path, required=True) + args = parser.parse_args() + if re.fullmatch(r"[0-9a-f]{40}", args.revision) is None: + raise ValueError("revision must be a full Git SHA") + expected_name = f"challenge-113-{args.revision[:7]}.tar.gz" + if args.archive.name != expected_name: + raise ValueError("archive name does not bind the revision") + payload = { + "archive_name": args.archive.name, + "archive_sha256": sha256(args.archive), + "cluster_profile": CLUSTER_PROFILE, + "critical_packages": { + "jax": "0.11.0", + "jaxlib": "0.11.0", + "numpy": "2.5.1", + "scipy": "1.18.0", + }, + "evidence_index_sha256": sha256(args.root / "evidence/task10a/index.json"), + "pyproject_sha256": sha256(args.root / "pyproject.toml"), + "python_version": "3.12.12", + "report_sha256": sha256(args.root / "REPORT.md"), + "revision": args.revision, + "schema_version": 1, + "sif_name": SIF_NAME, + "sif_sha256": SIF_SHA256, + "uv_lock_sha256": sha256(args.root / "uv.lock"), + "uv_version": "0.9.9", + } + args.output.parent.mkdir(parents=True, exist_ok=True) + args.output.write_text( + json.dumps(payload, allow_nan=False, separators=(",", ":"), sort_keys=True) + + "\n" + ) + + +if __name__ == "__main__": + main() diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/src/qcontrol/__init__.py b/tracks/qcs/solutions/frustration-free/challenge-113/src/qcontrol/__init__.py new file mode 100644 index 000000000..91b3a11be --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/src/qcontrol/__init__.py @@ -0,0 +1,5 @@ +"""Configuration and simulation tools for Challenge 113.""" + +from qcontrol.config import DeviceConfig, ExperimentConfig, SearchConfig, SystemConfig + +__all__ = ["DeviceConfig", "ExperimentConfig", "SearchConfig", "SystemConfig"] diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/src/qcontrol/analysis.py b/tracks/qcs/solutions/frustration-free/challenge-113/src/qcontrol/analysis.py new file mode 100644 index 000000000..727f8b12e --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/src/qcontrol/analysis.py @@ -0,0 +1,1522 @@ +from __future__ import annotations + +from collections.abc import Callable, Iterable, Mapping, Sequence +from dataclasses import dataclass +import hashlib +import json +import math +from statistics import NormalDist +from typing import Any + +import numpy as np + +from qcontrol.artifacts import ArtifactConflict, ArtifactStore, canonical_json_bytes +from qcontrol.experiments import TrialResult, read_plan, validate_sweep + + +class AnalysisError(ValueError): + """Verified artifacts cannot be analyzed without changing the study.""" + + +_CANONICAL_TOLERANCE = 1e-15 + + +def _finite_number(value: object, *, name: str) -> float: + if isinstance(value, bool) or not isinstance(value, (int, float)): + raise AnalysisError(f"{name} must be a finite number") + numeric = float(value) + if not math.isfinite(numeric): + raise AnalysisError(f"{name} must be finite") + return numeric + + +def _probability(value: object, *, name: str) -> float: + numeric = _finite_number(value, name=name) + if not 0.0 <= numeric <= 1.0: + raise AnalysisError(f"{name} must be a probability") + return numeric + + +def _strict_nonnegative_int(value: object, *, name: str) -> int: + if type(value) is not int or value < 0: + raise AnalysisError(f"{name} must be a nonnegative integer") + return value + + +def _strict_positive_int(value: object, *, name: str) -> int: + integer = _strict_nonnegative_int(value, name=name) + if integer == 0: + raise AnalysisError(f"{name} must be positive") + return integer + + +def _mapping(value: object, fields: set[str], *, name: str) -> Mapping[str, object]: + if not isinstance(value, Mapping) or set(value) != fields: + raise AnalysisError(f"{name} fields are not canonical") + return value + + +def _number_tuple(value: object, *, name: str) -> tuple[float, ...]: + if not isinstance(value, list): + raise AnalysisError(f"{name} must be a list") + return tuple(_finite_number(item, name=name) for item in value) + + +def _integer_tuple(value: object, *, name: str) -> tuple[int, ...]: + if not isinstance(value, list): + raise AnalysisError(f"{name} must be a list") + return tuple(_strict_nonnegative_int(item, name=name) for item in value) + + +@dataclass(frozen=True, slots=True) +class ProbabilityEstimate: + value: float + numerator: int + denominator: int + low: float + high: float + confidence: float = 0.95 + method: str = "wilson" + + def canonical_dict(self) -> dict[str, object]: + return { + "confidence": self.confidence, + "denominator": self.denominator, + "high": self.high, + "low": self.low, + "method": self.method, + "numerator": self.numerator, + "value": self.value, + } + + +@dataclass(frozen=True, slots=True) +class BootstrapInterval: + estimate: float + low: float + high: float + confidence: float + samples: int + seed: int + + def canonical_dict(self) -> dict[str, object]: + return { + "confidence": self.confidence, + "estimate": self.estimate, + "high": self.high, + "low": self.low, + "samples": self.samples, + "seed": self.seed, + } + + +@dataclass(frozen=True, slots=True) +class MetricAvailability: + state: str + reason: str | None + + def canonical_dict(self) -> dict[str, object]: + return {"reason": self.reason, "state": self.state} + + +@dataclass(frozen=True, slots=True) +class TrajectoryBand: + median: tuple[float, ...] + low: tuple[float, ...] + high: tuple[float, ...] + confidence: float + samples: int + seed: int + + def canonical_dict(self) -> dict[str, object]: + return { + "confidence": self.confidence, + "high": list(self.high), + "low": list(self.low), + "median": list(self.median), + "samples": self.samples, + "seed": self.seed, + } + + +@dataclass(frozen=True, slots=True) +class PairedSummary: + baseline: str + pair_count: int + cluster_count: int + success_probability_difference: BootstrapInterval + censored_query_difference: BootstrapInterval + total_shot_difference: BootstrapInterval + + def canonical_dict(self) -> dict[str, object]: + return { + "baseline": self.baseline, + "censored_query_difference": self.censored_query_difference.canonical_dict(), + "cluster_count": self.cluster_count, + "pair_count": self.pair_count, + "success_probability_difference": ( + self.success_probability_difference.canonical_dict() + ), + "total_shot_difference": self.total_shot_difference.canonical_dict(), + } + + +@dataclass(frozen=True, slots=True) +class StratumKey: + system_name: str + hilbert_dimension: int + segments: int + amplitude_bound: float + duration: float + search_dimension: int + gap: float + shots: int | None + + def sort_key(self) -> tuple[object, ...]: + return ( + self.system_name, + self.hilbert_dimension, + self.segments, + self.amplitude_bound, + self.duration, + self.search_dimension, + self.gap, + -1 if self.shots is None else self.shots, + ) + + def core_key(self) -> tuple[object, ...]: + return (*self.sort_key()[:5], self.gap, self.shots) + + def canonical_dict(self) -> dict[str, object]: + return { + "amplitude_bound": self.amplitude_bound, + "duration": self.duration, + "gap": self.gap, + "hilbert_dimension": self.hilbert_dimension, + "search_dimension": self.search_dimension, + "segments": self.segments, + "shots": self.shots, + "system_name": self.system_name, + } + + +@dataclass(frozen=True, slots=True) +class MethodSummary: + method: str + trial_count: int + failure_count: int + success_probability: ProbabilityEstimate + conditional_first_certified_queries: tuple[int, ...] + censored_first_certified_queries: tuple[int, ...] + total_shots: int + total_shots_by_trial: tuple[int, ...] + median_best_observed_infidelity_trajectory: tuple[float, ...] + metric_availability: MetricAvailability + principal_angle_availability: MetricAvailability + exact_infidelity_trajectory: TrajectoryBand | None + median_attained_infidelity_upper_bound: float | None + median_principal_angles: tuple[float, ...] | None + median_model_effective_ranks: tuple[float, ...] | None + median_truth_effective_ranks: tuple[float, ...] | None + median_signed_eigenvalue_gaps: tuple[float, ...] | None + + def canonical_dict(self) -> dict[str, object]: + return { + "conditional_first_certified_queries": list( + self.conditional_first_certified_queries + ), + "censored_first_certified_queries": list( + self.censored_first_certified_queries + ), + "exact_infidelity_trajectory": ( + None + if self.exact_infidelity_trajectory is None + else self.exact_infidelity_trajectory.canonical_dict() + ), + "failure_count": self.failure_count, + "median_attained_infidelity_upper_bound": ( + self.median_attained_infidelity_upper_bound + ), + "median_best_observed_infidelity_trajectory": list( + self.median_best_observed_infidelity_trajectory + ), + "median_model_effective_ranks": ( + None + if self.median_model_effective_ranks is None + else list(self.median_model_effective_ranks) + ), + "median_principal_angles": ( + None + if self.median_principal_angles is None + else list(self.median_principal_angles) + ), + "median_signed_eigenvalue_gaps": ( + None + if self.median_signed_eigenvalue_gaps is None + else list(self.median_signed_eigenvalue_gaps) + ), + "median_truth_effective_ranks": ( + None + if self.median_truth_effective_ranks is None + else list(self.median_truth_effective_ranks) + ), + "method": self.method, + "metric_availability": self.metric_availability.canonical_dict(), + "principal_angle_availability": ( + self.principal_angle_availability.canonical_dict() + ), + "success_probability": self.success_probability.canonical_dict(), + "total_shots": self.total_shots, + "total_shots_by_trial": list(self.total_shots_by_trial), + "trial_count": self.trial_count, + } + + +@dataclass(frozen=True, slots=True) +class StratumSummary: + key: StratumKey + methods: tuple[MethodSummary, ...] + paired_differences: tuple[PairedSummary, ...] + + def canonical_dict(self) -> dict[str, object]: + return { + "key": self.key.canonical_dict(), + "methods": [item.canonical_dict() for item in self.methods], + "paired_differences": [ + item.canonical_dict() for item in self.paired_differences + ], + } + + +@dataclass(frozen=True, slots=True) +class Summary: + strata: tuple[StratumSummary, ...] + bootstrap_confidence: float + bootstrap_samples: int + bootstrap_seed: int + schema_version: int = 1 + + def canonical_dict(self) -> dict[str, object]: + return { + "bootstrap_confidence": self.bootstrap_confidence, + "bootstrap_samples": self.bootstrap_samples, + "bootstrap_seed": self.bootstrap_seed, + "schema_version": self.schema_version, + "strata": [item.canonical_dict() for item in self.strata], + } + + @classmethod + def from_canonical_dict(cls, value: object) -> Summary: + payload = _mapping( + value, + { + "bootstrap_confidence", + "bootstrap_samples", + "bootstrap_seed", + "schema_version", + "strata", + }, + name="summary", + ) + if payload["schema_version"] != 1 or type(payload["schema_version"]) is not int: + raise AnalysisError("unsupported summary schema version") + confidence = _probability( + payload["bootstrap_confidence"], + name="bootstrap confidence", + ) + if confidence in {0.0, 1.0}: + raise AnalysisError("bootstrap confidence must be interior") + samples = _strict_positive_int( + payload["bootstrap_samples"], + name="bootstrap samples", + ) + seed = _strict_nonnegative_int(payload["bootstrap_seed"], name="bootstrap seed") + raw_strata = payload["strata"] + if not isinstance(raw_strata, list) or not raw_strata: + raise AnalysisError("summary strata must be a nonempty list") + strata = tuple(_parse_stratum(item) for item in raw_strata) + stratum_keys = tuple(item.key.sort_key() for item in strata) + if len(set(stratum_keys)) != len(stratum_keys): + raise AnalysisError("duplicate stratum key") + if tuple(sorted(strata, key=lambda item: item.key.sort_key())) != strata: + raise AnalysisError("summary strata must be sorted canonically") + for stratum in strata: + for method in stratum.methods: + if method.success_probability.confidence != confidence: + raise AnalysisError("method confidence does not match summary") + if method.exact_infidelity_trajectory is not None and ( + method.exact_infidelity_trajectory.confidence != confidence + or method.exact_infidelity_trajectory.samples != samples + ): + raise AnalysisError("trajectory bootstrap metadata is inconsistent") + for paired in stratum.paired_differences: + intervals = ( + paired.success_probability_difference, + paired.censored_query_difference, + paired.total_shot_difference, + ) + if any( + item.confidence != confidence or item.samples != samples + for item in intervals + ): + raise AnalysisError("paired bootstrap metadata is inconsistent") + summary = cls(strata, confidence, samples, seed) + if summary.canonical_dict() != dict(payload): + raise AnalysisError("summary is not canonical") + return summary + + +def _trial(value: object) -> TrialResult: + if isinstance(value, TrialResult): + return value + if not isinstance(value, Mapping): + raise AnalysisError("invalid trial: expected a TrialResult or mapping") + try: + return TrialResult.from_canonical_dict(value) + except (KeyError, TypeError, ValueError) as error: + raise AnalysisError(f"invalid trial: {error}") from error + + +def _strict_json_file(store: ArtifactStore, relative: str) -> object: + path = store.root / relative + + def unique_object(pairs: list[tuple[str, object]]) -> dict[str, object]: + result: dict[str, object] = {} + for key, value in pairs: + if key in result: + raise AnalysisError(f"duplicate JSON key {key!r} in {relative}") + result[key] = value + return result + + try: + raw = path.read_bytes() + payload = json.loads(raw.decode("utf-8"), object_pairs_hook=unique_object) + except AnalysisError: + raise + except (OSError, UnicodeDecodeError, json.JSONDecodeError) as error: + raise AnalysisError(f"invalid JSON artifact {relative}") from error + try: + canonical = canonical_json_bytes(payload) + except (TypeError, ValueError) as error: + raise AnalysisError(f"non-finite JSON artifact {relative}") from error + if raw != canonical: + raise AnalysisError(f"noncanonical JSON artifact {relative}") + return payload + + +def _result_mapping(value: object) -> Mapping[str, object]: + if isinstance(value, TrialResult): + return value.result + if not isinstance(value, Mapping): + raise AnalysisError("history must be a trial or result mapping") + if "result" in value: + return _trial(value).result + return value + + +def first_certified_query(history: object) -> int | None: + result = _result_mapping(history) + certified = result.get("certified") + query = result.get("first_certified_query") + crossings = result.get("provisional_crossings", []) + if type(certified) is not bool: + raise AnalysisError("certified must be a boolean") + if not isinstance(crossings, list) or any( + type(item) is not int or item <= 0 for item in crossings + ): + raise AnalysisError("provisional crossings must be positive integers") + if query is not None and (type(query) is not int or query <= 0): + raise AnalysisError("first certified query must be a positive integer or None") + if certified != (query is not None): + raise AnalysisError("certification state is inconsistent") + if query is not None and query not in crossings: + raise AnalysisError("certified query must occur in validation crossings") + return query + + +def _certified(value: object) -> bool: + return first_certified_query(value) is not None + + +def _wilson_bounds( + numerator: int, + denominator: int, + confidence: float, +) -> tuple[float, float]: + estimate = numerator / denominator + z = NormalDist().inv_cdf(0.5 + confidence / 2.0) + scale = 1.0 + z * z / denominator + center = (estimate + z * z / (2.0 * denominator)) / scale + radius = ( + z + * math.sqrt( + estimate * (1.0 - estimate) / denominator + + z * z / (4.0 * denominator * denominator) + ) + / scale + ) + return max(0.0, center - radius), min(1.0, center + radius) + + +def success_probability( + trials: Iterable[object], + *, + confidence: float = 0.95, +) -> ProbabilityEstimate: + materialized = tuple(trials) + if not materialized: + raise AnalysisError("success probability requires at least one trial") + confidence_value = _finite_number(confidence, name="binomial confidence") + if not 0.0 < confidence_value < 1.0: + raise AnalysisError("binomial confidence must be interior") + successes = sum(_certified(trial) for trial in materialized) + count = len(materialized) + estimate = successes / count + low, high = _wilson_bounds(successes, count, confidence_value) + return ProbabilityEstimate( + value=estimate, + numerator=successes, + denominator=count, + low=low, + high=high, + confidence=confidence_value, + ) + + +def _bootstrap_estimates( + values: np.ndarray, + *, + seed: int, + samples: int, + chunk_size: int, + statistic: Callable[[np.ndarray], np.ndarray], +) -> np.ndarray: + rng = np.random.default_rng(seed) + estimates = np.empty(samples, dtype=np.float64) + for start in range(0, samples, chunk_size): + stop = min(samples, start + chunk_size) + indices = rng.integers(0, values.size, size=(stop - start, values.size)) + estimates[start:stop] = statistic(values[indices]) + return estimates + + +def paired_bootstrap_ci( + differences: Sequence[float], + *, + seed: int, + samples: int = 10_000, + confidence: float = 0.95, + chunk_size: int = 256, +) -> BootstrapInterval: + seed_value = _strict_nonnegative_int(seed, name="bootstrap seed") + sample_count = _strict_positive_int(samples, name="bootstrap samples") + chunk = _strict_positive_int(chunk_size, name="bootstrap chunk size") + confidence_value = _finite_number(confidence, name="bootstrap confidence") + if not 0.0 < confidence_value < 1.0: + raise AnalysisError("bootstrap confidence must be interior") + values = np.asarray( + [_finite_number(value, name="paired difference") for value in differences], + dtype=np.float64, + ) + if values.size == 0: + raise AnalysisError("paired bootstrap requires at least one difference") + estimates = _bootstrap_estimates( + values, + seed=seed_value, + samples=sample_count, + chunk_size=chunk, + statistic=lambda selected: np.mean(selected, axis=1), + ) + alpha = (1.0 - confidence_value) / 2.0 + low, high = np.quantile(estimates, [alpha, 1.0 - alpha]) + return BootstrapInterval( + estimate=float(np.mean(values)), + low=float(low), + high=float(high), + confidence=confidence_value, + samples=sample_count, + seed=seed_value, + ) + + +def _hilbert_dimension(system_name: str) -> int: + dimensions = {"one_qubit": 2, "two_qubit": 4} + try: + return dimensions[system_name] + except KeyError as error: + raise AnalysisError(f"unsupported system {system_name!r}") from error + + +def _stratum_key(trial: TrialResult, *, dimension: int | None = None) -> StratumKey: + system = trial.config["system"] + device = trial.config["device"] + return StratumKey( + system_name=str(system["name"]), + hilbert_dimension=_hilbert_dimension(str(system["name"])), + segments=int(system["segments"]), + amplitude_bound=float(system["amplitude_bound"]), + duration=float(system["duration"]), + search_dimension=( + int(trial.config["search"]["dimension"]) + if dimension is None + else dimension + ), + gap=float(device["gap"]), + shots=device["shots"], + ) + + +def _cluster_identity(trial: TrialResult) -> tuple[object, ...]: + return ( + trial.device_id, + trial.config["device"]["perturbation_seed"], + trial.config["trial_seed"], + ) + + +def _pair_identity(trial: TrialResult) -> tuple[object, ...]: + return (*_stratum_key(trial).core_key(), *_cluster_identity(trial)) + + +def pair_trials( + trials: Iterable[TrialResult | Mapping[str, object]], + *, + reference_method: str = "model_hessian", +) -> dict[str, tuple[tuple[TrialResult, TrialResult], ...]]: + records = tuple(_trial(value) for value in trials) + references = sorted( + ( + item + for item in records + if item.config["search"]["method"] == reference_method + ), + key=lambda item: ( + item.config["search"]["dimension"], + repr(_cluster_identity(item)), + ), + ) + methods = {str(item.config["search"]["method"]) for item in records} + result: dict[str, tuple[tuple[TrialResult, TrialResult], ...]] = {} + for baseline in sorted(methods - {reference_method}): + candidates = [ + item for item in records if item.config["search"]["method"] == baseline + ] + def pairing_key(item: TrialResult) -> tuple[object, ...]: + base = _pair_identity(item) + return ( + base + if baseline == "full" + else (*base, item.config["search"]["dimension"]) + ) + + by_identity = {pairing_key(item): item for item in candidates} + if len(by_identity) != len(candidates): + raise AnalysisError(f"duplicate pair coverage for {baseline!r}") + pairs: list[tuple[TrialResult, TrialResult]] = [] + used: set[str] = set() + for reference in references: + reference_key = ( + _pair_identity(reference) + if baseline == "full" + else ( + *_pair_identity(reference), + reference.config["search"]["dimension"], + ) + ) + match = by_identity.get(reference_key) + if match is None: + raise AnalysisError( + f"pair coverage mismatch for {baseline!r} and " + f"{reference.trial_id!r}" + ) + if ( + baseline != "full" + and match.config["search"]["dimension"] + != reference.config["search"]["dimension"] + ): + raise AnalysisError(f"pair dimension mismatch for {baseline!r}") + pairs.append((reference, match)) + used.add(match.trial_id) + if len(used) != len(candidates) or ( + baseline != "full" and len(pairs) != len(candidates) + ): + raise AnalysisError(f"unmatched pair coverage for {baseline!r}") + result[baseline] = tuple(pairs) + return result + + +def _censored_query(trial: TrialResult) -> int: + query = first_certified_query(trial) + return int(trial.result["budget"]) if query is None else query + + +def _extend(values: Sequence[float], length: int) -> tuple[float, ...]: + if not values: + raise AnalysisError("trajectory cannot be empty") + numeric = tuple(_probability(item, name="trajectory value") for item in values) + if len(numeric) > length: + raise AnalysisError("trajectory exceeds declared budget") + return (*numeric, *((numeric[-1],) * (length - len(numeric)))) + + +def _best_observed_trajectory(trial: TrialResult) -> tuple[float, ...]: + budget = int(trial.result["budget"]) + by_query: dict[int, float] = {} + for observation in trial.result["observations"]: + query = int(observation["optimizer_query_index"]) + infidelity = 1.0 - _probability( + observation["estimate"], + name="observation estimate", + ) + by_query[query] = min(by_query.get(query, 1.0), infidelity) + best = 1.0 + values: list[float] = [] + for query in range(1, budget + 1): + if query in by_query: + best = min(best, by_query[query]) + values.append(best) + return tuple(values) + + +def _median_rows(rows: Sequence[Sequence[float]]) -> tuple[float, ...]: + matrix = np.asarray(rows, dtype=np.float64) + if matrix.ndim != 2 or not np.all(np.isfinite(matrix)): + raise AnalysisError("metric rows must form a finite matrix") + return tuple(float(item) for item in np.median(matrix, axis=0)) + + +def _derived_seed(seed: int, *parts: object) -> int: + digest = hashlib.sha256(canonical_json_bytes([seed, *parts])).digest() + return int.from_bytes(digest[:8], "big") + + +def _trajectory_band( + rows: Sequence[Sequence[float]], + *, + seed: int, + samples: int, + confidence: float, + chunk_size: int, +) -> TrajectoryBand: + matrix = np.asarray(rows, dtype=np.float64) + if matrix.ndim != 2 or matrix.shape[0] == 0 or not np.all(np.isfinite(matrix)): + raise AnalysisError("exact trajectories must form a finite matrix") + alpha = (1.0 - confidence) / 2.0 + lows: list[float] = [] + highs: list[float] = [] + for column in range(matrix.shape[1]): + estimates = _bootstrap_estimates( + matrix[:, column], + seed=seed, + samples=samples, + chunk_size=chunk_size, + statistic=lambda selected: np.median(selected, axis=1), + ) + low, high = np.quantile(estimates, [alpha, 1.0 - alpha]) + lows.append(float(low)) + highs.append(float(high)) + return TrajectoryBand( + median=tuple(float(item) for item in np.median(matrix, axis=0)), + low=tuple(lows), + high=tuple(highs), + confidence=confidence, + samples=samples, + seed=seed, + ) + + +def _geometry_records_agree( + left: Mapping[str, object], + right: Mapping[str, object], +) -> bool: + if set(left) != set(right): + return False + for name in ( + "model_effective_ranks", + "principal_angles_radians", + "rank_thresholds", + "signed_leading_eigenvalue_gaps", + "truth_effective_ranks", + ): + left_values = left[name] + right_values = right[name] + if ( + not isinstance(left_values, list) + or not isinstance(right_values, list) + or len(left_values) != len(right_values) + or any( + abs(float(first) - float(second)) > _CANONICAL_TOLERANCE + for first, second in zip(left_values, right_values, strict=True) + ) + ): + return False + return all( + left[name] == right[name] + for name in ( + "model_top_subspace_sha256", + "truth_top_subspace_sha256", + ) + ) + + +def _target_geometry_records( + records: Sequence[TrialResult], + *, + target_dimension: int, +) -> dict[tuple[object, ...], Mapping[str, object]]: + by_cluster: dict[tuple[object, ...], Mapping[str, object]] = {} + for item in records: + if ( + item.config["search"]["method"] == "full" + or item.config["search"]["dimension"] != target_dimension + or "derived_metrics" not in item.result + ): + continue + geometry = item.result["derived_metrics"]["geometry"] + identity = _cluster_identity(item) + existing = by_cluster.get(identity) + if existing is not None and not _geometry_records_agree(existing, geometry): + raise AnalysisError("target-k geometry conflict within paired cluster") + by_cluster[identity] = geometry + return by_cluster + + +def _method_summary( + records: Sequence[TrialResult], + *, + target_dimension: int, + target_geometry_records: Mapping[ + tuple[object, ...], + Mapping[str, object], + ], + bootstrap_seed: int, + bootstrap_samples: int, + confidence: float, + chunk_size: int, +) -> MethodSummary: + method = str(records[0].config["search"]["method"]) + probability = success_probability(records, confidence=confidence) + budgets = {int(item.result["budget"]) for item in records} + if len(budgets) != 1: + raise AnalysisError("method trajectories require one common budget") + observed = _median_rows([_best_observed_trajectory(item) for item in records]) + has_metrics = ["derived_metrics" in item.result for item in records] + if any(has_metrics) and not all(has_metrics): + raise AnalysisError("metric availability cannot be mixed within a method") + if not all(has_metrics): + if any(item.config["run_kind"] == "production" for item in records): + raise AnalysisError("production trials require schema-v3 derived metrics") + availability = MetricAvailability( + "unavailable", + "schema_v3_metrics_not_available", + ) + angle_availability = MetricAvailability( + "unavailable", + "schema_v3_metrics_not_available", + ) + exact_band = None + attained = None + angles = None + model_ranks = None + truth_ranks = None + gaps = None + else: + availability = MetricAvailability("available", None) + budget = next(iter(budgets)) + exact_rows = [ + _extend( + item.result["derived_metrics"]["exact_infidelity"][ + "cumulative_best_by_optimizer_query" + ], + budget, + ) + for item in records + ] + exact_band = _trajectory_band( + exact_rows, + seed=_derived_seed(bootstrap_seed, method, "exact"), + samples=bootstrap_samples, + confidence=confidence, + chunk_size=chunk_size, + ) + restricted = [ + item.result["derived_metrics"]["restricted_noiseless_optimization"] + for item in records + ] + geometry = [item.result["derived_metrics"]["geometry"] for item in records] + attained = float( + np.median( + [ + item["attained_infidelity_upper_bound"] + for item in restricted + ] + ) + ) + if method == "full": + matched_angles = [ + ( + None + if (record := target_geometry_records.get( + _cluster_identity(item) + )) + is None + else tuple( + float(value) + for value in record["principal_angles_radians"] + ) + ) + for item in records + ] + if any(item is None for item in matched_angles): + if any(item.config["run_kind"] == "production" for item in records): + raise AnalysisError( + "missing target-k principal angles for full comparator" + ) + angle_availability = MetricAvailability( + "unavailable", + "target_k_geometry_not_available", + ) + angles = None + else: + angle_availability = MetricAvailability("available", None) + angles = _median_rows( + [ + item + for item in matched_angles + if item is not None + ] + ) + else: + angle_availability = MetricAvailability("available", None) + angles = _median_rows( + [item["principal_angles_radians"] for item in geometry] + ) + model_ranks = _median_rows( + [item["model_effective_ranks"] for item in geometry] + ) + truth_ranks = _median_rows( + [item["truth_effective_ranks"] for item in geometry] + ) + gaps = _median_rows( + [ + item["signed_leading_eigenvalue_gaps"][:target_dimension] + for item in geometry + ] + ) + conditional = tuple(sorted( + query + for item in records + if (query := first_certified_query(item)) is not None + )) + censored = tuple(sorted(_censored_query(item) for item in records)) + shots_by_trial = tuple( + sorted(int(item.ledger["total_shots"]) for item in records) + ) + return MethodSummary( + method=method, + trial_count=len(records), + failure_count=len(records) - probability.numerator, + success_probability=probability, + conditional_first_certified_queries=conditional, + censored_first_certified_queries=censored, + total_shots=sum(shots_by_trial), + total_shots_by_trial=shots_by_trial, + median_best_observed_infidelity_trajectory=observed, + metric_availability=availability, + principal_angle_availability=angle_availability, + exact_infidelity_trajectory=exact_band, + median_attained_infidelity_upper_bound=attained, + median_principal_angles=angles, + median_model_effective_ranks=model_ranks, + median_truth_effective_ranks=truth_ranks, + median_signed_eigenvalue_gaps=gaps, + ) + + +def _paired_summaries( + records: Sequence[TrialResult], + *, + bootstrap_seed: int, + bootstrap_samples: int, + confidence: float, + chunk_size: int, + reference_method: str = "model_hessian", +) -> tuple[PairedSummary, ...]: + by_method: dict[str, dict[tuple[object, ...], TrialResult]] = {} + for item in records: + method = str(item.config["search"]["method"]) + identity = _cluster_identity(item) + target = by_method.setdefault(method, {}) + if identity in target: + raise AnalysisError(f"duplicate cluster unit for {method!r}") + target[identity] = item + references = by_method.get(reference_method) + if references is None: + if len(by_method) > 1: + raise AnalysisError(f"pair coverage has no {reference_method!r} trials") + return () + result: list[PairedSummary] = [] + for baseline in sorted(set(by_method) - {reference_method}): + comparisons = by_method[baseline] + if comparisons.keys() != references.keys(): + raise AnalysisError(f"pair cluster coverage mismatch for {baseline!r}") + identities = sorted(references, key=repr) + success = [ + float(_certified(references[key])) - float(_certified(comparisons[key])) + for key in identities + ] + queries = [ + float(_censored_query(comparisons[key]) - _censored_query(references[key])) + for key in identities + ] + shots = [ + float( + comparisons[key].ledger["total_shots"] + - references[key].ledger["total_shots"] + ) + for key in identities + ] + seed = _derived_seed(bootstrap_seed, baseline) + result.append( + PairedSummary( + baseline=baseline, + pair_count=len(identities), + cluster_count=len(identities), + success_probability_difference=paired_bootstrap_ci( + success, + seed=_derived_seed(seed, "success"), + samples=bootstrap_samples, + confidence=confidence, + chunk_size=chunk_size, + ), + censored_query_difference=paired_bootstrap_ci( + queries, + seed=_derived_seed(seed, "query"), + samples=bootstrap_samples, + confidence=confidence, + chunk_size=chunk_size, + ), + total_shot_difference=paired_bootstrap_ci( + shots, + seed=_derived_seed(seed, "shots"), + samples=bootstrap_samples, + confidence=confidence, + chunk_size=chunk_size, + ), + ) + ) + return tuple(result) + + +def _stratify(records: Sequence[TrialResult]) -> tuple[tuple[StratumKey, tuple[TrialResult, ...]], ...]: + target_keys = { + _stratum_key(item) + for item in records + if item.config["search"]["method"] != "full" + } + if not target_keys: + target_keys = {_stratum_key(item) for item in records} + strata: list[tuple[StratumKey, tuple[TrialResult, ...]]] = [] + for key in sorted(target_keys, key=StratumKey.sort_key): + selected = tuple( + item + for item in records + if _stratum_key(item).core_key() == key.core_key() + and ( + item.config["search"]["method"] == "full" + or item.config["search"]["dimension"] == key.search_dimension + ) + ) + if selected: + strata.append((key, selected)) + return tuple(strata) + + +def analyze_trials( + trials: Iterable[TrialResult | Mapping[str, object]], + *, + bootstrap_seed: int = 0, + bootstrap_samples: int = 10_000, + bootstrap_confidence: float = 0.95, + bootstrap_chunk_size: int = 256, +) -> Summary: + records = tuple(_trial(value) for value in trials) + if not records: + raise AnalysisError("analysis requires at least one trial") + if len({item.trial_id for item in records}) != len(records): + raise AnalysisError("duplicate trial coverage") + seed = _strict_nonnegative_int(bootstrap_seed, name="bootstrap seed") + samples = _strict_positive_int(bootstrap_samples, name="bootstrap samples") + chunk = _strict_positive_int(bootstrap_chunk_size, name="bootstrap chunk size") + confidence = _finite_number( + bootstrap_confidence, + name="bootstrap confidence", + ) + if not 0.0 < confidence < 1.0: + raise AnalysisError("bootstrap confidence must be interior") + summaries: list[StratumSummary] = [] + for key, selected in _stratify(records): + target_geometry = _target_geometry_records( + selected, + target_dimension=key.search_dimension, + ) + grouped: dict[str, list[TrialResult]] = {} + for item in selected: + grouped.setdefault(str(item.config["search"]["method"]), []).append(item) + methods = tuple( + _method_summary( + tuple( + sorted( + grouped[method], + key=lambda item: repr(_cluster_identity(item)), + ) + ), + target_dimension=key.search_dimension, + target_geometry_records=target_geometry, + bootstrap_seed=_derived_seed(seed, key.canonical_dict(), method), + bootstrap_samples=samples, + confidence=confidence, + chunk_size=chunk, + ) + for method in sorted(grouped) + ) + summaries.append( + StratumSummary( + key=key, + methods=methods, + paired_differences=_paired_summaries( + selected, + bootstrap_seed=_derived_seed(seed, key.canonical_dict(), "paired"), + bootstrap_samples=samples, + confidence=confidence, + chunk_size=chunk, + ), + ) + ) + return Summary(tuple(summaries), confidence, samples, seed) + + +def aggregate_run( + store: ArtifactStore, + *, + bootstrap_seed: int = 0, + bootstrap_samples: int = 10_000, + bootstrap_confidence: float = 0.95, + bootstrap_chunk_size: int = 256, +) -> Summary: + if not isinstance(store, ArtifactStore): + raise AnalysisError("store must be an ArtifactStore") + try: + _strict_json_file(store, "ready.json") + _strict_json_file(store, "plan.json") + _strict_json_file(store, "index.json") + specs = read_plan(store) + report = validate_sweep(specs, store) + except (ArtifactConflict, TypeError, ValueError) as error: + raise AnalysisError(f"invalid aggregate coverage: {error}") from error + if not specs or not report.valid or report.status.completed != report.status.expected: + detail = "; ".join(report.errors) if report.errors else "empty or incomplete plan" + raise AnalysisError(f"invalid aggregate coverage: {detail}") + records: list[TrialResult] = [] + for spec in specs: + try: + payload: Any = _strict_json_file( + store, + f"trials/{spec.trial_id}.json", + ) + trial = _trial(payload) + except (ArtifactConflict, AnalysisError) as error: + raise AnalysisError(f"invalid trial coverage for {spec.trial_id}: {error}") from error + if ( + trial.trial_id != spec.trial_id + or trial.device_id != spec.device_id + or trial.observation_stream_id != spec.observation_stream_id + or trial.config != spec.config.canonical_dict() + ): + raise AnalysisError(f"trial identity mismatch for {spec.trial_id}") + records.append(trial) + return analyze_trials( + records, + bootstrap_seed=bootstrap_seed, + bootstrap_samples=bootstrap_samples, + bootstrap_confidence=bootstrap_confidence, + bootstrap_chunk_size=bootstrap_chunk_size, + ) + + +def _parse_probability(value: object) -> ProbabilityEstimate: + payload = _mapping( + value, + {"confidence", "denominator", "high", "low", "method", "numerator", "value"}, + name="success probability", + ) + numerator = _strict_nonnegative_int(payload["numerator"], name="numerator") + denominator = _strict_positive_int(payload["denominator"], name="denominator") + estimate = _probability(payload["value"], name="probability estimate") + low = _probability(payload["low"], name="probability low") + high = _probability(payload["high"], name="probability high") + confidence = _probability(payload["confidence"], name="probability confidence") + expected_low, expected_high = _wilson_bounds( + numerator, + denominator, + confidence, + ) + if ( + numerator > denominator + or estimate != numerator / denominator + or low > high + or payload["method"] != "wilson" + or confidence in {0.0, 1.0} + or abs(low - expected_low) > _CANONICAL_TOLERANCE + or abs(high - expected_high) > _CANONICAL_TOLERANCE + ): + raise AnalysisError("success probability is inconsistent") + return ProbabilityEstimate( + estimate, + numerator, + denominator, + low, + high, + confidence, + ) + + +def _parse_interval(value: object) -> BootstrapInterval: + payload = _mapping( + value, + {"confidence", "estimate", "high", "low", "samples", "seed"}, + name="bootstrap interval", + ) + low = _finite_number(payload["low"], name="bootstrap low") + high = _finite_number(payload["high"], name="bootstrap high") + if low > high: + raise AnalysisError("bootstrap interval bounds are reversed") + confidence = _probability(payload["confidence"], name="bootstrap confidence") + if confidence in {0.0, 1.0}: + raise AnalysisError("bootstrap confidence must be interior") + return BootstrapInterval( + _finite_number(payload["estimate"], name="bootstrap estimate"), + low, + high, + confidence, + _strict_positive_int(payload["samples"], name="bootstrap samples"), + _strict_nonnegative_int(payload["seed"], name="bootstrap seed"), + ) + + +def _parse_availability(value: object) -> MetricAvailability: + payload = _mapping(value, {"reason", "state"}, name="metric availability") + state = payload["state"] + reason = payload["reason"] + if state == "available": + if reason is not None: + raise AnalysisError("available metrics cannot have a reason") + elif state == "unavailable": + if not isinstance(reason, str) or not reason: + raise AnalysisError("unavailable metrics require a reason") + else: + raise AnalysisError("invalid metric availability state") + return MetricAvailability(state, reason) + + +def _parse_band(value: object) -> TrajectoryBand: + payload = _mapping( + value, + {"confidence", "high", "low", "median", "samples", "seed"}, + name="trajectory band", + ) + median = _number_tuple(payload["median"], name="trajectory median") + low = _number_tuple(payload["low"], name="trajectory low") + high = _number_tuple(payload["high"], name="trajectory high") + if not median or len(low) != len(median) or len(high) != len(median): + raise AnalysisError("trajectory band dimensions are inconsistent") + if any(not 0.0 <= item <= 1.0 for item in (*median, *low, *high)): + raise AnalysisError("trajectory band values must be probabilities") + if any( + lower > center or center > upper + for lower, center, upper in zip(low, median, high, strict=True) + ): + raise AnalysisError("trajectory band does not contain its median") + confidence = _probability(payload["confidence"], name="trajectory confidence") + if confidence in {0.0, 1.0}: + raise AnalysisError("trajectory confidence must be interior") + return TrajectoryBand( + median, + low, + high, + confidence, + _strict_positive_int(payload["samples"], name="trajectory samples"), + _strict_nonnegative_int(payload["seed"], name="trajectory seed"), + ) + + +def _parse_key(value: object) -> StratumKey: + payload = _mapping( + value, + { + "amplitude_bound", + "duration", + "gap", + "hilbert_dimension", + "search_dimension", + "segments", + "shots", + "system_name", + }, + name="stratum key", + ) + if not isinstance(payload["system_name"], str) or not payload["system_name"]: + raise AnalysisError("stratum system_name must be a string") + shots = payload["shots"] + if shots is not None: + shots = _strict_positive_int(shots, name="stratum shots") + key = StratumKey( + payload["system_name"], + _strict_positive_int(payload["hilbert_dimension"], name="hilbert_dimension"), + _strict_positive_int(payload["segments"], name="segments"), + _finite_number(payload["amplitude_bound"], name="amplitude_bound"), + _finite_number(payload["duration"], name="duration"), + _strict_positive_int(payload["search_dimension"], name="search_dimension"), + _finite_number(payload["gap"], name="gap"), + shots, + ) + if key.hilbert_dimension != _hilbert_dimension(key.system_name): + raise AnalysisError("stratum system and Hilbert dimension disagree") + return key + + +def _optional_tuple(value: object, *, name: str) -> tuple[float, ...] | None: + return None if value is None else _number_tuple(value, name=name) + + +def _parse_method(value: object) -> MethodSummary: + fields = { + "conditional_first_certified_queries", + "censored_first_certified_queries", + "exact_infidelity_trajectory", + "failure_count", + "median_attained_infidelity_upper_bound", + "median_best_observed_infidelity_trajectory", + "median_model_effective_ranks", + "median_principal_angles", + "median_signed_eigenvalue_gaps", + "median_truth_effective_ranks", + "method", + "metric_availability", + "principal_angle_availability", + "success_probability", + "total_shots", + "total_shots_by_trial", + "trial_count", + } + payload = _mapping(value, fields, name="method summary") + method = payload["method"] + if not isinstance(method, str) or not method: + raise AnalysisError("method must be a string") + trial_count = _strict_positive_int(payload["trial_count"], name="trial_count") + failure_count = _strict_nonnegative_int( + payload["failure_count"], + name="failure_count", + ) + probability = _parse_probability(payload["success_probability"]) + availability = _parse_availability(payload["metric_availability"]) + angle_availability = _parse_availability( + payload["principal_angle_availability"] + ) + exact = ( + None + if payload["exact_infidelity_trajectory"] is None + else _parse_band(payload["exact_infidelity_trajectory"]) + ) + attained_raw = payload["median_attained_infidelity_upper_bound"] + attained = ( + None + if attained_raw is None + else _probability(attained_raw, name="median attained upper bound") + ) + optional = ( + exact, + attained, + payload["median_model_effective_ranks"], + payload["median_truth_effective_ranks"], + payload["median_signed_eigenvalue_gaps"], + ) + if availability.state == "available" and any(item is None for item in optional): + raise AnalysisError("available metrics must be complete") + if availability.state == "unavailable" and any(item is not None for item in optional): + raise AnalysisError("unavailable metrics must not contain values") + if probability.denominator != trial_count or failure_count != ( + trial_count - probability.numerator + ): + raise AnalysisError("method trial counts are inconsistent") + conditional = _integer_tuple( + payload["conditional_first_certified_queries"], + name="conditional queries", + ) + censored = _integer_tuple( + payload["censored_first_certified_queries"], + name="censored queries", + ) + observed = _number_tuple( + payload["median_best_observed_infidelity_trajectory"], + name="observed trajectory", + ) + shots_by_trial = _integer_tuple( + payload["total_shots_by_trial"], + name="total shots by trial", + ) + if ( + any(item <= 0 for item in (*conditional, *censored)) + or len(censored) != trial_count + or len(conditional) != probability.numerator + or tuple(sorted(conditional)) != conditional + or tuple(sorted(censored)) != censored + or not observed + or any(not 0.0 <= item <= 1.0 for item in observed) + or len(shots_by_trial) != trial_count + or tuple(sorted(shots_by_trial)) != shots_by_trial + ): + raise AnalysisError("method trajectories or query counts are inconsistent") + angles = _optional_tuple( + payload["median_principal_angles"], + name="principal angles", + ) + if ( + (angle_availability.state == "available") != (angles is not None) + or ( + availability.state == "unavailable" + and angle_availability.state != "unavailable" + ) + ): + raise AnalysisError("principal-angle availability is inconsistent") + model_ranks = _optional_tuple( + payload["median_model_effective_ranks"], + name="model ranks", + ) + truth_ranks = _optional_tuple( + payload["median_truth_effective_ranks"], + name="truth ranks", + ) + gaps = _optional_tuple( + payload["median_signed_eigenvalue_gaps"], + name="signed gaps", + ) + if availability.state == "available" and ( + exact is None + or len(exact.median) != len(observed) + or model_ranks is None + or truth_ranks is None + or gaps is None + or len(model_ranks) != 3 + or len(truth_ranks) != 3 + or any(item < 0.0 for item in (*model_ranks, *truth_ranks)) + or ( + angles is not None + and any(not 0.0 <= item <= math.pi / 2.0 for item in angles) + ) + ): + raise AnalysisError("available metric dimensions are inconsistent") + total_shots = _strict_nonnegative_int(payload["total_shots"], name="total_shots") + if total_shots != sum(shots_by_trial): + raise AnalysisError("method shot totals are inconsistent") + return MethodSummary( + method, + trial_count, + failure_count, + probability, + conditional, + censored, + total_shots, + shots_by_trial, + observed, + availability, + angle_availability, + exact, + attained, + angles, + model_ranks, + truth_ranks, + gaps, + ) + + +def _parse_paired(value: object) -> PairedSummary: + payload = _mapping( + value, + { + "baseline", + "censored_query_difference", + "cluster_count", + "pair_count", + "success_probability_difference", + "total_shot_difference", + }, + name="paired summary", + ) + if not isinstance(payload["baseline"], str) or not payload["baseline"]: + raise AnalysisError("paired baseline must be a string") + pair_count = _strict_positive_int(payload["pair_count"], name="pair_count") + cluster_count = _strict_positive_int( + payload["cluster_count"], + name="cluster_count", + ) + if pair_count != cluster_count: + raise AnalysisError("paired bootstrap must contain one row per cluster") + return PairedSummary( + payload["baseline"], + pair_count, + cluster_count, + _parse_interval(payload["success_probability_difference"]), + _parse_interval(payload["censored_query_difference"]), + _parse_interval(payload["total_shot_difference"]), + ) + + +def _parse_stratum(value: object) -> StratumSummary: + payload = _mapping( + value, + {"key", "methods", "paired_differences"}, + name="stratum", + ) + raw_methods = payload["methods"] + raw_pairs = payload["paired_differences"] + if not isinstance(raw_methods, list) or not raw_methods: + raise AnalysisError("stratum methods must be a nonempty list") + if not isinstance(raw_pairs, list): + raise AnalysisError("paired differences must be a list") + methods = tuple(_parse_method(item) for item in raw_methods) + pairs = tuple(_parse_paired(item) for item in raw_pairs) + method_names = tuple(item.method for item in methods) + baseline_names = tuple(item.baseline for item in pairs) + if len(set(method_names)) != len(method_names): + raise AnalysisError("duplicate method name") + if len(set(baseline_names)) != len(baseline_names): + raise AnalysisError("duplicate paired baseline name") + if tuple(sorted(methods, key=lambda item: item.method)) != methods: + raise AnalysisError("stratum methods must be sorted") + if tuple(sorted(pairs, key=lambda item: item.baseline)) != pairs: + raise AnalysisError("paired differences must be sorted") + key = _parse_key(payload["key"]) + if any( + ( + item.metric_availability.state == "available" + and ( + item.median_signed_eigenvalue_gaps is None + or len(item.median_signed_eigenvalue_gaps) + != key.search_dimension + ) + ) + or ( + item.principal_angle_availability.state == "available" + and ( + item.median_principal_angles is None + or len(item.median_principal_angles) != key.search_dimension + ) + ) + for item in methods + ): + raise AnalysisError("geometry metrics do not match stratum dimension") + method_name_set = set(method_names) + if any(item.baseline not in method_name_set for item in pairs): + raise AnalysisError("paired baseline is absent from method summaries") + return StratumSummary(key, methods, pairs) diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/src/qcontrol/artifacts.py b/tracks/qcs/solutions/frustration-free/challenge-113/src/qcontrol/artifacts.py new file mode 100644 index 000000000..3e5508319 --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/src/qcontrol/artifacts.py @@ -0,0 +1,973 @@ +from __future__ import annotations + +from collections.abc import Callable, Iterator, Mapping +from contextlib import AbstractContextManager, contextmanager +from dataclasses import dataclass +import fcntl +import hashlib +import importlib.metadata +import json +import os +from pathlib import Path +import platform +import re +import socket +import subprocess +import threading +import time +from typing import Any +import uuid + +import jax + +from qcontrol.config import ExperimentConfig + + +_TOKEN = re.compile(r"[a-z0-9][a-z0-9_-]*\Z", re.ASCII) +_THREAD_LOCKS_GUARD = threading.Lock() +_THREAD_LOCKS: dict[str, threading.RLock] = {} +_OWNER_FIELDS = { + "boot_id", + "hostname", + "lease_expires_at", + "nonce", + "pid", + "process_start_id", + "started_at", +} + + +class ArtifactConflict(RuntimeError): + """Persisted state cannot safely be reused or replaced.""" + + +class TrialClaimConflict(ArtifactConflict): + """A trial has an active or unverifiable owner.""" + + +class ArtifactDurabilityError(ArtifactConflict): + """A replace or rollback reached an uncertain durability boundary.""" + + def __init__( + self, + message: str, + *, + old_sha256: str | None, + new_sha256: str, + present: str, + ) -> None: + self.old_sha256 = old_sha256 + self.new_sha256 = new_sha256 + self.present = present + super().__init__( + f"{message}; present={present}, old_sha256={old_sha256}, " + f"new_sha256={new_sha256}" + ) + + +def canonical_json_bytes(payload: object) -> bytes: + return ( + json.dumps( + payload, + allow_nan=False, + ensure_ascii=False, + separators=(",", ":"), + sort_keys=True, + ) + + "\n" + ).encode("utf-8") + + +class _DuplicateJSONKey(ValueError): + pass + + +def _unique_object(pairs: list[tuple[str, object]]) -> dict[str, object]: + result: dict[str, object] = {} + for key, value in pairs: + if key in result: + raise _DuplicateJSONKey(key) + result[key] = value + return result + + +def _parse_canonical_json_bytes(data: bytes) -> object: + try: + text = data.decode("utf-8") + payload = json.loads(text, object_pairs_hook=_unique_object) + except _DuplicateJSONKey as error: + raise ArtifactConflict( + f"orphan trial JSON contains duplicate key {error.args[0]!r}" + ) from None + except (UnicodeDecodeError, json.JSONDecodeError): + raise ArtifactConflict("orphan trial JSON is noncanonical") from None + try: + canonical = canonical_json_bytes(payload) + except (TypeError, ValueError): + raise ArtifactConflict("orphan trial JSON is noncanonical") from None + if data != canonical: + raise ArtifactConflict("orphan trial JSON bytes are noncanonical") + return payload + + +def _sha256(data: bytes) -> str: + return hashlib.sha256(data).hexdigest() + + +def _file_sha256(path: Path) -> str: + digest = hashlib.sha256() + descriptor = os.open(path, os.O_RDONLY | getattr(os, "O_NOFOLLOW", 0)) + try: + with os.fdopen(descriptor, "rb", closefd=False) as stream: + for block in iter(lambda: stream.read(1024 * 1024), b""): + digest.update(block) + finally: + os.close(descriptor) + return digest.hexdigest() + + +def _strict_token(value: object, *, name: str) -> str: + if not isinstance(value, str) or _TOKEN.fullmatch(value) is None: + raise ValueError(f"{name} must be a strict lowercase ASCII token") + return value + + +def _safe_relative_path(value: str | os.PathLike[str]) -> Path: + raw = os.fspath(value) + if not isinstance(raw, str) or "\\" in raw or "\0" in raw: + raise ValueError("artifact path must be a nonempty relative path") + path = Path(value) + if ( + path.is_absolute() + or not path.parts + or any(part in {"", ".", ".."} for part in path.parts) + ): + raise ValueError("artifact path must be a nonempty relative path") + return path + + +def _git_state(root: Path) -> dict[str, object]: + def run_text(*arguments: str) -> str: + result = subprocess.run( + ["git", *arguments], + cwd=root, + check=True, + capture_output=True, + text=True, + ) + return result.stdout.strip() + + try: + revision = run_text("rev-parse", "HEAD") + repository = Path(run_text("rev-parse", "--show-toplevel")) + status = subprocess.run( + ["git", "status", "--porcelain=v1", "-z", "--untracked-files=all"], + cwd=root, + check=True, + capture_output=True, + ).stdout + tracked_diff = subprocess.run( + ["git", "diff", "--binary", "HEAD", "--"], + cwd=root, + check=True, + capture_output=True, + ).stdout + untracked = subprocess.run( + ["git", "ls-files", "--others", "--exclude-standard", "-z"], + cwd=root, + check=True, + capture_output=True, + ).stdout.split(b"\0") + digest = hashlib.sha256() + digest.update(status) + digest.update(tracked_diff) + for raw_path in sorted(path for path in untracked if path): + relative = os.fsdecode(raw_path) + path = repository / relative + digest.update(raw_path) + digest.update(b"\0") + if path.is_symlink(): + digest.update(os.readlink(path).encode("utf-8")) + elif path.is_file(): + digest.update(bytes.fromhex(_file_sha256(path))) + else: + digest.update(b"") + dirty = bool(status) + worktree_sha256 = digest.hexdigest() + except (OSError, subprocess.CalledProcessError): + revision = "unavailable" + dirty = True + worktree_sha256 = "unavailable" + return { + "dirty": dirty, + "revision": revision, + "worktree_sha256": worktree_sha256, + } + + +def _collect_provenance(config_payload: object) -> dict[str, object]: + root = Path(__file__).resolve().parents[2] + source_hashes = { + path.relative_to(root).as_posix(): _file_sha256(path) + for path in sorted((root / "src" / "qcontrol").glob("*.py")) + } + try: + device_platform = str(jax.devices()[0].platform) + except (IndexError, RuntimeError): + device_platform = "unavailable" + return { + "config": config_payload, + "git": _git_state(root), + "jax": { + "platform": device_platform, + "x64_enabled": bool(jax.config.x64_enabled), + }, + "source_hashes": source_hashes, + "uv_lock_sha256": _file_sha256(root / "uv.lock"), + "versions": { + "jax": importlib.metadata.version("jax"), + "jaxlib": importlib.metadata.version("jaxlib"), + "numpy": importlib.metadata.version("numpy"), + "python": platform.python_version(), + "scipy": importlib.metadata.version("scipy"), + }, + } + + +def collect_provenance(config: ExperimentConfig) -> dict[str, object]: + if not isinstance(config, ExperimentConfig): + raise ValueError("config must be an ExperimentConfig") + return _collect_provenance(config.canonical_dict()) + + +def _boot_id() -> str: + try: + return Path("/proc/sys/kernel/random/boot_id").read_text().strip() + except OSError: + return "unavailable" + + +def _process_start_identity(pid: int) -> str | None: + try: + value = Path(f"/proc/{pid}/stat").read_text() + except OSError: + return None + try: + fields = value.rsplit(")", 1)[1].split() + return fields[19] + except (IndexError, ValueError): + return None + + +@dataclass +class TrialClaim(AbstractContextManager["TrialClaim"]): + store: ArtifactStore + trial_id: str + path: Path + lock_path: Path + lock_descriptor: int + owner: dict[str, object] + _released: bool = False + + def __enter__(self) -> TrialClaim: + return self + + def release(self) -> None: + if self._released: + return + try: + self.store._release_claim(self) + finally: + self._released = True + + def __exit__(self, *exc_info: object) -> None: + self.release() + + +class ArtifactStore: + MANIFEST_SCHEMA = 2 + INDEX_SCHEMA = 1 + READY_SCHEMA = 1 + + def __init__( + self, + root: str | os.PathLike[str], + *, + stale_lock_seconds: float = 24 * 60 * 60, + ) -> None: + if isinstance(stale_lock_seconds, bool) or stale_lock_seconds < 0: + raise ValueError("stale_lock_seconds must be nonnegative") + requested = Path(root).absolute() + self._reject_symlink_chain(requested, include_final=True) + requested.mkdir(parents=True, exist_ok=True) + self._reject_symlink_chain(requested, include_final=True) + requested = requested.resolve(strict=True) + if not requested.is_dir(): + raise ArtifactConflict("artifact store root must be a directory") + self.root = requested + self.stale_lock_seconds = float(stale_lock_seconds) + self._instance_lock = threading.RLock() + self._lock_depth = 0 + self._bound_config_payload: object | None = None + self._bound_plan_sha256: str | None = None + key = str(self.root) + with _THREAD_LOCKS_GUARD: + self._thread_lock = _THREAD_LOCKS.setdefault(key, threading.RLock()) + + @staticmethod + def _reject_symlink_chain(path: Path, *, include_final: bool) -> None: + limit = len(path.parts) if include_final else len(path.parts) - 1 + current = Path(path.anchor) + for part in path.parts[1:limit]: + current /= part + if current.is_symlink(): + raise ArtifactConflict(f"symlink path component rejected: {current}") + + @contextmanager + def _exclusive(self) -> Iterator[None]: + with self._thread_lock, self._instance_lock: + if self._lock_depth: + self._lock_depth += 1 + try: + yield + finally: + self._lock_depth -= 1 + return + lock_path = self.root / ".store.lock" + descriptor = os.open( + lock_path, + os.O_RDWR | os.O_CREAT | getattr(os, "O_NOFOLLOW", 0), + 0o600, + ) + try: + fcntl.flock(descriptor, fcntl.LOCK_EX) + self._lock_depth = 1 + yield + finally: + self._lock_depth = 0 + try: + fcntl.flock(descriptor, fcntl.LOCK_UN) + finally: + os.close(descriptor) + + def _path(self, relative_path: str | os.PathLike[str], *, create: bool) -> Path: + relative = _safe_relative_path(relative_path) + destination = self.root.joinpath(relative) + try: + destination.relative_to(self.root) + except ValueError: + raise ValueError("artifact path must remain beneath store root") from None + current = self.root + for part in relative.parts[:-1]: + current /= part + if current.exists() or current.is_symlink(): + if current.is_symlink(): + raise ArtifactConflict( + f"symlink parent rejected: {current.relative_to(self.root)}" + ) + if not current.is_dir(): + raise ArtifactConflict("artifact parent is not a directory") + elif create: + current.mkdir() + self._fsync_directory(current.parent) + else: + break + if destination.is_symlink(): + raise ArtifactConflict( + f"symlink artifact rejected: {destination.relative_to(self.root)}" + ) + return destination + + @classmethod + def create( + cls, + root: str | os.PathLike[str], + config: ExperimentConfig, + ) -> ArtifactStore: + store = cls(root) + store.initialize_run(config.canonical_dict(), None) + return store + + @classmethod + def resume( + cls, + root: str | os.PathLike[str], + config: ExperimentConfig, + ) -> ArtifactStore: + store = cls(root) + store._bound_config_payload = config.canonical_dict() + store.verify_bound_provenance() + return store + + def create_or_resume(self, config: ExperimentConfig) -> ArtifactStore: + if (self.root / "ready.json").exists(): + return type(self).resume(self.root, config) + return type(self).create(self.root, config) + + def initialize_run( + self, + config_payload: object, + plan_payload: object | None, + ) -> None: + manifest = { + "provenance": _collect_provenance(config_payload), + "schema_version": self.MANIFEST_SCHEMA, + } + manifest_bytes = canonical_json_bytes(manifest) + plan_bytes = ( + None if plan_payload is None else canonical_json_bytes(plan_payload) + ) + marker = { + "manifest_sha256": _sha256(manifest_bytes), + "plan_sha256": None if plan_bytes is None else _sha256(plan_bytes), + "schema_version": self.READY_SCHEMA, + } + with self._exclusive(): + ready_path = self._path("ready.json", create=False) + if ready_path.exists(): + self._verify_initialization_locked(marker, manifest_bytes, plan_bytes) + self._bound_config_payload = config_payload + self._bound_plan_sha256 = marker["plan_sha256"] + return + transaction_path = self._path("initializing.json", create=True) + if transaction_path.exists(): + if transaction_path.read_bytes() != canonical_json_bytes(marker): + raise ArtifactConflict( + "incomplete initialization belongs to different configuration" + ) + else: + self._publish_bytes_locked( + Path("initializing.json"), + canonical_json_bytes(marker), + immutable=True, + ) + self._publish_bytes_locked( + Path("manifest.json"), + manifest_bytes, + immutable=True, + ) + if plan_bytes is not None: + self._publish_bytes_locked( + Path("plan.json"), + plan_bytes, + immutable=True, + ) + self._publish_bytes_locked( + Path("ready.json"), + canonical_json_bytes(marker), + immutable=True, + ) + self._best_effort_unlink(transaction_path) + self._bound_config_payload = config_payload + self._bound_plan_sha256 = marker["plan_sha256"] + + def _verify_initialization_locked( + self, + marker: dict[str, object], + manifest_bytes: bytes, + plan_bytes: bytes | None, + ) -> None: + ready = self._path("ready.json", create=False) + manifest = self._path("manifest.json", create=False) + plan = self._path("plan.json", create=False) + if ready.read_bytes() != canonical_json_bytes(marker): + raise ArtifactConflict("run plan/initialization marker differs") + if not manifest.is_file() or manifest.read_bytes() != manifest_bytes: + raise ArtifactConflict("run manifest differs") + if plan_bytes is None: + if plan.exists(): + raise ArtifactConflict("unexpected plan for single-config store") + elif not plan.is_file() or plan.read_bytes() != plan_bytes: + raise ArtifactConflict("run plan differs") + + def verify_bound_provenance(self) -> None: + if self._bound_config_payload is None: + raise ArtifactConflict("store has no bound provenance configuration") + expected = { + "provenance": _collect_provenance(self._bound_config_payload), + "schema_version": self.MANIFEST_SCHEMA, + } + with self._exclusive(): + ready = self._read_json_locked(Path("ready.json")) + manifest_path = self._path("manifest.json", create=False) + if ( + not isinstance(ready, dict) + or set(ready) != { + "manifest_sha256", + "plan_sha256", + "schema_version", + } + or ready["schema_version"] != self.READY_SCHEMA + or ready["manifest_sha256"] != _file_sha256(manifest_path) + or self._read_json_locked(Path("manifest.json")) != expected + ): + raise ArtifactConflict( + "artifact provenance does not match current state" + ) + if ready["plan_sha256"] != self._bound_plan_sha256: + raise ArtifactConflict("artifact plan binding changed") + if self._bound_plan_sha256 is None: + if self._path("plan.json", create=False).exists(): + raise ArtifactConflict("unexpected plan in single-config store") + elif not self.verify_file("plan.json", self._bound_plan_sha256): + raise ArtifactConflict("artifact plan changed during execution") + + def bind_provenance(self, config_payload: object) -> None: + self.initialize_run(config_payload, None) + + def _replace(self, source: Path, destination: Path) -> None: + os.replace(source, destination) + + def _unlink(self, path: Path, *, missing_ok: bool = False) -> None: + path.unlink(missing_ok=missing_ok) + + def _fsync_directory(self, path: Path) -> None: + descriptor = os.open( + path, + os.O_RDONLY | getattr(os, "O_DIRECTORY", 0) | getattr(os, "O_NOFOLLOW", 0), + ) + try: + os.fsync(descriptor) + finally: + os.close(descriptor) + + def _write_temp(self, parent: Path, data: bytes) -> Path: + temp = parent / f".artifact.tmp-{os.getpid()}-{uuid.uuid4().hex}" + descriptor = os.open( + temp, + os.O_WRONLY + | os.O_CREAT + | os.O_EXCL + | getattr(os, "O_NOFOLLOW", 0), + 0o644, + ) + try: + try: + view = memoryview(data) + while view: + written = os.write(descriptor, view) + view = view[written:] + os.fsync(descriptor) + finally: + os.close(descriptor) + except BaseException: + self._best_effort_unlink(temp) + raise + return temp + + def _best_effort_unlink(self, path: Path) -> None: + try: + self._unlink(path, missing_ok=True) + except OSError: + pass + + def _present_state( + self, + destination: Path, + old_digest: str | None, + new_digest: str, + ) -> str: + if not destination.exists(): + return "missing" + try: + digest = _file_sha256(destination) + except OSError: + return "unreadable" + if digest == new_digest: + return "new" + if old_digest is not None and digest == old_digest: + return "old" + return f"other:{digest}" + + def _rollback_or_raise( + self, + *, + destination: Path, + backup: Path | None, + old_digest: str | None, + new_digest: str, + cause: BaseException, + ) -> None: + try: + if backup is None: + self._unlink(destination, missing_ok=True) + else: + self._replace(backup, destination) + self._fsync_directory(destination.parent) + except BaseException as rollback_error: + raise ArtifactDurabilityError( + f"publication durability failed ({cause!r}); " + f"rollback failed ({rollback_error!r})", + old_sha256=old_digest, + new_sha256=new_digest, + present=self._present_state( + destination, + old_digest, + new_digest, + ), + ) from cause + raise ArtifactDurabilityError( + f"publication durability failed and rollback completed ({cause!r})", + old_sha256=old_digest, + new_sha256=new_digest, + present=self._present_state(destination, old_digest, new_digest), + ) from cause + + def _publish_bytes_locked( + self, + relative: Path, + data: bytes, + *, + immutable: bool, + ) -> str: + destination = self._path(relative, create=True) + digest = _sha256(data) + if destination.exists(): + existing = destination.read_bytes() + if immutable: + if existing != data: + raise ArtifactConflict(f"immutable artifact differs: {relative}") + return digest + temp = self._write_temp(destination.parent, data) + backup: Path | None = None + old_digest: str | None = None + replaced = False + try: + if destination.exists(): + old_digest = _file_sha256(destination) + backup = destination.parent / ( + f".artifact.backup-{os.getpid()}-{uuid.uuid4().hex}" + ) + os.link(destination, backup, follow_symlinks=False) + self._replace(temp, destination) + replaced = True + try: + self._fsync_directory(destination.parent) + except BaseException as error: + self._rollback_or_raise( + destination=destination, + backup=backup, + old_digest=old_digest, + new_digest=digest, + cause=error, + ) + try: + published_digest = _file_sha256(destination) + except OSError as error: + raise ArtifactDurabilityError( + "durable publication could not be read for verification", + old_sha256=old_digest, + new_sha256=digest, + present=self._present_state( + destination, + old_digest, + digest, + ), + ) from error + if published_digest != digest: + try: + if backup is None: + self._unlink(destination) + else: + self._replace(backup, destination) + self._fsync_directory(destination.parent) + except BaseException as error: + raise ArtifactDurabilityError( + "published hash mismatch and rollback failed", + old_sha256=old_digest, + new_sha256=digest, + present=self._present_state( + destination, + old_digest, + digest, + ), + ) from error + raise ArtifactConflict(f"published artifact hash mismatch: {relative}") + return digest + finally: + if not replaced: + self._best_effort_unlink(temp) + if backup is not None: + self._best_effort_unlink(backup) + + def publish_json( + self, + relative_path: str | os.PathLike[str], + payload: object, + *, + immutable: bool = False, + ) -> str: + relative = _safe_relative_path(relative_path) + data = canonical_json_bytes(payload) + with self._exclusive(): + return self._publish_bytes_locked( + relative, + data, + immutable=immutable, + ) + + def _read_json_locked(self, relative: Path) -> Any: + path = self._path(relative, create=False) + try: + return json.loads(path.read_text(encoding="utf-8")) + except (OSError, UnicodeDecodeError, json.JSONDecodeError) as error: + raise ArtifactConflict(f"invalid artifact: {path.name}") from error + + def read_json(self, relative_path: str | os.PathLike[str]) -> Any: + relative = _safe_relative_path(relative_path) + with self._exclusive(): + return self._read_json_locked(relative) + + def verify_file( + self, + relative_path: str | os.PathLike[str], + expected_sha256: str, + ) -> bool: + relative = _safe_relative_path(relative_path) + if ( + not isinstance(expected_sha256, str) + or re.fullmatch(r"[0-9a-f]{64}", expected_sha256, re.ASCII) is None + ): + return False + with self._exclusive(): + path = self._path(relative, create=False) + return path.is_file() and _file_sha256(path) == expected_sha256 + + def _read_index_locked(self) -> dict[str, object]: + path = self._path("index.json", create=False) + if not path.exists(): + return {"schema_version": self.INDEX_SCHEMA, "trials": {}} + index = self._read_json_locked(Path("index.json")) + if ( + not isinstance(index, dict) + or set(index) != {"schema_version", "trials"} + or type(index["schema_version"]) is not int + or index["schema_version"] != self.INDEX_SCHEMA + or not isinstance(index["trials"], dict) + or any( + _TOKEN.fullmatch(key) is None + or not isinstance(value, str) + or re.fullmatch(r"[0-9a-f]{64}", value, re.ASCII) is None + for key, value in index["trials"].items() + ) + ): + raise ArtifactConflict("invalid trial index schema") + return index + + def _update_index_locked(self, trial_id: str, digest: str) -> None: + index = self._read_index_locked() + trials = dict(index["trials"]) + prior = trials.get(trial_id) + if prior is not None and prior != digest: + raise ArtifactConflict(f"immutable trial index differs: {trial_id}") + trials[trial_id] = digest + self._publish_bytes_locked( + Path("index.json"), + canonical_json_bytes( + {"schema_version": self.INDEX_SCHEMA, "trials": trials} + ), + immutable=False, + ) + + def publish_trial(self, trial_id: str, payload: object) -> str: + token = _strict_token(trial_id, name="trial ID token") + data = canonical_json_bytes(payload) + with self._exclusive(): + digest = self._publish_bytes_locked( + Path("trials") / f"{token}.json", + data, + immutable=True, + ) + self._update_index_locked(token, digest) + return digest + + def adopt_trial( + self, + trial_id: str, + validator: Callable[[object], None], + ) -> bool: + token = _strict_token(trial_id, name="trial ID token") + with self._exclusive(): + path = self._path(Path("trials") / f"{token}.json", create=False) + if not path.exists(): + return False + try: + raw = path.read_bytes() + except OSError as error: + raise ArtifactConflict( + f"orphan trial cannot be read: {token}" + ) from error + payload = _parse_canonical_json_bytes(raw) + try: + validator(payload) + except (TypeError, ValueError) as error: + raise ArtifactConflict( + f"orphan trial has invalid strict schema: {token}: {error}" + ) from error + digest = _sha256(raw) + self._update_index_locked(token, digest) + return True + + def trial_hashes(self) -> dict[str, str]: + with self._exclusive(): + index = self._read_index_locked() + return dict(index["trials"]) + + def completed_trial_ids(self) -> frozenset[str]: + with self._exclusive(): + hashes = dict(self._read_index_locked()["trials"]) + for trial_id, digest in hashes.items(): + path = self._path( + Path("trials") / f"{trial_id}.json", + create=False, + ) + if not path.is_file() or _file_sha256(path) != digest: + raise ArtifactConflict(f"trial hash mismatch: {trial_id}") + return frozenset(hashes) + + def owner_identity( + self, + *, + lease_seconds: float | None = None, + ) -> dict[str, object]: + lease = ( + self.stale_lock_seconds + if lease_seconds is None + else float(lease_seconds) + ) + now = time.time() + return { + "boot_id": _boot_id(), + "hostname": socket.gethostname(), + "lease_expires_at": now + lease, + "nonce": uuid.uuid4().hex, + "pid": os.getpid(), + "process_start_id": _process_start_identity(os.getpid()), + "started_at": now, + } + + def _owner_is_stale(self, owner: object) -> bool: + if ( + not isinstance(owner, dict) + or set(owner) != _OWNER_FIELDS + or not isinstance(owner["hostname"], str) + or type(owner["pid"]) is not int + or owner["pid"] <= 0 + or not isinstance(owner["process_start_id"], str) + or not owner["process_start_id"] + or not isinstance(owner["boot_id"], str) + or type(owner["started_at"]) not in {int, float} + or type(owner["lease_expires_at"]) not in {int, float} + or not isinstance(owner["nonce"], str) + or re.fullmatch(r"[0-9a-f]{32}", owner["nonce"], re.ASCII) is None + ): + return False + if owner["hostname"] != socket.gethostname(): + return time.time() > float(owner["lease_expires_at"]) + if owner["boot_id"] != _boot_id(): + return True + current_start = _process_start_identity(int(owner["pid"])) + return current_start is None or current_start != owner["process_start_id"] + + def claim_trial(self, trial_id: str) -> TrialClaim: + token = _strict_token(trial_id, name="trial ID token") + owner = self.owner_identity() + owner_relative = Path("claims") / f"{token}.owner.json" + lock_relative = Path("claims") / f"{token}.flock" + with self._exclusive(): + lock_path = self._path(lock_relative, create=True) + lock_descriptor = os.open( + lock_path, + os.O_RDWR + | os.O_CREAT + | getattr(os, "O_NOFOLLOW", 0), + 0o600, + ) + try: + try: + fcntl.flock( + lock_descriptor, + fcntl.LOCK_EX | fcntl.LOCK_NB, + ) + except BlockingIOError: + raise TrialClaimConflict( + f"trial {token} kernel lock is held" + ) from None + owner_path = self._path(owner_relative, create=True) + if owner_path.exists(): + try: + existing = self._read_json_locked(owner_relative) + except ArtifactConflict: + raise TrialClaimConflict( + f"trial {token} claim cannot prove stale" + ) from None + if not self._owner_is_stale(existing): + detail = ( + "claimed by active owner" + if isinstance(existing, dict) + and type(existing.get("lease_expires_at")) + in {int, float} + and time.time() + <= float(existing["lease_expires_at"]) + else "claim cannot prove stale" + ) + raise TrialClaimConflict(f"trial {token} {detail}") + reread = self._read_json_locked(owner_relative) + if ( + reread != existing + or not isinstance(reread, dict) + or reread.get("nonce") != existing.get("nonce") + or not self._owner_is_stale(reread) + ): + raise TrialClaimConflict( + f"trial {token} stale owner changed during reclamation" + ) + self._unlink(owner_path) + self._fsync_directory(owner_path.parent) + self._publish_bytes_locked( + owner_relative, + canonical_json_bytes(owner), + immutable=True, + ) + return TrialClaim( + self, + token, + owner_path, + lock_path, + lock_descriptor, + owner, + ) + except BaseException: + try: + fcntl.flock(lock_descriptor, fcntl.LOCK_UN) + finally: + os.close(lock_descriptor) + raise + + def _release_claim(self, claim: TrialClaim) -> None: + try: + with self._exclusive(): + try: + current = self._read_json_locked( + Path("claims") + / f"{claim.trial_id}.owner.json" + ) + except ArtifactConflict as error: + raise TrialClaimConflict( + "trial claim disappeared or became unreadable before release" + ) from error + if ( + current != claim.owner + or not isinstance(current, dict) + or current.get("nonce") != claim.owner.get("nonce") + ): + raise TrialClaimConflict( + "trial claim owner changed before release" + ) + self._unlink(claim.path) + self._fsync_directory(claim.path.parent) + finally: + try: + fcntl.flock(claim.lock_descriptor, fcntl.LOCK_UN) + finally: + os.close(claim.lock_descriptor) diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/src/qcontrol/closed_loop.py b/tracks/qcs/solutions/frustration-free/challenge-113/src/qcontrol/closed_loop.py new file mode 100644 index 000000000..1ec981ae4 --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/src/qcontrol/closed_loop.py @@ -0,0 +1,918 @@ +from __future__ import annotations + +from collections.abc import Callable, Mapping +from dataclasses import dataclass +import math +from numbers import Integral, Real + +import cma +import numpy as np +from numpy.typing import NDArray + +from qcontrol.config import SearchConfig +from qcontrol.device import DeviceQueryError, Observation, QueryDevice + + +_TARGET_FIDELITY = 0.999 +_VALIDATION_SHOTS = 100_000 +_INITIAL_SCALE = 0.2 +_DEFAULT_BOUND = 1.0 + + +def _positive_integer(name: str, value: object) -> int: + if ( + isinstance(value, (bool, np.bool_)) + or not isinstance(value, Integral) + or value <= 0 + ): + raise ValueError(f"{name} must be a positive integer") + return int(value) + + +def _nonnegative_integer(name: str, value: object) -> int: + if ( + isinstance(value, (bool, np.bool_)) + or not isinstance(value, Integral) + or value < 0 + ): + raise ValueError(f"{name} must be a nonnegative integer") + return int(value) + + +def _finite_vector(name: str, value: object) -> NDArray[np.float64]: + array = np.asarray(value) + if np.iscomplexobj(array): + raise ValueError(f"{name} must be real") + try: + vector = np.asarray(array, dtype=np.float64) + except (TypeError, ValueError, OverflowError) as error: + raise ValueError(f"{name} must be a finite real vector") from error + if vector.ndim != 1 or not np.all(np.isfinite(vector)): + raise ValueError(f"{name} must be a finite real vector") + return np.array(vector, dtype=np.float64, copy=True) + + +def _finite_matrix(name: str, value: object) -> NDArray[np.float64]: + array = np.asarray(value) + if np.iscomplexobj(array): + raise ValueError(f"{name} must be real") + try: + matrix = np.asarray(array, dtype=np.float64) + except (TypeError, ValueError, OverflowError) as error: + raise ValueError(f"{name} must be a finite real matrix") from error + if matrix.ndim != 2 or not np.all(np.isfinite(matrix)): + raise ValueError(f"{name} must be a finite real matrix") + return np.array(matrix, dtype=np.float64, copy=True) + + +def _coordinate_bound(value: object, dimension: int) -> NDArray[np.float64]: + if isinstance(value, (bool, np.bool_)): + raise ValueError("bound must contain finite positive values") + try: + bound = np.asarray(value, dtype=np.float64) + except (TypeError, ValueError, OverflowError) as error: + raise ValueError("bound must contain finite positive values") from error + if bound.ndim == 0: + bound = np.full(dimension, float(bound), dtype=np.float64) + elif bound.shape == (dimension,): + bound = np.array(bound, dtype=np.float64, copy=True) + else: + raise ValueError(f"bound must be scalar or have shape ({dimension},)") + if not np.all(np.isfinite(bound)) or np.any(bound <= 0.0): + raise ValueError("bound must contain finite positive values") + return bound + + +def _readonly(array: NDArray[np.float64]) -> NDArray[np.float64]: + copied = np.array(array, dtype=np.float64, copy=True, order="C") + immutable = np.frombuffer( + copied.tobytes(), + dtype=np.dtype(np.float64), + ).reshape(copied.shape) + immutable.setflags(write=False) + return immutable + + +@dataclass(frozen=True, slots=True, init=False, eq=False) +class SearchSpace: + origin: NDArray[np.float64] + basis: NDArray[np.float64] + lower_bounds: NDArray[np.float64] + upper_bounds: NDArray[np.float64] + _hash_value: int + + def __init__( + self, + origin: object, + basis: object, + lower_bounds: object | None = None, + upper_bounds: object | None = None, + *, + bound: object = _DEFAULT_BOUND, + ) -> None: + origin_array = _finite_vector("origin", origin) + if np.any(np.abs(origin_array) > 1.0): + raise ValueError("origin must lie within normalized pulse bounds [-1, 1]") + basis_array = _finite_matrix("basis", basis) + if basis_array.shape[0] != origin_array.size or basis_array.shape[1] == 0: + raise ValueError( + "basis must have shape (origin.size, positive dimension)" + ) + dimension = basis_array.shape[1] + if dimension > origin_array.size: + raise ValueError("basis dimension cannot exceed pulse dimension") + gram = basis_array.T @ basis_array + if not np.allclose(gram, np.eye(dimension), rtol=0.0, atol=1e-10): + raise ValueError("basis columns must be orthonormal (whitened coordinates)") + + if (lower_bounds is None) != (upper_bounds is None): + raise ValueError("lower_bounds and upper_bounds must be supplied together") + if lower_bounds is None: + bound_array = _coordinate_bound(bound, dimension) + lower_array = -bound_array + upper_array = bound_array + else: + lower_array = _finite_vector("lower_bounds", lower_bounds) + upper_array = _finite_vector("upper_bounds", upper_bounds) + if lower_array.shape != (dimension,) or upper_array.shape != (dimension,): + raise ValueError( + f"coordinate bounds must both have shape ({dimension},)" + ) + if np.any(lower_array >= upper_array): + raise ValueError("each lower bound must be below its upper bound") + if np.any(lower_array > 0.0) or np.any(upper_array < 0.0): + raise ValueError("coordinate bounds must contain the zero origin") + + immutable_origin = _readonly(origin_array) + immutable_basis = _readonly(basis_array) + immutable_lower = _readonly(lower_array) + immutable_upper = _readonly(upper_array) + object.__setattr__(self, "origin", immutable_origin) + object.__setattr__(self, "basis", immutable_basis) + object.__setattr__(self, "lower_bounds", immutable_lower) + object.__setattr__(self, "upper_bounds", immutable_upper) + object.__setattr__( + self, + "_hash_value", + hash( + ( + tuple(float(value) for value in immutable_origin), + tuple( + tuple(float(value) for value in row) + for row in immutable_basis + ), + tuple(float(value) for value in immutable_lower), + tuple(float(value) for value in immutable_upper), + ) + ), + ) + + @property + def dimension(self) -> int: + return int(self.basis.shape[1]) + + @property + def pulse_dimension(self) -> int: + return int(self.origin.size) + + def to_pulse(self, coordinates: object) -> NDArray[np.float64]: + coordinate_array = _finite_vector("coordinates", coordinates) + if coordinate_array.shape != (self.dimension,): + raise ValueError(f"coordinates must have shape ({self.dimension},)") + pulse = self.origin + self.basis @ coordinate_array + return np.clip(pulse, -1.0, 1.0) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, SearchSpace): + return NotImplemented + return bool( + np.array_equal(self.origin, other.origin) + and np.array_equal(self.basis, other.basis) + and np.array_equal(self.lower_bounds, other.lower_bounds) + and np.array_equal(self.upper_bounds, other.upper_bounds) + ) + + def __hash__(self) -> int: + return self._hash_value + + def canonical_dict(self) -> dict[str, object]: + return { + "basis": [ + [float(value) for value in row] + for row in self.basis + ], + "lower_bounds": [float(value) for value in self.lower_bounds], + "origin": [float(value) for value in self.origin], + "upper_bounds": [float(value) for value in self.upper_bounds], + } + + @classmethod + def from_canonical_dict(cls, payload: object) -> SearchSpace: + if not isinstance(payload, Mapping): + raise ValueError("search-space payload must be a mapping") + try: + return cls( + payload["origin"], + payload["basis"], + payload["lower_bounds"], + payload["upper_bounds"], + ) + except KeyError as error: + raise ValueError( + f"search-space payload is missing {error.args[0]!r}" + ) from None + + +def _leading_space( + origin: object, + basis: object, + *, + dimension: object, + bound: object, +) -> SearchSpace: + origin_array = _finite_vector("origin", origin) + basis_array = _finite_matrix("basis", basis) + resolved_dimension = _positive_integer("dimension", dimension) + if basis_array.shape[0] != origin_array.size: + raise ValueError("basis row count must match origin size") + if basis_array.shape[1] < resolved_dimension: + raise ValueError("basis has fewer columns than the requested dimension") + return SearchSpace( + origin_array, + basis_array[:, :resolved_dimension], + bound=bound, + ) + + +def make_full_space( + origin: object, + *, + bound: object = _DEFAULT_BOUND, +) -> SearchSpace: + origin_array = _finite_vector("origin", origin) + return SearchSpace( + origin_array, + np.eye(origin_array.size, dtype=np.float64), + bound=bound, + ) + + +def make_model_hessian_space( + origin: object, + model_basis: object, + *, + dimension: object, + bound: object = _DEFAULT_BOUND, +) -> SearchSpace: + origin_array = _finite_vector("origin", origin) + basis_array = _finite_matrix("model_basis", model_basis) + resolved_dimension = _positive_integer("dimension", dimension) + if basis_array.shape[0] != origin_array.size: + raise ValueError("basis row count must match origin size") + if basis_array.shape[1] < resolved_dimension: + raise ValueError("basis has fewer columns than the requested dimension") + if resolved_dimension == origin_array.size: + return make_full_space(origin_array, bound=bound) + return _leading_space( + origin_array, + basis_array, + dimension=resolved_dimension, + bound=bound, + ) + + +def make_oracle_space( + origin: object, + oracle_basis: object, + *, + dimension: object, + bound: object = _DEFAULT_BOUND, +) -> SearchSpace: + return _leading_space( + origin, + oracle_basis, + dimension=dimension, + bound=bound, + ) + + +def make_random_space( + origin: object, + *, + dimension: object, + seed: object, + bound: object = _DEFAULT_BOUND, +) -> SearchSpace: + origin_array = _finite_vector("origin", origin) + resolved_dimension = _positive_integer("dimension", dimension) + resolved_seed = _nonnegative_integer("seed", seed) + if resolved_dimension > origin_array.size: + raise ValueError("dimension cannot exceed pulse dimension") + gaussian = np.random.default_rng(resolved_seed).normal( + size=(origin_array.size, resolved_dimension) + ) + basis, triangular = np.linalg.qr(gaussian, mode="reduced") + diagonal = np.diag(triangular) + signs = np.where(diagonal < 0.0, -1.0, 1.0) + basis = np.asarray(basis * signs, dtype=np.float64) + return SearchSpace(origin_array, basis, bound=bound) + + +def make_search_space( + config: SearchConfig, + origin: object, + *, + model_basis: object | None = None, + oracle_basis: object | None = None, + seed: object = 0, + bound: object = _DEFAULT_BOUND, +) -> SearchSpace: + if not isinstance(config, SearchConfig): + raise ValueError("config must be a SearchConfig") + if config.method == "full": + return make_full_space(origin, bound=bound) + if config.method == "model_hessian": + if model_basis is None: + raise ValueError("model_hessian search requires model_basis") + return make_model_hessian_space( + origin, + model_basis, + dimension=config.dimension, + bound=bound, + ) + if config.method == "random": + return make_random_space( + origin, + dimension=config.dimension, + seed=seed, + bound=bound, + ) + if oracle_basis is None: + raise ValueError("oracle search requires an externally constructed oracle_basis") + return make_oracle_space( + origin, + oracle_basis, + dimension=config.dimension, + bound=bound, + ) + + +def _observation_dict(observation: Observation) -> dict[str, object]: + return { + "attempt_index": int(observation.attempt_index), + "estimate": float(observation.estimate), + "observation_seed": int(observation.observation_seed), + "optimizer_query_index": int(observation.optimizer_query_index), + "seed_digest": str(observation.seed_digest), + "shots": int(observation.shots), + "validation": bool(observation.validation), + } + + +def _observation_from_dict(payload: object) -> Observation: + if not isinstance(payload, Mapping): + raise ValueError("observation payload must be a mapping") + try: + return Observation( + estimate=payload["estimate"], + shots=payload["shots"], + optimizer_query_index=payload["optimizer_query_index"], + validation=payload["validation"], + observation_seed=payload["observation_seed"], + attempt_index=payload["attempt_index"], + seed_digest=payload["seed_digest"], + ) + except KeyError as error: + raise ValueError( + f"observation payload is missing {error.args[0]!r}" + ) from None + + +def _optional_observation_dict( + observation: Observation | None, +) -> dict[str, object] | None: + return None if observation is None else _observation_dict(observation) + + +def _optional_observation_from_dict(payload: object) -> Observation | None: + return None if payload is None else _observation_from_dict(payload) + + +def _pulse_tuple(value: object) -> tuple[float, ...]: + pulse = _finite_vector("pulse", value) + if pulse.size == 0 or np.any(np.abs(pulse) > 1.0): + raise ValueError("pulse must be nonempty and within [-1, 1]") + return tuple(float(item) for item in pulse) + + +def _sanitized_failure_category(value: object) -> str: + if not isinstance(value, str) or not value: + return "device_query_failure" + if not value.isascii() or any( + not (character.isalnum() or character == "_") + for character in value + ): + return "device_query_failure" + return value + + +@dataclass(frozen=True, slots=True) +class ValidationAttempt: + optimizer_query_index: int + pulse: tuple[float, ...] + best_observation: Observation + device_attempt_index: int + validation_observation: Observation | None + failure_category: str | None + certified: bool + + def __post_init__(self) -> None: + optimizer_query_index = _positive_integer( + "optimizer_query_index", + self.optimizer_query_index, + ) + device_attempt_index = _positive_integer( + "device_attempt_index", + self.device_attempt_index, + ) + pulse = _pulse_tuple(self.pulse) + if ( + not isinstance(self.best_observation, Observation) + or self.best_observation.validation + or self.best_observation.optimizer_query_index + != optimizer_query_index + ): + raise ValueError( + "best_observation must be the matching optimizer observation" + ) + if not isinstance(self.certified, (bool, np.bool_)): + raise ValueError("certified must be a boolean") + validation = self.validation_observation + if validation is None: + if ( + not isinstance(self.failure_category, str) + or _sanitized_failure_category(self.failure_category) + != self.failure_category + ): + raise ValueError( + "failed validation attempts require a sanitized failure category" + ) + if self.certified: + raise ValueError("failed validation attempts cannot certify") + else: + if ( + not isinstance(validation, Observation) + or not validation.validation + or validation.optimizer_query_index != optimizer_query_index + or validation.attempt_index != device_attempt_index + ): + raise ValueError( + "validation_observation must match the validation attempt" + ) + if self.failure_category is not None: + raise ValueError( + "successful validation attempts cannot have a failure category" + ) + + object.__setattr__(self, "optimizer_query_index", optimizer_query_index) + object.__setattr__(self, "pulse", pulse) + object.__setattr__(self, "device_attempt_index", device_attempt_index) + object.__setattr__(self, "certified", bool(self.certified)) + + @property + def status(self) -> str: + if self.certified: + return "certified" + if self.validation_observation is None: + return "failed" + return "rejected" + + def canonical_dict(self) -> dict[str, object]: + return { + "best_observation": _observation_dict(self.best_observation), + "certified": bool(self.certified), + "device_attempt_index": int(self.device_attempt_index), + "failure_category": self.failure_category, + "optimizer_query_index": int(self.optimizer_query_index), + "pulse": [float(value) for value in self.pulse], + "status": self.status, + "validation_observation": _optional_observation_dict( + self.validation_observation + ), + } + + @classmethod + def from_canonical_dict(cls, payload: object) -> ValidationAttempt: + if not isinstance(payload, Mapping): + raise ValueError("validation-attempt payload must be a mapping") + try: + attempt = cls( + optimizer_query_index=payload["optimizer_query_index"], + pulse=payload["pulse"], + best_observation=_observation_from_dict( + payload["best_observation"] + ), + device_attempt_index=payload["device_attempt_index"], + validation_observation=_optional_observation_from_dict( + payload["validation_observation"] + ), + failure_category=payload["failure_category"], + certified=payload["certified"], + ) + if payload["status"] != attempt.status: + raise ValueError( + "serialized validation status does not match the attempt" + ) + return attempt + except KeyError as error: + raise ValueError( + f"validation-attempt payload is missing {error.args[0]!r}" + ) from None + + +@dataclass(frozen=True, slots=True) +class ClosedLoopResult: + space: SearchSpace + best_pulse: tuple[float, ...] + best_observation: Observation | None + observations: tuple[Observation, ...] + evaluations: int + budget: int + budget_exhausted: bool + validation_attempts: tuple[ValidationAttempt, ...] + certified: bool + first_certified_query: int | None + stop_reason: str + + def __post_init__(self) -> None: + if not isinstance(self.space, SearchSpace): + raise ValueError("space must be a SearchSpace") + best_pulse = _pulse_tuple(self.best_pulse) + observations = tuple(self.observations) + validation_attempts = tuple(self.validation_attempts) + evaluations = _nonnegative_integer("evaluations", self.evaluations) + budget = _positive_integer("budget", self.budget) + if evaluations > budget: + raise ValueError("evaluations cannot exceed budget") + if not isinstance(self.budget_exhausted, (bool, np.bool_)): + raise ValueError("budget_exhausted must be a boolean") + if not isinstance(self.certified, (bool, np.bool_)): + raise ValueError("certified must be a boolean") + if self.stop_reason not in {"budget", "certified", "optimizer_stopped"}: + raise ValueError("unsupported stop_reason") + if any( + not isinstance(observation, Observation) + or observation.validation + for observation in observations + ): + raise ValueError("observations must contain optimizer observations") + if ( + self.best_observation is not None + and self.best_observation not in observations + ): + raise ValueError("best_observation must appear in observations") + if any( + not isinstance(attempt, ValidationAttempt) + for attempt in validation_attempts + ): + raise ValueError( + "validation_attempts must contain ValidationAttempt records" + ) + if any( + attempt.best_observation not in observations + for attempt in validation_attempts + ): + raise ValueError( + "validation-attempt best observations must appear in history" + ) + if any( + len(attempt.pulse) != self.space.pulse_dimension + for attempt in validation_attempts + ): + raise ValueError( + "validation-attempt pulses must match the search pulse dimension" + ) + crossing_indices = [ + attempt.optimizer_query_index + for attempt in validation_attempts + ] + if any( + current <= previous + for previous, current in zip( + crossing_indices, + crossing_indices[1:], + ) + ): + raise ValueError( + "validation attempts must follow optimizer query order" + ) + certified_attempts = [ + attempt for attempt in validation_attempts if attempt.certified + ] + if len(certified_attempts) > 1 or ( + certified_attempts + and validation_attempts[-1] is not certified_attempts[0] + ): + raise ValueError( + "certification must be the final validation attempt" + ) + expected_first = ( + certified_attempts[0].optimizer_query_index + if certified_attempts + else None + ) + if self.first_certified_query != expected_first: + raise ValueError( + "first_certified_query must match validation_attempts" + ) + if bool(certified_attempts) != bool(self.certified): + raise ValueError("certified must match validation_attempts") + if self.certified: + if self.stop_reason != "certified" or self.budget_exhausted: + raise ValueError( + "certified results must stop without budget exhaustion" + ) + elif evaluations == budget: + if self.stop_reason != "budget" or not self.budget_exhausted: + raise ValueError( + "budget exhaustion must match evaluations and stop reason" + ) + elif self.stop_reason != "optimizer_stopped" or self.budget_exhausted: + raise ValueError( + "early uncertified results must be optimizer-stopped" + ) + + object.__setattr__(self, "best_pulse", best_pulse) + object.__setattr__(self, "observations", observations) + object.__setattr__(self, "validation_attempts", validation_attempts) + object.__setattr__(self, "evaluations", evaluations) + object.__setattr__(self, "budget", budget) + object.__setattr__(self, "budget_exhausted", bool(self.budget_exhausted)) + object.__setattr__(self, "certified", bool(self.certified)) + + @property + def provisional_crossings(self) -> tuple[int, ...]: + return tuple( + attempt.optimizer_query_index + for attempt in self.validation_attempts + ) + + @property + def validation_result(self) -> Observation | None: + return next( + ( + attempt.validation_observation + for attempt in reversed(self.validation_attempts) + if attempt.validation_observation is not None + ), + None, + ) + + def canonical_dict(self) -> dict[str, object]: + return { + "best_observation": _optional_observation_dict( + self.best_observation + ), + "best_pulse": [float(value) for value in self.best_pulse], + "budget": int(self.budget), + "budget_exhausted": bool(self.budget_exhausted), + "certified": bool(self.certified), + "evaluations": int(self.evaluations), + "first_certified_query": self.first_certified_query, + "observations": [ + _observation_dict(observation) + for observation in self.observations + ], + "provisional_crossings": list(self.provisional_crossings), + "schema_version": 1, + "space": self.space.canonical_dict(), + "stop_reason": self.stop_reason, + "validation_attempts": [ + attempt.canonical_dict() + for attempt in self.validation_attempts + ], + "validation_result": _optional_observation_dict( + self.validation_result + ), + } + + @classmethod + def from_canonical_dict(cls, payload: object) -> ClosedLoopResult: + if not isinstance(payload, Mapping): + raise ValueError("closed-loop result payload must be a mapping") + try: + if payload["schema_version"] != 1: + raise ValueError("unsupported closed-loop result schema version") + result = cls( + space=SearchSpace.from_canonical_dict(payload["space"]), + best_pulse=payload["best_pulse"], + best_observation=_optional_observation_from_dict( + payload["best_observation"] + ), + observations=tuple( + _observation_from_dict(item) + for item in payload["observations"] + ), + evaluations=payload["evaluations"], + budget=payload["budget"], + budget_exhausted=payload["budget_exhausted"], + validation_attempts=tuple( + ValidationAttempt.from_canonical_dict(item) + for item in payload["validation_attempts"] + ), + certified=payload["certified"], + first_certified_query=payload["first_certified_query"], + stop_reason=payload["stop_reason"], + ) + if list(result.provisional_crossings) != payload[ + "provisional_crossings" + ]: + raise ValueError( + "serialized provisional_crossings do not match history" + ) + if _optional_observation_dict(result.validation_result) != payload[ + "validation_result" + ]: + raise ValueError( + "serialized validation_result does not match history" + ) + return result + except KeyError as error: + raise ValueError( + f"closed-loop result payload is missing {error.args[0]!r}" + ) from None + + +def _population_size(dimension: int) -> int: + return 4 + int(math.floor(3.0 * math.log(dimension))) + + +def _cma_options(space: SearchSpace, seed: int) -> dict[str, object]: + options: dict[str, object] = { + "bounds": [space.lower_bounds.tolist(), space.upper_bounds.tolist()], + "popsize": _population_size(space.dimension), + # pycma reserves zero for a time-derived seed, so offset the public + # nonnegative seed to keep every accepted value deterministic. + "seed": seed + 1, + "verbose": -9, + "verb_disp": 0, + "verb_log": 0, + "verb_time": False, + } + if space.dimension == 1: + options["maxstd_boundrange"] = np.inf + return options + + +def run_closed_loop( + device: QueryDevice, + space: SearchSpace, + budget: object, + seed: object, + *, + audit_sink: Callable[[NDArray[np.float64], Observation | None], object] | None = None, +) -> ClosedLoopResult: + if not isinstance(space, SearchSpace): + raise ValueError("space must be a SearchSpace") + resolved_budget = _positive_integer("budget", budget) + resolved_seed = _nonnegative_integer("seed", seed) + try: + initial_queries = int(device.ledger.optimizer_queries) + except (AttributeError, TypeError, ValueError) as error: + raise ValueError("device must expose QueryDevice ledger accounting") from error + + observations: list[Observation] = [] + validation_attempts: list[ValidationAttempt] = [] + best_observation: Observation | None = None + best_pulse = np.array(space.origin, dtype=np.float64, copy=True) + certified = False + first_certified_query: int | None = None + + def evaluations() -> int: + count = int(device.ledger.optimizer_queries) - initial_queries + if count < 0: + raise RuntimeError("device optimizer ledger count moved backwards") + return count + + def evaluate(coordinates: NDArray[np.float64]) -> tuple[float, bool]: + nonlocal best_observation + nonlocal best_pulse + nonlocal certified + nonlocal first_certified_query + + pulse = space.to_pulse(coordinates) + try: + observation = device.query(pulse) + except DeviceQueryError: + if audit_sink is not None: + audit_sink(np.array(pulse, dtype=np.float64, copy=True), None) + return 1.0, False + if audit_sink is not None: + audit_sink( + np.array(pulse, dtype=np.float64, copy=True), + observation, + ) + observations.append(observation) + is_new_best = ( + best_observation is None + or observation.estimate > best_observation.estimate + ) + if is_new_best: + best_observation = observation + best_pulse = np.array(pulse, dtype=np.float64, copy=True) + if observation.estimate >= _TARGET_FIDELITY: + try: + validation = device.validate( + best_pulse, + shots=_VALIDATION_SHOTS, + ) + except DeviceQueryError as error: + validation_attempts.append( + ValidationAttempt( + optimizer_query_index=observation.optimizer_query_index, + pulse=tuple(float(value) for value in best_pulse), + best_observation=observation, + device_attempt_index=error.attempt_index, + validation_observation=None, + failure_category=_sanitized_failure_category( + error.category + ), + certified=False, + ) + ) + else: + attempt_certified = bool( + device.certifies(validation, _TARGET_FIDELITY) + ) + validation_attempts.append( + ValidationAttempt( + optimizer_query_index=observation.optimizer_query_index, + pulse=tuple(float(value) for value in best_pulse), + best_observation=observation, + device_attempt_index=int(validation.attempt_index), + validation_observation=validation, + failure_category=None, + certified=attempt_certified, + ) + ) + if attempt_certified: + certified = True + first_certified_query = observation.optimizer_query_index + return 1.0 - observation.estimate, True + + evaluate(np.zeros(space.dimension, dtype=np.float64)) + + optimizer: cma.CMAEvolutionStrategy | None = None + stop_reason = "certified" if certified else "budget" + while not certified and evaluations() < resolved_budget: + if optimizer is None: + optimizer = cma.CMAEvolutionStrategy( + np.zeros(space.dimension, dtype=np.float64), + _INITIAL_SCALE, + _cma_options(space, resolved_seed), + ) + if optimizer.stop(): + stop_reason = "optimizer_stopped" + break + candidates = optimizer.ask() + scores: list[float] = [] + evaluated_candidates: list[NDArray[np.float64]] = [] + for raw_candidate in candidates: + if evaluations() >= resolved_budget or certified: + break + candidate = np.clip( + np.asarray(raw_candidate, dtype=np.float64), + space.lower_bounds, + space.upper_bounds, + ) + score, _ = evaluate(candidate) + evaluated_candidates.append(candidate) + scores.append(score) + if certified: + stop_reason = "certified" + break + if len(evaluated_candidates) != len(candidates): + stop_reason = "budget" + break + optimizer.tell(evaluated_candidates, scores) + + used_evaluations = evaluations() + budget_exhausted = used_evaluations >= resolved_budget and not certified + if certified: + stop_reason = "certified" + elif budget_exhausted: + stop_reason = "budget" + + return ClosedLoopResult( + space=space, + best_pulse=tuple(float(value) for value in best_pulse), + best_observation=best_observation, + observations=tuple(observations), + evaluations=used_evaluations, + budget=resolved_budget, + budget_exhausted=budget_exhausted, + validation_attempts=tuple(validation_attempts), + certified=certified, + first_certified_query=first_certified_query, + stop_reason=stop_reason, + ) diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/src/qcontrol/config.py b/tracks/qcs/solutions/frustration-free/challenge-113/src/qcontrol/config.py new file mode 100644 index 000000000..db3ace3b9 --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/src/qcontrol/config.py @@ -0,0 +1,147 @@ +from __future__ import annotations + +import hashlib +import json +import math +from dataclasses import dataclass + + +def _require_positive_integer(name: str, value: object) -> None: + if isinstance(value, bool) or not isinstance(value, int) or value <= 0: + raise ValueError(f"{name} must be a positive integer") + + +def _require_nonnegative_integer(name: str, value: object) -> None: + if isinstance(value, bool) or not isinstance(value, int) or value < 0: + raise ValueError(f"{name} must be a nonnegative integer") + + +def _require_finite_number(name: str, value: object, *, positive: bool) -> None: + if isinstance(value, bool) or not isinstance(value, (int, float)): + raise ValueError(f"{name} must be a finite number") + try: + numeric_value = float(value) + except OverflowError: + raise ValueError(f"{name} must be a finite number") from None + if not math.isfinite(numeric_value) or (numeric_value <= 0 if positive else numeric_value < 0): + qualifier = "positive" if positive else "nonnegative" + raise ValueError(f"{name} must be finite and {qualifier}") + + +@dataclass(frozen=True) +class SystemConfig: + name: str + segments: int + amplitude_bound: float + duration: float | None = None + + def __post_init__(self) -> None: + if self.name not in {"one_qubit", "two_qubit"}: + raise ValueError("system name must be 'one_qubit' or 'two_qubit'") + _require_positive_integer("segments", self.segments) + _require_finite_number("amplitude_bound", self.amplitude_bound, positive=True) + if self.duration is not None: + _require_finite_number("duration", self.duration, positive=True) + object.__setattr__(self, "duration", float(self.duration)) + + @property + def effective_duration(self) -> float: + if self.duration is not None: + return self.duration + return 1.0 if self.name == "one_qubit" else 8.0 + + @property + def parameter_count(self) -> int: + control_count = 2 if self.name == "one_qubit" else 4 + return control_count * self.segments + + +@dataclass(frozen=True) +class DeviceConfig: + gap: float = 0.0 + shots: int | None = None + perturbation_seed: int = 0 + + def __post_init__(self) -> None: + _require_finite_number("gap", self.gap, positive=False) + if self.gap == 0: + object.__setattr__(self, "gap", 0.0) + if self.shots is not None: + _require_positive_integer("shots", self.shots) + _require_nonnegative_integer("perturbation_seed", self.perturbation_seed) + + +@dataclass(frozen=True) +class SearchConfig: + method: str + dimension: int + budget: int + + def __post_init__(self) -> None: + if self.method not in {"full", "model_hessian", "random", "oracle"}: + raise ValueError("unsupported search method") + _require_positive_integer("dimension", self.dimension) + _require_positive_integer("budget", self.budget) + + +@dataclass(frozen=True) +class ExperimentConfig: + run_kind: str + system: SystemConfig + device: DeviceConfig + search: SearchConfig + trial_seed: int + model_seed: int = 5 + + def __post_init__(self) -> None: + self.validate() + + def validate(self) -> None: + if self.run_kind not in {"development", "production"}: + raise ValueError("run_kind must be 'development' or 'production'") + if not isinstance(self.system, SystemConfig): + raise ValueError("system must be a SystemConfig") + if not isinstance(self.device, DeviceConfig): + raise ValueError("device must be a DeviceConfig") + if not isinstance(self.search, SearchConfig): + raise ValueError("search must be a SearchConfig") + _require_nonnegative_integer("model_seed", self.model_seed) + _require_nonnegative_integer("trial_seed", self.trial_seed) + if self.search.dimension > self.system.parameter_count: + raise ValueError("search dimension exceeds the system parameter count") + + expected_budget = 200 if self.run_kind == "development" else 2000 + if self.search.budget != expected_budget: + raise ValueError(f"{self.run_kind} budget must be {expected_budget}") + + def canonical_dict(self) -> dict[str, object]: + return { + "device": { + "gap": float(self.device.gap), + "perturbation_seed": self.device.perturbation_seed, + "shots": self.device.shots, + }, + "run_kind": self.run_kind, + "model_seed": self.model_seed, + "search": { + "budget": self.search.budget, + "dimension": self.search.dimension, + "method": self.search.method, + }, + "system": { + "amplitude_bound": float(self.system.amplitude_bound), + "duration": self.system.effective_duration, + "name": self.system.name, + "segments": self.system.segments, + }, + "trial_seed": self.trial_seed, + } + + def content_id(self) -> str: + payload = json.dumps( + self.canonical_dict(), + sort_keys=True, + separators=(",", ":"), + allow_nan=False, + ).encode("utf-8") + return hashlib.sha256(payload).hexdigest()[:20] diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/src/qcontrol/device.py b/tracks/qcs/solutions/frustration-free/challenge-113/src/qcontrol/device.py new file mode 100644 index 000000000..dc4556581 --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/src/qcontrol/device.py @@ -0,0 +1,603 @@ +from __future__ import annotations + +from collections.abc import Callable +from dataclasses import dataclass +import hashlib +import math +from numbers import Integral, Real +from threading import RLock +from typing import Protocol, runtime_checkable + +import numpy as np + +from qcontrol.config import DeviceConfig +from qcontrol.objectives import normalized_infidelity +from qcontrol.pulses import PulseSpace +from qcontrol.systems import ControlSystem + + +_CERTIFICATION_SHOTS = 100_000 +_CERTIFICATION_TARGET = 0.999 +_ONE_SIDED_95_Z = 1.6448536269514722 +_DIGEST_HEX_LENGTH = 64 + +RequestedShots = int | float | bool | str | None + + +class DeviceQueryError(RuntimeError): + __slots__ = ("attempt_index", "category") + + def __init__(self, attempt_index: int, category: str) -> None: + self.attempt_index = attempt_index + self.category = category + super().__init__(f"device query attempt {attempt_index} failed: {category}") + + +def _nonnegative_integer(name: str, value: object) -> int: + if isinstance(value, (bool, np.bool_)) or not isinstance(value, Integral): + raise ValueError(f"{name} must be a nonnegative integer") + result = int(value) + if result < 0: + raise ValueError(f"{name} must be a nonnegative integer") + return result + + +def _positive_integer(name: str, value: object) -> int: + result = _nonnegative_integer(name, value) + if result == 0: + raise ValueError(f"{name} must be a positive integer") + return result + + +def _sanitized_requested_shots(value: object) -> RequestedShots: + if value is None or isinstance(value, (bool, str)): + return value + if isinstance(value, Integral): + return int(value) + if isinstance(value, Real): + return float(value) + value_type = type(value) + return f"<{value_type.__module__}.{value_type.__qualname__}>" + + +def _default_seed_digest(seed: int) -> str: + return hashlib.sha256(str(seed).encode("ascii")).hexdigest() + + +@dataclass(frozen=True, slots=True) +class Observation: + estimate: float + shots: int + optimizer_query_index: int + validation: bool + observation_seed: int + attempt_index: int | None = None + seed_digest: str | None = None + + def __post_init__(self) -> None: + if isinstance(self.estimate, (bool, np.bool_)) or not isinstance( + self.estimate, Real + ): + raise ValueError("estimate must be a finite probability") + estimate = float(self.estimate) + if not math.isfinite(estimate) or not 0.0 <= estimate <= 1.0: + raise ValueError("estimate must be a finite probability") + shots = _nonnegative_integer("shots", self.shots) + optimizer_query_index = _nonnegative_integer( + "optimizer_query_index", + self.optimizer_query_index, + ) + observation_seed = _nonnegative_integer( + "observation_seed", + self.observation_seed, + ) + attempt_index = ( + optimizer_query_index + if self.attempt_index is None + else _positive_integer("attempt_index", self.attempt_index) + ) + seed_digest = ( + _default_seed_digest(observation_seed) + if self.seed_digest is None + else self.seed_digest + ) + if ( + not isinstance(seed_digest, str) + or len(seed_digest) != _DIGEST_HEX_LENGTH + or any(character not in "0123456789abcdef" for character in seed_digest) + ): + raise ValueError("seed_digest must be a lowercase SHA-256 digest") + if not isinstance(self.validation, (bool, np.bool_)): + raise ValueError("validation must be a boolean") + if self.validation and shots == 0: + raise ValueError("validation observations must use positive shots") + + object.__setattr__(self, "estimate", estimate) + object.__setattr__(self, "shots", shots) + object.__setattr__(self, "optimizer_query_index", optimizer_query_index) + object.__setattr__(self, "validation", bool(self.validation)) + object.__setattr__(self, "observation_seed", observation_seed) + object.__setattr__(self, "attempt_index", attempt_index) + object.__setattr__(self, "seed_digest", seed_digest) + + def certifies(self, threshold: float = _CERTIFICATION_TARGET) -> bool: + if isinstance(threshold, (bool, np.bool_)) or not isinstance(threshold, Real): + raise ValueError("threshold must be a finite probability") + threshold = float(threshold) + if not math.isfinite(threshold) or not 0.0 <= threshold <= 1.0: + raise ValueError("threshold must be a finite probability") + if ( + not self.validation + or self.shots != _CERTIFICATION_SHOTS + or threshold < _CERTIFICATION_TARGET + ): + return False + + z_squared = _ONE_SIDED_95_Z**2 + denominator = 1.0 + z_squared / self.shots + center = self.estimate + z_squared / (2.0 * self.shots) + radius = _ONE_SIDED_95_Z * math.sqrt( + self.estimate * (1.0 - self.estimate) / self.shots + + z_squared / (4.0 * self.shots**2) + ) + lower_bound = (center - radius) / denominator + return lower_bound >= threshold + + +@dataclass(frozen=True, slots=True) +class QueryRecord: + attempt_index: int + optimizer_query_index: int + validation: bool + status: str + requested_shots: RequestedShots + charged_shots: int + estimate: float | None + observation_seed: int | None + seed_digest: str | None + error_category: str | None + + @property + def success(self) -> bool: + return self.status == "succeeded" + + +@dataclass(frozen=True, slots=True) +class _AttemptRecord: + attempt_index: int + optimizer_query_index: int + validation: bool + status: str + requested_shots: RequestedShots + charged_shots: int + estimate: float | None + observation_seed: int | None + seed_digest: str | None + error_category: str | None + + @property + def success(self) -> bool: + return self.status == "succeeded" + + +def _public_record(record: _AttemptRecord) -> QueryRecord: + return QueryRecord( + attempt_index=record.attempt_index, + optimizer_query_index=record.optimizer_query_index, + validation=record.validation, + status=record.status, + requested_shots=record.requested_shots, + charged_shots=record.charged_shots, + estimate=record.estimate, + observation_seed=record.observation_seed, + seed_digest=record.seed_digest, + error_category=record.error_category, + ) + + +def _public_observation(record: _AttemptRecord) -> Observation: + if ( + not record.success + or record.estimate is None + or record.observation_seed is None + or record.seed_digest is None + ): + raise ValueError("failed attempts do not have observations") + return Observation( + estimate=record.estimate, + shots=record.charged_shots, + optimizer_query_index=record.optimizer_query_index, + validation=record.validation, + observation_seed=record.observation_seed, + attempt_index=record.attempt_index, + seed_digest=record.seed_digest, + ) + + +Certifier = Callable[[Observation, float, frozenset[int]], bool] + + +class QueryLedger: + __slots__ = ("_records", "_certifier", "_authorized_attempts") + + def __init__( + self, + records: tuple[_AttemptRecord, ...], + certifier: Certifier, + ) -> None: + self._records = records + self._certifier = certifier + self._authorized_attempts = frozenset( + record.attempt_index for record in records if record.success + ) + + @property + def records(self) -> tuple[QueryRecord, ...]: + return tuple(_public_record(record) for record in self._records) + + @property + def observations(self) -> tuple[Observation, ...]: + return tuple( + _public_observation(record) for record in self._records if record.success + ) + + @property + def optimizer_queries(self) -> int: + return sum(not record.validation for record in self._records) + + @property + def optimizer_shots(self) -> int: + return sum( + record.charged_shots for record in self._records if not record.validation + ) + + @property + def validation_queries(self) -> int: + return sum(record.validation for record in self._records) + + @property + def validation_shots(self) -> int: + return sum( + record.charged_shots for record in self._records if record.validation + ) + + @property + def total_queries(self) -> int: + return len(self._records) + + @property + def total_shots(self) -> int: + return sum(record.charged_shots for record in self._records) + + def certifies( + self, + observation: Observation, + threshold: float = _CERTIFICATION_TARGET, + ) -> bool: + return self._certifier(observation, threshold, self._authorized_attempts) + + def __reduce__(self) -> object: + raise TypeError("QueryLedger capability cannot be pickled") + + def __reduce_ex__(self, protocol: int) -> object: + raise TypeError("QueryLedger capability cannot be pickled") + + +@runtime_checkable +class QueryDevice(Protocol): + @property + def ledger(self) -> QueryLedger: ... + + def query(self, normalized_pulse: object) -> Observation: ... + + def validate( + self, + normalized_pulse: object, + shots: int = _CERTIFICATION_SHOTS, + ) -> Observation: ... + + def certifies( + self, + observation: Observation, + threshold: float = _CERTIFICATION_TARGET, + ) -> bool: ... + + +def _make_exact_fidelity_evaluator( + truth: ControlSystem, + space: PulseSpace, +) -> Callable[[object], float]: + if not isinstance(truth, ControlSystem): + raise ValueError("truth must be a ControlSystem") + if not isinstance(space, PulseSpace): + raise ValueError("space must be a PulseSpace") + if len(truth.controls) != space.control_count: + raise ValueError("pulse space control count does not match the truth system") + if tuple(truth.amplitude_scales) != tuple(space.amplitude_scales): + raise ValueError("pulse space amplitude scales do not match the truth system") + + def evaluate(normalized_pulse: object) -> float: + loss = float(normalized_infidelity(normalized_pulse, truth, space)) + fidelity = float(np.clip(1.0 - loss, 0.0, 1.0)) + if not math.isfinite(fidelity): + raise ValueError("truth evaluation did not produce a finite fidelity") + return fidelity + + return evaluate + + +def _seed_identity( + device_seed: int, + attempt_index: int, + validation: bool, +) -> tuple[int, str]: + payload = f"{device_seed}:{attempt_index}:{int(validation)}".encode("ascii") + digest = hashlib.sha256(payload).digest() + observation_seed = int.from_bytes(digest[:16], byteorder="big", signed=False) + return observation_seed, digest.hex() + + +def _failure_category(stage: str, error: Exception) -> str: + if stage == "request": + return "request_validation" + if stage == "seed": + return "seed_derivation_failure" + if stage == "seed_guard": + return "seed_collision" + if stage == "shot_validation": + return "invalid_shots" + if stage == "evaluation": + return "invalid_pulse" if isinstance(error, ValueError) else "propagation_failure" + if stage == "rng": + return "rng_failure" + if stage == "sampling": + return "sampling_failure" + if stage == "observation": + return "observation_failure" + return "internal_failure" + + +def _abort_category(error: BaseException) -> str: + if isinstance(error, KeyboardInterrupt): + return "keyboard_interrupt" + if isinstance(error, SystemExit): + return "system_exit" + return "process_abort" + + +def make_query_device( + truth: ControlSystem, + space: PulseSpace, + config: DeviceConfig, + *, + seed: int, +) -> QueryDevice: + if not isinstance(config, DeviceConfig): + raise ValueError("config must be a DeviceConfig") + device_seed = _nonnegative_integer("seed", seed) + evaluate = _make_exact_fidelity_evaluator(truth, space) + configured_shots = 0 if config.shots is None else config.shots + lock = RLock() + next_attempt_index = 0 + optimizer_attempts = 0 + records: dict[int, _AttemptRecord] = {} + issued: dict[int, tuple[Observation, int]] = {} + digest_by_seed: dict[int, str] = {} + seed_digests: set[str] = set() + + def allocate_attempt(validation: bool) -> _AttemptRecord: + nonlocal next_attempt_index, optimizer_attempts + with lock: + next_attempt_index += 1 + if not validation: + optimizer_attempts += 1 + reserved = _AttemptRecord( + attempt_index=next_attempt_index, + optimizer_query_index=optimizer_attempts, + validation=validation, + status="reserved", + requested_shots=None, + charged_shots=0, + estimate=None, + observation_seed=None, + seed_digest=None, + error_category=None, + ) + records[reserved.attempt_index] = reserved + return reserved + + def register_seed(observation_seed: int, seed_digest: str) -> bool: + with lock: + prior_digest = digest_by_seed.get(observation_seed) + seed_collision = ( + prior_digest is not None and prior_digest != seed_digest + ) or seed_digest in seed_digests + if prior_digest is None: + digest_by_seed[observation_seed] = seed_digest + seed_digests.add(seed_digest) + return seed_collision + + def finalize(record: _AttemptRecord, observation: Observation | None = None) -> None: + with lock: + records[record.attempt_index] = record + if observation is not None: + issued[id(observation)] = (observation, record.attempt_index) + + def terminal_record( + allocated: _AttemptRecord, + *, + status: str, + requested_shots: RequestedShots, + charged_shots: int, + observation_seed: int | None, + seed_digest: str | None, + estimate: float | None = None, + error_category: str | None = None, + ) -> _AttemptRecord: + return _AttemptRecord( + attempt_index=allocated.attempt_index, + optimizer_query_index=allocated.optimizer_query_index, + validation=allocated.validation, + status=status, + requested_shots=requested_shots, + charged_shots=charged_shots, + estimate=estimate, + observation_seed=observation_seed, + seed_digest=seed_digest, + error_category=error_category, + ) + + def invoke( + normalized_pulse: object, + *, + validation: bool, + raw_shots: object, + ) -> Observation: + allocated = allocate_attempt(validation) + requested_shots: RequestedShots = None + charged_shots = 0 + observation_seed: int | None = None + seed_digest: str | None = None + stage = "request" + public_error: DeviceQueryError | None = None + try: + requested_shots = _sanitized_requested_shots(raw_shots) + stage = "seed" + observation_seed, seed_digest = _seed_identity( + device_seed, + allocated.attempt_index, + validation, + ) + stage = "seed_guard" + if register_seed(observation_seed, seed_digest): + raise RuntimeError("deterministic observation seed collision") + stage = "shot_validation" + shots = ( + _positive_integer("shots", raw_shots) + if validation + else configured_shots + ) + stage = "evaluation" + exact_fidelity = evaluate(normalized_pulse) + if shots == 0: + estimate = exact_fidelity + else: + stage = "rng" + rng = np.random.default_rng(observation_seed) + stage = "sampling" + charged_shots = shots + successes = int(rng.binomial(shots, exact_fidelity)) + estimate = successes / shots + record = terminal_record( + allocated, + status="succeeded", + requested_shots=requested_shots, + charged_shots=charged_shots, + estimate=estimate, + observation_seed=observation_seed, + seed_digest=seed_digest, + ) + stage = "observation" + observation = _public_observation(record) + finalize(record, observation) + return observation + except Exception as error: + category = _failure_category(stage, error) + finalize( + terminal_record( + allocated, + status="failed", + requested_shots=requested_shots, + charged_shots=charged_shots, + observation_seed=observation_seed, + seed_digest=seed_digest, + error_category=category, + ) + ) + public_error = DeviceQueryError(allocated.attempt_index, category) + except BaseException as error: + finalize( + terminal_record( + allocated, + status="aborted", + requested_shots=requested_shots, + charged_shots=charged_shots, + observation_seed=observation_seed, + seed_digest=seed_digest, + error_category=_abort_category(error), + ) + ) + raise + if public_error is not None: + raise public_error from None + raise AssertionError("unreachable device query state") + + def certify( + observation: Observation, + threshold: float, + authorized_attempts: frozenset[int], + ) -> bool: + if not isinstance(observation, Observation): + return False + with lock: + authorization = issued.get(id(observation)) + if authorization is None or authorization[0] is not observation: + return False + attempt_index = authorization[1] + record = records.get(attempt_index) + if ( + attempt_index not in authorized_attempts + or record is None + or not record.success + ): + return False + canonical = _public_observation(record) + if observation != canonical: + return False + return canonical.certifies(threshold) + + class _OpaqueQueryDevice: + __slots__ = () + + @property + def ledger(self) -> QueryLedger: + with lock: + snapshot = tuple(records[index] for index in sorted(records)) + return QueryLedger(snapshot, certify) + + def query(self, normalized_pulse: object) -> Observation: + return invoke( + normalized_pulse, + validation=False, + raw_shots=configured_shots, + ) + + def validate( + self, + normalized_pulse: object, + shots: int = _CERTIFICATION_SHOTS, + ) -> Observation: + return invoke( + normalized_pulse, + validation=True, + raw_shots=shots, + ) + + def certifies( + self, + observation: Observation, + threshold: float = _CERTIFICATION_TARGET, + ) -> bool: + with lock: + authorized = frozenset( + record.attempt_index for record in records.values() if record.success + ) + return certify(observation, threshold, authorized) + + def __reduce__(self) -> object: + raise TypeError("QueryDevice capability cannot be pickled") + + def __reduce_ex__(self, protocol: int) -> object: + raise TypeError("QueryDevice capability cannot be pickled") + + return _OpaqueQueryDevice() diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/src/qcontrol/evidence.py b/tracks/qcs/solutions/frustration-free/challenge-113/src/qcontrol/evidence.py new file mode 100644 index 000000000..2091fa7ab --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/src/qcontrol/evidence.py @@ -0,0 +1,451 @@ +from __future__ import annotations + +import hashlib +import json +import math +from collections.abc import Mapping +from pathlib import Path +import re + + +_SHA256 = re.compile(r"[0-9a-f]{64}\Z", re.ASCII) +_REVISION = re.compile(r"[0-9a-f]{40}\Z", re.ASCII) +_PAYLOAD_FIELDS = { + "calibration": { + "config_sha256", + "cpu_count", + "exact_trajectory_seconds", + "first_query_compilation_inclusive_seconds", + "geometry_seconds", + "jax_platform", + "landscape_seconds", + "open_loop_seconds", + "parameter_count", + "peak_rss_kib", + "queries", + "restricted_nfev", + "restricted_optimization_seconds", + "search_dimension", + "warm_queries_per_second", + "warm_query_seconds", + "x64_enabled", + }, + "environment": { + "cpu_count", + "jax", + "jax_platform", + "jaxlib", + "numpy", + "platform", + "python", + "scipy", + "uv_lock_sha256", + "x64_enabled", + }, + "pilot": { + "artifact_bytes", + "config_sha256", + "evaluations", + "manifest_sha256", + "plan_sha256", + "ready_sha256", + "total_queries", + "trial_id", + "trial_sha256", + }, + "projection": { + "cpus_per_trial", + "formula", + "pilot_artifact_bytes", + "pilot_wall_seconds", + "projected_core_hours", + "projected_storage_bytes", + "projected_trial_hours", + "provisional", + "trial_count", + }, + "report_metadata": {"report_sha256"}, + "time": { + "command_sha256", + "cpu_percent", + "exit_status", + "peak_rss_kib", + "system_seconds", + "user_seconds", + "wall_seconds", + }, + "validation": {"completed", "errors", "expected", "pending", "valid"}, +} +REQUIRED_EVIDENCE_FILES = ( + "calibration.json", + "environment.json", + "pilot.json", + "projection.json", + "report_metadata.json", + "time.json", + "validation.json", + "index.json", +) + + +def _canonical_bytes(payload: object) -> bytes: + return ( + json.dumps( + payload, + allow_nan=False, + ensure_ascii=False, + separators=(",", ":"), + sort_keys=True, + ) + + "\n" + ).encode() + + +def _read_canonical(path: Path) -> tuple[dict[str, object], bytes]: + data = path.read_bytes() + try: + payload = json.loads(data) + except (UnicodeDecodeError, json.JSONDecodeError) as error: + raise ValueError(f"{path.name} is not valid JSON") from error + if not isinstance(payload, dict) or data != _canonical_bytes(payload): + raise ValueError(f"{path.name} is not canonical JSON") + return payload, data + + +def _sha256(data: bytes) -> str: + return hashlib.sha256(data).hexdigest() + + +def _require_hashes(value: object, *, name: str) -> dict[str, str]: + if ( + not isinstance(value, Mapping) + or not value + or any( + not isinstance(key, str) + or not key + or not isinstance(digest, str) + or _SHA256.fullmatch(digest) is None + for key, digest in value.items() + ) + ): + raise ValueError(f"{name} must contain canonical SHA256 values") + return dict(value) + + +def _exact(value: object, expected: type, name: str) -> object: + if type(value) is not expected: + raise ValueError(f"{name} has invalid JSON type") + return value + + +def _integer(value: object, name: str, *, minimum: int = 0) -> int: + _exact(value, int, name) + if value < minimum: + raise ValueError(f"{name} is outside its valid range") + return value + + +def _number(value: object, name: str, *, minimum: float = 0.0) -> float: + if type(value) not in {int, float} or not math.isfinite(value) or value < minimum: + raise ValueError(f"{name} must be finite and in range") + return float(value) + + +def _validate_payload(evidence_type: str, detail: Mapping[str, object]) -> None: + hashes = { + "config_sha256", + "manifest_sha256", + "plan_sha256", + "ready_sha256", + "report_sha256", + "trial_sha256", + "uv_lock_sha256", + "command_sha256", + } + for name in hashes & set(detail): + _exact(detail[name], str, name) + if _SHA256.fullmatch(detail[name]) is None: + raise ValueError(f"{name} is not a SHA256 digest") + integer_fields = { + "artifact_bytes", + "completed", + "cpu_count", + "cpus_per_trial", + "evaluations", + "expected", + "exit_status", + "parameter_count", + "peak_rss_kib", + "pending", + "queries", + "restricted_nfev", + "search_dimension", + "total_queries", + "trial_count", + } + for name in integer_fields & set(detail): + _integer(detail[name], name) + number_fields = { + "cpu_percent", + "exact_trajectory_seconds", + "first_query_compilation_inclusive_seconds", + "geometry_seconds", + "landscape_seconds", + "open_loop_seconds", + "pilot_wall_seconds", + "projected_core_hours", + "projected_storage_bytes", + "projected_trial_hours", + "restricted_optimization_seconds", + "system_seconds", + "user_seconds", + "wall_seconds", + "warm_queries_per_second", + "warm_query_seconds", + } + for name in number_fields & set(detail): + _number(detail[name], name) + for name in ("x64_enabled", "provisional", "valid"): + if name in detail: + _exact(detail[name], bool, name) + if "errors" in detail and ( + type(detail["errors"]) is not list + or any(type(item) is not str for item in detail["errors"]) + ): + raise ValueError("errors must be a JSON string array") + for name in ( + "formula", + "jax", + "jax_platform", + "jaxlib", + "numpy", + "platform", + "python", + "scipy", + "trial_id", + ): + if name in detail: + _exact(detail[name], str, name) + if evidence_type in {"calibration", "environment"}: + if detail["x64_enabled"] is not True: + raise ValueError("evidence must use the x64 runtime") + if detail["jax_platform"] not in {"cpu", "gpu", "tpu"}: + raise ValueError("JAX platform is invalid") + if evidence_type == "projection" and detail["provisional"] is not True: + raise ValueError("projection must remain provisional") + + +def validate_evidence_document(payload: object) -> None: + if not isinstance(payload, Mapping) or set(payload) != { + "evidence_type", + "inputs", + "payload", + "schema_version", + "source_revision", + }: + raise ValueError("evidence fields are not canonical") + evidence_type = payload["evidence_type"] + if evidence_type not in _PAYLOAD_FIELDS: + raise ValueError("unsupported evidence type") + if type(payload["schema_version"]) is not int or payload["schema_version"] != 1: + raise ValueError("unsupported evidence schema") + revision = payload["source_revision"] + if not isinstance(revision, str) or _REVISION.fullmatch(revision) is None: + raise ValueError("evidence source revision is invalid") + _require_hashes(payload["inputs"], name="evidence inputs") + detail = payload["payload"] + if not isinstance(detail, Mapping) or set(detail) != _PAYLOAD_FIELDS[evidence_type]: + raise ValueError(f"{evidence_type} payload fields are not canonical") + _validate_payload(str(evidence_type), detail) + + +def validate_evidence_directory(root: str | Path) -> dict[str, str]: + directory = Path(root) + expected_documents = set(REQUIRED_EVIDENCE_FILES) - {"index.json"} + document_hashes: dict[str, str] = {} + documents: dict[str, dict[str, object]] = {} + revisions: set[str] = set() + for name in sorted(expected_documents): + payload, data = _read_canonical(directory / name) + validate_evidence_document(payload) + document_hashes[name] = _sha256(data) + revisions.add(str(payload["source_revision"])) + documents[name] = payload + index, index_data = _read_canonical(directory / "index.json") + if ( + set(index) != {"documents", "schema_version", "source_revision"} + or type(index["schema_version"]) is not int + or index["schema_version"] != 1 + or index["documents"] != document_hashes + or len(revisions) != 1 + or index["source_revision"] not in revisions + ): + raise ValueError("evidence index is stale or noncanonical") + calibration = documents["calibration.json"] + environment = documents["environment.json"] + pilot = documents["pilot.json"] + projection = documents["projection.json"] + timing = documents["time.json"] + validation = documents["validation.json"] + for dependent_name, required in { + "projection.json": {"pilot": "pilot.json", "time": "time.json"}, + }.items(): + inputs = documents[dependent_name]["inputs"] + for key, source_name in required.items(): + if inputs[key] != document_hashes[source_name]: + raise ValueError("cross-document evidence hash is stale") + calibration_payload = calibration["payload"] + environment_payload = environment["payload"] + pilot_payload = pilot["payload"] + projection_payload = projection["payload"] + timing_payload = timing["payload"] + validation_payload = validation["payload"] + if ( + calibration_payload["jax_platform"] != environment_payload["jax_platform"] + or calibration_payload["x64_enabled"] != environment_payload["x64_enabled"] + or calibration["inputs"]["uv_lock"] != environment_payload["uv_lock_sha256"] + or environment["inputs"]["uv_lock"] != environment_payload["uv_lock_sha256"] + or pilot["inputs"]["uv_lock"] != environment_payload["uv_lock_sha256"] + or pilot["inputs"]["plan"] != pilot_payload["plan_sha256"] + or pilot["inputs"]["trial"] != pilot_payload["trial_sha256"] + or timing["inputs"]["trial"] != pilot_payload["trial_sha256"] + or validation["inputs"]["trial"] != pilot_payload["trial_sha256"] + or validation["inputs"]["ready"] != pilot_payload["ready_sha256"] + or pilot_payload["evaluations"] != pilot_payload["total_queries"] + or timing_payload["wall_seconds"] != projection_payload["pilot_wall_seconds"] + or validation_payload["valid"] is not True + or validation_payload["completed"] != validation_payload["expected"] + or validation_payload["pending"] != 0 + or validation_payload["errors"] != [] + or pilot_payload["config_sha256"] != calibration_payload["config_sha256"] + ): + raise ValueError("evidence documents are semantically inconsistent") + trial_count = projection_payload["trial_count"] + trial_hours = projection_payload["pilot_wall_seconds"] * trial_count / 3600 + if ( + projection_payload["projected_trial_hours"] != trial_hours + or projection_payload["projected_core_hours"] + != trial_hours * projection_payload["cpus_per_trial"] + or projection_payload["projected_storage_bytes"] + != projection_payload["pilot_artifact_bytes"] * trial_count + or projection_payload["pilot_artifact_bytes"] + != pilot_payload["artifact_bytes"] + ): + raise ValueError("projection arithmetic is inconsistent") + return {**document_hashes, "index.json": _sha256(index_data)} + + +def validate_deployment( + root: str | Path, + *, + archive_path: str | Path, + deployment_metadata_path: str | Path, + expected_revision: str, + expected_archive_sha256: str, + expected_evidence_revision: str, + expected_sif_sha256: str, + expected_deployment_metadata_sha256: str, + expected_pyproject_sha256: str, + expected_uv_lock_sha256: str, + expected_cluster_profile: str, +) -> None: + directory = Path(root) + archive = Path(archive_path) + metadata = Path(deployment_metadata_path) + if metadata.is_symlink() or not metadata.is_file(): + raise ValueError("deployment metadata must be a regular file") + try: + metadata.resolve(strict=True).relative_to(directory.resolve(strict=True)) + except ValueError: + pass + else: + raise ValueError("deployment metadata must live outside the source tree") + if ( + not isinstance(expected_deployment_metadata_sha256, str) + or _SHA256.fullmatch(expected_deployment_metadata_sha256) is None + ): + raise ValueError("expected deployment metadata SHA256 is invalid") + deployment, metadata_bytes = _read_canonical(metadata) + if _sha256(metadata_bytes) != expected_deployment_metadata_sha256: + raise ValueError("deployment metadata bytes are stale") + if set(deployment) != { + "archive_name", + "archive_sha256", + "cluster_profile", + "critical_packages", + "evidence_index_sha256", + "pyproject_sha256", + "python_version", + "report_sha256", + "revision", + "schema_version", + "sif_name", + "sif_sha256", + "uv_lock_sha256", + "uv_version", + } or deployment.get("schema_version") != 1: + raise ValueError("deployment metadata is noncanonical") + if deployment.get("revision") != expected_revision: + raise ValueError("deployment revision is stale") + if deployment.get("archive_sha256") != expected_archive_sha256: + raise ValueError("deployment archive is stale") + if ( + deployment.get("sif_sha256") != expected_sif_sha256 + or deployment.get("pyproject_sha256") != expected_pyproject_sha256 + or deployment.get("uv_lock_sha256") != expected_uv_lock_sha256 + or deployment.get("cluster_profile") != expected_cluster_profile + ): + raise ValueError("deployment runtime binding is stale") + expected_packages = { + "jax": "0.11.0", + "jaxlib": "0.11.0", + "numpy": "2.5.1", + "scipy": "1.18.0", + } + if ( + deployment.get("python_version") != "3.12.12" + or deployment.get("uv_version") != "0.9.9" + or deployment.get("critical_packages") != expected_packages + ): + raise ValueError("deployment runtime versions are stale") + archive_name = deployment.get("archive_name") + if ( + not isinstance(archive_name, str) + or not archive_name + or Path(archive_name).name != archive_name + or archive.name != archive_name + or archive_name != f"challenge-113-{expected_revision[:7]}.tar.gz" + ): + raise ValueError("deployment archive name is invalid") + if _sha256(archive.read_bytes()) != expected_archive_sha256: + raise ValueError("deployment archive bytes are stale") + if deployment.get("sif_name") != "uv-0.9.9-python3.12-bookworm-slim.sif": + raise ValueError("deployment SIF identity is stale") + if ( + _sha256((directory / "pyproject.toml").read_bytes()) + != expected_pyproject_sha256 + or _sha256((directory / "uv.lock").read_bytes()) + != expected_uv_lock_sha256 + ): + raise ValueError("deployment lock inputs are stale") + evidence_directory = directory / "evidence" / "task10a" + hashes = validate_evidence_directory(evidence_directory) + index, index_data = _read_canonical(evidence_directory / "index.json") + if index.get("source_revision") != expected_evidence_revision: + raise ValueError("deployment evidence revision is stale") + if ( + deployment.get("evidence_index_sha256") != hashes["index.json"] + or deployment.get("evidence_index_sha256") != _sha256(index_data) + ): + raise ValueError("deployment evidence binding is stale") + report_metadata, _ = _read_canonical( + directory / "evidence" / "task10a" / "report_metadata.json" + ) + validate_evidence_document(report_metadata) + expected_report = report_metadata["payload"]["report_sha256"] + report_sha256 = _sha256((directory / "REPORT.md").read_bytes()) + if ( + report_sha256 != expected_report + or deployment.get("report_sha256") != report_sha256 + ): + raise ValueError("deployment report metadata is stale") diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/src/qcontrol/experiments.py b/tracks/qcs/solutions/frustration-free/challenge-113/src/qcontrol/experiments.py new file mode 100644 index 000000000..6ee9a0f5b --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/src/qcontrol/experiments.py @@ -0,0 +1,1630 @@ +from __future__ import annotations + +from collections.abc import Callable, Iterable, Mapping, Sequence +from dataclasses import dataclass, replace +import hashlib +import json +import math +from pathlib import Path +import re +import threading +from typing import Any + +import jax +import jax.numpy as jnp +import numpy as np + +from qcontrol.artifacts import ArtifactConflict, ArtifactStore, canonical_json_bytes +from qcontrol.closed_loop import SearchSpace, make_search_space, run_closed_loop +from qcontrol.config import DeviceConfig, ExperimentConfig, SearchConfig, SystemConfig +from qcontrol.device import Observation, make_query_device +from qcontrol.landscape import analyze_landscape +from qcontrol.objectives import normalized_infidelity +from qcontrol.offline import ( + classify_solver_termination, + compute_geometry_diagnostics, + cumulative_best_exact_infidelity, + finalize_restricted_attained_bound, + make_offline_evaluator, + optimize_restricted_noiseless_upper_bound, +) +from qcontrol.open_loop import optimize_open_loop +from qcontrol.pulses import PulseSpace +from qcontrol.systems import ControlSystem, make_system, perturb_system + + +_DERIVED_CACHE_LOCK = threading.RLock() +_DERIVED_STATIC_CACHE: dict[str, dict[tuple[object, ...], object]] = {} +_MODEL_STATIC_CACHE: dict[tuple[bytes, int], ModelPreparation] = {} + + +def _content_id(prefix: str, payload: object, length: int = 24) -> str: + digest = hashlib.sha256(canonical_json_bytes(payload)).hexdigest()[:length] + return f"{prefix}-{digest}" + + +def _stream_seed(stream_id: str) -> int: + digest = hashlib.sha256(stream_id.encode("ascii")).digest() + return int.from_bytes(digest[:8], "big") + + +def _array_sha256(value: object) -> str: + array = np.ascontiguousarray(np.asarray(value, dtype=np.float64)) + digest = hashlib.sha256() + digest.update( + canonical_json_bytes( + {"dtype": "float64", "shape": list(array.shape)} + ) + ) + digest.update(array.tobytes()) + return digest.hexdigest() + + +def config_from_dict(payload: object) -> ExperimentConfig: + if not isinstance(payload, Mapping): + raise ValueError("configuration payload must be a mapping") + if set(payload) != { + "device", + "model_seed", + "run_kind", + "search", + "system", + "trial_seed", + }: + raise ValueError("configuration payload fields are not canonical") + try: + system = payload["system"] + device = payload["device"] + search = payload["search"] + if not all(isinstance(item, Mapping) for item in (system, device, search)): + raise ValueError("nested configuration payloads must be mappings") + if set(system) != { + "amplitude_bound", + "duration", + "name", + "segments", + } or set(device) != { + "gap", + "perturbation_seed", + "shots", + } or set(search) != { + "budget", + "dimension", + "method", + }: + raise ValueError("nested configuration fields are not canonical") + return ExperimentConfig( + run_kind=payload["run_kind"], + system=SystemConfig( + name=system["name"], + segments=system["segments"], + amplitude_bound=system["amplitude_bound"], + duration=system["duration"], + ), + device=DeviceConfig( + gap=device["gap"], + shots=device["shots"], + perturbation_seed=device["perturbation_seed"], + ), + search=SearchConfig( + method=search["method"], + dimension=search["dimension"], + budget=search["budget"], + ), + trial_seed=payload["trial_seed"], + model_seed=payload["model_seed"], + ) + except KeyError as error: + raise ValueError(f"configuration payload missing {error.args[0]!r}") from None + + +@dataclass(frozen=True, slots=True) +class TrialSpec: + trial_id: str + device_id: str + observation_stream_id: str + config: ExperimentConfig + + def __post_init__(self) -> None: + for name, value in ( + ("trial ID", self.trial_id), + ("device ID", self.device_id), + ("observation stream ID", self.observation_stream_id), + ): + if ( + not isinstance(value, str) + or re.fullmatch( + r"[a-z0-9][a-z0-9_-]*", + value, + re.ASCII, + ) + is None + ): + raise ValueError(f"{name} must be a strict ASCII token") + if not isinstance(self.config, ExperimentConfig): + raise ValueError("config must be an ExperimentConfig") + + def canonical_dict(self) -> dict[str, object]: + return { + "config": self.config.canonical_dict(), + "device_id": self.device_id, + "observation_stream_id": self.observation_stream_id, + "trial_id": self.trial_id, + } + + @classmethod + def from_canonical_dict(cls, payload: object) -> TrialSpec: + if not isinstance(payload, Mapping): + raise ValueError("trial specification must be a mapping") + try: + spec = cls( + trial_id=payload["trial_id"], + device_id=payload["device_id"], + observation_stream_id=payload["observation_stream_id"], + config=config_from_dict(payload["config"]), + ) + except KeyError as error: + raise ValueError(f"trial specification missing {error.args[0]!r}") from None + expected = generate_paired_trials([spec.config]) + if len(expected) != 1 or expected[0] != spec: + raise ValueError("trial specification identities are not canonical") + return spec + + +@dataclass(frozen=True, slots=True) +class ModelPreparation: + model_seed: int + model: ControlSystem + pulse_space: PulseSpace + open_loop: object + landscape: object + origin: np.ndarray + + +_OBSERVATION_FIELDS = { + "attempt_index", + "estimate", + "observation_seed", + "optimizer_query_index", + "seed_digest", + "shots", + "validation", +} +_ATTEMPT_FIELDS = { + "attempt_index", + "charged_shots", + "error_category", + "estimate", + "observation_seed", + "optimizer_query_index", + "requested_shots", + "seed_digest", + "status", + "validation", +} +_RESULT_FIELDS = { + "best_observation", + "best_pulse", + "budget", + "budget_exhausted", + "certified", + "evaluations", + "first_certified_query", + "observations", + "provisional_crossings", + "schema_version", + "search", + "stop_reason", + "validation_attempts", + "validation_result", +} +_DERIVED_RESULT_FIELDS = _RESULT_FIELDS | {"derived_metrics"} + + +def _exact_nonnegative_int(value: object, *, name: str) -> int: + if type(value) is not int or value < 0: + raise ValueError(f"{name} must be a nonnegative integer") + return value + + +def _observation(value: object, *, validation: bool | None = None) -> dict[str, object]: + if not isinstance(value, Mapping) or set(value) != _OBSERVATION_FIELDS: + raise ValueError("observation fields are not canonical") + payload = dict(value) + estimate = payload["estimate"] + if ( + isinstance(estimate, bool) + or not isinstance(estimate, (int, float)) + or not math.isfinite(float(estimate)) + or not 0.0 <= float(estimate) <= 1.0 + ): + raise ValueError("observation estimate must be a finite probability") + for key in ("attempt_index", "observation_seed", "optimizer_query_index", "shots"): + _exact_nonnegative_int(payload[key], name=key) + if type(payload["validation"]) is not bool: + raise ValueError("observation validation must be a boolean") + if validation is not None and payload["validation"] is not validation: + raise ValueError("observation kind is inconsistent") + if ( + not isinstance(payload["seed_digest"], str) + or re.fullmatch(r"[0-9a-f]{64}", payload["seed_digest"], re.ASCII) is None + ): + raise ValueError("observation seed digest is invalid") + return payload + + +def _attempt_observation(attempt: Mapping[str, object]) -> dict[str, object]: + return { + "attempt_index": attempt["attempt_index"], + "estimate": attempt["estimate"], + "observation_seed": attempt["observation_seed"], + "optimizer_query_index": attempt["optimizer_query_index"], + "seed_digest": attempt["seed_digest"], + "shots": attempt["charged_shots"], + "validation": attempt["validation"], + } + + +def _finite_probability(value: object, *, name: str) -> float: + if ( + isinstance(value, bool) + or not isinstance(value, (int, float)) + or not math.isfinite(float(value)) + or not 0.0 <= float(value) <= 1.0 + ): + raise ValueError(f"{name} must be a finite probability") + return float(value) + + +def _validate_derived_metrics( + value: object, + *, + evaluations: int, + attempts: tuple[dict[str, object], ...], + config: ExperimentConfig, +) -> None: + if not isinstance(value, Mapping) or set(value) != { + "exact_infidelity", + "geometry", + "restricted_noiseless_optimization", + }: + raise ValueError("derived metric fields are not canonical") + exact = value["exact_infidelity"] + if not isinstance(exact, Mapping) or set(exact) != { + "best_successful_audited_infidelity", + "cumulative_best_by_optimizer_query", + "initial_infidelity", + }: + raise ValueError("exact-infidelity fields are not canonical") + initial = _finite_probability( + exact["initial_infidelity"], + name="initial exact infidelity", + ) + best_successful_raw = exact["best_successful_audited_infidelity"] + best_successful = ( + None + if best_successful_raw is None + else _finite_probability( + best_successful_raw, + name="best successful audited exact infidelity", + ) + ) + trajectory = exact["cumulative_best_by_optimizer_query"] + if not isinstance(trajectory, list) or len(trajectory) != evaluations: + raise ValueError("exact-infidelity trajectory must align with optimizer queries") + values = [ + _finite_probability(item, name="exact-infidelity trajectory value") + for item in trajectory + ] + if any(current > previous for previous, current in zip([initial, *values], values)): + raise ValueError("exact-infidelity trajectory must be cumulative nonincreasing") + optimizer_attempts = [item for item in attempts if not item["validation"]] + has_successful_audit = any( + item["status"] == "succeeded" for item in optimizer_attempts + ) + if has_successful_audit != (best_successful is not None): + raise ValueError("best audited exact infidelity lacks matching query evidence") + expected_final = ( + initial + if best_successful is None + else min(initial, best_successful) + ) + if values and values[-1] != expected_final: + raise ValueError("exact-infidelity trajectory and audited best disagree") + previous = initial + for item, current in zip(optimizer_attempts, values, strict=True): + if item["status"] != "succeeded" and current != previous: + raise ValueError("failed optimizer queries require exact carry-forward") + previous = current + + restricted = value["restricted_noiseless_optimization"] + restricted_fields = { + "attained_infidelity_upper_bound", + "attained_infidelity_source", + "best_successful_audited_exact_infidelity", + "cached_solver_attained_infidelity_upper_bound", + "cached_solver_starting_infidelity_upper_bound", + "certified", + "consistency_tolerance", + "gradient_tolerance", + "max_evaluations", + "max_iterations", + "nfev", + "nit", + "solver", + "solver_message_code", + "solver_output_finite", + "solver_status", + "solver_success", + "initial_exact_infidelity", + "termination", + } + if not isinstance(restricted, Mapping) or set(restricted) != restricted_fields: + raise ValueError("restricted-optimization fields are not canonical") + attained = _finite_probability( + restricted["attained_infidelity_upper_bound"], + name="restricted attained upper bound", + ) + cached_attained = _finite_probability( + restricted["cached_solver_attained_infidelity_upper_bound"], + name="cached solver attained upper bound", + ) + cached_starting = _finite_probability( + restricted["cached_solver_starting_infidelity_upper_bound"], + name="cached solver starting upper bound", + ) + if ( + type(restricted["max_iterations"]) is not int + or restricted["max_iterations"] <= 0 + or type(restricted["max_evaluations"]) is not int + or restricted["max_evaluations"] <= 0 + or type(restricted["nfev"]) is not int + or restricted["nfev"] <= 0 + or type(restricted["nit"]) is not int + or restricted["nit"] < 0 + or type(restricted["certified"]) is not bool + or type(restricted["solver_success"]) is not bool + or type(restricted["solver_status"]) is not int + or type(restricted["solver_output_finite"]) is not bool + or restricted["solver_message_code"] + not in { + "convergence", + "evaluation_limit", + "iteration_limit", + "line_search_failure", + "numerical_failure", + "solver_failure", + } + or restricted["solver"] != "L-BFGS-B" + or restricted["attained_infidelity_source"] + not in {"restricted_solver", "audited_candidate", "initial_origin"} + or restricted["termination"] + not in { + "converged", + "evaluation_limit", + "iteration_limit", + "line_search_failure", + "numerical_failure", + "solver_failure", + } + or restricted["certified"] + != (restricted["termination"] == "converged") + ): + raise ValueError("restricted-optimization solver metadata is invalid") + gradient_tolerance = restricted["gradient_tolerance"] + consistency_tolerance = restricted["consistency_tolerance"] + if ( + isinstance(gradient_tolerance, bool) + or not isinstance(gradient_tolerance, (int, float)) + or not math.isfinite(float(gradient_tolerance)) + or float(gradient_tolerance) <= 0.0 + or isinstance(consistency_tolerance, bool) + or not isinstance(consistency_tolerance, (int, float)) + or not math.isfinite(float(consistency_tolerance)) + or float(consistency_tolerance) < 0.0 + ): + raise ValueError("restricted-optimization tolerances are invalid") + restricted_initial = _finite_probability( + restricted["initial_exact_infidelity"], + name="restricted initial exact infidelity", + ) + restricted_best_raw = restricted["best_successful_audited_exact_infidelity"] + restricted_best = ( + None + if restricted_best_raw is None + else _finite_probability( + restricted_best_raw, + name="restricted best audited exact infidelity", + ) + ) + tolerance = float(consistency_tolerance) + + def within_tolerance(left: float, right: float) -> bool: + return abs(left - right) <= tolerance + + evidence = {"initial_origin": initial} + if best_successful is not None: + evidence["audited_candidate"] = best_successful + if restricted["solver_output_finite"]: + evidence["restricted_solver"] = cached_attained + expected_bound = min(evidence.values()) + source = restricted["attained_infidelity_source"] + if ( + not within_tolerance(restricted_initial, initial) + or ( + (restricted_best is None) != (best_successful is None) + or ( + restricted_best is not None + and best_successful is not None + and not within_tolerance(restricted_best, best_successful) + ) + ) + or not within_tolerance(cached_starting, initial) + or ( + not restricted["solver_output_finite"] + and not within_tolerance(cached_attained, cached_starting) + ) + or not within_tolerance(attained, expected_bound) + or source not in evidence + or not within_tolerance(evidence[source], attained) + or cached_attained > cached_starting + tolerance + or attained > min([initial, *values]) + tolerance + ): + raise ValueError("restricted attained upper bound is inconsistent") + classified = classify_solver_termination( + success=restricted["solver_success"], + status=restricted["solver_status"], + message_code=restricted["solver_message_code"], + output_finite=restricted["solver_output_finite"], + nit=restricted["nit"], + nfev=restricted["nfev"], + max_iterations=restricted["max_iterations"], + max_evaluations=restricted["max_evaluations"], + ) + if classified != restricted["termination"]: + raise ValueError("restricted-optimization termination is inconsistent") + + geometry = value["geometry"] + geometry_fields = { + "model_effective_ranks", + "model_top_subspace_sha256", + "principal_angles_radians", + "rank_thresholds", + "signed_leading_eigenvalue_gaps", + "truth_effective_ranks", + "truth_top_subspace_sha256", + } + if not isinstance(geometry, Mapping) or set(geometry) != geometry_fields: + raise ValueError("geometry fields are not canonical") + thresholds = geometry["rank_thresholds"] + model_ranks = geometry["model_effective_ranks"] + truth_ranks = geometry["truth_effective_ranks"] + if ( + not isinstance(thresholds, list) + or thresholds != [1e-6, 1e-8, 1e-10] + or not isinstance(model_ranks, list) + or not isinstance(truth_ranks, list) + or len(model_ranks) != len(thresholds) + or len(truth_ranks) != len(thresholds) + ): + raise ValueError("geometry rank diagnostics are dimensionally inconsistent") + for ranks in (model_ranks, truth_ranks): + if any( + type(rank) is not int + or not 0 <= rank <= config.system.parameter_count + for rank in ranks + ) or any(current < previous for previous, current in zip(ranks, ranks[1:])): + raise ValueError("geometry effective ranks are invalid") + dimension = config.search.dimension + gaps = geometry["signed_leading_eigenvalue_gaps"] + angles = geometry["principal_angles_radians"] + if ( + not isinstance(gaps, list) + or not isinstance(angles, list) + or len(gaps) != dimension + or len(angles) != dimension + or any( + isinstance(item, bool) + or not isinstance(item, (int, float)) + or not math.isfinite(float(item)) + for item in gaps + ) + or any( + isinstance(item, bool) + or not isinstance(item, (int, float)) + or not math.isfinite(float(item)) + or not 0.0 <= float(item) <= math.pi / 2.0 + 1e-12 + for item in angles + ) + ): + raise ValueError("geometry subspace diagnostics are invalid") + for key in ("model_top_subspace_sha256", "truth_top_subspace_sha256"): + if ( + not isinstance(geometry[key], str) + or re.fullmatch(r"[0-9a-f]{64}", geometry[key], re.ASCII) is None + ): + raise ValueError("geometry subspace hash is invalid") + + +def _validate_result_and_attempts( + result: object, + ledger: Mapping[str, int], + attempts: tuple[dict[str, object], ...], + config: ExperimentConfig, +) -> None: + if not isinstance(result, Mapping): + raise ValueError("public result fields are not canonical") + schema_version = result.get("schema_version") + expected_fields = _DERIVED_RESULT_FIELDS if schema_version == 3 else _RESULT_FIELDS + if set(result) != expected_fields: + raise ValueError("public result fields are not canonical") + if type(schema_version) is not int or schema_version not in {2, 3}: + raise ValueError("unsupported public result schema") + if schema_version == 2 and config.run_kind == "production": + raise ValueError("production trials require derived-metric schema 3") + search = result["search"] + if not isinstance(search, Mapping) or set(search) != { + "basis_sha256", + "dimension", + "method", + "origin_sha256", + "source_basis_sha256", + }: + raise ValueError("public search identity fields are not canonical") + if ( + search["method"] != config.search.method + or search["dimension"] != config.search.dimension + or type(search["dimension"]) is not int + or any( + not isinstance(search[key], str) + or re.fullmatch(r"[0-9a-f]{64}", search[key], re.ASCII) is None + for key in ("basis_sha256", "origin_sha256", "source_basis_sha256") + ) + ): + raise ValueError("public search identity does not match configuration") + evaluations = _exact_nonnegative_int( + result["evaluations"], + name="evaluations", + ) + budget = _exact_nonnegative_int(result["budget"], name="budget") + if budget != config.search.budget or evaluations != ledger["optimizer_queries"]: + raise ValueError("result budget/evaluations do not match config and ledger") + if type(result["budget_exhausted"]) is not bool or type(result["certified"]) is not bool: + raise ValueError("result completion flags must be booleans") + if result["stop_reason"] not in {"budget", "certified", "optimizer_stopped"}: + raise ValueError("result stop reason is invalid") + if ( + not isinstance(result["best_pulse"], list) + or len(result["best_pulse"]) != config.system.parameter_count + or any( + isinstance(item, bool) + or not isinstance(item, (int, float)) + or not math.isfinite(float(item)) + or not -1.0 <= float(item) <= 1.0 + for item in result["best_pulse"] + ) + ): + raise ValueError("best pulse must contain finite numbers") + if not isinstance(result["observations"], list) or not isinstance( + result["validation_attempts"], list + ): + raise ValueError("result histories must be lists") + first_certified = result["first_certified_query"] + if first_certified is not None: + _exact_nonnegative_int( + first_certified, + name="first_certified_query", + ) + if not isinstance(result["provisional_crossings"], list) or any( + type(item) is not int or item <= 0 + for item in result["provisional_crossings"] + ): + raise ValueError("provisional crossings must be positive integers") + if result["certified"]: + if ( + result["stop_reason"] != "certified" + or result["budget_exhausted"] + or first_certified is None + ): + raise ValueError("certified result state is inconsistent") + elif evaluations == budget: + if result["stop_reason"] != "budget" or not result["budget_exhausted"]: + raise ValueError("budget-exhausted result state is inconsistent") + elif result["stop_reason"] != "optimizer_stopped" or result["budget_exhausted"]: + raise ValueError("early result state is inconsistent") + + if any(not isinstance(attempt, Mapping) or set(attempt) != _ATTEMPT_FIELDS for attempt in attempts): + raise ValueError("attempt fields are not canonical") + indices: list[int] = [] + optimizer_records: list[dict[str, object]] = [] + validation_records: list[dict[str, object]] = [] + for attempt in attempts: + attempt_index = _exact_nonnegative_int( + attempt["attempt_index"], + name="attempt_index", + ) + if attempt_index == 0: + raise ValueError("attempt indices are one-based") + indices.append(attempt_index) + _exact_nonnegative_int( + attempt["optimizer_query_index"], + name="optimizer_query_index", + ) + _exact_nonnegative_int(attempt["charged_shots"], name="charged_shots") + requested = attempt["requested_shots"] + if requested is not None: + _exact_nonnegative_int(requested, name="requested_shots") + if type(attempt["validation"]) is not bool: + raise ValueError("attempt validation must be a boolean") + if attempt["status"] not in {"succeeded", "failed", "aborted"}: + raise ValueError("attempt status is invalid") + if attempt["status"] == "succeeded": + _observation(_attempt_observation(attempt), validation=attempt["validation"]) + if attempt["error_category"] is not None: + raise ValueError("successful attempts cannot have errors") + else: + if attempt["estimate"] is not None or not isinstance( + attempt["error_category"], str + ): + raise ValueError("failed attempts require only an error category") + if attempt["observation_seed"] is not None: + _exact_nonnegative_int( + attempt["observation_seed"], + name="observation_seed", + ) + if attempt["seed_digest"] is not None and ( + not isinstance(attempt["seed_digest"], str) + or re.fullmatch( + r"[0-9a-f]{64}", + attempt["seed_digest"], + re.ASCII, + ) + is None + ): + raise ValueError("failed-attempt seed digest is invalid") + expected_requested = ( + 100_000 + if attempt["validation"] + else (0 if config.device.shots is None else config.device.shots) + ) + if requested != expected_requested: + raise ValueError("attempt requested shots do not match configuration") + if attempt["charged_shots"] not in {0, expected_requested}: + raise ValueError("attempt charged shots are inconsistent") + if ( + attempt["status"] == "succeeded" + and attempt["charged_shots"] != expected_requested + ): + raise ValueError("successful attempt must charge requested shots") + (validation_records if attempt["validation"] else optimizer_records).append(attempt) + + if indices != list(range(1, len(attempts) + 1)): + raise ValueError("attempt indices must be unique and contiguous") + if [item["optimizer_query_index"] for item in optimizer_records] != list( + range(1, len(optimizer_records) + 1) + ): + raise ValueError("optimizer query indices are not contiguous") + if len(optimizer_records) != ledger["optimizer_queries"] or len( + validation_records + ) != ledger["validation_queries"]: + raise ValueError("attempt counts do not match ledger") + if sum(item["charged_shots"] for item in optimizer_records) != ledger[ + "optimizer_shots" + ] or sum(item["charged_shots"] for item in validation_records) != ledger[ + "validation_shots" + ]: + raise ValueError("attempt shots do not match ledger") + if len(attempts) != ledger["total_queries"]: + raise ValueError("attempt total does not match ledger") + + optimizer_observations = [ + _attempt_observation(item) + for item in optimizer_records + if item["status"] == "succeeded" + ] + serialized_observations = [ + _observation(item, validation=False) for item in result["observations"] + ] + if serialized_observations != optimizer_observations: + raise ValueError("optimizer observations do not reconcile with attempts") + best = result["best_observation"] + if best is not None and _observation(best, validation=False) not in serialized_observations: + raise ValueError("best observation is not in optimizer history") + + validation_by_attempt = { + item["attempt_index"]: item for item in validation_records + } + used_validation_attempts: set[int] = set() + serialized_validation_observations: list[dict[str, object]] = [] + crossings: list[int] = [] + certified_crossings: list[int] = [] + for item in result["validation_attempts"]: + if not isinstance(item, Mapping): + raise ValueError("validation attempt must be a mapping") + required = { + "best_observation", + "certified", + "device_attempt_index", + "failure_category", + "optimizer_query_index", + "pulse", + "status", + "validation_observation", + } + if set(item) != required: + raise ValueError("validation-attempt fields are not canonical") + device_index = _exact_nonnegative_int( + item["device_attempt_index"], + name="device_attempt_index", + ) + if device_index == 0 or device_index in used_validation_attempts: + raise ValueError("validation attempt identities must be unique") + used_validation_attempts.add(device_index) + record = validation_by_attempt.get(device_index) + if record is None: + raise ValueError("validation history has no matching ledger attempt") + crossing = _exact_nonnegative_int( + item["optimizer_query_index"], + name="optimizer_query_index", + ) + crossings.append(crossing) + best_observation = _observation( + item["best_observation"], + validation=False, + ) + if ( + record["optimizer_query_index"] != crossing + or best_observation["optimizer_query_index"] != crossing + ): + raise ValueError("validation optimizer query identity is inconsistent") + if best_observation not in serialized_observations: + raise ValueError("validation best observation is not in optimizer history") + if ( + not isinstance(item["pulse"], list) + or len(item["pulse"]) != len(result["best_pulse"]) + or any( + isinstance(value, bool) + or not isinstance(value, (int, float)) + or not math.isfinite(float(value)) + or not -1.0 <= float(value) <= 1.0 + for value in item["pulse"] + ) + ): + raise ValueError("validation pulse is invalid") + if type(item["certified"]) is not bool or item["status"] not in { + "certified", + "failed", + "rejected", + }: + raise ValueError("validation status is invalid") + validation_observation = item["validation_observation"] + if record["status"] == "succeeded": + observed = _observation(validation_observation, validation=True) + if observed != _attempt_observation(record): + raise ValueError("validation observation does not match attempt") + serialized_validation_observations.append(observed) + if item["failure_category"] is not None: + raise ValueError("successful validation cannot have a failure") + expected_status = "certified" if item["certified"] else "rejected" + if item["status"] != expected_status: + raise ValueError("successful validation status is inconsistent") + elif ( + validation_observation is not None + or item["failure_category"] != record["error_category"] + ): + raise ValueError("failed validation does not match attempt") + elif item["status"] != "failed" or item["certified"]: + raise ValueError("failed validation status is inconsistent") + if item["certified"]: + certified_crossings.append(item["optimizer_query_index"]) + if len(result["validation_attempts"]) != len(validation_records): + raise ValueError("validation history does not cover all attempts") + if used_validation_attempts != set(validation_by_attempt): + raise ValueError("validation ledger attempts are not covered exactly once") + if any( + current <= previous + for previous, current in zip(crossings, crossings[1:]) + ): + raise ValueError( + "validation optimizer queries must be strictly increasing" + ) + if result["provisional_crossings"] != crossings: + raise ValueError("provisional crossings do not match validation history") + latest_validation = ( + serialized_validation_observations[-1] + if serialized_validation_observations + else None + ) + if result["validation_result"] != latest_validation: + raise ValueError("latest validation result is inconsistent") + expected_first = certified_crossings[0] if certified_crossings else None + if ( + len(certified_crossings) > 1 + or result["certified"] != bool(certified_crossings) + or first_certified != expected_first + ): + raise ValueError("certification history is inconsistent") + if certified_crossings and certified_crossings[0] != crossings[-1]: + raise ValueError("certified validation attempt must be final") + if schema_version == 3: + _validate_derived_metrics( + result["derived_metrics"], + evaluations=evaluations, + attempts=attempts, + config=config, + ) + + +@dataclass(frozen=True, slots=True) +class TrialResult: + trial_id: str + device_id: str + observation_stream_id: str + config: dict[str, object] + result: dict[str, object] + ledger: dict[str, int] + attempts: tuple[dict[str, object], ...] | Iterable[dict[str, object]] + execution: int | None = None + + def __post_init__(self) -> None: + if not all( + isinstance(value, str) + and re.fullmatch( + r"[a-z0-9][a-z0-9_-]*", + value, + re.ASCII, + ) + is not None + for value in ( + self.trial_id, + self.device_id, + self.observation_stream_id, + ) + ): + raise ValueError("trial identities must be strict ASCII tokens") + canonical_config = config_from_dict(self.config).canonical_dict() + if self.config != canonical_config: + raise ValueError("trial configuration is not canonical") + required_ledger = { + "optimizer_queries", + "optimizer_shots", + "validation_queries", + "validation_shots", + "total_queries", + "total_shots", + } + if set(self.ledger) != required_ledger or any( + isinstance(value, bool) or not isinstance(value, int) or value < 0 + for value in self.ledger.values() + ): + raise ValueError("ledger must contain nonnegative canonical totals") + if self.ledger["total_queries"] != ( + self.ledger["optimizer_queries"] + self.ledger["validation_queries"] + ): + raise ValueError("ledger query total is inconsistent") + if self.ledger["total_shots"] != ( + self.ledger["optimizer_shots"] + self.ledger["validation_shots"] + ): + raise ValueError("ledger shot total is inconsistent") + attempts = tuple(dict(attempt) for attempt in self.attempts) + _validate_result_and_attempts( + self.result, + self.ledger, + attempts, + config_from_dict(self.config), + ) + if self.execution is not None and ( + isinstance(self.execution, bool) + or not isinstance(self.execution, int) + or self.execution <= 0 + ): + raise ValueError("execution must be a positive integer or None") + object.__setattr__(self, "attempts", attempts) + + def canonical_dict(self) -> dict[str, object]: + schema_version = self.result["schema_version"] + payload: dict[str, object] = { + "attempts": list(self.attempts), + "config": self.config, + "device_id": self.device_id, + "ledger": self.ledger, + "observation_stream_id": self.observation_stream_id, + "result": self.result, + "schema_version": schema_version, + "trial_id": self.trial_id, + } + if self.execution is not None: + payload["execution"] = self.execution + return payload + + @classmethod + def from_canonical_dict(cls, payload: object) -> TrialResult: + if not isinstance(payload, Mapping) or payload.get("schema_version") not in { + 2, + 3, + }: + raise ValueError("unsupported trial-result schema") + expected = { + "attempts", + "config", + "device_id", + "ledger", + "observation_stream_id", + "result", + "schema_version", + "trial_id", + } + if "execution" in payload: + expected.add("execution") + if set(payload) != expected: + raise ValueError("invalid trial-result fields") + result_payload = payload.get("result") + if ( + not isinstance(result_payload, Mapping) + or result_payload.get("schema_version") != payload["schema_version"] + ): + raise ValueError("trial and public result schema versions differ") + try: + result = cls( + trial_id=payload["trial_id"], + device_id=payload["device_id"], + observation_stream_id=payload["observation_stream_id"], + config=dict(payload["config"]), + result=dict(payload["result"]), + ledger=dict(payload["ledger"]), + attempts=tuple(dict(item) for item in payload["attempts"]), + execution=payload.get("execution"), + ) + except (KeyError, TypeError, ValueError) as error: + raise ValueError(f"invalid trial-result payload: {error}") from error + return result + + +@dataclass(frozen=True, slots=True) +class SweepStatus: + expected: int + completed: int + pending: int + + def canonical_dict(self) -> dict[str, int]: + return { + "completed": self.completed, + "expected": self.expected, + "pending": self.pending, + } + + +@dataclass(frozen=True, slots=True) +class ValidationReport: + valid: bool + status: SweepStatus + errors: tuple[str, ...] + + def canonical_dict(self) -> dict[str, object]: + return { + "errors": list(self.errors), + **self.status.canonical_dict(), + "valid": self.valid, + } + + +def _normalized_full_config(config: ExperimentConfig) -> ExperimentConfig: + if config.search.method != "full": + return config + return replace( + config, + search=SearchConfig( + method="full", + dimension=config.system.parameter_count, + budget=config.search.budget, + ), + ) + + +def _device_payload(config: ExperimentConfig) -> dict[str, object]: + canonical = config.canonical_dict() + return { + "device": canonical["device"], + "model_seed": canonical["model_seed"], + "run_kind": canonical["run_kind"], + "system": canonical["system"], + "trial_seed": canonical["trial_seed"], + } + + +def generate_paired_trials(configs: Iterable[ExperimentConfig]) -> tuple[TrialSpec, ...]: + materialized = tuple(configs) + if any(not isinstance(config, ExperimentConfig) for config in materialized): + raise ValueError("configs must contain ExperimentConfig values") + run_kinds = {config.run_kind for config in materialized} + if len(run_kinds) > 1: + raise ValueError("development and production run kinds cannot be mixed") + + by_trial_id: dict[str, TrialSpec] = {} + for original in materialized: + config = _normalized_full_config(original) + canonical = config.canonical_dict() + device_id = _content_id("device", _device_payload(config)) + trial_id = _content_id("trial", canonical) + stream_id = _content_id( + "stream", + { + "device_id": device_id, + "dimension": config.search.dimension, + "method": config.search.method, + "trial_seed": config.trial_seed, + }, + ) + spec = TrialSpec(trial_id, device_id, stream_id, config) + prior = by_trial_id.setdefault(trial_id, spec) + if prior != spec: + raise ArtifactConflict("trial identifier collision") + return tuple(by_trial_id[key] for key in sorted(by_trial_id)) + + +def _oracle_basis( + truth: object, + pulse_space: PulseSpace, + origin: np.ndarray, +) -> np.ndarray: + point = jnp.asarray(origin, dtype=jnp.float64) + hessian = np.asarray( + jax.hessian( + lambda pulse: normalized_infidelity(pulse, truth, pulse_space) + )(point), + dtype=np.float64, + ) + eigenvalues, eigenvectors = np.linalg.eigh(0.5 * (hessian + hessian.T)) + ordering = np.argsort(np.abs(eigenvalues))[::-1] + return np.asarray(eigenvectors[:, ordering], dtype=np.float64) + + +def _landscape_leading_count( + config: ExperimentConfig, + model_dimension: int, +) -> int: + del model_dimension + return config.system.parameter_count - 1 + + +def prepare_model(config: ExperimentConfig) -> ModelPreparation: + system_payload = config.canonical_dict()["system"] + key = (canonical_json_bytes(system_payload), config.model_seed) + with _DERIVED_CACHE_LOCK: + cached = _MODEL_STATIC_CACHE.get(key) + if cached is not None: + return cached + model = make_system(config.system) + pulse_space = PulseSpace.from_system(model, config.system.segments) + open_loop = optimize_open_loop(model, pulse_space, seed=config.model_seed) + landscape = analyze_landscape( + model, + pulse_space, + open_loop, + leading_count=_landscape_leading_count(config, model.dimension), + dense_validation=pulse_space.parameter_count <= 80, + ) + origin = np.asarray( + ( + landscape.polishing.normalized_pulse + if landscape.polishing is not None + else open_loop.normalized_pulse + ), + dtype=np.float64, + ) + origin.setflags(write=False) + prepared = ModelPreparation( + model_seed=config.model_seed, + model=model, + pulse_space=pulse_space, + open_loop=open_loop, + landscape=landscape, + origin=origin, + ) + _MODEL_STATIC_CACHE[key] = prepared + return prepared + + +def _cached_derived_metrics( + config: ExperimentConfig, + model: ControlSystem, + truth: ControlSystem, + pulse_space: PulseSpace, + search_space: SearchSpace, + audited_queries: Sequence[tuple[np.ndarray, Observation | None]], +) -> tuple[object, object, object]: + system = config.canonical_dict()["system"] + device = config.canonical_dict()["device"] + truth_key = ( + canonical_json_bytes(system), + device["gap"], + device["perturbation_seed"], + ) + pulse_matrix = np.stack( + [np.asarray(search_space.origin, dtype=np.float64)] + + [np.asarray(pulse, dtype=np.float64) for pulse, _ in audited_queries], + axis=0, + ) + success_mask = tuple(observation is not None for _, observation in audited_queries) + exact_key = ( + truth_key, + _array_sha256(pulse_matrix), + success_mask, + ) + geometry_key = ( + truth_key, + _array_sha256(search_space.origin), + ) + restricted_key = ( + truth_key, + _array_sha256(search_space.origin), + _array_sha256(search_space.basis), + _array_sha256(search_space.lower_bounds), + _array_sha256(search_space.upper_bounds), + search_space.dimension, + ) + with _DERIVED_CACHE_LOCK: + exact_cache = _DERIVED_STATIC_CACHE.setdefault("exact", {}) + exact = exact_cache.get(exact_key) + if exact is None: + exact = cumulative_best_exact_infidelity( + make_offline_evaluator(truth, pulse_space), + initial_pulse=search_space.origin, + audited_queries=audited_queries, + ) + exact_cache[exact_key] = exact + geometry_cache = _DERIVED_STATIC_CACHE.setdefault("geometry", {}) + full_geometry = geometry_cache.get(geometry_key) + if full_geometry is None: + full_geometry = compute_geometry_diagnostics( + model, + truth, + pulse_space, + search_space.origin, + ) + geometry_cache[geometry_key] = full_geometry + geometry = full_geometry.slice(search_space.dimension) + restricted_cache = _DERIVED_STATIC_CACHE.setdefault("restricted", {}) + restricted = restricted_cache.get(restricted_key) + if restricted is None: + restricted = optimize_restricted_noiseless_upper_bound( + truth, + pulse_space, + search_space, + ) + restricted_cache[restricted_key] = restricted + return exact, geometry, restricted + + +def run_trial(config: ExperimentConfig, store: ArtifactStore) -> TrialResult: + del store + specs = generate_paired_trials([config]) + if len(specs) != 1: + raise ValueError("config must identify exactly one trial") + spec = specs[0] + config = spec.config + + prepared = prepare_model(config) + model = prepared.model + pulse_space = prepared.pulse_space + landscape = prepared.landscape + origin = prepared.origin + truth = perturb_system( + model, + config.device.gap, + config.device.perturbation_seed, + ) + oracle_basis = ( + _oracle_basis(truth, pulse_space, origin) + if config.search.method == "oracle" + else None + ) + search_space = make_search_space( + config.search, + origin, + model_basis=landscape.model_basis, + oracle_basis=oracle_basis, + seed=config.trial_seed, + ) + device = make_query_device( + truth, + pulse_space, + config.device, + seed=_stream_seed(spec.observation_stream_id), + ) + audited_queries: list[tuple[np.ndarray, Observation | None]] = [] + + def record_query( + pulse: np.ndarray, + observation: Observation | None, + ) -> None: + audited_queries.append( + (np.array(pulse, dtype=np.float64, copy=True), observation) + ) + + closed = run_closed_loop( + device, + search_space, + config.search.budget, + config.trial_seed, + audit_sink=record_query, + ) + ledger = device.ledger + accounting_before_offline = ( + ledger.optimizer_queries, + ledger.optimizer_shots, + ledger.validation_queries, + ledger.validation_shots, + ledger.total_queries, + ledger.total_shots, + ) + exact_trajectory, geometry, restricted = _cached_derived_metrics( + config, + model, + truth, + pulse_space, + search_space, + audited_queries, + ) + accounting_after_offline = ( + ledger.optimizer_queries, + ledger.optimizer_shots, + ledger.validation_queries, + ledger.validation_shots, + ledger.total_queries, + ledger.total_shots, + ) + if accounting_after_offline != accounting_before_offline: + raise RuntimeError("offline derived metrics changed device accounting") + public_result = closed.canonical_dict() + public_result.pop("space") + public_result["schema_version"] = 3 + public_result["search"] = { + "basis_sha256": _array_sha256(search_space.basis), + "dimension": search_space.dimension, + "method": config.search.method, + "origin_sha256": _array_sha256(search_space.origin), + "source_basis_sha256": _array_sha256( + landscape.model_basis + if config.search.method == "model_hessian" + else ( + oracle_basis + if config.search.method == "oracle" + else search_space.basis + ) + ), + } + public_result["derived_metrics"] = { + "exact_infidelity": exact_trajectory.canonical_dict(), + "geometry": { + "model_effective_ranks": list(geometry.model_effective_ranks), + "model_top_subspace_sha256": _array_sha256( + geometry.model_top_subspace + ), + "principal_angles_radians": list( + geometry.principal_angles_radians + ), + "rank_thresholds": list(geometry.rank_thresholds), + "signed_leading_eigenvalue_gaps": list( + geometry.signed_leading_eigenvalue_gaps + ), + "truth_effective_ranks": list(geometry.truth_effective_ranks), + "truth_top_subspace_sha256": _array_sha256( + geometry.truth_top_subspace + ), + }, + "restricted_noiseless_optimization": finalize_restricted_attained_bound( + restricted, + exact_trajectory, + ), + } + attempts = tuple( + { + "attempt_index": record.attempt_index, + "charged_shots": record.charged_shots, + "error_category": record.error_category, + "estimate": record.estimate, + "observation_seed": record.observation_seed, + "optimizer_query_index": record.optimizer_query_index, + "requested_shots": record.requested_shots, + "seed_digest": record.seed_digest, + "status": record.status, + "validation": record.validation, + } + for record in ledger.records + ) + return TrialResult( + trial_id=spec.trial_id, + device_id=spec.device_id, + observation_stream_id=spec.observation_stream_id, + config=config.canonical_dict(), + result=public_result, + ledger={ + "optimizer_queries": ledger.optimizer_queries, + "optimizer_shots": ledger.optimizer_shots, + "validation_queries": ledger.validation_queries, + "validation_shots": ledger.validation_shots, + "total_queries": ledger.total_queries, + "total_shots": ledger.total_shots, + }, + attempts=attempts, + ) + + +def _plan_payload(specs: Sequence[TrialSpec]) -> dict[str, object]: + return { + "run_kind": specs[0].config.run_kind if specs else None, + "schema_version": 1, + "trials": [spec.canonical_dict() for spec in specs], + } + + +def _bind_plan(specs: Sequence[TrialSpec], store: ArtifactStore) -> None: + payload = _plan_payload(specs) + provenance_config = { + "run_kind": payload["run_kind"], + "trials": [spec.config.canonical_dict() for spec in specs], + } + store.initialize_run(provenance_config, payload) + + +def _validated_result_for_spec(payload: object, spec: TrialSpec) -> TrialResult: + result = TrialResult.from_canonical_dict(payload) + if ( + result.trial_id != spec.trial_id + or result.device_id != spec.device_id + or result.observation_stream_id != spec.observation_stream_id + or result.config != spec.config.canonical_dict() + ): + raise ValueError("trial identity does not match plan") + return result + + +def shard_specs( + specs: Sequence[TrialSpec], + shard_index: int, + shard_count: int, +) -> tuple[TrialSpec, ...]: + if ( + type(shard_index) is not int + or type(shard_count) is not int + or shard_count <= 0 + or not 0 <= shard_index < shard_count + ): + raise ValueError("shard index must satisfy 0 <= index < shard count") + return tuple(specs[shard_index::shard_count]) + + +def run_sweep( + specs: Sequence[TrialSpec], + store: ArtifactStore, + *, + executor: Callable[[TrialSpec], TrialResult] | None = None, + stop_after: int | None = None, + shard_index: int = 0, + shard_count: int = 1, +) -> SweepStatus: + specs = tuple(specs) + if stop_after is not None and ( + isinstance(stop_after, bool) + or not isinstance(stop_after, int) + or stop_after < 0 + ): + raise ValueError("stop_after must be a nonnegative integer or None") + selected_specs = shard_specs(specs, shard_index, shard_count) + _bind_plan(specs, store) + completed = store.completed_trial_ids() + expected = {spec.trial_id for spec in specs} + if not completed <= expected: + raise ArtifactConflict("completed trials are not in the requested plan") + execute = executor or (lambda spec: run_trial(spec.config, store)) + newly_executed = 0 + + for spec in selected_specs: + if spec.trial_id in completed: + continue + if stop_after is not None and newly_executed >= stop_after: + break + with store.claim_trial(spec.trial_id): + if store.adopt_trial( + spec.trial_id, + lambda payload, expected=spec: _validated_result_for_spec( + payload, + expected, + ), + ): + continue + result = execute(spec) + if ( + not isinstance(result, TrialResult) + or result.trial_id != spec.trial_id + or result.device_id != spec.device_id + or result.observation_stream_id != spec.observation_stream_id + or result.config != spec.config.canonical_dict() + ): + raise ArtifactConflict("executor returned a mismatched trial result") + store.verify_bound_provenance() + store.publish_trial(spec.trial_id, result.canonical_dict()) + newly_executed += 1 + + completed_count = len(store.completed_trial_ids() & expected) + return SweepStatus( + expected=len(expected), + completed=completed_count, + pending=len(expected) - completed_count, + ) + + +def read_plan(store: ArtifactStore) -> tuple[TrialSpec, ...]: + path = store.root / "plan.json" + if not path.exists(): + if (store.root / "ready.json").exists(): + raise ArtifactConflict("initialized store has no trial plan") + return () + if not (store.root / "ready.json").exists(): + raise ArtifactConflict("trial plan initialization is incomplete") + ready = store.read_json("ready.json") + if ( + not isinstance(ready, Mapping) + or not isinstance(ready.get("plan_sha256"), str) + or not store.verify_file("plan.json", ready["plan_sha256"]) + ): + raise ArtifactConflict("trial plan does not match initialization marker") + payload = store.read_json("plan.json") + if ( + not isinstance(payload, Mapping) + or payload.get("schema_version") != 1 + or not isinstance(payload.get("trials"), list) + ): + raise ArtifactConflict("invalid trial plan schema") + try: + specs = tuple( + TrialSpec.from_canonical_dict(item) for item in payload["trials"] + ) + except ValueError as error: + raise ArtifactConflict("invalid trial plan") from error + if payload.get("run_kind") != (specs[0].config.run_kind if specs else None): + raise ArtifactConflict("trial plan run kind is inconsistent") + if specs != generate_paired_trials(spec.config for spec in specs): + raise ArtifactConflict("trial plan is not canonical") + return specs + + +def sweep_status(specs: Sequence[TrialSpec], store: ArtifactStore) -> SweepStatus: + expected = {spec.trial_id for spec in specs} + completed = store.completed_trial_ids() if (store.root / "index.json").exists() else frozenset() + matching = completed & expected + return SweepStatus(len(expected), len(matching), len(expected) - len(matching)) + + +def _allowed_files(store: ArtifactStore, specs: Sequence[TrialSpec]) -> set[Path]: + allowed = { + Path(".store.lock"), + Path("index.json"), + Path("manifest.json"), + Path("plan.json"), + Path("ready.json"), + } + allowed.update(Path("trials") / f"{spec.trial_id}.json" for spec in specs) + claims = store.root / "claims" + if claims.is_dir(): + allowed.update( + path.relative_to(store.root) + for path in claims.glob("*.flock") + if path.is_file() and not path.is_symlink() + ) + return allowed + + +def validate_sweep( + specs: Sequence[TrialSpec], + store: ArtifactStore, +) -> ValidationReport: + specs = tuple(specs) + errors: list[str] = [] + try: + _bind_plan(specs, store) + completed = store.completed_trial_ids() + except ArtifactConflict as error: + completed = frozenset() + errors.append(str(error)) + expected = {spec.trial_id for spec in specs} + missing = expected - completed + extra = completed - expected + if missing: + errors.append(f"missing trials: {len(missing)}") + if extra: + errors.append(f"unexpected indexed trials: {len(extra)}") + + by_id = {spec.trial_id: spec for spec in specs} + for trial_id in sorted(completed & expected): + try: + result = TrialResult.from_canonical_dict( + store.read_json(f"trials/{trial_id}.json") + ) + spec = by_id[trial_id] + if ( + result.trial_id != spec.trial_id + or result.device_id != spec.device_id + or result.observation_stream_id != spec.observation_stream_id + or result.config != spec.config.canonical_dict() + ): + raise ValueError("trial identity does not match plan") + except (ArtifactConflict, TypeError, ValueError) as error: + errors.append(f"invalid trial {trial_id}: {error}") + + allowed = _allowed_files(store, specs) + for path in store.root.rglob("*"): + if not path.is_file(): + continue + relative = path.relative_to(store.root) + if ( + relative.parts + and relative.parts[0] == "claims" + and relative.suffixes[-2:] == [".owner", ".json"] + ): + errors.append(f"unexpected active claim: {relative.as_posix()}") + elif relative not in allowed: + errors.append(f"unexpected file: {relative.as_posix()}") + status = SweepStatus(len(expected), len(completed & expected), len(missing)) + return ValidationReport(not errors, status, tuple(errors)) + + +def default_sweep_configs(kind: str) -> tuple[ExperimentConfig, ...]: + if kind not in {"development", "production"}: + raise ValueError("kind must be development or production") + budget = 2_000 if kind == "production" else 200 + gaps = ( + (0.0, 0.02, 0.05, 0.10, 0.20) + if kind == "production" + else (0.0, 0.05) + ) + seeds = range(20) if kind == "production" else range(3) + matrices = ( + ( + SystemConfig("two_qubit", 20, 4.0), + (5, 10, 15, 20, 30, 80), + (None, 1_000, 10_000), + ), + ( + SystemConfig("one_qubit", 12, 4.0), + (1, 2, 3, 4, 6, 24), + (None, 1_000), + ), + ) if kind == "production" else ( + ( + SystemConfig("one_qubit", 6, 4.0), + (2, 3), + (None, 1_000), + ), + ) + configs: list[ExperimentConfig] = [] + for system, dimensions, shots in matrices: + for seed in seeds: + for gap in gaps: + for shot_count in shots: + for dimension in dimensions: + for method in ( + "full", + "model_hessian", + "random", + "oracle", + ): + configs.append( + ExperimentConfig( + run_kind=kind, + system=system, + device=DeviceConfig( + gap=gap, + shots=shot_count, + perturbation_seed=seed, + ), + search=SearchConfig( + method, + dimension, + budget, + ), + trial_seed=seed, + ) + ) + return tuple(configs) diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/src/qcontrol/figures.py b/tracks/qcs/solutions/frustration-free/challenge-113/src/qcontrol/figures.py new file mode 100644 index 000000000..0180f3220 --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/src/qcontrol/figures.py @@ -0,0 +1,790 @@ +from __future__ import annotations + +from collections.abc import Callable, Iterable, Sequence +from dataclasses import dataclass +import hashlib +import io +from pathlib import Path +import struct +import zlib + +import matplotlib +import matplotlib.pyplot as plt +import numpy as np + +from qcontrol.analysis import AnalysisError, MethodSummary, StratumSummary, Summary +from qcontrol.artifacts import canonical_json_bytes + + +class FigureError(ValueError): + """A strict production Summary cannot support the required figures.""" + + +@dataclass(frozen=True, slots=True) +class FigureManifestEntry: + filename: str + panel_strata: tuple[str, ...] + sha256: str + failure_reason: str | None = None + + def canonical_dict(self) -> dict[str, object]: + return { + "failure_reason": self.failure_reason, + "filename": self.filename, + "panel_strata": list(self.panel_strata), + "sha256": self.sha256, + } + + +@dataclass(frozen=True, slots=True) +class FigureManifest: + summary_sha256: str + matplotlib_version: str + numpy_version: str + source: str + config: str + run_id: str + figures: tuple[FigureManifestEntry, ...] + schema_version: int = 2 + + def canonical_dict(self) -> dict[str, object]: + return { + "config": self.config, + "figures": [item.canonical_dict() for item in self.figures], + "matplotlib_version": self.matplotlib_version, + "numpy_version": self.numpy_version, + "run_id": self.run_id, + "schema_version": self.schema_version, + "source": self.source, + "summary_sha256": self.summary_sha256, + } + + +_METHODS = ("full", "model_hessian", "oracle", "random") +_BASELINES = ("full", "oracle", "random") +_RANK_THRESHOLDS = (1e-6, 1e-8, 1e-10) +_TARGET_INFIDELITY = 1.0 - 0.999 +_STYLES = { + "full": {"color": "#000000", "marker": "s", "linestyle": "-"}, + "model_hessian": {"color": "#0072B2", "marker": "o", "linestyle": "-"}, + "oracle": {"color": "#009E73", "marker": "^", "linestyle": "--"}, + "random": {"color": "#E69F00", "marker": "D", "linestyle": ":"}, +} +_DISPLAY = { + "full": "Full space", + "model_hessian": "Model Hessian", + "oracle": "Oracle", + "random": "Random", +} +_FILENAMES = ( + "queries_vs_dimension.png", + "advantage_vs_gap.png", + "subspace_rotation_and_floor.png", + "rank_invariant_d2_d4.png", + "failure_case.png", +) +_RC = { + "axes.grid": True, + "axes.grid.axis": "y", + "axes.spines.right": False, + "axes.spines.top": False, + "font.family": "DejaVu Sans", + "font.sans-serif": ["DejaVu Sans"], + "font.size": 8, + "figure.dpi": 120, + "savefig.dpi": 120, +} + + +def _strict_summary(summary: Summary) -> Summary: + if not isinstance(summary, Summary): + raise FigureError("figures require a strict canonical Summary instance") + try: + parsed = Summary.from_canonical_dict(summary.canonical_dict()) + except (AnalysisError, TypeError, ValueError) as error: + raise FigureError(f"figures require a strict canonical Summary: {error}") from error + if parsed != summary: + raise FigureError("figures require a strict canonical Summary") + for stratum in summary.strata: + names = tuple(item.method for item in stratum.methods) + if set(names) != set(_METHODS): + missing = sorted(set(_METHODS) - set(names)) + extra = sorted(set(names) - set(_METHODS)) + raise FigureError( + "required methods are incomplete " + f"(missing={missing}, unexpected={extra}) in {_stratum_id(stratum)}" + ) + paired = {item.baseline for item in stratum.paired_differences} + if paired != set(_BASELINES): + raise FigureError( + f"required paired methods are incomplete in {_stratum_id(stratum)}" + ) + for method in stratum.methods: + if ( + method.metric_availability.state != "available" + or method.principal_angle_availability.state != "available" + or method.exact_infidelity_trajectory is None + or method.median_attained_infidelity_upper_bound is None + or method.median_principal_angles is None + or method.median_model_effective_ranks is None + or method.median_truth_effective_ranks is None + or method.median_signed_eigenvalue_gaps is None + ): + raise FigureError( + "required production metrics are unavailable for " + f"{method.method!r} in {_stratum_id(stratum)}" + ) + dimensions = {item.key.hilbert_dimension for item in summary.strata} + if not {2, 4}.issubset(dimensions): + raise FigureError("rank figure requires production strata for d=2 and d=4") + return parsed + + +def _stratum_id(stratum: StratumSummary) -> str: + key = stratum.key + shots = "exact" if key.shots is None else str(key.shots) + return ( + f"system={key.system_name}|d={key.hilbert_dimension}|" + f"segments={key.segments}|amplitude_bound={key.amplitude_bound:g}|" + f"duration={key.duration:g}|k={key.search_dimension}|gap={key.gap:g}|" + f"shots={shots}" + ) + + +def _method(stratum: StratumSummary, name: str) -> MethodSummary: + return next(item for item in stratum.methods if item.method == name) + + +def _caption(summary: Summary, source: str, config: str, run_id: str) -> str: + trials = sum(method.trial_count for stratum in summary.strata for method in stratum.methods) + clusters = sum( + pair.cluster_count + for stratum in summary.strata + for pair in stratum.paired_differences + ) + return ( + f"Source: {source} | config: {config} | run: {run_id} | " + f"method-stratum samples n={trials}; paired clusters n={clusters}; " + f"{summary.bootstrap_confidence:.0%} intervals, " + f"{summary.bootstrap_samples} bootstrap samples" + ) + + +def _finish_figure(figure: matplotlib.figure.Figure, caption: str) -> None: + figure.text(0.01, 0.006, caption, ha="left", va="bottom", fontsize=6) + figure.tight_layout(rect=(0.0, 0.035, 1.0, 0.965)) + + +def _groups( + strata: Iterable[StratumSummary], + key: Callable[[StratumSummary], tuple[object, ...]], +) -> tuple[tuple[tuple[object, ...], tuple[StratumSummary, ...]], ...]: + grouped: dict[tuple[object, ...], list[StratumSummary]] = {} + for stratum in strata: + grouped.setdefault(key(stratum), []).append(stratum) + return tuple( + (group, tuple(sorted(items, key=lambda item: item.key.sort_key()))) + for group, items in sorted(grouped.items(), key=lambda item: repr(item[0])) + ) + + +def _axes_grid( + rows: int, + columns: int, + *, + width: float, + height: float, +) -> tuple[matplotlib.figure.Figure, np.ndarray]: + figure, axes = plt.subplots( + rows, + columns, + figsize=(width * columns, height * rows), + squeeze=False, + ) + return figure, axes + + +def _bootstrap_median( + values: Sequence[int], + summary: Summary, + identity: str, +) -> tuple[float, float, float]: + data = np.asarray(values, dtype=np.float64) + digest = hashlib.sha256( + canonical_json_bytes([summary.bootstrap_seed, identity]) + ).digest() + rng = np.random.default_rng(int.from_bytes(digest[:8], "big")) + selected = data[ + rng.integers( + 0, + data.size, + size=(summary.bootstrap_samples, data.size), + ) + ] + estimates = np.median(selected, axis=1) + alpha = (1.0 - summary.bootstrap_confidence) / 2.0 + low, high = np.quantile(estimates, [alpha, 1.0 - alpha]) + return float(np.median(data)), float(low), float(high) + + +def _plot_interval_series( + axis: matplotlib.axes.Axes, + x: Sequence[float], + center: Sequence[float], + low: Sequence[float], + high: Sequence[float], + method: str, + *, + label: str | None = None, +) -> None: + style = _STYLES[method] + series_label = label or _DISPLAY[method] + axis.plot(x, center, label=series_label, **style) + axis.fill_between( + x, + low, + high, + color=style["color"], + alpha=0.16, + label=f"{series_label} confidence interval", + ) + + +def _plot_error_series( + axis: matplotlib.axes.Axes, + x: Sequence[float], + center: Sequence[float], + low: Sequence[float], + high: Sequence[float], + method: str, +) -> None: + values = np.asarray(center, dtype=np.float64) + lower = np.asarray(low, dtype=np.float64) + upper = np.asarray(high, dtype=np.float64) + axis.errorbar( + x, + values, + yerr=np.vstack((values - lower, upper - values)), + label=_DISPLAY[method], + capsize=3, + **_STYLES[method], + ) + + +def _method_legend(axis: matplotlib.axes.Axes) -> None: + handles = [ + line for line in axis.lines if line.get_label() in _DISPLAY.values() + ] + axis.legend(handles=handles) + + +def _queries_figure( + summary: Summary, + caption: str, +) -> tuple[matplotlib.figure.Figure, tuple[str, ...]]: + groups = _groups( + summary.strata, + lambda item: ( + item.key.system_name, + item.key.hilbert_dimension, + item.key.segments, + item.key.amplitude_bound, + item.key.duration, + item.key.gap, + item.key.shots, + ), + ) + figure, axes = _axes_grid(len(groups), 2, width=4.2, height=2.7) + panel_strata: list[str] = [] + for row, (_, strata) in enumerate(groups): + panel_strata.extend(_stratum_id(item) for item in strata) + x = [item.key.search_dimension for item in strata] + for name in _METHODS: + query_rows = [ + _bootstrap_median( + _method(item, name).censored_first_certified_queries, + summary, + f"{_stratum_id(item)}:{name}:censored-query", + ) + for item in strata + ] + _plot_error_series( + axes[row, 0], + x, + [item[0] for item in query_rows], + [item[1] for item in query_rows], + [item[2] for item in query_rows], + name, + ) + probabilities = [_method(item, name).success_probability for item in strata] + _plot_error_series( + axes[row, 1], + x, + [item.value for item in probabilities], + [item.low for item in probabilities], + [item.high for item in probabilities], + name, + ) + key = strata[0].key + facet = ( + f"{key.system_name}, d={key.hilbert_dimension}, " + f"segments={key.segments}, amplitude={key.amplitude_bound:g}, " + f"duration={key.duration:g}, gap={key.gap:g}, " + f"shots={'exact' if key.shots is None else key.shots}" + ) + axes[row, 0].set_title(facet) + axes[row, 1].set_title(facet) + axes[row, 0].set_xlabel("search dimension k [basis directions]") + axes[row, 1].set_xlabel("search dimension k [basis directions]") + axes[row, 0].set_ylabel( + "budget-censored median first-certified optimizer queries [count]" + ) + axes[row, 1].set_ylabel("success probability within budget [fraction]") + axes[row, 1].set_ylim(-0.03, 1.03) + axes[row, 0].legend() + axes[row, 1].legend() + figure.suptitle("Certified query cost and success versus search dimension") + _finish_figure(figure, caption) + return figure, tuple(panel_strata) + + +def _advantage_figure( + summary: Summary, + caption: str, +) -> tuple[matplotlib.figure.Figure, tuple[str, ...]]: + groups = _groups( + summary.strata, + lambda item: ( + item.key.system_name, + item.key.hilbert_dimension, + item.key.segments, + item.key.amplitude_bound, + item.key.duration, + item.key.search_dimension, + item.key.shots, + ), + ) + figure, axes = _axes_grid(len(groups), 3, width=3.8, height=2.7) + panel_strata: list[str] = [] + metrics = ( + ("censored_query_difference", "paired query advantage\nbaseline − model [queries]"), + ("total_shot_difference", "paired shot advantage\nbaseline − model [shots]"), + ( + "success_probability_difference", + "paired success advantage\nmodel − baseline [probability]", + ), + ) + for row, (_, strata) in enumerate(groups): + panel_strata.extend(_stratum_id(item) for item in strata) + x = [abs(item.key.gap) / item.key.amplitude_bound for item in strata] + for baseline in _BASELINES: + pairs = [ + next( + item + for item in stratum.paired_differences + if item.baseline == baseline + ) + for stratum in strata + ] + for column, (field, _) in enumerate(metrics): + intervals = [getattr(item, field) for item in pairs] + _plot_interval_series( + axes[row, column], + x, + [item.estimate for item in intervals], + [item.low for item in intervals], + [item.high for item in intervals], + baseline, + ) + key = strata[0].key + facet = ( + f"{key.system_name}, d={key.hilbert_dimension}, " + f"segments={key.segments}, amplitude={key.amplitude_bound:g}, " + f"duration={key.duration:g}, k={key.search_dimension}, " + f"shots={'exact' if key.shots is None else key.shots}" + ) + for column, (_, ylabel) in enumerate(metrics): + axes[row, column].axhline(0.0, color="#666666", linewidth=0.8) + axes[row, column].set_title(facet) + axes[row, column].set_xlabel( + "normalized gap |device gap| / amplitude bound [dimensionless]" + ) + axes[row, column].set_ylabel(ylabel) + _method_legend(axes[row, column]) + figure.suptitle("Paired Model-Hessian advantage versus normalized gap") + _finish_figure(figure, caption) + return figure, tuple(panel_strata) + + +def _positive_log_values(values: Sequence[float]) -> tuple[np.ndarray, float]: + data = np.asarray(values, dtype=np.float64) + positive = data[data > 0.0] + epsilon = ( + float(np.min(positive)) / 10.0 + if positive.size + else float(np.finfo(np.float64).tiny) + ) + return np.maximum(data, epsilon), epsilon + + +def _subspace_figure( + summary: Summary, + caption: str, +) -> tuple[matplotlib.figure.Figure, tuple[str, ...]]: + groups = _groups( + summary.strata, + lambda item: ( + item.key.system_name, + item.key.hilbert_dimension, + item.key.segments, + item.key.amplitude_bound, + item.key.duration, + item.key.search_dimension, + item.key.shots, + ), + ) + figure, axes = _axes_grid(len(groups), 2, width=4.4, height=2.7) + panel_strata: list[str] = [] + for row, (_, strata) in enumerate(groups): + panel_strata.extend(_stratum_id(item) for item in strata) + x = [abs(item.key.gap) / item.key.amplitude_bound for item in strata] + all_floors = [ + _method(item, name).median_attained_infidelity_upper_bound + for item in strata + for name in _METHODS + ] + _, epsilon = _positive_log_values( + [float(item) for item in all_floors if item is not None] + ) + for name in _METHODS: + for angle_index in range(strata[0].key.search_dimension): + angle_values = [ + (_method(item, name).median_principal_angles or ())[angle_index] + for item in strata + ] + angle_style = dict(_STYLES[name]) + angle_style["alpha"] = max(0.35, 1.0 - 0.15 * angle_index) + axes[row, 0].plot( + x, + angle_values, + label=f"{_DISPLAY[name]} θ{angle_index + 1}", + **angle_style, + ) + floors = np.maximum( + [ + float( + _method(item, name).median_attained_infidelity_upper_bound + ) + for item in strata + ], + epsilon, + ) + axes[row, 1].plot( + x, + floors, + label=_DISPLAY[name], + **_STYLES[name], + ) + key = strata[0].key + facet = ( + f"{key.system_name}, d={key.hilbert_dimension}, " + f"segments={key.segments}, amplitude={key.amplitude_bound:g}, " + f"duration={key.duration:g}, k={key.search_dimension}, " + f"shots={'exact' if key.shots is None else key.shots}" + ) + for axis in axes[row]: + axis.set_title(facet) + axis.set_xlabel( + "normalized gap |device gap| / amplitude bound [dimensionless]" + ) + axis.legend() + axes[row, 0].set_ylabel("target-k principal angles θᵢ [radians]") + axes[row, 1].set_ylabel( + "attained restricted infidelity upper bound " + f"max(I, ε), ε={epsilon:.1e} [dimensionless]" + ) + axes[row, 1].set_yscale("log") + axes[row, 1].axhline( + _TARGET_INFIDELITY, + color="#CC79A7", + linestyle="--", + label="restricted target I ≤ 1−0.999", + ) + axes[row, 1].legend() + figure.suptitle("Target-k subspace rotation and restricted fidelity floor") + _finish_figure(figure, caption) + return figure, tuple(panel_strata) + + +def _rank_figure( + summary: Summary, + caption: str, +) -> tuple[matplotlib.figure.Figure, tuple[str, ...]]: + strata = tuple( + item for item in summary.strata if item.key.hilbert_dimension in {2, 4} + ) + figure, axes = _axes_grid(len(strata), 2, width=4.4, height=2.6) + panel_strata: list[str] = [] + for row, stratum in enumerate(strata): + panel_strata.append(_stratum_id(stratum)) + model = _method(stratum, "model_hessian") + axes[row, 0].plot( + _RANK_THRESHOLDS, + model.median_model_effective_ranks, + label="Model Hessian model rank", + **_STYLES["model_hessian"], + ) + axes[row, 0].plot( + _RANK_THRESHOLDS, + model.median_truth_effective_ranks, + label="Model Hessian truth rank", + alpha=0.55, + **_STYLES["model_hessian"], + ) + expected = 3 if stratum.key.hilbert_dimension == 2 else 15 + axes[row, 0].axhline( + expected, + color="#666666", + linestyle=":", + label=f"Expected invariant rank {expected}", + ) + signed = model.median_signed_eigenvalue_gaps or () + components = np.arange(1, len(signed) + 1) + axes[row, 1].plot( + components, + signed, + label="Model Hessian signed leading gaps", + **_STYLES["model_hessian"], + ) + axes[row, 1].axhline(0.0, color="#666666", linewidth=0.8, label="zero") + facet = _stratum_id(stratum) + axes[row, 0].set_title(facet) + axes[row, 1].set_title(facet) + axes[row, 0].set_xticks( + _RANK_THRESHOLDS, + [f"{item:.0e}" for item in _RANK_THRESHOLDS], + ) + axes[row, 0].set_xlim(1.05e-6, 0.0) + axes[row, 0].set_xlabel( + "relative threshold τ in |λ| > τ max|λ| [dimensionless]" + ) + axes[row, 0].set_ylabel("effective Hessian rank [count]") + axes[row, 1].set_xlabel("target-k eigenvalue index [count]") + axes[row, 1].set_ylabel("signed leading eigenvalue gap [curvature units]") + axes[row, 0].legend() + axes[row, 1].legend() + figure.suptitle("d=2 and d=4 Hessian rank invariants") + _finish_figure(figure, caption) + return figure, tuple(panel_strata) + + +def _failure_reason(stratum: StratumSummary) -> str | None: + model = _method(stratum, "model_hessian") + reasons: list[str] = [] + attained = model.median_attained_infidelity_upper_bound + if attained is not None and attained > _TARGET_INFIDELITY: + reasons.append( + "restricted attained infidelity " + f"{attained:.3g} exceeds target {_TARGET_INFIDELITY:.3g}" + ) + for pair in stratum.paired_differences: + if pair.censored_query_difference.estimate < 0.0: + reasons.append(f"query disadvantage versus {_DISPLAY[pair.baseline]}") + if pair.total_shot_difference.estimate < 0.0: + reasons.append(f"shot disadvantage versus {_DISPLAY[pair.baseline]}") + if pair.success_probability_difference.estimate < 0.0: + reasons.append(f"success disadvantage versus {_DISPLAY[pair.baseline]}") + return "; ".join(reasons) if reasons else None + + +def _select_failure(summary: Summary) -> tuple[StratumSummary, str]: + candidates = [ + (item, reason) + for item in summary.strata + if (reason := _failure_reason(item)) is not None + ] + if not candidates: + raise FigureError( + "no qualifying production failure: Model Hessian has no paired " + "query/shot/success disadvantage and all restricted attained " + "infidelity upper bounds meet 1−0.999" + ) + return min(candidates, key=lambda item: item[0].key.sort_key()) + + +def _failure_figure( + stratum: StratumSummary, + reason: str, + caption: str, +) -> tuple[matplotlib.figure.Figure, tuple[str, ...]]: + figure, axes = _axes_grid(1, 4, width=3.5, height=3.1) + names = list(_METHODS) + positions = np.arange(len(names)) + labels = [_DISPLAY[name] for name in names] + queries = [ + float(np.median(_method(stratum, name).censored_first_certified_queries)) + for name in names + ] + shots = [ + float(np.median(_method(stratum, name).total_shots_by_trial)) + for name in names + ] + success = [_method(stratum, name).success_probability.value for name in names] + floors_raw = [ + float(_method(stratum, name).median_attained_infidelity_upper_bound) + for name in names + ] + floors, epsilon = _positive_log_values(floors_raw) + values = (queries, shots, success, floors) + ylabels = ( + "right-censored first certified query [optimizer queries]", + "total optimizer + validation shots [shots]", + "success probability within budget [fraction]", + f"restricted infidelity max(I, ε), ε={epsilon:.1e} [dimensionless]", + ) + for column, axis in enumerate(axes[0]): + for index, name in enumerate(names): + axis.plot( + [positions[index]], + [values[column][index]], + label=labels[index], + **_STYLES[name], + ) + axis.set_xticks(positions, labels, rotation=25, ha="right") + axis.set_xlabel("search method [fixed production method]") + axis.set_ylabel(ylabels[column]) + axis.legend(fontsize=6) + axes[0, 2].set_ylim(0.0, 1.05) + axes[0, 3].set_yscale("log") + axes[0, 3].axhline( + _TARGET_INFIDELITY, + color="#CC79A7", + linestyle="--", + label="target I ≤ 1−0.999", + ) + axes[0, 3].legend(fontsize=6) + figure.suptitle(f"Observed production failure: {_stratum_id(stratum)}\n{reason}") + _finish_figure(figure, caption) + return figure, (_stratum_id(stratum),) + + +def _strip_png_metadata(payload: bytes) -> bytes: + signature = b"\x89PNG\r\n\x1a\n" + if not payload.startswith(signature): + raise FigureError("matplotlib did not produce a PNG") + output = bytearray(signature) + offset = len(signature) + while offset < len(payload): + if offset + 12 > len(payload): + raise FigureError("truncated PNG output") + length = struct.unpack(">I", payload[offset : offset + 4])[0] + chunk_type = payload[offset + 4 : offset + 8] + stop = offset + 12 + length + if stop > len(payload): + raise FigureError("truncated PNG chunk") + data = payload[offset + 8 : offset + 8 + length] + if chunk_type[0] & 0x20 == 0: + output.extend(struct.pack(">I", length)) + output.extend(chunk_type) + output.extend(data) + output.extend( + struct.pack(">I", zlib.crc32(chunk_type + data) & 0xFFFFFFFF) + ) + offset = stop + if chunk_type == b"IEND": + break + return bytes(output) + + +def _save_png(figure: matplotlib.figure.Figure, path: Path) -> str: + buffer = io.BytesIO() + figure.savefig( + buffer, + format="png", + dpi=120, + facecolor="white", + metadata={}, + ) + payload = _strip_png_metadata(buffer.getvalue()) + path.write_bytes(payload) + return hashlib.sha256(payload).hexdigest() + + +def render_publication_figures( + summary: Summary, + output_directory: str | Path, + *, + source: str, + config: str, + run_id: str, +) -> FigureManifest: + """Render all required deterministic figures from a strict canonical Summary.""" + for name, value in (("source", source), ("config", config), ("run_id", run_id)): + if not isinstance(value, str) or not value.strip(): + raise FigureError(f"{name} caption must be a nonempty string") + canonical = _strict_summary(summary) + summary_digest = hashlib.sha256( + canonical_json_bytes(summary.canonical_dict()) + ).hexdigest() + failure, failure_reason = _select_failure(canonical) + output = Path(output_directory) + if output.exists() and not output.is_dir(): + raise FigureError("figure output path must be a directory") + output.mkdir(parents=True, exist_ok=True) + caption = _caption(canonical, source, config, run_id) + builders: tuple[ + tuple[ + str, + Callable[ + [], + tuple[matplotlib.figure.Figure, tuple[str, ...]], + ], + ], + ..., + ] = ( + (_FILENAMES[0], lambda: _queries_figure(canonical, caption)), + (_FILENAMES[1], lambda: _advantage_figure(canonical, caption)), + (_FILENAMES[2], lambda: _subspace_figure(canonical, caption)), + (_FILENAMES[3], lambda: _rank_figure(canonical, caption)), + ( + _FILENAMES[4], + lambda: _failure_figure(failure, failure_reason, caption), + ), + ) + entries: list[FigureManifestEntry] = [] + with matplotlib.rc_context(): + matplotlib.rcdefaults() + matplotlib.rcParams.update(_RC) + for filename, builder in builders: + existing_numbers = set(plt.get_fignums()) + try: + figure, panel_strata = builder() + if not figure.axes or any(not axis.has_data() for axis in figure.axes): + raise FigureError(f"{filename} contains an empty panel") + digest = _save_png(figure, output / filename) + finally: + for number in set(plt.get_fignums()) - existing_numbers: + plt.close(number) + entries.append( + FigureManifestEntry( + filename=filename, + panel_strata=panel_strata, + sha256=digest, + failure_reason=( + failure_reason if filename == "failure_case.png" else None + ), + ) + ) + manifest = FigureManifest( + summary_sha256=summary_digest, + matplotlib_version=matplotlib.__version__, + numpy_version=np.__version__, + source=source, + config=config, + run_id=run_id, + figures=tuple(entries), + ) + (output / "figure_manifest.json").write_bytes( + canonical_json_bytes(manifest.canonical_dict()) + ) + return manifest diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/src/qcontrol/landscape.py b/tracks/qcs/solutions/frustration-free/challenge-113/src/qcontrol/landscape.py new file mode 100644 index 000000000..50eaa798b --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/src/qcontrol/landscape.py @@ -0,0 +1,695 @@ +from __future__ import annotations + +from collections.abc import Callable +from dataclasses import dataclass +from numbers import Integral + +import jax +import jax.numpy as jnp +import numpy as np +from numpy.typing import NDArray +from scipy.optimize import least_squares +from scipy.sparse.linalg import LinearOperator, eigsh + +from qcontrol.objectives import normalized_infidelity +from qcontrol.open_loop import OpenLoopResult +from qcontrol.propagation import propagate +from qcontrol.pulses import PulseSpace +from qcontrol.systems import ControlSystem + + +RankMap = dict[float, int] +AngleMap = dict[float, NDArray[np.float64]] +_RANK_THRESHOLDS = (1e-6, 1e-8, 1e-10) +_MAX_DENSE_PARAMETERS = 80 +_ACCEPTANCE_LOSS = 1e-8 +_POLISH_LOSS_TOLERANCE = 1e-12 +_POLISH_GRADIENT_TOLERANCE = 1e-10 +_POLISH_RESIDUAL_TOLERANCE = 1e-12 +_POLISH_PHASE_TOLERANCE = 1e-12 + + +@dataclass(frozen=True) +class EndpointPolishResult: + normalized_pulse: tuple[float, ...] + loss: float + gradient_norm: float + projected_gradient_norm: float + residual_norm: float + phase_consistency_error: float + evaluations: int + jacobian_evaluations: int + status: int + message: str + cost: float + optimality: float + step_norm: float + converged: bool + source_loss: float + source_gradient_norm: float + duration: float + fixed_phase_real: float + fixed_phase_imag: float + + +class EndpointPolishingError(RuntimeError): + def __init__(self, message: str, diagnostics: EndpointPolishResult) -> None: + self.diagnostics = diagnostics + super().__init__(message) + + +@dataclass(frozen=True) +class LandscapeResult: + leading_eigenvalues: NDArray[np.float64] + leading_eigenvectors: NDArray[np.float64] + jacobian_singular_values: NDArray[np.float64] + hessian_ranks: RankMap + hessian_rank_is_lower_bound: dict[float, bool] + jacobian_ranks: RankMap + model_basis: NDArray[np.float64] + search_basis_available_columns: int + endpoint_basis: NDArray[np.float64] + dense_hessian: NDArray[np.float64] | None + dense_eigenvalues: NDArray[np.float64] | None + dense_eigenvectors: NDArray[np.float64] | None + dense_hvp_projector_residuals: dict[float, float] + dense_hvp_principal_angles: AngleMap + polishing: EndpointPolishResult | None + eigenvalue_ordering: str + + +def hessian_vector_product( + loss_fn: Callable[[jax.Array], jax.Array], + point: jax.Array, + vector: jax.Array, +) -> jax.Array: + _, product = jax.jvp(jax.grad(loss_fn), (point,), (vector,)) + return product + + +def dense_hessian( + loss_fn: Callable[[jax.Array], jax.Array], + point: object, +) -> NDArray[np.float64]: + point_array = jnp.asarray(point, dtype=jnp.float64) + if point_array.ndim != 1: + raise ValueError("point must be a one-dimensional real vector") + if point_array.size > _MAX_DENSE_PARAMETERS: + raise ValueError("dense Hessian validation supports at most 80 parameters") + matrix = np.asarray(jax.hessian(loss_fn)(point_array), dtype=np.float64) + return np.asarray(0.5 * (matrix + matrix.T), dtype=np.float64) + + +def _generalized_pauli_basis(dimension: int) -> jax.Array: + generators: list[NDArray[np.complex128]] = [] + for row in range(dimension): + for column in range(row + 1, dimension): + symmetric = np.zeros((dimension, dimension), dtype=np.complex128) + symmetric[row, column] = 1.0 / np.sqrt(2.0) + symmetric[column, row] = 1.0 / np.sqrt(2.0) + generators.append(symmetric) + + antisymmetric = np.zeros((dimension, dimension), dtype=np.complex128) + antisymmetric[row, column] = -1.0j / np.sqrt(2.0) + antisymmetric[column, row] = 1.0j / np.sqrt(2.0) + generators.append(antisymmetric) + + for index in range(1, dimension): + diagonal = np.zeros((dimension, dimension), dtype=np.complex128) + normalization = np.sqrt(index * (index + 1.0)) + diagonal[np.arange(index), np.arange(index)] = 1.0 / normalization + diagonal[index, index] = -index / normalization + generators.append(diagonal) + + return jnp.asarray(np.stack(generators), dtype=jnp.complex128) + + +def _target_endpoint_residual( + system: ControlSystem, + space: PulseSpace, + reference_point: jax.Array, +) -> tuple[Callable[[jax.Array], jax.Array], complex]: + reference = propagate(system, space.to_physical(reference_point)) + target = jnp.asarray(system.target, dtype=jnp.complex128) + overlap = jnp.trace(target.conj().T @ reference) + overlap_magnitude = float(np.abs(np.asarray(overlap))) + if not np.isfinite(overlap_magnitude) or overlap_magnitude == 0.0: + raise ValueError("accepted endpoint has no well-defined target phase") + fixed_phase = complex(np.asarray(overlap / jnp.abs(overlap))) + fixed_target = jax.lax.stop_gradient(target * overlap / jnp.abs(overlap)) + generators = _generalized_pauli_basis(system.dimension) + identity = jnp.eye(system.dimension, dtype=jnp.complex128) + + def residual(candidate: jax.Array) -> jax.Array: + endpoint = propagate(system, space.to_physical(candidate)) + relative = fixed_target.conj().T @ endpoint + delta_a = (relative - relative.conj().T) / (2.0j) + traceless = delta_a - ( + jnp.trace(delta_a) / jnp.float64(system.dimension) + ) * identity + return jnp.real(jnp.einsum("kij,ij->k", generators.conj(), traceless)) + + return residual, fixed_phase + + +def _projected_gradient_norm( + point: NDArray[np.float64], + gradient: NDArray[np.float64], +) -> float: + projected = np.array(gradient, dtype=np.float64, copy=True) + lower_active = point <= -1.0 + 1e-10 + upper_active = point >= 1.0 - 1e-10 + projected[lower_active & (gradient > 0.0)] = 0.0 + projected[upper_active & (gradient < 0.0)] = 0.0 + return float(np.linalg.norm(projected)) + + +def polish_endpoint( + system: ControlSystem, + space: PulseSpace, + accepted: OpenLoopResult, + *, + max_nfev: int = 100, +) -> EndpointPolishResult: + if not isinstance(system, ControlSystem): + raise ValueError("system must be a ControlSystem") + if not isinstance(space, PulseSpace): + raise ValueError("space must be a PulseSpace") + if not isinstance(accepted, OpenLoopResult): + raise ValueError("accepted must be an OpenLoopResult") + if accepted.loss > _ACCEPTANCE_LOSS or not np.isfinite(accepted.loss): + raise ValueError("OpenLoopResult must satisfy the 1e-8 acceptance threshold") + if ( + isinstance(max_nfev, (bool, np.bool_)) + or not isinstance(max_nfev, Integral) + or max_nfev <= 0 + ): + raise ValueError("max_nfev must be a positive integer") + + initial = jnp.asarray(accepted.normalized_pulse, dtype=jnp.float64) + if initial.shape != (space.parameter_count,): + raise ValueError("accepted pulse shape does not match the PulseSpace") + initial_numpy = np.asarray(initial, dtype=np.float64) + if not np.all(np.isfinite(initial_numpy)) or np.any(np.abs(initial_numpy) > 1.0): + raise ValueError("accepted pulse must be finite and within [-1, 1]") + + residual_fn, fixed_phase = _target_endpoint_residual( + system, + space, + initial, + ) + compiled_residual = jax.jit(residual_fn).lower(initial).compile() + compiled_jacobian = jax.jit(jax.jacrev(residual_fn)).lower(initial).compile() + loss_fn = lambda candidate: normalized_infidelity(candidate, system, space) + compiled_value_gradient = ( + jax.jit(jax.value_and_grad(loss_fn)).lower(initial).compile() + ) + + def scipy_residual(point: NDArray[np.float64]) -> NDArray[np.float64]: + values = compiled_residual(jnp.asarray(point, dtype=jnp.float64)) + return np.ascontiguousarray(np.asarray(values, dtype=np.float64)) + + def scipy_jacobian(point: NDArray[np.float64]) -> NDArray[np.float64]: + values = compiled_jacobian(jnp.asarray(point, dtype=jnp.float64)) + return np.ascontiguousarray(np.asarray(values, dtype=np.float64)) + + def diagnostics( + point: NDArray[np.float64], + *, + evaluations: int, + jacobian_evaluations: int, + status: int, + message: str, + cost: float, + optimality: float, + solver_success: bool, + ) -> EndpointPolishResult: + finite_and_bounded = bool( + point.shape == (space.parameter_count,) + and np.all(np.isfinite(point)) + and np.all(np.abs(point) <= 1.0) + ) + if finite_and_bounded: + value, gradient_array = compiled_value_gradient( + jnp.asarray(point, dtype=jnp.float64) + ) + gradient = np.asarray(gradient_array, dtype=np.float64) + loss = float(value) + gradient_norm = float(np.linalg.norm(gradient)) + projected_gradient_norm = _projected_gradient_norm(point, gradient) + residual_norm = float(np.linalg.norm(scipy_residual(point))) + endpoint = propagate(system, space.to_physical(point)) + overlap = np.trace(system.target.conj().T @ np.asarray(endpoint)) + phase_consistency_error = abs(abs(overlap) / system.dimension - 1.0) + else: + loss = float("inf") + gradient_norm = float("inf") + projected_gradient_norm = float("inf") + residual_norm = float("inf") + phase_consistency_error = float("inf") + + converged = bool( + solver_success + and finite_and_bounded + and np.isfinite(loss) + and loss <= _POLISH_LOSS_TOLERANCE + and gradient_norm <= _POLISH_GRADIENT_TOLERANCE + and projected_gradient_norm <= _POLISH_GRADIENT_TOLERANCE + and residual_norm <= _POLISH_RESIDUAL_TOLERANCE + and phase_consistency_error <= _POLISH_PHASE_TOLERANCE + ) + return EndpointPolishResult( + normalized_pulse=tuple(float(value) for value in point), + loss=loss, + gradient_norm=gradient_norm, + projected_gradient_norm=projected_gradient_norm, + residual_norm=residual_norm, + phase_consistency_error=float(phase_consistency_error), + evaluations=evaluations, + jacobian_evaluations=jacobian_evaluations, + status=status, + message=message, + cost=cost, + optimality=optimality, + step_norm=float(np.linalg.norm(point - initial_numpy)), + converged=converged, + source_loss=float(accepted.loss), + source_gradient_norm=float(accepted.gradient_norm), + duration=system.duration, + fixed_phase_real=float(fixed_phase.real), + fixed_phase_imag=float(fixed_phase.imag), + ) + + try: + solver = least_squares( + scipy_residual, + initial_numpy, + jac=scipy_jacobian, + bounds=(-1.0, 1.0), + method="trf", + tr_solver="lsmr", + x_scale="jac", + ftol=1e-15, + xtol=1e-15, + gtol=1e-15, + max_nfev=int(max_nfev), + ) + except Exception as error: + failed = diagnostics( + initial_numpy, + evaluations=0, + jacobian_evaluations=0, + status=-1, + message=f"{type(error).__name__}: {error}", + cost=float("inf"), + optimality=float("inf"), + solver_success=False, + ) + raise EndpointPolishingError( + f"endpoint polishing solver failed: {error}", + failed, + ) from error + + def malformed_solver_result(reason: str) -> EndpointPolishingError: + failed = diagnostics( + initial_numpy, + evaluations=0, + jacobian_evaluations=0, + status=-1, + message=f"malformed SciPy result: {reason}", + cost=float("inf"), + optimality=float("inf"), + solver_success=False, + ) + return EndpointPolishingError( + f"endpoint polishing received a malformed SciPy result: {reason}", + failed, + ) + + try: + raw_candidate = solver.x + except Exception as error: + raise malformed_solver_result(f"missing x ({error})") from error + try: + candidate = np.asarray(raw_candidate, dtype=np.float64) + except Exception as error: + raise malformed_solver_result(f"x is not a real array ({error})") from error + if candidate.shape != (space.parameter_count,): + raise malformed_solver_result( + f"x must have shape ({space.parameter_count},), got {candidate.shape}" + ) + + try: + raw_success = solver.success + raw_status = solver.status + raw_message = solver.message + raw_evaluations = solver.nfev + raw_jacobian_evaluations = solver.njev + raw_cost = solver.cost + raw_optimality = solver.optimality + except Exception as error: + raise malformed_solver_result(f"missing solver metadata ({error})") from error + + if not isinstance(raw_success, (bool, np.bool_)): + raise malformed_solver_result("success must be a boolean") + if ( + isinstance(raw_status, (bool, np.bool_)) + or not isinstance(raw_status, Integral) + ): + raise malformed_solver_result("status must be a finite integer") + if ( + isinstance(raw_evaluations, (bool, np.bool_)) + or not isinstance(raw_evaluations, Integral) + or raw_evaluations <= 0 + ): + raise malformed_solver_result("nfev must be a positive integer") + if ( + isinstance(raw_jacobian_evaluations, (bool, np.bool_)) + or not isinstance(raw_jacobian_evaluations, Integral) + or raw_jacobian_evaluations <= 0 + ): + raise malformed_solver_result("njev must be a positive integer") + try: + message = str(raw_message) + cost = float(raw_cost) + optimality = float(raw_optimality) + except Exception as error: + raise malformed_solver_result( + f"solver metadata is not convertible ({error})" + ) from error + if not message: + raise malformed_solver_result("message must be nonempty") + if not np.isfinite(cost) or cost < 0.0: + raise malformed_solver_result("cost must be finite and nonnegative") + if not np.isfinite(optimality) or optimality < 0.0: + raise malformed_solver_result("optimality must be finite and nonnegative") + + result = diagnostics( + candidate, + evaluations=int(raw_evaluations), + jacobian_evaluations=int(raw_jacobian_evaluations), + status=int(raw_status), + message=message, + cost=cost, + optimality=optimality, + solver_success=bool(raw_success), + ) + if not np.all(np.isfinite(candidate)): + raise EndpointPolishingError( + "endpoint polishing returned a nonfinite or malformed candidate", + result, + ) + if np.any(np.abs(candidate) > 1.0): + raise EndpointPolishingError( + "endpoint polishing candidate violates normalized bounds", + result, + ) + if ( + result.loss > _POLISH_LOSS_TOLERANCE + or result.phase_consistency_error > _POLISH_PHASE_TOLERANCE + ): + raise EndpointPolishingError( + "endpoint polishing reached an inconsistent target/global phase", + result, + ) + if not result.converged: + raise EndpointPolishingError( + "endpoint polishing did not converge to stationarity", + result, + ) + return result + + +def endpoint_jacobian( + system: ControlSystem, + space: PulseSpace, + point: object, +) -> NDArray[np.float64]: + if not isinstance(system, ControlSystem): + raise ValueError("system must be a ControlSystem") + if not isinstance(space, PulseSpace): + raise ValueError("space must be a PulseSpace") + if space.control_count != len(system.controls): + raise ValueError("pulse space control count does not match the system") + + point_array = jnp.asarray(point) + if jnp.iscomplexobj(point_array) or point_array.shape != (space.parameter_count,): + raise ValueError( + f"point must be a real vector with shape ({space.parameter_count},)" + ) + point_array = jnp.asarray(point_array, dtype=jnp.float64) + concrete_point = np.asarray(point_array) + if not np.all(np.isfinite(concrete_point)): + raise ValueError("point must contain only finite values") + if np.any(np.abs(concrete_point) > 1.0): + raise ValueError("point exceeds the normalized pulse bounds") + + reference = jax.lax.stop_gradient( + propagate(system, space.to_physical(point_array)) + ) + generators = _generalized_pauli_basis(system.dimension) + identity = jnp.eye(system.dimension, dtype=jnp.complex128) + + def endpoint_coordinates(candidate: jax.Array) -> jax.Array: + endpoint = propagate(system, space.to_physical(candidate)) + relative = reference.conj().T @ endpoint + delta_a = (relative - relative.conj().T) / (2.0j) + traceless = delta_a - ( + jnp.trace(delta_a) / jnp.float64(system.dimension) + ) * identity + return jnp.real(jnp.einsum("kij,ij->k", generators.conj(), traceless)) + + jacobian = jax.jacrev(endpoint_coordinates)(point_array) + return np.asarray(jacobian, dtype=np.float64) + + +def _relative_ranks(values: NDArray[np.float64]) -> RankMap: + magnitudes = np.abs(np.asarray(values, dtype=np.float64)) + scale = float(np.max(magnitudes, initial=0.0)) + if not np.isfinite(scale) or scale == 0.0: + return {threshold: 0 for threshold in _RANK_THRESHOLDS} + return { + threshold: int(np.count_nonzero(magnitudes > threshold * scale)) + for threshold in _RANK_THRESHOLDS + } + + +def _matrix_free_rank_diagnostics( + values: NDArray[np.float64], + *, + spectrum_truncated: bool, +) -> tuple[RankMap, dict[float, bool]]: + magnitudes = np.abs(np.asarray(values, dtype=np.float64)) + ranks = _relative_ranks(values) + scale = float(np.max(magnitudes, initial=0.0)) + smallest_retrieved = float(np.min(magnitudes, initial=np.inf)) + lower_bounds = { + threshold: bool( + spectrum_truncated + and scale > 0.0 + and smallest_retrieved > threshold * scale + ) + for threshold in _RANK_THRESHOLDS + } + return ranks, lower_bounds + + +def _absolute_mode_mask( + values: NDArray[np.float64], + threshold: float, + *, + scale: float | None = None, +) -> NDArray[np.bool_]: + magnitudes = np.abs(np.asarray(values, dtype=np.float64)) + resolved_scale = ( + float(np.max(magnitudes, initial=0.0)) if scale is None else float(scale) + ) + return np.asarray(magnitudes > threshold * resolved_scale, dtype=np.bool_) + + +def _orthonormalize(columns: NDArray[np.float64]) -> NDArray[np.float64]: + if columns.shape[1] == 0: + return np.empty((columns.shape[0], 0), dtype=np.float64) + basis, _ = np.linalg.qr(np.asarray(columns, dtype=np.float64), mode="reduced") + return np.asarray(basis, dtype=np.float64) + + +def _subspace_diagnostics( + left: NDArray[np.float64], + right: NDArray[np.float64], +) -> tuple[float, NDArray[np.float64]]: + left_basis = _orthonormalize(left) + right_basis = _orthonormalize(right) + if left_basis.shape[1] != right_basis.shape[1]: + raise ValueError("subspaces must have the same dimension") + if left_basis.shape[1] == 0: + return 0.0, np.empty(0, dtype=np.float64) + left_projector = left_basis @ left_basis.T + right_projector = right_basis @ right_basis.T + residual = float(np.linalg.norm(left_projector - right_projector, ord=2)) + cosines = np.linalg.svd(left_basis.T @ right_basis, compute_uv=False) + angles = np.arccos(np.clip(cosines, 0.0, 1.0)) + return residual, np.sort(np.asarray(angles, dtype=np.float64))[::-1] + + +def _leading_eigenpairs( + loss_fn: Callable[[jax.Array], jax.Array], + point: jax.Array, + count: int, +) -> tuple[NDArray[np.float64], NDArray[np.float64]]: + parameter_count = int(point.size) + if count >= parameter_count: + raise ValueError("leading_count must be smaller than the parameter count") + + compiled_hvp = jax.jit( + lambda vector: hessian_vector_product(loss_fn, point, vector) + ).lower(jnp.zeros_like(point)).compile() + + def matrix_vector(vector: NDArray[np.float64]) -> NDArray[np.float64]: + product = compiled_hvp(jnp.asarray(vector, dtype=jnp.float64)) + return np.ascontiguousarray(np.asarray(product, dtype=np.float64)) + + operator = LinearOperator( + shape=(parameter_count, parameter_count), + matvec=matrix_vector, + rmatvec=matrix_vector, + dtype=np.dtype(np.float64), + ) + initial = np.linspace(-1.0, 1.0, parameter_count, dtype=np.float64) + initial /= np.linalg.norm(initial) + values, vectors = eigsh( + operator, + k=count, + which="LM", + v0=initial, + ncv=min(parameter_count, max(2 * count + 1, 20)), + tol=1e-12, + maxiter=max(1000, 20 * parameter_count), + ) + order = np.argsort(np.abs(values))[::-1] + return ( + np.asarray(values[order], dtype=np.float64), + _orthonormalize(np.asarray(vectors[:, order], dtype=np.float64)), + ) + + +def analyze_landscape( + system: ControlSystem, + space: PulseSpace, + accepted: OpenLoopResult, + *, + leading_count: int, + dense_validation: bool = True, + polish: bool = True, +) -> LandscapeResult: + if not isinstance(system, ControlSystem): + raise ValueError("system must be a ControlSystem") + if not isinstance(space, PulseSpace): + raise ValueError("space must be a PulseSpace") + if not isinstance(accepted, OpenLoopResult): + raise ValueError("accepted must be an OpenLoopResult") + if accepted.loss > _ACCEPTANCE_LOSS or not np.isfinite(accepted.loss): + raise ValueError("OpenLoopResult must satisfy the 1e-8 acceptance threshold") + if ( + isinstance(leading_count, (bool, np.bool_)) + or not isinstance(leading_count, Integral) + or leading_count <= 0 + ): + raise ValueError("leading_count must be a positive integer") + leading_count = int(leading_count) + if not isinstance(polish, (bool, np.bool_)): + raise ValueError("polish must be a boolean") + + polishing = polish_endpoint(system, space, accepted) if polish else None + source_point = ( + polishing.normalized_pulse + if polishing is not None + else accepted.normalized_pulse + ) + point = jnp.asarray(source_point, dtype=jnp.float64) + if point.shape != (space.parameter_count,): + raise ValueError("accepted pulse shape does not match the PulseSpace") + if leading_count >= space.parameter_count: + raise ValueError("leading_count must be smaller than the parameter count") + + loss_fn = lambda candidate: normalized_infidelity(candidate, system, space) + leading_values, leading_vectors = _leading_eigenpairs( + loss_fn, + point, + leading_count, + ) + jacobian = endpoint_jacobian(system, space, point) + _, singular_values, right_vectors_transpose = np.linalg.svd( + jacobian, + full_matrices=False, + ) + jacobian_ranks = _relative_ranks(singular_values) + endpoint_basis = _orthonormalize(right_vectors_transpose.T) + + dense_matrix: NDArray[np.float64] | None = None + dense_values: NDArray[np.float64] | None = None + dense_vectors: NDArray[np.float64] | None = None + projector_residuals: dict[float, float] = {} + principal_angles: AngleMap = {} + if dense_validation: + dense_matrix = dense_hessian(loss_fn, point) + ascending_values, ascending_vectors = np.linalg.eigh(dense_matrix) + order = np.argsort(np.abs(ascending_values))[::-1] + dense_values = np.asarray(ascending_values[order], dtype=np.float64) + dense_vectors = np.asarray(ascending_vectors[:, order], dtype=np.float64) + hessian_ranks = _relative_ranks(dense_values) + hessian_rank_is_lower_bound = { + threshold: False for threshold in _RANK_THRESHOLDS + } + dense_scale = float(np.max(np.abs(dense_values), initial=0.0)) + for threshold, rank in hessian_ranks.items(): + dense_mask = _absolute_mode_mask( + dense_values, + threshold, + scale=dense_scale, + ) + leading_mask = _absolute_mode_mask( + leading_values, + threshold, + scale=dense_scale, + ) + if rank > leading_count or np.count_nonzero(leading_mask) != rank: + continue + residual, angles = _subspace_diagnostics( + dense_vectors[:, dense_mask], + leading_vectors[:, leading_mask], + ) + projector_residuals[threshold] = residual + principal_angles[threshold] = angles + else: + hessian_ranks, hessian_rank_is_lower_bound = ( + _matrix_free_rank_diagnostics( + leading_values, + spectrum_truncated=leading_count < space.parameter_count, + ) + ) + + model_basis = ( + np.asarray(dense_vectors, dtype=np.float64) + if dense_vectors is not None + else np.asarray(leading_vectors, dtype=np.float64) + ) + + return LandscapeResult( + leading_eigenvalues=leading_values, + leading_eigenvectors=leading_vectors, + jacobian_singular_values=np.asarray(singular_values, dtype=np.float64), + hessian_ranks=hessian_ranks, + hessian_rank_is_lower_bound=hessian_rank_is_lower_bound, + jacobian_ranks=jacobian_ranks, + model_basis=model_basis, + search_basis_available_columns=int(model_basis.shape[1]), + endpoint_basis=endpoint_basis, + dense_hessian=dense_matrix, + dense_eigenvalues=dense_values, + dense_eigenvectors=dense_vectors, + dense_hvp_projector_residuals=projector_residuals, + dense_hvp_principal_angles=principal_angles, + polishing=polishing, + eigenvalue_ordering="descending absolute", + ) diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/src/qcontrol/objectives.py b/tracks/qcs/solutions/frustration-free/challenge-113/src/qcontrol/objectives.py new file mode 100644 index 000000000..bd11910d2 --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/src/qcontrol/objectives.py @@ -0,0 +1,106 @@ +from __future__ import annotations + +import jax +import jax.numpy as jnp +import numpy as np + +from qcontrol.propagation import propagate +from qcontrol.pulses import PulseSpace +from qcontrol.systems import ControlSystem + + +def _raise_for_nonfinite_eager(array: jax.Array, name: str) -> None: + if isinstance(array, jax.core.Tracer): + return + if not np.all(np.isfinite(np.asarray(array))): + raise ValueError(f"{name} must contain only finite values") + + +def _guarded_unclipped_process_infidelity( + unitary: object, + target: object, +) -> tuple[jax.Array, jax.Array]: + unitary_array = jnp.asarray(unitary, dtype=jnp.complex128) + target_array = jnp.asarray(target, dtype=jnp.complex128) + if ( + unitary_array.ndim != 2 + or unitary_array.shape[0] == 0 + or unitary_array.shape[0] != unitary_array.shape[1] + ): + raise ValueError("unitary must be a nonempty square matrix") + if target_array.shape != unitary_array.shape: + raise ValueError("target must match the unitary shape") + _raise_for_nonfinite_eager(unitary_array, "unitary") + _raise_for_nonfinite_eager(target_array, "target") + + dimension = unitary_array.shape[0] + valid = jnp.all(jnp.isfinite(unitary_array)) & jnp.all( + jnp.isfinite(target_array) + ) + safe_unitary = jnp.where(valid, unitary_array, jnp.zeros_like(unitary_array)) + safe_target = jnp.where(valid, target_array, jnp.zeros_like(target_array)) + overlap = jnp.trace(safe_target.conj().T @ safe_unitary) + fidelity = jnp.real(overlap.conj() * overlap) / jnp.float64(dimension**2) + loss = jnp.asarray(1.0 - fidelity, dtype=jnp.float64) + return jnp.where(valid, loss, jnp.float64(jnp.inf)), valid + + +def process_infidelity_from_unitary( + unitary: object, + target: object, +) -> jax.Array: + loss, valid = _guarded_unclipped_process_infidelity(unitary, target) + reported = jnp.clip(loss, jnp.float64(0.0), jnp.float64(1.0)) + return jnp.where(valid, reported, jnp.float64(jnp.inf)) + + +def normalized_infidelity( + normalized: object, + system: ControlSystem, + space: PulseSpace, +) -> jax.Array: + if not isinstance(system, ControlSystem): + raise ValueError("system must be a ControlSystem") + if not isinstance(space, PulseSpace): + raise ValueError("space must be a PulseSpace") + if len(system.controls) != space.control_count: + raise ValueError("pulse space control count does not match the system") + if tuple(space.amplitude_scales) != tuple(system.amplitude_scales): + raise ValueError("pulse space amplitude scales do not match the system") + + normalized_array = jnp.asarray(normalized) + if jnp.iscomplexobj(normalized_array): + raise ValueError("normalized pulse must be real") + expected_shape = (space.control_count * space.segments,) + if normalized_array.shape != expected_shape: + raise ValueError( + "normalized pulse shape does not match the PulseSpace segment count" + ) + normalized_array = jnp.asarray(normalized_array, dtype=jnp.float64) + + if not isinstance(normalized_array, jax.core.Tracer): + concrete = np.asarray(normalized_array) + if not np.all(np.isfinite(concrete)): + raise ValueError("normalized pulse must contain only finite values") + if np.any(np.abs(concrete) > 1.0): + raise ValueError("normalized pulse exceeds the hard [-1, 1] bounds") + + valid = jnp.all(jnp.isfinite(normalized_array)) & jnp.all( + jnp.abs(normalized_array) <= 1.0 + ) + safe_normalized = jnp.where( + valid, + normalized_array, + jnp.zeros_like(normalized_array), + ) + physical_pulse = space.to_physical(safe_normalized) + unitary = propagate(system, physical_pulse) + loss, propagation_valid = _guarded_unclipped_process_infidelity( + unitary, + system.target, + ) + return jnp.where( + valid & propagation_valid, + loss, + jnp.float64(jnp.inf), + ) diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/src/qcontrol/offline.py b/tracks/qcs/solutions/frustration-free/challenge-113/src/qcontrol/offline.py new file mode 100644 index 000000000..007431f16 --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/src/qcontrol/offline.py @@ -0,0 +1,514 @@ +from __future__ import annotations + +from collections.abc import Callable, Sequence +from dataclasses import dataclass +import math + +import jax +import jax.numpy as jnp +import numpy as np +from scipy.linalg import subspace_angles +from scipy.optimize import minimize + +from qcontrol.closed_loop import SearchSpace +from qcontrol.device import Observation +from qcontrol.objectives import normalized_infidelity +from qcontrol.pulses import PulseSpace +from qcontrol.systems import ControlSystem + + +@dataclass(frozen=True, slots=True) +class ExactInfidelityTrajectory: + initial_infidelity: float + cumulative_best_by_optimizer_query: tuple[float, ...] + best_successful_audited_infidelity: float | None = None + + def canonical_dict(self) -> dict[str, object]: + return { + "cumulative_best_by_optimizer_query": list( + self.cumulative_best_by_optimizer_query + ), + "best_successful_audited_infidelity": ( + self.best_successful_audited_infidelity + ), + "initial_infidelity": self.initial_infidelity, + } + + +@dataclass(frozen=True, slots=True) +class RestrictedOptimizationResult: + attained_infidelity_upper_bound: float + starting_infidelity_upper_bound: float + max_iterations: int + max_evaluations: int + gradient_tolerance: float + consistency_tolerance: float + nfev: int + nit: int + solver_success: bool + solver_status: int + solver_message_code: str + solver_output_finite: bool + termination: str + + def canonical_dict(self) -> dict[str, object]: + return { + "cached_solver_attained_infidelity_upper_bound": ( + self.attained_infidelity_upper_bound + ), + "cached_solver_starting_infidelity_upper_bound": ( + self.starting_infidelity_upper_bound + ), + "certified": self.termination == "converged", + "consistency_tolerance": self.consistency_tolerance, + "gradient_tolerance": self.gradient_tolerance, + "max_evaluations": self.max_evaluations, + "max_iterations": self.max_iterations, + "nfev": self.nfev, + "nit": self.nit, + "solver": "L-BFGS-B", + "solver_message_code": self.solver_message_code, + "solver_output_finite": self.solver_output_finite, + "solver_status": self.solver_status, + "solver_success": self.solver_success, + "termination": self.termination, + } + + +@dataclass(frozen=True, slots=True) +class GeometryDiagnostics: + rank_thresholds: tuple[float, ...] + model_effective_ranks: tuple[int, ...] + truth_effective_ranks: tuple[int, ...] + model_eigenvalues: np.ndarray + truth_eigenvalues: np.ndarray + model_eigenvectors: np.ndarray + truth_eigenvectors: np.ndarray + + @property + def signed_eigenvalue_gaps(self) -> tuple[float, ...]: + return tuple( + float(value) + for value in self.truth_eigenvalues - self.model_eigenvalues + ) + + @property + def principal_angles_radians(self) -> tuple[float, ...]: + return self.slice(len(self.model_eigenvalues)).principal_angles_radians + + def slice(self, top_k: int) -> GeometrySlice: + if type(top_k) is not int or not 0 < top_k <= len(self.model_eigenvalues): + raise ValueError("geometry top_k must fit the available spectrum") + model_top = np.asarray( + self.model_eigenvectors[:, :top_k], + dtype=np.float64, + ) + truth_top = np.asarray( + self.truth_eigenvectors[:, :top_k], + dtype=np.float64, + ) + angles = np.asarray(subspace_angles(model_top, truth_top), dtype=np.float64) + if not np.all(np.isfinite(angles)): + raise ValueError("principal angles must be finite") + return GeometrySlice( + rank_thresholds=self.rank_thresholds, + model_effective_ranks=self.model_effective_ranks, + truth_effective_ranks=self.truth_effective_ranks, + signed_leading_eigenvalue_gaps=tuple( + float(value) + for value in ( + self.truth_eigenvalues[:top_k] + - self.model_eigenvalues[:top_k] + ) + ), + principal_angles_radians=tuple(float(value) for value in angles), + model_top_subspace=model_top, + truth_top_subspace=truth_top, + ) + + +@dataclass(frozen=True, slots=True) +class GeometrySlice: + rank_thresholds: tuple[float, ...] + model_effective_ranks: tuple[int, ...] + truth_effective_ranks: tuple[int, ...] + signed_leading_eigenvalue_gaps: tuple[float, ...] + principal_angles_radians: tuple[float, ...] + model_top_subspace: np.ndarray + truth_top_subspace: np.ndarray + + +def make_offline_evaluator( + truth: ControlSystem, + space: PulseSpace, +) -> Callable[[object], float]: + """Build an exact evaluator for analysis code outside the device API.""" + if not isinstance(truth, ControlSystem): + raise ValueError("truth must be a ControlSystem") + if not isinstance(space, PulseSpace): + raise ValueError("space must be a PulseSpace") + if len(truth.controls) != space.control_count: + raise ValueError("pulse space control count does not match the truth system") + if tuple(truth.amplitude_scales) != tuple(space.amplitude_scales): + raise ValueError("pulse space amplitude scales do not match the truth system") + + def evaluate(normalized_pulse: object) -> float: + loss = float(normalized_infidelity(normalized_pulse, truth, space)) + fidelity = float(np.clip(1.0 - loss, 0.0, 1.0)) + if not math.isfinite(fidelity): + raise ValueError("truth evaluation did not produce a finite fidelity") + return fidelity + + return evaluate + + +def _finite_probability(value: object, *, name: str) -> float: + if isinstance(value, bool) or not isinstance(value, (int, float)): + raise ValueError(f"{name} must be a finite probability") + result = float(value) + if not math.isfinite(result) or not 0.0 <= result <= 1.0: + raise ValueError(f"{name} must be a finite probability") + return result + + +def cumulative_best_exact_infidelity( + evaluator: Callable[[object], float], + *, + initial_pulse: object, + audited_queries: Sequence[tuple[object, Observation | None]], +) -> ExactInfidelityTrajectory: + if not callable(evaluator): + raise ValueError("evaluator must be callable") + initial = 1.0 - _finite_probability( + evaluator(initial_pulse), + name="initial exact fidelity", + ) + best = initial + best_successful: float | None = None + values: list[float] = [] + for expected_query, (pulse, observation) in enumerate(audited_queries, start=1): + if observation is not None: + if ( + not isinstance(observation, Observation) + or observation.validation + or observation.optimizer_query_index != expected_query + ): + raise ValueError("audited observation is not query-aligned") + infidelity = 1.0 - _finite_probability( + evaluator(pulse), + name="offline exact fidelity", + ) + best_successful = ( + infidelity + if best_successful is None + else min(best_successful, infidelity) + ) + best = min(best, infidelity) + values.append(float(best)) + return ExactInfidelityTrajectory( + float(initial), + tuple(values), + None if best_successful is None else float(best_successful), + ) + + +def effective_ranks( + spectrum: object, + thresholds: Sequence[float] = (1e-6, 1e-8, 1e-10), +) -> tuple[int, ...]: + values = np.asarray(spectrum, dtype=np.float64) + if values.ndim != 1 or not np.all(np.isfinite(values)): + raise ValueError("effective-rank spectrum must be a finite vector") + resolved_thresholds = tuple(float(value) for value in thresholds) + if ( + not resolved_thresholds + or any( + not math.isfinite(value) or value <= 0.0 + for value in resolved_thresholds + ) + ): + raise ValueError("effective-rank thresholds must be finite and positive") + scale = float(np.max(np.abs(values), initial=0.0)) + if scale == 0.0: + return tuple(0 for _ in resolved_thresholds) + return tuple( + int(np.count_nonzero(np.abs(values) > threshold * scale)) + for threshold in resolved_thresholds + ) + + +def canonical_solver_message_code(message: object) -> str: + normalized = str(message).upper() + if "CONVERGENCE" in normalized: + return "convergence" + if "ITERATION" in normalized and "LIMIT" in normalized: + return "iteration_limit" + if ( + ("EVALUATION" in normalized or "F AND G" in normalized) + and ("LIMIT" in normalized or "EXCEED" in normalized) + ): + return "evaluation_limit" + if ( + "LNSRCH" in normalized + or "LINE SEARCH" in normalized + or "ABNORMAL" in normalized + ): + return "line_search_failure" + if any(token in normalized for token in ("NAN", "INF", "NUMERICAL")): + return "numerical_failure" + return "solver_failure" + + +def classify_solver_termination( + *, + success: bool, + status: int, + message_code: str, + output_finite: bool, + nit: int, + nfev: int, + max_iterations: int, + max_evaluations: int, +) -> str: + allowed_codes = { + "convergence", + "evaluation_limit", + "iteration_limit", + "line_search_failure", + "numerical_failure", + "solver_failure", + } + if ( + type(success) is not bool + or type(status) is not int + or message_code not in allowed_codes + or type(output_finite) is not bool + or type(nit) is not int + or nit < 0 + or type(nfev) is not int + or nfev <= 0 + or type(max_iterations) is not int + or max_iterations <= 0 + or type(max_evaluations) is not int + or max_evaluations <= 0 + ): + raise ValueError("solver raw facts are invalid") + if success: + if status != 0 or message_code != "convergence" or not output_finite: + raise ValueError("solver success facts are contradictory") + return "converged" + if status == 0 or message_code == "convergence": + raise ValueError("solver status-zero facts are contradictory") + if not output_finite: + return "numerical_failure" + if message_code == "numerical_failure": + raise ValueError("finite solver output cannot be numerical_failure") + if message_code == "iteration_limit": + if status != 1 or nit == 0 or nit < max_iterations: + raise ValueError("solver iteration-limit facts are contradictory") + return "iteration_limit" + if message_code == "evaluation_limit": + if status != 1 or nfev < max_evaluations: + raise ValueError("solver evaluation-limit facts are contradictory") + return "evaluation_limit" + if message_code == "line_search_failure": + if status != 2: + raise ValueError("solver line-search facts are contradictory") + return "line_search_failure" + if status == 1: + raise ValueError("solver limit status lacks a limit message") + return "solver_failure" + + +def optimize_restricted_noiseless_upper_bound( + truth: ControlSystem, + pulse_space: PulseSpace, + search_space: SearchSpace, + *, + max_iterations: int = 100, + max_evaluations: int = 1_000, + gradient_tolerance: float = 1e-9, + consistency_tolerance: float = 1e-10, +) -> RestrictedOptimizationResult: + if not isinstance(truth, ControlSystem) or not isinstance(pulse_space, PulseSpace): + raise ValueError("restricted optimization requires truth and pulse space") + if not isinstance(search_space, SearchSpace): + raise ValueError("restricted optimization requires a SearchSpace") + if type(max_iterations) is not int or max_iterations <= 0: + raise ValueError("max_iterations must be a positive integer") + if type(max_evaluations) is not int or max_evaluations <= 0: + raise ValueError("max_evaluations must be a positive integer") + if ( + not math.isfinite(gradient_tolerance) + or gradient_tolerance <= 0.0 + or not math.isfinite(consistency_tolerance) + or consistency_tolerance < 0.0 + ): + raise ValueError("restricted optimization tolerances must be finite") + + evaluator = make_offline_evaluator(truth, pulse_space) + starting = float(1.0 - evaluator(search_space.origin)) + coordinates = np.zeros(search_space.dimension, dtype=np.float64) + origin = jnp.asarray(search_space.origin, dtype=jnp.float64) + basis = jnp.asarray(search_space.basis, dtype=jnp.float64) + + def objective(raw_coordinates: jax.Array) -> jax.Array: + pulse = jnp.clip(origin + basis @ raw_coordinates, -1.0, 1.0) + return normalized_infidelity(pulse, truth, pulse_space) + + value_and_gradient = jax.jit(jax.value_and_grad(objective)) + + def scipy_objective(raw_coordinates: np.ndarray) -> tuple[float, np.ndarray]: + value, gradient = value_and_gradient( + jnp.asarray(raw_coordinates, dtype=jnp.float64) + ) + return float(value), np.asarray(gradient, dtype=np.float64) + + solved = minimize( + scipy_objective, + coordinates, + method="L-BFGS-B", + jac=True, + bounds=list( + zip( + search_space.lower_bounds.tolist(), + search_space.upper_bounds.tolist(), + ) + ), + options={ + "ftol": consistency_tolerance, + "gtol": gradient_tolerance, + "maxfun": max_evaluations, + "maxiter": max_iterations, + "maxls": 50, + }, + ) + output_finite = bool( + np.all(np.isfinite(np.asarray(solved.fun))) + and np.all(np.isfinite(np.asarray(solved.x))) + and np.all(np.isfinite(np.asarray(solved.jac))) + ) + message_code = canonical_solver_message_code(solved.message) + termination = classify_solver_termination( + success=bool(solved.success), + status=int(solved.status), + message_code=message_code, + output_finite=output_finite, + nit=int(solved.nit), + nfev=int(solved.nfev), + max_iterations=max_iterations, + max_evaluations=max_evaluations, + ) + solved_value = float(solved.fun) + finite_solved = ( + math.isfinite(solved_value) + and solved_value >= -consistency_tolerance + and np.all(np.isfinite(solved.x)) + ) + attained = float( + np.clip( + min(starting, solved_value) if finite_solved else starting, + 0.0, + 1.0, + ) + ) + return RestrictedOptimizationResult( + attained_infidelity_upper_bound=float(attained), + starting_infidelity_upper_bound=starting, + max_iterations=max_iterations, + max_evaluations=max_evaluations, + gradient_tolerance=float(gradient_tolerance), + consistency_tolerance=float(consistency_tolerance), + nfev=int(solved.nfev), + nit=int(solved.nit), + solver_success=bool(solved.success), + solver_status=int(solved.status), + solver_message_code=message_code, + solver_output_finite=output_finite, + termination=termination, + ) + + +def finalize_restricted_attained_bound( + cached: RestrictedOptimizationResult, + exact: ExactInfidelityTrajectory, +) -> dict[str, object]: + if not isinstance(cached, RestrictedOptimizationResult) or not isinstance( + exact, + ExactInfidelityTrajectory, + ): + raise ValueError("restricted finalization requires derived metric results") + evidence = ( + ("initial_origin", exact.initial_infidelity), + ("audited_candidate", exact.best_successful_audited_infidelity), + ("restricted_solver", cached.attained_infidelity_upper_bound), + ) + available = [ + (source, float(value)) + for source, value in evidence + if value is not None + ] + if any(not math.isfinite(value) or not 0.0 <= value <= 1.0 for _, value in available): + raise ValueError("attained-bound evidence must be finite probabilities") + minimum = min(value for _, value in available) + source = next(source for source, value in available if value == minimum) + return { + **cached.canonical_dict(), + "attained_infidelity_upper_bound": minimum, + "attained_infidelity_source": source, + "best_successful_audited_exact_infidelity": ( + exact.best_successful_audited_infidelity + ), + "initial_exact_infidelity": exact.initial_infidelity, + } + + +def compute_geometry_diagnostics( + model: ControlSystem, + truth: ControlSystem, + pulse_space: PulseSpace, + origin: object, + *, + rank_thresholds: tuple[float, ...] = (1e-6, 1e-8, 1e-10), +) -> GeometryDiagnostics: + point = jnp.asarray(origin, dtype=jnp.float64) + if point.ndim != 1: + raise ValueError("geometry origin must be a vector") + + def hessian(system: ControlSystem) -> np.ndarray: + matrix = np.asarray( + jax.hessian( + lambda pulse: normalized_infidelity(pulse, system, pulse_space) + )(point), + dtype=np.float64, + ) + return np.asarray(0.5 * (matrix + matrix.T), dtype=np.float64) + + model_hessian = hessian(model) + truth_hessian = hessian(truth) + if not np.all(np.isfinite(model_hessian)) or not np.all(np.isfinite(truth_hessian)): + raise ValueError("geometry Hessians must be finite") + model_values, model_vectors = np.linalg.eigh(model_hessian) + truth_values, truth_vectors = np.linalg.eigh(truth_hessian) + model_order = np.argsort(np.abs(model_values))[::-1] + truth_order = np.argsort(np.abs(truth_values))[::-1] + model_values = model_values[model_order] + truth_values = truth_values[truth_order] + model_vectors = np.asarray(model_vectors[:, model_order], dtype=np.float64) + truth_vectors = np.asarray(truth_vectors[:, truth_order], dtype=np.float64) + if ( + not np.all(np.isfinite(model_values)) + or not np.all(np.isfinite(truth_values)) + or not np.all(np.isfinite(model_vectors)) + or not np.all(np.isfinite(truth_vectors)) + ): + raise ValueError("geometry diagnostics must be finite") + return GeometryDiagnostics( + rank_thresholds=rank_thresholds, + model_effective_ranks=effective_ranks(model_values, rank_thresholds), + truth_effective_ranks=effective_ranks(truth_values, rank_thresholds), + model_eigenvalues=np.asarray(model_values, dtype=np.float64), + truth_eigenvalues=np.asarray(truth_values, dtype=np.float64), + model_eigenvectors=model_vectors, + truth_eigenvectors=truth_vectors, + ) diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/src/qcontrol/open_loop.py b/tracks/qcs/solutions/frustration-free/challenge-113/src/qcontrol/open_loop.py new file mode 100644 index 000000000..95590a4cf --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/src/qcontrol/open_loop.py @@ -0,0 +1,186 @@ +from __future__ import annotations + +from dataclasses import dataclass +from numbers import Integral + +import jax +import jax.numpy as jnp +import numpy as np +from numpy.typing import NDArray +from scipy.optimize import minimize + +from qcontrol.objectives import normalized_infidelity +from qcontrol.pulses import PulseSpace +from qcontrol.systems import ControlSystem + + +_ACCEPTANCE_LOSS = 1e-8 + + +@dataclass(frozen=True) +class StartDiagnostic: + index: int + loss: float + gradient_norm: float + success: bool + status: int + message: str + evaluations: int + + +@dataclass(frozen=True) +class OpenLoopResult: + normalized_pulse: tuple[float, ...] + loss: float + gradient_norm: float + starts: int + evaluations: int + + +class OpenLoopAcceptanceError(RuntimeError): + def __init__(self, diagnostics: tuple[StartDiagnostic, ...]) -> None: + self.diagnostics = diagnostics + details = "; ".join( + ( + f"start {item.index}: loss={item.loss:.17g}, " + f"gradient_norm={item.gradient_norm:.17g}, " + f"success={item.success}, status={item.status}, " + f"evaluations={item.evaluations}, message={item.message!r}" + ) + for item in diagnostics + ) + super().__init__( + f"no open-loop start reached loss <= {_ACCEPTANCE_LOSS:.0e}; {details}" + ) + + +def _validate_inputs( + system: ControlSystem, + space: PulseSpace, + seed: object, + starts: object, +) -> tuple[int, int]: + if not isinstance(system, ControlSystem): + raise ValueError("system must be a ControlSystem") + if not isinstance(space, PulseSpace): + raise ValueError("space must be a PulseSpace") + if isinstance(seed, (bool, np.bool_)) or not isinstance(seed, Integral) or seed < 0: + raise ValueError("seed must be a nonnegative integer") + if ( + isinstance(starts, (bool, np.bool_)) + or not isinstance(starts, Integral) + or starts <= 0 + ): + raise ValueError("starts must be a positive integer") + return int(seed), int(starts) + + +def optimize_open_loop( + system: ControlSystem, + space: PulseSpace, + seed: int, + starts: int = 5, +) -> OpenLoopResult: + seed, starts = _validate_inputs(system, space, seed, starts) + parameter_count = space.parameter_count + + value_and_gradient = jax.jit( + jax.value_and_grad( + lambda pulse: normalized_infidelity(pulse, system, space) + ) + ) + compiled = value_and_gradient.lower( + jnp.zeros(parameter_count, dtype=jnp.float64) + ).compile() + + rng = np.random.default_rng(seed) + initial_points = [np.zeros(parameter_count, dtype=np.float64)] + initial_points.extend( + rng.uniform(-1.0, 1.0, parameter_count).astype(np.float64) + for _ in range(starts - 1) + ) + + diagnostics: list[StartDiagnostic] = [] + candidates: list[ + tuple[float, float, int, tuple[float, ...], StartDiagnostic] + ] = [] + total_evaluations = 0 + + for start_index, initial in enumerate(initial_points): + start_evaluations = 0 + + def scipy_objective( + pulse: NDArray[np.float64], + ) -> tuple[float, NDArray[np.float64]]: + nonlocal start_evaluations, total_evaluations + start_evaluations += 1 + total_evaluations += 1 + value, gradient = compiled(jnp.asarray(pulse, dtype=jnp.float64)) + return ( + float(value), + np.ascontiguousarray(np.asarray(gradient, dtype=np.float64)), + ) + + try: + optimization = minimize( + scipy_objective, + np.ascontiguousarray(initial, dtype=np.float64), + method="L-BFGS-B", + jac=True, + bounds=[(-1.0, 1.0)] * parameter_count, + options={ + "ftol": 1e-15, + "gtol": 1e-10, + "maxiter": 2000, + "maxls": 50, + }, + ) + except Exception as error: + diagnostics.append( + StartDiagnostic( + index=start_index, + loss=float("inf"), + gradient_norm=float("inf"), + success=False, + status=-1, + message=f"{type(error).__name__}: {error}", + evaluations=start_evaluations, + ) + ) + raise OpenLoopAcceptanceError(tuple(diagnostics)) from error + pulse = np.ascontiguousarray(optimization.x, dtype=np.float64) + gradient = np.ascontiguousarray(optimization.jac, dtype=np.float64) + loss = float(optimization.fun) + gradient_norm = float(np.linalg.norm(gradient)) + diagnostic = StartDiagnostic( + index=start_index, + loss=loss, + gradient_norm=gradient_norm, + success=bool(optimization.success), + status=int(optimization.status), + message=str(optimization.message), + evaluations=start_evaluations, + ) + diagnostics.append(diagnostic) + candidates.append( + ( + loss, + gradient_norm, + start_index, + tuple(float(value) for value in pulse), + diagnostic, + ) + ) + + accepted = [candidate for candidate in candidates if candidate[0] <= _ACCEPTANCE_LOSS] + if not accepted: + raise OpenLoopAcceptanceError(tuple(diagnostics)) + + loss, gradient_norm, _, normalized_pulse, _ = min(accepted, key=lambda item: item[:3]) + return OpenLoopResult( + normalized_pulse=normalized_pulse, + loss=loss, + gradient_norm=gradient_norm, + starts=starts, + evaluations=total_evaluations, + ) diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/src/qcontrol/propagation.py b/tracks/qcs/solutions/frustration-free/challenge-113/src/qcontrol/propagation.py new file mode 100644 index 000000000..44f5e414a --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/src/qcontrol/propagation.py @@ -0,0 +1,92 @@ +from __future__ import annotations + +import jax +import jax.numpy as jnp +import numpy as np + +from qcontrol.systems import ControlSystem + + +jax.config.update("jax_enable_x64", True) + + +@jax.jit +def _propagate_kernel( + drift: jax.Array, + controls: jax.Array, + physical_pulse: jax.Array, + duration: jax.Array, +) -> jax.Array: + segment_duration = duration / physical_pulse.shape[1] + identity = jnp.eye(drift.shape[0], dtype=jnp.complex128) + + def apply_segment(unitary: jax.Array, amplitudes: jax.Array) -> tuple[jax.Array, None]: + hamiltonian = drift + jnp.tensordot(amplitudes, controls, axes=1) + segment = jax.scipy.linalg.expm(-1.0j * segment_duration * hamiltonian) + return segment @ unitary, None + + unitary, _ = jax.lax.scan(apply_segment, identity, physical_pulse.T) + return unitary + + +def _raise_for_invalid_eager_pulse(pulse: jax.Array) -> None: + if isinstance(pulse, jax.core.Tracer): + return + if not np.all(np.isfinite(np.asarray(pulse))): + raise ValueError("physical pulse must contain only finite values") + + +def _raise_for_invalid_eager_duration(array: jax.Array) -> None: + if isinstance(array, jax.core.Tracer): + return + value = float(np.asarray(array)) + if not np.isfinite(value) or value <= 0.0: + raise ValueError("duration must be a positive finite number") + + +def propagate( + system: ControlSystem, + physical_pulse: object, + duration: float | None = None, +) -> jax.Array: + if not isinstance(system, ControlSystem): + raise ValueError("system must be a ControlSystem") + pulse = jnp.asarray(physical_pulse) + if jnp.iscomplexobj(pulse): + raise ValueError("physical pulse must be real") + if pulse.ndim != 2 or pulse.shape[0] != len(system.controls) or pulse.shape[1] <= 0: + raise ValueError( + "physical pulse must have shape (control_count, positive_segments)" + ) + pulse = jnp.asarray(pulse, dtype=jnp.float64) + _raise_for_invalid_eager_pulse(pulse) + + resolved_duration = system.duration if duration is None else duration + duration_array = jnp.asarray(resolved_duration) + if duration_array.shape != () or jnp.iscomplexobj(duration_array): + raise ValueError("duration must be a real scalar") + if jnp.issubdtype(duration_array.dtype, jnp.bool_): + raise ValueError("duration must be a positive finite number") + duration_array = jnp.asarray(duration_array, dtype=jnp.float64) + _raise_for_invalid_eager_duration(duration_array) + + valid = ( + jnp.all(jnp.isfinite(pulse)) + & jnp.isfinite(duration_array) + & (duration_array > 0.0) + ) + safe_pulse = jnp.where(valid, pulse, jnp.zeros_like(pulse)) + safe_duration = jnp.where(valid, duration_array, jnp.float64(0.0)) + + drift = jnp.asarray(system.drift, dtype=jnp.complex128) + controls = jnp.stack( + tuple(jnp.asarray(control, dtype=jnp.complex128) for control in system.controls) + ) + unitary = _propagate_kernel( + drift, + controls, + safe_pulse, + safe_duration, + ) + invalid = jnp.full_like(unitary, jnp.nan + 1.0j * jnp.nan) + return jnp.where(valid, unitary, invalid) diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/src/qcontrol/pulses.py b/tracks/qcs/solutions/frustration-free/challenge-113/src/qcontrol/pulses.py new file mode 100644 index 000000000..840d17192 --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/src/qcontrol/pulses.py @@ -0,0 +1,122 @@ +from __future__ import annotations + +from dataclasses import dataclass +import math +from numbers import Integral, Real + +import jax +import jax.numpy as jnp +import numpy as np + +from qcontrol.systems import ControlSystem + + +def _positive_integer(name: str, value: object) -> int: + if isinstance(value, (bool, np.bool_)) or not isinstance(value, Integral): + raise ValueError(f"{name} must be a positive integer") + result = int(value) + if result <= 0: + raise ValueError(f"{name} must be a positive integer") + return result + + +def _positive_finite(name: str, value: object) -> float: + if isinstance(value, (bool, np.bool_)) or not isinstance(value, Real): + raise ValueError(f"{name} must be a positive finite number") + result = float(value) + if not math.isfinite(result) or result <= 0.0: + raise ValueError(f"{name} must be a positive finite number") + return result + + +def _raise_for_invalid_eager_values( + array: jax.Array, + *, + name: str, + bound: jax.Array, +) -> None: + if isinstance(array, jax.core.Tracer): + return + concrete = np.asarray(array) + if not np.all(np.isfinite(concrete)): + raise ValueError(f"{name} pulse must contain only finite values") + if np.any(np.abs(concrete) > np.asarray(bound)): + raise ValueError(f"{name} pulse exceeds its hard amplitude bounds") + + +@dataclass(frozen=True) +class PulseSpace: + control_count: int + segments: int + amplitude_scales: tuple[float, ...] + bound: float + + def __post_init__(self) -> None: + control_count = _positive_integer("control_count", self.control_count) + segments = _positive_integer("segments", self.segments) + try: + raw_scales = tuple(self.amplitude_scales) + except TypeError: + raise ValueError("amplitude_scales must be a sequence") from None + if len(raw_scales) != control_count: + raise ValueError("each control must have an amplitude scale") + scales = tuple( + _positive_finite("amplitude scale", scale) for scale in raw_scales + ) + bound = _positive_finite("bound", self.bound) + if bound != 1.0: + raise ValueError("normalized pulse bound must be exactly 1.0") + + object.__setattr__(self, "control_count", control_count) + object.__setattr__(self, "segments", segments) + object.__setattr__(self, "amplitude_scales", scales) + object.__setattr__(self, "bound", bound) + + @classmethod + def from_system(cls, system: ControlSystem, segments: int) -> PulseSpace: + if not isinstance(system, ControlSystem): + raise ValueError("system must be a ControlSystem") + return cls( + control_count=len(system.controls), + segments=segments, + amplitude_scales=system.amplitude_scales, + bound=1.0, + ) + + @property + def parameter_count(self) -> int: + return self.control_count * self.segments + + def to_physical(self, normalized: object) -> jax.Array: + array = jnp.asarray(normalized) + if jnp.iscomplexobj(array): + raise ValueError("normalized pulse must be real") + if array.shape != (self.parameter_count,): + raise ValueError( + f"normalized pulse must have shape ({self.parameter_count},)" + ) + array = jnp.asarray(array, dtype=jnp.float64) + normalized_bound = jnp.float64(1.0) + _raise_for_invalid_eager_values( + array, + name="normalized", + bound=normalized_bound, + ) + scales = jnp.asarray(self.amplitude_scales, dtype=jnp.float64)[:, None] + valid = jnp.all(jnp.isfinite(array)) & jnp.all(jnp.abs(array) <= normalized_bound) + physical = array.reshape(self.control_count, self.segments) * scales + return jnp.where(valid, physical, jnp.full_like(physical, jnp.nan)) + + def to_normalized(self, physical: object) -> jax.Array: + array = jnp.asarray(physical) + if jnp.iscomplexobj(array): + raise ValueError("physical pulse must be real") + expected_shape = (self.control_count, self.segments) + if array.shape != expected_shape: + raise ValueError(f"physical pulse must have shape {expected_shape}") + array = jnp.asarray(array, dtype=jnp.float64) + scales = jnp.asarray(self.amplitude_scales, dtype=jnp.float64)[:, None] + _raise_for_invalid_eager_values(array, name="physical", bound=scales) + valid = jnp.all(jnp.isfinite(array)) & jnp.all(jnp.abs(array) <= scales) + normalized = (array / scales).reshape(self.parameter_count) + return jnp.where(valid, normalized, jnp.full_like(normalized, jnp.nan)) diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/src/qcontrol/systems.py b/tracks/qcs/solutions/frustration-free/challenge-113/src/qcontrol/systems.py new file mode 100644 index 000000000..9aa72464a --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/src/qcontrol/systems.py @@ -0,0 +1,351 @@ +from __future__ import annotations + +from dataclasses import dataclass +import math +from numbers import Integral, Real + +import numpy as np +from numpy.typing import NDArray + +from qcontrol.config import SystemConfig + + +ComplexMatrix = NDArray[np.complex128] + + +def _immutable_array( + value: object, + dtype: np.dtype[np.complex128] | np.dtype[np.float64], +) -> NDArray[np.complex128] | NDArray[np.float64]: + copied = np.array(value, dtype=dtype, copy=True, order="C") + immutable = np.frombuffer(copied.tobytes(), dtype=dtype).reshape(copied.shape) + immutable.setflags(write=False) + return immutable + + +def _immutable_complex_matrix(value: object) -> ComplexMatrix: + return np.asarray(_immutable_array(value, np.dtype(np.complex128))) + + +def _immutable_real_vector(value: object) -> NDArray[np.float64]: + return np.asarray(_immutable_array(value, np.dtype(np.float64))) + + +@dataclass(frozen=True, eq=False) +class _PerturbationDescriptor: + drift_direction: ComplexMatrix + control_gain_deltas: NDArray[np.float64] + unmodeled_direction: ComplexMatrix + gap: float + seed: int + + def __post_init__(self) -> None: + object.__setattr__( + self, + "drift_direction", + _immutable_complex_matrix(self.drift_direction), + ) + object.__setattr__( + self, + "control_gain_deltas", + _immutable_real_vector(self.control_gain_deltas), + ) + object.__setattr__( + self, + "unmodeled_direction", + _immutable_complex_matrix(self.unmodeled_direction), + ) + object.__setattr__(self, "gap", float(self.gap)) + object.__setattr__(self, "seed", int(self.seed)) + + +@dataclass(frozen=True, eq=False) +class ControlSystem: + drift: ComplexMatrix + controls: tuple[ComplexMatrix, ...] + target: ComplexMatrix + amplitude_scales: tuple[float, ...] + name: str + duration: float + _perturbation: _PerturbationDescriptor | None = None + + def __post_init__(self) -> None: + object.__setattr__(self, "drift", _immutable_complex_matrix(self.drift)) + object.__setattr__( + self, + "controls", + tuple(_immutable_complex_matrix(control) for control in self.controls), + ) + object.__setattr__(self, "target", _immutable_complex_matrix(self.target)) + object.__setattr__( + self, + "amplitude_scales", + tuple(float(scale) for scale in self.amplitude_scales), + ) + if ( + isinstance(self.duration, (bool, np.bool_)) + or not isinstance(self.duration, Real) + or not math.isfinite(float(self.duration)) + or self.duration <= 0.0 + ): + raise ValueError("duration must be a positive finite number") + object.__setattr__(self, "duration", float(self.duration)) + if self._perturbation is not None: + descriptor = self._perturbation + object.__setattr__( + self, + "_perturbation", + _PerturbationDescriptor( + descriptor.drift_direction, + descriptor.control_gain_deltas, + descriptor.unmodeled_direction, + descriptor.gap, + descriptor.seed, + ), + ) + + @property + def dimension(self) -> int: + return self.drift.shape[0] + + +def _normalized_paulis() -> tuple[ComplexMatrix, ...]: + scale = np.float64(1.0 / np.sqrt(2.0)) + identity = scale * np.eye(2, dtype=np.complex128) + x = scale * np.array([[0.0, 1.0], [1.0, 0.0]], dtype=np.complex128) + y = scale * np.array([[0.0, -1.0j], [1.0j, 0.0]], dtype=np.complex128) + z = scale * np.array([[1.0, 0.0], [0.0, -1.0]], dtype=np.complex128) + return identity, x, y, z + + +def _validate_system(system: ControlSystem) -> None: + dimension = system.dimension + expected_shape = (dimension, dimension) + if dimension < 2 or system.drift.shape != expected_shape: + raise ValueError("drift must be a square matrix of dimension at least two") + if not system.controls: + raise ValueError("at least one control Hamiltonian is required") + if len(system.controls) != len(system.amplitude_scales): + raise ValueError("each control must have an amplitude scale") + + for matrix in (system.drift, *system.controls): + if matrix.shape != expected_shape: + raise ValueError("Hamiltonians must have matching square shapes") + if not np.all(np.isfinite(matrix)): + raise ValueError("Hamiltonians must contain only finite values") + if not np.allclose(matrix, matrix.conj().T, rtol=0.0, atol=1e-12): + raise ValueError("Hamiltonians must be Hermitian") + + if system.target.shape != expected_shape: + raise ValueError("target must match the Hamiltonian dimension") + identity = np.eye(dimension, dtype=np.complex128) + if not np.allclose(system.target.conj().T @ system.target, identity, rtol=0.0, atol=1e-12): + raise ValueError("target must be unitary") + + +def make_system(config: SystemConfig) -> ControlSystem: + identity, x, y, z = _normalized_paulis() + amplitude = float(config.amplitude_bound) + + if config.name == "one_qubit": + drift = np.asarray(0.37 * z, dtype=np.complex128) + controls = (x.copy(), y.copy()) + target = np.asarray( + [[1.0, 1.0], [1.0, -1.0]], + dtype=np.complex128, + ) / np.sqrt(2.0) + elif config.name == "two_qubit": + zi = np.kron(z, identity) + iz = np.kron(identity, z) + zz = np.kron(z, z) + drift = np.asarray(0.31 * zi + 0.47 * iz + 0.23 * zz, dtype=np.complex128) + controls = ( + np.asarray(np.kron(x, identity), dtype=np.complex128), + np.asarray(np.kron(y, identity), dtype=np.complex128), + np.asarray(np.kron(identity, x), dtype=np.complex128), + np.asarray(np.kron(identity, y), dtype=np.complex128), + ) + target = np.array( + [ + [1.0, 0.0, 0.0, 0.0], + [0.0, 1.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 1.0], + [0.0, 0.0, 1.0, 0.0], + ], + dtype=np.complex128, + ) + else: + raise ValueError("system name must be 'one_qubit' or 'two_qubit'") + + system = ControlSystem( + drift=drift, + controls=controls, + target=target, + amplitude_scales=(amplitude,) * len(controls), + name=config.name, + duration=config.effective_duration, + ) + _validate_system(system) + return system + + +def _traceless(matrix: ComplexMatrix) -> ComplexMatrix: + dimension = matrix.shape[0] + trace_part = np.trace(matrix) / np.float64(dimension) + return np.asarray( + matrix - trace_part * np.eye(dimension, dtype=np.complex128), + dtype=np.complex128, + ) + + +def _skew_vector(matrix: ComplexMatrix) -> NDArray[np.float64]: + traceless = _traceless(matrix) + skew = np.asarray(0.5 * (traceless - traceless.conj().T), dtype=np.complex128) + return np.concatenate((skew.real.ravel(), skew.imag.ravel())).astype(np.float64) + + +def _vector_to_matrix(vector: NDArray[np.float64], dimension: int) -> ComplexMatrix: + element_count = dimension * dimension + matrix = vector[:element_count].reshape(dimension, dimension) + 1.0j * vector[ + element_count: + ].reshape(dimension, dimension) + return np.asarray(matrix, dtype=np.complex128) + + +def _orthogonal_residual( + vector: NDArray[np.float64], + basis: list[NDArray[np.float64]], +) -> NDArray[np.float64]: + residual = vector.copy() + for _ in range(2): + for basis_vector in basis: + residual -= np.dot(basis_vector, residual) * basis_vector + return residual + + +def lie_algebra_dimension(system: ControlSystem, tolerance: float = 1e-10) -> int: + if isinstance(tolerance, (bool, np.bool_)) or not isinstance(tolerance, Real): + raise ValueError("tolerance must be a positive finite number") + tolerance = float(tolerance) + if not math.isfinite(tolerance) or tolerance <= 0.0: + raise ValueError("tolerance must be a positive finite number") + _validate_system(system) + + dimension = system.dimension + maximum_dimension = dimension**2 - 1 + basis_vectors: list[NDArray[np.float64]] = [] + basis_matrices: list[ComplexMatrix] = [] + + def add_generator(matrix: ComplexMatrix) -> bool: + vector = _skew_vector(matrix) + residual = _orthogonal_residual(vector, basis_vectors) + norm = float(np.linalg.norm(residual)) + if norm <= tolerance: + return False + normalized = residual / norm + basis_vectors.append(normalized) + basis_matrices.append(_vector_to_matrix(normalized, dimension)) + return True + + for hamiltonian in (system.drift, *system.controls): + add_generator(np.asarray(-1.0j * hamiltonian, dtype=np.complex128)) + if len(basis_vectors) == maximum_dimension: + return maximum_dimension + + while len(basis_vectors) < maximum_dimension: + added = False + current_count = len(basis_matrices) + for left_index in range(current_count): + for right_index in range(left_index + 1, current_count): + left = basis_matrices[left_index] + right = basis_matrices[right_index] + commutator = left @ right - right @ left + if add_generator(np.asarray(commutator, dtype=np.complex128)): + added = True + break + if added: + break + if not added: + break + + return len(basis_vectors) + + +def _random_traceless_hermitian( + rng: np.random.Generator, + dimension: int, +) -> ComplexMatrix: + raw = rng.standard_normal((dimension, dimension)) + 1.0j * rng.standard_normal( + (dimension, dimension) + ) + hermitian = np.asarray(0.5 * (raw + raw.conj().T), dtype=np.complex128) + traceless = _traceless(hermitian) + norm = float(np.linalg.norm(traceless, "fro")) + if norm == 0.0: + raise RuntimeError("failed to generate a nonzero perturbation") + return np.asarray(traceless / norm, dtype=np.complex128) + + +def perturb_system(system: ControlSystem, gap: float, seed: int) -> ControlSystem: + if isinstance(gap, (bool, np.bool_)) or not isinstance(gap, Real): + raise ValueError("gap must be a finite nonnegative number") + gap = float(gap) + if not math.isfinite(gap) or gap < 0.0: + raise ValueError("gap must be a finite nonnegative number") + if isinstance(seed, (bool, np.bool_)) or not isinstance(seed, Integral) or seed < 0: + raise ValueError("seed must be a nonnegative integer") + seed = int(seed) + _validate_system(system) + + if gap == 0.0: + return ControlSystem( + drift=system.drift.copy(), + controls=tuple(control.copy() for control in system.controls), + target=system.target.copy(), + amplitude_scales=system.amplitude_scales, + name=system.name, + duration=system.duration, + _perturbation=None, + ) + + model_drift_norm = float(np.linalg.norm(system.drift, "fro")) + if model_drift_norm == 0.0: + raise ValueError("positive gap requires a nonzero drift Frobenius norm") + + rng = np.random.default_rng(seed) + drift_direction = _random_traceless_hermitian(rng, system.dimension) + unmodeled_direction = _random_traceless_hermitian(rng, system.dimension) + aggregate = np.asarray(drift_direction + unmodeled_direction, dtype=np.complex128) + aggregate /= np.linalg.norm(aggregate, "fro") + perturbation_norm = gap * model_drift_norm + perturbed_drift = np.asarray( + system.drift + perturbation_norm * aggregate, + dtype=np.complex128, + ) + + control_gain_deltas = np.asarray( + gap * rng.standard_normal(len(system.controls)), + dtype=np.float64, + ) + perturbed_controls = tuple( + np.asarray((1.0 + delta) * control, dtype=np.complex128) + for delta, control in zip(control_gain_deltas, system.controls, strict=True) + ) + descriptor = _PerturbationDescriptor( + drift_direction=drift_direction, + control_gain_deltas=control_gain_deltas, + unmodeled_direction=unmodeled_direction, + gap=gap, + seed=seed, + ) + truth = ControlSystem( + drift=perturbed_drift, + controls=perturbed_controls, + target=system.target.copy(), + amplitude_scales=system.amplitude_scales, + name=system.name, + duration=system.duration, + _perturbation=descriptor, + ) + _validate_system(truth) + return truth diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/tests/test_analysis.py b/tracks/qcs/solutions/frustration-free/challenge-113/tests/test_analysis.py new file mode 100644 index 000000000..7fed1e04a --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/tests/test_analysis.py @@ -0,0 +1,907 @@ +from __future__ import annotations + +import json + +import numpy as np +import pytest + +import qcontrol.analysis as analysis_module +from qcontrol.analysis import ( + AnalysisError, + Summary, + aggregate_run, + analyze_trials, + first_certified_query, + pair_trials, + paired_bootstrap_ci, + success_probability, +) +from qcontrol.artifacts import ArtifactStore, canonical_json_bytes +from qcontrol.config import DeviceConfig, ExperimentConfig, SearchConfig, SystemConfig +from qcontrol.experiments import TrialResult, generate_paired_trials, run_sweep + + +def _config( + method: str, + *, + seed: int, + gap: float = 0.02, + perturbation_seed: int = 7, + shots: int | None = 1_000, + kind: str = "development", + dimension: int = 2, + system: str = "one_qubit", + segments: int | None = None, +) -> ExperimentConfig: + segment_count = segments if segments is not None else ( + 3 if system == "one_qubit" else 10 + ) + return ExperimentConfig( + run_kind=kind, + system=SystemConfig(system, segment_count, 4.0), + device=DeviceConfig(gap, shots, perturbation_seed), + search=SearchConfig( + method, + dimension, + 200 if kind == "development" else 2_000, + ), + trial_seed=seed, + ) + + +def _observation( + attempt_index: int, + query: int, + estimate: float, + *, + validation: bool, + shots: int, +) -> dict[str, object]: + return { + "attempt_index": attempt_index, + "estimate": estimate, + "observation_seed": attempt_index, + "optimizer_query_index": query, + "seed_digest": f"{attempt_index:064x}", + "shots": shots, + "validation": validation, + } + + +def _trial( + config: ExperimentConfig, + *, + certified_query: int | None, + provisional_query: int | None = None, + exact_values: tuple[float, ...] | None = None, + attained_bound: float = 0.5, + principal_angles: tuple[float, ...] | None = None, + model_ranks: tuple[int, int, int] = (1, 1, 1), + truth_ranks: tuple[int, int, int] = (1, 1, 1), + signed_gaps: tuple[float, ...] | None = None, +) -> TrialResult: + spec = generate_paired_trials([config])[0] + evaluations = certified_query if certified_query is not None else config.search.budget + optimizer_shots = 0 if config.device.shots is None else config.device.shots + optimizer = [ + _observation( + index, + index, + 0.5 + index / 10_000, + validation=False, + shots=optimizer_shots, + ) + for index in range(1, evaluations + 1) + ] + attempts = [ + { + "attempt_index": item["attempt_index"], + "charged_shots": item["shots"], + "error_category": None, + "estimate": item["estimate"], + "observation_seed": item["observation_seed"], + "optimizer_query_index": item["optimizer_query_index"], + "requested_shots": item["shots"], + "seed_digest": item["seed_digest"], + "status": "succeeded", + "validation": False, + } + for item in optimizer + ] + validation_attempts: list[dict[str, object]] = [] + provisional_crossings: list[int] = [] + validation_result = None + next_attempt = evaluations + if provisional_query is not None: + next_attempt += 1 + provisional = _observation( + next_attempt, + provisional_query, + 0.99, + validation=True, + shots=100_000, + ) + attempts.append( + { + "attempt_index": next_attempt, + "charged_shots": 100_000, + "error_category": None, + "estimate": provisional["estimate"], + "observation_seed": provisional["observation_seed"], + "optimizer_query_index": provisional_query, + "requested_shots": 100_000, + "seed_digest": provisional["seed_digest"], + "status": "succeeded", + "validation": True, + } + ) + validation_attempts.append( + { + "best_observation": optimizer[provisional_query - 1], + "certified": False, + "device_attempt_index": next_attempt, + "failure_category": None, + "optimizer_query_index": provisional_query, + "pulse": [0.0] * config.system.parameter_count, + "status": "rejected", + "validation_observation": provisional, + } + ) + provisional_crossings.append(provisional_query) + validation_result = provisional + if certified_query is not None: + next_attempt += 1 + certified = _observation( + next_attempt, + certified_query, + 1.0, + validation=True, + shots=100_000, + ) + attempts.append( + { + "attempt_index": next_attempt, + "charged_shots": 100_000, + "error_category": None, + "estimate": certified["estimate"], + "observation_seed": certified["observation_seed"], + "optimizer_query_index": certified_query, + "requested_shots": 100_000, + "seed_digest": certified["seed_digest"], + "status": "succeeded", + "validation": True, + } + ) + validation_attempts.append( + { + "best_observation": optimizer[certified_query - 1], + "certified": True, + "device_attempt_index": next_attempt, + "failure_category": None, + "optimizer_query_index": certified_query, + "pulse": [0.0] * config.system.parameter_count, + "status": "certified", + "validation_observation": certified, + } + ) + provisional_crossings.append(certified_query) + validation_result = certified + validation_shots = 100_000 * len(validation_attempts) + ledger = { + "optimizer_queries": evaluations, + "optimizer_shots": evaluations * optimizer_shots, + "validation_queries": len(validation_attempts), + "validation_shots": validation_shots, + "total_queries": evaluations + len(validation_attempts), + "total_shots": evaluations * optimizer_shots + validation_shots, + } + result_payload = { + "best_observation": optimizer[-1], + "best_pulse": [0.0] * config.system.parameter_count, + "budget": config.search.budget, + "budget_exhausted": certified_query is None, + "certified": certified_query is not None, + "evaluations": evaluations, + "first_certified_query": certified_query, + "observations": optimizer, + "provisional_crossings": provisional_crossings, + "schema_version": 3 if config.run_kind == "production" else 2, + "search": { + "basis_sha256": "1" * 64, + "dimension": config.search.dimension, + "method": config.search.method, + "origin_sha256": "2" * 64, + "source_basis_sha256": "5" * 64, + }, + "stop_reason": "certified" if certified_query is not None else "budget", + "validation_attempts": validation_attempts, + "validation_result": validation_result, + } + if config.run_kind == "production": + exact = ( + exact_values + if exact_values is not None + else (0.5,) * evaluations + ) + if len(exact) != evaluations: + raise ValueError("test exact trajectory must align with evaluations") + angles = principal_angles or (0.0,) * config.search.dimension + gaps = signed_gaps or (0.0,) * config.search.dimension + best_audited = min(exact) + final_attained = min(attained_bound, best_audited, 0.5) + attained_source = ( + "restricted_solver" + if final_attained == attained_bound + else "audited_candidate" + ) + result_payload["derived_metrics"] = { + "exact_infidelity": { + "best_successful_audited_infidelity": best_audited, + "cumulative_best_by_optimizer_query": list(exact), + "initial_infidelity": 0.5, + }, + "geometry": { + "model_effective_ranks": list(model_ranks), + "model_top_subspace_sha256": "3" * 64, + "principal_angles_radians": list(angles), + "rank_thresholds": [1e-6, 1e-8, 1e-10], + "signed_leading_eigenvalue_gaps": list(gaps), + "truth_effective_ranks": list(truth_ranks), + "truth_top_subspace_sha256": "4" * 64, + }, + "restricted_noiseless_optimization": { + "attained_infidelity_upper_bound": final_attained, + "attained_infidelity_source": attained_source, + "best_successful_audited_exact_infidelity": best_audited, + "cached_solver_attained_infidelity_upper_bound": attained_bound, + "cached_solver_starting_infidelity_upper_bound": 0.5, + "certified": True, + "consistency_tolerance": 1e-10, + "gradient_tolerance": 1e-9, + "max_evaluations": 1_000, + "max_iterations": 100, + "nfev": 1, + "nit": 0, + "solver": "L-BFGS-B", + "solver_message_code": "convergence", + "solver_output_finite": True, + "solver_status": 0, + "solver_success": True, + "initial_exact_infidelity": 0.5, + "termination": "converged", + }, + } + return TrialResult( + trial_id=spec.trial_id, + device_id=spec.device_id, + observation_stream_id=spec.observation_stream_id, + config=config.canonical_dict(), + result=result_payload, + ledger=ledger, + attempts=attempts, + ) + + +def test_first_certified_query_ignores_provisional_crossing() -> None: + trial = _trial( + _config("model_hessian", seed=1), + provisional_query=4, + certified_query=9, + ) + + assert first_certified_query(trial) == 9 + + +def test_success_probability_keeps_budget_exhaustion_in_denominator() -> None: + trials = [ + _trial(_config("model_hessian", seed=1), certified_query=7), + _trial(_config("model_hessian", seed=2), certified_query=None), + _trial(_config("model_hessian", seed=3), certified_query=12), + ] + + estimate = success_probability(trials) + + assert estimate.value == pytest.approx(2 / 3) + assert estimate.numerator == 2 + assert estimate.denominator == 3 + + +def test_hand_computable_censoring_shots_and_conditional_queries() -> None: + trials = [ + _trial(_config("model_hessian", seed=1), certified_query=4), + _trial(_config("model_hessian", seed=2), certified_query=None), + _trial(_config("model_hessian", seed=3), certified_query=9), + ] + + summary = analyze_trials(trials) + method = _method(summary, 0, "model_hessian") + + assert method.trial_count == 3 + assert method.success_probability.value == pytest.approx(2 / 3) + assert method.conditional_first_certified_queries == (4, 9) + assert method.censored_first_certified_queries == (4, 9, 200) + assert method.total_shots == 413_000 + assert method.exact_infidelity_trajectory is None + assert len(method.median_best_observed_infidelity_trajectory) == 200 + + +def test_pairing_requires_exact_device_orientation_gap_shots_and_seed() -> None: + model = _trial(_config("model_hessian", seed=1), certified_query=4) + random = _trial(_config("random", seed=1), certified_query=6) + pairs = pair_trials([model, random]) + assert [(left.trial_id, right.trial_id) for left, right in pairs["random"]] == [ + (model.trial_id, random.trial_id) + ] + + mismatched = _trial( + _config("random", seed=1, perturbation_seed=8), + certified_query=6, + ) + with pytest.raises(AnalysisError, match="pair"): + pair_trials([model, mismatched]) + + +def test_seeded_paired_bootstrap_is_deterministic_and_hand_computable() -> None: + differences = (1.0, 1.0, 1.0) + + first = paired_bootstrap_ci(differences, seed=17, samples=200) + second = paired_bootstrap_ci(differences, seed=17, samples=200) + + assert first == second + assert first.low == first.high == first.estimate == 1.0 + + +def test_strict_noncoercive_and_finite_validation() -> None: + trial = _trial(_config("model_hessian", seed=1), certified_query=4) + malformed = trial.canonical_dict() + malformed["result"]["certified"] = 1 + with pytest.raises(AnalysisError, match="invalid trial"): + analyze_trials([malformed]) + with pytest.raises(AnalysisError, match="finite"): + paired_bootstrap_ci([float("nan")], seed=1) + + +def test_production_aggregation_rejects_missing_and_malformed_coverage( + tmp_path, +) -> None: + configs = [ + _config("model_hessian", seed=1, kind="production"), + _config("random", seed=1, kind="production"), + ] + specs = generate_paired_trials(configs) + store = ArtifactStore(tmp_path) + run_sweep( + specs, + store, + executor=lambda spec: _trial(spec.config, certified_query=4), + stop_after=1, + ) + + with pytest.raises(AnalysisError, match="coverage"): + aggregate_run(store) + + pending = next(spec for spec in specs if spec.trial_id not in store.completed_trial_ids()) + store.publish_trial(pending.trial_id, {"schema_version": 2}) + with pytest.raises(AnalysisError, match="invalid|coverage"): + aggregate_run(store) + + +def test_aggregate_run_never_drops_failures(tmp_path) -> None: + configs = [ + _config("model_hessian", seed=1), + _config("model_hessian", seed=2), + _config("model_hessian", seed=3), + ] + results = { + config.trial_seed: _trial( + config, + certified_query={1: 4, 2: None, 3: 9}[config.trial_seed], + ) + for config in configs + } + specs = generate_paired_trials(configs) + store = ArtifactStore(tmp_path) + run_sweep(specs, store, executor=lambda spec: results[spec.config.trial_seed]) + + summary = aggregate_run(store) + method = _method(summary, 0, "model_hessian") + + assert method.trial_count == 3 + assert method.failure_count == 1 + assert method.success_probability.denominator == 3 + + +def _method(summary, stratum_index: int, method: str): + return next( + item + for item in summary.strata[stratum_index].methods + if item.method == method + ) + + +def test_analysis_separates_system_dimension_gap_and_shot_strata() -> None: + trials = [ + _trial(_config("model_hessian", seed=1), certified_query=4), + _trial( + _config("model_hessian", seed=1, dimension=1), + certified_query=4, + ), + _trial( + _config("model_hessian", seed=1, gap=0.05), + certified_query=4, + ), + _trial( + _config("model_hessian", seed=1, shots=None), + certified_query=4, + ), + _trial( + _config("model_hessian", seed=1, system="two_qubit"), + certified_query=4, + ), + ] + + summary = analyze_trials(trials, bootstrap_samples=20) + + assert len(summary.strata) == 5 + identities = { + ( + item.key.hilbert_dimension, + item.key.search_dimension, + item.key.gap, + item.key.shots, + ) + for item in summary.strata + } + assert identities == { + (2, 1, 0.02, 1_000), + (2, 2, 0.02, 1_000), + (2, 2, 0.05, 1_000), + (2, 2, 0.02, None), + (4, 2, 0.02, 1_000), + } + + +def test_full_comparator_is_reused_once_per_k_stratum() -> None: + model_k1 = _trial( + _config("model_hessian", seed=1, dimension=1), + certified_query=4, + ) + model_k2 = _trial( + _config("model_hessian", seed=1, dimension=2), + certified_query=4, + ) + full = _trial( + _config("full", seed=1, dimension=3), + certified_query=6, + ) + + summary = analyze_trials( + [model_k1, model_k2, full], + bootstrap_seed=9, + bootstrap_samples=30, + ) + + assert [item.key.search_dimension for item in summary.strata] == [1, 2] + for stratum in summary.strata: + full_method = next(item for item in stratum.methods if item.method == "full") + effect = next( + item for item in stratum.paired_differences if item.baseline == "full" + ) + assert full_method.trial_count == 1 + assert effect.pair_count == effect.cluster_count == 1 + + +def test_method_success_intervals_and_paired_effect_keep_failures() -> None: + records = [] + for seed, model_query, random_query in ( + (1, 4, 6), + (2, None, None), + (3, 9, None), + ): + records.extend( + ( + _trial( + _config("model_hessian", seed=seed), + certified_query=model_query, + ), + _trial( + _config("random", seed=seed), + certified_query=random_query, + ), + ) + ) + + summary = analyze_trials(records, bootstrap_seed=4, bootstrap_samples=100) + model = _method(summary, 0, "model_hessian") + random = _method(summary, 0, "random") + effect = next( + item for item in summary.strata[0].paired_differences + if item.baseline == "random" + ) + + assert model.success_probability.numerator == 2 + assert model.success_probability.denominator == 3 + assert model.success_probability.low < 2 / 3 < model.success_probability.high + assert random.success_probability.numerator == 1 + assert random.success_probability.denominator == 3 + assert effect.success_probability_difference.estimate == pytest.approx(1 / 3) + assert effect.pair_count == effect.cluster_count == 3 + + +def test_schema_v3_exact_bands_and_geometry_are_aggregated() -> None: + first = _trial( + _config("model_hessian", seed=1, kind="production"), + certified_query=2, + exact_values=(0.5, 0.3), + attained_bound=0.2, + principal_angles=(0.1, 0.2), + model_ranks=(1, 2, 2), + truth_ranks=(2, 2, 3), + signed_gaps=(0.4, -0.2), + ) + second = _trial( + _config("model_hessian", seed=2, kind="production"), + certified_query=2, + exact_values=(0.4, 0.2), + attained_bound=0.4, + principal_angles=(0.3, 0.4), + model_ranks=(2, 2, 3), + truth_ranks=(2, 3, 3), + signed_gaps=(0.2, 0.0), + ) + + summary = analyze_trials( + [first, second], + bootstrap_seed=7, + bootstrap_samples=40, + ) + method = _method(summary, 0, "model_hessian") + + assert method.metric_availability.state == "available" + assert method.exact_infidelity_trajectory.median[:2] == pytest.approx((0.45, 0.25)) + assert len(method.exact_infidelity_trajectory.low) == 2_000 + assert method.median_attained_infidelity_upper_bound == pytest.approx(0.2) + assert method.median_principal_angles == pytest.approx((0.2, 0.3)) + assert method.median_model_effective_ranks == pytest.approx((1.5, 2.0, 2.5)) + assert method.median_truth_effective_ranks == pytest.approx((2.0, 2.5, 3.0)) + assert method.median_signed_eigenvalue_gaps == pytest.approx((0.3, -0.1)) + + +def test_development_metrics_have_explicit_unavailability() -> None: + summary = analyze_trials( + [_trial(_config("model_hessian", seed=1), certified_query=4)], + bootstrap_samples=20, + ) + method = _method(summary, 0, "model_hessian") + + assert method.metric_availability.state == "unavailable" + assert method.metric_availability.reason == "schema_v3_metrics_not_available" + assert method.exact_infidelity_trajectory is None + assert method.median_principal_angles is None + + +def test_missing_required_production_metrics_are_rejected() -> None: + trial = _trial( + _config("model_hessian", seed=1, kind="production"), + certified_query=2, + ).canonical_dict() + del trial["result"]["derived_metrics"] + + with pytest.raises(AnalysisError, match="invalid trial|derived"): + analyze_trials([trial], bootstrap_samples=20) + + +def test_summary_canonical_round_trip_and_strict_validation() -> None: + summary = analyze_trials( + [ + _trial(_config("model_hessian", seed=1), certified_query=4), + _trial( + _config("model_hessian", seed=1, dimension=1), + certified_query=4, + ), + ], + bootstrap_samples=20, + ) + payload = summary.canonical_dict() + + assert payload["schema_version"] == 1 + assert Summary.from_canonical_dict( + json.loads(json.dumps(payload, allow_nan=False)) + ) == summary + malformed = json.loads(json.dumps(payload, allow_nan=False)) + malformed["strata"][0]["methods"][0]["trial_count"] = True + with pytest.raises(AnalysisError, match="trial_count|integer"): + Summary.from_canonical_dict(malformed) + malformed = json.loads(json.dumps(payload, allow_nan=False)) + malformed["strata"].reverse() + with pytest.raises(AnalysisError, match="sorted|canonical"): + Summary.from_canonical_dict(malformed) + + +def test_chunked_bootstrap_is_chunk_size_independent() -> None: + differences = (1.0, -1.0, 2.0, 0.5) + + one = paired_bootstrap_ci( + differences, + seed=17, + samples=200, + chunk_size=1, + ) + many = paired_bootstrap_ci( + differences, + seed=17, + samples=200, + chunk_size=37, + ) + + assert one == many + + +def test_production_scale_bootstrap_never_allocates_full_sample_matrix( + monkeypatch, +) -> None: + requested_shapes: list[tuple[int, int]] = [] + + class RecordingRng: + def integers(self, low, high, *, size): + requested_shapes.append(size) + return np.zeros(size, dtype=np.int64) + + monkeypatch.setattr( + analysis_module.np.random, + "default_rng", + lambda seed: RecordingRng(), + ) + + paired_bootstrap_ci( + np.ones(9_500), + seed=1, + samples=257, + chunk_size=16, + ) + + assert requested_shapes + assert max(rows for rows, _ in requested_shapes) <= 16 + assert {columns for _, columns in requested_shapes} == {9_500} + + +@pytest.mark.parametrize( + ("system", "segments", "full_dimension", "target_dimension"), + ( + ("one_qubit", 12, 24, 3), + ("two_qubit", 20, 80, 4), + ), +) +def test_full_schema_v3_geometry_is_sliced_to_target_k_and_round_trips( + system, + segments, + full_dimension, + target_dimension, +) -> None: + model = _trial( + _config( + "model_hessian", + seed=1, + kind="production", + system=system, + segments=segments, + dimension=target_dimension, + ), + certified_query=2, + principal_angles=tuple( + 0.2 + 0.01 * index for index in range(target_dimension) + ), + signed_gaps=tuple(-0.02 * index for index in range(target_dimension)), + ) + target_angles = tuple( + 0.2 + 0.01 * index for index in range(target_dimension) + ) + full_angles = (0.0,) * full_dimension + full_gaps = tuple(-0.02 * index for index in range(full_dimension)) + full = _trial( + _config( + "full", + seed=1, + kind="production", + system=system, + segments=segments, + dimension=full_dimension, + ), + certified_query=2, + principal_angles=full_angles, + signed_gaps=full_gaps, + model_ranks=(7, 8, 9), + truth_ranks=(6, 7, 8), + ) + + summary = analyze_trials([model, full], bootstrap_samples=20) + full_method = _method(summary, 0, "full") + + assert full_method.median_principal_angles == target_angles + assert full_method.principal_angle_availability.state == "available" + assert full_method.median_signed_eigenvalue_gaps == full_gaps[:target_dimension] + assert full_method.median_model_effective_ranks == (7.0, 8.0, 9.0) + assert len( + full.result["derived_metrics"]["geometry"]["principal_angles_radians"] + ) == full_dimension + assert Summary.from_canonical_dict(summary.canonical_dict()) == summary + + +def test_conflicting_target_k_principal_angles_are_rejected() -> None: + records = [ + _trial( + _config("model_hessian", seed=1, kind="production"), + certified_query=2, + principal_angles=(0.2, 0.3), + ), + _trial( + _config("random", seed=1, kind="production"), + certified_query=2, + principal_angles=(0.2, 0.4), + ), + _trial( + _config("full", seed=1, kind="production", dimension=6), + certified_query=2, + principal_angles=(0.0,) * 6, + ), + ] + + with pytest.raises(AnalysisError, match="target-k geometry conflict"): + analyze_trials(records, bootstrap_samples=20) + + +def test_missing_target_k_principal_angles_are_rejected_in_production() -> None: + model = _trial( + _config("model_hessian", seed=1, kind="production"), + certified_query=2, + principal_angles=(0.2, 0.3), + ) + full = _trial( + _config( + "full", + seed=1, + kind="production", + dimension=6, + perturbation_seed=8, + ), + certified_query=2, + principal_angles=(0.0,) * 6, + ) + + with pytest.raises(AnalysisError, match="missing target-k principal angles"): + analyze_trials([model, full], bootstrap_samples=20) + + +def test_development_full_angles_have_explicit_unavailability_reason() -> None: + model = _trial( + _config("model_hessian", seed=1), + certified_query=2, + ) + full = _trial( + _config("full", seed=1, dimension=3), + certified_query=2, + ) + + summary = analyze_trials([model, full], bootstrap_samples=20) + full_method = _method(summary, 0, "full") + + assert full_method.principal_angle_availability.state == "unavailable" + assert ( + full_method.principal_angle_availability.reason + == "schema_v3_metrics_not_available" + ) + assert full_method.median_principal_angles is None + + +@pytest.mark.parametrize( + ("field", "value"), + ( + ("low", 0.0), + ("high", 0.1), + ("method", "jeffreys"), + ("confidence", 0.9), + ), +) +def test_summary_reader_recomputes_wilson_interval(field, value) -> None: + summary = analyze_trials( + [ + _trial(_config("model_hessian", seed=1), certified_query=4), + _trial(_config("model_hessian", seed=2), certified_query=None), + _trial(_config("model_hessian", seed=3), certified_query=9), + ], + bootstrap_samples=20, + ) + payload = summary.canonical_dict() + payload["strata"][0]["methods"][0]["success_probability"][field] = value + + with pytest.raises(AnalysisError, match="probability|Wilson|confidence"): + Summary.from_canonical_dict(payload) + + +@pytest.mark.parametrize("duplicate_kind", ("stratum", "method", "baseline")) +def test_summary_reader_rejects_duplicate_named_entries(duplicate_kind) -> None: + summary = analyze_trials( + [ + _trial(_config("model_hessian", seed=1), certified_query=4), + _trial(_config("random", seed=1), certified_query=6), + ], + bootstrap_samples=20, + ) + payload = summary.canonical_dict() + if duplicate_kind == "stratum": + payload["strata"].append(payload["strata"][0]) + elif duplicate_kind == "method": + payload["strata"][0]["methods"].append( + payload["strata"][0]["methods"][0] + ) + payload["strata"][0]["methods"].sort(key=lambda item: item["method"]) + else: + payload["strata"][0]["paired_differences"].append( + payload["strata"][0]["paired_differences"][0] + ) + + with pytest.raises(AnalysisError, match="duplicate"): + Summary.from_canonical_dict(payload) + + +def test_reordered_trials_have_identical_summary_bytes() -> None: + records = [ + _trial(_config("model_hessian", seed=1), certified_query=9), + _trial(_config("model_hessian", seed=2), certified_query=None), + _trial(_config("model_hessian", seed=3), certified_query=4), + ] + + forward = analyze_trials(records, bootstrap_seed=7, bootstrap_samples=40) + reverse = analyze_trials( + reversed(records), + bootstrap_seed=7, + bootstrap_samples=40, + ) + + assert canonical_json_bytes(forward.canonical_dict()) == canonical_json_bytes( + reverse.canonical_dict() + ) + method = _method(forward, 0, "model_hessian") + assert method.conditional_first_certified_queries == (4, 9) + assert method.censored_first_certified_queries == (4, 9, 200) + assert method.total_shots_by_trial == (104_000, 109_000, 200_000) + + +def test_public_pairing_handles_multiple_k_without_cross_pairing() -> None: + records = [] + for dimension in (1, 2): + records.extend( + ( + _trial( + _config("model_hessian", seed=1, dimension=dimension), + certified_query=4, + ), + _trial( + _config("random", seed=1, dimension=dimension), + certified_query=6, + ), + _trial( + _config("oracle", seed=1, dimension=dimension), + certified_query=5, + ), + ) + ) + records.append( + _trial(_config("full", seed=1, dimension=3), certified_query=7) + ) + + pairs = pair_trials(records) + + assert {name: len(items) for name, items in pairs.items()} == { + "full": 2, + "oracle": 2, + "random": 2, + } + for baseline in ("oracle", "random"): + assert [ + ( + reference.config["search"]["dimension"], + comparison.config["search"]["dimension"], + ) + for reference, comparison in pairs[baseline] + ] == [(1, 1), (2, 2)] diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/tests/test_artifacts.py b/tracks/qcs/solutions/frustration-free/challenge-113/tests/test_artifacts.py new file mode 100644 index 000000000..0dd611d2e --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/tests/test_artifacts.py @@ -0,0 +1,582 @@ +from __future__ import annotations + +from dataclasses import replace +import fcntl +import hashlib +import json +import multiprocessing +import os +from pathlib import Path +import threading +import time + +import pytest + +import qcontrol.artifacts as artifacts_module +from qcontrol.artifacts import ( + ArtifactConflict, + ArtifactDurabilityError, + ArtifactStore, + TrialClaimConflict, +) +from qcontrol.config import DeviceConfig, ExperimentConfig, SearchConfig, SystemConfig + + +@pytest.fixture +def config() -> ExperimentConfig: + return ExperimentConfig( + run_kind="development", + system=SystemConfig("one_qubit", 3, 4.0), + device=DeviceConfig(gap=0.02, shots=1_000, perturbation_seed=7), + search=SearchConfig("model_hessian", 2, 200), + trial_seed=11, + ) + + +def _claim_worker(root: str, queue: multiprocessing.Queue) -> None: + store = ArtifactStore(root, stale_lock_seconds=0.0) + try: + with store.claim_trial("trial-race"): + queue.put(("acquired", os.getpid())) + time.sleep(0.4) + except TrialClaimConflict: + queue.put(("conflict", os.getpid())) + + +def _initialize_worker( + root: str, + trial_seed: int, + queue: multiprocessing.Queue, +) -> None: + store = ArtifactStore(root) + plan = { + "run_kind": "development", + "schema_version": 1, + "trials": [{"seed": trial_seed}], + } + try: + store.initialize_run({"seed": trial_seed}, plan) + except ArtifactConflict: + queue.put(("conflict", trial_seed)) + else: + queue.put(("winner", trial_seed)) + + +def _crash_claim_worker(root: str, queue: multiprocessing.Queue) -> None: + store = ArtifactStore(root) + claim = store.claim_trial("trial-crash") + queue.put((claim.owner, claim.lock_path)) + queue.close() + queue.join_thread() + os._exit(0) + + +def test_failed_publish_preserves_previous_artifact(tmp_path, monkeypatch) -> None: + store = ArtifactStore(tmp_path) + store.publish_json("summary.json", {"version": 1}) + monkeypatch.setattr( + store, + "_replace", + lambda *_: (_ for _ in ()).throw(OSError("boom")), + ) + + with pytest.raises(OSError, match="boom"): + store.publish_json("summary.json", {"version": 2}) + + assert json.loads((tmp_path / "summary.json").read_text()) == {"version": 1} + assert not list(tmp_path.glob("*.tmp-*")) + + +def test_failed_post_replace_verification_restores_previous_artifact( + tmp_path, + monkeypatch, +) -> None: + store = ArtifactStore(tmp_path) + store.publish_json("summary.json", {"version": 1}) + monkeypatch.setattr(artifacts_module, "_file_sha256", lambda _: "0" * 64) + + with pytest.raises(ArtifactConflict, match="hash"): + store.publish_json("summary.json", {"version": 2}) + + assert json.loads((tmp_path / "summary.json").read_text()) == {"version": 1} + + +def test_publish_is_canonical_fsynced_and_hash_verified(tmp_path, monkeypatch) -> None: + fsync_calls: list[int] = [] + real_fsync = os.fsync + + def recording_fsync(fd: int) -> None: + fsync_calls.append(fd) + real_fsync(fd) + + monkeypatch.setattr(os, "fsync", recording_fsync) + store = ArtifactStore(tmp_path) + digest = store.publish_json("nested/value.json", {"z": 2, "a": 1}) + + content = (tmp_path / "nested/value.json").read_bytes() + assert content == b'{"a":1,"z":2}\n' + assert digest == hashlib.sha256(content).hexdigest() + assert len(fsync_calls) >= 2 + assert store.verify_file("nested/value.json", digest) + + +def test_immutable_artifact_rejects_different_bytes(tmp_path) -> None: + store = ArtifactStore(tmp_path) + first = store.publish_json("trials/trial-1.json", {"value": 1}, immutable=True) + + assert ( + store.publish_json("trials/trial-1.json", {"value": 1}, immutable=True) + == first + ) + with pytest.raises(ArtifactConflict, match="immutable"): + store.publish_json("trials/trial-1.json", {"value": 2}, immutable=True) + assert json.loads((tmp_path / "trials/trial-1.json").read_text()) == {"value": 1} + + +def test_resume_rejects_changed_config(tmp_path, config) -> None: + ArtifactStore.create(tmp_path, config) + + with pytest.raises(ArtifactConflict, match="provenance"): + ArtifactStore.resume( + tmp_path, + replace(config, trial_seed=config.trial_seed + 1), + ) + + +def test_resume_rejects_source_or_lock_change(tmp_path, config, monkeypatch) -> None: + store = ArtifactStore.create(tmp_path, config) + manifest = json.loads((tmp_path / "manifest.json").read_text()) + manifest["provenance"]["uv_lock_sha256"] = "0" * 64 + store.publish_json("manifest.json", manifest) + + with pytest.raises(ArtifactConflict, match="provenance"): + ArtifactStore.resume(tmp_path, config) + + +def test_manifest_contains_complete_public_provenance(tmp_path, config) -> None: + ArtifactStore.create(tmp_path, config) + provenance = json.loads((tmp_path / "manifest.json").read_text())["provenance"] + + assert provenance["config"] == config.canonical_dict() + assert set(provenance["source_hashes"]) + assert all(path.startswith("src/qcontrol/") for path in provenance["source_hashes"]) + assert set(provenance["versions"]) == { + "jax", + "jaxlib", + "numpy", + "python", + "scipy", + } + assert set(provenance["jax"]) == {"platform", "x64_enabled"} + assert set(provenance["git"]) == { + "dirty", + "revision", + "worktree_sha256", + } + assert len(provenance["uv_lock_sha256"]) == 64 + assert "truth" not in json.dumps(provenance).lower() + + +def test_live_claim_prevents_duplicate_trial_execution(tmp_path) -> None: + store = ArtifactStore(tmp_path) + claim = store.claim_trial("trial-1") + + with claim: + with pytest.raises(TrialClaimConflict, match="kernel lock"): + store.claim_trial("trial-1") + + with store.claim_trial("trial-1") as replacement: + assert replacement.owner["pid"] == os.getpid() + + +def test_stale_claim_requires_explicit_dead_owner_metadata(tmp_path) -> None: + store = ArtifactStore(tmp_path, stale_lock_seconds=0.0) + lock_path = tmp_path / "claims" / "trial-1.owner.json" + lock_path.parent.mkdir() + owner = store.owner_identity(lease_seconds=-1.0) + owner["pid"] = 999_999_999 + owner["process_start_id"] = "dead-process-start" + prior_nonce = owner["nonce"] + lock_path.write_bytes(artifacts_module.canonical_json_bytes(owner)) + + with store.claim_trial("trial-1") as claim: + assert claim.owner["nonce"] != prior_nonce + + remote = store.owner_identity(lease_seconds=60.0) + remote["hostname"] = "remote-host" + lock_path.write_bytes(artifacts_module.canonical_json_bytes(remote)) + with pytest.raises(TrialClaimConflict, match="active"): + store.claim_trial("trial-1") + + +def test_completed_trial_is_verified_before_skip(tmp_path) -> None: + store = ArtifactStore(tmp_path) + store.publish_trial("trial-1", {"schema_version": 1, "trial_id": "trial-1"}) + (tmp_path / "trials" / "trial-1.json").write_text('{"tampered":true}\n') + + with pytest.raises(ArtifactConflict, match="hash"): + store.completed_trial_ids() + + +def test_two_reclaimers_have_exactly_one_winner(tmp_path) -> None: + store = ArtifactStore(tmp_path, stale_lock_seconds=0.0) + lock_path = tmp_path / "claims" / "trial-race.owner.json" + lock_path.parent.mkdir() + owner = store.owner_identity(lease_seconds=-1.0) + owner["pid"] = 999_999_999 + owner["process_start_id"] = "dead-process-start" + lock_path.write_bytes(artifacts_module.canonical_json_bytes(owner)) + + context = multiprocessing.get_context("spawn") + queue = context.Queue() + workers = [ + context.Process(target=_claim_worker, args=(str(tmp_path), queue)) + for _ in range(2) + ] + for worker in workers: + worker.start() + results = [queue.get(timeout=5) for _ in workers] + for worker in workers: + worker.join(timeout=5) + assert worker.exitcode == 0 + + assert sorted(kind for kind, _ in results) == ["acquired", "conflict"] + + +def test_pid_reuse_requires_matching_process_start_identity(tmp_path) -> None: + store = ArtifactStore(tmp_path, stale_lock_seconds=0.0) + lock_path = tmp_path / "claims" / "trial-1.owner.json" + lock_path.parent.mkdir() + reused = store.owner_identity(lease_seconds=-1.0) + reused["process_start_id"] = f"{reused['process_start_id']}-different" + lock_path.write_bytes(artifacts_module.canonical_json_bytes(reused)) + + with store.claim_trial("trial-1"): + pass + + active = store.owner_identity(lease_seconds=-1.0) + lock_path.write_bytes(artifacts_module.canonical_json_bytes(active)) + with pytest.raises(TrialClaimConflict, match="cannot prove stale"): + store.claim_trial("trial-1") + + +def test_foreign_host_uses_explicit_lease_expiry(tmp_path) -> None: + store = ArtifactStore(tmp_path, stale_lock_seconds=0.0) + lock_path = tmp_path / "claims" / "trial-1.owner.json" + lock_path.parent.mkdir() + foreign = store.owner_identity(lease_seconds=60.0) + foreign["hostname"] = "other-host" + foreign["pid"] = os.getpid() + lock_path.write_bytes(artifacts_module.canonical_json_bytes(foreign)) + with pytest.raises(TrialClaimConflict, match="active"): + store.claim_trial("trial-1") + + foreign["lease_expires_at"] = time.time() - 1.0 + lock_path.write_bytes(artifacts_module.canonical_json_bytes(foreign)) + with store.claim_trial("trial-1"): + pass + + +def test_held_trial_flock_blocks_expired_foreign_reclaim_then_allows_it( + tmp_path, +) -> None: + store = ArtifactStore(tmp_path, stale_lock_seconds=0.0) + claims = tmp_path / "claims" + claims.mkdir() + lock_path = claims / "trial-foreign.flock" + descriptor = os.open(lock_path, os.O_RDWR | os.O_CREAT, 0o600) + fcntl.flock(descriptor, fcntl.LOCK_EX | fcntl.LOCK_NB) + owner = store.owner_identity(lease_seconds=-1.0) + owner["hostname"] = "foreign-container" + (claims / "trial-foreign.owner.json").write_bytes( + artifacts_module.canonical_json_bytes(owner) + ) + + with pytest.raises(TrialClaimConflict, match="kernel lock"): + store.claim_trial("trial-foreign") + + fcntl.flock(descriptor, fcntl.LOCK_UN) + os.close(descriptor) + with store.claim_trial("trial-foreign"): + pass + + +def test_crashed_claim_releases_kernel_lock_and_can_be_reclaimed(tmp_path) -> None: + context = multiprocessing.get_context("spawn") + queue = context.Queue() + worker = context.Process( + target=_crash_claim_worker, + args=(str(tmp_path), queue), + ) + worker.start() + owner, lock_path = queue.get(timeout=10) + worker.join(timeout=10) + assert worker.exitcode == 0 + assert Path(lock_path).exists() + + store = ArtifactStore(tmp_path) + with store.claim_trial("trial-crash") as replacement: + assert replacement.owner["nonce"] != owner["nonce"] + + +def test_claim_release_closes_dedicated_descriptor(tmp_path) -> None: + store = ArtifactStore(tmp_path) + before = len(list(Path("/proc/self/fd").iterdir())) + + claim = store.claim_trial("trial-fd") + assert claim.lock_descriptor >= 0 + claim.release() + + with pytest.raises(OSError): + os.fstat(claim.lock_descriptor) + assert len(list(Path("/proc/self/fd").iterdir())) <= before + + +def test_concurrent_different_immutable_writers_have_one_winner(tmp_path) -> None: + results: list[str] = [] + barrier = threading.Barrier(2) + + def publish(value: int) -> None: + store = ArtifactStore(tmp_path) + barrier.wait() + try: + store.publish_json("manifest.json", {"value": value}, immutable=True) + except ArtifactConflict: + results.append("conflict") + else: + results.append("winner") + + threads = [threading.Thread(target=publish, args=(value,)) for value in (1, 2)] + for thread in threads: + thread.start() + for thread in threads: + thread.join(timeout=5) + + assert sorted(results) == ["conflict", "winner"] + assert json.loads((tmp_path / "manifest.json").read_text()) in ( + {"value": 1}, + {"value": 2}, + ) + + +def test_concurrent_initialization_has_self_consistent_winner(tmp_path) -> None: + context = multiprocessing.get_context("spawn") + queue = context.Queue() + workers = [ + context.Process( + target=_initialize_worker, + args=(str(tmp_path), seed, queue), + ) + for seed in (1, 2) + ] + for worker in workers: + worker.start() + results = [queue.get(timeout=10) for _ in workers] + for worker in workers: + worker.join(timeout=5) + assert worker.exitcode == 0 + + assert sorted(kind for kind, _ in results) == ["conflict", "winner"] + winner = next(seed for kind, seed in results if kind == "winner") + ready = json.loads((tmp_path / "ready.json").read_text()) + manifest = json.loads((tmp_path / "manifest.json").read_text()) + plan = json.loads((tmp_path / "plan.json").read_text()) + assert manifest["provenance"]["config"] == {"seed": winner} + assert plan["trials"] == [{"seed": winner}] + assert ready["manifest_sha256"] == hashlib.sha256( + (tmp_path / "manifest.json").read_bytes() + ).hexdigest() + assert ready["plan_sha256"] == hashlib.sha256( + (tmp_path / "plan.json").read_bytes() + ).hexdigest() + + +def test_partial_initialization_is_not_resumable_until_ready( + tmp_path, + config, + monkeypatch, +) -> None: + store = ArtifactStore(tmp_path) + real_publish = store._publish_bytes_locked + + def crash_on_ready(relative, data, *, immutable): + if relative == Path("ready.json"): + raise KeyboardInterrupt() + return real_publish(relative, data, immutable=immutable) + + monkeypatch.setattr(store, "_publish_bytes_locked", crash_on_ready) + with pytest.raises(KeyboardInterrupt): + store.initialize_run(config.canonical_dict(), {"schema_version": 1}) + + assert (tmp_path / "manifest.json").exists() + assert not (tmp_path / "ready.json").exists() + with pytest.raises(ArtifactConflict): + ArtifactStore.resume(tmp_path, config) + + monkeypatch.setattr(store, "_publish_bytes_locked", real_publish) + store.initialize_run(config.canonical_dict(), {"schema_version": 1}) + assert (tmp_path / "ready.json").exists() + + +@pytest.mark.parametrize( + "token", + ("", "..", "../x", "x/y", "/absolute", "UPPER", "white space"), +) +def test_trial_id_rejects_unsafe_tokens(tmp_path, token) -> None: + store = ArtifactStore(tmp_path) + with pytest.raises(ValueError, match="token"): + store.claim_trial(token) + with pytest.raises(ValueError, match="token"): + store.publish_trial(token, {"trial_id": token}) + + +def test_store_rejects_symlink_root_parent_and_final(tmp_path) -> None: + real = tmp_path / "real" + real.mkdir() + root_link = tmp_path / "root-link" + root_link.symlink_to(real, target_is_directory=True) + with pytest.raises(ArtifactConflict, match="symlink"): + ArtifactStore(root_link) + parent_link = tmp_path / "parent-link" + parent_link.symlink_to(real, target_is_directory=True) + with pytest.raises(ArtifactConflict, match="symlink"): + ArtifactStore(parent_link / "child") + + store = ArtifactStore(tmp_path / "store") + outside = tmp_path / "outside" + outside.mkdir() + (store.root / "linked").symlink_to(outside, target_is_directory=True) + with pytest.raises(ArtifactConflict, match="symlink"): + store.publish_json("linked/value.json", {"value": 1}) + + target = outside / "target.json" + target.write_text("{}") + (store.root / "final.json").symlink_to(target) + with pytest.raises(ArtifactConflict, match="symlink"): + store.read_json("final.json") + with pytest.raises(ArtifactConflict, match="symlink"): + store.publish_json("final.json", {"value": 1}) + + +def test_store_rejects_traversal_and_absolute_artifact_paths(tmp_path) -> None: + store = ArtifactStore(tmp_path) + for path in ( + "../escape.json", + "/tmp/escape.json", + "a/../../escape.json", + r"..\escape.json", + r"C:\absolute.json", + ): + with pytest.raises(ValueError, match="relative"): + store.publish_json(path, {"value": 1}) + with pytest.raises(ValueError, match="relative"): + store.read_json(path) + + +def test_cleanup_failure_after_durable_replace_does_not_report_failure( + tmp_path, + monkeypatch, +) -> None: + store = ArtifactStore(tmp_path) + store.publish_json("summary.json", {"version": 1}) + monkeypatch.setattr( + store, + "_unlink", + lambda *_args, **_kwargs: (_ for _ in ()).throw(OSError("cleanup")), + ) + + digest = store.publish_json("summary.json", {"version": 2}) + + assert digest == hashlib.sha256(b'{"version":2}\n').hexdigest() + assert json.loads((tmp_path / "summary.json").read_text()) == {"version": 2} + + +def test_directory_fsync_failure_reports_verified_bytes(tmp_path, monkeypatch) -> None: + store = ArtifactStore(tmp_path) + store.publish_json("summary.json", {"version": 1}) + calls = 0 + real_fsync = store._fsync_directory + + def fail_once(path: Path) -> None: + nonlocal calls + calls += 1 + if calls == 1: + raise OSError("fsync") + real_fsync(path) + + monkeypatch.setattr(store, "_fsync_directory", fail_once) + with pytest.raises(ArtifactDurabilityError) as raised: + store.publish_json("summary.json", {"version": 2}) + + assert raised.value.present == "old" + assert json.loads((tmp_path / "summary.json").read_text()) == {"version": 1} + + +def test_rollback_failure_reports_actual_new_bytes(tmp_path, monkeypatch) -> None: + store = ArtifactStore(tmp_path) + store.publish_json("summary.json", {"version": 1}) + real_replace = store._replace + replaces = 0 + + def fail_rollback(source: Path, destination: Path) -> None: + nonlocal replaces + replaces += 1 + if replaces == 2: + raise OSError("rollback") + real_replace(source, destination) + + monkeypatch.setattr(store, "_replace", fail_rollback) + monkeypatch.setattr( + store, + "_fsync_directory", + lambda *_: (_ for _ in ()).throw(OSError("fsync")), + ) + with pytest.raises(ArtifactDurabilityError) as raised: + store.publish_json("summary.json", {"version": 2}) + + assert raised.value.present == "new" + assert json.loads((tmp_path / "summary.json").read_text()) == {"version": 2} + + +def test_write_failure_closes_descriptor_and_removes_temp( + tmp_path, + monkeypatch, +) -> None: + store = ArtifactStore(tmp_path) + before = len(list(Path("/proc/self/fd").iterdir())) + monkeypatch.setattr( + os, + "write", + lambda *_: (_ for _ in ()).throw(OSError("write failed")), + ) + + with pytest.raises(OSError, match="write failed"): + store.publish_json("value.json", {"value": 1}) + + after = len(list(Path("/proc/self/fd").iterdir())) + assert after <= before + assert not list(tmp_path.glob(".artifact.tmp-*")) + + +def test_post_replace_hash_read_failure_reports_unknown_durability( + tmp_path, + monkeypatch, +) -> None: + store = ArtifactStore(tmp_path) + store.publish_json("summary.json", {"version": 1}) + real_hash = artifacts_module._file_sha256 + calls = 0 + + def fail_new_hash(path: Path) -> str: + nonlocal calls + calls += 1 + if calls >= 2: + raise OSError("read failed") + return real_hash(path) + + monkeypatch.setattr(artifacts_module, "_file_sha256", fail_new_hash) + with pytest.raises(ArtifactDurabilityError) as raised: + store.publish_json("summary.json", {"version": 2}) + + assert raised.value.present == "unreadable" + assert json.loads((tmp_path / "summary.json").read_text()) == {"version": 2} diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/tests/test_closed_loop.py b/tracks/qcs/solutions/frustration-free/challenge-113/tests/test_closed_loop.py new file mode 100644 index 000000000..e10d38bb3 --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/tests/test_closed_loop.py @@ -0,0 +1,847 @@ +from __future__ import annotations + +from dataclasses import dataclass, replace +import inspect +import json +from types import SimpleNamespace + +import numpy as np +import pytest +from scipy.optimize import minimize + +import qcontrol.closed_loop as closed_loop_module +from qcontrol.closed_loop import ( + ClosedLoopResult, + SearchSpace, + ValidationAttempt, + make_full_space, + make_model_hessian_space, + make_oracle_space, + make_random_space, + make_search_space, + run_closed_loop, +) +from qcontrol.config import DeviceConfig, SearchConfig, SystemConfig +from qcontrol.device import DeviceQueryError, Observation, make_query_device +from qcontrol.landscape import analyze_landscape, dense_hessian +from qcontrol.objectives import normalized_infidelity +from qcontrol.open_loop import optimize_open_loop +from qcontrol.pulses import PulseSpace +from qcontrol.systems import make_system, perturb_system + + +@dataclass +class _SyntheticDevice: + target: np.ndarray + failures: frozenset[int] = frozenset() + allow_certification: bool = True + + def __post_init__(self) -> None: + self.optimizer_queries = 0 + self.optimizer_shots = 0 + self.validation_queries = 0 + self.validation_shots = 0 + self._issued_validations: dict[int, Observation] = {} + + @property + def ledger(self) -> SimpleNamespace: + return SimpleNamespace( + optimizer_queries=self.optimizer_queries, + optimizer_shots=self.optimizer_shots, + validation_queries=self.validation_queries, + validation_shots=self.validation_shots, + ) + + def _fidelity(self, pulse: object) -> float: + candidate = np.asarray(pulse, dtype=np.float64) + error = candidate - self.target + curvature = np.linspace(1.0, 0.2, candidate.size) + return float(np.clip(1.0 - np.vdot(error, curvature * error), 0.0, 1.0)) + + def query(self, normalized_pulse: object) -> Observation: + self.optimizer_queries += 1 + self.optimizer_shots += 1_000 + query_index = self.optimizer_queries + if query_index in self.failures: + raise DeviceQueryError(query_index, "propagation_failure") + return Observation( + self._fidelity(normalized_pulse), + 1_000, + query_index, + False, + query_index, + attempt_index=query_index, + ) + + def validate( + self, + normalized_pulse: object, + shots: int = 100_000, + ) -> Observation: + self.validation_queries += 1 + self.validation_shots += shots + attempt_index = self.optimizer_queries + self.validation_queries + observation = Observation( + self._fidelity(normalized_pulse), + shots, + self.optimizer_queries, + True, + attempt_index, + attempt_index=attempt_index, + ) + self._issued_validations[id(observation)] = observation + return observation + + def certifies(self, observation: Observation, threshold: float = 0.999) -> bool: + return bool( + self.allow_certification + and self._issued_validations.get(id(observation)) is observation + and observation.estimate >= threshold + ) + + +@dataclass +class _ScriptedValidationDevice: + optimizer_estimates: tuple[float, ...] + + def __post_init__(self) -> None: + self.optimizer_queries = 0 + self.optimizer_shots = 0 + self.validation_queries = 0 + self.validation_shots = 0 + self._issued_validations: dict[int, Observation] = {} + + @property + def ledger(self) -> SimpleNamespace: + return SimpleNamespace( + optimizer_queries=self.optimizer_queries, + optimizer_shots=self.optimizer_shots, + validation_queries=self.validation_queries, + validation_shots=self.validation_shots, + ) + + def query(self, normalized_pulse: object) -> Observation: + del normalized_pulse + self.optimizer_queries += 1 + self.optimizer_shots += 1_000 + query_index = self.optimizer_queries + estimate = self.optimizer_estimates[ + min(query_index - 1, len(self.optimizer_estimates) - 1) + ] + return Observation( + estimate, + 1_000, + query_index, + False, + query_index, + attempt_index=query_index + self.validation_queries, + ) + + def validate( + self, + normalized_pulse: object, + shots: int = 100_000, + ) -> Observation: + del normalized_pulse + self.validation_queries += 1 + self.validation_shots += shots + attempt_index = self.optimizer_queries + self.validation_queries + if self.validation_queries == 1: + raise DeviceQueryError(attempt_index, "sampling_failure") + observation = Observation( + 1.0, + shots, + self.optimizer_queries, + True, + attempt_index, + attempt_index=attempt_index, + ) + self._issued_validations[id(observation)] = observation + return observation + + def certifies(self, observation: Observation, threshold: float = 0.999) -> bool: + return bool( + self._issued_validations.get(id(observation)) is observation + and observation.estimate >= threshold + ) + + +@pytest.fixture +def origin() -> np.ndarray: + return np.asarray([0.1, -0.2, 0.0, 0.3, -0.1, 0.2], dtype=np.float64) + + +def test_subspace_coordinates_map_to_identical_origin(origin: np.ndarray) -> None: + basis = np.eye(origin.size, dtype=np.float64)[:, :3] + space = SearchSpace(origin, basis, bound=1.0) + + np.testing.assert_allclose(space.to_pulse(np.zeros(3)), origin) + + +def test_search_space_has_array_aware_equality_and_canonical_round_trip( + origin: np.ndarray, +) -> None: + source_origin = origin.copy() + source_basis = np.eye(origin.size)[:, :3] + first = SearchSpace(source_origin, source_basis, bound=0.7) + second = SearchSpace(origin.copy(), source_basis.copy(), bound=0.7) + + source_origin[:] = 0.0 + source_basis[:] = 0.0 + payload = json.loads(json.dumps(first.canonical_dict(), allow_nan=False)) + replayed = SearchSpace.from_canonical_dict(payload) + + assert first == second + assert first == replayed + assert hash(first) == hash(replayed) + assert replayed.canonical_dict() == payload + assert not first.origin.flags.writeable + assert not first.basis.flags.writeable + assert not first.lower_bounds.flags.writeable + assert not first.upper_bounds.flags.writeable + + +def test_search_space_bytes_backing_prevents_mutation_and_stabilizes_hash( + origin: np.ndarray, +) -> None: + space = SearchSpace(origin, np.eye(origin.size)[:, :3], bound=0.7) + equal_space = SearchSpace(origin.copy(), np.eye(origin.size)[:, :3], bound=0.7) + original_hash = hash(space) + mapping = {space: "stable"} + members = {space} + + for array in ( + space.origin, + space.basis, + space.lower_bounds, + space.upper_bounds, + ): + with pytest.raises(ValueError): + array.setflags(write=True) + + assert space == equal_space + assert hash(space) == original_hash == hash(equal_space) + assert mapping[equal_space] == "stable" + assert equal_space in members + + +def test_all_candidate_spaces_share_origin_bounds_and_coordinate_scaling( + origin: np.ndarray, +) -> None: + model_basis = np.eye(origin.size, dtype=np.float64) + oracle_basis = np.roll(model_basis, 1, axis=0) + spaces = ( + make_full_space(origin, bound=0.6), + make_model_hessian_space(origin, model_basis, dimension=3, bound=0.6), + make_random_space(origin, dimension=3, seed=9, bound=0.6), + make_oracle_space(origin, oracle_basis, dimension=3, bound=0.6), + ) + + for space in spaces: + np.testing.assert_allclose( + space.to_pulse(np.zeros(space.dimension)), + origin, + ) + np.testing.assert_allclose(space.lower_bounds, -0.6) + np.testing.assert_allclose(space.upper_bounds, 0.6) + np.testing.assert_allclose( + space.basis.T @ space.basis, + np.eye(space.dimension), + atol=1e-12, + ) + np.testing.assert_array_equal(spaces[0].basis, np.eye(origin.size)) + + +def test_seeded_random_basis_uses_reproducible_canonical_qr( + origin: np.ndarray, +) -> None: + first = make_random_space(origin, dimension=3, seed=9) + second = make_random_space(origin, dimension=3, seed=9) + gaussian = np.random.default_rng(9).normal(size=(origin.size, 3)) + + np.testing.assert_allclose(first.basis, second.basis) + np.testing.assert_allclose(first.basis.T @ first.basis, np.eye(3), atol=1e-12) + assert np.all(np.diag(first.basis.T @ gaussian) >= 0.0) + + +def test_mapping_clips_normalized_pulses_consistently(origin: np.ndarray) -> None: + spaces = ( + make_full_space(origin), + make_model_hessian_space(origin, np.eye(origin.size), dimension=3), + make_random_space(origin, dimension=3, seed=2), + make_oracle_space(origin, np.eye(origin.size), dimension=3), + ) + + for space in spaces: + pulse = space.to_pulse(np.full(space.dimension, 100.0)) + assert np.all(pulse <= 1.0) + assert np.all(pulse >= -1.0) + + +def test_search_config_factory_keeps_oracle_basis_external( + origin: np.ndarray, +) -> None: + model_basis = np.eye(origin.size) + full = make_search_space(SearchConfig("full", 3, 20), origin) + informed = make_search_space( + SearchConfig("model_hessian", 3, 20), + origin, + model_basis=model_basis, + ) + random = make_search_space( + SearchConfig("random", 3, 20), + origin, + seed=7, + ) + + assert full.dimension == origin.size + assert informed.dimension == random.dimension == 3 + with pytest.raises(ValueError, match="externally constructed"): + make_search_space(SearchConfig("oracle", 3, 20), origin) + + +def test_budget_is_never_exceeded_and_failed_queries_are_charged() -> None: + origin = np.zeros(5, dtype=np.float64) + device = _SyntheticDevice( + target=np.asarray([0.5, -0.4, 0.3, 0.0, 0.0]), + failures=frozenset({2, 4, 7}), + ) + + result = run_closed_loop( + device, + make_full_space(origin), + budget=11, + seed=2, + ) + + assert device.ledger.optimizer_queries == 11 + assert result.evaluations == 11 + assert len(result.observations) == 8 + assert result.budget_exhausted + + +def test_one_dimensional_cma_avoids_broken_bound_range_std_limiter() -> None: + origin = np.zeros(3, dtype=np.float64) + space = make_random_space(origin, dimension=1, seed=8, bound=0.1) + device = _SyntheticDevice( + target=np.ones(3, dtype=np.float64), + allow_certification=False, + ) + + result = run_closed_loop(device, space, budget=5, seed=8) + + assert result.evaluations == 5 + assert result.budget_exhausted + assert device.ledger.optimizer_queries == 5 + + +@pytest.mark.parametrize("method", ("full", "model_hessian", "random", "oracle")) +def test_one_dimensional_search_kinds_are_reproducible_and_bounded(method) -> None: + origin = np.zeros(1 if method == "full" else 4, dtype=np.float64) + config = SearchConfig(method, 1, 5) + kwargs = { + "model_basis": np.eye(origin.size), + "oracle_basis": np.eye(origin.size), + "seed": 8, + } + first_space = make_search_space(config, origin, **kwargs) + second_space = make_search_space(config, origin, **kwargs) + first_audit = [] + second_audit = [] + + first = run_closed_loop( + _SyntheticDevice( + target=np.ones(origin.size, dtype=np.float64), + allow_certification=False, + ), + first_space, + budget=5, + seed=8, + audit_sink=lambda pulse, observation: first_audit.append( + (pulse.copy(), observation) + ), + ) + second = run_closed_loop( + _SyntheticDevice( + target=np.ones(origin.size, dtype=np.float64), + allow_certification=False, + ), + second_space, + budget=5, + seed=8, + audit_sink=lambda pulse, observation: second_audit.append( + (pulse.copy(), observation) + ), + ) + replayed = ClosedLoopResult.from_canonical_dict( + json.loads(json.dumps(first.canonical_dict(), allow_nan=False)) + ) + + assert first == second == replayed + assert first.evaluations == 5 + assert first.budget_exhausted + assert len(first_audit) == len(second_audit) == 5 + assert all(np.all(np.abs(pulse) <= 1.0) for pulse, _ in first_audit) + for (first_pulse, _), (second_pulse, _) in zip(first_audit, second_audit): + np.testing.assert_array_equal(first_pulse, second_pulse) + + +@pytest.mark.parametrize("shots", (None, 1_000)) +def test_one_dimensional_cma_preserves_exact_and_finite_shot_accounting( + shots, +) -> None: + system_config = SystemConfig("one_qubit", 3, 4.0) + model = make_system(system_config) + pulse_space = PulseSpace.from_system(model, system_config.segments) + truth = perturb_system(model, gap=0.2, seed=8) + device_config = DeviceConfig(gap=0.2, shots=shots, perturbation_seed=8) + space = make_random_space( + np.zeros(pulse_space.parameter_count, dtype=np.float64), + dimension=1, + seed=8, + ) + device = make_query_device(truth, pulse_space, device_config, seed=8) + + result = run_closed_loop(device, space, budget=5, seed=8) + + assert result.evaluations == device.ledger.optimizer_queries + assert result.evaluations <= 5 + assert device.ledger.optimizer_shots == result.evaluations * (shots or 0) + assert device.ledger.total_queries == ( + device.ledger.optimizer_queries + device.ledger.validation_queries + ) + assert device.ledger.total_shots == ( + device.ledger.optimizer_shots + device.ledger.validation_shots + ) + + +def test_one_dimensional_cma_certifies_and_stops_on_flat_fitness() -> None: + certified = run_closed_loop( + _SyntheticDevice(target=np.zeros(1, dtype=np.float64)), + make_full_space(np.zeros(1, dtype=np.float64)), + budget=20, + seed=8, + ) + stopped = run_closed_loop( + _ScriptedValidationDevice((0.5,)), + make_full_space(np.zeros(1, dtype=np.float64)), + budget=100, + seed=8, + ) + + assert certified.certified + assert certified.stop_reason == "certified" + assert certified.evaluations == 1 + assert not stopped.certified + assert stopped.stop_reason == "optimizer_stopped" + assert not stopped.budget_exhausted + assert stopped.evaluations < stopped.budget + + +def test_cma_options_for_dimension_two_remain_unchanged() -> None: + options = closed_loop_module._cma_options( + make_full_space(np.zeros(2, dtype=np.float64)), + seed=8, + ) + + assert "maxstd_boundrange" not in options + assert options["popsize"] == 6 + assert options["seed"] == 9 + + +def test_audit_sink_receives_copies_without_influencing_cma() -> None: + origin = np.zeros(5, dtype=np.float64) + target = np.asarray([0.5, -0.4, 0.3, 0.0, 0.0]) + audited: list[tuple[np.ndarray, Observation | None]] = [] + + def sink(pulse: np.ndarray, observation: Observation | None) -> object: + audited.append((pulse.copy(), observation)) + pulse[:] = 99.0 + return {"ignored": True} + + with_sink = run_closed_loop( + _SyntheticDevice(target=target, failures=frozenset({2})), + make_full_space(origin), + budget=8, + seed=2, + audit_sink=sink, + ) + replay = run_closed_loop( + _SyntheticDevice(target=target, failures=frozenset({2})), + make_full_space(origin), + budget=8, + seed=2, + ) + + assert len(audited) == with_sink.evaluations == 8 + assert audited[1][1] is None + assert all(np.all(np.abs(pulse) <= 1.0) for pulse, _ in audited) + np.testing.assert_allclose(with_sink.best_pulse, replay.best_pulse, atol=0.0, rtol=0.0) + assert [item.estimate for item in with_sink.observations] == [ + item.estimate for item in replay.observations + ] + + +def test_seed_zero_replays_the_same_cma_trajectory() -> None: + origin = np.zeros(5, dtype=np.float64) + target = np.asarray([0.2, -0.1, 0.15, 0.0, 0.0]) + first = run_closed_loop( + _SyntheticDevice(target=target), + make_full_space(origin), + budget=20, + seed=0, + ) + second = run_closed_loop( + _SyntheticDevice(target=target), + make_full_space(origin), + budget=20, + seed=0, + ) + + np.testing.assert_allclose(first.best_pulse, second.best_pulse, rtol=0.0, atol=0.0) + assert [item.estimate for item in first.observations] == [ + item.estimate for item in second.observations + ] + + +def test_zero_gap_origin_uses_one_optimizer_query_and_one_validation() -> None: + origin = np.zeros(5, dtype=np.float64) + device = _SyntheticDevice(target=origin.copy()) + + result = run_closed_loop( + device, + make_model_hessian_space(origin, np.eye(5), dimension=3), + budget=20, + seed=4, + ) + + assert result.certified + assert result.first_certified_query == 1 + assert result.provisional_crossings == (1,) + assert len(result.observations) == 1 + assert result.validation_result is not None + assert device.ledger.optimizer_queries == 1 + assert device.ledger.validation_queries == 1 + assert device.ledger.optimizer_shots == 1_000 + assert device.ledger.validation_shots == 100_000 + + +def test_completion_requires_device_backed_certification() -> None: + origin = np.zeros(4, dtype=np.float64) + device = _SyntheticDevice(target=origin.copy(), allow_certification=False) + + result = run_closed_loop( + device, + make_full_space(origin), + budget=5, + seed=3, + ) + + assert result.validation_result is not None + assert result.validation_result.certifies() + assert not result.certified + assert result.first_certified_query is None + assert device.ledger.optimizer_queries == 5 + + +def test_validation_attempt_history_preserves_failure_then_certification() -> None: + device = _ScriptedValidationDevice((0.9991, 0.9992)) + result = run_closed_loop( + device, + make_full_space(np.zeros(3)), + budget=5, + seed=7, + ) + + assert result.certified + assert result.first_certified_query == 2 + assert result.evaluations == 2 + assert result.provisional_crossings == (1, 2) + assert len(result.validation_attempts) == 2 + failed, succeeded = result.validation_attempts + assert isinstance(failed, ValidationAttempt) + assert failed.optimizer_query_index == 1 + assert failed.best_observation == result.observations[0] + assert failed.validation_observation is None + assert failed.failure_category == "sampling_failure" + assert failed.status == "failed" + assert not failed.certified + assert succeeded.optimizer_query_index == 2 + assert succeeded.best_observation == result.observations[1] + assert succeeded.validation_observation is result.validation_result + assert succeeded.failure_category is None + assert succeeded.status == "certified" + assert succeeded.certified + assert device.ledger.optimizer_queries == 2 + assert device.ledger.validation_queries == 2 + assert device.ledger.optimizer_shots == 2_000 + assert device.ledger.validation_shots == 200_000 + + +def test_closed_loop_result_is_comparable_and_json_round_trips_complete_history() -> None: + result = run_closed_loop( + _ScriptedValidationDevice((0.9991, 0.9992)), + make_full_space(np.zeros(3)), + budget=5, + seed=7, + ) + + payload = json.loads(json.dumps(result.canonical_dict(), allow_nan=False)) + replayed = ClosedLoopResult.from_canonical_dict(payload) + + assert replayed == result + assert hash(replayed) == hash(result) + assert replayed.canonical_dict() == payload + assert len(replayed.observations) == 2 + assert len(replayed.validation_attempts) == 2 + assert replayed.validation_attempts[0].failure_category == "sampling_failure" + assert payload["validation_attempts"][0]["status"] == "failed" + assert payload["validation_attempts"][1]["status"] == "certified" + assert replayed.validation_attempts[1].validation_observation is not None + assert ( + replayed.validation_result + == replayed.validation_attempts[1].validation_observation + ) + + +def _integrity_result(kind: str) -> ClosedLoopResult: + if kind == "certified": + return run_closed_loop( + _ScriptedValidationDevice((0.9991, 0.9992)), + make_full_space(np.zeros(3)), + budget=5, + seed=7, + ) + budget_result = run_closed_loop( + _SyntheticDevice(target=np.zeros(3), allow_certification=False), + make_full_space(np.zeros(3)), + budget=5, + seed=7, + ) + if kind == "budget": + return budget_result + assert kind == "optimizer_stopped" + return replace( + budget_result, + observations=budget_result.observations[:4], + evaluations=4, + budget_exhausted=False, + stop_reason="optimizer_stopped", + ) + + +@pytest.mark.parametrize( + ("kind", "changes"), + [ + ("certified", {"certified": False}), + ("certified", {"stop_reason": "budget"}), + ("certified", {"budget_exhausted": True}), + ("certified", {"evaluations": 6}), + ("budget", {"budget_exhausted": False}), + ("budget", {"stop_reason": "optimizer_stopped"}), + ("optimizer_stopped", {"certified": True}), + ("optimizer_stopped", {"budget_exhausted": True}), + ("optimizer_stopped", {"stop_reason": "budget"}), + ("optimizer_stopped", {"stop_reason": "certified"}), + ], +) +def test_closed_loop_result_rejects_contradictory_direct_states( + kind: str, + changes: dict[str, object], +) -> None: + with pytest.raises(ValueError): + replace(_integrity_result(kind), **changes) + + +@pytest.mark.parametrize( + ("kind", "changes"), + [ + ("certified", {"certified": False}), + ("certified", {"stop_reason": "budget"}), + ("certified", {"budget_exhausted": True}), + ("certified", {"evaluations": 6}), + ("budget", {"budget_exhausted": False}), + ("budget", {"stop_reason": "optimizer_stopped"}), + ("optimizer_stopped", {"certified": True}), + ("optimizer_stopped", {"budget_exhausted": True}), + ("optimizer_stopped", {"stop_reason": "budget"}), + ("optimizer_stopped", {"stop_reason": "certified"}), + ], +) +def test_closed_loop_result_rejects_json_state_tampering( + kind: str, + changes: dict[str, object], +) -> None: + payload = json.loads( + json.dumps(_integrity_result(kind).canonical_dict(), allow_nan=False) + ) + payload.update(changes) + + with pytest.raises(ValueError): + ClosedLoopResult.from_canonical_dict(payload) + + +@pytest.mark.parametrize( + ("field", "value"), + [ + ("provisional_crossings", [999]), + ("validation_result", None), + ], +) +def test_closed_loop_result_rejects_tampered_derived_validation_fields( + field: str, + value: object, +) -> None: + payload = json.loads( + json.dumps( + _integrity_result("certified").canonical_dict(), + allow_nan=False, + ) + ) + payload[field] = value + + with pytest.raises(ValueError): + ClosedLoopResult.from_canonical_dict(payload) + + +def test_closed_loop_imports_only_query_boundary() -> None: + source = inspect.getsource(closed_loop_module) + assert "qcontrol.offline" not in source + assert "qcontrol.landscape" not in source + assert "qcontrol.objectives" not in source + assert "qcontrol.systems" not in source + + +def test_deterministic_small_gap_d2_fixture_orders_candidate_spaces() -> None: + model = make_system(SystemConfig("one_qubit", 6, 4.0)) + pulse_space = PulseSpace.from_system(model, segments=6) + accepted = optimize_open_loop(model, pulse_space, seed=5, starts=5) + landscape = analyze_landscape( + model, + pulse_space, + accepted, + leading_count=5, + ) + assert landscape.polishing is not None + origin = np.asarray(landscape.polishing.normalized_pulse) + truth = perturb_system(model, gap=0.02, seed=9) + truth_hessian = dense_hessian( + lambda pulse: normalized_infidelity(pulse, truth, pulse_space), + origin, + ) + truth_values, truth_vectors = np.linalg.eigh(truth_hessian) + oracle_order = np.argsort(np.abs(truth_values))[::-1] + oracle_basis = truth_vectors[:, oracle_order] + budget = 120 + + def device(): + return make_query_device( + truth, + pulse_space, + DeviceConfig(gap=0.02, shots=None, perturbation_seed=9), + seed=100, + ) + + top_space = make_model_hessian_space( + origin, + landscape.model_basis, + dimension=3, + ) + random_space = make_random_space(origin, dimension=3, seed=12) + oracle_space = make_oracle_space(origin, oracle_basis, dimension=3) + top_device = device() + random_device = device() + oracle_device = device() + top = run_closed_loop( + top_device, + top_space, + budget=budget, + seed=11, + ) + random = run_closed_loop( + random_device, + random_space, + budget=budget, + seed=11, + ) + oracle = run_closed_loop( + oracle_device, + oracle_space, + budget=budget, + seed=11, + ) + + def restricted_floor(space: SearchSpace) -> float: + optimization = minimize( + lambda coordinates: float( + normalized_infidelity(space.to_pulse(coordinates), truth, pulse_space) + ), + np.zeros(space.dimension), + method="Nelder-Mead", + bounds=list(zip(space.lower_bounds, space.upper_bounds, strict=True)), + options={"maxiter": 1_000, "xatol": 1e-11, "fatol": 1e-13}, + ) + assert optimization.success + return max(0.0, float(optimization.fun)) + + random_floor = restricted_floor(random_space) + oracle_floor = restricted_floor(oracle_space) + + assert top.certified + assert top.first_certified_query == 4 + assert random.first_certified_query == 76 + assert oracle.first_certified_query == 4 + assert random.first_certified_query >= top.first_certified_query + assert oracle_floor <= random_floor + assert oracle_floor <= 1e-12 + for result, query_device in ( + (top, top_device), + (random, random_device), + (oracle, oracle_device), + ): + assert result.evaluations == result.first_certified_query + assert query_device.ledger.optimizer_queries == result.evaluations + assert query_device.ledger.optimizer_shots == 0 + assert query_device.ledger.validation_shots == ( + 100_000 * query_device.ledger.validation_queries + ) + + +@pytest.mark.parametrize("parameter_count", (24, 80)) +def test_full_dimension_model_hessian_exactly_matches_bounded_full_space( + parameter_count: int, +) -> None: + origin = np.linspace(-0.95, 0.95, parameter_count) + gaussian = np.random.default_rng(parameter_count).normal( + size=(parameter_count, parameter_count) + ) + curvature_basis, _ = np.linalg.qr(gaussian) + bound = 0.375 + model_hessian = make_search_space( + SearchConfig("model_hessian", parameter_count, 200), + origin, + model_basis=curvature_basis, + bound=bound, + ) + full = make_search_space( + SearchConfig("full", parameter_count, 200), + origin, + bound=bound, + ) + + assert np.array_equal(model_hessian.origin, full.origin) + assert np.array_equal(model_hessian.basis, full.basis) + assert np.array_equal(model_hessian.lower_bounds, full.lower_bounds) + assert np.array_equal(model_hessian.upper_bounds, full.upper_bounds) + samples = ( + full.lower_bounds, + full.upper_bounds, + np.where(np.arange(parameter_count) % 2, bound, -bound), + np.eye(parameter_count)[0] * bound, + -np.eye(parameter_count)[-1] * bound, + ) + for coordinates in samples: + assert np.array_equal( + model_hessian.to_pulse(coordinates), + full.to_pulse(coordinates), + ) diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/tests/test_config.py b/tracks/qcs/solutions/frustration-free/challenge-113/tests/test_config.py new file mode 100644 index 000000000..7b4cfd5fe --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/tests/test_config.py @@ -0,0 +1,275 @@ +import math +from collections.abc import Callable +from dataclasses import FrozenInstanceError, replace + +import pytest + +from qcontrol.config import DeviceConfig, ExperimentConfig, SearchConfig, SystemConfig + + +def valid_config() -> ExperimentConfig: + return ExperimentConfig( + run_kind="development", + system=SystemConfig(name="two_qubit", segments=20, amplitude_bound=4.0), + device=DeviceConfig(gap=0.05, shots=1000, perturbation_seed=7), + search=SearchConfig(method="model_hessian", dimension=15, budget=200), + trial_seed=11, + ) + + +def test_config_id_is_stable_and_semantic() -> None: + config = valid_config() + assert config.content_id() == config.content_id() + assert replace(config, trial_seed=12).content_id() != config.content_id() + assert replace(config, model_seed=6).content_id() != config.content_id() + assert replace(config, system=replace(config.system, duration=9.0)).content_id() != ( + config.content_id() + ) + assert len(config.content_id()) == 20 + assert int(config.content_id(), 16) >= 0 + + +def test_model_seed_is_explicit_canonical_and_defaults_to_five() -> None: + config = valid_config() + + assert config.model_seed == 5 + assert config.canonical_dict()["model_seed"] == 5 + + +def test_system_duration_defaults_are_canonical_and_serialized() -> None: + one_qubit = SystemConfig("one_qubit", 12, 4.0) + two_qubit = SystemConfig("two_qubit", 20, 4.0) + + assert one_qubit.duration is None + assert two_qubit.duration is None + assert one_qubit.effective_duration == 1.0 + assert two_qubit.effective_duration == 8.0 + assert valid_config().canonical_dict()["system"]["duration"] == 8.0 # type: ignore[index] + + +def test_replacing_an_omitted_duration_rederives_it_from_system_name() -> None: + one_qubit = SystemConfig("one_qubit", 12, 4.0) + two_qubit = replace(one_qubit, name="two_qubit") + + assert two_qubit.duration is None + assert two_qubit.effective_duration == 8.0 + + +def test_system_duration_accepts_a_positive_finite_override() -> None: + config = SystemConfig("two_qubit", 20, 4.0, duration=6.5) + assert config.duration == 6.5 + assert config.effective_duration == 6.5 + assert replace(config, name="one_qubit").effective_duration == 6.5 + + +def test_omitted_and_explicit_default_durations_have_the_same_content_id() -> None: + omitted = valid_config() + explicit = replace( + omitted, + system=replace(omitted.system, duration=omitted.system.effective_duration), + ) + assert omitted.content_id() == explicit.content_id() + + +@pytest.mark.parametrize("duration", [0.0, -1.0, float("nan"), float("inf"), True]) +def test_system_duration_rejects_invalid_values(duration: object) -> None: + with pytest.raises(ValueError, match="duration"): + SystemConfig("one_qubit", 12, 4.0, duration=duration) # type: ignore[arg-type] + + +def test_negative_zero_gap_is_normalized_for_semantic_ids() -> None: + config = valid_config() + negative_zero_device = DeviceConfig( + gap=-0.0, + shots=config.device.shots, + perturbation_seed=config.device.perturbation_seed, + ) + positive_zero_device = replace(negative_zero_device, gap=0.0) + normalized = replace(config, device=negative_zero_device) + positive_zero = replace(config, device=positive_zero_device) + + assert math.copysign(1.0, normalized.device.gap) == 1.0 + assert normalized.canonical_dict() == positive_zero.canonical_dict() + assert normalized.content_id() == positive_zero.content_id() + + +def test_configuration_instances_are_frozen() -> None: + instances = [ + valid_config(), + valid_config().system, + valid_config().device, + valid_config().search, + ] + for instance in instances: + with pytest.raises(FrozenInstanceError): + setattr(instance, next(iter(instance.__dataclass_fields__)), "changed") + + +@pytest.mark.parametrize( + "factory", + [ + lambda: SystemConfig("one_qubit", True, 1.0), + lambda: DeviceConfig(shots=True), + lambda: DeviceConfig(perturbation_seed=True), + lambda: SearchConfig("full", True, 200), + lambda: SearchConfig("full", 1, True), + lambda: replace(valid_config(), model_seed=True), + lambda: replace(valid_config(), trial_seed=True), + ], +) +def test_boolean_integer_fields_are_rejected(factory: Callable[[], object]) -> None: + with pytest.raises(ValueError): + factory() + + +@pytest.mark.parametrize("name", ["", "three_qubit"]) +def test_invalid_system_names_are_rejected(name: str) -> None: + with pytest.raises(ValueError): + SystemConfig(name, 1, 1.0) + + +@pytest.mark.parametrize("method", ["", "gradient"]) +def test_invalid_search_methods_are_rejected(method: str) -> None: + with pytest.raises(ValueError): + SearchConfig(method, 1, 200) + + +@pytest.mark.parametrize("method", ["full", "model_hessian", "random", "oracle"]) +def test_supported_search_methods_are_accepted(method: str) -> None: + assert SearchConfig(method, 1, 200).method == method + + +@pytest.mark.parametrize("value", [float("nan"), float("inf"), float("-inf")]) +@pytest.mark.parametrize("field", ["amplitude_bound", "gap"]) +def test_nonfinite_numeric_values_are_rejected(field: str, value: float) -> None: + with pytest.raises(ValueError): + if field == "amplitude_bound": + SystemConfig("one_qubit", 1, value) + else: + DeviceConfig(gap=value) + + +@pytest.mark.parametrize("field", ["amplitude_bound", "gap"]) +def test_huge_integers_raise_value_error(field: str) -> None: + huge = 10**10_000 + with pytest.raises(ValueError): + if field == "amplitude_bound": + SystemConfig("one_qubit", 1, huge) + else: + DeviceConfig(gap=huge) + + +@pytest.mark.parametrize( + "factory", + [ + lambda: SystemConfig("one_qubit", 0, 1.0), + lambda: SystemConfig("one_qubit", -1, 1.0), + lambda: SystemConfig("one_qubit", 1, 0.0), + lambda: SystemConfig("one_qubit", 1, -1.0), + lambda: SearchConfig("full", 0, 200), + lambda: SearchConfig("full", -1, 200), + lambda: SearchConfig("full", 1, 0), + lambda: SearchConfig("full", 1, -1), + ], +) +def test_nonpositive_required_values_are_rejected(factory: Callable[[], object]) -> None: + with pytest.raises(ValueError): + factory() + + +@pytest.mark.parametrize( + "factory", + [ + lambda: DeviceConfig(shots=0), + lambda: DeviceConfig(perturbation_seed=-1), + lambda: replace(valid_config(), model_seed=-1), + lambda: replace(valid_config(), trial_seed=-1), + ], +) +def test_invalid_shots_and_seeds_are_rejected(factory: Callable[[], object]) -> None: + with pytest.raises(ValueError): + factory() + + +def test_dimension_cannot_exceed_system_parameter_count() -> None: + with pytest.raises(ValueError, match="parameter count"): + ExperimentConfig( + run_kind="development", + system=SystemConfig("one_qubit", 1, 1.0), + device=DeviceConfig(), + search=SearchConfig("full", 3, 200), + trial_seed=0, + ) + + +@pytest.mark.parametrize( + ("system", "dimension"), + [ + (SystemConfig("one_qubit", 1, 1.0), 2), + (SystemConfig("two_qubit", 1, 1.0), 4), + ], +) +def test_dimension_can_equal_system_parameter_count( + system: SystemConfig, + dimension: int, +) -> None: + config = ExperimentConfig( + run_kind="development", + system=system, + device=DeviceConfig(), + search=SearchConfig("full", dimension, 200), + trial_seed=0, + ) + assert config.search.dimension == system.parameter_count + + +def test_exact_development_and_production_budgets_are_accepted() -> None: + development = valid_config() + production = replace( + development, + run_kind="production", + search=replace(development.search, budget=2000), + ) + + assert development.search.budget == 200 + assert production.search.budget == 2000 + + +def test_invalid_run_kind_is_rejected() -> None: + with pytest.raises(ValueError, match="run_kind"): + replace(valid_config(), run_kind="staging") + + +@pytest.mark.parametrize( + ("run_kind", "budget", "message"), + [ + ("development", 2000, "development budget"), + ("production", 200, "production budget"), + ], +) +def test_run_kinds_reject_the_other_budget( + run_kind: str, + budget: int, + message: str, +) -> None: + config = valid_config() + with pytest.raises(ValueError, match=message): + replace( + config, + run_kind=run_kind, + search=replace(config.search, budget=budget), + ) + + +@pytest.mark.parametrize( + ("field", "value"), + [("gap", -0.1), ("shots", -1)], +) +def test_device_config_rejects_invalid_values(field: str, value: float) -> None: + with pytest.raises(ValueError): + DeviceConfig(**{field: value}) + + +def test_production_cannot_use_development_budget() -> None: + with pytest.raises(ValueError, match="production budget"): + replace(valid_config(), run_kind="production").validate() diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/tests/test_device.py b/tracks/qcs/solutions/frustration-free/challenge-113/tests/test_device.py new file mode 100644 index 000000000..607589a1c --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/tests/test_device.py @@ -0,0 +1,557 @@ +from concurrent.futures import ThreadPoolExecutor +from dataclasses import FrozenInstanceError, replace +import inspect +import pickle +from threading import Event + +import numpy as np +import pytest + +import qcontrol.device as device_module +from qcontrol.config import DeviceConfig, SystemConfig +from qcontrol.device import DeviceQueryError, Observation, make_query_device +from qcontrol.objectives import normalized_infidelity +from qcontrol.offline import make_offline_evaluator +from qcontrol.pulses import PulseSpace +from qcontrol.systems import ControlSystem, make_system, perturb_system + + +@pytest.fixture +def device_inputs() -> tuple[ControlSystem, PulseSpace, np.ndarray]: + model = make_system(SystemConfig("one_qubit", 2, 4.0)) + truth = perturb_system(model, gap=0.03, seed=9) + space = PulseSpace.from_system(model, segments=2) + pulse = np.zeros(space.parameter_count, dtype=np.float64) + return truth, space, pulse + + +def make_device( + device_inputs: tuple[ControlSystem, PulseSpace, np.ndarray], + *, + seed: int = 4, + shots: int | None = 1_000, +): + truth, space, _ = device_inputs + return make_query_device( + truth, + space, + DeviceConfig(gap=0.03, shots=shots, perturbation_seed=9), + seed=seed, + ) + + +def test_fixed_seed_observations_are_reproducible(device_inputs) -> None: + _, _, pulse = device_inputs + first_device = make_device(device_inputs, seed=4) + second_device = make_device(device_inputs, seed=4) + + first = [first_device.query(pulse), first_device.validate(pulse)] + second = [second_device.query(pulse), second_device.validate(pulse)] + + assert first == second + + +def test_observation_seed_depends_on_sequence_and_validation_kind(device_inputs) -> None: + _, _, pulse = device_inputs + device = make_device(device_inputs, seed=4) + + first = device.query(pulse) + validation = device.validate(pulse) + second = device.query(pulse) + + assert len({first.observation_seed, validation.observation_seed, second.observation_seed}) == 3 + assert first.optimizer_query_index == 1 + assert validation.optimizer_query_index == 1 + assert second.optimizer_query_index == 2 + assert first.attempt_index == 1 + assert validation.attempt_index == 2 + assert second.attempt_index == 3 + assert all(len(item.seed_digest) == 64 for item in (first, validation, second)) + assert all(item.observation_seed.bit_length() <= 128 for item in (first, validation, second)) + + +def test_query_and_validation_accounting_is_exact(device_inputs) -> None: + _, _, pulse = device_inputs + device = make_device(device_inputs, seed=4, shots=1_000) + + device.query(pulse) + device.query(pulse) + device.validate(pulse, shots=100_000) + + assert device.ledger.optimizer_queries == 2 + assert device.ledger.optimizer_shots == 2_000 + assert device.ledger.validation_queries == 1 + assert device.ledger.validation_shots == 100_000 + assert device.ledger.total_queries == 3 + assert device.ledger.total_shots == 102_000 + + +def test_ledger_is_append_only_from_the_caller_perspective(device_inputs) -> None: + _, _, pulse = device_inputs + device = make_device(device_inputs) + empty = device.ledger + + first = device.query(pulse) + after_first = device.ledger + device.validate(pulse) + + assert empty.observations == () + assert after_first.observations == (first,) + assert len(device.ledger.observations) == 2 + assert after_first.observations[0] is not first + assert after_first.observations[0] is not after_first.observations[0] + with pytest.raises((FrozenInstanceError, AttributeError)): + after_first.observations = () # type: ignore[misc] + + +def test_public_mutation_cannot_change_private_ledger_accounting(device_inputs) -> None: + _, _, pulse = device_inputs + device = make_device(device_inputs) + returned = device.query(pulse) + snapshot = device.ledger + public_record = snapshot.records[0] + + object.__setattr__(returned, "shots", 999_999) + object.__setattr__(public_record, "charged_shots", 999_999) + + assert snapshot.optimizer_shots == 1_000 + assert snapshot.observations[0].shots == 1_000 + assert device.ledger.optimizer_shots == 1_000 + assert device.ledger.observations[0].shots == 1_000 + + +def test_device_and_ledger_capabilities_cannot_be_pickled(device_inputs) -> None: + device = make_device(device_inputs) + + with pytest.raises(TypeError, match="pickled"): + pickle.dumps(device) + with pytest.raises(TypeError, match="pickled"): + pickle.dumps(device.ledger) + + +def test_exact_mode_returns_clipped_fidelity_and_records_zero_shots( + device_inputs, +) -> None: + truth, space, pulse = device_inputs + device = make_device(device_inputs, shots=None) + + observation = device.query(pulse) + expected = float(np.clip(1.0 - normalized_infidelity(pulse, truth, space), 0.0, 1.0)) + + assert observation.estimate == pytest.approx(expected, abs=1e-14) + assert observation.shots == 0 + assert device.ledger.optimizer_shots == 0 + + +def test_public_device_and_observation_do_not_expose_truth(device_inputs) -> None: + _, _, pulse = device_inputs + device = make_device(device_inputs) + observation = device.query(pulse) + + for name in ("exact_fidelity", "hamiltonian", "truth", "_truth", "evaluator", "_evaluator"): + assert not hasattr(device, name) + assert "exact_fidelity" not in observation.__dataclass_fields__ + with pytest.raises(AttributeError): + _ = device.truth + + +def test_offline_evaluator_is_separate_from_public_device(device_inputs) -> None: + truth, space, pulse = device_inputs + device = make_device(device_inputs, shots=None) + offline_evaluator = make_offline_evaluator(truth, space) + + assert offline_evaluator(pulse) == device.query(pulse).estimate + assert not hasattr(device, "offline_evaluator") + assert not hasattr(device_module, "make_offline_evaluator") + assert "qcontrol.offline" not in inspect.getsource(device_module) + + +def test_only_independent_100000_shot_validation_can_certify() -> None: + optimizer = Observation(1.0, 100_000, 1, False, 7) + too_few_shots = Observation(1.0, 99_999, 1, True, 8) + validation = Observation(1.0, 100_000, 1, True, 9) + below_target = Observation(0.999, 100_000, 1, True, 10) + + assert not optimizer.certifies(0.999) + assert not too_few_shots.certifies(0.999) + assert validation.certifies(0.999) + assert not validation.certifies(0.998) + assert not below_target.certifies(0.999) + + +def test_backed_certification_rejects_forgery_and_mutation( + device_inputs, + monkeypatch, +) -> None: + _, _, pulse = device_inputs + monkeypatch.setattr(device_module, "normalized_infidelity", lambda *args: 0.0) + device = make_device(device_inputs) + validation = device.validate(pulse) + ledger = device.ledger + forged = replace(validation) + + assert validation.certifies(0.999) + assert forged.certifies(0.999) + assert device.certifies(validation, 0.999) + assert ledger.certifies(validation, 0.999) + assert not device.certifies(forged, 0.999) + assert not ledger.certifies(forged, 0.999) + + object.__setattr__(validation, "seed_digest", "0" * 64) + + assert validation.certifies(0.999) + assert not device.certifies(validation, 0.999) + assert not ledger.certifies(validation, 0.999) + assert ledger.validation_shots == 100_000 + assert ledger.observations[0].seed_digest != "0" * 64 + + +@pytest.mark.parametrize("shots", [0, -1, True, 1.5]) +def test_validation_rejects_invalid_shot_counts(device_inputs, shots) -> None: + _, _, pulse = device_inputs + device = make_device(device_inputs) + + with pytest.raises(DeviceQueryError) as captured: + device.validate(pulse, shots=shots) + + record = device.ledger.records[0] + assert captured.value.attempt_index == 1 + assert captured.value.category == "invalid_shots" + assert captured.value.__context__ is None + assert record.attempt_index == 1 + assert record.validation + assert not record.success + assert record.status == "failed" + assert record.requested_shots == shots + assert record.charged_shots == 0 + assert record.error_category == "invalid_shots" + assert device.ledger.optimizer_queries == 0 + assert device.ledger.validation_queries == 1 + + +def test_invalid_pulse_is_a_failed_chargeless_optimizer_attempt(device_inputs) -> None: + _, space, pulse = device_inputs + device = make_device(device_inputs) + invalid = np.full(space.parameter_count, np.nan) + + with pytest.raises(DeviceQueryError) as captured: + device.query(invalid) + successful = device.query(pulse) + + failed, passed = device.ledger.records + assert (failed.attempt_index, passed.attempt_index) == (1, 2) + assert failed.optimizer_query_index == 1 + assert passed.optimizer_query_index == 2 + assert captured.value.category == "invalid_pulse" + assert not failed.success + assert failed.error_category == "invalid_pulse" + assert failed.requested_shots == 1_000 + assert failed.charged_shots == 0 + assert device.ledger.optimizer_queries == 2 + assert device.ledger.optimizer_shots == 1_000 + assert successful.attempt_index == 2 + + +def test_propagation_failure_is_sanitized_and_ledgered( + device_inputs, + monkeypatch, +) -> None: + _, _, pulse = device_inputs + device = make_device(device_inputs) + + def fail_propagation(*args): + raise RuntimeError("private propagation detail") + + monkeypatch.setattr(device_module, "normalized_infidelity", fail_propagation) + + with pytest.raises(DeviceQueryError) as captured: + device.query(pulse) + + record = device.ledger.records[0] + assert captured.value.attempt_index == 1 + assert captured.value.category == "propagation_failure" + assert captured.value.__context__ is None + assert "private propagation detail" not in str(captured.value) + assert "private propagation detail" not in repr(captured.value) + assert record.error_category == "propagation_failure" + assert not hasattr(record, "error_message") + assert record.charged_shots == 0 + + +def test_sampling_failure_charges_requested_shots(device_inputs, monkeypatch) -> None: + _, _, pulse = device_inputs + device = make_device(device_inputs) + + class FailingSampler: + def binomial(self, shots, probability): + raise RuntimeError("sensitive backend detail") + + monkeypatch.setattr(device_module.np.random, "default_rng", lambda seed: FailingSampler()) + + with pytest.raises(DeviceQueryError) as captured: + device.query(pulse) + + record = device.ledger.records[0] + assert captured.value.category == "sampling_failure" + assert captured.value.__context__ is None + assert "sensitive backend detail" not in str(captured.value) + assert "sensitive backend detail" not in repr(captured.value) + assert not record.success + assert record.error_category == "sampling_failure" + assert record.requested_shots == 1_000 + assert record.charged_shots == 1_000 + assert device.ledger.optimizer_shots == 1_000 + + +def test_rng_setup_failure_is_not_charged(device_inputs, monkeypatch) -> None: + _, _, pulse = device_inputs + device = make_device(device_inputs) + + def fail_rng(seed): + raise RuntimeError("rng setup failed") + + monkeypatch.setattr(device_module.np.random, "default_rng", fail_rng) + + with pytest.raises(DeviceQueryError) as captured: + device.query(pulse) + + record = device.ledger.records[0] + assert captured.value.category == "rng_failure" + assert "rng setup failed" not in str(captured.value) + assert record.error_category == "rng_failure" + assert record.charged_shots == 0 + + +def test_observation_construction_failure_is_ledgered(device_inputs, monkeypatch) -> None: + _, _, pulse = device_inputs + device = make_device(device_inputs) + + def fail_observation(record): + raise RuntimeError("public conversion failed") + + monkeypatch.setattr(device_module, "_public_observation", fail_observation) + + with pytest.raises(DeviceQueryError) as captured: + device.query(pulse) + + record = device.ledger.records[0] + assert captured.value.category == "observation_failure" + assert "public conversion failed" not in str(captured.value) + assert record.error_category == "observation_failure" + assert record.charged_shots == 1_000 + + +def test_concurrent_attempts_have_unique_ordered_indices_and_seeds( + device_inputs, + monkeypatch, +) -> None: + _, _, pulse = device_inputs + monkeypatch.setattr(device_module, "normalized_infidelity", lambda *args: 0.25) + device = make_device(device_inputs) + + with ThreadPoolExecutor(max_workers=8) as executor: + observations = list(executor.map(device.query, [pulse.copy() for _ in range(64)])) + + records = device.ledger.records + assert [record.attempt_index for record in records] == list(range(1, 65)) + assert [record.optimizer_query_index for record in records] == list(range(1, 65)) + assert len({record.observation_seed for record in records}) == 64 + assert len({record.seed_digest for record in records}) == 64 + assert {item.attempt_index for item in observations} == set(range(1, 65)) + + +def test_pending_attempt_remains_visible_before_later_completion( + device_inputs, + monkeypatch, +) -> None: + _, _, pulse = device_inputs + first_started = Event() + release_first = Event() + + def blocking_evaluator(*args): + if not first_started.is_set(): + first_started.set() + assert release_first.wait(timeout=10) + return 0.25 + + monkeypatch.setattr(device_module, "normalized_infidelity", blocking_evaluator) + device = make_device(device_inputs) + + with ThreadPoolExecutor(max_workers=1) as executor: + first_future = executor.submit(device.query, pulse) + assert first_started.wait(timeout=10) + pending_snapshot = device.ledger + second = device.query(pulse) + overlap_snapshot = device.ledger + release_first.set() + first = first_future.result(timeout=10) + + assert first.attempt_index == 1 + assert second.attempt_index == 2 + assert [(item.attempt_index, item.status) for item in pending_snapshot.records] == [ + (1, "reserved") + ] + assert pending_snapshot.optimizer_queries == 1 + assert [(item.attempt_index, item.status) for item in overlap_snapshot.records] == [ + (1, "reserved"), + (2, "succeeded"), + ] + assert overlap_snapshot.optimizer_queries == 2 + assert [(item.attempt_index, item.status) for item in device.ledger.records] == [ + (1, "succeeded"), + (2, "succeeded"), + ] + assert [(item.attempt_index, item.status) for item in overlap_snapshot.records] == [ + (1, "reserved"), + (2, "succeeded"), + ] + + +def test_reentrant_query_does_not_deadlock_or_reorder_attempts( + device_inputs, + monkeypatch, +) -> None: + _, _, pulse = device_inputs + device = make_device(device_inputs) + nested = [] + during_reentry = [] + entered = False + + def reentrant_evaluator(*args): + nonlocal entered + if not entered: + entered = True + nested.append(device.query(pulse)) + during_reentry.extend(device.ledger.records) + return 0.25 + + monkeypatch.setattr(device_module, "normalized_infidelity", reentrant_evaluator) + outer = device.query(pulse) + + assert outer.attempt_index == 1 + assert nested[0].attempt_index == 2 + assert [(item.attempt_index, item.status) for item in during_reentry] == [ + (1, "reserved"), + (2, "succeeded"), + ] + assert [record.attempt_index for record in device.ledger.records] == [1, 2] + + +def test_seed_identity_is_unique_and_replayable_over_bounded_run( + device_inputs, + monkeypatch, +) -> None: + _, _, pulse = device_inputs + monkeypatch.setattr(device_module, "normalized_infidelity", lambda *args: 0.25) + + def run(): + device = make_device(device_inputs, seed=41) + for index in range(256): + if index % 7: + device.query(pulse) + else: + device.validate(pulse) + return [ + (record.observation_seed, record.seed_digest) + for record in device.ledger.records + ] + + first = run() + second = run() + + assert first == second + assert len(set(first)) == 256 + assert all(seed.bit_length() <= 128 for seed, _ in first) + assert all(len(digest) == 64 for _, digest in first) + + +def test_truncated_seed_collision_guard_fails_closed(device_inputs, monkeypatch) -> None: + _, _, pulse = device_inputs + device = make_device(device_inputs) + identities = iter( + [ + (7, "1" * 64), + (7, "2" * 64), + ] + ) + monkeypatch.setattr(device_module, "_seed_identity", lambda *args: next(identities)) + + device.query(pulse) + with pytest.raises(DeviceQueryError) as captured: + device.query(pulse) + + first, collision = device.ledger.records + assert first.success + assert not collision.success + assert captured.value.category == "seed_collision" + assert collision.error_category == "seed_collision" + assert collision.seed_digest == "2" * 64 + assert collision.charged_shots == 0 + + +def test_request_sanitization_failure_is_reserved_and_publicly_sanitized( + device_inputs, + monkeypatch, +) -> None: + _, _, pulse = device_inputs + device = make_device(device_inputs) + + def fail_request(value): + raise RuntimeError("sensitive request detail") + + monkeypatch.setattr(device_module, "_sanitized_requested_shots", fail_request) + + with pytest.raises(DeviceQueryError) as captured: + device.query(pulse) + + record = device.ledger.records[0] + assert captured.value.attempt_index == 1 + assert captured.value.category == "request_validation" + assert captured.value.__context__ is None + assert "sensitive request detail" not in str(captured.value) + assert record.status == "failed" + assert record.error_category == "request_validation" + + +def test_seed_derivation_failure_is_reserved(device_inputs, monkeypatch) -> None: + _, _, pulse = device_inputs + device = make_device(device_inputs) + + def fail_seed(*args): + raise RuntimeError("sensitive seed detail") + + monkeypatch.setattr(device_module, "_seed_identity", fail_seed) + + with pytest.raises(DeviceQueryError) as captured: + device.query(pulse) + + record = device.ledger.records[0] + assert captured.value.category == "seed_derivation_failure" + assert "sensitive seed detail" not in repr(captured.value) + assert record.attempt_index == 1 + assert record.status == "failed" + assert record.error_category == "seed_derivation_failure" + + +def test_keyboard_interrupt_finalizes_aborted_attempt_without_swallowing( + device_inputs, + monkeypatch, +) -> None: + _, _, pulse = device_inputs + device = make_device(device_inputs) + + def interrupt(*args): + raise KeyboardInterrupt("process control detail") + + monkeypatch.setattr(device_module, "normalized_infidelity", interrupt) + + with pytest.raises(KeyboardInterrupt, match="process control detail"): + device.query(pulse) + + record = device.ledger.records[0] + assert record.attempt_index == 1 + assert record.status == "aborted" + assert not record.success + assert record.error_category == "keyboard_interrupt" + assert record.charged_shots == 0 + assert device.ledger.optimizer_queries == 1 diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/tests/test_dynamics.py b/tracks/qcs/solutions/frustration-free/challenge-113/tests/test_dynamics.py new file mode 100644 index 000000000..670c3d5ba --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/tests/test_dynamics.py @@ -0,0 +1,355 @@ +import jax +import jax.numpy as jnp +import numpy as np +import pytest +from jax.scipy.linalg import expm + +from qcontrol.config import SystemConfig +from qcontrol.objectives import normalized_infidelity, process_infidelity_from_unitary +from qcontrol.propagation import propagate +from qcontrol.pulses import PulseSpace +from qcontrol.systems import make_system + + +jax.config.update("jax_enable_x64", True) + + +def test_normalized_coordinates_round_trip() -> None: + space = PulseSpace.from_system( + make_system(SystemConfig("two_qubit", 20, 4.0)), + 20, + ) + pulse = jnp.linspace(-0.8, 0.8, space.parameter_count) + np.testing.assert_allclose(space.to_normalized(space.to_physical(pulse)), pulse) + assert space.to_physical(pulse).shape == (4, 20) + + +@pytest.mark.parametrize( + "pulse", + [ + jnp.zeros(23), + jnp.zeros((2, 12)), + jnp.full(24, jnp.nan), + jnp.full(24, jnp.inf), + jnp.full(24, 1.01), + jnp.full(24, -1.01), + ], +) +def test_normalized_coordinates_reject_invalid_values(pulse: jax.Array) -> None: + system = make_system(SystemConfig("one_qubit", 12, 4.0)) + space = PulseSpace.from_system(system, 12) + with pytest.raises(ValueError, match="normalized"): + space.to_physical(pulse) + + +@pytest.mark.parametrize( + ("control_count", "segments", "scales", "bound"), + [ + (0, 12, (4.0,), 1.0), + (1, 0, (4.0,), 1.0), + (2, 12, (4.0,), 1.0), + (1, 12, (0.0,), 1.0), + (1, 12, (np.inf,), 1.0), + (1, 12, (4.0,), 0.0), + (1, 12, (4.0,), 0.5), + (1, 12, (4.0,), 1.01), + (1, 12, (4.0,), np.nan), + ], +) +def test_pulse_space_rejects_invalid_construction( + control_count: int, + segments: int, + scales: tuple[float, ...], + bound: float, +) -> None: + with pytest.raises(ValueError): + PulseSpace(control_count, segments, scales, bound) + + +def test_physical_coordinates_reject_wrong_shape_nonfinite_and_out_of_bounds() -> None: + system = make_system(SystemConfig("one_qubit", 12, 4.0)) + space = PulseSpace.from_system(system, 12) + invalid = ( + jnp.zeros(24), + jnp.zeros((2, 11)), + jnp.full((2, 12), jnp.nan), + jnp.full((2, 12), 4.01), + ) + for pulse in invalid: + with pytest.raises(ValueError, match="physical"): + space.to_normalized(pulse) + + +def test_propagator_is_unitary() -> None: + system = make_system(SystemConfig("two_qubit", 20, 4.0)) + pulse = jnp.zeros((4, 20)) + unitary = propagate(system, pulse) + np.testing.assert_allclose( + unitary.conj().T @ unitary, + jnp.eye(4), + rtol=0.0, + atol=1e-12, + ) + assert unitary.dtype == jnp.complex128 + + +def test_propagator_left_multiplies_segments_in_chronological_order() -> None: + system = make_system(SystemConfig("one_qubit", 2, 4.0)) + pulse = jnp.array([[0.2, -0.3], [0.4, 0.1]], dtype=jnp.float64) + duration = 0.7 + controls = jnp.stack(tuple(jnp.asarray(control) for control in system.controls)) + drift = jnp.asarray(system.drift) + segment_0 = expm( + -1.0j + * duration + / 2 + * (drift + jnp.tensordot(pulse[:, 0], controls, axes=1)) + ) + segment_1 = expm( + -1.0j + * duration + / 2 + * (drift + jnp.tensordot(pulse[:, 1], controls, axes=1)) + ) + actual = propagate(system, pulse, duration=duration) + + np.testing.assert_allclose( + actual, + segment_1 @ segment_0, + rtol=0.0, + atol=1e-14, + ) + assert not np.allclose(actual, segment_0 @ segment_1) + + +def test_propagator_supports_jitted_traced_duration() -> None: + system = make_system(SystemConfig("one_qubit", 2, 4.0)) + pulse = jnp.array([[0.2, -0.3], [0.4, 0.1]], dtype=jnp.float64) + jitted = jax.jit(lambda value: propagate(system, pulse, duration=value)) + expected = propagate(system, pulse, 0.7) + np.testing.assert_allclose(propagate(system, pulse, jnp.float64(0.7)), expected) + np.testing.assert_allclose(jitted(jnp.float64(0.7)), expected) + + +def test_propagator_uses_system_duration_and_allows_explicit_override() -> None: + system = make_system(SystemConfig("one_qubit", 2, 4.0, duration=1.7)) + pulse = jnp.array([[0.2, -0.3], [0.4, 0.1]], dtype=jnp.float64) + + np.testing.assert_allclose(propagate(system, pulse), propagate(system, pulse, 1.7)) + assert not np.allclose(propagate(system, pulse), propagate(system, pulse, 0.7)) + + +def test_jitted_propagator_uses_immutable_system_duration() -> None: + system = make_system(SystemConfig("one_qubit", 2, 4.0, duration=1.7)) + pulse = jnp.array([[0.2, -0.3], [0.4, 0.1]], dtype=jnp.float64) + jitted = jax.jit(lambda values: propagate(system, values)) + + np.testing.assert_allclose(jitted(pulse), propagate(system, pulse, 1.7)) + + +@pytest.mark.parametrize( + ("pulse", "duration"), + [ + (jnp.full((2, 2), jnp.nan), jnp.float64(1.0)), + (jnp.full((2, 2), jnp.inf), jnp.float64(1.0)), + (jnp.zeros((2, 2)), jnp.float64(-1.0)), + (jnp.zeros((2, 2)), jnp.float64(jnp.inf)), + ], +) +def test_jitted_propagator_returns_nonfinite_sentinel_for_invalid_tracers( + pulse: jax.Array, + duration: jax.Array, +) -> None: + system = make_system(SystemConfig("one_qubit", 2, 4.0)) + result = jax.jit(lambda values, time: propagate(system, values, time))( + pulse, + duration, + ) + assert jnp.all(~jnp.isfinite(result)) + + +@pytest.mark.parametrize( + ("pulse", "duration"), + [ + (jnp.zeros(24), 1.0), + (jnp.zeros((3, 12)), 1.0), + (jnp.zeros((2, 0)), 1.0), + (jnp.full((2, 12), jnp.nan), 1.0), + (jnp.zeros((2, 12)), -1.0), + (jnp.zeros((2, 12)), 0.0), + (jnp.zeros((2, 12)), np.inf), + (jnp.zeros((2, 12)), True), + ], +) +def test_propagator_rejects_invalid_inputs( + pulse: jax.Array, + duration: object, +) -> None: + system = make_system(SystemConfig("one_qubit", 12, 4.0)) + with pytest.raises(ValueError): + propagate(system, pulse, duration=duration) # type: ignore[arg-type] + + +def test_infidelity_is_global_phase_invariant() -> None: + system = make_system(SystemConfig("one_qubit", 12, 4.0)) + pulse = jnp.zeros((2, 12)) + unitary = propagate(system, pulse) + assert np.isclose( + process_infidelity_from_unitary(unitary, system.target), + process_infidelity_from_unitary(jnp.exp(0.3j) * unitary, system.target), + ) + + +def test_reported_infidelity_is_bounded_but_internal_loss_is_unclipped() -> None: + target = jnp.eye(2, dtype=jnp.complex128) + nonunitary = 2.0 * target + assert process_infidelity_from_unitary(nonunitary, target) == 0.0 + + system = make_system(SystemConfig("one_qubit", 1, 4.0)) + space = PulseSpace.from_system(system, 1) + value = normalized_infidelity(jnp.zeros(space.parameter_count), system, space) + expected_unitary = propagate(system, jnp.zeros((2, 1))) + overlap = jnp.trace(jnp.asarray(system.target).conj().T @ expected_unitary) + expected = 1.0 - jnp.real(overlap.conj() * overlap) / system.dimension**2 + np.testing.assert_allclose(value, expected, rtol=0.0, atol=1e-15) + + +def test_normalized_infidelity_uses_system_duration() -> None: + system = make_system(SystemConfig("one_qubit", 2, 4.0, duration=1.7)) + space = PulseSpace.from_system(system, 2) + normalized = jnp.linspace(-0.2, 0.2, space.parameter_count) + physical = space.to_physical(normalized) + unitary = propagate(system, physical, duration=system.duration) + overlap = jnp.trace(jnp.asarray(system.target).conj().T @ unitary) + expected = 1.0 - jnp.real(overlap.conj() * overlap) / system.dimension**2 + + np.testing.assert_allclose( + normalized_infidelity(normalized, system, space), + expected, + rtol=0.0, + atol=1e-15, + ) + + +@pytest.mark.parametrize( + "invalid_value", + [1.01, -1.01, jnp.nan, jnp.inf, -jnp.inf], +) +def test_jitted_normalized_infidelity_returns_infinity_for_invalid_controls( + invalid_value: float, +) -> None: + system = make_system(SystemConfig("one_qubit", 12, 4.0)) + space = PulseSpace.from_system(system, 12) + pulse = jnp.zeros(space.parameter_count).at[3].set(invalid_value) + jitted = jax.jit(lambda values: normalized_infidelity(values, system, space)) + assert jnp.isposinf(jitted(pulse)) + + +@pytest.mark.parametrize("invalid_value", [1.01, jnp.nan, jnp.inf]) +def test_grad_normalized_infidelity_returns_deterministic_invalid_sentinel( + invalid_value: float, +) -> None: + system = make_system(SystemConfig("one_qubit", 12, 4.0)) + space = PulseSpace.from_system(system, 12) + pulse = jnp.zeros(space.parameter_count).at[3].set(invalid_value) + value, gradient = jax.jit( + jax.value_and_grad(lambda values: normalized_infidelity(values, system, space)) + )(pulse) + assert jnp.isposinf(value) + np.testing.assert_array_equal(gradient, jnp.zeros_like(gradient)) + + +def test_normalized_infidelity_rejects_mismatched_pulse_space() -> None: + system = make_system(SystemConfig("one_qubit", 12, 4.0)) + wrong_controls = PulseSpace(1, 12, (4.0,), 1.0) + space = PulseSpace.from_system(system, 12) + wrong_scales = PulseSpace(2, 12, (3.0, 4.0), 1.0) + + with pytest.raises(ValueError, match="control count"): + normalized_infidelity( + jnp.zeros(wrong_controls.parameter_count), + system, + wrong_controls, + ) + with pytest.raises(ValueError, match="segment"): + normalized_infidelity( + jnp.zeros(space.parameter_count - space.control_count), + system, + space, + ) + with pytest.raises(ValueError, match="amplitude scales"): + normalized_infidelity( + jnp.zeros(wrong_scales.parameter_count), + system, + wrong_scales, + ) + + +@pytest.mark.parametrize( + ("unitary", "target"), + [ + ( + jnp.zeros((0, 0), dtype=jnp.complex128), + jnp.zeros((0, 0), dtype=jnp.complex128), + ), + (jnp.zeros((2, 3), dtype=jnp.complex128), jnp.eye(2)), + (jnp.eye(2), jnp.eye(3)), + (jnp.full((2, 2), jnp.nan + 0.0j), jnp.eye(2)), + (jnp.eye(2), jnp.full((2, 2), jnp.inf + 0.0j)), + ], +) +def test_direct_objective_rejects_invalid_eager_inputs( + unitary: jax.Array, + target: jax.Array, +) -> None: + with pytest.raises(ValueError): + process_infidelity_from_unitary(unitary, target) + + +@pytest.mark.parametrize("invalid_value", [jnp.nan, jnp.inf]) +def test_jitted_direct_objective_returns_infinity_for_nonfinite_inputs( + invalid_value: float, +) -> None: + target = jnp.eye(2, dtype=jnp.complex128) + unitary = target.at[0, 0].set(invalid_value + 0.0j) + jitted = jax.jit(process_infidelity_from_unitary) + assert jnp.isposinf(jitted(unitary, target)) + + +def test_grad_direct_objective_has_finite_zero_invalid_gradient() -> None: + target = jnp.eye(2, dtype=jnp.complex128) + + def loss(value: jax.Array) -> jax.Array: + unitary = target.at[0, 0].set(value + 0.0j) + return process_infidelity_from_unitary(unitary, target) + + value, gradient = jax.jit(jax.value_and_grad(loss))(jnp.float64(jnp.nan)) + assert jnp.isposinf(value) + assert gradient == 0.0 + + +def test_gradient_matches_central_difference_and_is_real_float64() -> None: + system = make_system(SystemConfig("one_qubit", 12, 4.0)) + space = PulseSpace.from_system(system, 12) + x = jnp.linspace(-0.1, 0.1, space.parameter_count) + value, gradient = jax.value_and_grad(normalized_infidelity)(x, system, space) + direction = jnp.arange(space.parameter_count, dtype=jnp.float64) + direction /= jnp.linalg.norm(direction) + step = 1e-5 + finite = ( + normalized_infidelity(x + step * direction, system, space) + - normalized_infidelity(x - step * direction, system, space) + ) / (2 * step) + directional = jnp.vdot(gradient, direction) + assert jnp.linalg.norm(gradient) > 1e-4 + assert jnp.abs(directional) > 1e-5 + assert jnp.abs(finite) > 1e-5 + np.testing.assert_allclose( + directional, + finite, + rtol=1e-6, + atol=1e-8, + ) + assert value.dtype == jnp.float64 + assert gradient.dtype == jnp.float64 + assert not jnp.iscomplexobj(gradient) diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/tests/test_evidence.py b/tracks/qcs/solutions/frustration-free/challenge-113/tests/test_evidence.py new file mode 100644 index 000000000..5bda2d987 --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/tests/test_evidence.py @@ -0,0 +1,681 @@ +from __future__ import annotations + +import hashlib +import importlib.util +import json +import os +from pathlib import Path +import shutil +import subprocess + +import pytest + +from qcontrol.evidence import ( + REQUIRED_EVIDENCE_FILES, + validate_deployment, + validate_evidence_document, + validate_evidence_directory, +) + + +ROOT = Path(__file__).parents[1] +REVISION = "a" * 40 +SHA256 = "b" * 64 + + +def _write_json(path: Path, payload: object) -> str: + data = ( + json.dumps(payload, allow_nan=False, separators=(",", ":"), sort_keys=True) + + "\n" + ).encode() + path.parent.mkdir(parents=True, exist_ok=True) + path.write_bytes(data) + return hashlib.sha256(data).hexdigest() + + +def test_tracked_task10a_evidence_is_independently_schema_valid() -> None: + hashes = validate_evidence_directory(ROOT / "evidence" / "task10a") + + assert set(hashes) == set(REQUIRED_EVIDENCE_FILES) + assert all(len(value) == 64 for value in hashes.values()) + + +def test_calibration_uses_canonical_representative_model_seed() -> None: + path = ROOT / "scripts" / "calibrate_pilot.py" + spec = importlib.util.spec_from_file_location("calibrate_pilot", path) + assert spec is not None and spec.loader is not None + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + + config = module.representative_config() + + assert config.model_seed == 5 + assert config.trial_seed == 0 + assert config.device.perturbation_seed == 0 + assert config.system.parameter_count == 80 + assert config.search.dimension == 4 + + +def test_slurm_pilot_decouples_model_and_statistical_seeds() -> None: + script = (ROOT / "scripts" / "slurm_pilot.sh").read_text() + + assert "--model-seed 5" in script + assert "--perturbation-seed 0" in script + assert "--seed 0" in script + + +def test_calibration_writer_creates_canonical_nested_output(tmp_path) -> None: + path = ROOT / "scripts" / "calibrate_pilot.py" + spec = importlib.util.spec_from_file_location("calibrate_pilot_writer", path) + assert spec is not None and spec.loader is not None + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + output = tmp_path / "nested" / "calibration.json" + + module.write_json(output, {"z": 2, "a": 1}) + + assert output.read_bytes() == b'{"a":1,"z":2}\n' + + +def test_pre_submit_runtime_gate_checks_versions_platform_and_objective() -> None: + path = ROOT / "scripts" / "pre_submit_gate.py" + spec = importlib.util.spec_from_file_location("pre_submit_gate", path) + assert spec is not None and spec.loader is not None + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + deployment = { + "critical_packages": { + "jax": "0.11.0", + "jaxlib": "0.11.0", + "numpy": "2.5.1", + "scipy": "1.18.0", + }, + "python_version": "3.12.12", + "uv_version": "0.9.9", + } + observation = { + **deployment, + "jax_platform": "cpu", + "objective": module.EXPECTED_OBJECTIVE, + "propagation_finite": True, + "x64_enabled": True, + } + + module.validate_runtime(observation, deployment) + marker = module.prepared_runtime_marker( + { + "archive_sha256": "1" * 64, + "cluster_profile": "lasg02-cpu-v1", + "deployment_metadata_sha256": "2" * 64, + "evidence_index_sha256": "3" * 64, + "pyproject_sha256": "4" * 64, + "report_sha256": "5" * 64, + "revision": "a" * 40, + "schema_version": 1, + "sif_sha256": "6" * 64, + "uv_lock_sha256": "7" * 64, + }, + observation, + ) + assert marker["preparation_mode"] == "one-time-frozen-networked-sync" + assert marker["execution_isolation"] == "cleanenv-network-none" + assert marker["runtime_versions"] == { + "critical_packages": deployment["critical_packages"], + "python_version": "3.12.12", + "uv_version": "0.9.9", + } + assert marker["isolated_smoke"]["objective"] == module.EXPECTED_OBJECTIVE + assert marker["isolated_smoke"]["propagation_finite"] is True + observation["objective"] += 1e-10 + with pytest.raises(RuntimeError, match="objective"): + module.validate_runtime(observation, deployment) + + +def test_deployment_rejects_stale_revision_archive_and_report(tmp_path) -> None: + report = shutil.copy(ROOT / "REPORT.md", tmp_path / "REPORT.md") + pyproject = shutil.copy(ROOT / "pyproject.toml", tmp_path / "pyproject.toml") + uv_lock = shutil.copy(ROOT / "uv.lock", tmp_path / "uv.lock") + pyproject_sha256 = hashlib.sha256(pyproject.read_bytes()).hexdigest() + uv_lock_sha256 = hashlib.sha256(uv_lock.read_bytes()).hexdigest() + report_sha256 = hashlib.sha256(report.read_bytes()).hexdigest() + evidence = shutil.copytree( + ROOT / "evidence" / "task10a", + tmp_path / "evidence" / "task10a", + ) + evidence_revision = json.loads((evidence / "index.json").read_text())[ + "source_revision" + ] + evidence_index_sha256 = hashlib.sha256( + (evidence / "index.json").read_bytes() + ).hexdigest() + archive = tmp_path / f"challenge-113-{REVISION[:7]}.tar.gz" + archive.write_bytes(b"exact archive bytes") + archive_sha256 = hashlib.sha256(archive.read_bytes()).hexdigest() + sif = tmp_path.parent / "uv-0.9.9-python3.12-bookworm-slim.sif" + sif.write_bytes(b"exact sif bytes") + sif_sha256 = hashlib.sha256(sif.read_bytes()).hexdigest() + metadata = tmp_path.parent / f"{tmp_path.name}-deployment.json" + metadata_sha256 = _write_json( + metadata, + { + "archive_name": archive.name, + "archive_sha256": archive_sha256, + "evidence_index_sha256": evidence_index_sha256, + "cluster_profile": "lasg02-cpu-v1", + "critical_packages": { + "jax": "0.11.0", + "jaxlib": "0.11.0", + "numpy": "2.5.1", + "scipy": "1.18.0", + }, + "pyproject_sha256": pyproject_sha256, + "python_version": "3.12.12", + "report_sha256": report_sha256, + "revision": REVISION, + "schema_version": 1, + "sif_name": sif.name, + "sif_sha256": sif_sha256, + "uv_lock_sha256": uv_lock_sha256, + "uv_version": "0.9.9", + }, + ) + + validate_deployment( + tmp_path, + archive_path=archive, + deployment_metadata_path=metadata, + expected_sif_sha256=sif_sha256, + expected_deployment_metadata_sha256=metadata_sha256, + expected_cluster_profile="lasg02-cpu-v1", + expected_pyproject_sha256=pyproject_sha256, + expected_uv_lock_sha256=uv_lock_sha256, + expected_revision=REVISION, + expected_archive_sha256=archive_sha256, + expected_evidence_revision=evidence_revision, + ) + with pytest.raises(ValueError, match="revision"): + validate_deployment( + tmp_path, + archive_path=archive, + deployment_metadata_path=metadata, + expected_sif_sha256=sif_sha256, + expected_deployment_metadata_sha256=metadata_sha256, + expected_cluster_profile="lasg02-cpu-v1", + expected_pyproject_sha256=pyproject_sha256, + expected_uv_lock_sha256=uv_lock_sha256, + expected_revision="d" * 40, + expected_archive_sha256=archive_sha256, + expected_evidence_revision=evidence_revision, + ) + with pytest.raises(ValueError, match="archive"): + validate_deployment( + tmp_path, + archive_path=archive, + deployment_metadata_path=metadata, + expected_sif_sha256=sif_sha256, + expected_deployment_metadata_sha256=metadata_sha256, + expected_cluster_profile="lasg02-cpu-v1", + expected_pyproject_sha256=pyproject_sha256, + expected_uv_lock_sha256=uv_lock_sha256, + expected_revision=REVISION, + expected_archive_sha256="e" * 64, + expected_evidence_revision=evidence_revision, + ) + archive.write_bytes(b"mutated archive bytes") + with pytest.raises(ValueError, match="archive bytes"): + validate_deployment( + tmp_path, + archive_path=archive, + deployment_metadata_path=metadata, + expected_sif_sha256=sif_sha256, + expected_deployment_metadata_sha256=metadata_sha256, + expected_cluster_profile="lasg02-cpu-v1", + expected_pyproject_sha256=pyproject_sha256, + expected_uv_lock_sha256=uv_lock_sha256, + expected_revision=REVISION, + expected_archive_sha256=archive_sha256, + expected_evidence_revision=evidence_revision, + ) + archive.write_bytes(b"exact archive bytes") + report.write_text("stale report\n") + with pytest.raises(ValueError, match="report"): + validate_deployment( + tmp_path, + archive_path=archive, + deployment_metadata_path=metadata, + expected_sif_sha256=sif_sha256, + expected_deployment_metadata_sha256=metadata_sha256, + expected_cluster_profile="lasg02-cpu-v1", + expected_pyproject_sha256=pyproject_sha256, + expected_uv_lock_sha256=uv_lock_sha256, + expected_revision=REVISION, + expected_archive_sha256=archive_sha256, + expected_evidence_revision=evidence_revision, + ) + + +def test_evidence_rejects_coerced_types_and_nonfinite_values() -> None: + calibration = json.loads( + (ROOT / "evidence" / "task10a" / "calibration.json").read_text() + ) + calibration["payload"]["cpu_count"] = True + with pytest.raises(ValueError, match="JSON type"): + validate_evidence_document(calibration) + calibration["payload"]["cpu_count"] = 32 + calibration["payload"]["warm_query_seconds"] = float("inf") + with pytest.raises(ValueError, match="finite"): + validate_evidence_document(calibration) + + +@pytest.mark.integration +def test_documented_clean_production_check_reaches_ready_gate(tmp_path) -> None: + source = shutil.copytree( + ROOT, + tmp_path / "source", + ignore=shutil.ignore_patterns( + ".pytest_cache", + ".venv", + "__pycache__", + "results", + ), + ) + commit_environment = { + **os.environ, + "GIT_AUTHOR_NAME": "Challenge 113 Test", + "GIT_AUTHOR_EMAIL": "challenge113@example.invalid", + "GIT_COMMITTER_NAME": "Challenge 113 Test", + "GIT_COMMITTER_EMAIL": "challenge113@example.invalid", + } + subprocess.run(["git", "init", "-q"], cwd=source, check=True) + subprocess.run(["git", "add", "."], cwd=source, check=True) + subprocess.run( + ["git", "commit", "-q", "-m", "fixture"], + cwd=source, + env=commit_environment, + check=True, + ) + revision = subprocess.check_output( + ["git", "rev-parse", "HEAD"], + cwd=source, + text=True, + ).strip() + archive = tmp_path / f"challenge-113-{revision[:7]}.tar.gz" + archive.write_bytes(b"clean production check archive") + archive_sha256 = hashlib.sha256(archive.read_bytes()).hexdigest() + evidence_revision = json.loads( + (source / "evidence" / "task10a" / "index.json").read_text() + )["source_revision"] + metadata = tmp_path / "deployment.json" + pyproject_sha256 = hashlib.sha256( + (source / "pyproject.toml").read_bytes() + ).hexdigest() + uv_lock_sha256 = hashlib.sha256((source / "uv.lock").read_bytes()).hexdigest() + sif_sha256 = "2" * 64 + metadata_sha256 = _write_json( + metadata, + { + "archive_name": archive.name, + "archive_sha256": archive_sha256, + "cluster_profile": "lasg02-cpu-v1", + "critical_packages": { + "jax": "0.11.0", + "jaxlib": "0.11.0", + "numpy": "2.5.1", + "scipy": "1.18.0", + }, + "evidence_index_sha256": hashlib.sha256( + (source / "evidence" / "task10a" / "index.json").read_bytes() + ).hexdigest(), + "pyproject_sha256": pyproject_sha256, + "python_version": "3.12.12", + "report_sha256": hashlib.sha256( + (source / "REPORT.md").read_bytes() + ).hexdigest(), + "revision": revision, + "schema_version": 1, + "sif_name": "uv-0.9.9-python3.12-bookworm-slim.sif", + "sif_sha256": sif_sha256, + "uv_lock_sha256": uv_lock_sha256, + "uv_version": "0.9.9", + }, + ) + result = subprocess.run( + ["bash", "scripts/run_production.sh"], + cwd=source, + env={ + **os.environ, + "CHALLENGE113_ACK_PRODUCTION": "1", + "CHALLENGE113_ARCHIVE_PATH": str(archive), + "CHALLENGE113_ARCHIVE_SHA256": archive_sha256, + "CHALLENGE113_CHECK_ONLY": "1", + "CHALLENGE113_CLUSTER_PROFILE": "lasg02-cpu-v1", + "CHALLENGE113_DEPLOYMENT_METADATA": str(metadata), + "CHALLENGE113_DEPLOYMENT_METADATA_SHA256": metadata_sha256, + "CHALLENGE113_EVIDENCE_REVISION": evidence_revision, + "CHALLENGE113_EXPECTED_REVISION": revision, + "CHALLENGE113_JAX_PLATFORM": "cpu", + "CHALLENGE113_PYPROJECT_SHA256": pyproject_sha256, + "CHALLENGE113_PRODUCTION_OUTPUT": str(tmp_path / "production"), + "CHALLENGE113_SIF_SHA256": sif_sha256, + "CHALLENGE113_UV_LOCK_SHA256": uv_lock_sha256, + }, + text=True, + capture_output=True, + check=True, + ) + + assert '{"production_gate":"ready"}' in result.stdout + assert ( + subprocess.check_output( + ["git", "status", "--porcelain=v1", "--untracked-files=all"], + cwd=source, + text=True, + ) + == "" + ) + assert not (source / ".deployment.json").exists() + + +@pytest.mark.integration +def test_apptainer_prepare_then_pilot_is_offline_with_fake_runtime(tmp_path) -> None: + deployment = tmp_path / "source" + deployment.mkdir() + (deployment / "scripts").mkdir() + shutil.copy( + ROOT / "scripts" / "apptainer_job_gate.sh", + deployment / "scripts" / "apptainer_job_gate.sh", + ) + shutil.copy(ROOT / "pyproject.toml", deployment / "pyproject.toml") + shutil.copy(ROOT / "uv.lock", deployment / "uv.lock") + revision = "a" * 40 + (deployment / ".source-revision").write_text(revision + "\n") + archive = tmp_path / f"challenge-113-{revision[:7]}.tar.gz" + archive.write_bytes(b"archive") + sif = tmp_path / "uv-0.9.9-python3.12-bookworm-slim.sif" + sif.write_bytes(b"sif") + metadata = tmp_path / "deployment.json" + metadata.write_text("{}\n") + log = tmp_path / "apptainer.log" + fake = tmp_path / "apptainer" + fake.write_text( + "#!/usr/bin/env bash\n" + "set -euo pipefail\n" + "printf '%s\\n' \"$*\" >> \"${FAKE_APPTAINER_LOG}\"\n" + "if [[ \"$*\" == *'uv sync --frozen'* ]]; then\n" + " mkdir -p \"${CHALLENGE113_DEPLOYMENT}/.venv/bin\"\n" + " ln -s /opt/container-python/bin/python " + "\"${CHALLENGE113_DEPLOYMENT}/.venv/bin/python\"\n" + "fi\n" + "if [[ \"$*\" == *'--write-marker'* ]]; then\n" + " mkdir -p \"${CHALLENGE113_DEPLOYMENT}/.runtime\"\n" + " printf '{}\\n' > " + "\"${CHALLENGE113_DEPLOYMENT}/.runtime/task10c-ready.json\"\n" + "fi\n" + ) + fake.chmod(0o755) + environment = { + **os.environ, + "CHALLENGE113_ACK_NETWORKED_PREPARE": "1", + "CHALLENGE113_APPTAINER": str(fake), + "CHALLENGE113_ARCHIVE_PATH": str(archive), + "CHALLENGE113_ARCHIVE_SHA256": hashlib.sha256( + archive.read_bytes() + ).hexdigest(), + "CHALLENGE113_CLUSTER_PROFILE": "lasg02-cpu-v1", + "CHALLENGE113_DEPLOYMENT": str(deployment), + "CHALLENGE113_DEPLOYMENT_METADATA": str(metadata), + "CHALLENGE113_DEPLOYMENT_METADATA_SHA256": hashlib.sha256( + metadata.read_bytes() + ).hexdigest(), + "CHALLENGE113_EVIDENCE_REVISION": "b" * 40, + "CHALLENGE113_EXPECTED_REVISION": revision, + "CHALLENGE113_PYPROJECT_SHA256": hashlib.sha256( + (deployment / "pyproject.toml").read_bytes() + ).hexdigest(), + "CHALLENGE113_RUN_ROOT": str(tmp_path / "output"), + "CHALLENGE113_SIF_PATH": str(sif), + "CHALLENGE113_SIF_SHA256": hashlib.sha256(sif.read_bytes()).hexdigest(), + "CHALLENGE113_UV_LOCK_SHA256": hashlib.sha256( + (deployment / "uv.lock").read_bytes() + ).hexdigest(), + "FAKE_APPTAINER_LOG": str(log), + "SLURM_CPUS_PER_TASK": "8", + } + mismatched = { + **environment, + "CHALLENGE113_DEPLOYMENT_METADATA_SHA256": "0" * 64, + } + rejected = subprocess.run( + ["bash", str(ROOT / "scripts" / "prepare_apptainer_runtime.sh")], + env=mismatched, + capture_output=True, + text=True, + ) + assert rejected.returncode != 0 + assert "CHALLENGE113_DEPLOYMENT_METADATA_SHA256 mismatch" in rejected.stderr + assert f"expected={'0' * 64}" in rejected.stderr + assert "actual=" in rejected.stderr + assert not log.exists() + malicious_archive = tmp_path / "--evil.tar.gz" + malicious_archive.write_bytes(b"archive") + malicious = { + **environment, + "CHALLENGE113_ARCHIVE_PATH": str(malicious_archive), + "CHALLENGE113_ARCHIVE_SHA256": hashlib.sha256( + malicious_archive.read_bytes() + ).hexdigest(), + } + rejected = subprocess.run( + ["bash", str(ROOT / "scripts" / "prepare_apptainer_runtime.sh")], + env=malicious, + ) + assert rejected.returncode != 0 + assert not log.exists() + for acknowledgement in (None, "0"): + unacknowledged = dict(environment) + if acknowledgement is None: + unacknowledged.pop("CHALLENGE113_ACK_NETWORKED_PREPARE") + else: + unacknowledged["CHALLENGE113_ACK_NETWORKED_PREPARE"] = acknowledgement + rejected = subprocess.run( + ["bash", str(ROOT / "scripts" / "prepare_apptainer_runtime.sh")], + env=unacknowledged, + ) + assert rejected.returncode != 0 + assert not log.exists() + + subprocess.run( + ["bash", str(ROOT / "scripts" / "prepare_apptainer_runtime.sh")], + env=environment, + check=True, + ) + prepare_log = log.read_text() + assert prepare_log.count("uv sync --frozen") == 1 + assert prepare_log.count("--no-home") == 3 + prepare_commands = prepare_log.splitlines() + sync_command = next( + line for line in prepare_commands if "uv sync --frozen" in line + ) + assert "--cleanenv" in sync_command + assert "--net" not in sync_command and "--network" not in sync_command + assert sync_command.endswith("uv sync --frozen --group dev --project /workspace") + isolated_commands = [ + line for line in prepare_commands if line != sync_command + ] + assert len(isolated_commands) == 2 + assert all( + "--cleanenv --net --network none" in line for line in isolated_commands + ) + container_archive = f"/{archive.name}" + assert f"{archive}:{container_archive}:ro" in prepare_log + assert f"--archive {container_archive}" in prepare_log + + log.write_text("") + spool = tmp_path / "slurm-spool" + spool.mkdir() + spooled_pilot = shutil.copy( + ROOT / "scripts" / "slurm_pilot.sh", + spool / "pilot.sh", + ) + spooled_array = shutil.copy( + ROOT / "scripts" / "slurm_production_array.sh", + spool / "array.sh", + ) + for launcher in (spooled_pilot, spooled_array): + base_environment = dict(environment) + if launcher == spooled_array: + base_environment.update( + CHALLENGE113_ACK_PRODUCTION="1", + SLURM_ARRAY_TASK_ID="0", + ) + for invalid_deployment in ("relative/source", str(tmp_path / "missing")): + invalid = { + **base_environment, + "CHALLENGE113_DEPLOYMENT": invalid_deployment, + } + rejected = subprocess.run(["bash", launcher], env=invalid, cwd=spool) + assert rejected.returncode != 0 + assert log.read_text() == "" + gate = deployment / "scripts" / "apptainer_job_gate.sh" + real_gate = gate.with_name("apptainer_job_gate.real.sh") + gate.rename(real_gate) + gate.symlink_to(real_gate.name) + rejected = subprocess.run( + ["bash", spooled_pilot], + env=environment, + cwd=spool, + capture_output=True, + text=True, + ) + assert rejected.returncode != 0 + assert "deployment job gate must be a regular non-symlink file" in rejected.stderr + assert log.read_text() == "" + gate.unlink() + real_gate.rename(gate) + + for digest_name in ( + "CHALLENGE113_ARCHIVE_SHA256", + "CHALLENGE113_DEPLOYMENT_METADATA_SHA256", + "CHALLENGE113_PYPROJECT_SHA256", + "CHALLENGE113_SIF_SHA256", + "CHALLENGE113_UV_LOCK_SHA256", + ): + truncated = { + **environment, + digest_name: environment[digest_name][:-1], + } + rejected = subprocess.run( + ["bash", spooled_pilot], + env=truncated, + cwd=spool, + capture_output=True, + text=True, + ) + assert rejected.returncode != 0 + assert ( + f"{digest_name} must be exactly 64 lowercase hex" + in rejected.stderr + ) + assert log.read_text() == "" + + python_entry = deployment / ".venv" / "bin" / "python" + python_entry.unlink() + rejected = subprocess.run( + ["bash", spooled_pilot], + env=environment, + cwd=spool, + capture_output=True, + text=True, + ) + assert rejected.returncode != 0 + assert "runtime Python path entry is missing" in rejected.stderr + assert log.read_text() == "" + python_entry.symlink_to("/opt/container-python/bin/python") + + rejected = subprocess.run( + ["bash", spooled_pilot], + env=malicious, + cwd=spool, + ) + assert rejected.returncode != 0 + assert log.read_text() == "" + + subprocess.run( + ["bash", spooled_pilot], + env=environment, + cwd=spool, + check=True, + ) + pilot_log = log.read_text() + assert "uv sync" not in pilot_log + assert pilot_log.count("--no-home") == 5 + assert pilot_log.count("--cleanenv --net --network none") == 5 + assert "test -x /workspace/.venv/bin/python" in pilot_log + assert pilot_log.index("test -x /workspace/.venv/bin/python") < pilot_log.index( + "run.py trial" + ) + assert "--env JAX_ENABLE_X64=1 --env JAX_PLATFORMS=cpu" in pilot_log + assert f"{archive}:{container_archive}:ro" in pilot_log + assert f"--archive {container_archive}" in pilot_log + + log.write_text("") + array_environment = { + **environment, + "CHALLENGE113_ACK_PRODUCTION": "1", + "SLURM_ARRAY_TASK_ID": "0", + } + subprocess.run( + ["bash", spooled_array], + env=array_environment, + cwd=spool, + check=True, + ) + assert "run.py sweep" in log.read_text() + + +@pytest.mark.parametrize( + "name", + ["run_production.sh", "slurm_pilot.sh", "slurm_production_array.sh"], +) +def test_production_entrypoints_verify_deployment_metadata(name) -> None: + script = (ROOT / "scripts" / name).read_text() + inspected = script + if name.startswith("slurm_"): + inspected += (ROOT / "scripts" / "apptainer_job_gate.sh").read_text() + + assert "verify_deployment.py" in inspected + assert "CHALLENGE113_ARCHIVE_SHA256" in inspected + assert "CHALLENGE113_ARCHIVE_PATH" in inspected + assert "CHALLENGE113_DEPLOYMENT_METADATA" in inspected + assert "CHALLENGE113_DEPLOYMENT_METADATA_SHA256" in inspected + assert "CHALLENGE113_EVIDENCE_REVISION" in inspected + assert "^[0-9a-f]{64}$" in inspected + assert "mismatch: expected=" in inspected + assert "sbatch" not in inspected + if name.startswith("slurm_"): + assert "CHALLENGE113_SIF_PATH" in inspected + assert "CHALLENGE113_SIF_SHA256" in inspected + assert "CHALLENGE113_PYPROJECT_SHA256" in inspected + assert "CHALLENGE113_UV_LOCK_SHA256" in inspected + assert "exec" in inspected and "--no-home" in inspected + assert "--cleanenv" in inspected + assert "--net" in inspected and "--network" in inspected and "none" in inspected + assert "uv sync" not in script + + +def test_readme_clean_gate_supplies_every_required_variable() -> None: + readme = (ROOT / "README.md").read_text() + for name in ( + "CHALLENGE113_ACK_PRODUCTION", + "CHALLENGE113_ARCHIVE_PATH", + "CHALLENGE113_ARCHIVE_SHA256", + "CHALLENGE113_CHECK_ONLY", + "CHALLENGE113_DEPLOYMENT_METADATA", + "CHALLENGE113_DEPLOYMENT_METADATA_SHA256", + "CHALLENGE113_EVIDENCE_REVISION", + "CHALLENGE113_EXPECTED_REVISION", + "CHALLENGE113_JAX_PLATFORM", + "CHALLENGE113_PRODUCTION_OUTPUT", + ): + assert f"export {name}=" in readme + assert "dd16192953c130d738716238525760de73343e09" in readme diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/tests/test_experiments.py b/tracks/qcs/solutions/frustration-free/challenge-113/tests/test_experiments.py new file mode 100644 index 000000000..4b8c78aeb --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/tests/test_experiments.py @@ -0,0 +1,1407 @@ +from __future__ import annotations + +from dataclasses import replace +import copy +import json +from pathlib import Path +import subprocess +import sys +from types import SimpleNamespace + +import numpy as np +import pytest + +import qcontrol.artifacts as artifacts_module +import qcontrol.closed_loop as closed_loop_module +import qcontrol.experiments as experiments_module +from qcontrol.artifacts import ArtifactConflict, ArtifactStore +from qcontrol.closed_loop import make_model_hessian_space, make_search_space +from qcontrol.config import DeviceConfig, ExperimentConfig, SearchConfig, SystemConfig +from qcontrol.device import Observation +from qcontrol.experiments import ( + SweepStatus, + TrialResult, + TrialSpec, + default_sweep_configs, + generate_paired_trials, + run_sweep, + run_trial, + validate_sweep, +) +from qcontrol.pulses import PulseSpace +from qcontrol.systems import make_system, perturb_system + + +def _config( + method: str, + dimension: int, + *, + seed: int = 3, + kind: str = "development", +) -> ExperimentConfig: + return ExperimentConfig( + run_kind=kind, + system=SystemConfig("one_qubit", 3, 4.0), + device=DeviceConfig(gap=0.02, shots=1_000, perturbation_seed=7), + search=SearchConfig(method, dimension, 200 if kind == "development" else 2_000), + trial_seed=seed, + ) + + +def test_paired_methods_share_device_but_not_observation_stream() -> None: + specs = generate_paired_trials( + [ + _config("model_hessian", 1), + _config("random", 1), + _config("oracle", 1), + ] + ) + + assert len({spec.device_id for spec in specs}) == 1 + assert len({spec.observation_stream_id for spec in specs}) == len(specs) + assert len({spec.trial_id for spec in specs}) == len(specs) + + +def test_model_preparation_is_bitwise_shared_across_statistical_seeds() -> None: + experiments_module._MODEL_STATIC_CACHE.clear() + first = _config("model_hessian", 1, seed=0) + second = replace( + first, + device=replace(first.device, perturbation_seed=9), + trial_seed=11, + ) + + prepared_first = experiments_module.prepare_model(first) + prepared_second = experiments_module.prepare_model(second) + + assert prepared_first is prepared_second + np.testing.assert_array_equal(prepared_first.origin, prepared_second.origin) + np.testing.assert_array_equal( + prepared_first.landscape.leading_eigenvalues, + prepared_second.landscape.leading_eigenvalues, + ) + np.testing.assert_array_equal( + prepared_first.landscape.model_basis, + prepared_second.landscape.model_basis, + ) + truth_first = perturb_system( + prepared_first.model, + first.device.gap, + first.device.perturbation_seed, + ) + truth_second = perturb_system( + prepared_second.model, + second.device.gap, + second.device.perturbation_seed, + ) + assert not np.array_equal(truth_first.drift, truth_second.drift) + random_search = SearchConfig("random", 1, 200) + search_first = make_search_space( + random_search, + prepared_first.origin, + seed=first.trial_seed, + ) + search_second = make_search_space( + random_search, + prepared_second.origin, + seed=second.trial_seed, + ) + assert not np.array_equal(search_first.basis, search_second.basis) + first_spec = generate_paired_trials([first])[0] + second_spec = generate_paired_trials([second])[0] + assert first_spec.device_id != second_spec.device_id + assert first_spec.observation_stream_id != second_spec.observation_stream_id + + +@pytest.mark.integration +def test_two_qubit_trial_seed_zero_uses_accepted_model_seed_five() -> None: + experiments_module._MODEL_STATIC_CACHE.clear() + config = ExperimentConfig( + run_kind="production", + system=SystemConfig("two_qubit", 20, 4.0), + device=DeviceConfig(gap=0.05, shots=None, perturbation_seed=0), + search=SearchConfig("model_hessian", 4, 2_000), + trial_seed=0, + ) + + prepared = experiments_module.prepare_model(config) + + assert prepared.open_loop.loss <= 1e-8 + assert prepared.model_seed == 5 + + +def test_trial_ids_bind_every_pairing_dimension() -> None: + baseline = generate_paired_trials([_config("random", 1)])[0] + variants = [ + _config("random", 2), + replace(_config("random", 1), trial_seed=4), + replace( + _config("random", 1), + device=DeviceConfig(gap=0.05, shots=1_000, perturbation_seed=7), + ), + replace( + _config("random", 1), + device=DeviceConfig(gap=0.02, shots=10_000, perturbation_seed=7), + ), + replace( + _config("random", 1), + device=DeviceConfig(gap=0.02, shots=1_000, perturbation_seed=8), + ), + replace( + _config("random", 1), + system=SystemConfig("one_qubit", 4, 4.0), + ), + ] + assert all( + generate_paired_trials([variant])[0].trial_id != baseline.trial_id + for variant in variants + ) + + +@pytest.mark.parametrize("identity", ("", "../trial", "trial/path", "UPPER", "a b")) +def test_trial_spec_rejects_unsafe_identity_tokens(identity) -> None: + config = _config("random", 1) + with pytest.raises(ValueError, match="token"): + TrialSpec(identity, "device-1", "stream-1", config) + with pytest.raises(ValueError, match="token"): + TrialSpec("trial-1", identity, "stream-1", config) + with pytest.raises(ValueError, match="token"): + TrialSpec("trial-1", "device-1", identity, config) + + +def test_full_space_occurs_once_per_device_shot_seed_not_per_k() -> None: + configs = [] + for dimension in (1, 2, 3): + configs.extend( + [ + _config("full", dimension), + _config("model_hessian", dimension), + _config("random", dimension), + ] + ) + + specs = generate_paired_trials(configs) + + assert sum(spec.config.search.method == "full" for spec in specs) == 1 + full = next(spec for spec in specs if spec.config.search.method == "full") + assert full.config.search.dimension == full.config.system.parameter_count + + +def test_full_space_landscape_count_stays_below_parameter_count() -> None: + full = generate_paired_trials([_config("full", 1)])[0].config + + assert experiments_module._landscape_leading_count(full, 2) == 5 + + +def test_development_and_production_cannot_mix() -> None: + with pytest.raises(ValueError, match="run kinds"): + generate_paired_trials( + [_config("random", 1), _config("random", 1, kind="production")] + ) + + +def _result(spec, execution: int) -> TrialResult: + derived = { + "exact_infidelity": { + "best_successful_audited_infidelity": None, + "cumulative_best_by_optimizer_query": [0.5, 0.5], + "initial_infidelity": 0.5, + }, + "geometry": { + "model_effective_ranks": [1, 1, 1], + "model_top_subspace_sha256": "3" * 64, + "principal_angles_radians": [0.0] * spec.config.search.dimension, + "rank_thresholds": [1e-6, 1e-8, 1e-10], + "signed_leading_eigenvalue_gaps": [0.0] + * spec.config.search.dimension, + "truth_effective_ranks": [1, 1, 1], + "truth_top_subspace_sha256": "4" * 64, + }, + "restricted_noiseless_optimization": { + "attained_infidelity_upper_bound": 0.5, + "attained_infidelity_source": "restricted_solver", + "best_successful_audited_exact_infidelity": None, + "cached_solver_attained_infidelity_upper_bound": 0.5, + "cached_solver_starting_infidelity_upper_bound": 0.5, + "certified": True, + "consistency_tolerance": 1e-10, + "gradient_tolerance": 1e-9, + "max_evaluations": 1_000, + "max_iterations": 100, + "nfev": 1, + "nit": 0, + "solver": "L-BFGS-B", + "solver_message_code": "convergence", + "solver_output_finite": True, + "solver_status": 0, + "solver_success": True, + "initial_exact_infidelity": 0.5, + "termination": "converged", + }, + } + result_payload = { + "schema_version": 3 if spec.config.run_kind == "production" else 2, + "search": { + "basis_sha256": "1" * 64, + "dimension": spec.config.search.dimension, + "method": spec.config.search.method, + "origin_sha256": "2" * 64, + "source_basis_sha256": "5" * 64, + }, + "best_pulse": [0.0] * 6, + "best_observation": None, + "certified": False, + "evaluations": 2, + "budget": spec.config.search.budget, + "budget_exhausted": False, + "stop_reason": "optimizer_stopped", + "observations": [], + "validation_attempts": [], + "first_certified_query": None, + "provisional_crossings": [], + "validation_result": None, + } + if spec.config.run_kind == "production": + result_payload["derived_metrics"] = derived + return TrialResult( + trial_id=spec.trial_id, + device_id=spec.device_id, + observation_stream_id=spec.observation_stream_id, + config=spec.config.canonical_dict(), + result=result_payload, + ledger={ + "optimizer_queries": 2, + "optimizer_shots": 2_000, + "validation_queries": 0, + "validation_shots": 0, + "total_queries": 2, + "total_shots": 2_000, + }, + attempts=( + { + "attempt_index": index, + "charged_shots": 1_000, + "error_category": "sampling_failure", + "estimate": None, + "observation_seed": index, + "optimizer_query_index": index, + "requested_shots": 1_000, + "seed_digest": f"{index:064x}", + "status": "failed", + "validation": False, + } + for index in (1, 2) + ), + execution=execution, + ) + + +def _result_with_two_validations(spec) -> TrialResult: + def observation( + attempt_index: int, + optimizer_query_index: int, + estimate: float, + *, + validation: bool, + shots: int, + ) -> dict[str, object]: + return { + "attempt_index": attempt_index, + "estimate": estimate, + "observation_seed": attempt_index, + "optimizer_query_index": optimizer_query_index, + "seed_digest": f"{attempt_index:064x}", + "shots": shots, + "validation": validation, + } + + optimizer_one = observation(1, 1, 0.9991, validation=False, shots=1_000) + validation_one = observation(2, 1, 0.998, validation=True, shots=100_000) + optimizer_two = observation(3, 2, 0.9992, validation=False, shots=1_000) + validation_two = observation(4, 2, 1.0, validation=True, shots=100_000) + attempts = tuple( + { + "attempt_index": item["attempt_index"], + "charged_shots": item["shots"], + "error_category": None, + "estimate": item["estimate"], + "observation_seed": item["observation_seed"], + "optimizer_query_index": item["optimizer_query_index"], + "requested_shots": item["shots"], + "seed_digest": item["seed_digest"], + "status": "succeeded", + "validation": item["validation"], + } + for item in ( + optimizer_one, + validation_one, + optimizer_two, + validation_two, + ) + ) + pulse = [0.0] * 6 + validation_attempts = [ + { + "best_observation": optimizer_one, + "certified": False, + "device_attempt_index": 2, + "failure_category": None, + "optimizer_query_index": 1, + "pulse": pulse, + "status": "rejected", + "validation_observation": validation_one, + }, + { + "best_observation": optimizer_two, + "certified": True, + "device_attempt_index": 4, + "failure_category": None, + "optimizer_query_index": 2, + "pulse": pulse, + "status": "certified", + "validation_observation": validation_two, + }, + ] + return TrialResult( + trial_id=spec.trial_id, + device_id=spec.device_id, + observation_stream_id=spec.observation_stream_id, + config=spec.config.canonical_dict(), + result={ + "best_observation": optimizer_two, + "best_pulse": pulse, + "budget": 200, + "budget_exhausted": False, + "certified": True, + "evaluations": 2, + "first_certified_query": 2, + "observations": [optimizer_one, optimizer_two], + "provisional_crossings": [1, 2], + "schema_version": 2, + "search": { + "basis_sha256": "1" * 64, + "dimension": 1, + "method": "random", + "origin_sha256": "2" * 64, + "source_basis_sha256": "5" * 64, + }, + "stop_reason": "certified", + "validation_attempts": validation_attempts, + "validation_result": validation_two, + }, + ledger={ + "optimizer_queries": 2, + "optimizer_shots": 2_000, + "validation_queries": 2, + "validation_shots": 200_000, + "total_queries": 4, + "total_shots": 202_000, + }, + attempts=attempts, + execution=1, + ) + + +def test_interrupted_sweep_resumes_without_duplicate_ledgers(tmp_path) -> None: + specs = generate_paired_trials( + [ + _config("model_hessian", 1, seed=seed) + for seed in range(5) + ] + ) + store = ArtifactStore(tmp_path) + calls: list[str] = [] + + def execute(spec) -> TrialResult: + calls.append(spec.trial_id) + return _result(spec, len(calls)) + + first = run_sweep(specs, store, executor=execute, stop_after=2) + before = store.trial_hashes() + assert first == SweepStatus(expected=5, completed=2, pending=3) + + calls.clear() + resumed = run_sweep(specs, store, executor=execute) + + assert resumed == SweepStatus(expected=5, completed=5, pending=0) + assert len(calls) == 3 + assert { + trial_id: store.trial_hashes()[trial_id] for trial_id in before + } == before + ledgers = [ + json.loads(path.read_text())["ledger"] + for path in sorted((tmp_path / "trials").glob("*.json")) + ] + assert sum(ledger["optimizer_queries"] for ledger in ledgers) == 10 + + +def test_sweep_shards_bind_full_plan_and_execute_deterministic_subset(tmp_path) -> None: + specs = generate_paired_trials( + [_config("model_hessian", 1, seed=seed) for seed in range(5)] + ) + store = ArtifactStore(tmp_path) + calls: list[str] = [] + + status = run_sweep( + specs, + store, + executor=lambda spec: calls.append(spec.trial_id) or _result(spec, 1), + shard_index=1, + shard_count=3, + ) + + assert calls == [specs[1].trial_id, specs[4].trial_id] + assert status == SweepStatus(expected=5, completed=2, pending=3) + assert json.loads((tmp_path / "plan.json").read_text())["trials"] == [ + spec.canonical_dict() for spec in specs + ] + + +@pytest.mark.parametrize("shard_count", [1, 7, 32, 9_500]) +def test_production_shards_partition_canonical_plan_exactly(shard_count) -> None: + specs = generate_paired_trials(default_sweep_configs("production")) + canonical_ids = tuple(spec.trial_id for spec in specs) + shards = [ + experiments_module.shard_specs(specs, index, shard_count) + for index in range(shard_count) + ] + + flattened = [spec.trial_id for shard in shards for spec in shard] + assert len(flattened) == len(canonical_ids) == 9_500 + assert set(flattened) == set(canonical_ids) + assert len(flattened) == len(set(flattened)) + assert all( + tuple(spec.trial_id for spec in shard) + == canonical_ids[index::shard_count] + for index, shard in enumerate(shards) + ) + + +def test_sharding_preserves_full_comparator_pairing() -> None: + specs = generate_paired_trials(default_sweep_configs("production")) + by_device = {} + for spec in specs: + by_device.setdefault(spec.device_id, []).append(spec) + + shards = [ + experiments_module.shard_specs(specs, index, 32) + for index in range(32) + ] + reconstructed = {spec.trial_id: spec for shard in shards for spec in shard} + + assert set(reconstructed) == {spec.trial_id for spec in specs} + assert all( + sum(spec.config.search.method == "full" for spec in group) == 1 + for group in by_device.values() + ) + + +@pytest.mark.parametrize( + ("shard_index", "shard_count"), + [(-1, 2), (2, 2), (0, 0), (True, 2), (0, True)], +) +def test_sweep_rejects_invalid_shard_coordinates( + tmp_path, + shard_index, + shard_count, +) -> None: + specs = generate_paired_trials([_config("random", 1)]) + + with pytest.raises(ValueError, match="shard"): + run_sweep( + specs, + ArtifactStore(tmp_path), + executor=lambda spec: _result(spec, 1), + shard_index=shard_index, + shard_count=shard_count, + ) + + +def test_resume_fails_closed_on_plan_or_claim_mismatch(tmp_path) -> None: + specs = generate_paired_trials([_config("random", 1)]) + store = ArtifactStore(tmp_path) + run_sweep(specs, store, executor=lambda spec: _result(spec, 1)) + + changed = generate_paired_trials([_config("random", 2)]) + with pytest.raises(ArtifactConflict, match="plan"): + run_sweep(changed, store, executor=lambda spec: _result(spec, 2)) + + +def test_validate_checks_coverage_hashes_ledgers_and_unexpected_files(tmp_path) -> None: + specs = generate_paired_trials([_config("random", 1)]) + store = ArtifactStore(tmp_path) + run_sweep(specs, store, executor=lambda spec: _result(spec, 1)) + + report = validate_sweep(specs, store) + assert report.valid + assert report.status == SweepStatus(expected=1, completed=1, pending=0) + + (tmp_path / "surprise.txt").write_text("unexpected") + report = validate_sweep(specs, store) + assert not report.valid + assert any("unexpected" in error for error in report.errors) + + +def test_partial_production_validation_fails_but_status_succeeds(tmp_path) -> None: + specs = generate_paired_trials( + [_config("random", 1, seed=1, kind="production"), _config("random", 1, seed=2, kind="production")] + ) + store = ArtifactStore(tmp_path) + run_sweep(specs, store, executor=lambda spec: _result(spec, 1), stop_after=1) + + report = validate_sweep(specs, store) + assert not report.valid + assert report.status.pending == 1 + + +def test_trial_artifact_contains_no_private_truth(tmp_path) -> None: + spec = generate_paired_trials([_config("random", 1)])[0] + store = ArtifactStore(tmp_path) + run_sweep([spec], store, executor=lambda item: _result(item, 1)) + + artifact = (tmp_path / "trials" / f"{spec.trial_id}.json").read_text() + assert "truth" not in artifact.lower() + assert "hamiltonian" not in artifact.lower() + + +def test_cli_exposes_strict_modes_and_status_smoke(tmp_path) -> None: + root = Path(__file__).parents[1] + help_result = subprocess.run( + [sys.executable, "run.py", "--help"], + cwd=root, + check=False, + capture_output=True, + text=True, + ) + assert help_result.returncode == 0 + assert all( + command in help_result.stdout + for command in ("geometry", "trial", "sweep", "validate", "status") + ) + + status_result = subprocess.run( + [sys.executable, "run.py", "status", "--output", str(tmp_path)], + cwd=root, + check=False, + capture_output=True, + text=True, + ) + assert status_result.returncode == 0 + assert json.loads(status_result.stdout)["completed"] == 0 + + invalid = subprocess.run( + [sys.executable, "run.py", "sweep", "--kind", "debug", "--output", str(tmp_path)], + cwd=root, + check=False, + capture_output=True, + text=True, + ) + assert invalid.returncode != 0 + + shard_help = subprocess.run( + [sys.executable, "run.py", "sweep", "--help"], + cwd=root, + check=False, + capture_output=True, + text=True, + ) + assert shard_help.returncode == 0 + assert "--shard-index" in shard_help.stdout + assert "--shard-count" in shard_help.stdout + + trial_help = subprocess.run( + [sys.executable, "run.py", "trial", "--help"], + cwd=root, + check=False, + capture_output=True, + text=True, + ) + assert trial_help.returncode == 0 + assert "--model-seed" in trial_help.stdout + + +def test_crash_after_trial_publish_adopts_without_rerunning_physics( + tmp_path, + monkeypatch, +) -> None: + spec = generate_paired_trials([_config("random", 1)])[0] + store = ArtifactStore(tmp_path) + calls = 0 + real_update = store._update_index_locked + + def execute(item) -> TrialResult: + nonlocal calls + calls += 1 + return _result(item, calls) + + def crash_before_index(*_args, **_kwargs) -> None: + raise KeyboardInterrupt() + + monkeypatch.setattr(store, "_update_index_locked", crash_before_index) + with pytest.raises(KeyboardInterrupt): + run_sweep([spec], store, executor=execute) + assert calls == 1 + assert (tmp_path / "trials" / f"{spec.trial_id}.json").is_file() + assert spec.trial_id not in store.trial_hashes() + + monkeypatch.setattr(store, "_update_index_locked", real_update) + status = run_sweep([spec], store, executor=execute) + + assert status == SweepStatus(expected=1, completed=1, pending=0) + assert calls == 1 + assert spec.trial_id in store.trial_hashes() + + +@pytest.mark.parametrize( + ("variant", "message"), + ( + ("whitespace", "noncanonical"), + ("alternate-number", "noncanonical"), + ("bom", "noncanonical"), + ("trailing", "noncanonical"), + ("duplicate-known", "duplicate"), + ("duplicate-private", "duplicate"), + ), +) +def test_orphan_adoption_rejects_noncanonical_or_duplicate_json( + tmp_path, + variant, + message, +) -> None: + spec = generate_paired_trials([_config("random", 1)])[0] + store = ArtifactStore(tmp_path) + run_sweep([spec], store, executor=lambda item: _result(item, 1), stop_after=0) + payload = _result(spec, 1).canonical_dict() + canonical = artifacts_module.canonical_json_bytes(payload) + if variant == "whitespace": + raw = canonical.replace(b'{"attempts"', b'{ "attempts"', 1) + elif variant == "alternate-number": + raw = canonical.replace(b'"charged_shots":1000', b'"charged_shots":1e3', 1) + elif variant == "bom": + raw = b"\xef\xbb\xbf" + canonical + elif variant == "trailing": + raw = canonical + b"trailing" + elif variant == "duplicate-known": + raw = canonical[:-2] + ( + f',"trial_id":"{spec.trial_id}"'.encode("ascii") + ) + b"}\n" + else: + raw = ( + b'{"private_basis":[1],"private_basis":[2],' + + canonical[1:] + ) + trial_path = tmp_path / "trials" / f"{spec.trial_id}.json" + trial_path.parent.mkdir(exist_ok=True) + trial_path.write_bytes(raw) + calls = 0 + + def execute(item) -> TrialResult: + nonlocal calls + calls += 1 + return _result(item, calls) + + with pytest.raises(ArtifactConflict, match=message): + run_sweep([spec], store, executor=execute) + + assert calls == 0 + assert trial_path.read_bytes() == raw + assert spec.trial_id not in store.trial_hashes() + + +def test_source_change_during_trial_aborts_before_publication( + tmp_path, + monkeypatch, +) -> None: + spec = generate_paired_trials([_config("random", 1)])[0] + store = ArtifactStore(tmp_path) + calls = 0 + real_collect = artifacts_module._collect_provenance + collections = 0 + + def changing_provenance(payload): + nonlocal collections + collections += 1 + provenance = real_collect(payload) + if collections > 1: + provenance["source_hashes"] = { + **provenance["source_hashes"], + "src/qcontrol/changed.py": "0" * 64, + } + return provenance + + monkeypatch.setattr( + artifacts_module, + "_collect_provenance", + changing_provenance, + ) + + def execute(item) -> TrialResult: + nonlocal calls + calls += 1 + return _result(item, calls) + + with pytest.raises(ArtifactConflict, match="provenance"): + run_sweep([spec], store, executor=execute) + + assert calls == 1 + assert not (tmp_path / "trials" / f"{spec.trial_id}.json").exists() + + +def _walk_keys(value): + if isinstance(value, dict): + for key, item in value.items(): + yield key + yield from _walk_keys(item) + elif isinstance(value, list): + for item in value: + yield from _walk_keys(item) + + +@pytest.mark.parametrize("method", ("model_hessian", "random", "oracle")) +def test_trial_artifact_structurally_excludes_numeric_search_bases( + tmp_path, + method, +) -> None: + spec = generate_paired_trials([_config(method, 1)])[0] + store = ArtifactStore(tmp_path) + run_sweep([spec], store, executor=lambda item: _result(item, 1)) + payload = json.loads( + (tmp_path / "trials" / f"{spec.trial_id}.json").read_text() + ) + + keys = set(_walk_keys(payload)) + assert "basis" not in keys + assert "origin" not in keys + assert "drift_direction" not in keys + assert "control_gain_deltas" not in keys + assert "unmodeled_direction" not in keys + assert payload["result"]["search"] == { + "basis_sha256": "1" * 64, + "dimension": 1, + "method": method, + "origin_sha256": "2" * 64, + "source_basis_sha256": "5" * 64, + } + + +@pytest.mark.integration +def test_real_trial_uses_public_search_identity_without_numeric_basis( + tmp_path, + monkeypatch, +) -> None: + config = ExperimentConfig( + run_kind="development", + system=SystemConfig("one_qubit", 6, 4.0), + device=DeviceConfig(gap=0.0, shots=None, perturbation_seed=5), + search=SearchConfig("random", 3, 200), + trial_seed=5, + ) + + geometry_calls = 0 + restricted_calls = 0 + real_geometry = experiments_module.compute_geometry_diagnostics + real_restricted = experiments_module.optimize_restricted_noiseless_upper_bound + + def counted_geometry(*args, **kwargs): + nonlocal geometry_calls + geometry_calls += 1 + return real_geometry(*args, **kwargs) + + def counted_restricted(*args, **kwargs): + nonlocal restricted_calls + restricted_calls += 1 + return real_restricted(*args, **kwargs) + + experiments_module._DERIVED_STATIC_CACHE.clear() + monkeypatch.setattr( + experiments_module, + "compute_geometry_diagnostics", + counted_geometry, + ) + monkeypatch.setattr( + experiments_module, + "optimize_restricted_noiseless_upper_bound", + counted_restricted, + ) + result = run_trial(config, ArtifactStore(tmp_path)) + run_trial(config, ArtifactStore(tmp_path / "replay")) + payload = result.canonical_dict() + + assert geometry_calls == restricted_calls == 1 + + assert "space" not in payload["result"] + assert "basis" not in set(_walk_keys(payload)) + assert payload["result"]["search"]["method"] == "random" + assert len(payload["result"]["search"]["basis_sha256"]) == 64 + assert len(payload["attempts"]) == payload["ledger"]["total_queries"] + assert payload["schema_version"] == 3 + assert payload["result"]["schema_version"] == 3 + + derived = payload["result"]["derived_metrics"] + exact = derived["exact_infidelity"] + trajectory = exact["cumulative_best_by_optimizer_query"] + assert len(trajectory) == payload["ledger"]["optimizer_queries"] + assert all(np.isfinite([exact["initial_infidelity"], *trajectory])) + assert all(current <= previous for previous, current in zip(trajectory, trajectory[1:])) + + restricted = derived["restricted_noiseless_optimization"] + assert restricted["attained_infidelity_upper_bound"] <= min( + exact["initial_infidelity"], + *trajectory, + ) + restricted["consistency_tolerance"] + assert restricted["max_iterations"] > 0 + assert restricted["gradient_tolerance"] > 0.0 + + geometry = derived["geometry"] + assert geometry["rank_thresholds"] == [1e-06, 1e-08, 1e-10] + assert geometry["model_effective_ranks"] == geometry["truth_effective_ranks"] + assert np.max(np.abs(geometry["signed_leading_eigenvalue_gaps"])) < 1e-10 + assert np.max(np.abs(geometry["principal_angles_radians"])) < 1e-7 + assert all(np.isfinite(value) for value in geometry["principal_angles_radians"]) + + private_keys = { + "basis", + "origin", + "truth", + "hamiltonian", + "drift_direction", + "control_gain_deltas", + "unmodeled_direction", + "pulse_history", + "pulses", + } + assert not (private_keys & set(_walk_keys(payload))) + + before = dict(payload["ledger"]) + TrialResult.from_canonical_dict(payload) + assert payload["ledger"] == before + + +def test_derived_caches_follow_scientific_dependencies(monkeypatch) -> None: + base = _config("model_hessian", 1, seed=3) + shot_variant = replace( + base, + device=replace(base.device, shots=10_000), + ) + k_variant = replace( + shot_variant, + search=replace(shot_variant.search, dimension=2), + ) + device_variant = replace( + base, + device=replace(base.device, gap=0.05), + ) + model = make_system(base.system) + pulse_space = PulseSpace.from_system(model, base.system.segments) + origin = np.zeros(pulse_space.parameter_count) + basis = np.eye(pulse_space.parameter_count) + spaces = { + 1: make_model_hessian_space(origin, basis, dimension=1), + 2: make_model_hessian_space(origin, basis, dimension=2), + } + observation = Observation(0.5, 1_000, 1, False, 1) + audited = ((origin.copy(), observation),) + counts = {"exact": 0, "geometry": 0, "restricted": 0} + real_exact = experiments_module.cumulative_best_exact_infidelity + real_geometry = experiments_module.compute_geometry_diagnostics + real_restricted = experiments_module.optimize_restricted_noiseless_upper_bound + + def counted_exact(*args, **kwargs): + counts["exact"] += 1 + return real_exact(*args, **kwargs) + + def counted_geometry(*args, **kwargs): + counts["geometry"] += 1 + return real_geometry(*args, **kwargs) + + def counted_restricted(*args, **kwargs): + counts["restricted"] += 1 + return real_restricted(*args, **kwargs) + + experiments_module._DERIVED_STATIC_CACHE.clear() + monkeypatch.setattr( + experiments_module, + "cumulative_best_exact_infidelity", + counted_exact, + ) + monkeypatch.setattr( + experiments_module, + "compute_geometry_diagnostics", + counted_geometry, + ) + monkeypatch.setattr( + experiments_module, + "optimize_restricted_noiseless_upper_bound", + counted_restricted, + ) + base_restricted = None + for config in (base, shot_variant, k_variant, device_variant): + truth = perturb_system( + model, + config.device.gap, + config.device.perturbation_seed, + ) + _, _, restricted = experiments_module._cached_derived_metrics( + config, + model, + truth, + pulse_space, + spaces[config.search.dimension], + audited, + ) + if config is base: + base_restricted = restricted + + altered_audit = ((origin.copy() + 0.1, observation),) + truth = perturb_system(model, base.device.gap, base.device.perturbation_seed) + _, _, altered_restricted = experiments_module._cached_derived_metrics( + base, + model, + truth, + pulse_space, + spaces[base.search.dimension], + altered_audit, + ) + + assert counts == {"exact": 3, "geometry": 2, "restricted": 3} + assert altered_restricted is base_restricted + + +def test_old_production_trial_schema_and_orphan_are_rejected(tmp_path) -> None: + spec = generate_paired_trials([_config("random", 1, kind="production")])[0] + legacy = _result(spec, 1).canonical_dict() + legacy["schema_version"] = 2 + legacy["result"]["schema_version"] = 2 + del legacy["result"]["derived_metrics"] + + with pytest.raises(ValueError, match="production.*schema|schema.*production"): + TrialResult.from_canonical_dict(legacy) + + store = ArtifactStore(tmp_path) + trial_path = tmp_path / "trials" / f"{spec.trial_id}.json" + trial_path.parent.mkdir() + trial_path.write_bytes(artifacts_module.canonical_json_bytes(legacy)) + with pytest.raises(ArtifactConflict, match="invalid strict schema"): + run_sweep([spec], store, executor=lambda _: _result(spec, 2)) + + +def test_audited_candidate_bound_serializes_with_cached_solver_provenance() -> None: + spec = generate_paired_trials([_config("random", 1, kind="production")])[0] + payload = _result(spec, 1).canonical_dict() + derived = payload["result"]["derived_metrics"] + derived["exact_infidelity"].update( + { + "best_successful_audited_infidelity": 0.1, + "cumulative_best_by_optimizer_query": [0.5, 0.1], + } + ) + derived["restricted_noiseless_optimization"].update( + { + "attained_infidelity_upper_bound": 0.1, + "attained_infidelity_source": "audited_candidate", + "best_successful_audited_exact_infidelity": 0.1, + "cached_solver_attained_infidelity_upper_bound": 0.4, + } + ) + payload["attempts"][1].update( + { + "error_category": None, + "estimate": 0.9, + "status": "succeeded", + } + ) + payload["result"]["observations"] = [ + { + "attempt_index": 2, + "estimate": 0.9, + "observation_seed": 2, + "optimizer_query_index": 2, + "seed_digest": f"{2:064x}", + "shots": 1_000, + "validation": False, + } + ] + + replayed = TrialResult.from_canonical_dict(payload) + + restricted = replayed.result["derived_metrics"][ + "restricted_noiseless_optimization" + ] + assert restricted["attained_infidelity_source"] == "audited_candidate" + assert restricted["cached_solver_attained_infidelity_upper_bound"] == 0.4 + + +@pytest.mark.parametrize( + "mutation", + ( + { + "certified": False, + "solver_success": False, + "termination": "solver_failure", + }, + { + "certified": False, + "solver_message_code": "numerical_failure", + "solver_output_finite": True, + "solver_status": 2, + "solver_success": False, + "termination": "numerical_failure", + }, + { + "certified": False, + "nit": 0, + "solver_message_code": "iteration_limit", + "solver_status": 1, + "solver_success": False, + "termination": "iteration_limit", + }, + {"certified": False, "termination": "solver_failure"}, + { + "certified": False, + "solver_message_code": "line_search_failure", + "solver_status": 1, + "solver_success": False, + "termination": "line_search_failure", + }, + ), +) +def test_restricted_termination_rejects_tampered_raw_facts(mutation) -> None: + spec = generate_paired_trials([_config("random", 1, kind="production")])[0] + payload = _result(spec, 1).canonical_dict() + restricted = payload["result"]["derived_metrics"][ + "restricted_noiseless_optimization" + ] + restricted.update(mutation) + with pytest.raises(ValueError, match="solver|termination|restricted"): + TrialResult.from_canonical_dict(payload) + + +def _numerical_failure_payload() -> dict[str, object]: + spec = generate_paired_trials([_config("random", 1, kind="production")])[0] + payload = _result(spec, 1).canonical_dict() + restricted = payload["result"]["derived_metrics"][ + "restricted_noiseless_optimization" + ] + restricted.update( + { + "attained_infidelity_source": "initial_origin", + "certified": False, + "solver_message_code": "numerical_failure", + "solver_output_finite": False, + "solver_status": 2, + "solver_success": False, + "termination": "numerical_failure", + } + ) + return payload + + +def test_nonfinite_solver_cannot_claim_cached_improvement() -> None: + payload = _numerical_failure_payload() + restricted = payload["result"]["derived_metrics"][ + "restricted_noiseless_optimization" + ] + restricted["cached_solver_attained_infidelity_upper_bound"] = 0.0 + restricted["attained_infidelity_upper_bound"] = 0.0 + restricted["attained_infidelity_source"] = "restricted_solver" + + with pytest.raises(ValueError, match="restricted attained upper bound"): + TrialResult.from_canonical_dict(payload) + + +def test_cached_solver_start_must_match_exact_initial_origin() -> None: + spec = generate_paired_trials([_config("random", 1, kind="production")])[0] + payload = _result(spec, 1).canonical_dict() + restricted = payload["result"]["derived_metrics"][ + "restricted_noiseless_optimization" + ] + restricted["cached_solver_starting_infidelity_upper_bound"] = 0.4 + restricted["cached_solver_attained_infidelity_upper_bound"] = 0.4 + restricted["attained_infidelity_upper_bound"] = 0.4 + restricted["attained_infidelity_source"] = "restricted_solver" + + with pytest.raises(ValueError, match="restricted attained upper bound"): + TrialResult.from_canonical_dict(payload) + + +def test_nonfinite_solver_cannot_be_selected_as_final_source() -> None: + payload = _numerical_failure_payload() + restricted = payload["result"]["derived_metrics"][ + "restricted_noiseless_optimization" + ] + restricted["attained_infidelity_source"] = "restricted_solver" + + with pytest.raises(ValueError, match="restricted attained upper bound"): + TrialResult.from_canonical_dict(payload) + + +def test_valid_numerical_failure_falls_back_to_initial_origin() -> None: + payload = _numerical_failure_payload() + + replayed = TrialResult.from_canonical_dict(payload) + + restricted = replayed.result["derived_metrics"][ + "restricted_noiseless_optimization" + ] + assert restricted["cached_solver_attained_infidelity_upper_bound"] == 0.5 + assert restricted["attained_infidelity_upper_bound"] == 0.5 + assert restricted["attained_infidelity_source"] == "initial_origin" + + +def test_production_matrix_has_exact_design_coverage() -> None: + specs = generate_paired_trials(default_sweep_configs("production")) + + assert len(specs) == 9_500 + by_system = { + name: [spec for spec in specs if spec.config.system.name == name] + for name in ("one_qubit", "two_qubit") + } + assert len(by_system["two_qubit"]) == 5_700 + assert len(by_system["one_qubit"]) == 3_800 + + for name, dimensions, shots in ( + ("two_qubit", {5, 10, 15, 20, 30, 80}, {None, 1_000, 10_000}), + ("one_qubit", {1, 2, 3, 4, 6, 24}, {None, 1_000}), + ): + subset = by_system[name] + assert {spec.config.device.gap for spec in subset} == { + 0.0, + 0.02, + 0.05, + 0.10, + 0.20, + } + assert {spec.config.device.shots for spec in subset} == shots + assert {spec.config.trial_seed for spec in subset} == set(range(20)) + assert { + spec.config.search.dimension + for spec in subset + if spec.config.search.method != "full" + } == dimensions + full = [spec for spec in subset if spec.config.search.method == "full"] + assert len(full) == 5 * len(shots) * 20 + + +@pytest.mark.integration +def test_exact_failed_dimension_one_trial_uses_safe_cma_options() -> None: + trial_id = "trial-20e15a66415a0832b8c82051" + spec = next( + spec + for spec in generate_paired_trials(default_sweep_configs("production")) + if spec.trial_id == trial_id + ) + assert spec.config.search == SearchConfig("random", 1, 2_000) + assert spec.config.device == DeviceConfig(0.2, 1_000, 8) + assert spec.config.trial_seed == 8 + + prepared = experiments_module.prepare_model(spec.config) + space = make_search_space( + spec.config.search, + prepared.origin, + model_basis=prepared.landscape.model_basis, + seed=spec.config.trial_seed, + ) + + options = closed_loop_module._cma_options(space, spec.config.trial_seed) + assert np.isinf(options["maxstd_boundrange"]) + + +@pytest.mark.integration +def test_exact_failed_dimension_one_trial_publishes_and_resumes(tmp_path) -> None: + trial_id = "trial-20e15a66415a0832b8c82051" + spec = next( + spec + for spec in generate_paired_trials(default_sweep_configs("production")) + if spec.trial_id == trial_id + ) + store = ArtifactStore(tmp_path) + + first = run_sweep([spec], store) + digest = store.trial_hashes()[trial_id] + resumed = run_sweep( + [spec], + store, + executor=lambda unused: pytest.fail(f"reran completed trial: {unused}"), + ) + payload = store.read_json(f"trials/{trial_id}.json") + result = TrialResult.from_canonical_dict(payload) + report = validate_sweep([spec], store) + + assert first == resumed == SweepStatus(1, 1, 0) + assert store.trial_hashes()[trial_id] == digest + assert result.trial_id == trial_id + assert len(result.attempts) == result.ledger["total_queries"] + assert result.result["evaluations"] == result.ledger["optimizer_queries"] + assert report.valid + + +@pytest.mark.integration +def test_every_canonical_production_search_space_constructs() -> None: + specs = generate_paired_trials(default_sweep_configs("production")) + representatives = {} + for spec in specs: + config = spec.config + key = ( + config.system.name, + config.search.method, + config.search.dimension, + config.trial_seed, + ) + representatives.setdefault(key, config) + prepared = { + name: experiments_module.prepare_model( + next( + config + for config in representatives.values() + if config.system.name == name + ) + ) + for name in ("one_qubit", "two_qubit") + } + + for config in representatives.values(): + model = prepared[config.system.name] + space = make_search_space( + config.search, + model.origin, + model_basis=model.landscape.model_basis, + oracle_basis=model.landscape.model_basis, + seed=config.trial_seed, + ) + assert space.dimension == config.search.dimension + + +@pytest.mark.integration +def test_all_dimension_one_production_configs_execute_bounded_smoke() -> None: + specs = [ + spec + for spec in generate_paired_trials(default_sweep_configs("production")) + if spec.config.search.dimension == 1 + ] + assert len(specs) == 600 + prepared = experiments_module.prepare_model(specs[0].config) + + class SmokeDevice: + def __init__(self, shots: int | None) -> None: + self.shots = shots or 0 + self.ledger = SimpleNamespace( + optimizer_queries=0, + optimizer_shots=0, + validation_queries=0, + validation_shots=0, + ) + + def query(self, pulse) -> Observation: + del pulse + self.ledger.optimizer_queries += 1 + self.ledger.optimizer_shots += self.shots + index = self.ledger.optimizer_queries + return Observation(0.5, self.shots, index, False, index) + + def validate(self, pulse, shots=100_000): + raise AssertionError((pulse, shots)) + + def certifies(self, observation, threshold=0.999) -> bool: + del observation, threshold + return False + + for spec in specs: + config = spec.config + space = make_search_space( + config.search, + prepared.origin, + model_basis=prepared.landscape.model_basis, + oracle_basis=prepared.landscape.model_basis, + seed=config.trial_seed, + ) + device = SmokeDevice(config.device.shots) + audited = [] + result = closed_loop_module.run_closed_loop( + device, + space, + budget=2, + seed=config.trial_seed, + audit_sink=lambda pulse, observation: audited.append( + (pulse.copy(), observation) + ), + ) + + assert result.evaluations == device.ledger.optimizer_queries == 2 + assert device.ledger.optimizer_shots == 2 * (config.device.shots or 0) + assert all(np.all(np.abs(pulse) <= 1.0) for pulse, _ in audited) + + +@pytest.mark.parametrize("invalid", (True, 1.0, "1")) +def test_trial_result_rejects_coerced_ledger_integers(invalid) -> None: + spec = generate_paired_trials([_config("random", 1)])[0] + payload = _result(spec, 1).canonical_dict() + payload["ledger"]["optimizer_queries"] = invalid + + with pytest.raises(ValueError, match="invalid trial-result"): + TrialResult.from_canonical_dict(payload) + + +def test_trial_result_rejects_missing_extra_and_duplicate_attempts() -> None: + spec = generate_paired_trials([_config("random", 1)])[0] + canonical = _result(spec, 1).canonical_dict() + mutations = [] + + missing = copy.deepcopy(canonical) + del missing["ledger"]["total_queries"] + mutations.append(missing) + + extra = copy.deepcopy(canonical) + extra["ledger"]["extra"] = 0 + mutations.append(extra) + + extra_result = copy.deepcopy(canonical) + extra_result["result"]["private_basis"] = [[1.0]] + mutations.append(extra_result) + + missing_attempt_field = copy.deepcopy(canonical) + del missing_attempt_field["attempts"][0]["requested_shots"] + mutations.append(missing_attempt_field) + + duplicate = copy.deepcopy(canonical) + duplicate["attempts"][1]["attempt_index"] = 1 + mutations.append(duplicate) + + inconsistent = copy.deepcopy(canonical) + inconsistent["attempts"][0]["validation"] = True + mutations.append(inconsistent) + + wrong_requested = copy.deepcopy(canonical) + wrong_requested["attempts"][0]["requested_shots"] = 999 + mutations.append(wrong_requested) + + false_success = copy.deepcopy(canonical) + false_success["attempts"][0]["status"] = "succeeded" + mutations.append(false_success) + + for payload in mutations: + with pytest.raises(ValueError): + TrialResult.from_canonical_dict(payload) + + +def test_trial_result_rejects_decreasing_validation_crossings() -> None: + spec = generate_paired_trials([_config("random", 1)])[0] + payload = _result_with_two_validations(spec).canonical_dict() + payload["result"]["validation_attempts"].reverse() + payload["result"]["provisional_crossings"] = [2, 1] + + with pytest.raises(ValueError, match="increasing"): + TrialResult.from_canonical_dict(payload) + + +def test_trial_result_requires_certified_attempt_to_be_final() -> None: + spec = generate_paired_trials([_config("random", 1)])[0] + payload = _result_with_two_validations(spec).canonical_dict() + first, second = payload["result"]["validation_attempts"] + first["certified"] = True + first["status"] = "certified" + second["certified"] = False + second["status"] = "rejected" + payload["result"]["first_certified_query"] = 1 + + with pytest.raises(ValueError, match="final"): + TrialResult.from_canonical_dict(payload) diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/tests/test_figures.py b/tracks/qcs/solutions/frustration-free/challenge-113/tests/test_figures.py new file mode 100644 index 000000000..aeb8b9939 --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/tests/test_figures.py @@ -0,0 +1,871 @@ +from __future__ import annotations + +from dataclasses import replace +import hashlib +import json +from pathlib import Path +import struct + +import matplotlib +import matplotlib.figure +import matplotlib.pyplot as plt +import numpy as np +import pytest + +import qcontrol.figures as figures_module +from qcontrol.analysis import ( + BootstrapInterval, + MethodSummary, + MetricAvailability, + PairedSummary, + ProbabilityEstimate, + StratumKey, + StratumSummary, + Summary, + TrajectoryBand, +) +from qcontrol.artifacts import canonical_json_bytes +from qcontrol.figures import FigureError, render_publication_figures + + +matplotlib.use("Agg", force=True) + +METHODS = ("full", "model_hessian", "oracle", "random") +FILENAMES = ( + "queries_vs_dimension.png", + "advantage_vs_gap.png", + "subspace_rotation_and_floor.png", + "rank_invariant_d2_d4.png", + "failure_case.png", +) +GOLDEN_PNG_SHA256 = { + "queries_vs_dimension.png": ( + "f114f76ec10dd5068bd1a3e0994778069a3a2ad7e187f477cd4ba0b8f10a90da" + ), + "advantage_vs_gap.png": ( + "738d875c86d779ccdfb65d5b37d227c5c3bf01c54f51428861b158ff9edecccf" + ), + "subspace_rotation_and_floor.png": ( + "2588603ade7d3ef501f1c46e1ee4649a1ad6b126ce20ebf6525540a003336f27" + ), + "rank_invariant_d2_d4.png": ( + "f3d613ae09f9ec9f9e9a623d132b52a85f8c2cb94c5f8eba564da64612fe4de2" + ), + "failure_case.png": ( + "fe004c0ecf459c618d1a84d63a938161b5747af0c6b5f9e5e75fdbc4c45c6d57" + ), +} + + +def _interval(estimate: float, *, seed: int = 7) -> BootstrapInterval: + return BootstrapInterval( + estimate=estimate, + low=estimate - 0.1, + high=estimate + 0.1, + confidence=0.95, + samples=20, + seed=seed, + ) + + +def _probability(value: float, numerator: int) -> ProbabilityEstimate: + # These are fixture intervals; production strictness is checked by canonical + # round-tripping, so use exact Wilson bounds from the public constructor path. + from qcontrol.analysis import success_probability + + trials = [ + { + "certified": index < numerator, + "first_certified_query": 2 if index < numerator else None, + "provisional_crossings": [2] if index < numerator else [], + } + for index in range(4) + ] + estimate = success_probability(trials) + assert estimate.value == value + return estimate + + +def _method( + name: str, + *, + dimension: int, + query: int, + success_numerator: int, + attained: float, + rank: int, + gap: float, +) -> MethodSummary: + probability = _probability(success_numerator / 4, success_numerator) + trajectory = TrajectoryBand( + median=(0.4, 0.2, 0.1), + low=(0.35, 0.15, 0.08), + high=(0.45, 0.25, 0.12), + confidence=0.95, + samples=20, + seed=7, + ) + return MethodSummary( + method=name, + trial_count=4, + failure_count=4 - success_numerator, + success_probability=probability, + conditional_first_certified_queries=(query,) * success_numerator, + censored_first_certified_queries=(query,) * success_numerator + + (40,) * (4 - success_numerator), + total_shots=4 * query * 1_000, + total_shots_by_trial=(query * 1_000,) * 4, + median_best_observed_infidelity_trajectory=(0.4, 0.2, 0.1), + metric_availability=MetricAvailability("available", None), + principal_angle_availability=MetricAvailability("available", None), + exact_infidelity_trajectory=trajectory, + median_attained_infidelity_upper_bound=attained, + median_principal_angles=tuple( + 0.02 * (index + 1) + gap for index in range(dimension) + ), + median_model_effective_ranks=(float(rank),) * 3, + median_truth_effective_ranks=(float(rank),) * 3, + median_signed_eigenvalue_gaps=tuple( + ((-1.0) ** index) * gap for index in range(dimension) + ), + ) + + +def production_summary(*, include_failure: bool = True) -> Summary: + strata = [] + for system_name, hilbert_dimension, expected_rank in ( + ("one_qubit", 2, 3), + ("two_qubit", 4, 15), + ): + for gap in (0.01, 0.02): + for dimension in (1, 2): + model_query = 5 + dimension + model_success = 4 + attained = 5e-4 + if ( + include_failure + and hilbert_dimension == 4 + and gap == 0.02 + and dimension == 2 + ): + model_query = 11 + model_success = 2 + attained = 2e-3 + values = { + "full": (10, 3), + "model_hessian": (model_query, model_success), + "oracle": (7 + dimension, 4), + "random": (12 + dimension, 2), + } + methods = tuple( + _method( + name, + dimension=dimension, + query=query, + success_numerator=successes, + attained=attained if name == "model_hessian" else 7e-4, + rank=expected_rank, + gap=gap, + ) + for name, (query, successes) in sorted(values.items()) + ) + model = values["model_hessian"] + pairs = tuple( + PairedSummary( + baseline=name, + pair_count=4, + cluster_count=4, + success_probability_difference=_interval( + model[1] / 4 - successes / 4 + ), + censored_query_difference=_interval(query - model[0]), + total_shot_difference=_interval( + float((query - model[0]) * 1_000) + ), + ) + for name, (query, successes) in sorted(values.items()) + if name != "model_hessian" + ) + strata.append( + StratumSummary( + key=StratumKey( + system_name=system_name, + hilbert_dimension=hilbert_dimension, + segments=3 if hilbert_dimension == 2 else 10, + amplitude_bound=4.0, + duration=1.0, + search_dimension=dimension, + gap=gap, + shots=1_000, + ), + methods=methods, + paired_differences=pairs, + ) + ) + return Summary( + strata=tuple(sorted(strata, key=lambda item: item.key.sort_key())), + bootstrap_confidence=0.95, + bootstrap_samples=20, + bootstrap_seed=7, + ) + + +def _replace_stratum(summary: Summary, target: StratumSummary) -> Summary: + return replace( + summary, + strata=tuple( + sorted( + ( + target + if item.key.sort_key() == target.key.sort_key() + else item + for item in summary.strata + ), + key=lambda item: item.key.sort_key(), + ) + ), + ) + + +def _all_failure_summary() -> Summary: + summary = production_summary() + original = next( + item + for item in summary.strata + if item.key.system_name == "one_qubit" + and item.key.gap == 0.01 + and item.key.search_dimension == 1 + ) + methods = tuple( + replace( + item, + failure_count=4, + success_probability=_probability(0.0, 0), + conditional_first_certified_queries=(), + censored_first_certified_queries=(40, 40, 40, 40), + ) + for item in original.methods + ) + return _replace_stratum(summary, replace(original, methods=methods)) + + +def _configuration_variant_summary() -> Summary: + summary = production_summary() + original = next( + item + for item in summary.strata + if item.key.system_name == "one_qubit" + and item.key.gap == 0.01 + and item.key.search_dimension == 1 + ) + variants = ( + replace(original, key=replace(original.key, duration=2.0)), + replace(original, key=replace(original.key, segments=4)), + replace(original, key=replace(original.key, amplitude_bound=5.0)), + ) + return replace( + summary, + strata=tuple( + sorted((*summary.strata, *variants), key=lambda item: item.key.sort_key()) + ), + ) + + +def _capture_figures( + monkeypatch: pytest.MonkeyPatch, +) -> list[matplotlib.figure.Figure]: + captured: list[matplotlib.figure.Figure] = [] + original = matplotlib.figure.Figure.savefig + + def recording_savefig(self, file, *args, **kwargs): + captured.append(self) + return original(self, file, *args, **kwargs) + + monkeypatch.setattr(matplotlib.figure.Figure, "savefig", recording_savefig) + return captured + + +def _method_line(axis, label: str): + direct = next( + (line for line in axis.lines if line.get_label() == label), + None, + ) + if direct is not None: + return direct + return next( + container.lines[0] + for container in axis.containers + if container.get_label() == label and hasattr(container, "lines") + ) + + +def _confidence_band(axis, label: str): + return next(item for item in axis.collections if item.get_label() == label) + + +def _band_y_bounds(axis, label: str) -> tuple[float, float]: + band = _confidence_band(axis, label) + y_values = np.concatenate([path.vertices[:, 1] for path in band.get_paths()]) + return float(np.min(y_values)), float(np.max(y_values)) + + +def _png_chunks(payload: bytes) -> tuple[str, ...]: + assert payload.startswith(b"\x89PNG\r\n\x1a\n") + chunks: list[str] = [] + offset = 8 + while offset < len(payload): + length = struct.unpack(">I", payload[offset : offset + 4])[0] + chunk = payload[offset + 4 : offset + 8].decode("ascii") + chunks.append(chunk) + offset += length + 12 + if chunk == "IEND": + assert offset == len(payload) + break + return tuple(chunks) + + +def _fixture_stratum_id(stratum: StratumSummary) -> str: + key = stratum.key + return ( + f"system={key.system_name}|d={key.hilbert_dimension}|" + f"segments={key.segments}|amplitude_bound={key.amplitude_bound:g}|" + f"duration={key.duration:g}|k={key.search_dimension}|gap={key.gap:g}|" + f"shots={'exact' if key.shots is None else key.shots}" + ) + + +def _render(summary: Summary, output: Path): + return render_publication_figures( + summary, + output, + source="strict Summary fixture", + config="production fixture", + run_id="fixture-001", + ) + + +def test_renders_five_closed_nonempty_publication_figures_and_manifest( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + saved: dict[str, matplotlib.figure.Figure] = {} + original = matplotlib.figure.Figure.savefig + + def recording_savefig(self, file, *args, **kwargs): + saved[Path(file).name if not hasattr(file, "write") else f"buffer-{len(saved)}"] = self + return original(self, file, *args, **kwargs) + + monkeypatch.setattr(matplotlib.figure.Figure, "savefig", recording_savefig) + manifest = _render(production_summary(), tmp_path) + + assert tuple(item.filename for item in manifest.figures) == FILENAMES + assert all((tmp_path / name).stat().st_size > 0 for name in FILENAMES) + assert (tmp_path / "figure_manifest.json").is_file() + assert manifest.summary_sha256 == hashlib.sha256( + canonical_json_bytes(production_summary().canonical_dict()) + ).hexdigest() + figures = list(saved.values()) + assert figures + for figure in figures: + assert figure.axes + assert all(axis.has_data() for axis in figure.axes) + assert all(axis.get_xlabel() and axis.get_ylabel() for axis in figure.axes) + assert figure._suptitle is not None + + +def test_axes_expose_required_definitions_scales_legends_and_methods( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + figures: list[matplotlib.figure.Figure] = [] + original = matplotlib.figure.Figure.savefig + + def recording_savefig(self, file, *args, **kwargs): + figures.append(self) + return original(self, file, *args, **kwargs) + + monkeypatch.setattr(matplotlib.figure.Figure, "savefig", recording_savefig) + _render(production_summary(), tmp_path) + + text = "\n".join( + [ + *(axis.get_xlabel() for figure in figures for axis in figure.axes), + *(axis.get_ylabel() for figure in figures for axis in figure.axes), + *( + label.get_text() + for figure in figures + for axis in figure.axes + for label in axis.get_legend().get_texts() + if axis.get_legend() is not None + ), + ] + ) + for label in ("Full space", "Random", "Oracle", "Model Hessian"): + assert label in text + assert "optimizer queries" in text + assert "success probability" in text + assert "normalized gap" in text + assert "radians" in text + assert "relative threshold" in text + assert any( + axis.get_yscale() == "log" and "infidelity" in axis.get_ylabel().lower() + for figure in figures + for axis in figure.axes + ) + assert all( + axis.get_yscale() != "log" or "infidelity" in axis.get_ylabel().lower() + for figure in figures + for axis in figure.axes + ) + assert all(figure.texts for figure in figures) + + +def test_artifacts_match_golden_hashes_canonical_manifest_and_png_contract( + tmp_path: Path, +) -> None: + summary = production_summary() + first_directory = tmp_path / "first" + second_directory = tmp_path / "second" + first = _render(summary, first_directory) + with matplotlib.rc_context({"font.family": "monospace"}): + second = _render(summary, second_directory) + + assert matplotlib.get_backend().lower() == "agg" + assert {item.filename: item.sha256 for item in first.figures} == ( + GOLDEN_PNG_SHA256 + ) + assert first.summary_sha256 == hashlib.sha256( + canonical_json_bytes(summary.canonical_dict()) + ).hexdigest() + assert first.source == "strict Summary fixture" + assert first.config == "production fixture" + assert first.run_id == "fixture-001" + assert first.matplotlib_version == matplotlib.__version__ + assert first.numpy_version == np.__version__ + expected_strata = {_fixture_stratum_id(item) for item in summary.strata} + for entry in first.figures[:4]: + assert set(entry.panel_strata) == expected_strata + assert first.figures[4].panel_strata == ( + "system=two_qubit|d=4|segments=10|amplitude_bound=4|" + "duration=1|k=2|gap=0.02|shots=1000", + ) + + first_manifest = (first_directory / "figure_manifest.json").read_bytes() + assert first_manifest == canonical_json_bytes(first.canonical_dict()) + assert json.loads(first_manifest) == first.canonical_dict() + assert first_manifest == ( + second_directory / "figure_manifest.json" + ).read_bytes() + assert first == second + + for entry in first.figures: + first_payload = (first_directory / entry.filename).read_bytes() + second_payload = (second_directory / entry.filename).read_bytes() + assert first_payload == second_payload + assert hashlib.sha256(first_payload).hexdigest() == entry.sha256 + chunks = _png_chunks(first_payload) + assert chunks[0] == "IHDR" + assert chunks[-1] == "IEND" + assert chunks.count("IHDR") == chunks.count("IEND") == 1 + assert "IDAT" in chunks + assert set(chunks) <= {"IHDR", "IDAT", "IEND"} + + +def test_selects_and_labels_real_failure_case(tmp_path: Path) -> None: + manifest = _render(production_summary(), tmp_path) + + failure = next( + item for item in manifest.figures if item.filename == "failure_case.png" + ) + assert failure.panel_strata == ( + "system=two_qubit|d=4|segments=10|amplitude_bound=4|" + "duration=1|k=2|gap=0.02|shots=1000", + ) + assert failure.failure_reason is not None + assert "restricted attained infidelity" in failure.failure_reason + + +def test_fails_precisely_when_no_real_failure_exists(tmp_path: Path) -> None: + with pytest.raises(FigureError, match="no qualifying production failure"): + _render(production_summary(include_failure=False), tmp_path) + assert not list(tmp_path.glob("*.png")) + + +@pytest.mark.parametrize( + ("mutation", "message"), + ( + ( + lambda payload: payload["strata"][0]["methods"].pop( + next( + index + for index, method in enumerate( + payload["strata"][0]["methods"] + ) + if method["method"] == "model_hessian" + ) + ), + "required methods", + ), + ( + lambda payload: payload["strata"][0]["methods"][0].update( + { + "metric_availability": { + "state": "unavailable", + "reason": "missing", + }, + "principal_angle_availability": { + "state": "unavailable", + "reason": "missing", + }, + "exact_infidelity_trajectory": None, + "median_attained_infidelity_upper_bound": None, + "median_model_effective_ranks": None, + "median_principal_angles": None, + "median_signed_eigenvalue_gaps": None, + "median_truth_effective_ranks": None, + } + ), + "production metrics", + ), + ), +) +def test_rejects_missing_methods_or_metrics( + tmp_path: Path, + mutation, + message: str, +) -> None: + payload = production_summary().canonical_dict() + mutation(payload) + payload["strata"][0]["methods"].sort(key=lambda item: item["method"]) + summary = Summary.from_canonical_dict(payload) + + with pytest.raises(FigureError, match=message): + _render(summary, tmp_path) + assert not list(tmp_path.glob("*.png")) + + +def test_rejects_noncanonical_summary_instance(tmp_path: Path) -> None: + valid = production_summary() + malformed = Summary( + strata=tuple(reversed(valid.strata)), + bootstrap_confidence=valid.bootstrap_confidence, + bootstrap_samples=valid.bootstrap_samples, + bootstrap_seed=valid.bootstrap_seed, + ) + + with pytest.raises(FigureError, match="strict canonical Summary"): + _render(malformed, tmp_path) + + +def test_primary_query_series_uses_censored_failures_and_all_failure_point( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + figures = _capture_figures(monkeypatch) + manifest = _render(_all_failure_summary(), tmp_path) + query_figure = figures[FILENAMES.index("queries_vs_dimension.png")] + query_axis, success_axis = next( + (query_figure.axes[index], query_figure.axes[index + 1]) + for index in range(0, len(query_figure.axes), 2) + if "one_qubit" in query_figure.axes[index].get_title() + and "gap=0.01" in query_figure.axes[index].get_title() + ) + + model_query = _method_line(query_axis, "Model Hessian") + model_success = _method_line(success_axis, "Model Hessian") + assert model_query.get_xdata().tolist() == [1, 2] + assert model_query.get_ydata().tolist() == [40.0, 7.0] + assert model_success.get_ydata().tolist() == [0.0, 1.0] + model_errors = next( + container + for container in query_axis.containers + if container.get_label() == "Model Hessian" + ) + segments = model_errors.lines[2][0].get_segments() + assert segments[0].tolist() == [[1.0, 40.0], [1.0, 40.0]] + success_errors = next( + container + for container in success_axis.containers + if container.get_label() == "Model Hessian" + ) + assert success_errors.lines[2][0].get_segments()[0].tolist() == [ + [1.0, 0.0], + [1.0, _probability(0.0, 0).high], + ] + random_query = _method_line(query_axis, "Random") + assert random_query.get_ydata().tolist() == [40.0, 27.0] + random_errors = next( + container + for container in query_axis.containers + if container.get_label() == "Random" + ) + assert random_errors.lines[2][0].get_segments()[1].tolist() == [ + [2.0, 14.0], + [2.0, 40.0], + ] + assert "budget-censored median" in query_axis.get_ylabel() + assert manifest.figures[0].panel_strata + + +def test_full_control_configuration_separates_panels_and_manifest_ids( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + figures = _capture_figures(monkeypatch) + manifest = _render(_configuration_variant_summary(), tmp_path) + query_figure = figures[FILENAMES.index("queries_vs_dimension.png")] + query_entry = manifest.figures[0] + + assert manifest.source == "strict Summary fixture" + assert manifest.config == "production fixture" + assert manifest.run_id == "fixture-001" + assert len(query_figure.axes) == 14 + assert len(query_entry.panel_strata) == len(set(query_entry.panel_strata)) + assert any("segments=4" in item for item in query_entry.panel_strata) + assert any("amplitude_bound=5" in item for item in query_entry.panel_strata) + assert any("duration=2" in item for item in query_entry.panel_strata) + assert all( + all( + field in item + for field in ( + "system=", + "d=", + "segments=", + "amplitude_bound=", + "duration=", + "k=", + "gap=", + "shots=", + ) + ) + for item in query_entry.panel_strata + ) + + +def test_exact_artist_data_and_method_styles( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + figures = _capture_figures(monkeypatch) + _render(production_summary(), tmp_path) + by_title = { + figure._suptitle.get_text().splitlines()[0]: figure + for figure in figures + } + advantage = by_title[ + "Paired Model-Hessian advantage versus normalized gap" + ] + subspace = by_title[ + "Target-k subspace rotation and restricted fidelity floor" + ] + rank = by_title["d=2 and d=4 Hessian rank invariants"] + failure = next( + figure + for title, figure in by_title.items() + if title.startswith("Observed production failure:") + ) + advantage_axes = { + "query": next( + axis + for axis in advantage.axes + if "one_qubit" in axis.get_title() + and "k=1" in axis.get_title() + and "query advantage" in axis.get_ylabel() + ), + "shot": next( + axis + for axis in advantage.axes + if "one_qubit" in axis.get_title() + and "k=1" in axis.get_title() + and "shot advantage" in axis.get_ylabel() + ), + "success": next( + axis + for axis in advantage.axes + if "one_qubit" in axis.get_title() + and "k=1" in axis.get_title() + and "success advantage" in axis.get_ylabel() + ), + } + assert _method_line( + advantage_axes["query"], "Full space" + ).get_ydata().tolist() == [ + 4.0, + 4.0, + ] + assert _method_line( + advantage_axes["shot"], "Full space" + ).get_ydata().tolist() == [ + 4000.0, + 4000.0, + ] + assert _method_line( + advantage_axes["success"], "Full space" + ).get_ydata().tolist() == [ + 0.25, + 0.25, + ] + assert _band_y_bounds( + advantage_axes["query"], "Full space confidence interval" + ) == ( + 3.9, + 4.1, + ) + assert _band_y_bounds( + advantage_axes["shot"], "Full space confidence interval" + ) == ( + 3999.9, + 4000.1, + ) + assert _band_y_bounds( + advantage_axes["success"], "Full space confidence interval" + ) == ( + 0.15, + 0.35, + ) + + k2_angles = next( + axis + for axis in subspace.axes + if "one_qubit" in axis.get_title() + and "k=2" in axis.get_title() + and "principal angles" in axis.get_ylabel() + ) + for method in ("Full space", "Model Hessian", "Oracle", "Random"): + assert _method_line(k2_angles, f"{method} θ1").get_xdata().tolist() == [ + 0.0025, + 0.005, + ] + assert _method_line(k2_angles, f"{method} θ1").get_ydata().tolist() == [ + 0.03, + 0.04, + ] + assert _method_line(k2_angles, f"{method} θ2").get_ydata().tolist() == [ + 0.05, + 0.06, + ] + + summary = production_summary() + for stratum in summary.strata: + identity = _fixture_stratum_id(stratum) + rank_axis = next( + axis + for axis in rank.axes + if axis.get_title() == identity + and "effective Hessian rank" in axis.get_ylabel() + ) + gap_axis = next( + axis + for axis in rank.axes + if axis.get_title() == identity + and "signed leading" in axis.get_ylabel() + ) + expected_rank = 3.0 if stratum.key.hilbert_dimension == 2 else 15.0 + for label in ( + "Model Hessian model rank", + "Model Hessian truth rank", + ): + assert _method_line(rank_axis, label).get_xdata().tolist() == [ + 1e-6, + 1e-8, + 1e-10, + ] + assert _method_line(rank_axis, label).get_ydata().tolist() == [ + expected_rank, + expected_rank, + expected_rank, + ] + reference = f"Expected invariant rank {int(expected_rank)}" + assert list(_method_line(rank_axis, reference).get_ydata()) == [ + expected_rank, + expected_rank, + ] + expected_gaps = [ + ((-1.0) ** index) * stratum.key.gap + for index in range(stratum.key.search_dimension) + ] + signed_gap = _method_line( + gap_axis, + "Model Hessian signed leading gaps", + ) + assert signed_gap.get_xdata().tolist() == list( + range(1, stratum.key.search_dimension + 1) + ) + assert signed_gap.get_ydata().tolist() == expected_gaps + + expected_failure_values = { + "right-censored first certified query": 25.5, + "total optimizer + validation shots": 11000.0, + "success probability within budget": 0.5, + "restricted infidelity": 0.002, + } + for ylabel, expected_value in expected_failure_values.items(): + axis = next( + item for item in failure.axes if ylabel in item.get_ylabel() + ) + assert _method_line( + axis, + "Model Hessian", + ).get_ydata().item() == expected_value + + expected = { + "Full space": ("#000000", "s", "-"), + "Model Hessian": ("#0072B2", "o", "-"), + "Oracle": ("#009E73", "^", "--"), + "Random": ("#E69F00", "D", ":"), + } + for figure in figures: + for axis in figure.axes: + labeled_lines = [ + (line, line.get_label()) + for line in axis.lines + ] + labeled_lines.extend( + (container.lines[0], container.get_label()) + for container in axis.containers + if hasattr(container, "lines") + ) + for line, label in labeled_lines: + method = next( + ( + name + for name in expected + if label == name + or label.startswith(f"{name} ") + ), + None, + ) + if method is None: + continue + color, marker, linestyle = expected[method] + assert line.get_color().lower() == color.lower() + assert line.get_marker() == marker + assert line.get_linestyle() == linestyle + +def test_builder_exception_closes_only_new_figures_and_restores_rc( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + existing = plt.figure() + before_numbers = plt.get_fignums() + before_rc = { + "axes.grid": matplotlib.rcParams["axes.grid"], + "font.size": matplotlib.rcParams["font.size"], + } + + def broken_builder(summary, caption): + plt.figure() + raise RuntimeError("intentional builder failure") + + monkeypatch.setattr(figures_module, "_queries_figure", broken_builder) + try: + with pytest.raises(RuntimeError, match="intentional builder failure"): + _render(production_summary(), tmp_path) + assert plt.get_fignums() == before_numbers + assert { + "axes.grid": matplotlib.rcParams["axes.grid"], + "font.size": matplotlib.rcParams["font.size"], + } == before_rc + assert plt.fignum_exists(existing.number) + finally: + plt.close(existing) diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/tests/test_landscape.py b/tracks/qcs/solutions/frustration-free/challenge-113/tests/test_landscape.py new file mode 100644 index 000000000..3b18385f9 --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/tests/test_landscape.py @@ -0,0 +1,449 @@ +from __future__ import annotations + +from collections.abc import Callable +from types import SimpleNamespace + +import jax +import jax.numpy as jnp +import numpy as np +import pytest + +from qcontrol.closed_loop import make_full_space, make_model_hessian_space +from qcontrol.config import SystemConfig +from qcontrol.landscape import ( + EndpointPolishingError, + _leading_eigenpairs, + _matrix_free_rank_diagnostics, + analyze_landscape, + dense_hessian, + endpoint_jacobian, + hessian_vector_product, + polish_endpoint, +) +from qcontrol.objectives import normalized_infidelity +from qcontrol.open_loop import OpenLoopResult, optimize_open_loop +from qcontrol.propagation import propagate +from qcontrol.pulses import PulseSpace +from qcontrol.systems import ControlSystem, make_system + + +@pytest.fixture(scope="module") +def accepted_one_qubit() -> tuple[ + ControlSystem, + PulseSpace, + OpenLoopResult, + Callable[[jax.Array], jax.Array], +]: + system = make_system(SystemConfig("one_qubit", 12, 4.0)) + space = PulseSpace.from_system(system, 12) + accepted = optimize_open_loop(system, space, seed=5, starts=5) + loss_fn = lambda point: normalized_infidelity(point, system, space) + return system, space, accepted, loss_fn + + +@pytest.fixture(scope="module") +def accepted_two_qubit() -> tuple[ControlSystem, PulseSpace, OpenLoopResult]: + system = make_system(SystemConfig("two_qubit", 20, 4.0)) + space = PulseSpace.from_system(system, 20) + accepted = optimize_open_loop(system, space, seed=5, starts=5) + return system, space, accepted + + +def test_hvp_matches_dense_hessian( + accepted_one_qubit: tuple[ + ControlSystem, + PulseSpace, + OpenLoopResult, + Callable[[jax.Array], jax.Array], + ], +) -> None: + _, _, accepted, loss_fn = accepted_one_qubit + point = jnp.asarray(accepted.normalized_pulse, dtype=jnp.float64) + dense = dense_hessian(loss_fn, point) + vector = jnp.linspace(-1.0, 1.0, point.size, dtype=jnp.float64) + + actual = jax.jit(hessian_vector_product, static_argnums=0)( + loss_fn, + point, + vector, + ) + + np.testing.assert_allclose(actual, dense @ np.asarray(vector), rtol=1e-7, atol=1e-9) + + +def test_dense_hessian_is_bounded_to_eighty_parameters() -> None: + point = jnp.zeros(81, dtype=jnp.float64) + with pytest.raises(ValueError, match="at most 80"): + dense_hessian(lambda x: jnp.vdot(x, x), point) + + +def test_matrix_free_spectrum_includes_large_negative_modes() -> None: + diagonal = jnp.asarray([5.0, 2.0, 0.1, -12.0, -3.0, 0.01]) + point = jnp.zeros(diagonal.size, dtype=jnp.float64) + loss_fn = lambda x: 0.5 * jnp.vdot(x, diagonal * x) + + values, vectors = _leading_eigenpairs(loss_fn, point, count=4) + ranks, lower_bounds = _matrix_free_rank_diagnostics( + values, + spectrum_truncated=True, + ) + + np.testing.assert_allclose(values, [-12.0, 5.0, -3.0, 2.0], atol=1e-12) + np.testing.assert_allclose(vectors.T @ vectors, np.eye(4), atol=1e-12) + assert ranks == {1e-6: 4, 1e-8: 4, 1e-10: 4} + assert lower_bounds == {1e-6: True, 1e-8: True, 1e-10: True} + + +def test_one_qubit_geometry_has_primary_rank_three( + accepted_one_qubit: tuple[ + ControlSystem, + PulseSpace, + OpenLoopResult, + Callable[[jax.Array], jax.Array], + ], +) -> None: + system, space, accepted, _ = accepted_one_qubit + result = analyze_landscape(system, space, accepted, leading_count=6) + + assert set(result.hessian_ranks) == {1e-6, 1e-8, 1e-10} + assert set(result.jacobian_ranks) == {1e-6, 1e-8, 1e-10} + assert result.hessian_ranks[1e-8] == 3 + assert result.jacobian_ranks[1e-8] == 3 + assert result.eigenvalue_ordering == "descending absolute" + assert result.polishing.loss <= 1e-12 + assert result.polishing.gradient_norm <= 1e-10 + assert result.polishing.residual_norm <= 1e-12 + assert not any(result.hessian_rank_is_lower_bound.values()) + np.testing.assert_allclose( + result.model_basis.T @ result.model_basis, + np.eye(24), + rtol=0.0, + atol=1e-10, + ) + assert result.model_basis.shape == (24, 24) + assert result.search_basis_available_columns == 24 + assert np.all( + np.abs(result.dense_eigenvalues[:-1]) + >= np.abs(result.dense_eigenvalues[1:]) + ) + np.testing.assert_allclose( + result.endpoint_basis.T @ result.endpoint_basis, + np.eye(3), + rtol=0.0, + atol=1e-10, + ) + assert result.dense_hessian is not None + assert result.dense_eigenvalues is not None + assert result.dense_eigenvectors is not None + assert result.dense_hvp_projector_residuals[1e-8] <= 1e-7 + assert ( + float(np.max(result.dense_hvp_principal_angles[1e-8])) + <= 1e-6 + ) + + +@pytest.mark.integration +def test_dense_search_basis_supports_all_high_k_spaces( + accepted_one_qubit, + accepted_two_qubit, +) -> None: + one_system, one_space, one_accepted, _ = accepted_one_qubit + one = analyze_landscape(one_system, one_space, one_accepted, leading_count=6) + one_k24 = make_model_hessian_space( + np.asarray(one.polishing.normalized_pulse), + one.model_basis, + dimension=24, + ) + assert one_k24.dimension == 24 + assert one.hessian_ranks[1e-8] == 3 + + two_system, two_space, two_accepted = accepted_two_qubit + two = analyze_landscape(two_system, two_space, two_accepted, leading_count=20) + assert two.model_basis.shape == (80, 80) + assert two.search_basis_available_columns == 80 + assert two.hessian_ranks[1e-8] == 15 + spaces = [ + make_model_hessian_space( + np.asarray(two.polishing.normalized_pulse), + two.model_basis, + dimension=dimension, + ) + for dimension in (20, 30, 80) + ] + assert [space.dimension for space in spaces] == [20, 30, 80] + for left, right in ((spaces[0], spaces[1]),): + np.testing.assert_allclose( + left.basis @ left.basis.T, + right.basis[:, : left.dimension] @ right.basis[:, : left.dimension].T, + atol=1e-10, + ) + full = make_full_space(spaces[-1].origin) + delta = np.linspace(-0.01, 0.01, 80) + np.testing.assert_allclose( + full.to_pulse(delta), + spaces[-1].to_pulse(spaces[-1].basis.T @ delta), + atol=1e-10, + ) + + +def test_endpoint_jacobian_is_branch_free_and_has_expected_shape( + accepted_one_qubit: tuple[ + ControlSystem, + PulseSpace, + OpenLoopResult, + Callable[[jax.Array], jax.Array], + ], +) -> None: + system, space, accepted, _ = accepted_one_qubit + point = np.asarray(accepted.normalized_pulse) + jacobian = endpoint_jacobian(system, space, point) + + assert jacobian.shape == (3, 24) + assert np.all(np.isfinite(jacobian)) + singular_values = np.linalg.svd(jacobian, compute_uv=False) + assert ( + np.linalg.matrix_rank(jacobian, tol=1e-10 * singular_values[0]) == 3 + ) + + +@pytest.mark.integration +def test_two_qubit_unpolished_hessian_exposes_residual_curvature( + accepted_two_qubit: tuple[ControlSystem, PulseSpace, OpenLoopResult], +) -> None: + system, space, accepted = accepted_two_qubit + result = analyze_landscape( + system, + space, + accepted, + leading_count=20, + polish=False, + ) + + assert result.polishing is None + assert result.hessian_ranks[1e-8] == 19 + assert result.jacobian_ranks[1e-8] == 15 + + +@pytest.mark.integration +def test_endpoint_polishing_is_bounded_and_reproducible( + accepted_two_qubit: tuple[ControlSystem, PulseSpace, OpenLoopResult], +) -> None: + system, space, accepted = accepted_two_qubit + + first = analyze_landscape(system, space, accepted, leading_count=20) + second = analyze_landscape(system, space, accepted, leading_count=20) + assert first.polishing is not None + assert second.polishing is not None + first_polish = first.polishing + second_polish = second.polishing + + np.testing.assert_allclose( + first_polish.normalized_pulse, + second_polish.normalized_pulse, + rtol=0.0, + atol=1e-13, + ) + assert first_polish.loss == pytest.approx(second_polish.loss, abs=1e-15) + assert first_polish.residual_norm == pytest.approx( + second_polish.residual_norm, + abs=1e-15, + ) + assert first.hessian_ranks == second.hessian_ranks + assert first.jacobian_ranks == second.jacobian_ranks + assert first.hessian_ranks == {1e-6: 15, 1e-8: 15, 1e-10: 15} + assert first_polish.converged + assert second_polish.converged + for polishing in (first_polish, second_polish): + assert polishing.evaluations > 0 + assert polishing.jacobian_evaluations > 0 + assert polishing.evaluations >= polishing.jacobian_evaluations + assert polishing.status > 0 + assert polishing.message + assert first_polish.loss <= 1e-12 + assert first_polish.gradient_norm <= 1e-10 + assert first_polish.projected_gradient_norm <= 1e-10 + assert first_polish.residual_norm <= 1e-12 + assert first_polish.phase_consistency_error <= 1e-12 + assert first_polish.step_norm > 0.0 + assert np.all(np.abs(first_polish.normalized_pulse) <= 1.0) + + +@pytest.mark.integration +def test_endpoint_polishing_fails_closed_when_budget_is_exhausted( + accepted_two_qubit: tuple[ControlSystem, PulseSpace, OpenLoopResult], +) -> None: + system, space, accepted = accepted_two_qubit + + with pytest.raises(EndpointPolishingError, match="did not converge") as raised: + polish_endpoint(system, space, accepted, max_nfev=1) + + assert not raised.value.diagnostics.converged + assert 0 < raised.value.diagnostics.evaluations <= 1 + assert raised.value.diagnostics.residual_norm > 1e-12 + + +@pytest.mark.parametrize( + ("candidate", "message"), + [ + (np.full(80, 1.01), "bounds"), + (np.zeros(80), "target"), + ], +) +def test_endpoint_polishing_rejects_invalid_solver_candidate( + accepted_two_qubit: tuple[ControlSystem, PulseSpace, OpenLoopResult], + monkeypatch: pytest.MonkeyPatch, + candidate: np.ndarray, + message: str, +) -> None: + system, space, accepted = accepted_two_qubit + fake_result = SimpleNamespace( + x=candidate, + success=True, + status=1, + message="fake convergence", + nfev=1, + njev=1, + cost=0.0, + optimality=0.0, + ) + monkeypatch.setattr("qcontrol.landscape.least_squares", lambda *args, **kwargs: fake_result) + + with pytest.raises(EndpointPolishingError, match=message) as raised: + polish_endpoint(system, space, accepted) + + assert not raised.value.diagnostics.converged + + +@pytest.mark.parametrize( + "replacement", + [ + SimpleNamespace(), + SimpleNamespace(x=object()), + SimpleNamespace(x=np.zeros((24, 1))), + SimpleNamespace(x=np.zeros(23)), + SimpleNamespace( + x=np.zeros(24), + success=True, + status=np.nan, + message="bad status", + nfev=1, + njev=1, + cost=0.0, + optimality=0.0, + ), + SimpleNamespace( + x=np.zeros(24), + success=True, + status=1, + message="bad cost", + nfev=1, + njev=1, + cost=np.nan, + optimality=0.0, + ), + SimpleNamespace( + x=np.zeros(24), + success=True, + status=1, + message="bad optimality", + nfev=1, + njev=1, + cost=0.0, + optimality=np.inf, + ), + ], +) +def test_endpoint_polishing_fails_closed_for_malformed_scipy_result( + accepted_one_qubit: tuple[ + ControlSystem, + PulseSpace, + OpenLoopResult, + Callable[[jax.Array], jax.Array], + ], + monkeypatch: pytest.MonkeyPatch, + replacement: SimpleNamespace, +) -> None: + system, space, accepted, _ = accepted_one_qubit + monkeypatch.setattr( + "qcontrol.landscape.least_squares", + lambda *args, **kwargs: replacement, + ) + + with pytest.raises(EndpointPolishingError, match="malformed") as raised: + polish_endpoint(system, space, accepted) + + diagnostics = raised.value.diagnostics + assert not diagnostics.converged + assert diagnostics.status == -1 + assert diagnostics.message + assert len(diagnostics.normalized_pulse) == space.parameter_count + assert np.all(np.isfinite(diagnostics.normalized_pulse)) + + +@pytest.mark.parametrize("exception_type", [KeyboardInterrupt, SystemExit]) +def test_endpoint_polishing_preserves_process_control_exceptions( + accepted_one_qubit: tuple[ + ControlSystem, + PulseSpace, + OpenLoopResult, + Callable[[jax.Array], jax.Array], + ], + monkeypatch: pytest.MonkeyPatch, + exception_type: type[BaseException], +) -> None: + system, space, accepted, _ = accepted_one_qubit + + def interrupt(*args: object, **kwargs: object) -> object: + raise exception_type() + + monkeypatch.setattr("qcontrol.landscape.least_squares", interrupt) + with pytest.raises(exception_type): + polish_endpoint(system, space, accepted) + + +@pytest.mark.integration +def test_two_qubit_scientific_geometry_and_held_pulse_refinement( + accepted_two_qubit: tuple[ControlSystem, PulseSpace, OpenLoopResult], +) -> None: + system, space, accepted = accepted_two_qubit + coarse = analyze_landscape(system, space, accepted, leading_count=20) + + assert coarse.polishing is not None + coarse_point = np.asarray(coarse.polishing.normalized_pulse).reshape( + space.control_count, + space.segments, + ) + refined_point = np.repeat(coarse_point, 2, axis=1).reshape(-1) + refined_space = PulseSpace.from_system(system, 2 * space.segments) + np.testing.assert_allclose( + propagate(system, refined_space.to_physical(refined_point)), + propagate(system, space.to_physical(coarse_point.reshape(-1))), + rtol=0.0, + atol=2e-12, + ) + refined_loss = float(normalized_infidelity(refined_point, system, refined_space)) + refined = analyze_landscape( + system, + refined_space, + OpenLoopResult( + normalized_pulse=tuple(refined_point), + loss=refined_loss, + gradient_norm=float("nan"), + starts=accepted.starts, + evaluations=accepted.evaluations, + ), + leading_count=20, + dense_validation=False, + polish=False, + ) + + assert coarse.hessian_ranks == {1e-6: 15, 1e-8: 15, 1e-10: 15} + assert coarse.jacobian_ranks == {1e-6: 15, 1e-8: 15, 1e-10: 15} + assert refined.hessian_ranks == {1e-6: 15, 1e-8: 15, 1e-10: 15} + assert refined.jacobian_ranks == {1e-6: 15, 1e-8: 15, 1e-10: 15} + assert refined.dense_hessian is None + assert refined.search_basis_available_columns == 20 + assert refined.model_basis.shape == (160, 20) + assert refined.dense_hvp_projector_residuals == {} + assert not any(refined.hessian_rank_is_lower_bound.values()) + assert coarse.dense_hvp_projector_residuals[1e-8] <= 1e-7 diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/tests/test_offline.py b/tracks/qcs/solutions/frustration-free/challenge-113/tests/test_offline.py new file mode 100644 index 000000000..8f9d64fb6 --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/tests/test_offline.py @@ -0,0 +1,247 @@ +from __future__ import annotations + +import numpy as np +import pytest + +from qcontrol.config import SystemConfig +from qcontrol.device import Observation +from qcontrol.offline import ( + ExactInfidelityTrajectory, + RestrictedOptimizationResult, + canonical_solver_message_code, + classify_solver_termination, + compute_geometry_diagnostics, + cumulative_best_exact_infidelity, + effective_ranks, + finalize_restricted_attained_bound, +) +from qcontrol.pulses import PulseSpace +from qcontrol.systems import make_system + + +def test_exact_trajectory_aligns_failed_queries_with_carry_forward() -> None: + fidelities = { + (0.0,): 0.5, + (0.2,): 0.8, + (0.9,): 0.99, + (0.4,): 0.9, + } + + def evaluate(pulse: object) -> float: + return fidelities[tuple(np.asarray(pulse, dtype=np.float64))] + + successful_one = Observation(0.7, 1_000, 1, False, 1) + successful_three = Observation(0.8, 1_000, 3, False, 3) + + result = cumulative_best_exact_infidelity( + evaluate, + initial_pulse=np.asarray([0.0]), + audited_queries=( + (np.asarray([0.2]), successful_one), + (np.asarray([0.9]), None), + (np.asarray([0.4]), successful_three), + ), + ) + + assert result.initial_infidelity == 0.5 + np.testing.assert_allclose( + result.cumulative_best_by_optimizer_query, + (0.2, 0.2, 0.1), + rtol=0.0, + atol=1e-15, + ) + + +def test_effective_ranks_are_relative_and_rescaling_invariant() -> None: + spectrum = np.asarray([100.0, -1e-5, 0.0]) + thresholds = (1e-6, 1e-8, 1e-10) + + assert effective_ranks(spectrum, thresholds) == (1, 2, 2) + assert effective_ranks(1e-12 * spectrum, thresholds) == (1, 2, 2) + assert effective_ranks(np.zeros(4), thresholds) == (0, 0, 0) + with pytest.raises(ValueError, match="finite"): + effective_ranks(np.asarray([1.0, np.nan]), thresholds) + + +@pytest.mark.parametrize( + ("system_config", "expected_dimension"), + ( + (SystemConfig("one_qubit", 1, 4.0), 2), + (SystemConfig("two_qubit", 2, 4.0), 4), + ), +) +def test_zero_gap_geometry_fixtures_cover_d2_and_d4( + system_config, + expected_dimension, +) -> None: + model = make_system(system_config) + assert model.dimension == expected_dimension + pulse_space = PulseSpace.from_system(model, system_config.segments) + geometry = compute_geometry_diagnostics( + model, + model, + pulse_space, + np.zeros(pulse_space.parameter_count), + ) + + assert len(geometry.model_eigenvalues) == pulse_space.parameter_count + assert geometry.model_effective_ranks == geometry.truth_effective_ranks + assert max(map(abs, geometry.signed_eigenvalue_gaps)) == 0.0 + assert max(geometry.principal_angles_radians) < 1e-7 + + +@pytest.mark.parametrize( + ("facts", "expected"), + ( + ( + { + "success": True, + "status": 0, + "message_code": "convergence", + "output_finite": True, + "nit": 3, + "nfev": 7, + "max_iterations": 10, + "max_evaluations": 20, + }, + "converged", + ), + ( + { + "success": False, + "status": 1, + "message_code": "iteration_limit", + "output_finite": True, + "nit": 10, + "nfev": 12, + "max_iterations": 10, + "max_evaluations": 20, + }, + "iteration_limit", + ), + ( + { + "success": False, + "status": 1, + "message_code": "evaluation_limit", + "output_finite": True, + "nit": 4, + "nfev": 20, + "max_iterations": 10, + "max_evaluations": 20, + }, + "evaluation_limit", + ), + ( + { + "success": False, + "status": 2, + "message_code": "line_search_failure", + "output_finite": True, + "nit": 4, + "nfev": 12, + "max_iterations": 10, + "max_evaluations": 20, + }, + "line_search_failure", + ), + ( + { + "success": False, + "status": 2, + "message_code": "numerical_failure", + "output_finite": False, + "nit": 1, + "nfev": 2, + "max_iterations": 10, + "max_evaluations": 20, + }, + "numerical_failure", + ), + ( + { + "success": False, + "status": 3, + "message_code": "solver_failure", + "output_finite": True, + "nit": 1, + "nfev": 2, + "max_iterations": 10, + "max_evaluations": 20, + }, + "solver_failure", + ), + ), +) +def test_solver_termination_categories_use_only_raw_facts(facts, expected) -> None: + assert classify_solver_termination(**facts) == expected + + +@pytest.mark.parametrize( + "mutation", + ( + {"success": False, "status": 0}, + {"success": True, "status": 1}, + {"message_code": "numerical_failure", "output_finite": True}, + {"message_code": "iteration_limit", "nit": 0}, + {"message_code": "evaluation_limit", "nfev": 0}, + ), +) +def test_solver_termination_rejects_contradictory_raw_facts(mutation) -> None: + facts = { + "success": False, + "status": 2, + "message_code": "line_search_failure", + "output_finite": True, + "nit": 2, + "nfev": 4, + "max_iterations": 10, + "max_evaluations": 20, + } + facts.update(mutation) + with pytest.raises(ValueError, match="solver"): + classify_solver_termination(**facts) + + +@pytest.mark.parametrize( + ("message", "code"), + ( + ("CONVERGENCE: NORM_OF_PROJECTED_GRADIENT_<=_PGTOL", "convergence"), + ("STOP: TOTAL NO. of ITERATIONS REACHED LIMIT", "iteration_limit"), + ("TOTAL NO. OF F AND G EVALUATIONS EXCEEDS LIMIT", "evaluation_limit"), + ("ABNORMAL_TERMINATION_IN_LNSRCH", "line_search_failure"), + ("NaN result", "numerical_failure"), + ("callback halt", "solver_failure"), + ), +) +def test_solver_message_codes_are_canonical(message, code) -> None: + assert canonical_solver_message_code(message) == code + + +def test_audited_candidate_can_supply_final_restricted_upper_bound() -> None: + cached = RestrictedOptimizationResult( + attained_infidelity_upper_bound=0.4, + starting_infidelity_upper_bound=0.6, + max_iterations=100, + max_evaluations=1_000, + gradient_tolerance=1e-9, + consistency_tolerance=1e-10, + nfev=5, + nit=2, + solver_success=True, + solver_status=0, + solver_message_code="convergence", + solver_output_finite=True, + termination="converged", + ) + exact = ExactInfidelityTrajectory( + initial_infidelity=0.6, + cumulative_best_by_optimizer_query=(0.6, 0.1), + best_successful_audited_infidelity=0.1, + ) + + payload = finalize_restricted_attained_bound(cached, exact) + + assert payload["cached_solver_attained_infidelity_upper_bound"] == 0.4 + assert payload["attained_infidelity_upper_bound"] == 0.1 + assert payload["attained_infidelity_source"] == "audited_candidate" diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/tests/test_open_loop.py b/tracks/qcs/solutions/frustration-free/challenge-113/tests/test_open_loop.py new file mode 100644 index 000000000..261940266 --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/tests/test_open_loop.py @@ -0,0 +1,149 @@ +import numpy as np +import pytest +from scipy.optimize import OptimizeResult + +from qcontrol.config import SystemConfig +from qcontrol.open_loop import OpenLoopAcceptanceError, optimize_open_loop +from qcontrol.pulses import PulseSpace +from qcontrol.systems import make_system + + +def test_one_qubit_open_loop_reaches_acceptance() -> None: + system = make_system(SystemConfig("one_qubit", 12, 4.0)) + space = PulseSpace.from_system(system, 12) + result = optimize_open_loop(system, space, seed=5, starts=5) + assert result.loss <= 1e-8 + assert result.gradient_norm <= 1e-5 + assert result.starts == 5 + assert result.evaluations > 0 + assert np.asarray(result.normalized_pulse).dtype == np.float64 + assert np.all(np.abs(result.normalized_pulse) <= 1.0) + + +def test_open_loop_is_reproducible() -> None: + system = make_system(SystemConfig("one_qubit", 12, 4.0)) + space = PulseSpace.from_system(system, 12) + assert optimize_open_loop(system, space, 5) == optimize_open_loop(system, space, 5) + + +def test_duration_one_two_qubit_runs_all_starts_and_fails_closed() -> None: + system = make_system(SystemConfig("two_qubit", 20, 4.0, duration=1.0)) + space = PulseSpace.from_system(system, 20) + + with pytest.raises(OpenLoopAcceptanceError) as raised: + optimize_open_loop(system, space, seed=5, starts=2) + + diagnostics = raised.value.diagnostics + assert len(diagnostics) == 2 + assert [item.index for item in diagnostics] == [0, 1] + for item in diagnostics: + assert item.loss > 1e-8 + assert item.success in {True, False} + assert isinstance(item.status, int) + assert item.message + assert item.evaluations > 0 + + +def test_evaluations_match_per_start_scipy_nfev( + monkeypatch: pytest.MonkeyPatch, +) -> None: + system = make_system(SystemConfig("one_qubit", 2, 4.0)) + space = PulseSpace.from_system(system, 2) + expected_nfev = [2, 3] + scipy_results: list[OptimizeResult] = [] + + def fake_minimize(fun: object, x0: np.ndarray, **kwargs: object) -> OptimizeResult: + start_index = len(scipy_results) + evaluations = expected_nfev[start_index] + value = 0.0 + gradient = np.zeros_like(x0) + for _ in range(evaluations): + value, gradient = fun(x0) # type: ignore[operator] + assert isinstance(value, float) + assert gradient.dtype == np.float64 + assert gradient.flags.c_contiguous + result = OptimizeResult( + x=x0, + fun=max(float(value), 0.5), + jac=gradient, + success=False, + status=9, + message=f"failed start {start_index}", + nfev=evaluations, + ) + scipy_results.append(result) + return result + + monkeypatch.setattr("qcontrol.open_loop.minimize", fake_minimize) + with pytest.raises(OpenLoopAcceptanceError) as raised: + optimize_open_loop(system, space, seed=3, starts=2) + + assert [result.nfev for result in scipy_results] == expected_nfev + assert [item.evaluations for item in raised.value.diagnostics] == expected_nfev + assert [item.message for item in raised.value.diagnostics] == [ + "failed start 0", + "failed start 1", + ] + + +def test_unexpected_scipy_exception_fails_closed_with_prior_diagnostics( + monkeypatch: pytest.MonkeyPatch, +) -> None: + system = make_system(SystemConfig("one_qubit", 2, 4.0)) + space = PulseSpace.from_system(system, 2) + calls = 0 + + def fake_minimize(fun: object, x0: np.ndarray, **kwargs: object) -> OptimizeResult: + nonlocal calls + calls += 1 + value, gradient = fun(x0) # type: ignore[operator] + if calls == 2: + raise RuntimeError("scipy exploded") + return OptimizeResult( + x=x0, + fun=max(float(value), 0.5), + jac=gradient, + success=False, + status=7, + message="first start failed", + nfev=1, + ) + + monkeypatch.setattr("qcontrol.open_loop.minimize", fake_minimize) + with pytest.raises(OpenLoopAcceptanceError) as raised: + optimize_open_loop(system, space, seed=3, starts=3) + + assert calls == 2 + assert [item.index for item in raised.value.diagnostics] == [0, 1] + assert raised.value.diagnostics[0].status == 7 + assert raised.value.diagnostics[0].evaluations == 1 + exceptional = raised.value.diagnostics[1] + assert exceptional.success is False + assert exceptional.status == -1 + assert exceptional.message == "RuntimeError: scipy exploded" + assert exceptional.evaluations == 1 + + +@pytest.mark.parametrize("exception_type", [KeyboardInterrupt, SystemExit]) +def test_process_control_exceptions_are_not_swallowed( + monkeypatch: pytest.MonkeyPatch, + exception_type: type[BaseException], +) -> None: + system = make_system(SystemConfig("one_qubit", 2, 4.0)) + space = PulseSpace.from_system(system, 2) + + def fake_minimize(*args: object, **kwargs: object) -> OptimizeResult: + raise exception_type() + + monkeypatch.setattr("qcontrol.open_loop.minimize", fake_minimize) + with pytest.raises(exception_type): + optimize_open_loop(system, space, seed=3, starts=2) + + +@pytest.mark.integration +def test_two_qubit_open_loop_reaches_development_acceptance() -> None: + system = make_system(SystemConfig("two_qubit", 20, 4.0)) + space = PulseSpace.from_system(system, 20) + result = optimize_open_loop(system, space, seed=5, starts=5) + assert result.loss <= 1e-8 + assert result.gradient_norm <= 1e-5 diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/tests/test_runtime.py b/tracks/qcs/solutions/frustration-free/challenge-113/tests/test_runtime.py new file mode 100644 index 000000000..b25ad9128 --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/tests/test_runtime.py @@ -0,0 +1,7 @@ +import jax + + +def test_jax_x64_can_be_enabled() -> None: + jax.config.update("jax_enable_x64", True) + assert jax.config.x64_enabled + assert jax.devices() diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/tests/test_systems.py b/tracks/qcs/solutions/frustration-free/challenge-113/tests/test_systems.py new file mode 100644 index 000000000..5386d47da --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/tests/test_systems.py @@ -0,0 +1,248 @@ +import numpy as np +import pytest + +from qcontrol.config import SystemConfig +from qcontrol.systems import ( + ControlSystem, + _PerturbationDescriptor, + lie_algebra_dimension, + make_system, + perturb_system, +) + + +def test_one_qubit_system_is_su2_controllable() -> None: + system = make_system(SystemConfig("one_qubit", 12, 4.0)) + assert system.dimension == 2 + assert system.duration == 1.0 + assert len(system.controls) == 2 + assert lie_algebra_dimension(system) == 3 + + +def test_two_qubit_system_is_su4_controllable() -> None: + system = make_system(SystemConfig("two_qubit", 20, 4.0)) + assert system.dimension == 4 + assert system.duration == 8.0 + assert len(system.controls) == 4 + assert lie_algebra_dimension(system) == 15 + + +def test_system_duration_override_and_perturbation_provenance_are_preserved() -> None: + model = make_system(SystemConfig("two_qubit", 20, 4.0, duration=6.5)) + zero_gap = perturb_system(model, 0.0, 3) + nonzero_gap = perturb_system(model, 0.05, 3) + + assert model.duration == 6.5 + assert zero_gap.duration == model.duration + assert nonzero_gap.duration == model.duration + + +def test_gap_zero_preserves_model_and_nonzero_gap_is_reproducible() -> None: + model = make_system(SystemConfig("two_qubit", 20, 4.0)) + zero_gap = perturb_system(model, 0.0, 3) + np.testing.assert_allclose(zero_gap.drift, model.drift) + for actual, expected in zip(zero_gap.controls, model.controls, strict=True): + np.testing.assert_allclose(actual, expected) + truth_a = perturb_system(model, 0.05, 3) + truth_b = perturb_system(model, 0.05, 3) + np.testing.assert_allclose(truth_a.drift, truth_b.drift) + assert not np.allclose(truth_a.drift, model.drift) + + +def test_system_matrices_use_complex128_normalized_hermitian_products() -> None: + system = make_system(SystemConfig("two_qubit", 20, 4.0)) + assert system.drift.dtype == np.complex128 + assert all(control.dtype == np.complex128 for control in system.controls) + for control in system.controls: + np.testing.assert_allclose(control, control.conj().T, rtol=0.0, atol=1e-15) + np.testing.assert_allclose(np.linalg.norm(control, "fro"), 1.0, rtol=0.0, atol=1e-15) + identity = np.eye(system.dimension, dtype=np.complex128) + np.testing.assert_allclose(system.target.conj().T @ system.target, identity, atol=1e-15) + + +def test_nonzero_gap_has_exact_relative_drift_norm_and_independent_gain_errors() -> None: + model = make_system(SystemConfig("two_qubit", 20, 4.0)) + gap = 0.05 + truth = perturb_system(model, gap, 13) + relative_norm = np.linalg.norm(truth.drift - model.drift, "fro") / np.linalg.norm( + model.drift, "fro" + ) + np.testing.assert_allclose(relative_norm, gap, rtol=0.0, atol=1e-12) + assert all( + not np.allclose(actual, expected) + for actual, expected in zip(truth.controls, model.controls, strict=True) + ) + gain_ratios = np.array( + [ + np.vdot(expected, actual).real / np.vdot(expected, expected).real + for actual, expected in zip(truth.controls, model.controls, strict=True) + ] + ) + assert np.unique(gain_ratios).size == len(model.controls) + + +def test_control_system_defensively_copies_and_freezes_all_arrays() -> None: + drift = np.array([[1.0, 0.0], [0.0, -1.0]], dtype=np.complex128) + control = np.array([[0.0, 1.0], [1.0, 0.0]], dtype=np.complex128) + target = np.eye(2, dtype=np.complex128) + system = ControlSystem(drift, (control,), target, (4.0,), "custom", 2.0) + + drift[0, 0] = 9.0 + control[0, 1] = 9.0 + target[0, 0] = 9.0 + assert system.drift[0, 0] == 1.0 + assert system.controls[0][0, 1] == 1.0 + assert system.target[0, 0] == 1.0 + + for matrix in (system.drift, *system.controls, system.target): + assert not matrix.flags.writeable + with pytest.raises(ValueError): + matrix.flat[0] = 0.0 + with pytest.raises(ValueError): + matrix.setflags(write=True) + + +def test_direct_control_system_construction_requires_duration() -> None: + drift = np.array([[1.0, 0.0], [0.0, -1.0]], dtype=np.complex128) + control = np.array([[0.0, 1.0], [1.0, 0.0]], dtype=np.complex128) + target = np.eye(2, dtype=np.complex128) + + with pytest.raises(TypeError, match="duration"): + ControlSystem(drift, (control,), target, (4.0,), "two_qubit") # type: ignore[call-arg] + + +def test_control_system_equality_never_compares_ndarrays() -> None: + first = make_system(SystemConfig("one_qubit", 12, 4.0)) + second = make_system(SystemConfig("one_qubit", 12, 4.0)) + assert first == first + assert first != second + + +def test_perturbation_descriptor_is_complete_reproducible_and_private() -> None: + model = make_system(SystemConfig("two_qubit", 20, 4.0)) + truth = perturb_system(model, np.float64(0.05), np.int64(17)) + repeated = perturb_system(model, np.float64(0.05), np.int64(17)) + descriptor = truth._perturbation + repeated_descriptor = repeated._perturbation + + assert descriptor is not None + assert repeated_descriptor is not None + assert descriptor.gap == 0.05 + assert descriptor.seed == 17 + np.testing.assert_array_equal( + descriptor.drift_direction, + repeated_descriptor.drift_direction, + ) + np.testing.assert_array_equal( + descriptor.control_gain_deltas, + repeated_descriptor.control_gain_deltas, + ) + np.testing.assert_array_equal( + descriptor.unmodeled_direction, + repeated_descriptor.unmodeled_direction, + ) + for direction in (descriptor.drift_direction, descriptor.unmodeled_direction): + np.testing.assert_allclose(direction, direction.conj().T, atol=1e-15) + np.testing.assert_allclose(np.trace(direction), 0.0, atol=1e-15) + np.testing.assert_allclose(np.linalg.norm(direction, "fro"), 1.0, atol=1e-15) + assert not direction.flags.writeable + assert not descriptor.control_gain_deltas.flags.writeable + + aggregate = descriptor.drift_direction + descriptor.unmodeled_direction + aggregate /= np.linalg.norm(aggregate, "fro") + expected_drift = ( + model.drift + + descriptor.gap * np.linalg.norm(model.drift, "fro") * aggregate + ) + np.testing.assert_allclose(truth.drift, expected_drift, rtol=0.0, atol=1e-15) + for actual, expected, delta in zip( + truth.controls, + model.controls, + descriptor.control_gain_deltas, + strict=True, + ): + np.testing.assert_allclose(actual, (1.0 + delta) * expected) + + +def test_perturbation_descriptor_defensively_copies_and_freezes_arrays() -> None: + direction = np.array([[1.0, 0.0], [0.0, -1.0]], dtype=np.complex128) + gains = np.array([0.1, -0.2], dtype=np.float64) + descriptor = _PerturbationDescriptor(direction, gains, direction, 0.1, 5) + + direction[0, 0] = 7.0 + gains[0] = 7.0 + assert descriptor.drift_direction[0, 0] == 1.0 + assert descriptor.unmodeled_direction[0, 0] == 1.0 + assert descriptor.control_gain_deltas[0] == 0.1 + for array in ( + descriptor.drift_direction, + descriptor.control_gain_deltas, + descriptor.unmodeled_direction, + ): + assert not array.flags.writeable + with pytest.raises(ValueError): + array.flat[0] = 0.0 + with pytest.raises(ValueError): + array.setflags(write=True) + + +def test_zero_gap_preserves_complete_system_and_uses_no_descriptor() -> None: + model = make_system(SystemConfig("two_qubit", 20, 4.0)) + truth = perturb_system(model, np.float64(0.0), np.int64(3)) + np.testing.assert_array_equal(truth.drift, model.drift) + for actual, expected in zip(truth.controls, model.controls, strict=True): + np.testing.assert_array_equal(actual, expected) + np.testing.assert_array_equal(truth.target, model.target) + assert truth.amplitude_scales == model.amplitude_scales + assert truth.name == model.name + assert truth.duration == model.duration + assert truth._perturbation is None + + +def test_positive_gap_rejects_zero_drift_model() -> None: + model = make_system(SystemConfig("one_qubit", 12, 4.0)) + zero_drift = ControlSystem( + np.zeros_like(model.drift), + model.controls, + model.target, + model.amplitude_scales, + model.name, + model.duration, + ) + with pytest.raises(ValueError, match="drift Frobenius norm"): + perturb_system(zero_drift, 0.05, 3) + + +@pytest.mark.parametrize("gap", [-0.1, np.nan, np.inf, True, "0.1"]) +def test_perturb_system_rejects_invalid_gap(gap: object) -> None: + model = make_system(SystemConfig("one_qubit", 12, 4.0)) + with pytest.raises(ValueError, match="gap"): + perturb_system(model, gap, 3) # type: ignore[arg-type] + + +@pytest.mark.parametrize("seed", [-1, True, 1.5, np.float64(2.0)]) +def test_perturb_system_rejects_invalid_seed(seed: object) -> None: + model = make_system(SystemConfig("one_qubit", 12, 4.0)) + with pytest.raises(ValueError, match="seed"): + perturb_system(model, 0.05, seed) # type: ignore[arg-type] + + +@pytest.mark.parametrize("tolerance", [0.0, -1.0, np.nan, np.inf, True, "1e-10"]) +def test_lie_algebra_dimension_rejects_invalid_tolerance(tolerance: object) -> None: + model = make_system(SystemConfig("one_qubit", 12, 4.0)) + with pytest.raises(ValueError, match="tolerance"): + lie_algebra_dimension(model, tolerance) # type: ignore[arg-type] + + +def test_numpy_real_tolerance_is_accepted() -> None: + model = make_system(SystemConfig("two_qubit", 20, 4.0)) + assert lie_algebra_dimension(model, np.float64(1e-10)) == 15 + + +def test_make_system_rejects_unknown_name_when_config_validation_is_bypassed() -> None: + config = object.__new__(SystemConfig) + object.__setattr__(config, "name", "unknown") + object.__setattr__(config, "segments", 12) + object.__setattr__(config, "amplitude_bound", 4.0) + with pytest.raises(ValueError, match="system name"): + make_system(config) diff --git a/tracks/qcs/solutions/frustration-free/challenge-113/uv.lock b/tracks/qcs/solutions/frustration-free/challenge-113/uv.lock new file mode 100644 index 000000000..f8ce5ed15 --- /dev/null +++ b/tracks/qcs/solutions/frustration-free/challenge-113/uv.lock @@ -0,0 +1,350 @@ +version = 1 +revision = 3 +requires-python = "==3.12.*" + +[[package]] +name = "challenge-113-sim-to-real" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "cma" }, + { name = "jax" }, + { name = "matplotlib" }, + { name = "numpy" }, + { name = "scipy" }, +] + +[package.dev-dependencies] +dev = [ + { name = "pytest" }, +] + +[package.metadata] +requires-dist = [ + { name = "cma" }, + { name = "jax" }, + { name = "matplotlib" }, + { name = "numpy" }, + { name = "scipy" }, +] + +[package.metadata.requires-dev] +dev = [{ name = "pytest" }] + +[[package]] +name = "cma" +version = "4.4.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/67/ac/8c27720838e293898671f01b5c452236a0c74f4799a3f2d5fcccbbf50d71/cma-4.4.4.tar.gz", hash = "sha256:632bd654b5dce04c0eaa3166679d3e4773ce7a79eab7934e7f363c341b9a8170", size = 316645, upload-time = "2026-02-25T22:18:16Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/d4/ec46cedab6a6145e21768baa8110db3e2e836a320d8499e4ef18bc894e61/cma-4.4.4-py3-none-any.whl", hash = "sha256:edb6d02eb2aac2d54650f16a8f0c70711ff17445957de7c9de92ff7fd4b7ef38", size = 328311, upload-time = "2026-02-25T22:18:09.602Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "contourpy" +version = "1.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174, upload-time = "2025-07-26T12:03:12.549Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/45/adfee365d9ea3d853550b2e735f9d66366701c65db7855cd07621732ccfc/contourpy-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b08a32ea2f8e42cf1d4be3169a98dd4be32bafe4f22b6c4cb4ba810fa9e5d2cb", size = 293419, upload-time = "2025-07-26T12:01:21.16Z" }, + { url = "https://files.pythonhosted.org/packages/53/3e/405b59cfa13021a56bba395a6b3aca8cec012b45bf177b0eaf7a202cde2c/contourpy-1.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:556dba8fb6f5d8742f2923fe9457dbdd51e1049c4a43fd3986a0b14a1d815fc6", size = 273979, upload-time = "2025-07-26T12:01:22.448Z" }, + { url = "https://files.pythonhosted.org/packages/d4/1c/a12359b9b2ca3a845e8f7f9ac08bdf776114eb931392fcad91743e2ea17b/contourpy-1.3.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92d9abc807cf7d0e047b95ca5d957cf4792fcd04e920ca70d48add15c1a90ea7", size = 332653, upload-time = "2025-07-26T12:01:24.155Z" }, + { url = "https://files.pythonhosted.org/packages/63/12/897aeebfb475b7748ea67b61e045accdfcf0d971f8a588b67108ed7f5512/contourpy-1.3.3-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2e8faa0ed68cb29af51edd8e24798bb661eac3bd9f65420c1887b6ca89987c8", size = 379536, upload-time = "2025-07-26T12:01:25.91Z" }, + { url = "https://files.pythonhosted.org/packages/43/8a/a8c584b82deb248930ce069e71576fc09bd7174bbd35183b7943fb1064fd/contourpy-1.3.3-cp312-cp312-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:626d60935cf668e70a5ce6ff184fd713e9683fb458898e4249b63be9e28286ea", size = 384397, upload-time = "2025-07-26T12:01:27.152Z" }, + { url = "https://files.pythonhosted.org/packages/cc/8f/ec6289987824b29529d0dfda0d74a07cec60e54b9c92f3c9da4c0ac732de/contourpy-1.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d00e655fcef08aba35ec9610536bfe90267d7ab5ba944f7032549c55a146da1", size = 362601, upload-time = "2025-07-26T12:01:28.808Z" }, + { url = "https://files.pythonhosted.org/packages/05/0a/a3fe3be3ee2dceb3e615ebb4df97ae6f3828aa915d3e10549ce016302bd1/contourpy-1.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:451e71b5a7d597379ef572de31eeb909a87246974d960049a9848c3bc6c41bf7", size = 1331288, upload-time = "2025-07-26T12:01:31.198Z" }, + { url = "https://files.pythonhosted.org/packages/33/1d/acad9bd4e97f13f3e2b18a3977fe1b4a37ecf3d38d815333980c6c72e963/contourpy-1.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:459c1f020cd59fcfe6650180678a9993932d80d44ccde1fa1868977438f0b411", size = 1403386, upload-time = "2025-07-26T12:01:33.947Z" }, + { url = "https://files.pythonhosted.org/packages/cf/8f/5847f44a7fddf859704217a99a23a4f6417b10e5ab1256a179264561540e/contourpy-1.3.3-cp312-cp312-win32.whl", hash = "sha256:023b44101dfe49d7d53932be418477dba359649246075c996866106da069af69", size = 185018, upload-time = "2025-07-26T12:01:35.64Z" }, + { url = "https://files.pythonhosted.org/packages/19/e8/6026ed58a64563186a9ee3f29f41261fd1828f527dd93d33b60feca63352/contourpy-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:8153b8bfc11e1e4d75bcb0bff1db232f9e10b274e0929de9d608027e0d34ff8b", size = 226567, upload-time = "2025-07-26T12:01:36.804Z" }, + { url = "https://files.pythonhosted.org/packages/d1/e2/f05240d2c39a1ed228d8328a78b6f44cd695f7ef47beb3e684cf93604f86/contourpy-1.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:07ce5ed73ecdc4a03ffe3e1b3e3c1166db35ae7584be76f65dbbe28a7791b0cc", size = 193655, upload-time = "2025-07-26T12:01:37.999Z" }, +] + +[[package]] +name = "cycler" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615, upload-time = "2023-10-07T05:32:18.335Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321, upload-time = "2023-10-07T05:32:16.783Z" }, +] + +[[package]] +name = "fonttools" +version = "4.63.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/84/69/c97f2c18e0db87d2c7b15da1974dace76ae938f1cfa22e2727a648b7ed43/fonttools-4.63.0.tar.gz", hash = "sha256:caeb583deeb5168e694b65cda8b4ee62abedfa66cf88488734466f2366b9c4e0", size = 3597189, upload-time = "2026-05-14T12:04:30.958Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/ef/b3c6b9b5be2f82416d73fe2ed2e96e2793cd80e7510bd6a17ca79cdd88ec/fonttools-4.63.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:37dd23e621e3b0aef1baa70a303b80aaf38449632cfc8fd2a55fb285bbccfc02", size = 2881131, upload-time = "2026-05-14T12:03:13.386Z" }, + { url = "https://files.pythonhosted.org/packages/44/a0/c815bea63117fa63e4e1c01f8a1110d2112fa003f838e6467094ec2432ce/fonttools-4.63.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a9faff9e0c1f76f9fd55899d2ce785832efebab37eb8ae13995853aef178bef0", size = 2426704, upload-time = "2026-05-14T12:03:15.801Z" }, + { url = "https://files.pythonhosted.org/packages/44/04/0b91d8e916e92ad1fac9e4624760baf0fd5ff2ead614c2f68fb21373f03f/fonttools-4.63.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef3048ef05dbb552b89817713d9cac912e00d0fde4a3105c00d29e52e10c89af", size = 5044298, upload-time = "2026-05-14T12:03:18.085Z" }, + { url = "https://files.pythonhosted.org/packages/77/c7/2342da9830e3e9d4870305ca5d2091d2a83284f2953079b7bdd3b5e029d8/fonttools-4.63.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:58dc6bb86a78d782f00f9190ca02c119cf5bbe2807536e361e18d42019f877d8", size = 4999800, upload-time = "2026-05-14T12:03:20.161Z" }, + { url = "https://files.pythonhosted.org/packages/e6/6d/67fe16c48d7ce050979b33f47e0d28a318f02da030602e944c34f7a16ef3/fonttools-4.63.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ee08ebfa58f6e1aeff5697ab9582105bb620008c1caafb681e4c557e7483027b", size = 4982666, upload-time = "2026-05-14T12:03:22.87Z" }, + { url = "https://files.pythonhosted.org/packages/f2/00/3bbab338c07c71fa56269953845e92c951a61457bbbb0f1022551ea266d9/fonttools-4.63.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:27fdc65af8da6f88b9c6121c47a464cbe359fcfff7ff6fc2d37a1f395d755b78", size = 5133598, upload-time = "2026-05-14T12:03:25.168Z" }, + { url = "https://files.pythonhosted.org/packages/62/f2/aa27c7f98db5b064883dadcc5283947e81e034de42e22a33675878d98b54/fonttools-4.63.0-cp312-cp312-win32.whl", hash = "sha256:af2fd1664d00a397d75f806985ddb36282091c2131a73a6485c23b4a34722263", size = 2292575, upload-time = "2026-05-14T12:03:27.496Z" }, + { url = "https://files.pythonhosted.org/packages/87/36/cccb9bc2a6ab63d1b2980374f0dca72ce95ae267c9b4cfe77455bb70d0d4/fonttools-4.63.0-cp312-cp312-win_amd64.whl", hash = "sha256:59ac449f8cca9b4ffa08d2e7bbadad87ce710d69d1eda5c3c1ce579baa987272", size = 2343211, upload-time = "2026-05-14T12:03:30.057Z" }, + { url = "https://files.pythonhosted.org/packages/2c/47/c99d5268f354002ce80f8d029cd9d7d872969da1de8b93d32de4dc56d6f4/fonttools-4.63.0-py3-none-any.whl", hash = "sha256:445af2eab030a16b9171ea8bdda7ebf7d96bda2df88ee182a464252f6e05e20d", size = 1164562, upload-time = "2026-05-14T12:04:29.092Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "jax" +version = "0.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jaxlib" }, + { name = "ml-dtypes" }, + { name = "numpy" }, + { name = "opt-einsum" }, + { name = "scipy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/81/20/90a137c178de8f2b9170bd4dfb934e261213fdaac67e5c5183b132be85e7/jax-0.11.0.tar.gz", hash = "sha256:a2feb7cfa48bb35d36b8ecec16a4ec24044dec01935f779b28b017213697b195", size = 2813185, upload-time = "2026-07-16T19:00:14.519Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/5a/26a1305f8978bc41f889a4172198c7d8976e8e394fc4d03597d9edfd6b7e/jax-0.11.0-py3-none-any.whl", hash = "sha256:b31ed66c4321d5c9e48b861f51a72ed5f7960c93514296202d2041cbb9ac45bf", size = 3255281, upload-time = "2026-07-16T18:58:25.02Z" }, +] + +[[package]] +name = "jaxlib" +version = "0.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ml-dtypes" }, + { name = "numpy" }, + { name = "scipy" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/13/629fd9f50e15424358b7c53b6ec729e2d7163d64942817bee9adeee04ed1/jaxlib-0.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f5bf0b3bfc2ef4778580047389dacb51ae000e50c6b3b6f98d10788927066c97", size = 62546563, upload-time = "2026-07-16T18:59:16.788Z" }, + { url = "https://files.pythonhosted.org/packages/8e/1d/a85feebcfc6fa702bcaadd74f3c13283e1613f7598de5ca69bdbaf4c2671/jaxlib-0.11.0-cp312-cp312-manylinux_2_27_aarch64.whl", hash = "sha256:1b767c4d7ad4dfc6358313b63ae6d83c3571d3ce966de04d8aaf29eb44633786", size = 82155667, upload-time = "2026-07-16T18:59:20.477Z" }, + { url = "https://files.pythonhosted.org/packages/86/f0/89c34a4877628edea6585699e42f7d3ed4c1e50140ade9c6efce85a0ab84/jaxlib-0.11.0-cp312-cp312-manylinux_2_27_x86_64.whl", hash = "sha256:7ea9dc06d94f536deabcf304513143bc89f51173ddfd786d44f8ac303efdf643", size = 87263507, upload-time = "2026-07-16T18:59:24.258Z" }, + { url = "https://files.pythonhosted.org/packages/59/6b/e6caeaff6bd1537becbbcde8985fd65a71374e33f9e8c1aa3371bd16c349/jaxlib-0.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:166ca16586a8aeb23f9c5e322845f3789037ed294f0a1539404a50f7c5fb13cf", size = 67743601, upload-time = "2026-07-16T18:59:27.981Z" }, +] + +[[package]] +name = "kiwisolver" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/67/9c61eccb13f0bdca9307614e782fec49ffdde0f7a2314935d489fa93cd9c/kiwisolver-1.5.0.tar.gz", hash = "sha256:d4193f3d9dc3f6f79aaed0e5637f45d98850ebf01f7ca20e69457f3e8946b66a", size = 103482, upload-time = "2026-03-09T13:15:53.382Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/b2/818b74ebea34dabe6d0c51cb1c572e046730e64844da6ed646d5298c40ce/kiwisolver-1.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4e9750bc21b886308024f8a54ccb9a2cc38ac9fa813bf4348434e3d54f337ff9", size = 123158, upload-time = "2026-03-09T13:13:23.127Z" }, + { url = "https://files.pythonhosted.org/packages/bf/d9/405320f8077e8e1c5c4bd6adc45e1e6edf6d727b6da7f2e2533cf58bff71/kiwisolver-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:72ec46b7eba5b395e0a7b63025490d3214c11013f4aacb4f5e8d6c3041829588", size = 66388, upload-time = "2026-03-09T13:13:24.765Z" }, + { url = "https://files.pythonhosted.org/packages/99/9f/795fedf35634f746151ca8839d05681ceb6287fbed6cc1c9bf235f7887c2/kiwisolver-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ed3a984b31da7481b103f68776f7128a89ef26ed40f4dc41a2223cda7fb24819", size = 64068, upload-time = "2026-03-09T13:13:25.878Z" }, + { url = "https://files.pythonhosted.org/packages/c4/13/680c54afe3e65767bed7ec1a15571e1a2f1257128733851ade24abcefbcc/kiwisolver-1.5.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bb5136fb5352d3f422df33f0c879a1b0c204004324150cc3b5e3c4f310c9049f", size = 1477934, upload-time = "2026-03-09T13:13:27.166Z" }, + { url = "https://files.pythonhosted.org/packages/c8/2f/cebfcdb60fd6a9b0f6b47a9337198bcbad6fbe15e68189b7011fd914911f/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b2af221f268f5af85e776a73d62b0845fc8baf8ef0abfae79d29c77d0e776aaf", size = 1278537, upload-time = "2026-03-09T13:13:28.707Z" }, + { url = "https://files.pythonhosted.org/packages/f2/0d/9b782923aada3fafb1d6b84e13121954515c669b18af0c26e7d21f579855/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b0f172dc8ffaccb8522d7c5d899de00133f2f1ca7b0a49b7da98e901de87bf2d", size = 1296685, upload-time = "2026-03-09T13:13:30.528Z" }, + { url = "https://files.pythonhosted.org/packages/27/70/83241b6634b04fe44e892688d5208332bde130f38e610c0418f9ede47ded/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6ab8ba9152203feec73758dad83af9a0bbe05001eb4639e547207c40cfb52083", size = 1346024, upload-time = "2026-03-09T13:13:32.818Z" }, + { url = "https://files.pythonhosted.org/packages/e4/db/30ed226fb271ae1a6431fc0fe0edffb2efe23cadb01e798caeb9f2ceae8f/kiwisolver-1.5.0-cp312-cp312-manylinux_2_39_riscv64.whl", hash = "sha256:cdee07c4d7f6d72008d3f73b9bf027f4e11550224c7c50d8df1ae4a37c1402a6", size = 987241, upload-time = "2026-03-09T13:13:34.435Z" }, + { url = "https://files.pythonhosted.org/packages/ec/bd/c314595208e4c9587652d50959ead9e461995389664e490f4dce7ff0f782/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7c60d3c9b06fb23bd9c6139281ccbdc384297579ae037f08ae90c69f6845c0b1", size = 2227742, upload-time = "2026-03-09T13:13:36.4Z" }, + { url = "https://files.pythonhosted.org/packages/c1/43/0499cec932d935229b5543d073c2b87c9c22846aab48881e9d8d6e742a2d/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e315e5ec90d88e140f57696ff85b484ff68bb311e36f2c414aa4286293e6dee0", size = 2323966, upload-time = "2026-03-09T13:13:38.204Z" }, + { url = "https://files.pythonhosted.org/packages/3d/6f/79b0d760907965acfd9d61826a3d41f8f093c538f55cd2633d3f0db269f6/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:1465387ac63576c3e125e5337a6892b9e99e0627d52317f3ca79e6930d889d15", size = 1977417, upload-time = "2026-03-09T13:13:39.966Z" }, + { url = "https://files.pythonhosted.org/packages/ab/31/01d0537c41cb75a551a438c3c7a80d0c60d60b81f694dac83dd436aec0d0/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:530a3fd64c87cffa844d4b6b9768774763d9caa299e9b75d8eca6a4423b31314", size = 2491238, upload-time = "2026-03-09T13:13:41.698Z" }, + { url = "https://files.pythonhosted.org/packages/e4/34/8aefdd0be9cfd00a44509251ba864f5caf2991e36772e61c408007e7f417/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1d9daea4ea6b9be74fe2f01f7fbade8d6ffab263e781274cffca0dba9be9eec9", size = 2294947, upload-time = "2026-03-09T13:13:43.343Z" }, + { url = "https://files.pythonhosted.org/packages/ad/cf/0348374369ca588f8fe9c338fae49fa4e16eeb10ffb3d012f23a54578a9e/kiwisolver-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:f18c2d9782259a6dc132fdc7a63c168cbc74b35284b6d75c673958982a378384", size = 73569, upload-time = "2026-03-09T13:13:45.792Z" }, + { url = "https://files.pythonhosted.org/packages/28/26/192b26196e2316e2bd29deef67e37cdf9870d9af8e085e521afff0fed526/kiwisolver-1.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:f7c7553b13f69c1b29a5bde08ddc6d9d0c8bfb84f9ed01c30db25944aeb852a7", size = 64997, upload-time = "2026-03-09T13:13:46.878Z" }, + { url = "https://files.pythonhosted.org/packages/1c/fa/2910df836372d8761bb6eff7d8bdcb1613b5c2e03f260efe7abe34d388a7/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-macosx_10_13_x86_64.whl", hash = "sha256:5ae8e62c147495b01a0f4765c878e9bfdf843412446a247e28df59936e99e797", size = 130262, upload-time = "2026-03-09T13:15:35.629Z" }, + { url = "https://files.pythonhosted.org/packages/0f/41/c5f71f9f00aabcc71fee8b7475e3f64747282580c2fe748961ba29b18385/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:f6764a4ccab3078db14a632420930f6186058750df066b8ea2a7106df91d3203", size = 138036, upload-time = "2026-03-09T13:15:36.894Z" }, + { url = "https://files.pythonhosted.org/packages/fa/06/7399a607f434119c6e1fdc8ec89a8d51ccccadf3341dee4ead6bd14caaf5/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c31c13da98624f957b0fb1b5bae5383b2333c2c3f6793d9825dd5ce79b525cb7", size = 194295, upload-time = "2026-03-09T13:15:38.22Z" }, + { url = "https://files.pythonhosted.org/packages/b5/91/53255615acd2a1eaca307ede3c90eb550bae9c94581f8c00081b6b1c8f44/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-win_amd64.whl", hash = "sha256:1f1489f769582498610e015a8ef2d36f28f505ab3096d0e16b4858a9ec214f57", size = 75987, upload-time = "2026-03-09T13:15:39.65Z" }, +] + +[[package]] +name = "matplotlib" +version = "3.11.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "contourpy" }, + { name = "cycler" }, + { name = "fonttools" }, + { name = "kiwisolver" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pillow" }, + { name = "pyparsing" }, + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/49/64/f9a391af28f518b11ad45a8a712353c94a0aefce09d3703200e5c54b610a/matplotlib-3.11.1.tar.gz", hash = "sha256:69647db5746941c793d6e445a4cd349323ffb87d9cc958c2ad84a659b4832d30", size = 32612045, upload-time = "2026-07-18T03:39:46.63Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f2/6c/7ef7ebcb2bd9739b2b66b18b076e077f44bb46fdbe28ca0506edb3c62c79/matplotlib-3.11.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e15ef41507f3d525f46154ac9e3ae785dacde9f20e593a25de8986267892ef74", size = 9453849, upload-time = "2026-07-18T03:38:19.593Z" }, + { url = "https://files.pythonhosted.org/packages/eb/f8/6d0c312c8d9738e7d9677f09fe5c986b3239e651a7b73a2deb38b65e4a71/matplotlib-3.11.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:21a67b961a6d597bca54fae826cd20695ba4a6e4d05424a08da6e13e3176fd6b", size = 9283113, upload-time = "2026-07-18T03:38:21.95Z" }, + { url = "https://files.pythonhosted.org/packages/c9/cf/b4ad2cc81b6672ea29ea04e64e350a9f9b493b0908ccd884c67eeff8f7b2/matplotlib-3.11.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ba8f811b8ddfac493734d6af0b2dff96919d0c28ca0d641858dab4262777c6ea", size = 10035615, upload-time = "2026-07-18T03:38:24.315Z" }, + { url = "https://files.pythonhosted.org/packages/88/90/4e10e033d9b66589d8ed98b84c95cdbb57033d57c1f41339d7393dbd2f2e/matplotlib-3.11.1-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c52f7ad20ef476806ed212380b1d54d20310c8b86bdc2c9a68b51f0024a44472", size = 10842559, upload-time = "2026-07-18T03:38:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/88/eb/799612d0f8cd3e816a10fec59329fca52cd2353264df80378dfc541ae855/matplotlib-3.11.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8b14eb22961fe865efb0e4ff167e333e428908b00115a8d800ccb65ee108e481", size = 10927532, upload-time = "2026-07-18T03:38:28.532Z" }, + { url = "https://files.pythonhosted.org/packages/88/89/56649bbaa2fd12e20f3be03dbcc135b0c8676d88bac17977599e3eb442a0/matplotlib-3.11.1-cp312-cp312-win_amd64.whl", hash = "sha256:88a2a27dd9691ae448dfae4b26f59036be90c3c28757edd3553a29559d00859f", size = 9333886, upload-time = "2026-07-18T03:38:30.477Z" }, + { url = "https://files.pythonhosted.org/packages/c1/11/4d124efbbad677b7b7552f6f85a3bd432d4232f95400cea98fcd2ae36ef3/matplotlib-3.11.1-cp312-cp312-win_arm64.whl", hash = "sha256:480194afceca4df2f137c2721227d3cba67121fbf4397b69cee7f83714b0a58a", size = 9007545, upload-time = "2026-07-18T03:38:32.833Z" }, +] + +[[package]] +name = "ml-dtypes" +version = "0.5.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0e/4a/c27b42ed9b1c7d13d9ba8b6905dece787d6259152f2309338aed29b2447b/ml_dtypes-0.5.4.tar.gz", hash = "sha256:8ab06a50fb9bf9666dd0fe5dfb4676fa2b0ac0f31ecff72a6c3af8e22c063453", size = 692314, upload-time = "2025-11-17T22:32:31.031Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/b8/3c70881695e056f8a32f8b941126cf78775d9a4d7feba8abcb52cb7b04f2/ml_dtypes-0.5.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a174837a64f5b16cab6f368171a1a03a27936b31699d167684073ff1c4237dac", size = 676927, upload-time = "2025-11-17T22:31:48.182Z" }, + { url = "https://files.pythonhosted.org/packages/54/0f/428ef6881782e5ebb7eca459689448c0394fa0a80bea3aa9262cba5445ea/ml_dtypes-0.5.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a7f7c643e8b1320fd958bf098aa7ecf70623a42ec5154e3be3be673f4c34d900", size = 5028464, upload-time = "2025-11-17T22:31:50.135Z" }, + { url = "https://files.pythonhosted.org/packages/3a/cb/28ce52eb94390dda42599c98ea0204d74799e4d8047a0eb559b6fd648056/ml_dtypes-0.5.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ad459e99793fa6e13bd5b7e6792c8f9190b4e5a1b45c63aba14a4d0a7f1d5ff", size = 5009002, upload-time = "2025-11-17T22:31:52.001Z" }, + { url = "https://files.pythonhosted.org/packages/f5/f0/0cfadd537c5470378b1b32bd859cf2824972174b51b873c9d95cfd7475a5/ml_dtypes-0.5.4-cp312-cp312-win_amd64.whl", hash = "sha256:c1a953995cccb9e25a4ae19e34316671e4e2edaebe4cf538229b1fc7109087b7", size = 212222, upload-time = "2025-11-17T22:31:53.742Z" }, + { url = "https://files.pythonhosted.org/packages/16/2e/9acc86985bfad8f2c2d30291b27cd2bb4c74cea08695bd540906ed744249/ml_dtypes-0.5.4-cp312-cp312-win_arm64.whl", hash = "sha256:9bad06436568442575beb2d03389aa7456c690a5b05892c471215bfd8cf39460", size = 160793, upload-time = "2025-11-17T22:31:55.358Z" }, +] + +[[package]] +name = "numpy" +version = "2.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/22/fd/89965aa4ac08c74998539fcbf24fa3540f3e15237fbeb6bcf9c908f4aade/numpy-2.5.1.tar.gz", hash = "sha256:a48a113e6afea91f5608793bafa7ef2ad481fefbda87ec5069f483de61cb9fa3", size = 20755553, upload-time = "2026-07-04T17:08:00.933Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/7b/14687aa674250e5e546f616f486b0d56d3631cd5b2415739141ce40bdcea/numpy-2.5.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2c889b56fe48b1018f764b0eec8df59ab654e9148aa91faa12596043500de277", size = 16801574, upload-time = "2026-07-04T17:06:12.423Z" }, + { url = "https://files.pythonhosted.org/packages/e1/19/cc5bb2a3f2913d27d6dbb2c78d25921fabaedc6741d4a5a615a11f3c5bf3/numpy-2.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ab451b59c5643c570974c43aef780703ef1d3b4965d2be07afd530615a9358d1", size = 11772250, upload-time = "2026-07-04T17:06:15.726Z" }, + { url = "https://files.pythonhosted.org/packages/42/77/fdf34a71dd30f54979b18603bee915e0aaf825b07afe79acd60b04b691e2/numpy-2.5.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:78798bd5b9ad744056af8efa90e3b9ddaa53272a0848a483084a1cc0a13b2dc0", size = 5331516, upload-time = "2026-07-04T17:06:17.913Z" }, + { url = "https://files.pythonhosted.org/packages/ce/e2/eb7efa015b4cce41e2517bf182a7fce0d7d5b9d9ed76a29bfa0f4fe4505c/numpy-2.5.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:2ae0ca40bcb22d6ba59c1dfd5446f49940b0f2d821fde133f10dda11f816b84e", size = 6664863, upload-time = "2026-07-04T17:06:20.02Z" }, + { url = "https://files.pythonhosted.org/packages/a9/4b/a2b32dd94ee9ffbeecb28152240042a3949db33b1c834d44090b80e1b3b8/numpy-2.5.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:61ac47e772e6b8ea489e1d2f441a34c5c3ac17327e7ce294cbdf535795ad4e75", size = 15167977, upload-time = "2026-07-04T17:06:21.621Z" }, + { url = "https://files.pythonhosted.org/packages/b8/a9/6e73d68500f80773f65f0654ea932019d6694329a0eb0ed0533de38df376/numpy-2.5.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:59fda5e192b570217ec2580c96f00e9a7e12ef6866a900eb089b62c1a32545ca", size = 16672469, upload-time = "2026-07-04T17:06:24.064Z" }, + { url = "https://files.pythonhosted.org/packages/24/7d/ad3e59015135f5261c95fd4cafeff159c955febd83a99a1d9250c4233815/numpy-2.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f7119ebff1a9829e9f431a4f9d28e703023bb6b9fe7c8f724467dbfc27c94ab3", size = 16527531, upload-time = "2026-07-04T17:06:26.69Z" }, + { url = "https://files.pythonhosted.org/packages/83/d0/a39b2fbcde9cb17a1dac678f254b33a6336298af9df338824c685425d5e8/numpy-2.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e824c2acf8862052246be5a44c15da1777940c60d010dd2aab897824d9c430f9", size = 18431940, upload-time = "2026-07-04T17:06:29.521Z" }, + { url = "https://files.pythonhosted.org/packages/04/12/cff070947791c1ed425ff76413189adbdc2fbe215eba7ce7fa454a03c7f8/numpy-2.5.1-cp312-cp312-win32.whl", hash = "sha256:08d60c810432eb83360958dea0999ac4cfb94531ea8efcbf0b7f277c2068aeb2", size = 6066764, upload-time = "2026-07-04T17:06:32.571Z" }, + { url = "https://files.pythonhosted.org/packages/65/66/53f31807a48a750f9d748da273bc3fcedd12b27ff1f3e373bfec55ef2dc0/numpy-2.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:f7d60026c0bdb1380e83bfa7a0419c4577ee4b9a08880afcb6dadeb74c649fa2", size = 12430966, upload-time = "2026-07-04T17:06:34.926Z" }, + { url = "https://files.pythonhosted.org/packages/2b/2a/d1a88066b1c14186f5d3c0d18c94f17b064511982bab0578d49ee9d43c29/numpy-2.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:17a25e09640602e10bc8de0e6fa2b3fd68eedd84ba6d7842dc8f32f9ab87bd0b", size = 10350488, upload-time = "2026-07-04T17:06:37.785Z" }, +] + +[[package]] +name = "opt-einsum" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/b9/2ac072041e899a52f20cf9510850ff58295003aa75525e58343591b0cbfb/opt_einsum-3.4.0.tar.gz", hash = "sha256:96ca72f1b886d148241348783498194c577fa30a8faac108586b14f1ba4473ac", size = 63004, upload-time = "2024-09-26T14:33:24.483Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/23/cd/066e86230ae37ed0be70aae89aabf03ca8d9f39c8aea0dec8029455b5540/opt_einsum-3.4.0-py3-none-any.whl", hash = "sha256:69bb92469f86a1565195ece4ac0323943e83477171b91d24c35afe028a90d7cd", size = 71932, upload-time = "2024-09-26T14:33:23.039Z" }, +] + +[[package]] +name = "packaging" +version = "26.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, +] + +[[package]] +name = "pillow" +version = "12.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/3d/bb7fca845737cf9d7dbde16ed1843984665ff2e0a518f5db43e77ec540b9/pillow-12.3.0.tar.gz", hash = "sha256:3b8182a766685eaa002637e28b4ec8d6b18819a0c71f579bf0dbaa5830297cce", size = 47025035, upload-time = "2026-07-01T11:56:38.965Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/37/bf/fb3ebff8ddcb76aac5a01389251bbbb9519922a9b520d8247c1ca864a25d/pillow-12.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ba09209fbe443b4acccebe845d8a138b89a8f4fbaeedd44953490b5315d5e965", size = 5345969, upload-time = "2026-07-01T11:54:06.397Z" }, + { url = "https://files.pythonhosted.org/packages/d8/66/9a386a92561f402389a4fc70c18838bf6d35eb5eb5c6850b4b2dc64f5048/pillow-12.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffd0c5368496f41b0944be820fcb7a838aa6e623d250b01acf2643939c3f99d7", size = 4780323, upload-time = "2026-07-01T11:54:09.351Z" }, + { url = "https://files.pythonhosted.org/packages/25/27/ac8f99618ffd3dde21db0f4d4b1d2ab00c0880595bfd17df103f7f39fd0c/pillow-12.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d9c7f76c0673154f044e9d78c8655fb4213f6ca31a836df48b40fe5d187717b9", size = 6266838, upload-time = "2026-07-01T11:54:11.71Z" }, + { url = "https://files.pythonhosted.org/packages/84/21/a35af28dcc61f37ed850a2d64c65c701321dfbf25085e469d5559360cbbf/pillow-12.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:78cb2c6865a35ab8ff8b75fd122f6033b92a62c82801110e48ddd6c936a45d91", size = 6940830, upload-time = "2026-07-01T11:54:13.732Z" }, + { url = "https://files.pythonhosted.org/packages/eb/51/8b08617af3ad95e33ce6d7dd2c99ed6c8298f7fb131636303956be022e25/pillow-12.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e491916b378fba47242221bb9ead245211b70d504f495d105d17b14a24b4907c", size = 6344383, upload-time = "2026-07-01T11:54:15.756Z" }, + { url = "https://files.pythonhosted.org/packages/1d/72/cf78ac9780bb93c28328f408973845a309d4d145041665f734572ced1b52/pillow-12.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0dd2064cbc55aaec028ef5fbb60fa47bb6c3e7918e07ff17935284b227a9d2df", size = 7052934, upload-time = "2026-07-01T11:54:17.721Z" }, + { url = "https://files.pythonhosted.org/packages/20/20/25e0f4dc178a6bc0696793720055519a0de89e7661dae886992decbd2f81/pillow-12.3.0-cp312-cp312-win32.whl", hash = "sha256:dbce0b29841537a2fa4a214c2bbf14de3587c9680caa9b4e217568472490b28f", size = 6472684, upload-time = "2026-07-01T11:54:19.839Z" }, + { url = "https://files.pythonhosted.org/packages/45/89/da2f7971a317f83d807fdd4065c0af40208e59e692cc43d315a71a0e96d1/pillow-12.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a2b55dd6b2a4c4b7d87ffa56bdb33fdc5fdb9a462173861a7bc097f17d91cb09", size = 7227137, upload-time = "2026-07-01T11:54:22.025Z" }, + { url = "https://files.pythonhosted.org/packages/de/47/4845a0a6c0dbf1db8456bd9fc791f13c5ced7ced20606d08a0aacfd25b49/pillow-12.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:331b624368d4f1d069149002f25f44bc61c8919ce8ddb3c45bdad8f6e2d89510", size = 2568267, upload-time = "2026-07-01T11:54:24.051Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "pygments" +version = "2.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, +] + +[[package]] +name = "pyparsing" +version = "3.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/91/9c6ee907786a473bf81c5f53cf703ba0957b23ab84c264080fb5a450416f/pyparsing-3.3.2.tar.gz", hash = "sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc", size = 6851574, upload-time = "2026-01-21T03:57:59.36Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d", size = 122781, upload-time = "2026-01-21T03:57:55.912Z" }, +] + +[[package]] +name = "pytest" +version = "9.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "scipy" +version = "1.18.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a7/25/c2700dfaf6442b4effaa91af24ebce5dc9d31bb4a69706313aae70d72cd0/scipy-1.18.0.tar.gz", hash = "sha256:67b2ad2ad54c72ca6d04975a9b2df8c3638c34ddd5b28738e94fc2b57929d378", size = 30774447, upload-time = "2026-06-19T15:01:43.456Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/19/ca10ead60b0acc80b2b833c2c4a4f2ff753d0f58b811f70d911c7e94a25c/scipy-1.18.0-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:7bd21faaf5a1a3b2eff922d02db5f191b99a6518db9078a8fb23169f6d22259a", size = 31056519, upload-time = "2026-06-19T14:59:45.203Z" }, + { url = "https://files.pythonhosted.org/packages/96/72/1e6442a00cd2924d361aa1b642ab6373ec35c6fabf311a760be9f76e0f13/scipy-1.18.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:265915e79107de9f946b855e50d7470d5893ec3f54b342e1aa6201cbdcd8bb6b", size = 28681889, upload-time = "2026-06-19T14:59:48.103Z" }, + { url = "https://files.pythonhosted.org/packages/9b/2d/11dd93d21e147a73ba22bd75c0b9208d3a2e0ec76d53170ce7d9029b1015/scipy-1.18.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9ab7b758be6940954a713ee466e2043e9f6e2ed965c1fce5c91039f4be3d90a9", size = 20423580, upload-time = "2026-06-19T14:59:50.665Z" }, + { url = "https://files.pythonhosted.org/packages/9c/01/93552f75e0d2a7dd115a45e59209c51e8d514daff02fc887d2623be06fe1/scipy-1.18.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:97b6cddaaee0a779ef6b5ca83c9604b27cc16b2b8fc22c142652df8793319fb8", size = 23054441, upload-time = "2026-06-19T14:59:53.564Z" }, + { url = "https://files.pythonhosted.org/packages/3c/23/21f5e703643d66f21faa6b4c73195bfcad70c55efcb4f1ab327cd7c4101a/scipy-1.18.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:52a96e21517c7292375c0e27dd796a811f03fcea5fd4d108fdfea8145dcf17ab", size = 33968720, upload-time = "2026-06-19T14:59:56.415Z" }, + { url = "https://files.pythonhosted.org/packages/dd/aa/1b939f6c67ed68635bb538e6752d3dacc02f66535182e939a89581a44e9c/scipy-1.18.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f55797419e16e7f30cf88ffb3113ce0467f00cfe3f70d5c281730b21769bfc2", size = 35287115, upload-time = "2026-06-19T14:59:59.411Z" }, + { url = "https://files.pythonhosted.org/packages/b6/ff/eec46be7e9234208f801062b53e1983085eddebd693f6c9bfb03b459830d/scipy-1.18.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ad033410e2e0672ffdc1042110cef20e1c46f8fd0616cee1d44d8d58fad8fc11", size = 35577989, upload-time = "2026-06-19T15:00:02.235Z" }, + { url = "https://files.pythonhosted.org/packages/84/ca/210d4759c7210bb7d269437421959b39a33434e2776b60c5cb8a763bb30a/scipy-1.18.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4a55985d54c769c872e64b7f4c8a81cc30ef700cc04296abbbf3705439c126de", size = 37421717, upload-time = "2026-06-19T15:00:05.102Z" }, + { url = "https://files.pythonhosted.org/packages/2b/54/9a9edb45345bd6744da5ddfb6628e5d5185920494c6a67ec45b6381004cb/scipy-1.18.0-cp312-cp312-win_amd64.whl", hash = "sha256:71ccc8faa2dd16ac310233203474a8b5cb67f10dedd54a3116d34943f4b19132", size = 36597428, upload-time = "2026-06-19T15:00:08.112Z" }, + { url = "https://files.pythonhosted.org/packages/99/0e/33f32a2a58987e26aec0f7df252cbbad1e90ae77bdbc76f40dd4ed0cf0ea/scipy-1.18.0-cp312-cp312-win_arm64.whl", hash = "sha256:d88363fd9d8fbd3511bd273f1a49efb2a540773ddf92a91d57498ce7dd7f3e76", size = 24351481, upload-time = "2026-06-19T15:00:11.103Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] diff --git "a/tracks/qmc/solutions/frustration-free/challenge-113/\346\214\221\346\210\230113\346\261\207\346\212\245.md" "b/tracks/qmc/solutions/frustration-free/challenge-113/\346\214\221\346\210\230113\346\261\207\346\212\245.md" new file mode 100644 index 000000000..b1490b035 --- /dev/null +++ "b/tracks/qmc/solutions/frustration-free/challenge-113/\346\214\221\346\210\230113\346\261\207\346\212\245.md" @@ -0,0 +1,883 @@ +# 挑战113汇报 + +> **题目**:[Sim-to-Real for Quantum Gates(量子门的仿真到现实迁移)](https://github.com/QuantumBFS/quantum.harness/issues/113) +> **赛道**:量子线路模拟(QCS)· 可微分编程 · 量子最优控制 +> **团队成员**:蒋伟琪、陈硕、马追景 +> **实现目录**:`tracks/qcs/solutions/frustration-free/challenge-113/` +> **生产代码版本**:`07c21a85caa3c4c0c0a20649a377e3ad4e30473b` +> **报告口径**:截至 2026-07-30 19:45(UTC+8);所有结论均与代码版本、生产快照和审计证据逐项绑定。 + +--- + +## 摘要 + +真实量子芯片上的每一次脉冲评估都需要实验查询和测量 shots,不能像模拟器一样直接求梯度。挑战 113 的核心想法是:先在便宜、可微分的模型上找到高保真控制脉冲和损失 Hessian 的主曲率方向,再只沿这些方向对昂贵的黑盒设备做无梯度闭环优化。对于一个可控、资源充足、目标为 \(d\) 维酉门的系统,过程保真度对全局相位不敏感,因此局部曲率的有效维数预期为 + +\[ +\dim \mathfrak{su}(d)=d^2-1. +\] + +我们从零实现了完整的三阶段流水线: + +1. JAX 可微分的分段常数脉冲传播和多起点 L-BFGS-B 开环优化; +2. 稠密 Hessian、Hessian-vector product、端点 Jacobian、曲率秩和子空间几何; +3. 严格查询边界下的噪声黑盒设备,以及 full、model-Hessian、random、oracle 四类 CMA-ES 闭环搜索。 + +在工程层面,我们进一步实现了不可伪造的查询账本、查询/shot 双重计费、配对随机实验、原子产物发布、并发 claim、崩溃恢复、严格 schema 校验、bootstrap 分析和五张论文级图的生成器。为适配 LASG02 的 glibc 2.17,生产计算使用哈希锁定的 Apptainer 运行时。 + +我们已经取得以下可验证成果: + +- 单比特和双比特控制系统均达到预期可控维数,规则最优点的有效 Hessian 秩分别为 \(3\) 和 \(15\),与 \(d^2-1\) 一致; +- 代表性双比特、80 参数先导任务完成 881 次严格计费查询,产物校验为 `valid=true`; +- 生产作业 `2818121` 的 180 个 Slurm array task 全部以 `COMPLETED / 0:0` 结束; +- 9,500 个规范化配对试验全部生成并下载,本地使用生产代码和完全一致的 Python 3.12.12 环境严格验证:`completed=9500, pending=0, errors=[], valid=true`; +- 生产级 bootstrap 汇总器和五张统计图生成器已经实现;9,500 条完整数据可在后续直接恢复分析,无需重跑超算。本文当前聚焦已经闭环的几何结论、端到端方法、生产计算和严格审计。 + +这项工作的价值不只是“写了一个优化器”,而是把一个容易出现数据泄漏、基线不公平和查询漏计的 sim-to-real 演示,做成了可审计、可恢复、可扩展的数值实验系统,并完成了一轮 9,500-trial 的真实生产运行。 + +--- + +# 正文 + +## 1. 问题背景:为什么量子门标定需要 sim-to-real + +量子门由时间依赖哈密顿量产生。对漂移哈密顿量 \(H_0\)、控制哈密顿量 \(H_j\) 和控制波形 \(u_j(t)\),传播满足 + +\[ +\frac{\mathrm d U(t)}{\mathrm dt} +=-i\left(H_0+\sum_j u_j(t)H_j\right)U(t), +\qquad U(0)=I. +\] + +目标是找到一组波形,使终态传播子 \(U(T)\) 接近目标门 \(U_{\mathrm{target}}\)。本文使用相位不敏感的过程保真度 + +\[ +F(U,U_{\mathrm{target}}) +=\frac{\left|\operatorname{Tr} +\left(U_{\mathrm{target}}^\dagger U\right)\right|^2}{d^2}, +\qquad +\mathcal L=1-F. +\] + +### 1.1 开环:便宜、可微,但模型不等于设备 + +在模拟器中,\(H_0\)、\(H_j\) 和传播过程都已知,可以通过自动微分得到 +\(\nabla_u\mathcal L\),再用 L-BFGS-B、GRAPE 等方法快速求得模型最优脉冲 \(u^\star\)。 + +问题是实际芯片总有模型误差: + +- 漂移项标定偏差; +- 控制增益误差; +- 模型中遗漏的耦合; +- 随时间变化的漂移; +- 测量统计噪声。 + +因此模型上高保真的脉冲,到了真实设备上可能失效。 + +### 1.2 闭环:能修正模型误差,但真实查询昂贵 + +闭环标定把设备放进优化循环: + +1. 发送一组脉冲; +2. 在设备上执行并测量; +3. 从有限 shots 得到一个带噪声的标量保真度; +4. 无梯度优化器提出下一组脉冲。 + +它能适应未知误差,但每次目标函数评估都是一次昂贵实验,而且无法穿过真实硬件求梯度。若直接在 \(p\) 个原始脉冲参数中搜索,查询复杂度会随维数迅速增加。 + +### 1.3 为什么曲率维数是 \(d^2-1\) + +在目标门附近写成 + +\[ +U(T)=U_{\mathrm{target}}e^{iA}, +\] + +其中 \(A\) 是小的 Hermitian 生成元。损失的二阶展开为 + +\[ +\mathcal L \approx +\frac{1}{2d}\operatorname{Tr}\left[ +\left(A-\frac{\operatorname{Tr}A}{d}I\right)^2 +\right]. +\] + +损失只看到 \(A\) 的无迹部分。\(A\propto I\) 只改变全局相位,而过程保真度对全局相位不敏感。无迹 Hermitian 矩阵构成 \(\mathfrak{su}(d)\),维数正好是 \(d^2-1\)。 + +因此,在系统可控、控制时间和带宽充足、最优点足够规则时,即使脉冲有几百个参数,真正有曲率的局部方向仍只应有: + +- 单比特 \(d=2\):\(d^2-1=3\); +- 双比特 \(d=4\):\(d^2-1=15\); +- 三比特 \(d=8\):\(d^2-1=63\)。 + +其余参数不是“无用”,而是沿着实现同一目标门的近似平坦解流形移动。 + +## 2. 我们的研究问题 + +我们把题目拆成四个可检验问题: + +1. **不变量**:单比特和双比特系统的 Hessian 有效秩是否分别为 3 和 15? +2. **节省**:模型 Hessian 主子空间是否比全参数搜索使用更少的黑盒查询和 shots? +3. **失效边界**:模型—设备差距 \(\varepsilon\) 墌大时,模型子空间何时因旋转而失效? +4. **噪声**:有限 shots 是否会进一步放大高维无梯度搜索的劣势? + +实验中比较四种搜索空间: + +- `full`:全部原始脉冲坐标; +- `model_hessian`:模型 Hessian 按曲率排序的前 \(k\) 个方向; +- `random`:同维数随机正交子空间,用来排除“只因为维数小”的解释; +- `oracle`:从真实设备的离线精确 Hessian 得到的方向,只作为诊断上界,绝不提供给黑盒优化器。 + +## 3. 完整方法 + +```mermaid +flowchart LR + A["可微模型
H0, Hj"] --> B["开环优化
JAX + L-BFGS-B"] + B --> C["模型最优脉冲 u*"] + C --> D["Hessian / HVP
端点 Jacobian"] + D --> E["模型主子空间 Vk"] + C --> F["受扰动真值设备
仅 query()"] + E --> G["u = u* + Vk c"] + F --> H["有限 shots 观测"] + G --> I["CMA-ES 闭环搜索"] + H --> I + I --> J["查询数、shots、成功率"] + J --> K["配对统计与 bootstrap"] + + R["随机子空间"] --> I + Q["oracle 子空间
仅离线诊断"] --> K + P["全参数基线"] --> I +``` + +图 1:Challenge 113 的三阶段 sim-to-real 流水线。模型可以求导;真值设备只暴露带计费的标量查询接口。 + +### 3.1 控制系统 + +实现包含两个系统: + +**单比特系统** + +- \(H_0=0.37Z\)(代码中的 Pauli 基按 Frobenius 范数归一化); +- 控制项 \(X,Y\); +- 目标门为 Hadamard; +- 2 个控制通道、12 个时间段,共 \(p=24\) 个生产参数; +- 总控制时间 \(T=1\)。 + +**双比特系统** + +- \(H_0=0.31\,ZI+0.47\,IZ+0.23\,ZZ\)(同样使用归一化 Pauli 基); +- 控制项 \(XI,YI,IX,IY\); +- 目标门为 CNOT; +- 4 个控制通道、20 个时间段,共 \(p=80\) 个生产参数; +- 总控制时间 \(T=8\)。 + +双比特 \(T=8\) 不是任意选择。早期 \(T=1\) 时优化损失停在约 0.44,系统在给定耦合强度和时间内无法到达 CNOT 类目标。扫描控制时间后确认可达性阈值约在 \(T\approx6.83\),因此生产配置采用 \(T=8\)。这也是本项目中“先判断物理可达性,再调优化器”的一个重要教训。 + +### 3.2 脉冲、传播与目标函数 + +脉冲采用有硬边界的分段常数表示。归一化坐标始终位于 \([-1,1]\),再乘以各控制通道的幅度尺度。每个时间片使用矩阵指数 + +\[ +U_{n+1}=\exp[-i\Delta t\,H(u_n)]U_n +\] + +传播,避免通用 ODE 积分带来的非幺正漂移。JAX 负责 `expm`、目标函数和导数。 + +### 3.3 开环模型优化 + +开环阶段使用确定性的多起点 L-BFGS-B。模型随机种子固定为 `model_seed=5`,与后续真值扰动和测量随机性分离。优化后再使用端点方程的有界 least-squares polishing,把残余非平稳性压低,避免有限梯度残差在 Hessian 中制造伪曲率方向。 + +### 3.4 景观和子空间 + +景观模块同时提供: + +- 小规模稠密 Hessian; +- JAX Hessian-vector product; +- `scipy.sparse.linalg.eigsh` 的矩阵自由主特征方向; +- 端点 Jacobian; +- 相对阈值下的有效秩; +- 模型/真值子空间主角和双向正交残差; +- 有界端点 polishing。 + +矩阵自由谱提取按特征值绝对值处理,而不是只取最大代数特征值,防止遗漏绝对值大的负曲率方向。 + +### 3.5 查询黑盒 + +真值系统由模型经过三类可重复扰动构造: + +- 漂移哈密顿量方向扰动; +- 控制通道增益误差; +- 未建模 Hermitian 项。 + +总扰动强度由 gap \(\varepsilon\) 控制。黑盒只公开 + +```text +query(pulse) -> noisy scalar observation +``` + +而不公开真值哈密顿量、梯度或精确保真度。有限 shots 模式使用 Bernoulli 估计;`shots=None` 表示精确观测模式。正式成功判定使用独立的高 shots 验证查询。 + +每次调用在执行前先占用单调递增的 attempt index。成功、失败和中止都会留下记录,防止异常路径获得“免费查询”。账本分别统计: + +- optimizer queries; +- validation queries; +- optimizer shots; +- validation shots; +- total queries; +- total shots。 + +离线精确评估器放在独立模块中,且调用它不会改变设备账本。 + +### 3.6 闭环搜索和公平基线 + +四类方法统一使用 pycma CMA-ES,具有相同的: + +- 查询预算; +- 设备实例; +- 扰动种子; +- 测量随机流; +- 初始模型脉冲; +- 验证规则。 + +`model_hessian` 和 `random` 只改变搜索基底。`k=p` 时,model-Hessian 会精确退化到 full 的单位坐标、边界和映射,避免“旋转后的超立方体”与原始 full 边界不等价。 + +pycma 4.4.4 在一维有初始化缺陷:它无法构造长度一的 bound-range 标准差限制向量。我们的修复只在 \(k=1\) 时关闭 `maxstd_boundrange`,保留原有边界变换、candidate clipping、ask/evaluate/tell、种子和计费,不添加虚假维度。 + +### 3.7 配对实验设计 + +生产矩阵覆盖: + +- gap:\(0,0.02,0.05,0.10,0.20\); +- 20 个扰动/试验种子; +- 双比特:\(k=5,10,15,20,30,80\),shots 为 exact、1,000、10,000; +- 单比特:\(k=1,2,3,4,6,24\),shots 为 exact、1,000; +- 方法:full、model-Hessian、random、oracle; +- 每个试验的优化预算:2,000 次设备查询。 + +full 方法会规范化到 \(k=p\),重复的 full 配置按内容 ID 去重,最终得到 9,500 个规范试验,而不是直接笛卡尔积的 12,000 条。 + +## 4. 结果 + +### 4.1 几何不变量 + +在相对阈值 \(10^{-8}\) 下,规则开环最优点得到: + +| 系统 | Hilbert 维数 \(d\) | 脉冲参数 \(p\) | 理论 \(d^2-1\) | 有效 Hessian 秩 | +|---|---:|---:|---:|---:| +| 单比特 Hadamard | 2 | 24 | 3 | 3 | +| 双比特 CNOT | 4 | 80 | 15 | 15 | + +这支持题目的核心局部几何判断:过参数化增加的是近似平坦解流形,而不是目标门附近的曲率维数。但这不是无条件定理;控制时间、带宽不足或不可控都会使秩下降。 + +### 4.2 代表性先导任务 + +先导证据来自代码版本 `dd16192953c130d738716238525760de73343e09`,配置为双比特、\(p=80\)、gap \(=0.05\)、model-Hessian \(k=4\)、精确观测、预算 2,000。 + +| 指标 | 实测值 | +|---|---:| +| 首次查询(含 JIT 编译) | 0.217 s | +| 19 次 warm query 总时间 | 0.0338 s | +| warm 查询吞吐 | 562 query/s | +| 开环优化 | 7.75 s | +| 稠密 Hessian 景观 | 5.81 s | +| 几何诊断 | 1.67 s | +| 受限空间优化 | 0.430 s / 8 evaluations | +| 完整先导查询数 | 881 | +| 完整先导墙钟时间 | 21.91 s | +| 峰值 RSS | 864,260 KiB | +| 严格产物验证 | `valid=true` | + +该先导任务证明一条 trial 从模型准备、设备查询、账本、离线诊断到产物校验可以端到端闭合。它不等价于生产统计结论。 + +### 4.3 生产运行 + +生产代码版本为 `07c21a85caa3c4c0c0a20649a377e3ad4e30473b`。LASG02 的 Slurm `MaxArraySize` 和 `GrpSubmitJobs` 不允许直接提交 9,500 元素数组,因此使用 180 个 array slice,每个 slice 处理规范计划中 52 或 53 个 trial,并限制最多 8 个 slice 并发。 + +| 生产审计项 | 结果 | +|---|---:| +| Slurm 作业 | `2818121` | +| array task | 180 | +| 成功 task | 180 | +| 非零退出 task | 0 | +| 使用节点数 | 22 | +| 单 task 最短/中位/最长 | 512 s / 1,432.5 s / 9,832 s | +| task wall-time 总和 | 129.47 h | +| trial 文件 | 9,500 | +| 活动 owner claim | 0 | +| partial artifact | 0 | +| 远程 store 大小 | 5,440,450 KiB(约 5.19 GiB) | + +生产数据随后压缩下载到本地,并通过远程 SHA256 manifest。严格验证必须同时匹配: + +- 生产源代码; +- Python 3.12.12; +- JAX/JAXLIB 0.11.0; +- NumPy 2.5.1; +- SciPy 1.18.0; +- CPU x64; +- store manifest 和 trial plan。 + +初次 provenance 检查准确识别出本地 Python 3.12.13 与生产 Python 3.12.12 的差异,以及父 Git worktree 对提取目录的影响。切换到 Python 3.12.12,并用 `GIT_CEILING_DIRECTORIES` 隔离父仓库后,严格验证通过: + +```json +{"completed":9500,"errors":[],"expected":9500,"pending":0,"valid":true} +``` + +### 4.4 可直接恢复的统计收尾 + +原计划从 9,500 条数据做 10,000 次 deterministic cluster bootstrap,并生成: + +1. `queries_vs_dimension.png`; +2. `advantage_vs_gap.png`; +3. `subspace_rotation_and_floor.png`; +4. `rank_invariant_d2_d4.png`; +5. `failure_case.png`。 + +由于提交窗口临近,我们优先保全并验证完整生产快照,将耗时的最终 bootstrap 留作可恢复的离线收尾。因此当前状态是: + +- 生产数据完整性已经通过哈希和严格 schema 双重验证; +- 分析和绘图代码已经实现并覆盖测试; +- 五张生产图可由现有快照继续生成,不需要再次消耗超算资源; +- 当前正文只采用已经完成验证的几何、先导和生产审计数字; +- 后续由 `summary.json` 补充成功概率、置信区间、queries-to-target 优势和 gap crossover。 + +这保证报告中的每个定量判断都能回到明确证据,同时保留了完整数据继续回答 headline 科学问题的能力。 + +## 5. 为什么这些结果有用、为什么可以相信 + +### 5.1 物理上有用 + +如果完整统计最终支持模型 Hessian 子空间优势,那么真实硬件标定可以把 \(p\) 维搜索降到接近 \(d^2-1\) 维。例如双比特的 80 个分段脉冲参数可降到约 15 个方向;随着脉冲参数继续增加,内禀维数仍由目标酉群决定,而不是由参数化大小决定。 + +除量化优势区间外,这套实验还能定位方法的适用边界: + +- \(k\tau\max_j|\lambda_j|,\qquad \tau=10^{-8} +\] + +计数。使用相对阈值是为了跨单比特、双比特和不同参数化比较。 + +### B.3 子空间搜索 + +给定 origin \(u^\star\) 和正交基 \(V_k\in\mathbb R^{p\times k}\): + +\[ +u(c)=\operatorname{clip}(u^\star+V_kc,-1,1). +\] + +`SearchSpace` 保存不可变 origin、basis、坐标边界和来源哈希。full 使用单位基;random 使用确定性种子的 QR 正交基;model-Hessian 按 \(|\lambda|\) 排序;oracle 只由离线 evaluator 构建。 + +### B.4 成功和删失 + +优化查询只指导 CMA-ES。达到候选阈值后,用独立验证 shots 做 Wilson 置信认证。预算内未认证成功的 trial 不是缺失数据,而是右删失/失败,绘图时保留在预算端,不能从 query 分布中删除。 + +## C. 生产配置的精确生成 + +`default_sweep_configs("production")` 使用: + +```python +budget = 2000 +gaps = (0.0, 0.02, 0.05, 0.10, 0.20) +seeds = range(20) + +two_qubit: + segments = 20 + amplitude_bound = 4.0 + dimensions = (5, 10, 15, 20, 30, 80) + shots = (None, 1_000, 10_000) + +one_qubit: + segments = 12 + amplitude_bound = 4.0 + dimensions = (1, 2, 3, 4, 6, 24) + shots = (None, 1_000) + +methods = ("full", "model_hessian", "random", "oracle") +model_seed = 5 +trial_seed = perturbation_seed +``` + +`generate_paired_trials` 将 full 统一到系统参数总维数,按 canonical config 的 SHA256 ID 去重和排序,形成固定 9,500-trial plan。 + +## D. 随机性和统计单位 + +三个随机概念明确分开: + +- `model_seed=5`:开环模型准备;所有配对方法共享; +- `perturbation_seed`:真值设备方向; +- `trial_seed`:CMA-ES 与观测随机流。 + +统计分析不能把不同 system、segments、duration、gap、shots、dimension、budget 混在一起。full 结果在同一设备配置内只算一次,避免因其在多个 \(k\) 行上重复而形成伪重复。配对 bootstrap 的抽样单位是 seed/device cluster,而不是单条 query。 + +## E. 产物 schema 和发布协议 + +每个 store 至少包含: + +```text +ready.json +manifest.json +plan.json +index.json +.store.lock +claims/*.flock +trials/trial-.json +``` + +初始化协议: + +1. 取得 store 级 `flock`; +2. 写 `initializing.json`; +3. 校验 manifest、plan 和 provenance; +4. 原子发布 `ready.json`; +5. 后续调用逐字节比较 marker 和 canonical bytes。 + +trial 协议: + +1. 在锁内检查已存在 trial; +2. 创建带 host/PID/boot ID/nonce 的 owner claim; +3. 锁外运行计算; +4. 重新检查代码 provenance; +5. 临时文件写入、flush、fsync、rename; +6. 锁内更新 index 并释放 claim。 + +所有 JSON 使用排序键、禁止 NaN 的 canonical 编码。schema 校验不做字符串到数字等宽松强制转换。 + +## F. 运行时和超算复现 + +### F.1 本地开发 + +```bash +cd tracks/qcs/solutions/frustration-free/challenge-113 +uv sync --frozen --group dev +JAX_ENABLE_X64=1 JAX_PLATFORMS=cpu \ + uv run python -m pytest -q + +CHALLENGE113_DEVELOPMENT_OUTPUT="$PWD/results/development" \ + bash scripts/run_development.sh + +uv run python run.py validate \ + --output results/development +``` + +### F.2 生产运行时 + +生产环境固定为: + +- Apptainer 1.3.4; +- SIF:`uv-0.9.9-python3.12-bookworm-slim.sif`; +- SIF SHA256:`2405a769d520e6d0f680c0f1dff0d9f92083724f1ffd85ea0c26b5e36defa323`; +- Python 3.12.12; +- uv 0.9.9; +- glibc 2.36; +- JAX/JAXLIB 0.11.0; +- NumPy 2.5.1; +- SciPy 1.18.0; +- `JAX_ENABLE_X64=1`; +- `JAX_PLATFORMS=cpu`; +- 物理运行阶段 `--net --network none`。 + +只有一次明确确认的 `uv sync --frozen` 允许联网。后续 smoke、pilot 和 production 均不联网、不重新解析依赖,并逐项校验 SIF、archive、metadata、`pyproject.toml`、`uv.lock` 和 source revision 哈希。 + +### F.3 生产分片 + +实际提交的 wrapper 使用: + +```bash +test "${SLURM_ARRAY_TASK_COUNT}" = "180" + +/workspace/.venv/bin/python -u /workspace/run.py sweep \ + --kind production \ + --shard-index "${SLURM_ARRAY_TASK_ID}" \ + --shard-count "${SLURM_ARRAY_TASK_COUNT}" \ + --output /output/production +``` + +提交拓扑为 `0-179%8`。每个 shard 读取同一个完整 plan,但只执行满足 + +\[ +\text{canonical index}\bmod 180 +=\text{SLURM\_ARRAY\_TASK\_ID} +\] + +的 trial。 + +## G. 严格验证和最终分析复现 + +### G.1 已完成的严格验证 + +从生产 revision 解压独立 source,必须避免父 Git worktree 污染 provenance: + +```bash +export RUNTIME=/absolute/path/07c21a8-validation-runtime +export SOURCE="$RUNTIME/tracks/qcs/solutions/frustration-free/challenge-113" +export SNAPSHOT=/absolute/path/.07c21a8-production-002.incoming/store + +uv python install 3.12.12 +uv sync --python 3.12.12 --frozen --group dev --project "$SOURCE" + +GIT_CEILING_DIRECTORIES="$RUNTIME" \ +PYTHONPATH="$SOURCE/src" \ +JAX_ENABLE_X64=1 \ +JAX_PLATFORMS=cpu \ +"$SOURCE/.venv/bin/python" "$SOURCE/run.py" validate \ + --output "$SNAPSHOT" +``` + +预期唯一成功口径: + +```json +{ + "completed": 9500, + "errors": [], + "expected": 9500, + "pending": 0, + "valid": true +} +``` + +随后再在 store 内执行: + +```bash +sha256sum -c ../control/remote-store.sha256 +``` + +### G.2 可恢复的最终统计分析 + +最终分析脚本不会修改 snapshot;它先再次严格验证,再做 deterministic bootstrap,写到临时目录,最后原子 rename: + +```bash +PYTHONPATH="$SOURCE/src" \ +JAX_ENABLE_X64=1 \ +JAX_PLATFORMS=cpu \ +"$SOURCE/.venv/bin/python" scripts/finalize_snapshot.py \ + --snapshot /absolute/path/to/snapshot/store \ + --output /absolute/path/to/07c21a8-production-002.analysis \ + --source-revision 07c21a85caa3c4c0c0a20649a377e3ad4e30473b \ + --run-id production-002 \ + --expected-trials 9500 \ + --snapshot-manifest /absolute/path/to/remote-store.sha256 \ + --snapshot-manifest-sha256 \ + --source-archive /absolute/path/to/challenge-113-07c21a8.tar.gz \ + --source-archive-sha256 c2c7c47a14cc667c43195d18a0933a2c914cf006ab094a2165b3d4a35f58a532 \ + --deployment-metadata /absolute/path/to/challenge-113-07c21a8.deployment.json \ + --deployment-metadata-sha256 15c119baa505b4b9eff7f656e4088fab107144d2304a613b4b8b22cfdef424e1 \ + --bootstrap-seed 113 \ + --bootstrap-samples 10000 \ + --bootstrap-confidence 0.95 \ + --bootstrap-chunk-size 256 +``` + +输出应包括 `summary.json`、`analysis_provenance.json`、`finalization_manifest.json` 和五张 PNG。相同绑定再次运行时只验证已有输出并返回 `resumed`,不会覆盖。 + +## H. 测试覆盖 + +15 个测试模块分别覆盖: + +- 配置和 canonical ID; +- 系统 Hermitian/Unitary/可控性; +- JAX 传播、有限差分梯度和非法 traced 输入; +- 开环可达性和确定性; +- Hessian/HVP/端点 Jacobian/秩/子空间; +- 黑盒隐私边界、失败计费、账本认证; +- 离线 truth 不改变计费; +- CMA-ES 四类空间、公平性和一维回归; +- 原子 store、并发 claim、stale reclaim、orphan adoption; +- 完整 production plan 和 shard 覆盖; +- 分层统计、Wilson 区间、cluster bootstrap、删失; +- 图中 artist 语义、固定样式、golden SHA256; +- evidence、部署哈希、Apptainer 和 Slurm gate; +- post-production finalization 的原子性和 resume。 + +## I. 关键技术突破与工程修复 + +1. **双比特损失停在约 0.44**:不是优化器参数问题,而是 \(T=1\) 时 CNOT 不可达;改为 \(T=8\)。 +2. **双比特 Hessian 秩得到 19 而不是 15**:有限端点残差制造伪曲率;增加端点 least-squares polishing 后恢复 15。 +3. **矩阵自由谱可能漏负特征值**:从 largest algebraic 改为按绝对值完整提取。 +4. **设备真值可从 Python closure 恢复**:拆出离线 evaluator,公开结果与内部账本分离。 +5. **异常查询不计费**:调用开始即保留 attempt index,失败也记录。 +6. **并发 claim 竞争和崩溃重复计算**:`flock`、owner identity、原子 rename 和 orphan adoption。 +7. **full 与旋转 \(k=p\) 可行域不等价**:\(k=p\) model-Hessian 精确派发到 full identity space。 +8. **CentOS 7 glibc 2.17 不兼容 JAX 0.11 wheel**:使用哈希固定的 Debian bookworm Apptainer SIF。 +9. **Slurm spool 中 `BASH_SOURCE[0]` 路径错误**:改从显式 `CHALLENGE113_DEPLOYMENT` 加载 gate。 +10. **pycma 一维初始化失败**:只对 \(k=1\) 关闭有缺陷的 `maxstd_boundrange`。 +11. **9,500 元素 array 超过集群限制**:改为 180 slice,每 slice 52–53 trial。 +12. **本地严格验证 provenance 冲突**:使用生产 Python 3.12.12,并阻止 Git 向父目录搜索。 + +## J. 参考资料 + +1. Challenge 113 原题:[GitHub Issue #113](https://github.com/QuantumBFS/quantum.harness/issues/113)。 +2. 黑客松流程:[智御量子 2026 指南](https://giggleliu.github.io/summer-school-2026/zh/guide)。 +3. Khaneja et al., GRAPE, *J. Magn. Reson.* 172, 296 (2005)。 +4. Caneva et al., CRAB, *Phys. Rev. A* 84, 022326 (2011)。 +5. Judson & Rabitz, adaptive feedback control, *PRL* 68, 1500 (1992)。 +6. Egger & Wilhelm, Ad-HOC, *PRL* 112, 240503 (2014)。 +7. Kelly et al., randomized-benchmarking closed-loop control, *PRL* 112, 240504 (2014)。 +8. Rabitz et al., trap-free control landscapes, *Science* 303, 1998 (2004)。 +9. Shen et al., Hessian analysis, *J. Chem. Phys.* 124, 204106 (2006)。 +10. Roslund & Rabitz, dynamic dimensionality identification, *PRL* 112, 143001 (2014)。 +11. Petersson et al., discrete adjoints, arXiv:2001.01013。 +12. Day et al., glassy phase near the speed limit, *PRL* 122, 020601 (2019)。 + +下载的论文、渲染文本、BibTeX 和来源校验位于 `references/`;完整索引见 `references/rendered/INDEX.md` 与 `SOURCES.md`。 + +--- + +# Prompt 记录 + +## 说明 + +以下按项目发生顺序整理人类给智能体的关键提示词。能从聊天记录恢复原文的使用引号;大量重复的“在跑吗”“继续”“跑完了吗”合并记录,避免伪造逐字稿。系统自动生成的工具消息和智能体内部提示不计入人类 Prompt。 + +## 1. 建立独立工作区 + +> “你现在被fork出来了,你看懂工作流了吗,你准备做113” + +目的:从多挑战并行工作流中建立 `challenge/113` 独立 Git worktree,避免与 Challenge 81、148、194 等目录冲突。 + +## 2. 理解题目 + +> “问题113说的啥,详细解释” + +目的:解释开环/闭环控制、\(d^2-1\) 曲率维数、模型—真值 gap、有限 shots、核心交付物和可发表扩展。 + +## 3. 准备文献和代码 + +> “下载需要的论文和代码” + +目的:保存 Challenge Issue、起始 notebook 或公开替代材料、GRAPE/CRAB/Ad-HOC/控制景观/Hessian/过参数化等论文,并生成来源和 SHA256。 + +## 4. 固化已有材料并设计方案 + +> “提交 仔细阅读问题,想想应该怎么做” + +目的:先提交文献和初始材料,再设计完整实现,而不是直接堆叠 notebook cell。 + +## 5. 确认研究范围 + +> “Challenge 113 实施范围: sim_research” + +目的:选择软件模拟设备的 research-grade 路线,不依赖尚未获得权限的真实 pulse-level 硬件。 + +## 6. 确认执行风格 + +> “数值路线: 别问问题,always run” + +目的:在安全、非破坏性的技术选择上使用合理默认值,持续实现和验证。 + +## 7. 追问实际执行位置 + +> “在跑吗,没看见啊,在哪执行的” + +> “在跑吗?没看到subagent啊” + +目的:要求实际启动可观察的任务,而不是只给计划或口头状态。 + +## 8. 中断后继续 + +> “刚刚意外中断了,继续” + +> “刚刚断了,继续” + +目的:从任务和 Git 状态恢复,不从头覆盖已有成果。 + +## 9. 工作区清理 + +> “Task 10 前的工作区清理: 别问,always run” + +目的:在生产门之前清理无关变更,保证 revision、archive 和工作区 provenance 一致。 + +## 10. 阶段性盘点 + +> “在做吗,这个问题还有多少,已经做了多少?” + +> “题目让你做了什么,你现在做完了哪些?接下来做哪些?” + +目的:把题目交付物映射到已实现模块和剩余生产任务。 + +## 11. 是否需要超算 + +> “所以这个任务需要超算吗?” + +> “能上超算就上超算” + +目的:本地完成单元测试和小规模 pilot,完整 9,500-trial 矩阵优先部署到超算。 + +## 12. 集群选择 + +> “尽量在一个集群上跑” + +目的:优先在 LASG02 单一运行时和文件系统完成生产,减少跨集群 provenance 和合并复杂度。 + +## 13. 放宽非核心审查、推进运行 + +> “搞得差不多了就跑,不用那么严格” + +目的:在核心正确性已经建立后尽快做 pilot 和生产。实际执行仍保留了查询计费、哈希和运行时 gate,未放弃科学正确性。 + +## 14. 提前准备收尾 + +> “跑完了还需要做什么,可以准备一下” + +> “时刻注意超算跑的情况” + +目的:并行准备下载、SHA256、严格验证、bootstrap、五张图和报告 runbook。 + +## 15. 增加计算资源 + +> “为啥跑的这么慢,你多用点计算资源啊” + +> “还有空闲可以继续加” + +目的:在 Slurm/QOS/内存限制允许的范围内提高并发。最终采用 180 slice、最多 8 个并发 slice。 + +## 16. 高频状态查询 + +> “在跑吗,还有多久?” + +> “跑好了吗” + +> “跑完了吗” + +这些提示用于持续监控生产 array、发现失败 task、下载结果并估计剩余时间。 + +## 17. 截止时间压力 + +> “快点,来得及吗,还有半个小时” + +目的:优先完成数据下载、哈希和 9,500/9,500 严格验证,再尝试最终分析。 + +## 18. 停止耗时分析 + +> “算了,来不及,停掉吧” + +> “停掉吧,来不及做这个了” + +目的:优先保留已验证生产 snapshot,把无需超算的 bootstrap 和五图生成留作可恢复的离线收尾。 + +## 19. 生成本报告 + +> “在这个包里写一个md,用中文,标题叫做挑战113汇报” + +> “里面包括正文,支撑材料,prompt” + +> “正文。需要介绍挑战物理背景还有我们做了什么,包括相关的结果(图表或者图片),讲一个完整的故事,做到哪说到哪。” + +> “支撑材料也就是附录。需要包括代码实现的全部细节,可以额外做一张矢量图,包括如何复现正文的全部内容” + +> “prompt。需要尽可能按顺序记录完成这个项目用的提示词” + +> “1. 足够的信息去复现结果,2. 一个清晰的给人看的文件论证你的结果有用性和正确性。” + +补充要求: + +> “别问,always run” + +> “具体代码不用再跑一遍验证” + +目的:基于已有代码、审计和严格生产验证直接成稿;不因写报告重复执行耗时测试。 + +--- + +## 最终状态一句话 + +我们完成并严格验证了 Challenge 113 的可微模型、景观子空间、查询黑盒、公平闭环基线、9,500-trial 生产数据与可复现基础设施,支持了 \(d^2-1\) 的核心几何预期;完整生产快照已经就绪,可直接继续生成 bootstrap 汇总和 headline 查询节省图。