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

Describe extension to gate functionality #346

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

Conversation

ndebeaudrap
Copy link

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 a gate body may consist of.

  • This allows the classical parameters of a gate to have different types (the 'default' type of a gate is considered retrospectively to correspond to an angle).*
  • It also allows the quantum operands of a 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 type angle, 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 of gate subroutines which accept float parameters instead, for those cases where this would be desirable.)

The gate body is allowed to contain program logic in the form of for loops and if statements, which may involve the classical gate 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 as def subroutines, albeit with the limitation that locally defined identifiers (and classical parameters) may not be re-assigned values after they are initialised.

@CLAassistant
Copy link

CLAassistant commented Apr 21, 2022

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution.
2 out of 3 committers have signed the CLA.

✅ hodgestar
✅ ndebeaudrap
❌ Jonathan Robert Niel de Beaudrap


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.

@jwoehr
Copy link
Collaborator

jwoehr commented Apr 21, 2022

@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?!

@ndebeaudrap ndebeaudrap changed the title Describe extension to gate functionality, as per Issue #323 Describe extension to gate functionality Apr 21, 2022
@ndebeaudrap
Copy link
Author

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.

@ndebeaudrap
Copy link
Author

(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.)

@jwoehr
Copy link
Collaborator

jwoehr commented Apr 21, 2022

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

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
Copy link
Contributor

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.

Copy link
Author

Choose a reason for hiding this comment

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

Noted and amended

.. code-block:: c

// this is ok:
gate g (angle alpha, int k): qubit[3] a
Copy link
Contributor

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

Copy link
Author

Choose a reason for hiding this comment

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

Noted and amended

* 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
Copy link
Contributor

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;
   } 

Copy link
Contributor

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)

Copy link
Contributor

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 gates having a 1-1 correspondence with unitaries.

Copy link
Contributor

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.

Copy link
Author

Choose a reason for hiding this comment

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

Fair point; amended.

@@ -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``.
Copy link
Contributor

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

Copy link
Contributor

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)
@blakejohnson blakejohnson added the TSC Technical Steering Committee label May 20, 2022
@blakejohnson blakejohnson added this to the 3.0 milestone May 20, 2022
blakejohnson
blakejohnson previously approved these changes Jun 3, 2022
Copy link
Contributor

@taalexander taalexander left a 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.
@levbishop
Copy link
Contributor

levbishop commented Jun 14, 2022

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 gate keyword. Example:

# 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 g1 and g2 and rewrite g1(phi, lam) as g2(3*phi/5, 5*lam/3) which can then match with the defcal and the program can execute. The calculation of 3*phi/5 will be done at angle[12] precision which is what the author of the user code asked for and presumably they have determined this is sufficient precision for their algorithm, so that seems fine (maybe1). When it goes into the defcal that result is cast to angle[13] because that is the best the hardware can support, which also seems fine and unavoidable.

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:

  • Does the compiler search for an angle[8] x such that the rounded x*5 is equal to angle[8] y = angle[8](angle[12](pi/7))*3, and then cast back to angle[12]? What if there is no such angle[8] x?
  • Does the compiler search for an angle[8] x such that the rounded x/5 is equal to angle[8] y = angle[8](angle[12](pi/7))/3, and then cast back to angle[12]? What if there is more than one such angle[8] x? (This one seems less of a problem - since we already have multiple ways of implementing the same unitary, eg trivially U(x, y, z) has equivalences mod 2pi, and all of U(0, x, -x) are identity: we trust the compiler to choose something sensible for these cases)
  • Does the compiler just calculate 3*phi/5 in angle[12] precision like in the arbitrary-precision gate case? If so, then what is the purpose of the type annotation in the gate definition?
  • On what basis is the precision angle[8] (or any other precision in gate definitions) to be chosen? It would be nice to write gate in a generic way that's independent of both the user algorithm and the hardware implementation. For example, I think we'd like to give a gate crz(theta) c, t in hardware/algortihm independent way as in stdgates.inc.

Footnotes

  1. hmm. maybe assuming the precision of the user variable is appropriate for gate-rewriting is not ideal. What if the user algorithm is doing something like 4 rounds of iterative phase estimation and thus defines the phase as angle [4] phase_est and does the bit twiddling on the 4 bits of the angle to make that work. If the gates involving phase_est need to be rewritten for the hardware, the user probably still wants that rewriting done at higher accuracy than angle[4]. Maybe it makes more sense to do all of the rewriting at the precision of the defcal, angle[13] in the example, since that is the ultimate precision that goes to the hardware anyway? (if so, what if the hardware has different precision for phases and amplitudes...?)

@jwoehr
Copy link
Collaborator

jwoehr commented Jun 14, 2022

From a process point of view, it might be a good to aim for explicit syntax that won't be invalidated by later simplifying assumptions. Can it be the case for 3.0 that angle precision must, for now, either match perfectly or go unstated so as to let the compiler decide on the basis of the applicable defcal? 6 months of writing library code constrained thusly should reveal the correct path for the future.

@ndebeaudrap
Copy link
Author

@levbishop : I empathise with this priority, but it strikes me that this ties in much more generally with the type system of OpenQASM. If an 'infinite precision' angle type did exist, this would be a sensible type for a gate parameter. At the moment, as a result of discussions in the Types and Casting WG, we currently don't contemplate the possibility of having infinite/indefinite precision types in the type system (nor the more modest aim of having symbolic expressions for angle values which are rational multiples of pi).

The question then is, what type the parameters of the gate type are considered (retroactively) to have, and whether this can preserve backwards compatibility. I am open to suggestion, but this almost necessarily involves significant discussion with the Types and Casting WG. One approach that we might take is that suggested above by @jakelishman, but I wonder if this might not lead to infinite precision types (or symbolic angle expressions) through the back-door, if the precision of an angle type is allowed to be resolved according to the demands of context.

@jakelishman
Copy link
Contributor

Just for reference for everyone: I asked Lev to write out some examples of his concerns ahead of the types-and-casting group meeting. I'm planning to make his comment here the agenda for this week's meeting.

@blakejohnson blakejohnson modified the milestones: 3.0, 3.1 Jun 17, 2022
@jwoehr jwoehr modified the milestone: 3.1 Jun 17, 2022
@levbishop
Copy link
Contributor

Pulled the parameter typing question out into a separate issue #375.
General consensus from discussion at the last TSC meeting was that the remainder of this PR is in good shape and can. be ready to merge after we have some decision on how to proceed on #375.

@ndebeaudrap
Copy link
Author

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?

@bettinaheim
Copy link

Looks good to me. I think this is just waiting for the updates from Lev, and then this should tie in nicely.

@blakejohnson
Copy link
Contributor

I assume this will need to be rebased after #393 is merged, but I agree that this seems fine to proceed following that change.

@blakejohnson
Copy link
Contributor

#393 is finally merged. Can we refactor this change set accordingly?

@blakejohnson
Copy link
Contributor

@ndebeaudrap are you available to re-factor these changes on top of gates.rst as it sits now?

@jlapeyre jlapeyre added the enhance/change spec Semantic changes to language, not clarification label Jan 24, 2024
@hodgestar
Copy link
Contributor

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.

@blakejohnson
Copy link
Contributor

Thank you @hodgestar for doing that work. I will add re-reading this to my TODO list.

.. code-block:: c
gate QFT256 : qubit[8] q {
uint n = 8;
for uini j in [0 : n-1] {
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
for uini j in [0 : n-1] {
for uint j in [0 : n-1] {

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.
Copy link
Contributor

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.

Copy link
Contributor

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 gates 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 gates to not always be associated with unitaries?

Copy link

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.

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,
Copy link
Contributor

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.

Copy link
Contributor

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.

Copy link
Contributor

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.

Comment on lines +242 to +245
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.
Copy link
Contributor

Choose a reason for hiding this comment

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

This change looks fine.

Comment on lines +253 to +255
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
Copy link
Contributor

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
Copy link
Contributor

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
Copy link
Contributor

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 ...)"?

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
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
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
itself.) External functions, ``reset`` operations, ``measure`` operations (or other operations with potentially
itself.) ``extern`` functions, ``reset`` operations, ``measure`` operations (or other operations with potentially

Probably better to say extern rather than "External".

and the variable parameters ``params`` must have the appropriate type (or be expressions which can be implicitly
cast to the appropriate type).

The quantum operands of a ``gate`` invocation must have the appropriate types, to the declaration of the ``gate``.
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
The quantum operands of a ``gate`` invocation must have the appropriate types, to the declaration of the ``gate``.
The quantum operands of a ``gate`` invocation must have the appropriate types to the declaration of the ``gate``.

Comment on lines +377 to +418
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
Copy link
Contributor

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.

@blakejohnson
Copy link
Contributor

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 gates and unitary matrices. If so, we either need to accept that, or figure out other changes to the proposal.

@braised-babbage
Copy link
Contributor

braised-babbage commented Jun 18, 2024

Following up on our discussion of this at the last TSC, there are a few conceptual pieces here.

  1. The introduction of a typed parameter list for parametric gates (allowing classical parameters beyond angles).
  2. The introduction of a typed quantum argument list for gates (allowing more compact and sophisticated notion of gate applications involving qubit registers).
  3. An enrichment of what is allowed in the gate body (namely, if and for statements which involve only only const or gate parameter values in a way that can be resolved at compile time).

To a degree, these items can be considered separately.

For example, item (1) above can be useful when considering the interaction between OpenQASM's gate and defcal constructs. Whereas a gate formally represents a unitary operation, a defcal provides an operational description of how to perform such an operation. At present, a defcal cannot be parameterized by meaningful quantities (e.g. DAC scale factors, IQ classification thresholds, etc) that may otherwise be manipulated by a low-level OpenPulse program because these quantities are not of angle type. Without such parametrization, calibration definitions necessarily rely on a certain amount of global state. For this reason I find (1) to be of some interest, even if we do not expect that the corresponding gate definitions to have any mathematical dependence on these classical quantities.

While there is an interaction between (2) and (3), (e.g. if we have a qubit register we can loop over, this motivates supporting for statements in a gate body), we can still consider them separately. Fundamentally (3) is of interest to people defining gates, whereas (2) is also of interest to people invoking gates. Specifically considering (2), the current broadcasting mechanism can allow for compact expression e.g. of transversal operations, but is still relatively transparent: when you read a gate application, you can tell from the arguments what is going on. When we allow (2), we lose this property -- a reader of the program is ultimately going to have to dig in to the gate definition to make sense of it. This is the price of the added flexibility.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhance/change spec Semantic changes to language, not clarification TSC Technical Steering Committee
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet