Add support for parameter substitution
Pre-Request Checklist
- [x] I am running the latest versions of pyQuil and the Forest SDK
- [x] I checked to make sure that this feature has not already been requested
Feature Description
I would like to be able to substitute some or all memory parameters into a parameterized program, returning a program that has evaluated to the fullest extent any expressions involving those parameters.
e.g. RX(1 + 2 * theta) with { "theta": 0.1 } becomes RX(1.2)
This appears not to be supported in the pyquil API, though this type of substitution is done in the back prior to running on QPU.
Proposed Solution
There is a substitute method in pyquil, whichat first glance looks built for this purpose:
https://github.com/rigetti/pyquil/blob/master/pyquil/quilatom.py#L384
The first argument it typed to take any expression.
In the pyquil realization of Quil, a Parameter is an Expression, and a MemoryReference is an Expression, but they are otherwise separate. A parameter is denoted %something in text and is used in (at least) the DEFCAL and DEFGATE structures. In the Quil arXiv paper these are referred to as "formal parameters" and this makes sense from a language perspective.
A memory reference is denoted theta or theta[5] in text and is part of an instruction that refers to classical memory; which is always an array though the index can be omitted if it is length 1 and implies the zero index.
With this in mind, the only code path I see to substitute() is through expand_calibrations() (which calls fill_placeholders()). It seems to me in this use case we have a user-defined match-set of specific parameter values for a gate, and these are being filled to effectively return an instantiated calibration (eg. for some particular angle, and so resolving any control expressions such as shift-frequency that apply).
I do not think this code path should encounter a classical memory reference, and this is suggested also by the type of the second argument, which is a parameter to value map.
I therefore recommend expanding the map type to allow memory references, and providing the following implementation of the method _substitute() for MemoryReference so that the substitution is made. Something like the following, but with some adjustments for correct typing, and perhaps alleviating the need to cross-convert a memory reference to a parameter.
def _substitute(self, parameter_memory_map) -> ExpressionDesignator:
self_as_parameter = Parameter(self.name)
if self_as_parameter not in parameter_memory_map:
return self # cannot substitute; unchanged behavior
memory = parameter_memory_map[self_as_parameter]
if self.offset >= len(memory):
raise RuntimeError(f"memory access {self.name}[{self.offset}] exceeds bounds of parameter value")
return memory[self.offset]
Additional References
None.
:tada: This issue has been resolved in version 3.1.0-rc.1 :tada:
The release is available on GitHub release
Your semantic-release bot :package::rocket:
:tada: This issue has been resolved in version 3.2.0-rc.1 :tada:
The release is available on GitHub release
Your semantic-release bot :package::rocket:
:tada: This issue has been resolved in version 3.2.0 :tada:
The release is available on GitHub release
Your semantic-release bot :package::rocket:
already released