Skip to content

Add ROWCOL CNOT routing algorithm #7394

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 36 commits into
base: phase-polynomials
Choose a base branch
from
Draft

Add ROWCOL CNOT routing algorithm #7394

wants to merge 36 commits into from

Conversation

dwierichs
Copy link
Contributor

@dwierichs dwierichs commented May 7, 2025

Context:
A pure CNOT circuit can be described entirely via its parity matrix. In #7229, a function is added to qml.labs to compute the parity matrix for a CNOT circuit. The answer there is unique: A CNOT circuit maps to one unique parity matrix.
The reverse task of extracting, or synthesizing, a CNOT circuit that implements a given parity matrix is accomplished by a series of algorithms in the literature, a better-known one being ROWCOL introduced here and described in the compilation hub here.
Note that there are infinitely many CNOT circuits that produce the same parity matrix, so that the answer to this task is never unique, and different algorithms will return CNOT circuits with different gate counts and depths.

Description of the Change:
Add a rowcol function to qml.labs that takes a parity matrix $P$ and a qubit connectivity graph as inputs and returns a CNOT circuit that has the parity matrix $P$ and that respects the connectivity.

Benefits:
CNOT routing and circuit optimization capabilities.

Possible Drawbacks:
N/A

Related GitHub Issues:

runora95 and others added 29 commits April 29, 2025 13:08
**Context:**
As part of the sphinx upgrade, sphinx-action has been upgrade from
python 3.9 to 3.10 and sphinx 2.2 to min 7.0. The upgrade has caused
issues with the docs workflow as it has introduced new warnings that
cause the workflow to fail.

**Description of the Change:**
This change will change sphinx-action to use a version fixed to python
3.9 until the [permanent
sphinx](#7212) changes are
merged.

**Benefits:**
Sphinx build will not give any more unnecessary warnings.

**Possible Drawbacks:**
None
**Related GitHub Issues:**
**Context:**

The repr for empty `Sum` and `Prod` was just an empty string. This did
not communicate the existance of an operator instance, leading to
confusion.

**Description of the Change:**

Adds a special repr for empty sums and prods.

**Benefits:**

Tracebacks and logging now communicate what actually exists.

**Possible Drawbacks:**

**Related GitHub Issues:**

---------

Co-authored-by: Mudit Pandey <[email protected]>
**Context:**

**Description of the Change:**

```
dev = qml.device("default.qubit", wires=2)

@qml.qnode(dev, interface=None)
def circuit():
    qml.Snapshot("sample", measurement=qml.sample(), shots=5)
    return qml.expval(qml.X(0))
qml.snapshots(circuit)()
```
```
{'sample': array([[0, 0],
        [0, 0],
        [0, 0],
        [0, 0],
        [0, 0]]),
 'execution_results': 0.0}
```

**Benefits:**

**Possible Drawbacks:**

**Related GitHub Issues:**

[sc-89771]

---------

Co-authored-by: Isaac De Vlugt <[email protected]>
**Context:**

`Snapshot` wasn't working with defer measurements handling of mcms.
```
dev = qml.device("default.qubit", wires=1)

@qml.qnode(dev)
def func():
    qml.Hadamard(wires=0)
    m_0 = qml.measure(0)  # without this mcm everything's fine
    qml.Snapshot("label")
    return qml.probs()

print(qml.snapshots(func)())
```

**Description of the Change:**

Switch the order of applying defer measurements and adding the device
wires.

**Benefits:**

Now it works.

**Possible Drawbacks:**

We still need to figure out dynamic one shot and tree-traversal. But
those are harder problems.

**Related GitHub Issues:**

Partially solves #7334 . Only for defer measurements though. [sc-89863]

---------

Co-authored-by: Isaac De Vlugt <[email protected]>
)

Follow up to #5448,
applying the same solution to the 3-cnot case.

Fixes: #7339
[sc-90052]

---------

Co-authored-by: Yushao Chen (Jerry) <[email protected]>
**Context:**
The `process_counts` functions were added to `CountsMP` and `SamplesMP`
a while back, to expand support in cases with `shots` where the initial
device results are `counts` instead of `samples`.

Some cases where these measurement processes included observables (i.e.
`qml.counts(qml.Z(0))`, `qml.sample(qml.X("a")`) returned incorrect
results, specifically:

```
>>> counts_mp = qml.counts(qml.Z(0))
>>> counts_mp.process_counts({"00": 2, "10": 3}, wire_order=[0, 1])
{"0": 2, "1": 3} 
```
instead of `{-1.0: 3, 1.0: 2}`, and

```
>>> sample_mp = qml.sample(qml.Z(0) @ qml.Z(1))
>>> sample_mp.process_counts({"00": 2, "10": 3}, wire_order=[0, 1])
[[ 1.  1.]
 [ 1.  1.]
 [-1.  1.]
 [-1.  1.]
 [-1.  1.]]
```
instead of `[1., 1., -1., -1., -1.]`.

**Description of the Change:**
Added/modified handling of samples/counts when `CountsMP` or `SamplesMP`
have eigenvalues.

**Related GitHub Issues:**
#7344 #7343 

[sc-90071]
… be on any number of wires (#7312)

**Context:**

This PR is separated from #7311  to allow us to update catalyst easily.

`AllWires` and `AnyWires` are overkill that don't really need to exist.
Setting `Operator.num_wires = None` can sufficiently communicate "I can
accept any number of wires". We haven't validated `AllWires` or done
anything special about it for many releases. All these serve to do it
tell the default `Operator.__init__` to not validate how many wires the
user passes in. We can do that with `None`.

All they really do is make the interface more complicated and cluttered.

**Description of the Change:**

Default to using `Operator.num_wires = None` to indicate that the
operator can be on any number of wires.

**Benefits:**

Cleaner Operator interface. Sets us up for deprecating `WiresEnum`.

**Possible Drawbacks:**

Deprecations and removals always run the risk of breaking something for
someone, but I think this a harmless enough change.
[sc-89159]
PR #7312 just does the
num_wires = None part of this change and will allow us to update
catalyst before merging this PR.

Context:

AllWires and AnyWires are overkill that don't really need to exist.
Setting Operator.num_wires = None can sufficiently communicate "I can
accept any number of wires". We haven't validated AllWires or done
anything special about it for many releases. All these serve to do it
tell the default Operator.__init__ to not validate how many wires the
user passes in. We can do that with None.

All they really do is make the interface more complicated and cluttered.

Description of the Change:

Deprecate AllWires, AnyWires, and WiresEnum.

Benefits:

Cleaner Operator interface.

Possible Drawbacks:

Deprecations and removals always run the risk of breaking something for
someone, but I think this a harmless enough change.

Related GitHub Issues:

[[sc-89159](https://app.shortcut.com/xanaduai/story/89159)]

---------

Co-authored-by: Yushao Chen (Jerry) <[email protected]>
**Context:**

**Description of the Change:**

**Benefits:**

**Possible Drawbacks:**

**Related GitHub Issues:**

Fixes #7359
[sc-90233]

---------

Co-authored-by: Yushao Chen (Jerry) <[email protected]>
**Context:**

The `operation.py` module currently serves to provide both the core
interface for the operator abstraction, and a bunch of minor utility
functions.

Some of those minor utility functions have dependencies on downstream
components, like `MeasurementProcess` and `QuantumScript`. Others are
examples where the interface is more complicated than the code they
hide. They just end up complicating the interface and don't really even
simplify anything.

**Description of the Change:**

Deprecate all the `BooleanFn`'s present in `operation.py` in favour of
people just using the relevant code.

**Benefits:**

`operation.py` can be more focused on setting up the abstractions and
interfaces for operators. `operation.py` looses dependencies on
downstream structures. The interface is less cluttered with unnecessary
helpers.

**Possible Drawbacks:**

As it is still a deprecation, someone out there might still be depending
on these things.

**Related GitHub Issues:**

[sc-89158]

---------

Co-authored-by: Yushao Chen (Jerry) <[email protected]>
Co-authored-by: Isaac De Vlugt <[email protected]>
Co-authored-by: Pietropaolo Frisoni <[email protected]>
Current implementation summary:
- We leave `ftqc.measure_z` completely equivalent to `qml.measure`, so
it captures the standard `measure` primitive for PennyLane

```
qml.capture.enable()

@qml.qnode(dev)
def circ1():
    qml.measure(0)
    return qml.expval(Z(0))
    
@qml.qnode(dev)
def circ2():
    measure_z(0)
    return qml.expval(Z(0))
```

```
>>> jaxpr = jax.make_jaxpr(circ1)()
>>> jaxpr.eqns[0].params["qfunc_jaxpr"].eqns
[_:i64[] = measure[postselect=None reset=False] 0,
 a:AbstractOperator() = PauliZ[n_wires=1] 0,
 a:AbstractMeasurement(n_wires=None) = expval_obs b]
```

```
>>> jaxpr = jax.make_jaxpr(circ2)()
>>> jaxpr.eqns[0].params["qfunc_jaxpr"].eqns
[_:i64[] = measure[postselect=None reset=False] 0,
 a:AbstractOperator() = PauliZ[n_wires=1] 0,
 a:AbstractMeasurement(n_wires=None) = expval_obs b]
```

- We capture `measure_x`, `measure_y` and `measure_arbitrary_basis` all
with the same parametrized measurement primitive, `p_measure`. This is
under the assumption that the distinction is primarily a user-facing one
for reading code and drawing circuits, as well as more specific
diagonalization, and none of those things are relevant at the point
where we are capturing for catalyst, so they may as well all just be one
thing that is handled in a uniform manner. So when captured, these
circuits are identical:

```
@qml.qnode(dev)
def circ1():
    measure_x(0)
    return qml.expval(Z(0))

@qml.qnode(dev)
def circ2():
    measure_arbitrary_basis(0, angle=0, plane="XY")
    return qml.expval(Z(0))
```

```
>>> jaxpr = jax.make_jaxpr(circ1)()
>>> jaxpr.eqns[0].params["qfunc_jaxpr"].eqns
[_:i64[] = p_measure[angle=0 plane=XY postselect=None reset=False] 0,
 a:AbstractOperator() = PauliZ[n_wires=1] 0,
 a:AbstractMeasurement(n_wires=None) = expval_obs b]
```

```
>>> jaxpr = jax.make_jaxpr(circ2)()
>>> jaxpr.eqns[0].params["qfunc_jaxpr"].eqns
[_:i64[] = p_measure[angle=0 plane=XY postselect=None reset=False] 0,
 a:AbstractOperator() = PauliZ[n_wires=1] 0,
 a:AbstractMeasurement(n_wires=None) = expval_obs b]
```

- We capture `cond_measure` by putting it inside a `cond` primitive
using `qml.cond`. This wouldn't work without program capture on, but my
understanding is that this will be more flexible with plxpr. We can make
a new primitive if that's not the case. The essential thing here is that
we need to be able to use the value returned by whichever function is
called going forward, so `cond` needs to have the option to return
something, i.e. in the following we need `a:i64[]` to be the value
returned by whichever of the measurement functions we end up executing
inside cond:

```
@qml.qnode(dev)
def circ():
    m1 = measure_x(0)
    m2 = cond_measure(m1, measure_x, measure_y)(2)
    cond_measure(m2, measure_x, measure_y)(3)
    return qml.expval(Z(0))
```

```
>>> jaxpr = jax.make_jaxpr(circ)()
>>> jaxpr.eqns[0].params["qfunc_jaxpr"].eqns[1]
a:i64[] = cond[
  args_slice=slice(2, None, None)
  consts_slices=[slice(2, 2, None), slice(2, 2, None)]
  jaxpr_branches=[{ lambda ; a:i64[]. let
    b:i64[] = p_measure[angle=0 plane=XY postselect=None reset=False] a
  in (b,) }, { lambda ; a:i64[]. let
    b:i64[] = p_measure[
      angle=1.5707963267948966
      plane=XY
      postselect=None
      reset=False
    ] a
  in (b,) }]
] b True 2
```

- We make a new function, `make_graph_state`, that either creates a
GraphStatePrep operation (if `capture` is off), or decomposes it and
captures the operations generated in the decomposition (if `capture` is
on). This circumvents JAX's inability to handle a networkx graph. Maybe
we want to maintain some more efficient way of storing this in a single
primitive in the future, but for now this seems simple and acceptable.

```
import networkx as nx
from pennylane.ftqc import make_graph_state

@qml.qnode(dev)
def circ():
    make_graph_state(nx.grid_graph((4,)), wires=[1, 2, 3, 4])
    return qml.expval(X(4))
``` 

```
>>> jax.make_jaxpr(circ.func)()
{ lambda ; . let
    _:AbstractOperator() = Hadamard[n_wires=1] 1
    _:AbstractOperator() = Hadamard[n_wires=1] 2
    _:AbstractOperator() = Hadamard[n_wires=1] 3
    _:AbstractOperator() = Hadamard[n_wires=1] 4
    _:AbstractOperator() = CZ[n_wires=2] 1 2
    _:AbstractOperator() = CZ[n_wires=2] 2 3
    _:AbstractOperator() = CZ[n_wires=2] 3 4
    a:AbstractOperator() = PauliX[n_wires=1] 4
    b:AbstractMeasurement(n_wires=None) = expval_obs a
  in (b,) }
```

```
>>> qml.capture.disable()
>>> qml.workflow.construct_tape(circ)().operations
[GraphStatePrep(Hadamard, CZ)]
```
---------------------------------

**Context:**

**Description of the Change:**

**Benefits:**

**Possible Drawbacks:**

**Related GitHub Issues:**


[sc-88520]

---------

Co-authored-by: Joey Carter <[email protected]>
**Context:**
For benchmarking, we need a method of precise resource tracking. This
method has to be compatible with code `@qjit` compiled using Catalyst
and should be able to track precise resources after various
transformation passes have already occurred.

See also: PennyLaneAI/pennylane-benchmarks#76

**Description of the Change:**
Adds a new attribute to `null.qubit`, as well as an optional argument to
its `__init__` function. When this option is toggled on, `null.qubit`
will track precise gate counts when "executing" a circuit.

**Benefits:**
It is now possible to track precise gate counts (with or without gate
decomposition or other transformation passes) for measuring circuit
complexity.

**Possible Drawbacks:**
To be mutually compatible with the catalyst implementation, this
currently just saves the resources JSON to a file as opposed to neatly
returning an object. In the future, a better solution may be desired.
This implementation causes `null.qubit` to have a different capability
that other devices do not have. If this feature is useful elsewhere, it
might be wise to move resource tracking to the base Device class.

**Related GitHub Issues:**
[sc-88213]

---------

Co-authored-by: Christina Lee <[email protected]>
**Context:**

When fixing up the tests for jax v0.5.3, I realized we were capturing
QSVT wrong.

**Description of the Change:**

Treat the projectors as traceable objects, rather than metadata.

**Benefits:**

**Possible Drawbacks:**

**Related GitHub Issues:**
**Context:**
Docs.yml uses python3.9 and sphinx version 3.5. It is currently a
blocker to dropping python 3.10 in pennylane. Upgrading sphinx to
version 8.1 allows for higher versions that are compatible with >Python
3.11 to be used later.

**Description of the Change:**
Sphinx is upgraded to version 8.1 and uses Python 3.10. References to
`<demos>` are updated to remove the `:doc:` prefix which is incompatible
with sphinx 8.1 and was broken previously.

**Benefits:**
Python 3.9 will be dropped.

**Possible Drawbacks:**

**Related GitHub Issues:**

---------

Co-authored-by: Rashid N H M <[email protected]>
…om int to bool (#7368)

**Context:** The JAX primitive representing arbitrary-basis measurements
currently returns a `jax.core.ShapedArray` of type `int64` (or `int32`
if `jax.config.jax_enable_x64` is False). However, the corresponding
operation in Catalyst returns a `bool`. Having different return types is
generally fine, except in certain circumstances when qjit compiling a
circuit containing bitwise operations on a measurement result and
boolean literals. For instance, the following workload would generate
the following Catalyst jaxpr:

```python
dev = qml.device("null.qubit", wires=1)

qml.capture.enable()

@qjit(target="jaxpr")
@qml.qnode(dev)
def foo():
    m0 = qml.ftqc.measure_x(0)
    x = m0 ^ True
    return qml.expval(qml.Z(0))

qml.capture.disable()
```

```pycon
>>> print(foo.jaxpr)  # Output abbreviated for clarity
...
d:bool[] e:AbstractQbit() = measure_in_basis[
  plane=MeasurementPlane.XY
  postselect=None
] 0.0 c
_:bool[] = xor d 1    # <-- Literal `True` implicitly converted to `1`
...
```

Lowering this jaxpr to MLIR results in a compiler error because the
`xor` operation applied to variable `d` (a `bool`) and a literal `1`
(interpreted as an `int64`) is not permitted.

**Description of the Change:** Changes the return type of
`measure_in_basis_p` abstract eval from `int` to `bool` and updates the
affected tests appropriately. Running the same example above generates
the following Catalyst jaxpr, as expected:

```
d:bool[] e:AbstractQbit() = measure_in_basis[
  plane=MeasurementPlane.XY
  postselect=None
] 0.0 c
_:bool[] = xor d True    # <-- Literal `True` unchanged
```

**Benefits:** This change will enable us to qjit compile a CNOT gate
expressed in the MBQC formalism, which contains a by-product correction
of the form `m0 ^ m1 ^ ... ^ 1`, where `m*` are measurement outcomes.

**Possible Drawbacks:** The computation-basis mid-circuit measurement
operation `qml.measure()` continues to return 32-/64-bit `ints`, putting
the MCMs in the `ftqc` module out of sync with their core PennyLane
counterpart. We may be able to change the abstract eval return type of
the computation-basis measurement primitives as well, although such a
change could have broader implications elsewhere.
**Context:**

`Observable` no longer really serves a purpose, as operator arithmetic
is now generic for all operators. It only really serves to communicate
an implicit `is_hermitian`, and stops the object from getting processed
into the tape via `_queue_category=None`.

So we can now get rid of it in favor of using the simpler `Operator`
abstraction.

**Description of the Change:**

Deprecates `Observable`.

**Benefits:**

Simpler abstractions.

**Possible Drawbacks:**

**Related GitHub Issues:**

[sc-89295]

---------

Co-authored-by: Yushao Chen (Jerry) <[email protected]>
Co-authored-by: Isaac De Vlugt <[email protected]>
Co-authored-by: Pietropaolo Frisoni <[email protected]>
**Context:**
As title

**Description of the Change:**

**Benefits:**
This helps a lot when developers search for the correct method!

**Possible Drawbacks:**

**Related GitHub Issues:**
This PR applies some changes to master coming from [this
PR](#7369), which has been
merged in the release candidate branch for the bugfix release of
PennyLane 0.41.1.

More details can be found in the original PR description. This PR aims
to update the master branch with the changes currently present in the
stable version of PennyLane (0.41.1, no longer 0.41.0)
Automatic update of stable requirement files to snapshot valid python
environments.

Because bots are not able to trigger CI on their own, please do so by
pushing an empty commit to this branch using the following command:

```
git commit --allow-empty -m 'trigger ci'
```

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Pietropaolo Frisoni <[email protected]>
**Context:** PR #7226 added a resource-tracking feature to PennyLane.
The same feature is desired in Catalyst:
PennyLaneAI/catalyst#1619

**Description of the Change:** Adds a class attribute to `null.qubit`
which allows the `resource_tracking` argument to get passed through to
Catalyst.

**Benefits:** `track_resources` can more cleanly be passed through to
Catalyst.

**Possible Drawbacks:**

**Related GitHub Issues:**
#7226
PennyLaneAI/catalyst#1619
PennyLaneAI/pennylane-benchmarks#76
[sc-88213](https://app.shortcut.com/xanaduai/story/88213)

---------

Co-authored-by: Ali Asadi <[email protected]>
**Context:** 

This is one of the first PRs implementing the new quantum compiler in
PennyLane.

This PR was originally opened in Catalyst:
PennyLaneAI/catalyst#1691

**Description of the Change:** 

The changes introduced in this PR can be divided at a high level into 3
different steps:

*Step 1* 

Generated the Python dialect using `xdsl_tblgen.py`, using the JSON file
generated by `llvm-tblgen` starting from [this Catalyst
file](https://github.com/PennyLaneAI/catalyst/blob/main/mlir/include/Quantum/IR/QuantumOps.td).

*Step 2*

At the current stage, I commented the `assembly_format` attributes,
which generated an error, and added the `AttrSizedOperandSegments` and
`AttrSizedResultSegments` metadata

In fact, in the generated dialect, there is a class of errors that would
be good to fix in Catalyst. Erick is writing them as issues in Catalyst.
[Here](PennyLaneAI/catalyst#1692) and
[here](PennyLaneAI/catalyst#1690). Once these
issues are solved, we believe the whole quantum dialect would be easy to
generate and work relatively well to proceed to either other dialects or
the cancel-inverses transform.

*Step 3*

Writing some minimal tests for the dialect. These will be improved and
expanded (we will have multiple chats about this point).

**Benefits:**

**Possible Drawbacks:**

**Related GitHub Issues:** None.

**Related Shortcut Story:** [sc-89798]

---------

Co-authored-by: Isaac De Vlugt <[email protected]>
**Context:** The PyPI release process requires more strict adherence to
the
https://packaging.python.org/en/latest/specifications/binary-distribution-format/
guidelines. `PennyLane` the package name is not compliant with this
naming, and requires the released wheels to be named `pennylane`. Older
versions of setuptools were less strict about this naming, and forwarded
what was requested in the package metadata. Upgrade setuptools and wheel
build infrastructure ensures that we adhere with the wheel naming
expectations of PyPI.
Additionally, this PR also removes the deprecated license classifier,
removing a warning of `SetuptoolsDeprecationWarning: License classifiers
are deprecated`.

**Description of the Change:** Replaces the direct call to setup.py for
wheel building with a call to https://github.com/pypa/build

**Benefits:** Ensures adherence to Python packaging expectations coming
into enforcement from PyPI. Fixes future failure expected during
release.

**Possible Drawbacks:**

**Related GitHub Issues:**
@dwierichs dwierichs changed the base branch from master to phase-polynomials May 7, 2025 14:40
lillian542 and others added 7 commits May 7, 2025 16:12
**Context:**

When we diagonalize the parametric mid-circuit measurements to get
analytic results to validate our protocols, we swap them out with new,
updated measurements. This is currently updated in the conditionals, but
not on the measurements. That means that returning parametric mcms when
using the `diagonalize_mcms` transform currently fails.

**Description of the Change:**

We also update the measurements in the transform - if any
MeasurementProcess contains MCMs, we update them based on the existing
dictionary that's tracking updated measurement values, and create a copy
of the MP with the new MeasurementValue(s) instead of the original ones.

**Benefits:**

We can do this:

```
@diagonalize_mcms
@qml.qnode(dev)
def circ():
    qml.H(0)
    m = measure_x(0, reset=True)
    return qml.expval(m)
```

[sc-90609]
**Context:**
See #7215 for the bug
here. Separately recorded as a standalone bug:
#7395

**Description of the Change:**
change the `np.` in executable source code to `qml.math.` for
agnosticity.
added several tests for `default.mixed` to make sure the trainable
parameters are treated well

**Benefits:**
Users would be able to use `default.mixed` on other interfaces e.g.
torch cuda

**Possible Drawbacks:**
We don't have the GPU test suites for `default.mixed`!

**Related GitHub Issues:**
[sc-90500]

---------

Co-authored-by: Isaac De Vlugt <[email protected]>
…ottonenStatePreparation`) (#7377)

**Context:**
In `MottonenStatePreparation`, there is a step where the desired phases
need to be transformed into rotation angles for the Gray code-based
rotation sequence that implements the phases.
The sequence is shown in Fig. 2, the angle transformation in Eq. (3) of
[Möttönen et al.](https://arxiv.org/pdf/quant-ph/0407010).

Currently, the transformation is done by explicitly constructing the
matrix $M$ from Eq.(3) and multiplying it to the angles.
This is not only painstakingly slow but also requires quadratic memory
size compared to the input and output. If we care about preparing larger
state vectors, this limits the computation to about 15 qubits, which it
does not have to do.

Instead, one can compute the angles with a fast Walsh-Hadamard transform
(FWHT) and subsequent permutations. These permutations act as if we
would apply a ladder of CNOT gates to the angles, when interpreting them
as a quantum state.

**Description of the Change:**
Change `compute_theta`, which implements the transform, from the
matrix-based method to the FWHT-based one.

**Benefits:**
Roughly reduces memory requirement from `O(d^2)` to `O(d)`, where `d` is
the input (and output) dimension.
Reduces compute time _heavily_:

![image](https://github.com/user-attachments/assets/de083bcb-d5ff-4406-8dca-07395c13396d)


**Possible Drawbacks:**
N/A

**Related GitHub Issues:**
#7378 
[sc-90387]

---------

Co-authored-by: Korbinian Kottmann <[email protected]>
Co-authored-by: Isaac De Vlugt <[email protected]>
@Qottmann Qottmann self-requested a review May 8, 2025 13:12
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.