Skip to content

✨ Add Euler decomposition and supporting decomposition primitives#1672

Open
simon1hofmann wants to merge 7 commits intomainfrom
decomp/euler
Open

✨ Add Euler decomposition and supporting decomposition primitives#1672
simon1hofmann wants to merge 7 commits intomainfrom
decomp/euler

Conversation

@simon1hofmann
Copy link
Copy Markdown
Contributor

@simon1hofmann simon1hofmann commented Apr 28, 2026

Description

Split up from #1665 implementing Euler decomposition.

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.

If PR contains AI-assisted content:

  • I have disclosed the use of AI tools in the PR description as per our AI Usage Guidelines.
  • AI-assisted commits include an Assisted-by: [Model Name] via [Tool Name] footer.
  • I confirm that I have personally reviewed and understood all AI-generated content, and accept full responsibility for it.

@simon1hofmann simon1hofmann self-assigned this Apr 28, 2026
@simon1hofmann simon1hofmann added feature New feature or request MLIR Anything related to MLIR labels Apr 28, 2026
@simon1hofmann simon1hofmann added this to the MLIR Support milestone Apr 28, 2026
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 28, 2026

Codecov Report

❌ Patch coverage is 0% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...lir/Dialect/QCO/Transforms/Decomposition/Helpers.h 0.0% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@simon1hofmann simon1hofmann marked this pull request as ready for review April 28, 2026 18:31
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 28, 2026

Warning

Rate limit exceeded

@denialhaag has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 51 minutes and 29 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 75544bdc-ce4e-4e84-b9fa-9b7298ab9ad6

📥 Commits

Reviewing files that changed from the base of the PR and between b7307a3 and 1d1ba72.

📒 Files selected for processing (6)
  • mlir/include/mlir/Dialect/QCO/Transforms/Decomposition/EulerBasis.h
  • mlir/include/mlir/Dialect/QCO/Transforms/Decomposition/Gate.h
  • mlir/include/mlir/Dialect/QCO/Transforms/Decomposition/GateSequence.h
  • mlir/include/mlir/Dialect/QCO/Transforms/Decomposition/UnitaryMatrices.h
  • mlir/lib/Dialect/QCO/Transforms/Decomposition/EulerBasis.cpp
  • mlir/lib/Dialect/QCO/Transforms/Decomposition/UnitaryMatrices.cpp
📝 Walkthrough

Walkthrough

Adds a new Euler decomposition subsystem to the MLIR QCO dialect: gate/sequence abstractions, Euler-basis enums and mapping, algorithms to factor 2×2 unitaries into basis-specific gate sequences with exact global-phase accounting, matrix utilities, helpers, and extensive unit tests.

Changes

Cohort / File(s) Summary
Core Types
mlir/include/mlir/Dialect/QCO/Transforms/Decomposition/GateKind.h, mlir/include/mlir/Dialect/QCO/Transforms/Decomposition/Gate.h, mlir/include/mlir/Dialect/QCO/Transforms/Decomposition/GateSequence.h
Introduce GateKind enum, Gate struct (parameters + qubit ids), QubitGateSequence/OneQubitGateSequence with global-phase tracking and API declarations for complexity and unitary reconstruction.
Euler API
mlir/include/mlir/Dialect/QCO/Transforms/Decomposition/EulerBasis.h, mlir/include/mlir/Dialect/QCO/Transforms/Decomposition/EulerDecomposition.h
Add EulerBasis enum, DEFAULT_ATOL, getGateTypesForEulerBasis declaration, and EulerDecomposition class with public APIs generateCircuit and anglesFromUnitary.
Matrix & Helpers (headers)
mlir/include/mlir/Dialect/QCO/Transforms/Decomposition/UnitaryMatrices.h, mlir/include/mlir/Dialect/QCO/Transforms/Decomposition/Helpers.h
Declare builders for single-/two-qubit matrices (U/U2/RX/RY/RZ/RXX/RYY/RZZ/P), expand-to-two-qubits utilities, unitarity check, angle utilities (mod2pi, remEuclid), complexity heuristic, and global-phase factor.
Matrix & Helpers (impl)
mlir/lib/Dialect/QCO/Transforms/Decomposition/UnitaryMatrices.cpp, mlir/lib/Dialect/QCO/Transforms/Decomposition/Helpers.cpp
Implement matrix constructors, Kronecker expansion, gate-to-matrix mapping, numeric helpers for remainders/angle wrapping, complexity model, and global-phase factor.
Euler impl & mapping
mlir/lib/Dialect/QCO/Transforms/Decomposition/EulerBasis.cpp, mlir/lib/Dialect/QCO/Transforms/Decomposition/EulerDecomposition.cpp
Implement getGateTypesForEulerBasis and EulerDecomposition algorithms: angle extraction for multiple Euler factorizations, K‑A‑K and rz/sx-style circuit emission, phase bookkeeping, and simplification rules.
Sequence impl
mlir/lib/Dialect/QCO/Transforms/Decomposition/GateSequence.cpp
Implement QubitGateSequence methods: hasGlobalPhase, complexity, and getUnitaryMatrix (iterative reconstruction + phase application + unitary check).
Unit tests & utils
mlir/unittests/Dialect/QCO/Transforms/Decomposition/decomposition_test_utils.h, mlir/unittests/Dialect/QCO/Transforms/Decomposition/test_decomposition_helpers.cpp, mlir/unittests/Dialect/QCO/Transforms/Decomposition/test_euler_decomposition.cpp
Add random-unitary test utility and comprehensive tests for helpers, angle extraction, decomposition correctness across bases, gate-kind expectations, complexity/global-phase behavior, and structural checks (e.g., X vs SX usage).
Build
mlir/unittests/Dialect/QCO/Transforms/CMakeLists.txt, mlir/unittests/Dialect/QCO/Transforms/Decomposition/CMakeLists.txt
Register new Decomposition unit-test subdirectory and add mqt-core-mlir-unittest-decomposition target linking tests against MLIRQCOTransforms and Eigen.

Sequence Diagram

sequenceDiagram
    participant User
    participant EulerDecomposition
    participant AngleExtraction
    participant GateEmitter
    participant GateSequence

    User->>EulerDecomposition: generateCircuit(basis, unitary, simplify, atol)
    activate EulerDecomposition

    EulerDecomposition->>AngleExtraction: anglesFromUnitary(unitary, basis)
    activate AngleExtraction
    AngleExtraction->>AngleExtraction: paramsZyz/Zxz/Xyx/Xzx/U1x(unitary)
    AngleExtraction-->>EulerDecomposition: (theta, phi, lambda, phase)
    deactivate AngleExtraction

    alt Basis is Z-Y-Z / Z-X-Z / X-Y-X / X-Z-X
        EulerDecomposition->>GateEmitter: decomposeKAK(angles, kGate, aGate, simplify, atol)
    else Basis is ZSX / ZSXX
        EulerDecomposition->>GateEmitter: decomposePsxGen(angles, allowXShortcut, simplify, atol)
    else Basis is U / U321 / U3
        EulerDecomposition->>GateEmitter: emit single U gate
    end

    activate GateEmitter
    GateEmitter->>GateSequence: add Gate(type, params, qubits)
    GateEmitter->>GateSequence: set globalPhase
    GateEmitter-->>EulerDecomposition: OneQubitGateSequence
    deactivate GateEmitter

    EulerDecomposition-->>User: OneQubitGateSequence
    deactivate EulerDecomposition

    User->>GateSequence: getUnitaryMatrix()
    activate GateSequence
    GateSequence->>GateSequence: multiply gate matrices in order
    GateSequence->>GateSequence: apply globalPhaseFactor
    GateSequence-->>User: Eigen::Matrix4cd
    deactivate GateSequence
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested labels

c++

Suggested reviewers

  • burgholzer

Poem

🐰 I hop through angles, pi and zero to see,
Unitaries split for each Euler key.
Gates lined in order, phase tucked away,
A rabbit-built circuit to brighten your day! ✨

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.11% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The PR description is incomplete. While it mentions the split from issue #1665 and lists the checklist, several critical sections are unchecked or missing: tests (unchecked), changelog entries (unchecked), style/warnings (unchecked), CI checks (unchecked), and AI disclosure (unchecked despite potential complexity). Complete the checklist by checking the relevant items (tests, changelog, style, CI) or explicitly clarifying why they are not applicable. Also disclose any AI-assisted content per guidelines.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding Euler decomposition and supporting primitives, which is the core focus of all file additions in the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch decomp/euler

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
Review rate limit: 0/1 reviews remaining, refill in 51 minutes and 29 seconds.

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

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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@mlir/include/mlir/Dialect/QCO/Transforms/Decomposition/Helpers.h`:
- Around line 28-32: isUnitaryMatrix currently only checks
(matrix.transpose().conjugate() * matrix).isIdentity(...) which allows
rectangular matrices with orthonormal columns to pass; modify isUnitaryMatrix to
first confirm the matrix is square (matrix.rows() == matrix.cols()) and return
false if not, then perform the unitary check (using adjoint if preferred) so
only true square unitary matrices are accepted; reference isUnitaryMatrix and
the matrix.transpose().conjugate() * matrix identity check to locate where to
add the square check.

In `@mlir/lib/Dialect/QCO/Transforms/Decomposition/EulerDecomposition.cpp`:
- Around line 79-87: The ZYZ branch currently returns the raw phase from
paramsZyz(matrix) which exposes γ; to preserve the existing
anglesFromUnitary(..., EulerBasis::ZYZ) contract you must absorb the qco.u
intrinsic offset into the returned phase: call paramsZyz(matrix) to get the
decomposition, then adjust its phase by adding (phi + lambda) / 2 (i.e., phase
+= (phi + lambda)/2) before returning; reference EulerBasis::ZYZ, paramsZyz, and
anglesFromUnitary to locate and apply this change.

In `@mlir/lib/Dialect/QCO/Transforms/Decomposition/Helpers.cpp`:
- Around line 48-57: The getComplexity function incorrectly applies the
multi-qubit cost before checking for decomposition::GateKind::GPhase, causing
getComplexity(GPhase, n>1) to return a positive cost; change the logic in
getComplexity so that GateKind::GPhase is handled first (or otherwise
short-circuited) and always returns 0 regardless of numOfQubits—update the
function around the multi-qubit branch to check for
decomposition::GateKind::GPhase before applying the multiQubitFactor or add an
explicit early return for GPhase.

In
`@mlir/unittests/Dialect/QCO/Transforms/Decomposition/test_decomposition_helpers.cpp`:
- Around line 35-42: Add a regression assertion to the existing
TEST(DecompositionHelpersTest, GetComplexitySingleQubitAndGphase) that verifies
getComplexity(GateKind::GPhase, N) returns 0 for multi-qubit inputs (e.g., call
getComplexity(GateKind::GPhase, 2) and assert EXPECT_EQ(..., 0U)); this ensures
the zero-cost invariant for GateKind::GPhase holds when numOfQubits > 1.

In
`@mlir/unittests/Dialect/QCO/Transforms/Decomposition/test_euler_decomposition.cpp`:
- Around line 118-123: The test currently reconstructs only
u3Matrix(angles[0],angles[1],angles[2]) and uses isEquivalentUpToGlobalPhase,
which ignores angles[3]; update the test to include the extracted phase from
EulerDecomposition::anglesFromUnitary (angles[3]) by multiplying u3Matrix(...)
by exp(i*angles[3]) and assert that this phased reconstruction equals the
original hadamard (using a direct matrix equality/tolerance check rather than
isEquivalentUpToGlobalPhase) so the returned phase is validated for the
SingleQubitMode::U3 contract.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 60ac46cf-2573-4949-b882-448254fc15d0

📥 Commits

Reviewing files that changed from the base of the PR and between d7ced80 and 01bc84e.

📒 Files selected for processing (18)
  • mlir/include/mlir/Dialect/QCO/Transforms/Decomposition/EulerBasis.h
  • mlir/include/mlir/Dialect/QCO/Transforms/Decomposition/EulerDecomposition.h
  • mlir/include/mlir/Dialect/QCO/Transforms/Decomposition/Gate.h
  • mlir/include/mlir/Dialect/QCO/Transforms/Decomposition/GateKind.h
  • mlir/include/mlir/Dialect/QCO/Transforms/Decomposition/GateSequence.h
  • mlir/include/mlir/Dialect/QCO/Transforms/Decomposition/Helpers.h
  • mlir/include/mlir/Dialect/QCO/Transforms/Decomposition/UnitaryMatrices.h
  • mlir/lib/Dialect/QCO/Transforms/Decomposition/EulerBasis.cpp
  • mlir/lib/Dialect/QCO/Transforms/Decomposition/EulerDecomposition.cpp
  • mlir/lib/Dialect/QCO/Transforms/Decomposition/GateSequence.cpp
  • mlir/lib/Dialect/QCO/Transforms/Decomposition/Helpers.cpp
  • mlir/lib/Dialect/QCO/Transforms/Decomposition/UnitaryMatrices.cpp
  • mlir/unittests/Dialect/QCO/Transforms/CMakeLists.txt
  • mlir/unittests/Dialect/QCO/Transforms/Decomposition/CMakeLists.txt
  • mlir/unittests/Dialect/QCO/Transforms/Decomposition/decomposition_test_utils.h
  • mlir/unittests/Dialect/QCO/Transforms/Decomposition/test_decomposition_helpers.cpp
  • mlir/unittests/Dialect/QCO/Transforms/Decomposition/test_euler_decomposition.cpp
  • mlir/unittests/TestCaseUtils.h

Comment thread mlir/include/mlir/Dialect/QCO/Transforms/Decomposition/Helpers.h
Comment thread mlir/lib/Dialect/QCO/Transforms/Decomposition/Helpers.cpp Outdated
Comment thread mlir/unittests/Dialect/QCO/Transforms/Decomposition/test_euler_decomposition.cpp Outdated
@simon1hofmann
Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 28, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Co-authored-by: Tamino Bauknecht <dev@tb6.eu>
Copy link
Copy Markdown
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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@mlir/unittests/Dialect/QCO/Transforms/Decomposition/decomposition_test_utils.h`:
- Around line 24-49: randomUnitaryMatrix currently only enforces fixed-size
matrices but can accept non-square fixed-size types; add a compile-time check to
require square matrices by adding a static_assert that
MatrixType::RowsAtCompileTime == MatrixType::ColsAtCompileTime (with a clear
message like "randomUnitaryMatrix requires square matrices") near the existing
fixed-size static_assert in the randomUnitaryMatrix template so misuse with
rectangular fixed-size MatrixType is prevented; keep the existing use of
MatrixType and dim as-is.

In
`@mlir/unittests/Dialect/QCO/Transforms/Decomposition/test_euler_decomposition.cpp`:
- Around line 173-180: The test for ZSXX collapsing to a bare X should assert
there are no other gate kinds or extra gates: after calling
EulerDecomposition::generateCircuit(EulerBasis::ZSXX, pauliX, true,
std::nullopt) and the existing EXPECT_EQ(countGatesOfType(seq, GateKind::X), 1U)
and sequenceMatchesSingleQubitMatrix check, also assert the total gate count
equals 1 (e.g., using countTotalGates(seq) or summing known GateKind counts)
and/or assert that the set of gate kinds present is exactly {GateKind::X} (e.g.,
ensure countGatesOfType(seq, GateKind::RZ)==0 and similarly zero for SX, H,
etc.) so no RZ/SX/other gates accompany the X; reference the TEST name
EulerDecompositionTest::ZsxxPauliXUsesSingleXGate,
EulerDecomposition::generateCircuit, countGatesOfType, GateKind, and
sequenceMatchesSingleQubitMatrix when adding these assertions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: cce6649a-1227-417d-8c76-5eead11f9fca

📥 Commits

Reviewing files that changed from the base of the PR and between 01bc84e and 5b752b4.

📒 Files selected for processing (5)
  • mlir/include/mlir/Dialect/QCO/Transforms/Decomposition/Helpers.h
  • mlir/lib/Dialect/QCO/Transforms/Decomposition/Helpers.cpp
  • mlir/unittests/Dialect/QCO/Transforms/Decomposition/decomposition_test_utils.h
  • mlir/unittests/Dialect/QCO/Transforms/Decomposition/test_decomposition_helpers.cpp
  • mlir/unittests/Dialect/QCO/Transforms/Decomposition/test_euler_decomposition.cpp

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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@mlir/lib/Dialect/QCO/Transforms/Decomposition/UnitaryMatrices.cpp`:
- Around line 163-165: The current code silently returns
Eigen::Matrix4cd::Identity() when gate.qubitId.empty(), which masks malformed
input; instead, validate qubitId explicitly and fail fast: if gate.qubitId is
empty and gate.kind is not the explicit identity kind, emit a clear error or
assertion (e.g., llvm::report_fatal_error or assert) rather than returning an
identity matrix; only allow returning Eigen::Matrix4cd::Identity() when
gate.kind is the identity gate and qubitId legitimately indicates an identity
operation. Locate the branch using gate.qubitId and Eigen::Matrix4cd::Identity()
and replace the unconditional identity fallback with the described input check
and failure.

In
`@mlir/unittests/Dialect/QCO/Transforms/Decomposition/test_euler_decomposition.cpp`:
- Around line 90-110: The test TEST(EulerDecompositionTest, Random) uses
maxIterations = 10000 which is too slow for regular unit tests; change this test
to use a much smaller deterministic iteration count (e.g., 100) by reducing
maxIterations and keeping the same rng seed and logic, and move the heavy loop
into a new separate stress test (e.g., TEST(EulerDecompositionStress,
RandomStress)) that runs 10000 iterations; ensure the new stress test uses the
same calls (randomUnitaryMatrix, EulerDecomposition::generateCircuit,
EulerDecompositionTest::restore) and preserves the existing EXPECT_TRUE/isApprox
checks so CI unit tests stay fast while stress runs still validate the full
randomized workload.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: f3dacb7d-3453-4bde-8bba-2f70a91add21

📥 Commits

Reviewing files that changed from the base of the PR and between 5b752b4 and b7307a3.

📒 Files selected for processing (17)
  • mlir/include/mlir/Dialect/QCO/Transforms/Decomposition/EulerBasis.h
  • mlir/include/mlir/Dialect/QCO/Transforms/Decomposition/EulerDecomposition.h
  • mlir/include/mlir/Dialect/QCO/Transforms/Decomposition/Gate.h
  • mlir/include/mlir/Dialect/QCO/Transforms/Decomposition/GateKind.h
  • mlir/include/mlir/Dialect/QCO/Transforms/Decomposition/GateSequence.h
  • mlir/include/mlir/Dialect/QCO/Transforms/Decomposition/Helpers.h
  • mlir/include/mlir/Dialect/QCO/Transforms/Decomposition/UnitaryMatrices.h
  • mlir/lib/Dialect/QCO/Transforms/Decomposition/EulerBasis.cpp
  • mlir/lib/Dialect/QCO/Transforms/Decomposition/EulerDecomposition.cpp
  • mlir/lib/Dialect/QCO/Transforms/Decomposition/GateSequence.cpp
  • mlir/lib/Dialect/QCO/Transforms/Decomposition/Helpers.cpp
  • mlir/lib/Dialect/QCO/Transforms/Decomposition/UnitaryMatrices.cpp
  • mlir/unittests/Dialect/QCO/Transforms/CMakeLists.txt
  • mlir/unittests/Dialect/QCO/Transforms/Decomposition/CMakeLists.txt
  • mlir/unittests/Dialect/QCO/Transforms/Decomposition/decomposition_test_utils.h
  • mlir/unittests/Dialect/QCO/Transforms/Decomposition/test_decomposition_helpers.cpp
  • mlir/unittests/Dialect/QCO/Transforms/Decomposition/test_euler_decomposition.cpp

Comment thread mlir/lib/Dialect/QCO/Transforms/Decomposition/UnitaryMatrices.cpp
denialhaag and others added 2 commits April 29, 2026 16:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature New feature or request MLIR Anything related to MLIR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants