add new QMOO tutorial - #5499
Conversation
|
Thanks for contributing to Qiskit documentation! Before your PR can be merged, it will first need to pass continuous integration tests and be reviewed. Sometimes the review process can be slow, so please be patient. Thanks! 🙌 One or more of the following people are relevant to this code:
|
|
Check out this pull request on See visual diffs & provide feedback on Jupyter Notebooks. Powered by ReviewNB |
# Conflicts: # docs/tutorials/_toc.json # docs/tutorials/index.mdx # qiskit_bot.yaml # scripts/config/notebook-testing.toml
henryzou50
left a comment
There was a problem hiding this comment.
Thanks Katie, this is a nice tutorial and great work here!
Housekeeping I pushed today (so you don't have to): merged main into the branch to resolve the conflicts (kept both sides everywhere; restored the implicit-solvent-calculations entry in qiskit_bot.yaml that was commented out), ran tox -e fix on the notebook (import split + formatting), and ran prettier on qaoa_params.json (array layout only, the values are untouched). Lint should be green now. One heads-up for later: the retrain branch writes qaoa_params.json via json.dump, which produces formatting prettier rejects, we likely need to rerun npx prettier --write on it whenever the angles are regenerated.
I also ran the workflow end-to-end locally, and it nicely reproduces the committed outputs exactly. But while verifying I found two correctness bugs that affect the hardware results, plus a couple of potential blockers:
1. Trained γ is bound with the wrong sign (circuit anti-optimizes)
qaoa_training_pipeline maximizes energy, so we train on the negated op (training_op = sum(-1/n_obj * H_k)), but the trainer's evaluator internally builds its ansatz from that same negated op, i.e. it optimizes angles for exp(+iγH). The notebook then binds those angles into qaoa_ansatz(combined_cost_op) = exp(−iγH), which differs by γ -> −γ.
I verified this by replicating the small-scale pipeline exactly (same Hamiltonians, trainer, seed, and it reproduces the committed trained angles β=3.329, γ=3.445) and comparing the shot-weighted scalarized objective Σ c_k f_k(x) across 60 weight vectors:
| binding | scalarized objective (lower = better) |
|---|---|
| notebook binding | −1.834 -- worse than random on 60/60 weight vectors |
| negated-op fix | −2.017 -- better than random on 60/60 |
| random feasible baseline | −1.926 (best possible: −2.121) |
It's invisible in the committed small-scale outputs only because 200×500 shots over 8 qubits sample all 256 bitstrings, so the distinct-portfolio union enumerates the whole space regardless of what the circuit favors.
** Possible fix (either):** build the sampling ansatz from sum(-c[k] * H_k) so it matches the trained circuit (this is what I tested above), or bind -opt_gammas. The shipped qaoa_params.json was trained the same way (per its note), so would apply to the hardware section too.
2. Bitstrings are never reversed before scoring/reporting
get_counts() returns qubit 0 (= asset 0) at the rightmost position. The return Hamiltonian's Z on qubit 0 carries coefficient μ₀/2, but evaluate_portfolio/evaluate_40 and the "tickers held" table read left-to-right, so every portfolio is scored and reported as its mirror image (asset i <-> asset N−1−i). Same small-scale masking as above, but at 40 assets the printed Pareto ticker lists are the wrong portfolios, and the hypervolume comparison scores scrambled candidates (the committed 14.145 vs 12.973 margin is likely an artifact of bugs 1+2 partially offsetting).
** Possible fix:** reverse once at collection, e.g. bs = bs.replace(" ", "")[::-1], in both collection loops (the warm-start QAOA tutorial handles this explicitly). Post-selection should be unaffected.
3. Blockers for anyone running the notebook fresh
qaoa-training-pipelineisn't on PyPI, and its GitHubmainjust removedScipyTrainer.train()(replaced byprovide_params()), so a fresh install breaks the notebook. Only thev0.1.0tag works. The Requirements cell should give the exact command:pip install "git+https://github.com/qiskit-community/qaoa_training_pipeline.git@v0.1.0"(andpip install qiskit-addon-opt-mapper). Might be worth asking whether a PyPI release is planned, since git-main drift will silently break this tutorial again.- The
load_params_file = Falsebranch crashes after training:os.makedirs(os.path.dirname(params_path))with a bare filename ->makedirs("")raisesFileNotFoundError. We can just drop that line.
4. Consistency with other tutorials (smaller items)
- Add a
## Referencessection for Kotil et al. (see warm-start-qaoa for the[\[1\]](#Reference1)pattern) - Wrap Next steps in
<Admonition type="tip" title="Recommendations">, the "companion unconstrained notebook" bullet points to something that doesn't exist in this repo. Either remove or link it. - Add a job tag:
sampler_hw.options.environment.job_tags = ["TUT_QAMOO"] - Remove the
version-infocell (this is a guides-only convention; no tutorial has one and it'll never get populated since this notebook is CI-excluded) and thehardware-unruntags (no tooling reads them;[groups.exclude]already controls testing) - The market-data cell (first hardware cell) has no committed output, after the fixes above, please re-run the whole notebook top-to-bottom so outputs, figures, and execution counts are regenerated cleanly
5. Wording nits
- "The constraint is built into the circuit's structure, for free" is slightly oversold given post-selection is needed two sentences later. I suggest noting the product-state init trades depth for discarded shots
- "a penalty term … does not fit on hardware" -> "leads to much deeper circuits on hardware"
New portfolio optimization tutorial using the QMOO method introduced in Kotil, et al.
Summary
Adds a new tutorial, Quantum approximate multi-objective optimization applied to a portfolio optimization problem. The tutorial shows how to trace the risk/return/diversification Pareto front of a cardinality-constrained portfolio with QAOA. The cardinality constraint is enforced with an XY mixer, the three objectives are scalarized with the weighted-sum method, and the weights are swept to map out the Pareto front.
What's included
datasets/tutorialsand dowloaded at runtimeTesting / notes
Hardware section selects the least-busy Heron device via least_busy(min_num_qubits=156) (the 156-qubit threshold restricts selection to Heron, excluding Eagle and Nighthawk).
Adapted from Kotil et al. (2025), Nature Computational Science (arXiv:2503.22797), which develops the method for max-cut.