-
Notifications
You must be signed in to change notification settings - Fork 314
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
Describe extension to gate
functionality
#346
base: main
Are you sure you want to change the base?
Conversation
Jonathan Robert Niel de Beaudrap seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account. You have signed the CLA already but the status is still pending? Let us recheck it. |
@ndebeaudrap , there seems to be some automation fu blocking you signing the CLA ... can you please click the " add the email address used for this commit to your account " link and straighten out the angry machine?! |
gate
functionality, as per Issue #323gate
functionality
It appears to me, novice Git user that I am, that I did not configure my email properly on my local system. I do not believe that the email message in the commit is one that I would be able to 'verify', and that this is the root of the problem. I am attempting a fix, having configured my local system better. If this does not work, I would appreciate advice on the best way forward in producing a better formed PR along these lines, given the current state of it. |
(If — as I suspect — it would be expedient to simply remove this branch, please do so; I will try again once that has been done.) |
Looks like it's fixed. |
source/language/gates.rst
Outdated
transformation by a sequence of built-in gates. For example, a CPHASE | ||
operation is shown schematically in :numref:`fig_gate` | ||
corresponding OpenQASM code is | ||
Programmers may define new gates, which may be resolved to a sequence of built-in |
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.
Nit: which can be resolved
- it's required not optional that resolution exists to a sequence of built-in gates.
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.
Noted and amended
source/language/gates.rst
Outdated
.. code-block:: c | ||
|
||
// this is ok: | ||
gate g (angle alpha, int k): qubit[3] a |
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.
Nit: matching style from previous examples, there's no space after the gate name
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.
Noted and amended
source/language/gates.rst
Outdated
* Program logic (``if`` statements and ``for`` loops) with conditions/bounds involving constants, | ||
and simple expressions depending on the gate arguments and local identifiers / loop iterators; | ||
|
||
* Timing directives; and |
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.
Hm, this breaks the design principle we had discussed where a compiler could choose to represent gates as unitary matrices in its implementation. To such a compiler, the gates:
gate g : qubit a
{
U(0, 0, 0) a;
}
would be the same as
gate g : qubit a
{
delay[20ns] a;
U(0, 0, 0) a;
}
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.
That doesn't change the unitary meaning of the gate, though, does it? (assuming delay
is equivalent to an identity matrix)
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 guess the alternatives would connect delay
to some kind of drift operator (e.g. a static coupling term) or a Kraus operator representing amplitude dampling and/or dephasing. But, all of these break out of the model of gate
s having a 1-1 correspondence with unitaries.
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.
The point is that they look the same in one representation but they look different in another representation. Meaning a compiler storing gates as unitaries would think the gates interchangeable even though they aren't.
On the other hand that could already be accomplished today by adding an identity gate to a gate definition.
When a programmer puts a delay inside a gate definition, I'd guess that they really want a delay and would be unhappy if the compiler were to come up with a different implementation of the 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.
Fair point; amended.
source/language/gates.rst
Outdated
@@ -214,11 +216,12 @@ In general, new gates are defined by statements of the form | |||
|
|||
where the optional parameter list ``params`` is a comma-separated list of variable | |||
parameters, and the argument list ``qargs`` is a comma-separated list of qubit | |||
arguments. The parameters are identifiers with arbitrary-precision numeric types. | |||
arguments. The parameters are identifiers with the type ``angle``. |
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.
^^ N.B. to other reviewers
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 believe this is consistent with what we discussed at the TSC meeting.
Made amendments regarding timing, gate resolution, and to refine examples (addressing feedback up to 5 May @ 21:30 BST)
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 have no major objections to this, other than raising the question of if this should be required for version 1.0?
Also: a minor improvement in wording regarding classical storage types in gates.
…/openqasm into circuit-families-generics
My concern with this is that by changing from treating the parameters as exact angles to considering them as inexact types, then we can no longer represent the mathematical equivalence between parameterized gates, which is the intention of the # from some common gate library:
gate g1(a, b) q {
U(a*5, b/5, 0) q;
}
gate g2(a, b) q {
U(a*3, b/3, 0) q;
}
# from hardware definition:
defcal g2(angle[13] a, angle[13] b) $0 {...}
# from user code:
angle[12] phi = pi/7;
angle[12] lam = pi/7;
g1(phi, lam) $0; We'd like a compiler to be able to see this and infer the equivalence between If instead the include files have: gate g1(angle[8] a, angle[8] b) q {
U(a*5, b/5, 0) q;
}
gate g2(angle[8] a, angle[8] b) q {
U(a*3, b/3, 0) q;
} then the equivalence is unclear:
Footnotes
|
I believe, subject to the resolution of #375 (and in particular the presumption that OpenQASM2-style gate declarations take angles as parameters), this PR is ready to be merged. Do we concur? |
Looks good to me. I think this is just waiting for the updates from Lev, and then this should tie in nicely. |
I assume this will need to be rebased after #393 is merged, but I agree that this seems fine to proceed following that change. |
#393 is finally merged. Can we refactor this change set accordingly? |
@ndebeaudrap are you available to re-factor these changes on top of gates.rst as it sits now? |
I've merged main into this branch and resolved conflicts. The text likely needs a final round of clean ups and reviews. I'm happy to do the initial round of clean ups but reviews and comments welcomed. |
Thank you @hodgestar for doing that work. I will add re-reading this to my TODO list. |
the scope of any single iteration of the ``for`` loop, and can only be modified by the logic of the loop | ||
itself.) External functions, ``reset`` operations, ``measure`` operations (or other operations with potentially | ||
random outcomes), cannot be involved in the program logic of a ``gate`` body. This ensures that an invocation of | ||
a user-defined ``gate``, corresponds to a definite finite sequence of built-in unitary gates. |
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.
A common justification for distinguishing gates and subroutines is that a gate is a kind of mathematical specification that can be manipulated by a compiler. For a gate definition with no arguments this is a matrix with constant entries. For a gate definition with angle arguments the matrix entries may be symbolic expressions (already suggesting that a compiler may need to support symbolic algebra of some sort). When we further enrich the gate definition language with if
statements and for
loops which may involve gate arguments, the relationship between a gate definition and the corresponding matrix may be highly nontrivial.
It seems like this extension would require that a compiler handle certain gate applications by instantiating them as sub-circuits (i.e. substituting in arguments and performing compile-time evaluation of if
statements and for
loops, replacing the application with the result of this). At that point, why restrict ourselves to unitary operations? Many interesting sub-circuits involve measurements. OpenQASM subroutines do not have restrictions on whether or not expressions in them can be evaluated at compile time (assuming const
arguments), which is part of the reason for this proposal.
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 intent here was still to be able to associate generalized gate
s with unitary matrices, but allowing convenient short hands. I suppose we have failed to do that, though, because it is trivial to construct examples where this is no longer true, e.g.:
gate classically_controlled_not(bit b): qubit q {
if (b) { x q; }
}
I'm not sure if there are additional constraints we could introduce to restore this property? Or do we just give up on trying to keep it and allow gate
s to not always be associated with unitaries?
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.
Just weighing in here since this popped up in my inbox --- I don't see the example above as disassociating gates from unitary matrices. Rather, a gate is a family of unitary matrices defined by the values of all classical parameters. In this sense it's morally no different (I believe) from the interpretation of openQASM 2.0 style gates, it's just allowing other types of parameters and a different type of computation if the compiler actually intended on generating the unitary given a set of parameters. This proposal was a long time ago however so my memory of what was proposed and the issues may be a bit fuzzy.
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 see your point, @meamy. The thing which is changed is that I no longer have a mathematical structure for the associated unitary which is fixed other than for substitution of a variable into some algebraic expressions. But, my example is definitely still unitary for any value of b
.
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.
Indeed, you could say it takes it from a matrix with symbolic expressions inside
to a symbolic expression of a matrix
. The question comes down to which is more useful in practice, and how that balances against the perspective of the programmer (if openQASM is intended to be a programming language rather than an IR) and other considerations like broadcasting.
The ``gate`` statement also allows defining parameterized families of unitaries. For example, a CPHASE | ||
operation is shown schematically in :numref:`fig_gate` | ||
and the corresponding OpenQASM code is | ||
Controlled gates can be constructed by adding a control modifier to an existing gate. For example, |
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.
Prior to this change set, the discussion of controlled gates sat under a "quantum gate modifiers" section. I'd suggest preserving that organizational structure.
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.
Actually, this entire section of looks like it refers to a stale version of the "quantum gate modifiers" section. I believe this change should be reverted.
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.
Agreed. For example, the broadcasting discussion below is already discussed adequately elsewhere https://openqasm.com/language/gates.html#broadcasting.
New gates are defined from previously defined gates. | ||
The gates are applied using the statement ``name(params) qargs;`` just like the built-in gates. | ||
The parentheses are optional if there are no parameters. The gate :math:`{cphase}(\theta)` | ||
corresponds to the unitary matrix :math:`{diag}(1,1,1,e^{i\theta})` up to a global phase. |
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 change looks fine.
In general, new gates may be declared in two different ways: a 'short' | ||
declaration syntax, and a more versatile 'general' declaration syntax. | ||
'Short' gate declarations are statements of the form |
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 looks like the start of the changes we want to keep.
|
||
An empty body corresponds to the identity gate. | ||
// comment |
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.
The // comment
isn't part of the gate declaration, no?
} | ||
|
||
Classical storage types and parameters in a ``gate`` body are treated as being immutable, | ||
and cannot be assigned to more than once. (For loop induction variables are treated as being constant within |
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 struggled to scan "for loop" as a compound noun, and so was confused by the parenthetical statement. Would it be clearer to just say "(Loop induction variables ...)"?
For example, using a 'short' ``gate`` declaration (all of whose operands are individual qubits): | ||
the quantum circuit given by | ||
|
||
.. code-block:: c | ||
|
||
gate g qb0, qb1, qb2, qb3 | ||
{ | ||
// body | ||
} | ||
qubit qr0[1]; | ||
qubit qr1[2]; | ||
qubit qr2[3]; | ||
qubit qr3[2]; | ||
g qr0[0], qr1, qr2[0], qr3; // ok | ||
g qr0[0], qr2, qr1[0], qr3; // error! qr2 and qr3 differ in size | ||
|
||
has a second-to-last line that means | ||
|
||
.. code-block:: c | ||
|
||
// FIXME: insert translation of algorithmic block from TeX source. | ||
|
||
for j in [0:1] do | ||
g qr0[0],qr1[j],qr2[0],qr3[j]; | ||
|
||
We provide this so that user-defined gates can be applied in parallel | ||
like the built-in gates. This functionality extends also to any ``gate`` declared with the | ||
'general' form, so long as all operands which are given an explicit size in the declaration, | ||
are provided with arguments of the corresponding type. | ||
|
||
.. code-block:: c | ||
|
||
gate g : qubit qb0, qubit qb1, qubit[5] qreg | ||
{ | ||
// body | ||
} | ||
qubit a[2]; | ||
qubit b[2]; | ||
qubit c[5]; | ||
qubit d[10]; | ||
g a, b, c; // ok: performs "g a[0], b[0], c; g a[1], b[1], c;" | ||
g a, b, d; // error! third operand expects a register of size 5, not 10 |
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 entire section can be replaced with a reference to the broadcasting section above.
Having refreshed my memory on this topic by re-reading the PR, I am still broadly in favor of this change. However, I think @braised-babbage put his finger on the critical issue of whether this change breaks the correspondence between |
Following up on our discussion of this at the last TSC, there are a few conceptual pieces here.
To a degree, these items can be considered separately. For example, item (1) above can be useful when considering the interaction between OpenQASM's While there is an interaction between (2) and (3), (e.g. if we have a qubit register we can loop over, this motivates supporting |
* declaration and initialisation of classical variables (but not re-assignment to them); | ||
|
||
* program logic (``if`` statements and ``for`` loops) with conditions/bounds involving constants, | ||
and simple expressions depending on the gate arguments and local identifiers / loop iterators; |
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 we need to be more precise about what a "simple expression" is.
Co-authored-by: Erik Davis <ejdavis@amazon.com> Co-authored-by: Blake Johnson <blake.johnson@ibm.com>
Following up on today's TSC meeting (8/7/24), I created an issue #547 representing a kind of "item 0" for #346 (comment). |
Does not copy over generalizations of gate body syntax from #346. Co-authored-by: Jonathan Robert Niel de Beaudrap <niel.debeaudrap@gmail.com>
Does not copy over generalizations of gate body syntax from #346. Co-authored-by: Jonathan Robert Niel de Beaudrap <niel.debeaudrap@gmail.com>
Summary
This PR changes the open specification, consistent with the proposals in (the latter half of) Issue #323. This is submitted following discussions in the Circuit Families and Generics WG and in the TSC meetings, and collaboration with the Types and Casting WG.
Details and comments
We introduce a supplemental, more versatile syntax for defining
gate
subroutines, and a more versatile specification of what agate
body may consist of.gate
to have different types (the 'default' type of agate
is considered retrospectively to correspond to anangle
).*gate
to consist of qubit registers of a specific size (which are not considered to be subject to broadcasting).To do this, the additional declaration syntax is expanded to permit the programmer to explicitly specify the types of all of the parameters and operands. Example gate invocations are provided to demonstrate how such gates may be invoked, and under what conditions the invocations are considered to be well-typed.
(* That the parameters of
gate
declarations are of typeangle
, is a tentative position reached by the Types and Casting WG. Discussion of this point should involve other members of that WG. Note that the more versatile declaration syntax described in this PR, allows direct support ofgate
subroutines which acceptfloat
parameters instead, for those cases where this would be desirable.)The
gate
body is allowed to contain program logic in the form offor
loops andif
statements, which may involve the classicalgate
parameters (or expressions which use those parameters), so long as these can be determined at compile-time. It is also allowed to involve declaration of classical storage types, subject to the same constrants asdef
subroutines, albeit with the limitation that locally defined identifiers (and classical parameters) may not be re-assigned values after they are initialised.