-
Notifications
You must be signed in to change notification settings - Fork 121
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
feature: Gate and Circuit inversion #311
Conversation
Introduces the ability to take adjoints of Gates, Instructions and Circuits. Taking the adjoint is a lazy operation; the actual gates that invert the original gate do not appear until `to_ir` is called.
Codecov Report
@@ Coverage Diff @@
## main #311 +/- ##
=========================================
Coverage 100.00% 100.00%
=========================================
Files 66 66
Lines 4579 4653 +74
Branches 640 645 +5
=========================================
+ Hits 4579 4653 +74
Continue to review full report at Codecov.
|
Co-authored-by: Abe Coull <[email protected]>
def adjoint(self) -> Circuit: | ||
circ = Circuit([instr.adjoint() for instr in reversed(self.instructions)]) | ||
for result_type in self._result_types: | ||
circ.add_result_type(result_type) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I always worry that there's data we're forgetting to copy over. For example, do we need to copy over the parameters?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope, that's included in the gates. Right now, there are only two things you can add to a circuit: instructions and result types (and other circuits, which are made up of these)
src/braket/circuits/gate.py
Outdated
new._adjoint = not self._adjoint | ||
return new | ||
|
||
def adjoint_list(self) -> List[Gate]: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't it weird to have both adjoint and adjoint_list? When would we know which one to use? Why not just always use adjoint_list?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All adjoint
does is mark a gate as an adjoint; it doesn't have anything to do with the actual implementation of adjoints. adjoint_list
, on the other had, is a definition of the adjoint. In principle, adjoint_list
isn't necessary at all; the adjoint implementation could be handled by the device, but we currently have no way of communicating that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we differentiate the names more to make this difference more obvious?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
adjoint_expansion
?
Returns: | ||
List[Gate]: The gates comprising the adjoint of this gate. | ||
""" | ||
raise NotImplementedError(f"Gate {self.name} does not have adjoint implemented") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm confused why this isn't:
return [self.adjoint()]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See comment about difference between adjoint
and adjoint_list
src/braket/circuits/gate.py
Outdated
@@ -49,18 +73,35 @@ def to_ir(self, target: QubitSet) -> Any: | |||
Returns: | |||
IR object of the quantum operator and target | |||
""" | |||
if self._adjoint: | |||
# Use adjoint list of original gate | |||
return [elem.to_ir(target) for elem in self.adjoint().adjoint_list()] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.adjoin().adjoint_list()
doesn't make sense to me from the description of these functions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
adjoint_list
will often contain copies of self
, and we don't want to recursively call to_ir
on self because it'll never end. As well, semantically speaking, we want the list of gates that form the adjoint of the original gate, not the adjoint of the adjoint.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't there a possibility of an infinite loop here regardless? What if this is class A, and adjoint_list returns B.adjoint(), and adjoint_list of that returns A.adjoint()? Can we do some validation in the loop to make sure the returned gates are not self and not adjoint themselves?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the best way to go about this is to throw an exception if _adjoint
is True
for any gate in adjoint_list
, and include in the adjoint_list
doc that the adjoint list must consist only of non-adjoint gates.
src/braket/circuits/gates.py
Outdated
"""Hadamard gate.""" | ||
|
||
def __init__(self): | ||
super().__init__(qubit_count=None, ascii_symbols=["H"]) | ||
|
||
def to_ir(self, target: QubitSet): | ||
def _to_ir(self, target: QubitSet): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we just change all these to private? Is that a breaking change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Won't be breaking because they all inherit from Gate
, where the public to_ir
is defined.
src/braket/circuits/gates.py
Outdated
@@ -39,16 +40,31 @@ | |||
3. Register the class with the `Gate` class via `Gate.register_gate()`. | |||
""" | |||
|
|||
|
|||
class _FiniteOrderGate(Gate, ABC): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing docs.
Why are these new superclasses needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like having these because many gates fall into this category. I think it's much better than pasting
def adjoint_list(self):
return [self]
for all involutory gates, and
def adjoint_list(self):
return [self, self, self] # or more, etc
for other orders. As well, it helps prevent stop a developer from missing or incorrectly implementing these methods for new gates in the same category.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suggest we remove both FiniteOrderGate and HermitianGate. It's probably more confusing for developers to use instead of just defining the adjoint operator themselves.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm fine with removing _finiteOrderGate
, as there aren't too many instances of these yet, and it's less obvious than _HermitianGate
. However, _HermitianGate
s are pretty ubiquitous, and the meaning of the class is pretty clear from its name.
expected = ( | ||
"T : | 0 | 1 |", | ||
" ", | ||
"q0 : -(Rz(3.14))^†-(S)^†-", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rather than two characters it might look nicer if we just use an asterisk instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While an asterisk is standard for adjoints in math, in physics it just means the complex conjugate, which is not what we want. I'm fine with just having the dagger without the ^ though
src/braket/circuits/gate.py
Outdated
"""Tuple[str, ...]: Returns the ascii symbols for the quantum operator.""" | ||
if self._adjoint: | ||
return tuple( | ||
# "C" stands for control, and doesn't need a dagger symbol |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But the ascii symbols are determined by subclasses, it seems like this is information leaking to know what one of the subclass ascii symbols would be.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why can't the subclass just override this method?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I want to avoid having to copy the if
statement for every subclass, as it's easy for a developer to forget to add the branch.
src/braket/circuits/gates.py
Outdated
@@ -79,11 +107,11 @@ def h(target: QubitSetInput) -> Iterable[Instruction]: | |||
Gate.register_gate(H) | |||
|
|||
|
|||
class I(Gate): # noqa: E742, E261 | |||
class I(_FiniteOrderGate): # noqa: E742, E261 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't I
hermitian ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I
has order 1, which implies Hermiticity, but here we do one better by simply not adding a gate at all, as opposed to a single gate. I don't have a problem with making it explicitly Hermitian.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's this kind of discussion that makes me not want to have _HermitianGate in the first place; I don't think it adds too much value and just makes development slower.
@@ -87,6 +88,16 @@ def bind_values(self, **kwargs): | |||
""" | |||
raise NotImplementedError | |||
|
|||
def adjoint(self) -> List[Gate]: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is true for all of our current gates but might not be in the future. Is there a way to catch it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the default behavior, and future gates could just override it.
* feat: add queue position for tasks (#299) * feat: add queue position for tasks * update docstring for queue_position * update docstrings * update package_name * update to enums and dataclass * test: add integ test for task queue_position (#300) * refactor: dataclass and file naming for queue info (#301) * refactor: dataclass and file naming for queue info * apply suggestions * update: task queue position after refactor (#309) * update: task queue position after refactor * add queue_position type hint details, change order of info * feat: queue position for hybrid jobs (#302) * feat: queue position for hybrid jobs * handle message return * add docstring changes * update docstring, and return None * add context in dataclass * update docstrings * indent fix * test: add integ test for jobs queue position (#310) * feat: queue position for hybrid jobs * handle message return * add docstring changes * update docstring, and return None * add context in dataclass * test: add integ test for jobs queue position * remove comment * minor fix * remove unnecessary branching * fix docstring merge * remove dataclass redefinition after merge * feat: queue depth for devices (#306) * feat: queue depth for devices (dataclass version) * add unit-test for queue depth * modify docstrings, remove helper funcs * add more info to docstrings * minor test edit * docstrings * indent * update QueuePriority to QueueType * test: add integ test for queue_depth (#311) * refactor: job and quantum_task keywords for queue_depth (#312) * refactor: job and quantum_task keywords for queue_depth * Update src/braket/aws/queue_information.py Co-authored-by: Kshitij Chhabra <[email protected]> --------- Co-authored-by: Kshitij Chhabra <[email protected]> * deps: update boto3 version for queue visibility (#319) * sync: public-main changes into feature/queue_visibility (#320) * feat: add Aria2 enum (#653) Co-authored-by: Viraj Chaudhari <[email protected]> Co-authored-by: Cody Wang <[email protected]> * infra: bump actions/checkout from 3.6.0 to 4.0.0 (#696) Bumps [actions/checkout](https://github.com/actions/checkout) from 3.6.0 to 4.0.0. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](actions/checkout@f43a0e5...3df4ab1) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * prepare release v1.55.0 * update development version to v1.55.1.dev0 * Revert "update: restricting parameter names to not collide with ones we use for OpenQASM generation. (#675)" (#701) This reverts commit b158736. * infra: update codeowner file to amazon-braket/braket-dev (#699) Co-authored-by: Abe Coull <[email protected]> * doc: Replace aws org with amazon-braket (#705) * prepare release v1.55.1 * update development version to v1.55.2.dev0 * doc: change the sphinx requirement to be greater than 7.0.0 (#704) Co-authored-by: Cody Wang <[email protected]> * doc: add code contributors to the readme (#703) Co-authored-by: Cody Wang <[email protected]> * doc: Remove trailing backquotes (#706) * infra: update the pre-commit hook with linters (#678) * infra: update the pre-commit hook with linters and secrets check Co-authored-by: Abe Coull <[email protected]> Co-authored-by: Cody Wang <[email protected]> * prepare release v1.55.1.post0 * update development version to v1.55.2.dev0 --------- Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: ashlhans <[email protected]> Co-authored-by: Cody Wang <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: ci <ci> Co-authored-by: Milan <[email protected]> Co-authored-by: Angela Guo <[email protected]> Co-authored-by: Abe Coull <[email protected]> Co-authored-by: Abe Coull <[email protected]> * Revert "sync: public-main changes into feature/queue_visibility (#320)" This reverts commit be6460c. * update github script * minor fix --------- Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: Kshitij Chhabra <[email protected]> Co-authored-by: ashlhans <[email protected]> Co-authored-by: Cody Wang <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Milan <[email protected]> Co-authored-by: Angela Guo <[email protected]> Co-authored-by: Abe Coull <[email protected]> Co-authored-by: Abe Coull <[email protected]>
Introduces the ability to take adjoints of Gates, Instructions and Circuits.
Issue #, if available:
Description of changes:
Testing done:
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
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.