Skip to content

Add backend argument to Circuit.simulate_statevector#512

Merged
matulni merged 12 commits into
TeamGraphix:masterfrom
matulni:symb-backend
May 29, 2026
Merged

Add backend argument to Circuit.simulate_statevector#512
matulni merged 12 commits into
TeamGraphix:masterfrom
matulni:symb-backend

Conversation

@matulni
Copy link
Copy Markdown
Contributor

@matulni matulni commented May 20, 2026

This PR adds the possibility to pass a custom DenseStateBackend to Circuit.simulate_statevector.

Currently, backend instantiation is done internally. In a future PR, current StatevectorBackend will be subsumed by the jit backend which does not support symbolic calculations, and moved to graphix-symbolic plugin. To continue supporting symbolic circuit simulation it will be necessary to use Circuit.simulate_statevector with the symbolic backend.

Additionally, @pranav97nair mentioned that there is some interest in supporting circuit simulations on DensityMatrix objects (even if noise is not supported for circuits, and there aren't any plans to include this functionality). This PR allows so.

To discuss

With the current proposal, naming is not very accurate:

  • graphix.transpiler.SimulateResult.statevec would be better called state
  • graphix.transpiler.Circuit.simulate_statevector would be better called simulate (or simulate_circuit to mirror Pattern.simulate_pattern, although I find this naming redundant).

However, this refactoring will have an important impact in the API, and many changes will be necessary across dependencies. Is it worth?

@codecov
Copy link
Copy Markdown

codecov Bot commented May 20, 2026

Codecov Report

❌ Patch coverage is 85.71429% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 88.84%. Comparing base (b2832a6) to head (4833f57).
⚠️ Report is 1 commits behind head on master.

Files with missing lines Patch % Lines
graphix/transpiler.py 85.71% 5 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #512      +/-   ##
==========================================
- Coverage   88.91%   88.84%   -0.08%     
==========================================
  Files          49       49              
  Lines        7111     7127      +16     
==========================================
+ Hits         6323     6332       +9     
- Misses        788      795       +7     

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

Comment thread graphix/transpiler.py Outdated
"""

statevec: Statevec
statevec: _DenseStateT_co
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Unfortunately, mypy rejects covariant types as dataclass parameters starting with Python 3.13 (see the related thread for details):

graphix/transpiler.py:71: error: Cannot use a covariant type variable as a parameter  [misc]

The CI currently passes only because it still runs on Python 3.12.

The simplest fix is to drop the covariance annotation (i.e., use _DenseStateT as the type parameter for SimulateResult instead of _DenseStateT_co). We don't rely on this covariance anywhere in the code base, and SimulateResult is mainly used for internal testing.

The remaining occurrences of _DenseStateT_co are only for annotating polymorphic methods. In that context the variance annotation has no effect, so it's better to replace them with _DenseStateT as well.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Nice catch, thanks!

Comment thread graphix/transpiler.py Outdated
*,
stacklevel: int = 1,
) -> SimulateResult:
) -> SimulateResult[_DenseStateT_co]:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This return type is too restrictive and forces you to use a cast in the return clause. The type variable _DenseStateT_co isn't bound by the _DenseStateBackendLiteral branch of the backend type, which means that this method is expected to return SimulateResult[_DenseStateT] without any lower-bound on _DenseStateT. That's impossible because _DenseStateT could be an empty (unconstrained) type.

We should let the function return any of the state types that _initialize_backend can produce:

Suggested change
) -> SimulateResult[_DenseStateT_co]:
) -> SimulateResult[_DenseStateT] | SimulateResult[_DenseStateT | Statevec | DensityMatrix]:

This annotation is necessarily redundant: we have lost the information that SimulateResult is covariant, so SimulateResult[_DenseStateT] is not a subtype of SimulateResult[_DenseStateT | Statevec | DensityMatrix].

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the explanation, but I still don't understand why this works...

  1. Why is the expected type SimulateResult[_DenseStateT] | SimulateResult[_DenseStateT | Statevec | DensityMatrix] and not SimulateResult[_DenseStateT] | SimulateResult[Statevec] | SimulateResult[DensityMatrix] ?

  2. Why dropping the covariance fixes the issue on the lower bound of _DenseStateT?

I would say that with covariance SimulateResult[_DenseStateT_co] | SimulateResult[_DenseStateT_co | Statevec | DensityMatrix] is equivalent to SimulateResult[_DenseStateT_co].

I understand the need for redundancy with _DenseStateT, but why does this remove the need to recast ?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The type of _backend.state is _DenseStateT | Statevec | DensityMatrix. Therefore the call SimulateResult(_backend.state, ...) has the type SimulateResult[_DenseStateT | Statevec | DensityMatrix]. This type is a subtype of the declared result type SimulateResult[_DenseStateT] | SimulateResult[_DenseStateT | Statevec | DensityMatrix], so no cast is required.

The result type must also be a supertype of SimulateResult[_DenseStateT] in order to satisfy the overload signatures.

In general, we do not have an equation of the form F[A | B] == F[A] | F[B]. For example, a list containing elements of type A | B is not either a list of A or a list of B; it is a heterogeneous collection. The equation would happen to be true for SimulateResult, because the class only wraps one element of its parameter type, but mypy does not recognize this isomorphism. One could write an explicit conversion function that maps aSimulateResult[A | B] into SimulateResult[A] | SimulateResult[B], but that would be just a manual workaround.

I mentioned the covariance because, if SimulateResult is declared as covariant, then SimulateResult[_DenseStateT] would be a subtype of SimulateResult[_DenseStateT | Statevec | DensityMatrix]. Consequently, the union SimulateResult[_DenseStateT] | SimulateResult[_DenseStateT | Statevec | DensityMatrix] would collapse to the simpler type SimulateResult[_DenseStateT | Statevec | DensityMatrix].

@matulni
Copy link
Copy Markdown
Contributor Author

matulni commented May 27, 2026

Thanks for the review @thierry-martinez. I added your comments in 1737830 (even if I still would help some discussion to fully understand the logic).

What do you think about the naming convention ("To discuss" section in the original PR text)?
Since this function will be mostly relegated for internal use, I'd would be inclined to leave it as is.

@thierry-martinez
Copy link
Copy Markdown
Collaborator

What do you think about the naming convention ("To discuss" section in the original PR text)? Since this function will be mostly relegated for internal use, I'd would be inclined to leave it as is.

Yes, we can postpone the renaming for a latter refactoring PR.

Copy link
Copy Markdown
Collaborator

@thierry-martinez thierry-martinez left a comment

Choose a reason for hiding this comment

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

LGTM! Just one minor comment.

Comment thread CHANGELOG.md Outdated
Co-authored-by: thierry-martinez <thierry.martinez@inria.fr>
Copy link
Copy Markdown

@pranav97nair pranav97nair left a comment

Choose a reason for hiding this comment

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

Looks good to me, thanks Mateo! Though I would also like to get together to make sure I understand the use of covariant types properly.

@matulni matulni merged commit 6de358c into TeamGraphix:master May 29, 2026
24 checks passed
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