Skip to content

updates MPO class to allow construction of arbitrary Pauli Hamiltonians - #216

Merged
aaronleesander merged 44 commits into
munich-quantum-toolkit:mainfrom
thilomueller:MPO_generator
Jan 12, 2026
Merged

aaronleesander merged 44 commits into
munich-quantum-toolkit:mainfrom
thilomueller:MPO_generator

Conversation

@thilomueller

@thilomueller thilomueller commented Oct 16, 2025 •

Copy link
Copy Markdown
Contributor

Description

This PR implements a construction method that allows initializing an MPO directly from an arbitrary Hamiltonian expressed as a sum of weighted Pauli tensor product strings. This enables easy construction of complex Hamiltonians (e.g. transverse-field Ising, Fermi–Hubbard) without manually deriving MPOs. The implementation follows the algorithm presented in Hubig et al.

Additionally, it changes the basic usage of Hamiltonian initialization by using constructors:
H = MPO.ising(L, J, g)

Fixes #13

Checklist:

  • The pull request only contains commits that are focused and relevant to this change.
  • I have added appropriate tests that cover the new/changed functionality.
  • I have updated the documentation to reflect these changes.
  • I have added entries to the changelog for any noteworthy additions, changes, fixes, or removals.
  • I have added migration instructions to the upgrade guide (if needed).
  • The changes follow the project's style guidelines and introduce no new warnings.
  • The changes are fully tested and pass the CI checks.
  • I have reviewed my own code changes.

@coderabbitai

coderabbitai Bot commented Oct 16, 2025 •

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Construct Hamiltonians from Pauli-string specifications (class-level constructors and a from_pauli_sum path)
    • Improved MPO compression with multi-sweep SVD for better tensor optimization
  • Refactor

    • Renamed/init APIs consolidated into clearer class-level constructors (e.g., ising, heisenberg, identity, custom, finite_state_machine, coupled_transmon)
  • Documentation

    • Examples updated to show new constructor patterns
  • Tests

    • Tests updated to use the new public API and added Pauli-sum validation cases

✏️ Tip: You can customize this high-level summary in your review settings.

Walkthrough

Adds Pauli-string parsing and term-based MPO construction (MPO.hamiltonian, MPO.from_pauli_sum), SVD-based MPO.compress with directional sweeps, and refactors several instance init_* methods into builder-style class/factory APIs (ising, heisenberg, identity, custom, finite_state_machine). Tests and docs updated to use new APIs and validate dense operators.

Changes

Cohort / File(s) Summary
MPO Pauli-term builder & compression
src/mqt/yaqs/core/data_structures/networks.py
Add _PAULI_2, _VALID, _PAULI_TOKEN_RE; implement MPO.hamiltonian(...), MPO.from_pauli_sum(...), MPO.compress(...), _compress_one_sweep(...), _create_term, _parse_pauli_string, _sum_terms; replace legacy init_* initializers with builder-style classmethods (ising, heisenberg, identity, custom, finite_state_machine). Attention: Pauli parsing, term assembly/accumulation, and SVD bond-dimension trimming.
Tests: dense-Hamiltonian validation & API updates
tests/core/data_structures/test_networks.py, tests/**
Add dense-embedding helpers and exact dense Hamiltonian builders; update tests to compare mpo.to_matrix() with dense Hamiltonians; replace init_* calls across tests with new APIs (ising, heisenberg, identity, finite_state_machine, custom). Attention: test expectations for exact operator equality and new constructors.
Call-site API renames across code & docs
src/mqt/yaqs/digital/..., src/mqt/yaqs/digital/equivalence_checker.py, src/mqt/yaqs/digital/utils/mpo_utils.py, docs/examples/..., tests/*
Update call sites from instance init_* to class/factory methods (ising, heisenberg, identity, custom, finite_state_machine, coupled_transmon); update docs/examples to new constructor patterns. Attention: ensure signatures match updated usages.
Simulator & typing / CPU detection tweaks
src/mqt/yaqs/simulator.py
Adjust TYPE_CHECKING imports and refine available_cpus() to prefer sched_getaffinity(0) (if available), then os.cpu_count(), then multiprocessing.cpu_count(), with safe fallbacks.
Changelog & docs updates
CHANGELOG.md, docs/examples/analog_simulation.md, docs/examples/transmon_emulation.md
Document MPO Pauli-sum constructor, mention MPO.ising/from_pauli_sum alternatives, and update transmon example to MPO.coupled_transmon(...).

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant User as Test/User
  participant MPO as MPO.hamiltonian / MPO.from_pauli_sum
  participant Parser as _parse_pauli_string
  participant Builder as _create_term
  participant Accumulator as _sum_terms
  participant Compressor as _compress_one_sweep

  User->>MPO: construct(terms, L, bc, tol, max_bond_dim, n_sweeps)
  MPO->>Parser: parse Pauli-term strings
  Parser-->>MPO: site→op maps
  MPO->>Builder: build MPO tensors per term (with coeff)
  Builder-->>Accumulator: provide term tensors
  Accumulator-->>MPO: combined MPO tensors
  loop n_sweeps
    MPO->>Compressor: perform sweep (direction, tol, max_bond_dim)
    Compressor-->>MPO: updated/compressed tensors
  end
  MPO-->>User: return MPO
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

I hop through tokens, X and Z,
I stitch small terms to tapestry,
I sum, I SVD, then trim with glee,
From strings to tensors, bonds set free,
A rabbit hums: MPO melody. 🐇✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: enabling MPO construction from arbitrary Pauli Hamiltonians, which is the primary objective of this PR.
Description check ✅ Passed The PR description covers the main implementation (Pauli-sum Hamiltonian construction), references the algorithm source, explains the API changes (constructor-style usage), and lists issue #13. All checklist items are marked complete.
Linked Issues check ✅ Passed The PR successfully implements the core requirement from issue #13: a general Hamiltonian constructor accepting Pauli-string inputs. The implementation includes sparse input support, arbitrary Pauli tensor-product construction, and a constructor-style API as requested.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing Pauli-sum Hamiltonian construction and refactoring initialization API. Updates to simulator.py (CPU counting logic) appear tangential but remain minimal. No unrelated feature additions are present.
Docstring Coverage ✅ Passed Docstring coverage is 96.63% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 6

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5a3bef5 and 1f2149e.

📒 Files selected for processing (2)
  • src/mqt/yaqs/core/data_structures/networks.py (1 hunks)
  • tests/core/data_structures/test_networks.py (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
tests/core/data_structures/test_networks.py (1)
src/mqt/yaqs/core/data_structures/networks.py (3)
  • MPO (958-1658)
  • init_from_terms (1282-1341)
  • to_matrix (1579-1601)
🪛 Ruff (0.14.0)
src/mqt/yaqs/core/data_structures/networks.py

1421-1421: Unused noqa directive (non-enabled: N803)

Remove unused noqa directive

(RUF100)


1483-1483: Unused noqa directive (non-enabled: N803)

Remove unused noqa directive

(RUF100)


1523-1523: Unused noqa directive (non-enabled: N806)

Remove unused noqa directive

(RUF100)


1524-1524: Unused noqa directive (non-enabled: N806)

Remove unused noqa directive

(RUF100)


1541-1541: Unused noqa directive (non-enabled: N806)

Remove unused noqa directive

(RUF100)


1547-1547: Unused noqa directive (non-enabled: N806)

Remove unused noqa directive

(RUF100)


1548-1548: Unused noqa directive (non-enabled: N806)

Remove unused noqa directive

(RUF100)


1549-1549: Unused noqa directive (non-enabled: N806)

Remove unused noqa directive

(RUF100)


1552-1552: Unused noqa directive (non-enabled: N806)

Remove unused noqa directive

(RUF100)


1555-1555: Unused noqa directive (non-enabled: N806)

Remove unused noqa directive

(RUF100)


1556-1556: Unused noqa directive (non-enabled: N806)

Remove unused noqa directive

(RUF100)

⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: 🐍 Test (macos-15-intel) / 🐍 macos-15-intel
  • GitHub Check: 🐍 Test (windows-2022) / 🐍 windows-2022
  • GitHub Check: 🐍 Test (ubuntu-24.04) / 🐍 ubuntu-24.04
  • GitHub Check: 🐍 Test (ubuntu-24.04-arm) / 🐍 ubuntu-24.04-arm
🔇 Additional comments (1)
src/mqt/yaqs/core/data_structures/networks.py (1)

1282-1342: MPO.init_from_terms: solid construction and compression flow

Term-wise bond‑1 build, block‑diag sum, then local SVD sweeps is correct and matches Hubig et al. API/params read well. LGTM.

Comment thread src/mqt/yaqs/core/data_structures/networks.py Outdated
Comment thread src/mqt/yaqs/core/data_structures/networks.py Outdated
Comment thread src/mqt/yaqs/core/data_structures/networks.py Outdated
Comment thread tests/core/data_structures/test_networks.py Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
src/mqt/yaqs/core/data_structures/networks.py (1)

1505-1562: Remove unused noqa directives.

The compression logic is correct, but there are multiple unused # noqa: N806 directives that should be removed.

Apply this diff:

-            A = self.tensors[k]  # noqa: N806
-            B = self.tensors[k + 1]  # noqa: N806
+            A = self.tensors[k]
+            B = self.tensors[k + 1]
 
             phys_dim = A.shape[0]
             bond_dim_left = A.shape[2]
@@ -1540,9 +1540,9 @@
             matrix = theta.reshape(bond_dim_left * phys_dim * phys_dim, phys_dim * phys_dim * bond_dim_right)
 
             # Apply SVD + truncation
-            U, S, Vh = np.linalg.svd(matrix, full_matrices=False)  # noqa: N806
+            U, S, Vh = np.linalg.svd(matrix, full_matrices=False)
             keep = int(np.sum(tol < S))
             if max_bond_dim is not None:
                 keep = min(keep, max_bond_dim)
             keep = max(1, keep)  # keep at least one
 
-            U = U[:, :keep]  # noqa: N806 # (bond_dim_L phys_dim^2) x bond_dim_trim
-            S = S[:keep]  # noqa: N806 # bond_dim_trim
-            Vh = Vh[:keep, :]  # noqa: N806 # bond_dim_trim x (phys_dim^2 bond_dim_R)
+            U = U[:, :keep]  # (bond_dim_L phys_dim^2) x bond_dim_trim
+            S = S[:keep]  # bond_dim_trim
+            Vh = Vh[:keep, :]  # bond_dim_trim x (phys_dim^2 bond_dim_R)
 
             # Rebuild left tensor
-            UL = U.reshape(bond_dim_left, phys_dim, phys_dim, keep).transpose(1, 2, 0, 3)  # noqa: N806
+            UL = U.reshape(bond_dim_left, phys_dim, phys_dim, keep).transpose(1, 2, 0, 3)
 
             # Rebuild right tensor
-            SVh = (S[:, None] * Vh).reshape(keep, phys_dim, phys_dim, bond_dim_right)  # noqa: N806
-            VR = SVh.transpose(1, 2, 0, 3)  # noqa: N806
+            SVh = (S[:, None] * Vh).reshape(keep, phys_dim, phys_dim, bond_dim_right)
+            VR = SVh.transpose(1, 2, 0, 3)

Based on static analysis hints.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1f2149e and cc345de.

📒 Files selected for processing (2)
  • src/mqt/yaqs/core/data_structures/networks.py (1 hunks)
  • tests/core/data_structures/test_networks.py (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
tests/core/data_structures/test_networks.py (1)
src/mqt/yaqs/core/data_structures/networks.py (3)
  • MPO (958-1660)
  • init_from_terms (1282-1341)
  • to_matrix (1581-1603)
🪛 Ruff (0.14.0)
src/mqt/yaqs/core/data_structures/networks.py

1416-1416: Unused noqa directive (non-enabled: N803)

Remove unused noqa directive

(RUF100)


1482-1482: Unused noqa directive (non-enabled: N803)

Remove unused noqa directive

(RUF100)


1525-1525: Unused noqa directive (non-enabled: N806)

Remove unused noqa directive

(RUF100)


1526-1526: Unused noqa directive (non-enabled: N806)

Remove unused noqa directive

(RUF100)


1543-1543: Unused noqa directive (non-enabled: N806)

Remove unused noqa directive

(RUF100)


1549-1549: Unused noqa directive (non-enabled: N806)

Remove unused noqa directive

(RUF100)


1550-1550: Unused noqa directive (non-enabled: N806)

Remove unused noqa directive

(RUF100)


1551-1551: Unused noqa directive (non-enabled: N806)

Remove unused noqa directive

(RUF100)


1554-1554: Unused noqa directive (non-enabled: N806)

Remove unused noqa directive

(RUF100)


1557-1557: Unused noqa directive (non-enabled: N806)

Remove unused noqa directive

(RUF100)


1558-1558: Unused noqa directive (non-enabled: N806)

Remove unused noqa directive

(RUF100)

⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: 🐍 Test (macos-15-intel) / 🐍 macos-15-intel
  • GitHub Check: 🐍 Test (windows-2022) / 🐍 windows-2022
🔇 Additional comments (5)
src/mqt/yaqs/core/data_structures/networks.py (2)

1282-1342: LGTM! Well-implemented generic MPO construction.

The init_from_terms method correctly implements the Hubig et al. algorithm for constructing MPOs from Pauli strings. The logic handles edge cases (empty terms, single-site), validates input, and applies appropriate compression sweeps.


1371-1414: LGTM! Bond-1 MPO construction is correct.

The implementation correctly builds bond-1 MPOs for product terms and applies the coefficient to the first site to avoid numerical overflow.

tests/core/data_structures/test_networks.py (3)

22-22: LGTM! Necessary import for test implementation.

The reduce import is correctly used for constructing reference Hamiltonians via Kronecker products in the test functions below.


291-327: LGTM! Comprehensive test for multi-term MPO construction.

The test correctly validates the MPO construction from a sum of Pauli strings by comparing against a Kronecker-product reference. Static assertions for length, physical dimension, and tensor count are appropriate.


329-349: LGTM! Important edge case test for single-site MPO.

This test correctly validates the single-site MPO construction, which is an important edge case that was previously buggy (now fixed). The test ensures the special-case handling in _mpo_sum works as expected.

Comment thread src/mqt/yaqs/core/data_structures/networks.py Outdated
Comment thread src/mqt/yaqs/core/data_structures/networks.py Outdated
@denialhaag denialhaag changed the title Construct a general MPO for any arbitrary Pauli-based Hamiltonian (#13) Construct a general MPO for any arbitrary Pauli-based Hamiltonian Oct 16, 2025

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (3)
src/mqt/yaqs/core/data_structures/networks.py (3)

1414-1414: Remove unused noqa directive.

The # noqa: N803 directive on line 1414 is unnecessary and should be removed to satisfy the linter.

Apply this diff:

-    def _mpo_sum(A: list[np.ndarray], B: list[np.ndarray]) -> list[np.ndarray]:  # noqa: N803
+    def _mpo_sum(A: list[np.ndarray], B: list[np.ndarray]) -> list[np.ndarray]:

Based on static analysis hints.


1480-1480: Remove unused noqa directive.

The # noqa: N803 directive on line 1480 is unnecessary and should be removed to satisfy the linter.

Apply this diff:

-    def _mpo_product(A: list[np.ndarray], B: list[np.ndarray]) -> list[np.ndarray]:  # noqa: N803
+    def _mpo_product(A: list[np.ndarray], B: list[np.ndarray]) -> list[np.ndarray]:

Based on static analysis hints.


1503-1560: Remove unused noqa directives and consider relative threshold.

Multiple # noqa: N806 directives throughout this method are unnecessary and should be removed to satisfy the linter. Additionally, the current implementation uses an absolute threshold (tol < S), which may not scale well across different problem sizes. Consider using a relative threshold (tol * S[0] < S) to make the truncation scale-aware.

Apply this diff to remove unused noqa directives:

-            A = self.tensors[k]  # noqa: N806
-            B = self.tensors[k + 1]  # noqa: N806
+            A = self.tensors[k]
+            B = self.tensors[k + 1]
 
             phys_dim = A.shape[0]
             bond_dim_left = A.shape[2]
@@ -1538,17 +1538,17 @@
             matrix = theta.reshape(bond_dim_left * phys_dim * phys_dim, phys_dim * phys_dim * bond_dim_right)
 
             # Apply SVD + truncation
-            U, S, Vh = np.linalg.svd(matrix, full_matrices=False)  # noqa: N806
+            U, S, Vh = np.linalg.svd(matrix, full_matrices=False)
             keep = int(np.sum(tol < S))
             if max_bond_dim is not None:
                 keep = min(keep, max_bond_dim)
             keep = max(1, keep)  # keep at least one
 
-            U = U[:, :keep]  # noqa: N806 # (bond_dim_L phys_dim^2) x bond_dim_trim
-            S = S[:keep]  # noqa: N806 # bond_dim_trim
-            Vh = Vh[:keep, :]  # noqa: N806 # bond_dim_trim x (phys_dim^2 bond_dim_R)
+            U = U[:, :keep]
+            S = S[:keep]
+            Vh = Vh[:keep, :]
 
             # Rebuild left tensor
-            UL = U.reshape(bond_dim_left, phys_dim, phys_dim, keep).transpose(1, 2, 0, 3)  # noqa: N806
+            UL = U.reshape(bond_dim_left, phys_dim, phys_dim, keep).transpose(1, 2, 0, 3)
 
             # Rebuild right tensor
-            SVh = (S[:, None] * Vh).reshape(keep, phys_dim, phys_dim, bond_dim_right)  # noqa: N806
-            VR = SVh.transpose(1, 2, 0, 3)  # noqa: N806
+            SVh = (S[:, None] * Vh).reshape(keep, phys_dim, phys_dim, bond_dim_right)
+            VR = SVh.transpose(1, 2, 0, 3)

Optional: To make the threshold scale-aware, consider changing line 1542:

-            keep = int(np.sum(tol < S))
+            keep = int(np.sum(tol * S[0] < S)) if len(S) > 0 and S[0] > 0 else len(S)

Based on static analysis hints.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cc345de and daee7c3.

📒 Files selected for processing (1)
  • src/mqt/yaqs/core/data_structures/networks.py (1 hunks)
🧰 Additional context used
🪛 Ruff (0.14.2)
src/mqt/yaqs/core/data_structures/networks.py

1414-1414: Unused noqa directive (non-enabled: N803)

Remove unused noqa directive

(RUF100)


1480-1480: Unused noqa directive (non-enabled: N803)

Remove unused noqa directive

(RUF100)


1523-1523: Unused noqa directive (non-enabled: N806)

Remove unused noqa directive

(RUF100)


1524-1524: Unused noqa directive (non-enabled: N806)

Remove unused noqa directive

(RUF100)


1541-1541: Unused noqa directive (non-enabled: N806)

Remove unused noqa directive

(RUF100)


1547-1547: Unused noqa directive (non-enabled: N806)

Remove unused noqa directive

(RUF100)


1548-1548: Unused noqa directive (non-enabled: N806)

Remove unused noqa directive

(RUF100)


1549-1549: Unused noqa directive (non-enabled: N806)

Remove unused noqa directive

(RUF100)


1552-1552: Unused noqa directive (non-enabled: N806)

Remove unused noqa directive

(RUF100)


1555-1555: Unused noqa directive (non-enabled: N806)

Remove unused noqa directive

(RUF100)


1556-1556: Unused noqa directive (non-enabled: N806)

Remove unused noqa directive

(RUF100)

⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: 🐍 Test (macos-15-intel) / 🐍 macos-15-intel
  • GitHub Check: 🐍 Test (windows-2022) / 🐍 windows-2022
  • GitHub Check: 🐍 Test (ubuntu-24.04) / 🐍 ubuntu-24.04
🔇 Additional comments (1)
src/mqt/yaqs/core/data_structures/networks.py (1)

1369-1412: LGTM!

The bond-1 MPO construction logic is correct and well-documented. The coefficient is appropriately applied to the first site tensor to avoid numerical overflow when summing many terms.

Comment thread src/mqt/yaqs/core/data_structures/networks.py Outdated
@aaronleesander aaronleesander added the feature New feature or request label Oct 30, 2025
@aaronleesander
aaronleesander self-requested a review October 30, 2025 09:01
@thilomueller

Copy link
Copy Markdown
Contributor Author

Added a MPO initializer init_from_sparse_pauli_terms that lets users specify only the non-identity local operators of each Pauli string as either dictionaries/lists or compact strings like "X0 Y2 Z5". The rest is automatically filed with identities.
For example, the TFIM can easily be created with the following snippet:

from mqt.yaqs.core.data_structures.networks import MPO

L = 6
J = 1.0
g = 0.5

terms = []

# -J * Z_i Z_{i+1}
for i in range(L - 1):
    terms.append((-J, f"Z{i} Z{i+1}"))

# -g * X_i
for i in range(L):
    terms.append((-g, f"X{i}"))

mpo = MPO()
mpo.init_from_sparse_pauli_terms(terms, length=L)

@codecov

codecov Bot commented Nov 26, 2025 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.14286% with 6 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/mqt/yaqs/simulator.py 71.4% 4 Missing ⚠️
src/mqt/yaqs/core/data_structures/networks.py 98.9% 2 Missing ⚠️

📢 Thoughts on this report? Let us know!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🤖 Fix all issues with AI agents
In @src/mqt/yaqs/core/data_structures/networks.py:
- Around line 990-997: Annotate the module-level/class-level constants to
silence RUF012: declare _PAULI_2 as a ClassVar[Dict[str, np.ndarray]] and _VALID
as a ClassVar[FrozenSet[str]] (and _PAULI_TOKEN_RE as ClassVar[Pattern[str]] if
needed), and add the corresponding imports (from typing import ClassVar, Dict,
FrozenSet, Pattern) so linters treat them as class-level constants rather than
instance attributes; update the type hints for the symbols _PAULI_2, _VALID (and
optionally _PAULI_TOKEN_RE) accordingly.

In @tests/analog/test_analog_tjm.py:
- Around line 57-58: The call to MPO.ising(L, J, g) constructs an MPO but its
return value is unused; either remove this dead statement or assign the result
to a variable (e.g., m = MPO.ising(L, J, g)) and use that variable in subsequent
assertions; update references in the test to use the assigned MPO if the test
intended to validate properties of the constructed MPO.

In @tests/test_simulator.py:
- Around line 462-463: The call to the classmethod MPO.ising is discarding its
returned MPO and leaves H_0 uninitialized; change the call to capture and assign
the returned MPO back to H_0 (e.g., call MPO.ising(L, J, g) or H_0.ising(L, J,
g) and assign the result to H_0) so H_0 holds the new initialized MPO instance.
- Around line 572-573: The call to MPO.ising(L, J, g) is a classmethod that
returns an MPO, but its return value is being discarded; replace the two lines
with a single assignment that captures the returned MPO (e.g., H_0 =
MPO.ising(L, J, g)) so H_0 holds the constructed operator.
- Around line 686-687: The call to the classmethod ising is discarding its
returned MPO; change the test to capture the returned MPO by assigning the
result of MPO.ising(L, J, g) (or H_0 = MPO.ising(L, J, g)) instead of calling
H_0.ising(L, J, g) on the instance; update the reference in
tests/test_simulator.py so H_0 holds the MPO returned by the ising classmethod.
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2783a0f and 50f6089.

📒 Files selected for processing (7)
  • docs/examples/analog_simulation.md
  • src/mqt/yaqs/core/data_structures/networks.py
  • tests/analog/test_analog_tjm.py
  • tests/core/data_structures/test_networks.py
  • tests/core/methods/test_bug.py
  • tests/core/methods/test_tdvp.py
  • tests/test_simulator.py
🧰 Additional context used
🧠 Learnings (6)
📚 Learning: 2026-01-08T10:07:23.393Z
Learnt from: flowerthrower
Repo: munich-quantum-toolkit/core-plugins-catalyst PR: 20
File: python/mqt/core/plugins/catalyst/device.py:69-69
Timestamp: 2026-01-08T10:07:23.393Z
Learning: In the munich-quantum-toolkit/core-plugins-catalyst repository, Ruff is configured with select = ["ALL"] and only specific rules are disabled. SLF001 (flake8-self - private member access) is enabled, so `# noqa: SLF001` directives are necessary when accessing private attributes like `device._to_matrix_ops` in python/mqt/core/plugins/catalyst/device.py, even if Ruff reports RUF100 warnings suggesting the directive is unused.

Applied to files:

  • src/mqt/yaqs/core/data_structures/networks.py
📚 Learning: 2025-11-27T21:26:39.677Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/qmap PR: 846
File: python/mqt/qmap/plugins/qiskit/sc/load_calibration.py:34-34
Timestamp: 2025-11-27T21:26:39.677Z
Learning: In the qmap project, the Ruff linter has the "PL" (pylint) rule category enabled, which includes PLC0415 (import-outside-top-level). Therefore, `# noqa: PLC0415` directives on lazy imports are appropriate and necessary, not unused.

Applied to files:

  • src/mqt/yaqs/core/data_structures/networks.py
📚 Learning: 2025-11-24T10:19:41.147Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/core PR: 1326
File: python/mqt/core/__init__.py:22-22
Timestamp: 2025-11-24T10:19:41.147Z
Learning: In the munich-quantum-toolkit/core repository, Ruff is configured with 'ALL' rules enabled by default, and only specific rules are selectively disabled. When reviewing changes that enable previously-disabled rules (like PLC0415), noqa directives for those rules become necessary and should be retained.

Applied to files:

  • src/mqt/yaqs/core/data_structures/networks.py
📚 Learning: 2025-12-14T15:23:54.712Z
Learnt from: flowerthrower
Repo: munich-quantum-toolkit/core-plugins-catalyst PR: 23
File: docs/conf.py:110-130
Timestamp: 2025-12-14T15:23:54.712Z
Learning: In the munich-quantum-toolkit/core-plugins-catalyst repository, the Ruff configuration has 'ALL' rules enabled with only specific rules disabled. PLR6301 (no-self-use) is active, so `# noqa: PLR6301` directives are necessary for methods that don't use self, even if Ruff reports RUF100 warnings suggesting the directive is unused.

Applied to files:

  • src/mqt/yaqs/core/data_structures/networks.py
📚 Learning: 2025-12-25T13:28:25.619Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/predictor PR: 526
File: src/mqt/predictor/rl/predictorenv.py:271-271
Timestamp: 2025-12-25T13:28:25.619Z
Learning: In the munich-quantum-toolkit/predictor repository, Ruff is configured with an extensive set of rules including SLF (flake8-self) in the extend-select list. The `# noqa: SLF001` directive for private member access (e.g., `self.state._layout = self.layout` in src/mqt/predictor/rl/predictorenv.py) is necessary and appropriate, even if Ruff reports RUF100 warnings suggesting the directive is unused.

Applied to files:

  • src/mqt/yaqs/core/data_structures/networks.py
📚 Learning: 2025-12-25T13:28:10.402Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/predictor PR: 526
File: tests/compilation/test_predictor_rl.py:225-225
Timestamp: 2025-12-25T13:28:10.402Z
Learning: In the munich-quantum-toolkit/predictor repository, Ruff is configured with the "SLF" (flake8-self) rule category enabled in extend-select. SLF001 (private member access) is active, so `# noqa: SLF001` directives are necessary when accessing private methods/attributes (e.g., `_ensure_device_averages_cached()`), even if Ruff reports RUF100 warnings suggesting the directive is unused.

Applied to files:

  • src/mqt/yaqs/core/data_structures/networks.py
🧬 Code graph analysis (5)
tests/core/methods/test_bug.py (1)
src/mqt/yaqs/core/data_structures/networks.py (3)
  • custom (1294-1314)
  • MPO (957-1617)
  • ising (1054-1090)
src/mqt/yaqs/core/data_structures/networks.py (1)
src/mqt/yaqs/core/libraries/gate_library.py (5)
  • Destroy (690-714)
  • x (286-292)
  • h (313-319)
  • U (949-983)
  • conj (269-275)
tests/core/methods/test_tdvp.py (1)
src/mqt/yaqs/core/data_structures/networks.py (2)
  • MPO (957-1617)
  • ising (1054-1090)
tests/test_simulator.py (1)
src/mqt/yaqs/core/data_structures/networks.py (2)
  • MPO (957-1617)
  • ising (1054-1090)
tests/analog/test_analog_tjm.py (1)
src/mqt/yaqs/core/data_structures/networks.py (2)
  • MPO (957-1617)
  • ising (1054-1090)
🪛 Ruff (0.14.10)
src/mqt/yaqs/core/data_structures/networks.py

990-995: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: 🐍 Test (windows-2022) / 🐍 windows-2022
  • GitHub Check: 🐍 Test (ubuntu-24.04-arm) / 🐍 ubuntu-24.04-arm
  • GitHub Check: 🐍 Test (macos-14) / 🐍 macos-14
  • GitHub Check: 🐍 Test (ubuntu-24.04) / 🐍 ubuntu-24.04
  • GitHub Check: 🐍 Test (macos-15-intel) / 🐍 macos-15-intel
🔇 Additional comments (36)
docs/examples/analog_simulation.md (1)

30-30: LGTM!

The documentation correctly demonstrates the new MPO.ising(L, J, g) class method API, which aligns with the broader refactor replacing instance-based initializers with factory-style constructors.

tests/core/methods/test_bug.py (5)

83-85: LGTM!

The test correctly uses the new mpo.custom(tensors, transpose=False) API to create a random MPO.


98-99: LGTM!

Correctly updated to use the new mpo.custom([mpo_tensor]) API.


122-123: LGTM!

Correctly updated to use the new mpo.custom(mpo_tensors, transpose=False) API.


255-255: LGTM!

Correctly migrated to the factory-style MPO.ising(1, 1, 0.5) class method.


272-272: LGTM!

Correctly migrated to the factory-style MPO.ising(3, 1, 0.5) class method.

tests/analog/test_analog_tjm.py (2)

90-91: LGTM!

Correctly migrated to MPO.ising(L, J, g) and assigned to H.


124-125: LGTM!

All remaining test functions correctly use the new MPO.ising(...) factory method with proper variable assignment.

Also applies to: 154-155, 184-185, 214-215, 255-256, 381-382

tests/core/methods/test_tdvp.py (4)

245-246: LGTM!

Correctly migrated to MPO.ising(L, J, g) class method.


274-275: LGTM!

Correctly migrated to MPO.ising(L, J, g) class method.


315-315: LGTM!

Correctly migrated to MPO.ising(L, J, g) class method.


348-348: LGTM!

Correctly migrated to MPO.ising(L, J, g) class method.

tests/test_simulator.py (3)

77-77: LGTM!

Correctly uses the factory-style MPO.ising(length, J=1, g=0.5) class method.


130-130: LGTM!

Correctly uses the factory-style MPO.ising(length, J=1, g=0.5) class method.


179-179: LGTM!

Correctly uses the factory-style MPO.ising(length, J=1, g=0.5) class method.

tests/core/data_structures/test_networks.py (10)

38-38: LGTM!

Import updated to include GateLibrary and Id for test usage.


104-160: LGTM!

The dense Hamiltonian helper functions (_embed_one_body, _embed_two_body, _ising_dense, _heisenberg_dense) correctly implement reference dense matrix construction for verifying MPO correctness. These are well-structured and will serve as ground truth for MPO validation tests.


163-183: Excellent addition of correctness verification tests.

test_ising_correct_operator and test_heisenberg_correct_operator validate the new Pauli-sum MPO construction against analytically-constructed dense Hamiltonians. This is a strong verification approach that ensures the MPO to_matrix() representation matches the expected physics.


186-196: LGTM!

Test correctly migrated to use mpo.identity(length, physical_dimension=pdim).


207-235: LGTM!

Test correctly migrated to use mpo.finite_state_machine(length, left_bound, inner, right_bound).


238-261: LGTM!

Test correctly migrated to use mpo.custom(tensors).


264-285: LGTM!

Test correctly migrated to use MPO.ising(length, J, g) classmethod.


288-297: LGTM!

Test correctly migrated to use MPO.ising(length, J, g) classmethod.


300-319: LGTM!

Test correctly migrated to use MPO.ising(length, J, g) classmethod.


322-334: LGTM!

Test correctly migrated to use mpo.identity(length, pdim).

src/mqt/yaqs/core/data_structures/networks.py (11)

1053-1090: LGTM!

The ising classmethod cleanly delegates to hamiltonian with appropriate two-body (-J * Z⊗Z) and one-body (-g * X) terms. The docstring is comprehensive and matches the implementation.


1092-1133: LGTM!

The heisenberg classmethod correctly constructs an XYZ Heisenberg Hamiltonian with optional Z field. The conditional one_body=[(-h, "Z")] if h != 0 else [] appropriately handles the case when no field is applied.


1250-1265: LGTM!

The identity method is renamed from init_identity and maintains the same functionality.


1267-1292: LGTM!

The finite_state_machine method is renamed from init_custom_hamiltonian and maintains the same functionality for constructing MPOs from explicit boundary and inner tensors.


1294-1314: LGTM!

The custom method is renamed from init_custom and maintains the same functionality with improved keyword-only argument pattern for transpose.


1316-1363: LGTM!

The from_pauli_sum method correctly:

  1. Validates physical dimension and length
  2. Parses Pauli string specifications
  3. Creates individual term MPOs
  4. Sums terms via block-diagonal concatenation
  5. Compresses the result

The implementation follows the algorithm from Hubig et al. as mentioned in the PR objectives.


1365-1408: LGTM!

The compress method provides a clean public API for MPO compression with flexible sweep direction control. Input validation is thorough and the schedule mapping is clear.


1410-1459: LGTM!

The _compress_one_sweep method correctly implements local SVD-based compression:

  1. Contracts adjacent tensors
  2. Performs SVD with truncation based on tolerance and max_bond_dim
  3. Reconstructs left and right tensors with proper index ordering

The index manipulation and reshape operations correctly preserve the MPO structure.


1560-1571: LGTM!

The _create_term helper correctly constructs a single Pauli term MPO with bond dimension 1 throughout, applying the coefficient to the first tensor.


1573-1590: LGTM!

The _parse_pauli_string method provides robust parsing of Pauli string specifications with:

  1. Support for comma or space-separated tokens
  2. Case-insensitive matching
  3. Duplicate site detection
  4. Validation of remaining tokens after regex matching

1592-1617: LGTM!

The _sum_terms static method correctly implements MPO addition via block-diagonal concatenation of virtual bonds. The boundary handling for first/last sites is correct, and the inner site concatenation properly forms block-diagonal structure.

Comment thread src/mqt/yaqs/core/data_structures/networks.py
Comment thread tests/analog/test_analog_tjm.py
Comment thread tests/test_simulator.py Outdated
Comment thread tests/test_simulator.py Outdated
Comment thread tests/test_simulator.py Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/mqt/yaqs/core/data_structures/networks.py (1)

1254-1318: API inconsistency: mix of classmethods and instance methods.

ising(), heisenberg(), coupled_transmon(), and hamiltonian() are classmethods that return a new MPO instance, while identity(), finite_state_machine(), and custom() are instance methods that mutate self in-place. This inconsistency can confuse users:

# Classmethod pattern (returns new instance)
mpo = MPO.ising(L=10, J=1, g=0.5)

# Instance method pattern (mutates in-place)
mpo = MPO()
mpo.identity(length=10)

Consider refactoring identity(), finite_state_machine(), and custom() to classmethods for a consistent API surface.

🤖 Fix all issues with AI agents
In @src/mqt/yaqs/core/data_structures/networks.py:
- Around line 990-997: The class-level mutable/static attributes _PAULI_2,
_VALID, and _PAULI_TOKEN_RE should be annotated with typing.ClassVar to satisfy
static analysis; import ClassVar (and re.Pattern if you want precise type for
the regex) unconditionally at top-level and change their annotations to
something like _PAULI_2: ClassVar[dict[str, np.ndarray]] = {...}, _VALID:
ClassVar[frozenset[str]] = frozenset(...), and _PAULI_TOKEN_RE:
ClassVar[re.Pattern] = re.compile(...). Ensure the import of ClassVar is added
(not only under TYPE_CHECKING) so annotations exist at runtime.
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 50f6089 and 0a57dcf.

📒 Files selected for processing (3)
  • docs/examples/transmon_emulation.md
  • src/mqt/yaqs/core/data_structures/networks.py
  • tests/test_simulator.py
🧰 Additional context used
🧠 Learnings (6)
📚 Learning: 2026-01-08T10:07:23.393Z
Learnt from: flowerthrower
Repo: munich-quantum-toolkit/core-plugins-catalyst PR: 20
File: python/mqt/core/plugins/catalyst/device.py:69-69
Timestamp: 2026-01-08T10:07:23.393Z
Learning: In the munich-quantum-toolkit/core-plugins-catalyst repository, Ruff is configured with select = ["ALL"] and only specific rules are disabled. SLF001 (flake8-self - private member access) is enabled, so `# noqa: SLF001` directives are necessary when accessing private attributes like `device._to_matrix_ops` in python/mqt/core/plugins/catalyst/device.py, even if Ruff reports RUF100 warnings suggesting the directive is unused.

Applied to files:

  • src/mqt/yaqs/core/data_structures/networks.py
📚 Learning: 2025-11-27T21:26:39.677Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/qmap PR: 846
File: python/mqt/qmap/plugins/qiskit/sc/load_calibration.py:34-34
Timestamp: 2025-11-27T21:26:39.677Z
Learning: In the qmap project, the Ruff linter has the "PL" (pylint) rule category enabled, which includes PLC0415 (import-outside-top-level). Therefore, `# noqa: PLC0415` directives on lazy imports are appropriate and necessary, not unused.

Applied to files:

  • src/mqt/yaqs/core/data_structures/networks.py
📚 Learning: 2025-11-24T10:19:41.147Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/core PR: 1326
File: python/mqt/core/__init__.py:22-22
Timestamp: 2025-11-24T10:19:41.147Z
Learning: In the munich-quantum-toolkit/core repository, Ruff is configured with 'ALL' rules enabled by default, and only specific rules are selectively disabled. When reviewing changes that enable previously-disabled rules (like PLC0415), noqa directives for those rules become necessary and should be retained.

Applied to files:

  • src/mqt/yaqs/core/data_structures/networks.py
📚 Learning: 2025-12-14T15:23:54.712Z
Learnt from: flowerthrower
Repo: munich-quantum-toolkit/core-plugins-catalyst PR: 23
File: docs/conf.py:110-130
Timestamp: 2025-12-14T15:23:54.712Z
Learning: In the munich-quantum-toolkit/core-plugins-catalyst repository, the Ruff configuration has 'ALL' rules enabled with only specific rules disabled. PLR6301 (no-self-use) is active, so `# noqa: PLR6301` directives are necessary for methods that don't use self, even if Ruff reports RUF100 warnings suggesting the directive is unused.

Applied to files:

  • src/mqt/yaqs/core/data_structures/networks.py
📚 Learning: 2025-12-25T13:28:25.619Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/predictor PR: 526
File: src/mqt/predictor/rl/predictorenv.py:271-271
Timestamp: 2025-12-25T13:28:25.619Z
Learning: In the munich-quantum-toolkit/predictor repository, Ruff is configured with an extensive set of rules including SLF (flake8-self) in the extend-select list. The `# noqa: SLF001` directive for private member access (e.g., `self.state._layout = self.layout` in src/mqt/predictor/rl/predictorenv.py) is necessary and appropriate, even if Ruff reports RUF100 warnings suggesting the directive is unused.

Applied to files:

  • src/mqt/yaqs/core/data_structures/networks.py
📚 Learning: 2025-12-25T13:28:10.402Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/predictor PR: 526
File: tests/compilation/test_predictor_rl.py:225-225
Timestamp: 2025-12-25T13:28:10.402Z
Learning: In the munich-quantum-toolkit/predictor repository, Ruff is configured with the "SLF" (flake8-self) rule category enabled in extend-select. SLF001 (private member access) is active, so `# noqa: SLF001` directives are necessary when accessing private methods/attributes (e.g., `_ensure_device_averages_cached()`), even if Ruff reports RUF100 warnings suggesting the directive is unused.

Applied to files:

  • src/mqt/yaqs/core/data_structures/networks.py
🧬 Code graph analysis (1)
tests/test_simulator.py (1)
src/mqt/yaqs/core/data_structures/networks.py (3)
  • MPO (957-1621)
  • ising (1054-1090)
  • coupled_transmon (1136-1252)
🪛 Ruff (0.14.10)
src/mqt/yaqs/core/data_structures/networks.py

990-995: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: 🐍 Test (ubuntu-24.04) / 🐍 ubuntu-24.04
  • GitHub Check: 🐍 Test (macos-15-intel) / 🐍 macos-15-intel
  • GitHub Check: 🐍 Test (ubuntu-24.04-arm) / 🐍 ubuntu-24.04-arm
  • GitHub Check: 🐍 Test (macos-14) / 🐍 macos-14
  • GitHub Check: 🐍 Test (windows-2022) / 🐍 windows-2022
🔇 Additional comments (18)
docs/examples/transmon_emulation.md (1)

35-42: LGTM!

The documentation correctly demonstrates the updated API using the classmethod constructor MPO.coupled_transmon(...) instead of the previous instance-based initialization pattern.

tests/test_simulator.py (7)

77-77: LGTM!

The test correctly uses the new MPO.ising() classmethod constructor with appropriate parameters.


130-130: LGTM!

Consistent migration to MPO.ising() classmethod.


179-179: LGTM!

Consistent migration to MPO.ising() classmethod.


462-462: LGTM!

Consistent migration to MPO.ising() classmethod.


571-571: LGTM!

Consistent migration to MPO.ising() classmethod.


684-684: LGTM!

Consistent migration to MPO.ising() classmethod.


841-849: LGTM!

The test correctly uses the new MPO.coupled_transmon() classmethod constructor, and the parameter names match the new API signature.

src/mqt/yaqs/core/data_structures/networks.py (10)

999-1051: LGTM!

The hamiltonian() classmethod correctly:

  • Validates inputs (length > 0, valid boundary conditions)
  • Builds term specifications for both two-body and one-body interactions
  • Properly handles periodic boundary conditions with modulo arithmetic
  • Delegates to from_pauli_sum() for MPO construction

1053-1133: LGTM!

The ising() and heisenberg() classmethods correctly delegate to hamiltonian() with proper term specifications:

  • Ising: $-J \sum Z_i Z_{i+1} - g \sum X_i$
  • Heisenberg: $-J_x \sum X_i X_{i+1} - J_y \sum Y_i Y_{i+1} - J_z \sum Z_i Z_{i+1} - h \sum Z_i$

The conditional one_body for h != 0 in Heisenberg avoids unnecessary identity terms.


1135-1252: LGTM!

The coupled_transmon() classmethod correctly implements the transmon-resonator chain Hamiltonian:

  • Proper tensor structure for boundary and bulk sites
  • Correct physical dimension handling for alternating qubits/resonators
  • Appropriate transpose to match MPO index convention (phys_out, phys_in, left, right)

1358-1364: Verify: zero MPO for empty terms list.

When terms is empty, the method creates a zero MPO (tensors filled with zeros). This produces an MPO representing the zero operator, which may not be the expected behavior for users who accidentally pass an empty list.

Consider either:

  1. Raising an error for empty terms
  2. Documenting this behavior in the docstring

1414-1463: LGTM!

The _compress_one_sweep() method correctly implements local SVD compression:

  • Proper tensor contraction of adjacent sites
  • Correct reshape for SVD (grouping left/right virtual and physical indices)
  • Appropriate truncation based on tolerance threshold
  • Ensures at least one singular value is kept
  • Correct reconstruction of left/right tensors with proper transposition

1577-1594: LGTM!

The _parse_pauli_string() method correctly parses Pauli term specifications with proper validation for duplicate sites and invalid tokens. The regex pattern appropriately handles various input formats.


1596-1620: LGTM!

The _sum_terms() method correctly implements MPO summation via bond dimension expansion using block-diagonal concatenation. The special cases for boundary tensors (first and last sites) are handled correctly.


1564-1575: LGTM!

The _create_term() method correctly creates a bond-dimension-1 MPO for a single Pauli string term, with proper coefficient application and identity filling for unspecified sites.


1554-1555: LGTM!

The usage of identity_mpo.identity(self.length) is consistent with identity() being an instance method.


1465-1481: LGTM!

The rotate() method correctly implements MPO rotation by swapping physical dimensions. The keyword-only conjugate argument is a good API choice for boolean flags.

Comment thread src/mqt/yaqs/core/data_structures/networks.py Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/mqt/yaqs/core/data_structures/networks.py (1)

1291-1308: Bug: identity() ignores physical_dimension parameter.

The identity matrix is hardcoded as 2×2, ignoring the physical_dimension parameter. This will produce incorrect results for non-qubit systems (e.g., qutrits).

🐛 Proposed fix
     def identity(self, length: int, physical_dimension: int = 2) -> None:
         """Initialize identity MPO.

         Initializes the network with identity matrices.

         Parameters:
             length (int): The number of identity matrices to initialize.
             physical_dimension (int, optional): The physical dimension of the identity matrices. Default is 2.

         """
-        mat = np.eye(2, dtype=np.complex128)
+        mat = np.eye(physical_dimension, dtype=np.complex128)
         mat = np.expand_dims(mat, (2, 3))
         self.length = length
         self.physical_dimension = physical_dimension

         self.tensors = []
         for _ in range(length):
             self.tensors.append(mat)
🤖 Fix all issues with AI agents
In @src/mqt/yaqs/core/data_structures/networks.py:
- Line 1091: Remove the unused noqa directive on the parameter declaration:
locate the parameter "J: float,  # noqa: N803" in the affected function/class in
networks.py and delete the " # noqa: N803" suffix so the parameter is simply "J:
float,"; no other changes needed.
- Around line 1130-1132: Remove the unused inline noqa directives from the
parameter names: delete the trailing " # noqa: N803" on the Jx, Jy, and Jz
parameters so the signature simply uses Jx: float, Jy: float, Jz: float (locate
the occurrence in the function/method where Jx, Jy, Jz are declared and remove
the "# noqa: N803" tokens).

In @tests/core/data_structures/test_networks.py:
- Line 47: Remove the unused "# noqa: N803" directives from the function
definitions that currently include them: _embed_one_body, _embed_two_body, and
_embed_three_body; simply delete the trailing "# noqa: N803" from their def
lines so the function signatures remain unchanged but the stale noqa comments
are removed.
- Around line 113-139: Remove the unused lint suppression from the function
definition: edit the _heisenberg_dense function signature to delete the "# noqa:
N803" comment so the line reads "def _heisenberg_dense(L: int, Jx: float, Jy:
float, Jz: float, h: float) -> np.ndarray:"; leave all logic unchanged.
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0a57dcf and 8a56571.

📒 Files selected for processing (2)
  • src/mqt/yaqs/core/data_structures/networks.py
  • tests/core/data_structures/test_networks.py
🧰 Additional context used
🧠 Learnings (7)
📓 Common learnings
Learnt from: burgholzer
Repo: munich-quantum-toolkit/yaqs PR: 212
File: CHANGELOG.md:12-15
Timestamp: 2025-10-14T14:37:38.047Z
Learning: In the munich-quantum-toolkit/yaqs project, changelog entries follow the template: "- $TITLE ([#$NUMBER]($URL)) ([**AUTHOR**](https://github.com/$AUTHOR))". Issue references should not be included in changelog entries; the PR number is sufficient for traceability.
📚 Learning: 2026-01-08T10:07:23.393Z
Learnt from: flowerthrower
Repo: munich-quantum-toolkit/core-plugins-catalyst PR: 20
File: python/mqt/core/plugins/catalyst/device.py:69-69
Timestamp: 2026-01-08T10:07:23.393Z
Learning: In the munich-quantum-toolkit/core-plugins-catalyst repository, Ruff is configured with select = ["ALL"] and only specific rules are disabled. SLF001 (flake8-self - private member access) is enabled, so `# noqa: SLF001` directives are necessary when accessing private attributes like `device._to_matrix_ops` in python/mqt/core/plugins/catalyst/device.py, even if Ruff reports RUF100 warnings suggesting the directive is unused.

Applied to files:

  • src/mqt/yaqs/core/data_structures/networks.py
📚 Learning: 2025-11-27T21:26:39.677Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/qmap PR: 846
File: python/mqt/qmap/plugins/qiskit/sc/load_calibration.py:34-34
Timestamp: 2025-11-27T21:26:39.677Z
Learning: In the qmap project, the Ruff linter has the "PL" (pylint) rule category enabled, which includes PLC0415 (import-outside-top-level). Therefore, `# noqa: PLC0415` directives on lazy imports are appropriate and necessary, not unused.

Applied to files:

  • src/mqt/yaqs/core/data_structures/networks.py
📚 Learning: 2025-11-24T10:19:41.147Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/core PR: 1326
File: python/mqt/core/__init__.py:22-22
Timestamp: 2025-11-24T10:19:41.147Z
Learning: In the munich-quantum-toolkit/core repository, Ruff is configured with 'ALL' rules enabled by default, and only specific rules are selectively disabled. When reviewing changes that enable previously-disabled rules (like PLC0415), noqa directives for those rules become necessary and should be retained.

Applied to files:

  • src/mqt/yaqs/core/data_structures/networks.py
📚 Learning: 2025-12-14T15:23:54.712Z
Learnt from: flowerthrower
Repo: munich-quantum-toolkit/core-plugins-catalyst PR: 23
File: docs/conf.py:110-130
Timestamp: 2025-12-14T15:23:54.712Z
Learning: In the munich-quantum-toolkit/core-plugins-catalyst repository, the Ruff configuration has 'ALL' rules enabled with only specific rules disabled. PLR6301 (no-self-use) is active, so `# noqa: PLR6301` directives are necessary for methods that don't use self, even if Ruff reports RUF100 warnings suggesting the directive is unused.

Applied to files:

  • src/mqt/yaqs/core/data_structures/networks.py
📚 Learning: 2025-12-25T13:28:25.619Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/predictor PR: 526
File: src/mqt/predictor/rl/predictorenv.py:271-271
Timestamp: 2025-12-25T13:28:25.619Z
Learning: In the munich-quantum-toolkit/predictor repository, Ruff is configured with an extensive set of rules including SLF (flake8-self) in the extend-select list. The `# noqa: SLF001` directive for private member access (e.g., `self.state._layout = self.layout` in src/mqt/predictor/rl/predictorenv.py) is necessary and appropriate, even if Ruff reports RUF100 warnings suggesting the directive is unused.

Applied to files:

  • src/mqt/yaqs/core/data_structures/networks.py
📚 Learning: 2025-12-25T13:28:10.402Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/predictor PR: 526
File: tests/compilation/test_predictor_rl.py:225-225
Timestamp: 2025-12-25T13:28:10.402Z
Learning: In the munich-quantum-toolkit/predictor repository, Ruff is configured with the "SLF" (flake8-self) rule category enabled in extend-select. SLF001 (private member access) is active, so `# noqa: SLF001` directives are necessary when accessing private methods/attributes (e.g., `_ensure_device_averages_cached()`), even if Ruff reports RUF100 warnings suggesting the directive is unused.

Applied to files:

  • src/mqt/yaqs/core/data_structures/networks.py
🧬 Code graph analysis (1)
tests/core/data_structures/test_networks.py (2)
src/mqt/yaqs/core/libraries/gate_library.py (6)
  • GateLibrary (1630-1724)
  • Id (744-763)
  • X (602-621)
  • Z (646-665)
  • H (668-687)
  • h (313-319)
src/mqt/yaqs/core/data_structures/networks.py (8)
  • op (1056-1061)
  • MPO (957-1749)
  • ising (1088-1124)
  • to_matrix (1568-1590)
  • heisenberg (1127-1167)
  • identity (1291-1308)
  • finite_state_machine (1310-1335)
  • custom (1337-1357)
🪛 Ruff (0.14.10)
tests/core/data_structures/test_networks.py

47-47: Unused noqa directive (non-enabled: N803)

Remove unused noqa directive

(RUF100)


64-64: Unused noqa directive (non-enabled: N803)

Remove unused noqa directive

(RUF100)


88-88: Unused noqa directive (non-enabled: N803)

Remove unused noqa directive

(RUF100)


113-113: Unused noqa directive (non-enabled: N803)

Remove unused noqa directive

(RUF100)

src/mqt/yaqs/core/data_structures/networks.py

1091-1091: Unused noqa directive (non-enabled: N803)

Remove unused noqa directive

(RUF100)


1130-1130: Unused noqa directive (non-enabled: N803)

Remove unused noqa directive

(RUF100)


1131-1131: Unused noqa directive (non-enabled: N803)

Remove unused noqa directive

(RUF100)


1132-1132: Unused noqa directive (non-enabled: N803)

Remove unused noqa directive

(RUF100)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: 🐍 Test (ubuntu-24.04-arm) / 🐍 ubuntu-24.04-arm
  • GitHub Check: 🐍 Test (macos-15-intel) / 🐍 macos-15-intel
  • GitHub Check: 🐍 Test (windows-2022) / 🐍 windows-2022
  • GitHub Check: 🐍 Test (ubuntu-24.04) / 🐍 ubuntu-24.04
  • GitHub Check: 🐍 Test (macos-14) / 🐍 macos-14
🔇 Additional comments (13)
tests/core/data_structures/test_networks.py (4)

38-45: LGTM!

Defining local Pauli matrices for test verification is appropriate. This isolates tests from the production GateLibrary implementation when building reference dense Hamiltonians.


205-228: Excellent correctness tests for the new MPO builders.

These tests properly validate that the MPO representation matches the exact dense Hamiltonian. Using L=5 keeps the dense matrix tractable (32×32) while still exercising the construction logic across multiple sites.


230-306: LGTM!

The tests correctly exercise the builder methods (identity, finite_state_machine, custom) using the instance-method pattern and thoroughly validate tensor shapes and values.


308-378: LGTM!

Tests are properly updated to use the new MPO.ising(...) classmethod and continue to validate the core MPO operations (conversion, validation, rotation, identity check).

src/mqt/yaqs/core/data_structures/networks.py (9)

21-22: LGTM!

Appropriate imports for regex-based Pauli string parsing and proper type annotations.


995-1006: LGTM!

The class variables are well-defined. The regex pattern correctly handles case-insensitive Pauli labels with optional whitespace before the site index.


1008-1085: LGTM!

The hamiltonian() classmethod correctly generates Pauli-string terms for both open and periodic boundary conditions. The validation and delegation to from_pauli_sum() is clean.


1169-1289: LGTM!

The refactoring to a classmethod is clean. The tensor construction correctly implements the coupled transmon-resonator Hamiltonian with proper MPO conventions.


1359-1423: LGTM!

The from_pauli_sum() method correctly assembles MPO terms from Pauli string specifications with proper validation of sites and operators. The handling of empty terms (creating a zero operator) is mathematically sound.


1425-1530: LGTM!

The compression implementation is correct. The SVD-based sweep with configurable directions and tolerance is well-implemented, and the minimum rank of 1 ensures numerical stability.


1532-1548: LGTM!

The rotate() method correctly handles physical dimension transposition with optional conjugation.


1631-1662: LGTM!

The _create_term() method correctly constructs a bond-dimension-1 MPO for a single Pauli string term. Applying the coefficient to the first tensor is the standard convention.


1664-1748: LGTM!

Both helper methods are well-implemented:

  • _parse_pauli_string() provides robust parsing with duplicate site detection and validation of malformed tokens.
  • _sum_terms() correctly implements block-diagonal concatenation for MPO addition.

Comment thread src/mqt/yaqs/core/data_structures/networks.py
Comment thread src/mqt/yaqs/core/data_structures/networks.py
Comment thread tests/core/data_structures/test_networks.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/mqt/yaqs/core/data_structures/networks.py (1)

1297-1363: Consider API consistency: classmethods vs. instance methods.

The builder methods identity, finite_state_machine, and custom are implemented as in-place instance methods (returning None), while ising, heisenberg, hamiltonian, and coupled_transmon are classmethods that return new MPO instances.

This creates two different usage patterns:

# Classmethod pattern (newer, preferred?)
mpo1 = MPO.ising(length=10, J=1.0, g=0.5)

# Instance method pattern (legacy?)
mpo2 = MPO()
mpo2.identity(length=10)

Suggested improvement: Consider converting identity, finite_state_machine, and custom to classmethods for consistency, or document the rationale for the different patterns.

🤖 Fix all issues with AI agents
In @src/mqt/yaqs/simulator.py:
- Line 150: Wrap the fallback chain that returns os.cpu_count() or
multiprocessing.cpu_count() or 1 in a try/except so multiprocessing.cpu_count()
cannot raise out unhandled (e.g., NotImplementedError); catch
NotImplementedError (and a broad Exception as a safeguard), and on exception
return 1 or use os.cpu_count() result if available, ensuring the expression
using os.cpu_count() and multiprocessing.cpu_count() is evaluated safely.
- Around line 142-147: The attribute-check refactor removed the try/except
around os.sched_getaffinity(0), so when calling sched_getaffinity (via fn =
getattr(os, "sched_getaffinity", None) and sched_getaffinity = cast(...)) the
OSError can bubble up; wrap the call to sched_getaffinity(0) in a try/except
OSError (and OSError subclasses) and on exception fall through to the existing
fallback logic (the subsequent CPU-count fallback) instead of raising; preserve
the current behavior of returning len(result) when the call succeeds.
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8a56571 and 05f7098.

📒 Files selected for processing (2)
  • src/mqt/yaqs/core/data_structures/networks.py
  • src/mqt/yaqs/simulator.py
🧰 Additional context used
🧠 Learnings (6)
📚 Learning: 2026-01-08T10:07:23.393Z
Learnt from: flowerthrower
Repo: munich-quantum-toolkit/core-plugins-catalyst PR: 20
File: python/mqt/core/plugins/catalyst/device.py:69-69
Timestamp: 2026-01-08T10:07:23.393Z
Learning: In the munich-quantum-toolkit/core-plugins-catalyst repository, Ruff is configured with select = ["ALL"] and only specific rules are disabled. SLF001 (flake8-self - private member access) is enabled, so `# noqa: SLF001` directives are necessary when accessing private attributes like `device._to_matrix_ops` in python/mqt/core/plugins/catalyst/device.py, even if Ruff reports RUF100 warnings suggesting the directive is unused.

Applied to files:

  • src/mqt/yaqs/core/data_structures/networks.py
📚 Learning: 2025-11-27T21:26:39.677Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/qmap PR: 846
File: python/mqt/qmap/plugins/qiskit/sc/load_calibration.py:34-34
Timestamp: 2025-11-27T21:26:39.677Z
Learning: In the qmap project, the Ruff linter has the "PL" (pylint) rule category enabled, which includes PLC0415 (import-outside-top-level). Therefore, `# noqa: PLC0415` directives on lazy imports are appropriate and necessary, not unused.

Applied to files:

  • src/mqt/yaqs/core/data_structures/networks.py
📚 Learning: 2025-11-24T10:19:41.147Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/core PR: 1326
File: python/mqt/core/__init__.py:22-22
Timestamp: 2025-11-24T10:19:41.147Z
Learning: In the munich-quantum-toolkit/core repository, Ruff is configured with 'ALL' rules enabled by default, and only specific rules are selectively disabled. When reviewing changes that enable previously-disabled rules (like PLC0415), noqa directives for those rules become necessary and should be retained.

Applied to files:

  • src/mqt/yaqs/core/data_structures/networks.py
📚 Learning: 2025-12-14T15:23:54.712Z
Learnt from: flowerthrower
Repo: munich-quantum-toolkit/core-plugins-catalyst PR: 23
File: docs/conf.py:110-130
Timestamp: 2025-12-14T15:23:54.712Z
Learning: In the munich-quantum-toolkit/core-plugins-catalyst repository, the Ruff configuration has 'ALL' rules enabled with only specific rules disabled. PLR6301 (no-self-use) is active, so `# noqa: PLR6301` directives are necessary for methods that don't use self, even if Ruff reports RUF100 warnings suggesting the directive is unused.

Applied to files:

  • src/mqt/yaqs/core/data_structures/networks.py
📚 Learning: 2025-12-25T13:28:25.619Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/predictor PR: 526
File: src/mqt/predictor/rl/predictorenv.py:271-271
Timestamp: 2025-12-25T13:28:25.619Z
Learning: In the munich-quantum-toolkit/predictor repository, Ruff is configured with an extensive set of rules including SLF (flake8-self) in the extend-select list. The `# noqa: SLF001` directive for private member access (e.g., `self.state._layout = self.layout` in src/mqt/predictor/rl/predictorenv.py) is necessary and appropriate, even if Ruff reports RUF100 warnings suggesting the directive is unused.

Applied to files:

  • src/mqt/yaqs/core/data_structures/networks.py
📚 Learning: 2025-12-25T13:28:10.402Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/predictor PR: 526
File: tests/compilation/test_predictor_rl.py:225-225
Timestamp: 2025-12-25T13:28:10.402Z
Learning: In the munich-quantum-toolkit/predictor repository, Ruff is configured with the "SLF" (flake8-self) rule category enabled in extend-select. SLF001 (private member access) is active, so `# noqa: SLF001` directives are necessary when accessing private methods/attributes (e.g., `_ensure_device_averages_cached()`), even if Ruff reports RUF100 warnings suggesting the directive is unused.

Applied to files:

  • src/mqt/yaqs/core/data_structures/networks.py
🧬 Code graph analysis (1)
src/mqt/yaqs/core/data_structures/networks.py (1)
src/mqt/yaqs/core/libraries/gate_library.py (5)
  • x (286-292)
  • h (313-319)
  • u (412-421)
  • conj (269-275)
  • p (400-409)
🪛 Ruff (0.14.10)
src/mqt/yaqs/core/data_structures/networks.py

1097-1097: Unused noqa directive (non-enabled: N803)

Remove unused noqa directive

(RUF100)


1136-1136: Unused noqa directive (non-enabled: N803)

Remove unused noqa directive

(RUF100)


1137-1137: Unused noqa directive (non-enabled: N803)

Remove unused noqa directive

(RUF100)


1138-1138: Unused noqa directive (non-enabled: N803)

Remove unused noqa directive

(RUF100)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: 🐍 Test (ubuntu-24.04-arm) / 🐍 ubuntu-24.04-arm
  • GitHub Check: 🐍 Test (macos-14) / 🐍 macos-14
  • GitHub Check: 🐍 Test (macos-15-intel) / 🐍 macos-15-intel
  • GitHub Check: 🐍 Test (ubuntu-24.04) / 🐍 ubuntu-24.04
  • GitHub Check: 🐍 Test (windows-2022) / 🐍 windows-2022
  • GitHub Check: 🐍 Lint / 🚨 Lint
🔇 Additional comments (14)
src/mqt/yaqs/simulator.py (2)

57-60: LGTM! Good typing hygiene.

The addition of cast and guarding Callable under TYPE_CHECKING is a best practice that reduces runtime overhead while maintaining type safety.


96-96: LGTM! Consistent with the import reorganization.

Correctly removed the duplicate Callable import since it's now imported at the top-level TYPE_CHECKING block.

src/mqt/yaqs/core/data_structures/networks.py (12)

21-21: LGTM: Clean import additions and type alias.

The new imports (re, NDArray, Destroy) and the ComplexTensor type alias support the Pauli-string parsing and MPO construction features. The type alias improves code readability.

Also applies to: 26-26, 29-29, 956-956


997-1008: LGTM: Well-defined Pauli constants and parsing pattern.

The class-level constants are properly defined:

  • _PAULI_2 contains correct 2×2 Pauli matrices
  • _VALID ensures type safety for operator validation
  • _PAULI_TOKEN_RE correctly parses compact Pauli-string notation like "X0 Y2 Z5"

Using ClassVar appropriately marks these as class-level attributes.


1014-1091: LGTM: Solid API for Hamiltonian construction.

The hamiltonian classmethod provides a clean interface for building MPOs from one- and two-body Pauli interactions. The implementation correctly:

  • Validates inputs (length, boundary conditions, operator labels)
  • Handles open vs. periodic boundary conditions
  • Expands interaction templates to full Pauli-string terms
  • Delegates to from_pauli_sum for the actual MPO assembly

1175-1295: LGTM: Clean refactor to classmethod with good documentation.

The conversion of coupled_transmon to a classmethod aligns with the new builder pattern. The implementation correctly:

  • Constructs alternating qubit/resonator chain tensors
  • Handles boundary conditions (first, last, middle sites)
  • Documents the backward-compatibility caveat about physical_dimension storing only qubit_dim despite alternating dimensions
  • Validates the constructed MPO

1365-1429: Solid Pauli-sum MPO builder with one edge case to verify.

The from_pauli_sum method correctly:

  • Validates inputs (dimension, length, sites, operators)
  • Parses and assembles Pauli-string terms
  • Sums terms via block-diagonal bond concatenation
  • Compresses the result to reduce bond dimension

Edge case: When terms is empty (line 1420), the method constructs a zero MPO (all-zero tensors). Verify this is the intended behavior rather than an identity MPO or an error.

# Verify zero MPO behavior for empty terms
mpo = MPO()
mpo.from_pauli_sum(terms=[], length=4)
mat = mpo.to_matrix()
print("Zero MPO check:", np.allclose(mat, 0))  # Should be True

1431-1536: Excellent MPO compression implementation.

The compression methods are well-designed:

  • Public compress API provides flexible multi-sweep scheduling
  • Internal _compress_one_sweep implements the SVD logic cleanly
  • Proper handling of edge cases (empty MPO, single-site, minimum rank)

The SVD truncation correctly:

  • Contracts adjacent tensor pairs
  • Reshapes to matrix form for SVD
  • Keeps singular values where tol < s (line 1519)
  • Enforces keep = max(1, keep) to prevent rank-zero bonds (line 1522)

1538-1554: LGTM: Simple and correct dimension rotation.

The rotate method correctly swaps the physical output/input legs of the MPO, with optional complex conjugation for Hermitian transposition.


1637-1668: LGTM: Correct single-term MPO construction.

The _create_term helper correctly constructs a bond-dimension-1 MPO for a single Pauli-string term:

  • Defaults unspecified sites to identity
  • Applies the coefficient to the first site tensor
  • Produces properly shaped (2, 2, 1, 1) tensors

1670-1710: LGTM: Robust Pauli-string parser.

The _parse_pauli_string method provides robust parsing with good error handling:

  • Accepts flexible whitespace/comma-separated notation
  • Detects duplicate site specifications
  • Validates token format
  • Provides clear error messages

1712-1755: LGTM: Correct MPO direct-sum implementation.

The _sum_terms method correctly implements block-diagonal concatenation of MPO bond spaces:

  • Handles single-site case (direct addition)
  • Properly sizes new tensors with enlarged bonds
  • Places sub-blocks correctly (top-left and bottom-right)
  • Maintains MPO structural validity

1627-1628: LGTM: Updated to use refactored API.

Correctly updated to call identity(...) instead of the legacy init_identity(...).


1093-1173: Clean Hamiltonian builder methods with correct noqa directives.

The ising and heisenberg classmethods provide convenient physics-oriented APIs that delegate to the general hamiltonian builder. The implementations are correct and follow standard conventions for these models.

The # noqa: N803 directives on lines 1097, 1136-1138 are necessary and correct. The Ruff configuration enables all rules via select = ["ALL"] and does not disable N803, so these directives appropriately suppress the invalid-name warning for the physics-convention parameter names (J, g, Jx, Jy, Jz).

Comment thread src/mqt/yaqs/simulator.py Outdated
Comment thread src/mqt/yaqs/simulator.py Outdated
aaronleesander and others added 2 commits January 9, 2026 15:58
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Signed-off-by: Aaron Sander <61705296+aaronleesander@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Signed-off-by: Aaron Sander <61705296+aaronleesander@users.noreply.github.com>
@aaronleesander aaronleesander changed the title Construct a general MPO for any arbitrary Pauli-based Hamiltonian updates MPO class to allow construction of arbitrary Pauli Hamiltonians Jan 9, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tests/core/data_structures/test_networks.py (1)

303-305: Consider using strict=True for additional safety.

Since the list lengths are already asserted equal on line 301, using strict=True would be more idiomatic and provide an extra validation layer.

🧹 Proposed fix
-    for original, created in zip(tensors, mpo.tensors, strict=False):
+    for original, created in zip(tensors, mpo.tensors, strict=True):
🤖 Fix all issues with AI agents
In @tests/core/data_structures/test_networks.py:
- Line 88: Remove the unused " # noqa: N803" directive from the helper function
definitions that disable the N803 rule; specifically edit the function signature
for _ising_dense and the other helper in the same file (e.g., _ising_sparse) in
tests/core/data_structures/test_networks.py to drop the trailing "  # noqa:
N803" so the definitions are just plain def _ising_dense(L: int, J: float, g:
float) -> np.ndarray: (and similarly for the other helper).
- Line 64: The function definition for _embed_two_body currently includes an
unnecessary noqa directive ("# noqa: N803"); remove the trailing ",  # noqa:
N803" from the def line so the signature reads def _embed_two_body(op1:
np.ndarray, op2: np.ndarray, L: int, i: int) -> np.ndarray: to eliminate the
unused linter suppression; locate the def _embed_two_body function in
tests/core/data_structures/test_networks.py and delete only the noqa comment.
- Line 47: The function definition for _embed_one_body contains an unnecessary
noqa directive ("noqa: N803"); remove the trailing "  # noqa: N803" from the def
line in the _embed_one_body function to eliminate the unused suppression (or
alternatively enable the rule in Ruff config if suppression was intended),
leaving the signature as def _embed_one_body(op: np.ndarray, L: int, i: int) ->
np.ndarray:.
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 796d86e and 0a24d72.

📒 Files selected for processing (1)
  • tests/core/data_structures/test_networks.py
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-11-27T21:26:39.677Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/qmap PR: 846
File: python/mqt/qmap/plugins/qiskit/sc/load_calibration.py:34-34
Timestamp: 2025-11-27T21:26:39.677Z
Learning: In the qmap project, the Ruff linter has the "PL" (pylint) rule category enabled, which includes PLC0415 (import-outside-top-level). Therefore, `# noqa: PLC0415` directives on lazy imports are appropriate and necessary, not unused.

Applied to files:

  • tests/core/data_structures/test_networks.py
📚 Learning: 2026-01-08T10:07:32.871Z
Learnt from: flowerthrower
Repo: munich-quantum-toolkit/core-plugins-catalyst PR: 20
File: python/mqt/core/plugins/catalyst/device.py:69-69
Timestamp: 2026-01-08T10:07:32.871Z
Learning: In the munich-quantum-toolkit/core-plugins-catalyst repository, Ruff is configured with select = ["ALL"] and only specific rules are disabled. SLF001 (flake8-self - private member access) is enabled, so `# noqa: SLF001` directives are necessary when accessing private attributes like `device._to_matrix_ops` in python/mqt/core/plugins/catalyst/device.py, even if Ruff reports RUF100 warnings suggesting the directive is unused.

Applied to files:

  • tests/core/data_structures/test_networks.py
🧬 Code graph analysis (1)
tests/core/data_structures/test_networks.py (1)
src/mqt/yaqs/core/data_structures/networks.py (7)
  • op (1062-1067)
  • MPO (959-1755)
  • identity (1297-1314)
  • finite_state_machine (1316-1341)
  • custom (1343-1363)
  • hamiltonian (1015-1091)
  • from_pauli_sum (1365-1429)
🪛 Ruff (0.14.10)
tests/core/data_structures/test_networks.py

47-47: Unused noqa directive (non-enabled: N803)

Remove unused noqa directive

(RUF100)


64-64: Unused noqa directive (non-enabled: N803)

Remove unused noqa directive

(RUF100)


88-88: Unused noqa directive (non-enabled: N803)

Remove unused noqa directive

(RUF100)


113-113: Unused noqa directive (non-enabled: N803)

Remove unused noqa directive

(RUF100)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: 🐍 Test (macos-15-intel) / 🐍 macos-15-intel
  • GitHub Check: 🐍 Test (ubuntu-24.04) / 🐍 ubuntu-24.04
  • GitHub Check: 🐍 Test (macos-14) / 🐍 macos-14
  • GitHub Check: 🐍 Test (windows-2022) / 🐍 windows-2022
  • GitHub Check: 🐍 Test (ubuntu-24.04-arm) / 🐍 ubuntu-24.04-arm
🔇 Additional comments (6)
tests/core/data_structures/test_networks.py (6)

38-45: LGTM!

The imports and Pauli matrix definitions are correct and properly typed for the dense Hamiltonian construction utilities.


205-227: LGTM!

Excellent tests that validate the new MPO.ising and MPO.heisenberg class methods against exact dense Hamiltonian representations. This provides strong correctness guarantees for the MPO construction.


230-378: LGTM!

The test updates correctly reflect the new builder-style API (MPO.ising, MPO.heisenberg) while maintaining instance method usage for identity, finite_state_machine, and custom where appropriate.


1185-1229: LGTM!

Comprehensive validation tests for MPO.hamiltonian covering non-positive length, invalid boundary conditions, invalid operator labels, and case normalization. The regex patterns correctly match the expected error messages.


1232-1279: LGTM!

Thorough validation tests for from_pauli_sum covering unsupported physical dimensions, non-positive length, out-of-bounds site indices, invalid operator labels, and empty term handling. The use of monkeypatch to inject specific parsing results is an effective technique for testing edge cases.


1282-1307: White-box test for internal behavior—acceptable but brittle.

This test verifies that _create_term is called for each term during MPO construction. While effective for ensuring correct internal flow, note that this test is tightly coupled to implementation details (method names, signatures, call order). If the internal implementation changes, this test will need updates even if external behavior remains correct.

Consider whether an integration-style test comparing the final MPO against expected values might provide equivalent coverage with better resilience to refactoring.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 Fix all issues with AI agents
In @tests/core/data_structures/test_networks.py:
- Line 113: The function definition _heisenberg_dense has an unnecessary noqa
comment for N803; remove the `# noqa: N803` directive from the def line so the
function name remains as-is and the linter directive does not reference a rule
that’s not enabled.
- Line 1350: The trailing noqa directive on the call to mpo._compress_one_sweep
(the "  # noqa: SLF001" comment) is unnecessary because SLF001 is not enabled;
remove the "  # noqa: SLF001" text so the call is simply
mpo._compress_one_sweep(direction="xx", tol=1e-12, max_bond_dim=None).
- Line 64: The function definition for _embed_two_body unnecessarily includes a
`# noqa: N803` directive even though N803 is not enabled; remove the unused
directive from the `_embed_two_body(op1: np.ndarray, op2: np.ndarray, L: int, i:
int) -> np.ndarray` definition so the signature is plain and no longer annotated
with `# noqa: N803`.
- Line 47: The noqa N803 directive on the _embed_one_body function is unused;
remove the trailing comment "  # noqa: N803" from the def line (function name
_embed_one_body) so the signature reads "def _embed_one_body(op: np.ndarray, L:
int, i: int) -> np.ndarray:" and leave the rest unchanged.
- Line 88: The function definition for _ising_dense contains an unused noqa
directive; remove the "  # noqa: N803" suffix from the _ising_dense function
declaration so the line reads a normal def without the N803 suppression (no
other changes needed).
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0a24d72 and 6689d0c.

📒 Files selected for processing (1)
  • tests/core/data_structures/test_networks.py
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2026-01-08T10:07:32.871Z
Learnt from: flowerthrower
Repo: munich-quantum-toolkit/core-plugins-catalyst PR: 20
File: python/mqt/core/plugins/catalyst/device.py:69-69
Timestamp: 2026-01-08T10:07:32.871Z
Learning: In the munich-quantum-toolkit/core-plugins-catalyst repository, Ruff is configured with select = ["ALL"] and only specific rules are disabled. SLF001 (flake8-self - private member access) is enabled, so `# noqa: SLF001` directives are necessary when accessing private attributes like `device._to_matrix_ops` in python/mqt/core/plugins/catalyst/device.py, even if Ruff reports RUF100 warnings suggesting the directive is unused.

Applied to files:

  • tests/core/data_structures/test_networks.py
📚 Learning: 2025-12-14T15:23:54.712Z
Learnt from: flowerthrower
Repo: munich-quantum-toolkit/core-plugins-catalyst PR: 23
File: docs/conf.py:110-130
Timestamp: 2025-12-14T15:23:54.712Z
Learning: In the munich-quantum-toolkit/core-plugins-catalyst repository, the Ruff configuration has 'ALL' rules enabled with only specific rules disabled. PLR6301 (no-self-use) is active, so `# noqa: PLR6301` directives are necessary for methods that don't use self, even if Ruff reports RUF100 warnings suggesting the directive is unused.

Applied to files:

  • tests/core/data_structures/test_networks.py
📚 Learning: 2025-11-24T10:19:41.147Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/core PR: 1326
File: python/mqt/core/__init__.py:22-22
Timestamp: 2025-11-24T10:19:41.147Z
Learning: In the munich-quantum-toolkit/core repository, Ruff is configured with 'ALL' rules enabled by default, and only specific rules are selectively disabled. When reviewing changes that enable previously-disabled rules (like PLC0415), noqa directives for those rules become necessary and should be retained.

Applied to files:

  • tests/core/data_structures/test_networks.py
🧬 Code graph analysis (1)
tests/core/data_structures/test_networks.py (2)
src/mqt/yaqs/core/libraries/gate_library.py (6)
  • GateLibrary (1630-1724)
  • Id (744-763)
  • X (602-621)
  • Z (646-665)
  • H (668-687)
  • h (313-319)
src/mqt/yaqs/core/data_structures/networks.py (4)
  • op (1062-1067)
  • MPO (959-1755)
  • identity (1297-1314)
  • hamiltonian (1015-1091)
🪛 Ruff (0.14.10)
tests/core/data_structures/test_networks.py

47-47: Unused noqa directive (non-enabled: N803)

Remove unused noqa directive

(RUF100)


64-64: Unused noqa directive (non-enabled: N803)

Remove unused noqa directive

(RUF100)


88-88: Unused noqa directive (non-enabled: N803)

Remove unused noqa directive

(RUF100)


113-113: Unused noqa directive (non-enabled: N803)

Remove unused noqa directive

(RUF100)


1350-1350: Unused noqa directive (non-enabled: SLF001)

Remove unused noqa directive

(RUF100)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: 🐍 Test (ubuntu-24.04) / 🐍 ubuntu-24.04
  • GitHub Check: 🐍 Test (macos-14) / 🐍 macos-14
  • GitHub Check: 🐍 Test (windows-2022) / 🐍 windows-2022
  • GitHub Check: 🐍 Test (macos-15-intel) / 🐍 macos-15-intel
  • GitHub Check: 🐍 Test (ubuntu-24.04-arm) / 🐍 ubuntu-24.04-arm
  • GitHub Check: 🐍 Lint / 🚨 Lint
🔇 Additional comments (6)
tests/core/data_structures/test_networks.py (6)

38-45: LGTM!

Clean separation of concerns: gate library imports for observable tests, and local numpy Pauli matrices for dense Hamiltonian construction. The operator definitions are correct.


205-227: Solid ground-truth validation tests.

Comparing MPO.to_matrix() against independently constructed dense Hamiltonians is an excellent approach for verifying correctness of the new MPO builders.


230-378: LGTM!

The updated tests correctly exercise both the classmethod factory pattern (MPO.ising(), MPO.heisenberg()) and the instance method initialization pattern (mpo.identity(), mpo.finite_state_machine(), mpo.custom()). Test coverage is comprehensive.


1185-1230: LGTM!

Comprehensive input validation tests for MPO.hamiltonian(). Error message assertions use appropriate regex patterns matching the implementation.


1232-1308: LGTM!

Effective use of monkeypatch to isolate validation logic and test edge cases that would be difficult to trigger through normal API usage. The test for _create_term invocation count provides good coverage of the construction flow.


1353-1381: LGTM!

Solid integration tests covering Pauli string parsing edge cases: empty specs, comma/whitespace handling, case normalization, duplicate site detection, and invalid token rejection. These align well with the _parse_pauli_string implementation shown in the relevant code snippets.

Comment thread tests/core/data_structures/test_networks.py
Comment thread tests/core/data_structures/test_networks.py
Comment thread tests/core/data_structures/test_networks.py
Comment thread tests/core/data_structures/test_networks.py
Comment thread tests/core/data_structures/test_networks.py
@aaronleesander
aaronleesander merged commit 23b7f26 into munich-quantum-toolkit:main Jan 12, 2026
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

✨ Hamiltonian Construction

2 participants