Skip to content
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

feat: Implement built-in state_result function #905

Open
wants to merge 8 commits into
base: main
Choose a base branch
from

Conversation

tatiana-s
Copy link
Contributor

@tatiana-s tatiana-s commented Apr 1, 2025

Closes #899

@codecov-commenter
Copy link

codecov-commenter commented Apr 1, 2025

Codecov Report

Attention: Patch coverage is 98.83721% with 1 line in your changes missing coverage. Please review.

Project coverage is 93.16%. Comparing base (51b1b81) to head (bf464c5).

Files with missing lines Patch % Lines
guppylang/std/_internal/debug.py 97.72% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #905      +/-   ##
==========================================
+ Coverage   93.12%   93.16%   +0.04%     
==========================================
  Files          84       86       +2     
  Lines        9701     9775      +74     
==========================================
+ Hits         9034     9107      +73     
- Misses        667      668       +1     

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

)

qubits_in = [self.visit(e) for e in node.args[1:]]
# TODO: This should be caught during checking.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Importing qubit for the StateResultChecker in checker.py leads to circular import errors - not sure if there is an obvious other place to move it to

Copy link
Collaborator

Choose a reason for hiding this comment

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

The problem is that qubit is a library defined type and not specifically known by the compiler (unlike the types in builtins).

You would need to do something like the following:

class StateResultChecker(CustomCallChecker):
    """Call checker for the `state_result` function."""

    def synthesize(self, args: list[ast.expr]) -> tuple[ast.expr, Type]:
        ...
        from guppylang.std.quantum import qubit

        # Need to make sure that the quantum module is loaded
        qubit_defn = self.globals[qubit.id]
        assert isinstance(qubit_defn, TypeDef)
        qubit_ty = qubit_defn.check_instantiate([], self.globals)
        # Check against qubit_ty

Also, this only works if the quantum module is loaded. Therefore, it might be a good idea to move state_result into quantum? Or its own debug module that depends on quantum?

Comment on lines 562 to 564
qubits_arr = self.builder.add_op(array_new(ht.Qubit, num_qubits), *qubits_in)
qubits_arr = self.builder.add_op(op, qubits_arr)
qubits_out = unpack_array(self.builder, qubits_arr)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not sure if using arrays in the hugr op is worth this overhead or there is a better way of representing the list of qubits in the extension

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think this is fine for now. Would be a good idea to add an overload of result_state that takes an array anyways as a follow-up

10 | def main() -> None:
11 | q1 = qubit()
12 | state_result(q1)
| ^^ Expected expression of type `str`, got `qubit`
Copy link
Contributor Author

Choose a reason for hiding this comment

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

(While it would be nice to be more specific about the missing tag this is the same as result at the moment)

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think this is fine and also consistent with the message you would get when calling regular functions 👍

@tatiana-s tatiana-s requested a review from mark-koch April 8, 2025 14:31
@tatiana-s tatiana-s marked this pull request as ready for review April 8, 2025 14:31
@tatiana-s tatiana-s requested a review from a team as a code owner April 8, 2025 14:31
@tatiana-s tatiana-s added the wait to merge This PR to be merged after all dependencies are ready label Apr 8, 2025
Copy link
Collaborator

@mark-koch mark-koch 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! Just a couple of nits and suggestions

Comment on lines 549 to 553
raise GuppyTypeError(ExpectedError(tag, "a string literal"))
if len(tag.value.encode("utf-8")) > TAG_MAX_LEN:
err: Error = TooLongError(tag)
err.add_sub_diagnostic(TooLongError.Hint(None))
raise GuppyTypeError(err)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could you add tests for those two errors?

10 | def main() -> None:
11 | q1 = qubit()
12 | state_result(q1)
| ^^ Expected expression of type `str`, got `qubit`
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think this is fine and also consistent with the message you would get when calling regular functions 👍

)

qubits_in = [self.visit(e) for e in node.args[1:]]
# TODO: This should be caught during checking.
Copy link
Collaborator

Choose a reason for hiding this comment

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

The problem is that qubit is a library defined type and not specifically known by the compiler (unlike the types in builtins).

You would need to do something like the following:

class StateResultChecker(CustomCallChecker):
    """Call checker for the `state_result` function."""

    def synthesize(self, args: list[ast.expr]) -> tuple[ast.expr, Type]:
        ...
        from guppylang.std.quantum import qubit

        # Need to make sure that the quantum module is loaded
        qubit_defn = self.globals[qubit.id]
        assert isinstance(qubit_defn, TypeDef)
        qubit_ty = qubit_defn.check_instantiate([], self.globals)
        # Check against qubit_ty

Also, this only works if the quantum module is loaded. Therefore, it might be a good idea to move state_result into quantum? Or its own debug module that depends on quantum?

Comment on lines 562 to 564
qubits_arr = self.builder.add_op(array_new(ht.Qubit, num_qubits), *qubits_in)
qubits_arr = self.builder.add_op(op, qubits_arr)
qubits_out = unpack_array(self.builder, qubits_arr)
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think this is fine for now. Would be a good idea to add an overload of result_state that takes an array anyways as a follow-up

err.add_sub_diagnostic(TooLongError.Hint(None))
raise GuppyTypeError(err)

tys = [ExprSynthesizer(self.ctx).synthesize(val)[1] for val in args]
Copy link
Collaborator

Choose a reason for hiding this comment

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

You shouldn't ignore the expression returned by synthesize. E.g. instead

Suggested change
tys = [ExprSynthesizer(self.ctx).synthesize(val)[1] for val in args]
args, tys = zip(*(ExprSynthesizer(self.ctx).synthesize(val) for val in args), strict=True)

Copy link
Collaborator

Choose a reason for hiding this comment

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

Also no need to resynthesize the type for args[0] since you already checked it above

Comment on lines 557 to 558
[FuncInput(tys[0], InputFlags.NoFlags)]
+ [FuncInput(t, InputFlags.Inout) for t in tys[1:]],
Copy link
Collaborator

Choose a reason for hiding this comment

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

Then you could just do

Suggested change
[FuncInput(tys[0], InputFlags.NoFlags)]
+ [FuncInput(t, InputFlags.Inout) for t in tys[1:]],
[FuncInput(string_type(), InputFlags.NoFlags)]
+ [FuncInput(t, InputFlags.Inout) for t in tys],

here

Comment on lines 5 to 6
9 | state_result("tag", x)
| ^^^^^^^^^^^^^^^^^^^^^^ Non-qubit inputs to state_result are not supported
Copy link
Collaborator

Choose a reason for hiding this comment

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

Would be nicer if the span only highlighted x

def test_empty(validate):
@compile_quantum_guppy
def main() -> None:
state_result("tag")
Copy link
Collaborator

Choose a reason for hiding this comment

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

Not sure if we should allow this. I could see users writing this with the assumption that the whole state will be outputted because they are not aware that you have to pass the qubits.

Since empty state results are useless anyways, maybe it would be more helpful to add a diagnostic along the lines of

2 |     state_result("tag")
  |                       ^ Qubits whose state should be reported must be passed explicitly

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
wait to merge This PR to be merged after all dependencies are ready
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add result_state function
3 participants