Conversation
impl_num_ancillas arg from verifier, use @expected_qubits hint instead.@expected_qubits hint in Clifford verifier
PabloAndresCQ
left a comment
There was a problem hiding this comment.
Looks good overall. I'll have a closer look when you mark it as ready for review. Request a re-review at that point.
| semantic_function: SemanticStabilizerState | SemanticStabilizerStateDouble, | ||
| impl_function: ImplementationStabilizerState | ImplementationStabilizerStateDouble, | ||
| code_definition: StabilizerCode, | ||
| impl_num_ancillas: int = 0, |
There was a problem hiding this comment.
We've agreed this should be kept as an optional parameter that overrides the @expected_qubits decorator if present. It should become impl_num_ancillas: int | None = None so that you can identify whether or not the user provided anything that is not None.
Just writing it here so we don't miss it.
@expected_qubits hint in Clifford verifier@expected_qubits hint in Clifford verifier
…semantic simulation
This reverts commit 3cbece5.
| if num_ancilla_qubits > 0: | ||
| return num_qubits + num_ancilla_qubits | ||
|
|
||
| metadata = func.wrapped.metadata._node_metadata # type: ignore[attr-defined] |
There was a problem hiding this comment.
I don't like using ._node_metadata here.
As far as I understand it should also work to do the following with __guppy_metadata__ (more reliable?)
from guppylang.decorator import expected_qubits, guppy
from guppylang.std.quantum import qubit, h
@guppy
@expected_qubits(8)
def steane_impl_h(block: array[qubit, 7]) -> None:
for i in range(len(block)):
h(block[i])
num = steane_impl_h.wrapped.python_func.__guppy_metadata__['tket.hint.expected_qubits']This example works but if I try and use this inside _get_num_func_qubits I get an error. I think this is becuase I should try-except on the AttributeError rather than using .get(). Not totally sure. Any ideas?
Would you prefer
- using
._node_metadataand.get()as is done currently - Avoid using
._node_metadataand use__guppy_metadata__but use a try-except on the AttributeError (or whatever else is needed to make__guppy_metadata__work reliably.__hasattr__would probably work as well actually. - we could compile the function and read the metadata from the HUGR. However this doesn't seem ideal either as we wouldn't compile the function otherwise as it isn't an entrypoint.
| states_dict: dict[str, SeleneStimState] = _invoke_selene_stim( | ||
| main, num_selene_qubits | ||
| ) | ||
| states_dict: dict[str, SeleneStimState] = _invoke_selene_stim(main, num_qubits) |
There was a problem hiding this comment.
I could technically use an expected_qubits hint on the main function. This would require refactoring _invoke_selene_stim to respect the hint though. I think this requires making use of the Guppy .emulator() interface rather than selene_sim.
There was a problem hiding this comment.
Why would adding expected_qubits be required? Is there a reason to have a helper function to invoke selene rather than just using .emulator() already?
There was a problem hiding this comment.
Using @expected_qubits on main isn't necessary, its merely an option.
Yes tbh we should use the guppy .emulator() interface instead and get rid of/modify _invoke_selene_stim. Extracting state outputs for stabilizer simulation with the Guppy interface is undocumented (also untested in guppylang seemingly?). I was going to do some cleanup to use the Guppy interface directly. Would you prefer this to be done here or in a follow up PR?
| # If the user specifies an impl_num_ancillas argument in | ||
| # valid_clifford_implementation or valid_stabilizer_state_preparation this | ||
| # will override any @expected_qubits metadata. | ||
| if num_ancilla_qubits > 0: |
There was a problem hiding this comment.
Maybe if num_ancilla_qubits: is better? Although people shouldn't be passing negative values.
hsemenenko
left a comment
There was a problem hiding this comment.
Some initial comments.
There was a problem hiding this comment.
There's a comment from Pablo that the default should be int | None = None. Is there a reason that it's not? Someone may want to override the number of ancillas to be 0, but if the default is 0 that's not possible.
| num_qubits: int, | ||
| num_ancilla_qubits: int = 0, |
There was a problem hiding this comment.
Is it necessary to have both inputs here (and other _compute functions)? It seems like num_qubits could already be the total number of qubits required.
| if num_ancilla_qubits > 0: | ||
| return num_qubits + num_ancilla_qubits | ||
|
|
||
| metadata = func.wrapped.metadata._node_metadata # type: ignore[attr-defined] |
There was a problem hiding this comment.
Perhaps this is better?
| metadata = func.wrapped.metadata._node_metadata # type: ignore[attr-defined] | |
| assert isinstance(func.wrapped, RawFunctionDef) | |
| metadata = func.wrapped.metadata["tket.hint.expected_qubits"] |
There was a problem hiding this comment.
Building on this I thought I was getting somewhere with
assert isinstance(func.wrapped, RawFunctionDef)
assert isinstance(func.wrapped.metadata, FunctionMetadata)
num_hinted_qubits: int | None func.wrapped.metadata.get_expected_qubits()However if I use this my code seems to hang when running the test suite which is really strange.
There was a problem hiding this comment.
Seems to work now... idk what was going wrong earlier.
| states_dict: dict[str, SeleneStimState] = _invoke_selene_stim( | ||
| main, num_selene_qubits | ||
| ) | ||
| states_dict: dict[str, SeleneStimState] = _invoke_selene_stim(main, num_qubits) |
There was a problem hiding this comment.
Why would adding expected_qubits be required? Is there a reason to have a helper function to invoke selene rather than just using .emulator() already?
|
|
||
| @guppy | ||
| @no_type_check | ||
| @expected_qubits(7 + 14) |
There was a problem hiding this comment.
Or add a comment why the 7 and 14 should be separate.
| @expected_qubits(7 + 14) | |
| @expected_qubits(21) |
| from typing import no_type_check | ||
|
|
||
| from guppylang import guppy | ||
| from guppylang.decorator import expected_qubits, guppy |
There was a problem hiding this comment.
This seems unconventional
| from guppylang.decorator import expected_qubits, guppy | |
| from guppylang import guppy | |
| from guppylang.decorator import expected_qubits |
| def test_zero_ancilla_override() -> None: | ||
| assert valid_clifford_implementation( | ||
| steane.specify_h, | ||
| steane.implement_h_wasted_qubit, | ||
| code_definition=steane.STEANE_DEF, | ||
| impl_num_ancillas=0, | ||
| ) |
There was a problem hiding this comment.
This doesn't seem to test that the number of qubits is actually overridden.
There was a problem hiding this comment.
Would you prefer that I removed it? I can Perhaps change it to an example where the hint has enough qubits but the impl_num_ancillas=0 overrides the ancillas leading to a selene panic. I an add a test that this panic happens.
|
This PR contains breaking changes to the public Python API. Breaking changes summary |
@expected_qubits hint in Clifford verifier@expected_qubits hint in Clifford verifier
|
|
||
| impl_num_qubits = num_blocks * code_definition.num_physical_qubits | ||
|
|
||
| if num_hinted_qubits and not impl_num_ancillas: |
There was a problem hiding this comment.
Same logic in the _compute_clifford_tableaux function attempted to refactor this into a helper but something weird was going on casuing one of the tests to fail.
There was a problem hiding this comment.
something like
def _add_impl_qubits(
impl_num_qubits: int,
num_physical_qubits: int,
impl_num_ancillas: int | None,
num_hinted_qubits: int | None,
) -> None:
if num_hinted_qubits and not impl_num_ancillas:
impl_num_qubits += num_hinted_qubits - num_physical_qubits
if impl_num_ancillas is not None:
impl_num_qubits += impl_num_ancillasShould work but test_steane_ft_zero_state fails which I'm puzzled by.
| parameters, stabilizer generators, and logical operators. | ||
| impl_num_ancillas: The number of ancilla qubits used in the implementation. | ||
| Defaults to zero. | ||
| Defaults to None. |
There was a problem hiding this comment.
Should document what the semantics is when omitting the argument (or passing None): is there a default? Is it zero? ...
There was a problem hiding this comment.
From discussion IRL, if the argument is omitted or None, the value is inferred from the @expected_qubits annotation. (Should document this and also add a test.)
There was a problem hiding this comment.
I realised that most of the tests I've modified to use @expected_qubits satisfy this. I.e. If we specify @expected_qubits and not impl_num_ancillas then the number specified in @expected_qubits is used. I got confused
See the changes to test_verifier_steane.py
| parameters, stabilizer generators and logical operators. | ||
| impl_num_ancillas: The number of ancilla qubits used in the implementation. | ||
| Defaults to zero. | ||
| Defaults to None. |
closes #223
This PR allows the user to specify the number of qubits required by an
impl_functionpassed tovalid_clifford_implementationorvalid_stabilizer_state_preparationwith an@expected_qubitshint. This will allow them to check animpl_functionthat uses ancillas and avoid specifiying animpl_num_ancillasargument tovalid_clifford_implementationorvalid_stabilizer_state_preparation.impl_num_ancillasis now an optional argument (set toNoneby default). In the event that bothimpl_num_ancillasand@expected_qubitsare specified, impl_num_ancillas will be used. This is consistent with the guppy/selene behaviour for@expected_qubits. See thetest_hint_overridefunction intest_verifier_steane.pyNote that we are annotating functions which are not entrypoints, therefore I am reading off the
@expected_qubitsmetadata with the_get_num_func_qubitshelper and pass it on. Not entirely happy with it. See #328 (comment).BREAKING CHANGE:
The
impl_num_ancillasarg tovalid_stabilizer_state_preparationandvalid_clifford_implementationis no longer anint. Its now of typeint | None.