Skip to content

add new QMOO tutorial - #5499

Open
kcmccormibm wants to merge 7 commits into
mainfrom
km/add-qmoo-portfolio
Open

add new QMOO tutorial#5499
kcmccormibm wants to merge 7 commits into
mainfrom
km/add-qmoo-portfolio

Conversation

@kcmccormibm

Copy link
Copy Markdown
Collaborator

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

  • Small-scale walkthrough (8-asset, exact simulation) that can be verified by brute force.
  • Large-scale example (40 assets, 8 GICS sectors, K=6) sized for IBM Heron hardware, using QAP-truncation to sparsify the covariance and moocore for hypervolume and dominated-solution filtering.
  • QAOA angles trained classically (exact statevector at small scale, MPS at 40 qubits); pre-trained angles ship in instances/ so the slow training step is skipped by default.
  • Real market data and parameters file in datasets/tutorials and dowloaded at runtime

Testing / 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.

@kcmccormibm
kcmccormibm requested a review from a team August 11, 2026 19:36
@qiskit-bot

Copy link
Copy Markdown
Contributor

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:

  • @nathanearnestnoble

@review-notebook-app

Copy link
Copy Markdown

Check out this pull request on  ReviewNB

See visual diffs & provide feedback on Jupyter Notebooks.


Powered by ReviewNB

@kcmccormibm
kcmccormibm requested a review from henryzou50 August 12, 2026 16:14
# Conflicts:
#	docs/tutorials/_toc.json
#	docs/tutorials/index.mdx
#	qiskit_bot.yaml
#	scripts/config/notebook-testing.toml

@henryzou50 henryzou50 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-pipeline isn't on PyPI, and its GitHub main just removed ScipyTrainer.train() (replaced by provide_params()), so a fresh install breaks the notebook. Only the v0.1.0 tag works. The Requirements cell should give the exact command: pip install "git+https://github.com/qiskit-community/qaoa_training_pipeline.git@v0.1.0" (and pip 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 = False branch crashes after training: os.makedirs(os.path.dirname(params_path)) with a bare filename -> makedirs("") raises FileNotFoundError. We can just drop that line.

4. Consistency with other tutorials (smaller items)

  • Add a ## References section 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-info cell (this is a guides-only convention; no tutorial has one and it'll never get populated since this notebook is CI-excluded) and the hardware-unrun tags (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"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

3 participants