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: build autoqasm program directly from main decorator #804

Merged
merged 50 commits into from
Dec 4, 2023

Conversation

ajberdy
Copy link
Contributor

@ajberdy ajberdy commented Nov 17, 2023

Issue #, if available:

Fixes #805, fixes #806, fixes #813

Description of changes:

  • Update aq.main to build AutoQASM program directly.
  • Add support for typed input parameters and auto-capture of inputs.
    • usable in qubit indices
  • Various typing simplifications

Wrapped testing programs in fixtures so they don't get built during other tests.

Testing done:

Unit tests updated and passing, notebook tests passing as well.

Merge Checklist

Put an x in the boxes that apply. You can also fill these out after creating the PR. If you're unsure about any of them, don't hesitate to ask. We're here to help! This is simply a reminder of what we are going to look for before merging your pull request.

General

Tests

  • I have added tests that prove my fix is effective or that my feature works (if appropriate)
  • I have checked that my tests are not configured for a specific region or account (if appropriate)

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

@ajberdy ajberdy requested a review from a team as a code owner November 17, 2023 01:26
Base automatically changed from remove-args to feature/autoqasm November 17, 2023 22:18
@ajberdy
Copy link
Contributor Author

ajberdy commented Nov 17, 2023

Two tests failing in test_api: test_nested_function and test_main_from_main

Copy link
Contributor

@laurencap laurencap left a comment

Choose a reason for hiding this comment

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

Reviewed test_api.py


assert main(4).to_ir() == expected
assert main.make_bound_program(param_values={"n": 4}).to_ir() == expected
Copy link
Contributor

Choose a reason for hiding this comment

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

Since these weren't free parameters before, would you mind updating most of the tests to have the meta-programming style like you showed in your doc, with the outer builder function?

That way we can see what the new syntax looks like. I don't think make_bound_program is the recommended way for customers to pass these meta programming inputs

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah I see. In my head, I interpreted these tests as testing the user story "customer wants to define this program and then build the program with a specific value for its input". I agree make_bound_program is a bit clunky today for this, but we can add easier methods for Program -> Program maps that bind variables, either by simply replacing the input declaration with an assignment, or fully inlining as an optimization pass.

If instead these tests were meant to test the codepath "non-input python variables are used during the transpilation step", then I can see how your suggestion is more relevant. It's not obvious to me which is the case, maybe we can chat so I can better understand here

Copy link
Contributor

Choose a reason for hiding this comment

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

From sync review: We will update with the second interpretation

Copy link
Contributor

Choose a reason for hiding this comment

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

Just a note, please also run tox -e notebooks to test the service-dependent example notebooks that currently don't run in CI.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, thanks for pointing this out! Just confirmed that it passes with the latest updates; I'll note this in the PR description

Copy link

codecov bot commented Nov 28, 2023

Codecov Report

All modified and coverable lines are covered by tests ✅

Comparison is base (8cdf8a4) 100.00% compared to head (4406d4c) 100.00%.
Report is 7 commits behind head on feature/autoqasm.

Additional details and impacted files
@@                Coverage Diff                 @@
##           feature/autoqasm      amazon-braket/amazon-braket-sdk-python#804   +/-   ##
==================================================
  Coverage            100.00%   100.00%           
==================================================
  Files                   166       166           
  Lines                  9895      9921   +26     
  Branches               2102      2107    +5     
==================================================
+ Hits                   9895      9921   +26     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@laurencap laurencap self-assigned this Nov 29, 2023
expected = """OPENQASM 3.0;
qubit[4] __qubits__;
expected_no_arg_partial = """OPENQASM 3.0;
qubit[2] __qubits__;
Copy link
Contributor

Choose a reason for hiding this comment

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

The qubit length needs to be max qubit index + 1

Suggested change
qubit[2] __qubits__;
qubit[4] __qubits__;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Do we have any plans to validate this? This scenario seems like an example of when we have sufficient information to assert this

h __qubits__[1];
cnot __qubits__[1], __qubits__[3];"""

bell_partial = aq.main(functools.partial(bell, 1))
assert bell_partial(3).to_ir() == expected
bell_partial = aq.main(num_qubits=2)(functools.partial(bell, 1))
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we have to supply num_qubits on this input now? Maybe it's a rare enough use case that we don't have to worry about it. @rmshaffer, thoughts?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Here we need num_qubits because the program is built with variable qubit indices in use. For the case below, where we pass in a partial fully binding the qubit args, num_qubits can be omitted. This seems correct to me.

It's worth noting that this function previously always built the Program with all variables bound, even though the aq.main call was on varying partials. Now that building happens during the decorator call, incomplete partials will have free parameters.

We could test partials on a meta-programming approach that would more closely match the end result of the previous test, but I think this approach is more fitting, as the essence of this test was asserting the behavior of decorating partial functions.

Copy link
Contributor

Choose a reason for hiding this comment

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

Agreed, this new behavior seems reasonable and the test makes sense to me.


a = MyClass()
assert aq.main(a.bell)(1, 3).to_ir() == expected
assert a.bell_decorated(1, 3).to_ir() == expected
assert aq.main(num_qubits=2)(a.rx).to_ir() == expected
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't rx be called with q0?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This line passes the function a.rx to the decorator; the function isn't actually called at all. You can see from the expected ir that q0 is captured from the signature as an input. Regardless, I will delete this test as mentioned above

bell_noarg_partial = functools.partial(bell_decorated, 1, 3)
assert bell_noarg_partial().to_ir() == expected
bell_noarg_partial = aq.main(num_qubits=2)(functools.partial(bell, 1, 3))
assert bell_noarg_partial.to_ir() == expected_no_arg_partial


def test_classmethod() -> None:
Copy link
Contributor

Choose a reason for hiding this comment

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

@rmshaffer @yitchen-tim is this important functionality? It seems like we lose this feature, of AutoQASM instance and class methods, if we go this route

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Here is some context from Ryan's comment on this test:

I believe I added these class method tests primarily for getting to 100% code coverage in transpiler, and since it was pretty easy to make it work in the old model.

I think it's legitimate to say that this no longer makes sense in the world where the program conversion happens at function declaration time, and we don't need to support this.

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think it's important. A user can still easily make a class method which wraps an @aq.main function and returns the Program (which is essentially what it previously meant to mark an instance/class method as @aq.main).

Copy link
Contributor

@laurencap laurencap left a comment

Choose a reason for hiding this comment

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

Reviewed through notebook 4

@rmshaffer
Copy link
Contributor

rmshaffer commented Nov 30, 2023

@ajberdy what's the current status of this PR... seems like it's getting close? If you could please just ping @laurencap and me when all of the TODOs/comments have been addressed (or have issues opened) and you are ready for a final PR review pass.

@ajberdy
Copy link
Contributor Author

ajberdy commented Nov 30, 2023

@ajberdy what's the current status of this PR... seems like it's getting close? If you could please just ping @laurencap and me when all of the TODOs/comments have been addressed (or have issues opened) and you are ready for a final PR review pass.

@rmshaffer Yes, looks like it's getting close! There are 3 TODOs on this PR, all tagged with issues and to be fixed in later PRs (#809, amazon-braket/autoqasm#14, amazon-braket/autoqasm#7). I believe @laurencap has a couple example notebooks left to review and possibly some remaining test files.

I've responded and acted on all the comments, so there aren't currently any action items for the PR on my end. Once all the files are reviewed, I'd say we're good for a final pass.

Copy link
Contributor

@rmshaffer rmshaffer left a comment

Choose a reason for hiding this comment

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

A few very minor comments. Overall looks good to me, approving from my end but also deferring to @laurencap after she has a chance to re-review.

@@ -69,8 +69,12 @@
"metadata": {},
"outputs": [],
"source": [
"# TODO: determine if this example should make these variables inputs to the program\n",
Copy link
Contributor

Choose a reason for hiding this comment

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

Reminder to remove this TODO before merging. IMO, keeping these as Python variables is fine. (Actually I'm not sure how you'd make qubit an input given how it's used in a format string.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Technically, should work with qubit as an int. But agree that's not as straightforward as using it directly

Comment on lines 68 to 69
Program | partial: A callable
which returns the converted quantum program when called.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
Program | partial: A callable
which returns the converted quantum program when called.
Program | partial: The Program object containing the converted quantum program, or a partial function
which returns the converted quantum program when called.

Copy link
Contributor Author

@ajberdy ajberdy Nov 30, 2023

Choose a reason for hiding this comment

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

Good catch! Still a bit reminiscent of the old interface, so I've updated this doc string to

Program | partial: The Program object containing the converted quantum program, or a
partial function of the `main` decorator.

(Technically the partial function returns the Program when called on the decorated function, but the language seems to imply an interface like we previously had)

Comment on lines +29 to +31
def _test_on_local_sim(program: aq.Program, inputs=None) -> None:
device = LocalSimulator(backend=StateVectorSimulator())
task = device.run(program, shots=10)
task = device.run(program, shots=10, inputs=inputs or {})
Copy link
Contributor

Choose a reason for hiding this comment

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

[nit] any reason to not just have inputs={} as the default value?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, Python behaves weirdly when you put a mutable object as a default in a function signature (if you mutate the object, it mutates the default for the function). (source)

Copy link
Contributor

Choose a reason for hiding this comment

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

I had no idea.

test/unit_tests/braket/experimental/autoqasm/test_api.py Outdated Show resolved Hide resolved
test/unit_tests/braket/experimental/autoqasm/test_api.py Outdated Show resolved Hide resolved
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
3 participants