Skip to content

Conversation

@thilomueller
Copy link
Contributor

@thilomueller thilomueller commented Oct 16, 2025

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
Copy link
Contributor

coderabbitai bot commented Oct 16, 2025

📝 Walkthrough

Summary by CodeRabbit

Release Notes

  • New Features

    • Construct Hamiltonians directly from Pauli-sum operator expressions
    • Enhanced MPO compression with configurable multi-sweep optimization
    • Flexible hamiltonian builder accepting one- and two-body Pauli terms with boundary conditions
  • Refactor

    • Simplified and renamed API for common Hamiltonian constructors (Ising, Heisenberg, identity)
    • Transitioned from instance-based initialization to factory-method pattern for more intuitive operator creation

✏️ 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 legacy MPO initializers into builder-style APIs (ising, heisenberg, identity, custom, finite_state_machine). Tests now validate MPOs via dense mpo.to_matrix() comparisons.

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; refactor legacy init_* initializers into 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/**
Replace block/shape assertions with exact dense-matrix equality checks via mpo.to_matrix(); add dense embedding and dense Hamiltonian builders; update tests to call new MPO APIs (ising, heisenberg, identity, finite_state_machine, custom). Attention: tests rely on 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/*
Replace instance init_* calls with class/factory methods (ising, heisenberg, identity, custom, finite_state_machine, coupled_transmon) across code, docs, and tests. Attention: ensure all call sites use updated names/signatures.
Simulator & typing / CPU detection tweaks
src/mqt/yaqs/simulator.py
Adjust TYPE_CHECKING imports and refine available_cpus() detection to prefer attribute-checked sched_getaffinity, then os.cpu_count(), then multiprocessing.cpu_count(), with robust fallbacks.
Docs / Examples updates
docs/examples/analog_simulation.md, docs/examples/transmon_emulation.md
Update examples to use new classmethod constructors (e.g., MPO.ising, MPO.coupled_transmon) and show from_pauli_sum / hamiltonian usage as alternative APIs.

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: request construction (terms, L, tol, max_bond_dim, n_sweeps)
  MPO->>Parser: parse Pauli-strings into site→op maps
  Parser-->>MPO: parsed ops
  MPO->>Builder: build MPO tensors for each term (with coeff)
  Builder-->>Accumulator: submit term tensors
  Accumulator-->>MPO: accumulated MPO tensors
  loop n_sweeps
    MPO->>Compressor: perform directional SVD sweep (direction, tol, max_bond_dim)
    Compressor-->>MPO: updated tensors
  end
  MPO-->>User: return constructed (and compressed) 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 compress with glee—
From strings to tensors, bonds set free.
A rabbit's hop: MPO melody. 🐇✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: updating the MPO class to support construction of arbitrary Pauli Hamiltonians through new API methods and the hamiltonian/from_pauli_sum constructors.
Description check ✅ Passed The description covers the main implementation (Pauli-based Hamiltonian construction following Hubig et al.), API changes (constructor-style usage like MPO.ising), linked issue reference, and all checklist items are marked complete with appropriate documentation, tests, and changelog updates.
Linked Issues check ✅ Passed The PR meets all objectives from issue #13: provides a general Hamiltonian constructor via hamiltonian() and from_pauli_sum() accepting Pauli-string inputs; supports compact/sparse input formats with automatic identity-padding; generalizes beyond model-specific builders; exposes consistent API matching other quantum libraries.
Out of Scope Changes check ✅ Passed All changes are in-scope: core MPO Hamiltonian construction (networks.py), corresponding test updates, documentation/examples, and simulator improvements for available_cpus() utility. No unrelated functionality added beyond the linked issue scope.
Docstring Coverage ✅ Passed Docstring coverage is 98.48% 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

📜 Recent review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 05f7098 and 796d86e.

📒 Files selected for processing (4)
  • CHANGELOG.md
  • docs/examples/analog_simulation.md
  • docs/examples/transmon_emulation.md
  • src/mqt/yaqs/simulator.py
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-10-14T14:37:38.047Z
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.

Applied to files:

  • CHANGELOG.md
📚 Learning: 2025-12-05T17:45:37.602Z
Learnt from: denialhaag
Repo: munich-quantum-toolkit/core PR: 1360
File: .github/workflows/reusable-mlir-tests.yml:40-43
Timestamp: 2025-12-05T17:45:37.602Z
Learning: In the munich-quantum-toolkit/core repository, patch releases of LLVM dependencies don't require documentation updates, changelog entries, or additional tests beyond what's validated by passing CI checks.

Applied to files:

  • CHANGELOG.md
📚 Learning: 2025-12-01T11:00:40.342Z
Learnt from: flowerthrower
Repo: munich-quantum-toolkit/core-plugins-catalyst PR: 1
File: CHANGELOG.md:18-18
Timestamp: 2025-12-01T11:00:40.342Z
Learning: In the munich-quantum-toolkit/core-plugins-catalyst repository, the CHANGELOG.md intentionally references the parent MQT Core repository's release notes (https://github.com/munich-quantum-toolkit/core/releases) because the plugin repository is based on work performed in the parent repository.

Applied to files:

  • CHANGELOG.md
🪛 markdownlint-cli2 (0.18.1)
CHANGELOG.md

70-70: Link and image reference definitions should be needed
Unused link or image reference definition: "@thilomueller"

(MD053, link-image-reference-definitions)

⏰ 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 (macos-14) / 🐍 macos-14
  • GitHub Check: 🐍 Test (ubuntu-24.04) / 🐍 ubuntu-24.04
  • GitHub Check: 🐍 Test (windows-2022) / 🐍 windows-2022
  • GitHub Check: 🐍 Test (ubuntu-24.04-arm) / 🐍 ubuntu-24.04-arm
🔇 Additional comments (7)
CHANGELOG.md (1)

14-14: Changelog entry properly formatted; static linter hint is a false positive.

The entry at line 14 follows the project's changelog conventions (PR number + contributor links). The static analysis hint about an unused link at line 70 is incorrect—the [**@thilomueller**] link is referenced in line 14. Based on learnings, changelog format is consistent with project practice.

Also applies to: 55-55, 70-70

docs/examples/transmon_emulation.md (1)

35-43: API pattern clean and consistent with new builder constructors.

The MPO.coupled_transmon(...) factory-style constructor aligns well with the broader refactoring toward class-method builders. The comment on line 42 helpfully clarifies the relationship between the coupling parameter and the T_swap value.

docs/examples/analog_simulation.md (1)

42-54: The review comment is based on an incorrect assumption. The implementation of from_pauli_sum in src/mqt/yaqs/core/data_structures/networks.py is an instance method (takes self as the first parameter, returns None), not a class method. The documentation in Method 3 (lines 42–54) correctly reflects this pattern by creating an instance and then calling the method on it. No changes are needed.

Likely an incorrect or invalid review comment.

src/mqt/yaqs/simulator.py (4)

57-60: LGTM: Proper use of TYPE_CHECKING for runtime optimization.

The addition of cast and moving Callable under the TYPE_CHECKING guard are correct. Since the file uses from __future__ import annotations (line 25), all type annotations are stored as strings and not evaluated at runtime, making this optimization safe and appropriate.


96-96: LGTM: Consistent with the import refactoring above.

Removing Callable from this TYPE_CHECKING block is correct since it's now imported in the earlier block (line 60).


142-151: LGTM: Improved error handling for platform-specific CPU detection.

The refactored logic properly handles OSError exceptions that can occur when sched_getaffinity fails (e.g., due to permission issues or unsupported system calls). The use of cast with a string type annotation (line 145) is correct since Callable is only imported under TYPE_CHECKING and is not available at runtime.


154-157: LGTM: Robust fallback CPU count with improved error handling.

The consolidated exception handling for both NotImplementedError and OSError is an improvement. The or chain correctly handles None returns from os.cpu_count() and multiprocessing.cpu_count(), with a safe fallback to 1 CPU if all detection methods fail.


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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

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.

@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
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

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.

@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
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
Copy link

codecov bot commented Nov 26, 2025

Codecov Report

❌ Patch coverage is 84.28571% with 33 lines in your changes missing coverage. Please review.

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

📢 Thoughts on this report? Let us know!

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In @src/mqt/yaqs/core/data_structures/networks.py:
- Around line 1354-1360: The current branch that handles an empty mpo_terms list
silently creates zero-valued tensors (setting self.tensors to zeros), which can
be surprising; update the constructor/initializer logic that processes mpo_terms
so that when mpo_terms is empty it either raises a clear exception (e.g.,
ValueError("mpo_terms must not be empty")) or emits a warning via the module
logger before returning the zero tensors. Locate the block that checks `if not
mpo_terms:` and replace the silent zero-initialization with the chosen behavior,
keeping the existing flow for the non-empty case that accumulates terms with
`_sum_terms`.
- Around line 990-997: The class-level mutable-looking attributes _PAULI_2,
_VALID, and _PAULI_TOKEN_RE should be annotated as typing.ClassVar to show they
are class-level constants; add from typing import ClassVar (if not already
imported) and change their declarations to use ClassVar[...] types (e.g.,
ClassVar[Dict[str, np.ndarray]] for _PAULI_2, ClassVar[FrozenSet[str]] for
_VALID, and ClassVar[re.Pattern] for _PAULI_TOKEN_RE) so static type checkers
understand these are class variables and not instance fields.
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 08a9aff and 21c1401.

📒 Files selected for processing (2)
  • src/mqt/yaqs/core/data_structures/networks.py
  • tests/core/data_structures/test_networks.py
🧰 Additional context used
🧠 Learnings (2)
📚 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
🧬 Code graph analysis (2)
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 (5)
  • op (1285-1290)
  • MPO (957-1616)
  • init_ising (999-1037)
  • to_matrix (1537-1559)
  • init_heisenberg (1039-1079)
src/mqt/yaqs/core/data_structures/networks.py (1)
src/mqt/yaqs/core/libraries/gate_library.py (3)
  • Destroy (690-714)
  • h (313-319)
  • x (286-292)
🪛 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). (3)
  • 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 (12)
tests/core/data_structures/test_networks.py (3)

38-38: LGTM!

The import change correctly removes Y since the tests now validate MPOs via dense matrix comparison rather than inspecting individual tensor blocks. The remaining imports (GateLibrary, Id, X, Z) are sufficient for the current test suite.


104-161: LGTM!

The dense Hamiltonian helpers are well-implemented:

  • Pauli matrices are correct
  • Kronecker product embedding follows standard conventions
  • Sign conventions (-J, -g, -Jx, etc.) match the MPO init_ising and init_heisenberg implementations

163-185: LGTM!

The new tests properly validate MPO construction by comparing the full dense matrix against reference implementations. This approach is more robust than inspecting individual tensor blocks. The tolerance atol=1e-12 is appropriate for numerical precision.

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

21-21: LGTM!

Import of re is appropriate for the new Pauli string parsing functionality.


28-28: LGTM!

Import simplified to only Destroy since Pauli matrices are now defined as class-level constants in MPO._PAULI_2.


1022-1037: LGTM!

Good refactoring: init_ising now delegates to the general hamiltonian() builder, reducing code duplication. Using type(self) enables proper subclass behavior.


1064-1079: LGTM!

init_heisenberg correctly delegates to hamiltonian() with appropriate two-body and one-body terms matching the original sign conventions.


1262-1314: LGTM!

The hamiltonian() classmethod is well-designed:

  • Keyword-only arguments prevent positional errors
  • Input validation is thorough
  • Correct handling of open vs. periodic boundary conditions
  • Clean delegation to from_pauli_sum() for term construction

1461-1471: LGTM!

Clean implementation of single-term MPO creation. The coefficient is correctly applied to the first tensor, and the copy prevents unintended mutation of the _PAULI_2 arrays.


1473-1490: LGTM!

The Pauli string parser is robust with good error handling:

  • Flexible input format (spaces or commas)
  • Duplicate site detection
  • Leftover token validation for malformed input

1492-1517: LGTM!

The _sum_terms method correctly implements MPO addition via bond dimension expansion with block-diagonal structure for interior tensors and appropriate boundary handling.

One minor note: The assert on line 1495 is acceptable for this internal method, but consider using raise ValueError for consistency with other validation in the module.


1441-1445: No issues found. The truncation logic at line 1442 correctly implements the documented behavior: keep = int(np.sum(tol < S)) keeps singular values greater than tol, which discards values ≤ tol as specified in the docstring. The tol parameter is an absolute threshold (default 1e-12), and the comparison direction is correct.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🤖 Fix all issues with AI agents
In @src/mqt/yaqs/core/data_structures/networks.py:
- Around line 1592-1617: The _sum_terms function assumes matching physical
dimensions between MPO terms A and B but lacks a defensive check; add an
assertion at the start of _sum_terms (or inside the loop) to verify physical
dimension equality (e.g., assert A[0].shape[0] == B[0].shape[0], "Physical
dimensions must match") or assert a.shape[0] == b.shape[0] for each k, so
mismatched d values in arrays handled by _sum_terms are caught early.
- Around line 1410-1459: The compress method should validate that tol is
non-negative before running sweeps: inside compress (the public method that
calls _compress_one_sweep) add a check like raising ValueError with message "tol
must be non-negative." when tol < 0; this prevents the SVD truncation line in
_compress_one_sweep (keep = int(np.sum(tol < S))) from producing incorrect
behavior for negative tolerances and ensures callers must pass tol >= 0.
- Line 1093: The method definition heisenberg currently includes an unnecessary
"# noqa: N803" directive (same as ising); remove the trailing noqa from the def
heisenberg(self, length: int, Jx: float, Jy: float, Jz: float, h: float)
declaration so the function signature does not contain the unused lint
suppression.
- Line 1053: Remove the unused inline noqa directive from the ising function
signature: edit the def ising(...) declaration and delete the " # noqa: N803" at
the end so the function definition no longer contains that comment; ensure no
other references to N803 remain in the same declaration (function name ising).
- Around line 990-997: The class-level constants _PAULI_2, _VALID, and
_PAULI_TOKEN_RE should be annotated with typing.ClassVar to indicate they are
not instance attributes; update their declarations to use ClassVar[Dict[str,
np.ndarray]] (or appropriate types like ClassVar[frozenset] and
ClassVar[re.Pattern]) so the linter stops flagging RUF012 and static type
checkers treat them as constants.

In @tests/core/data_structures/test_networks.py:
- Around line 111-129: Add lightweight input validation to _embed_one_body and
_embed_two_body: assert that L is a non-negative int, that i is an int within
valid range (0 <= i < L) and for _embed_two_body also ensure i+1 < L, and assert
op (and op1/op2) are 2x2 numpy arrays (shape == (2,2)); raise AssertionError
with a clear message when checks fail so tests fail fast on bad inputs.

In @tests/core/methods/test_bug.py:
- Around line 98-100: The MPO.custom() call is using the default transpose which
flips the single-site tensor axes (mpo_tensor shape (2,2,1,1)); update the call
to pass transpose=False to preserve the supplied axis order (i.e., call
MPO.custom with transpose=False for mpo_tensor) so
prepare_canonical_site_tensors(mps, mpo) receives the correct MPO site shape and
canonicalization works as expected.
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 21c1401 and 2783a0f.

📒 Files selected for processing (12)
  • docs/examples/analog_simulation.md
  • src/mqt/yaqs/core/data_structures/networks.py
  • src/mqt/yaqs/digital/digital_tjm.py
  • src/mqt/yaqs/digital/equivalence_checker.py
  • src/mqt/yaqs/digital/utils/mpo_utils.py
  • tests/analog/test_analog_tjm.py
  • tests/core/data_structures/test_networks.py
  • tests/core/libraries/test_gate_library.py
  • tests/core/methods/test_bug.py
  • tests/core/methods/test_tdvp.py
  • tests/digital/utils/test_mpo_utils.py
  • tests/test_simulator.py
🧰 Additional context used
🧠 Learnings (2)
📚 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
🧬 Code graph analysis (9)
tests/digital/utils/test_mpo_utils.py (1)
src/mqt/yaqs/core/data_structures/networks.py (1)
  • identity (1250-1265)
tests/core/libraries/test_gate_library.py (1)
src/mqt/yaqs/core/data_structures/networks.py (1)
  • custom (1294-1314)
tests/core/methods/test_tdvp.py (1)
src/mqt/yaqs/core/data_structures/networks.py (1)
  • ising (1053-1091)
tests/analog/test_analog_tjm.py (1)
src/mqt/yaqs/core/data_structures/networks.py (1)
  • ising (1053-1091)
tests/test_simulator.py (1)
src/mqt/yaqs/core/data_structures/networks.py (1)
  • ising (1053-1091)
src/mqt/yaqs/digital/equivalence_checker.py (1)
src/mqt/yaqs/core/data_structures/networks.py (1)
  • identity (1250-1265)
src/mqt/yaqs/digital/utils/mpo_utils.py (1)
src/mqt/yaqs/core/data_structures/networks.py (1)
  • custom (1294-1314)
src/mqt/yaqs/core/data_structures/networks.py (1)
src/mqt/yaqs/core/libraries/gate_library.py (3)
  • Destroy (690-714)
  • x (286-292)
  • h (313-319)
src/mqt/yaqs/digital/digital_tjm.py (1)
src/mqt/yaqs/core/data_structures/networks.py (1)
  • custom (1294-1314)
🪛 Ruff (0.14.10)
src/mqt/yaqs/core/data_structures/networks.py

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

(RUF012)


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

Remove unused noqa directive

(RUF100)


1093-1093: 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-14) / 🐍 macos-14
  • 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 (31)
docs/examples/analog_simulation.md (1)

31-31: LGTM! API rename correctly reflected in documentation.

The update from init_ising to ising aligns with the new public API introduced in this PR.

tests/test_simulator.py (1)

78-78: LGTM! Test updates correctly use the new API.

All instances of init_ising have been properly updated to ising, maintaining the same test logic and parameters.

Also applies to: 132-132, 182-182, 466-466, 576-576, 690-690

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

21-21: LGTM! Import additions are appropriate.

The re module is used for Pauli string parsing, and Destroy import supports the transmon MPO initialization.

Also applies to: 28-28


1000-1051: LGTM! Well-designed Hamiltonian builder with proper validation.

The method correctly handles:

  • Input validation (L > 0, valid boundary conditions)
  • Open vs. periodic boundary condition logic
  • Operator validation via the nested op() helper
  • Delegation to from_pauli_sum for actual MPO construction

The API design is clean and follows good practices.


1076-1091: LGTM! Cleaner implementation via delegation to the builder.

The refactored ising method correctly delegates to the new hamiltonian builder with appropriate Pauli terms for the Ising model and adopts the resulting MPO attributes.


1118-1133: LGTM! Heisenberg model correctly delegates to the builder.

The implementation properly specifies all three Heisenberg coupling terms (XX, YY, ZZ) and the magnetic field term.


1250-1265: LGTM! Identity MPO initialization is correct.

The method properly constructs identity MPO tensors with appropriate dimensions.


1267-1292: LGTM! Finite state machine MPO construction is correct.

The method properly assembles the MPO from boundary and inner tensors with appropriate transposition and validation.


1294-1314: LGTM! Custom MPO initialization is clean and flexible.

The keyword-only transpose parameter is good API design, and the validation ensures correctness.


1316-1363: LGTM! Robust Pauli-sum MPO construction with thorough validation.

The method includes:

  • Proper input validation (dimension checks, length validation)
  • Clear error messages for invalid operators and out-of-range site indices
  • Correct handling of empty terms (zero MPO)
  • Appropriate compression after construction

The implementation is well-designed and defensive.


1365-1408: LGTM! Well-designed compression API with flexible sweep scheduling.

The method provides:

  • Clear parameter validation
  • Flexible direction schedules for different compression strategies
  • Efficient early return for n_sweeps == 0
  • Clean delegation to the internal sweep implementation

The design is both user-friendly and flexible.


1461-1477: LGTM! MPO rotation correctly swaps physical dimensions.

The method properly handles both standard rotation and conjugate rotation for Hermitian conjugate operations.


1560-1571: LGTM! Clean implementation of product-state MPO term creation.

The method correctly constructs bond-dimension-1 MPO terms with proper coefficient scaling.


1573-1590: LGTM! Robust Pauli string parsing with excellent validation.

The parser includes:

  • Flexible handling of whitespace and commas
  • Regex-based token extraction
  • Duplicate site detection
  • Invalid token detection via clever regex substitution check

The implementation is both robust and user-friendly.

src/mqt/yaqs/digital/utils/mpo_utils.py (1)

279-279: LGTM! API rename correctly applied.

The update from init_custom to custom is consistent with the new MPO API, and the transpose=False parameter is correctly preserved.

tests/digital/utils/test_mpo_utils.py (1)

238-238: LGTM! Test updates correctly use the new API.

All instances of init_identity have been properly updated to identity, maintaining the same test logic.

Also applies to: 260-260, 281-281

src/mqt/yaqs/digital/digital_tjm.py (2)

175-177: MPO.custom() usage is consistent with tensor layout here.

tensors are constructed as (bond_left, bond_right, phys, phys), so relying on custom()’s default transpose=True is correct.


204-206: Good: transpose=False when re-wrapping existing MPO tensors.

Prevents accidental reordering when slicing mpo.tensors.

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

83-85: Looks good: custom(..., transpose=False) keeps tensor ordering stable in tests.


122-124: Looks good: custom(..., transpose=False) matches random MPO tensor shapes used in this test.


255-257: LGTM: updated to renamed MPO.ising() API.


273-275: LGTM: updated to renamed MPO.ising() API.

tests/analog/test_analog_tjm.py (1)

57-59: LGTM: tests consistently use renamed MPO.ising() constructor.

Also applies to: 90-92, 124-126, 154-156, 184-186, 214-216, 255-257, 381-383

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

245-247: LGTM: updated to renamed MPO.ising() API.

Also applies to: 274-276, 315-317, 349-351

tests/core/libraries/test_gate_library.py (2)

102-109: Good: custom(..., transpose=False) preserves expected tensor axis order for identity insertion assertions.


121-129: Good: transpose=False maintained for forward/reverse MPO tensor comparisons.

src/mqt/yaqs/digital/equivalence_checker.py (1)

60-60: LGTM! Clean API migration.

The rename from init_identity to identity aligns with the new MPO initialization API. The method call correctly passes the number of qubits and uses the default physical dimension.

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

104-108: Good practice: Test-independent Pauli definitions.

Defining local Pauli matrices for the test helpers decouples the validation logic from the production GateLibrary implementation, which is appropriate for unit tests that verify correctness.


132-160: Dense Hamiltonian reference implementations are correct.

These functions correctly construct the full dense matrices for Ising and Heisenberg models with open boundary conditions. The sign conventions are clearly documented and will be validated by the comparison tests.


163-185: Excellent: Matrix-equality validation for MPO correctness.

These tests provide strong validation by comparing the MPO representation to exact dense Hamiltonians. This approach effectively catches operator ordering, sign errors, and structural issues in the MPO construction. The tolerance and system size are well-chosen for numerical testing.


188-339: LGTM! Consistent API migration across all tests.

All test methods have been correctly updated to use the new MPO initialization API (identity, ising, heisenberg, finite_state_machine, custom). The test logic remains unchanged, confirming this is a pure API surface update.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

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).

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 <[email protected]>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Signed-off-by: Aaron Sander <[email protected]>
@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
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