Skip to content

Add Tanner graph type to qecp dialect#2563

Merged
joeycarter merged 41 commits intomainfrom
joeycarter/qecp-tanner-graph
Mar 27, 2026
Merged

Add Tanner graph type to qecp dialect#2563
joeycarter merged 41 commits intomainfrom
joeycarter/qecp-tanner-graph

Conversation

@joeycarter
Copy link
Copy Markdown
Contributor

@joeycarter joeycarter commented Mar 10, 2026

Context: This PR is one in a series of PRs to build a prototype QEC compilation pipeline in Catalyst.

Description of the Change: This PRs adds two abstractions relating to Tanner graphs to the qecp dialect:

  1. Type !qecp.tanner_graph to represent a Tanner graph by its adjacency matrix in Compressed Sparse Column (CSC) form.
  2. Op qecp.assemble_tanner to assemble row_idx and col_ptr arrays into a CSC adjacency matrix representing a Tanner graph.

Example:

%row_idx = arith.constant dense<[3, 3, 4, 4, 0, 1, 1, 2]> : tensor<8xi32>
%col_ptr = arith.constant dense<[0, 1, 3, 4, 6, 8]> : tensor<6xi32>
%tgraph = qecp.assemble_tanner %row_idx, %col_ptr : tensor<8xi32>, tensor<6xi32> -> !qecp.tanner_graph<8, 6, i32>

// Yields the CSC adjacency matrix:
//    ┌           ┐
//    │ 0 0 0 1 0 │
//    │ 0 0 0 1 1 │
//    │ 0 0 0 0 1 │
//    │ 1 1 0 0 0 │
//    │ 0 1 1 0 0 │
//    └           ┘

The equivalent type and operation have also been added to the xDSL python interface to Catalyst.

Note

This dialect is experimental and subject to change extensively in the future.

[sc-111756]

@joeycarter joeycarter force-pushed the joeycarter/qecp-tanner-graph branch from 055b98a to 0664025 Compare March 10, 2026 20:33
@joeycarter joeycarter force-pushed the joeycarter/qecp-tanner-graph branch from 71b00d9 to ca22a18 Compare March 12, 2026 17:27
Copy link
Copy Markdown
Member

@multiphaseCFD multiphaseCFD left a comment

Choose a reason for hiding this comment

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

Nice work Joey! A couple of Qs

Comment thread frontend/catalyst/python_interface/dialects/qecp.py
Comment thread frontend/test/pytest/python_interface/dialects/test_qecp_dialect.py
Comment thread frontend/catalyst/python_interface/dialects/qecp.py
@joeycarter joeycarter marked this pull request as ready for review March 24, 2026 13:13
@codecov
Copy link
Copy Markdown

codecov Bot commented Mar 24, 2026

Codecov Report

❌ Patch coverage is 97.56098% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 97.01%. Comparing base (84fb174) to head (f2729b5).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
...rontend/catalyst/python_interface/dialects/qecp.py 97.56% 1 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main    #2563   +/-   ##
=======================================
  Coverage   97.01%   97.01%           
=======================================
  Files         156      156           
  Lines       17622    17662   +40     
  Branches     1692     1692           
=======================================
+ Hits        17096    17135   +39     
- Misses        388      389    +1     
  Partials      138      138           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Base automatically changed from joeycarter/qecp-basic-ops to main March 26, 2026 17:13
@joeycarter joeycarter requested a review from lillian542 March 26, 2026 17:44
Copy link
Copy Markdown
Member

@multiphaseCFD multiphaseCFD left a comment

Choose a reason for hiding this comment

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

Thanks @joeycarter . One more question: what is the reason that IntegerType is adopted here instead of IndexType here? I think all components in the type and ops would be signless.

@joeycarter
Copy link
Copy Markdown
Contributor Author

Thanks @joeycarter . One more question: what is the reason that IntegerType is adopted here instead of IndexType here? I think all components in the type and ops would be signless.

Good question. It's partly a matter of taste and partly a matter of precedent already established in MLIR.

From the index dialect documentation,

IndexType values are typically used as loop bounds, array subscripts, tensor dimensions, etc.

It's not just a question of signedness, index values have an unknown platform-dependent bit width and have to eventually be lowered to a specific bit width for the target platform. Compare this to integer values which have a known bit width from the beginning.

When MLIR says that index values are used for tensor dimensions, this is slightly misleading, and is mostly applicable to the tensor.dim op. When specifying the shape of a tensor type, in fact it uses int64_t values:

https://github.com/llvm/llvm-project/blob/e80b46efc500d603e0f10f95714765fc406bc625/mlir/include/mlir/IR/BuiltinTypes.td#L1184

The reason being that dynamically shaped tensors need a way to signal that they are dynamic, and under the hood they use a negative number, specifically std::numeric_limits<int64_t>::min() (see here).

All that to say, I've opted to use integer values here to follow what's already done in the definition of tensor types. I've also used integer types as the underlying element type since using index elements would ultimately require a cast operation to concretize the desired bit width, and I think there's no need for this and easier to chose the bit width up front.

@multiphaseCFD
Copy link
Copy Markdown
Member

Thanks @joeycarter . One more question: what is the reason that IntegerType is adopted here instead of IndexType here? I think all components in the type and ops would be signless.

Good question. It's partly a matter of taste and partly a matter of precedent already established in MLIR.

From the index dialect documentation,

IndexType values are typically used as loop bounds, array subscripts, tensor dimensions, etc.

It's not just a question of signedness, index values have an unknown platform-dependent bit width and have to eventually be lowered to a specific bit width for the target platform. Compare this to integer values which have a known bit width from the beginning.

When MLIR says that index values are used for tensor dimensions, this is slightly misleading, and is mostly applicable to the tensor.dim op. When specifying the shape of a tensor type, in fact it uses int64_t values:

https://github.com/llvm/llvm-project/blob/e80b46efc500d603e0f10f95714765fc406bc625/mlir/include/mlir/IR/BuiltinTypes.td#L1184

The reason being that dynamically shaped tensors need a way to signal that they are dynamic, and under the hood they use a negative number, specifically std::numeric_limits<int64_t>::min() (see here).

All that to say, I've opted to use integer values here to follow what's already done in the definition of tensor types. I've also used integer types as the underlying element type since using index elements would ultimately require a cast operation to concretize the desired bit width, and I think there's no need for this and easier to chose the bit width up front.

A quick dive into the xDSL repository confirms that IntegerType natively supports the SIGNLESS attribute. Here is a brief implementation example. We could come back to this if needed later.

Copy link
Copy Markdown
Contributor

@lillian542 lillian542 left a comment

Choose a reason for hiding this comment

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

LGTM, thanks!

@joeycarter joeycarter merged commit 6d0a9f4 into main Mar 27, 2026
38 checks passed
@joeycarter joeycarter deleted the joeycarter/qecp-tanner-graph branch March 27, 2026 00:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants