Skip to content

Conversation

DmitryVasilevsky
Copy link
Contributor

@DmitryVasilevsky DmitryVasilevsky commented Sep 30, 2025

This check-in improves implementation of adjoin variant of Select operation (Unlookup operation). Previously it was based on May 2019 paper by Craig Gidney, and this proposed implementation is based on 2025 paper by Craig Gidney.

The implementation of Unlookup is measurement-based with phase correction via Phase Lookup operation described in the aforementioned paper. Phase Lookup is implemented by splitting address register and computing power products for both halves. Then the necessary phase corrections are applied and power products are uncomputed.

This implementation uses fast Boolean Mobius transform instead of matrix multiplication as in the paper to convert classical data from truth table representation to coefficients of an algebraic normal form over GF(2) field. This results in simpler code.

A test is added to check that address register remains in correct state after uncomputation.

This implementation may reduce required resources to uncompute lookup operation. As one example, the circuit to uncompute one particular case with previous and proposed approaches. This is a highly dynamic algorithm that uses results of measurements to apply gates conditionally; actual execution trace and actual savings may vary.

Before:
Unlookup
After:
Phaseup

Copy link

Change in memory usage detected by benchmark.

Memory Report for b44f15e

Test This Branch On Main Difference
compile core + standard lib 24787854 bytes 24606746 bytes 181108 bytes

Copy link

Change in memory usage detected by benchmark.

Memory Report for fb4d8bc

Test This Branch On Main Difference
compile core + standard lib 24787854 bytes 24606746 bytes 181108 bytes

@swernli
Copy link
Collaborator

swernli commented Sep 30, 2025

Does this change the profile compatibility of the Select operation? By that I mean, since the new one uses measurement-based uncomputation it would only be usable in Adaptive profile programs. If Select was already only usable from Adaptive this isn't a change because it was never available for Base profile, but if it used to work then this could be considered a breaking change.

@DmitryVasilevsky
Copy link
Contributor Author

... By that I mean, since the new one uses measurement-based uncomputation it would only be usable in Adaptive profile ...
Current code already uses measurement-based uncomputation.

@swernli
Copy link
Collaborator

swernli commented Oct 1, 2025

... By that I mean, since the new one uses measurement-based uncomputation it would only be usable in Adaptive profile ...

Current code already uses measurement-based uncomputation.

Actually, I don't think that is the case. Without your changes, Adjoint Select was usable in Base profile:
image

But after with the changes it produces an error:
image

I think this is because previously the adjoint implementation called Std.Intrinsic.AND which has two implementations, one compatible with Base profile and one that uses the measurement-based uncomputation. If we want to avoid a breaking change but still have the updated version of Select we would need to use a similar strategy and define the operation twice using the @Config attribute to make sure a compatible version is present like how AND does.

@DmitryVasilevsky
Copy link
Contributor Author

DmitryVasilevsky commented Oct 1, 2025

... By that I mean, since the new one uses measurement-based uncomputation it would only be usable in Adaptive profile ...

Current code already uses measurement-based uncomputation.

Actually, I don't think that is the case.

OK, maybe we are both right. :) It was using measurement based uncomputation but maybe in a way suitable for base profile - i.e. nothing actually depended on those measurements? I will need to dig deeper.

let res = Mapped(r -> r == One, ForEach(MResetX, target));

Copy link
Collaborator

@swernli swernli left a comment

Choose a reason for hiding this comment

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

Wow, you are right and I missed that! It turns out that this particular line you quote is exactly the problem; there's a class of code patterns where RCA can't definitively determine the compatibility of an operation (usually when it is passed as callable argument into another operation, as is done with ForEach and MResetX here). We used to err on the side of caution and reject these with an error, but too many valid programs were rejected that way. So instead we relaxed the restriction and deferred validation of these passed callables to QIR codegen (see #1497). That means that even though the editor doesn't show an error, Adjoint Select with more than one data entry will fail Base profile QIR codegen.

Long story short, this is not a breaking change at all, and arguably is an improvement since the editor now shows the incompatibility at design time rather than at codegen time.

Copy link
Member

@msoeken msoeken left a comment

Choose a reason for hiding this comment

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

Really exciting new operation to have in the libraries!


// From the second row on, take control from the first half and apply multi-target CZ gates.
for row in 0..Length(products1)-1 {
ApplyMaskedMultitargetCZ(products1[row], products2, Rest(ColumnAt(row + 1, mask)));
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
ApplyMaskedMultitargetCZ(products1[row], products2, Rest(ColumnAt(row + 1, mask)));
Controlled ApplyMaskedMultitargetZ([products1[row]], (products2, Rest(ColumnAt(row + 1, mask))));

You could do this and remove ApplyMaskedMultitargetCZ by also making ApplyMaskedMultitargetZ controlled. Or you do this with Controlled ApplyPauliFromBitString.

Fact(Length(mask[0]) == Length(products1) + 1, "Mask column count must match products1 length.");

// products1[0] doesn't include any qbits from the first half, so we need to apply Z instead of CZ.
ApplyMaskedMultitargetZ(products2, Rest(ColumnAt(0, mask)));
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
ApplyMaskedMultitargetZ(products2, Rest(ColumnAt(0, mask)));
ApplyPauliFromBitString(PauliZ, true, Rest(ColumnAt(0, mask)), products2);

Same for the other ones.

ApplyMaskedMultitargetZ(products2, Rest(ColumnAt(0, mask)));

// products2[0] doesn't include any qubits from the second half, so we need to apply Z instead of CZ.
ApplyMaskedMultitargetZ(products1, Rest(mask[0]));
Copy link
Member

Choose a reason for hiding this comment

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

One could apply the operation once to produts2 + products1 by also concatenating the masks.

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