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
The inputs and outputs of the quantum function circuit #11
Comments
circuit()
Currently, the user defined quantum function takes an arbitrary number of positional arguments: def circuit(x,y,z): Each of these can either be a variational parameter or input data, depending on how the function is defined in the cost function. Sneaky edit: the following suggestions seem more apt for the cost function, not the quantum node/quantum function. Perhaps the quantum function should remain arbitrary and up to the user, and only the cost function is restricted. Some suggestions are:
Downsides of 2. and 3. are that the user would have to use the (not commonly known) syntax |
Another factor in the design is how to get autograd to play nicely; we want the wrapped quantum function to be called exactly as it is defined, to avoid confusing the user. At the moment, my def qfunc(device):
def qfunc_decorator(func):
qnode = QNode(func, device)
@wraps(func)
def wrapper(*args, **kwargs):
return qnode(*args, **kwargs)
return wrapper
return qfunc_decorator i.e. if the function is defined with multiple positional arguments, it must be called with a single argument, that is unwrapped behind the scenes. This is confusing, but for some reason, the only way I could get autograd to work with multiple arguments; otherwise I get @smite, do you have any suggestions? |
I am very much in favour of 1. However, we should keep in mind that a circuit might not take any data, or even any parameters. For example when the quantum node is already optimized and one wants to now optimize a classical node, the parameters of the quantum node are fixed. Or if the quantum node is part of the ML model but not trainable (think of the kernel methods where the quantum node takes data and computes kernel values that are part of a classically trained model). |
Actually, thinking about it some more, do we need to put constraints on the arguments of the quantum function? Perhaps we would need constraints on the arguments to the cost function, since that is being passed on to the optimizer. |
My suggestion is to leave the QNode fairly abstract, a R^n \to R^m mapping, with one input argument and one output argument, which are NumPy 1D arrays. The classical cost function can distinguish between trainable parameters and data. See for example |
Programmatically, there is not really any need to distinguish between data and parameters for quantum functions. Within the variational approach, these are all used the same way: as arguments to one-parameter gates. Later on, we can tell the optimizer which we are optimizing (and this retains the flexibility to 'freeze' some parameters while updating others). Let's remember that something being a "parameter" of the quantum circuit does not require that it is trainable/updatable. A parameter is simply something which determines which function from the class of possible functions is the one that is applied to the inputs. However, there is an argument for making things conceptually clear for the user. Users might not be too familiar with the ideas developing around variational circuits, especially the notion of circuit gradients. Explicitly specifying which parts of the circuit are parameters will make it clearer what is happening with the automatic differentiation. |
@smite I am thinking along the same lines, but does the input have to be a 1D array? Doesn't autograd support tuples, lists, nested inputs as well? |
I think there could be two options. Make circuit(...) something that has fully user defined inputs, or use keyword arguments "weights" and "data" (which default to None). If it is user defined, do we get into trouble because computing the gradient of a QNode assumes that all arguments are trainable parameters? |
By the way, can we call all trainable parameters simpy "weights"? That is clearly separated from circuit parameters, hyperparameters and has an intuitive meaning in ML... |
@smite: an example based on my earlier comment:
This would certainly make things more flexible for the user |
As @smite mentions above, we likely want to allow for a quantum node to output a vector in R^m. This has multiple use-cases:
A smart way to do this is for us to make measurements/expvals a separate abstraction. All the gates a user declares in Sketch pseudocode:
|
Two quick comments, if I may:
Maybe Expectation could have attributes Sorry just contributing my two cents... |
Some thoughts from my side:
|
Quick comment on number 3: The dictionary idea is indeed important for more complex machine learning tasks, but if I am not mistaken this would cause major fixes because we cannot apply autograd directly to cost. I thought about this at length for the QMLT and came to the conclusion that within a realistic 5-year timeframe, having weights as nested arrays or tuples is enough for the type of experiments people do. In 5 years we could always wrap a more abstract argument type around everything. What do you think @cgogolin? |
If this causes major headaches with autograd, then let's forget about it. I naively thought that it should be easy to serialize a dictionary into an array, then use autograd, and then de-serialize the array back into a dictionary. |
@cgogolin I agree with your points 1, 2. We should be able to support both @co9olguy, regarding your proposal for expectation values - it makes sense to implement multiple expectation values on different wires, that can be run by a single hardware device: def circuit():
...
qm.expectation.PauliZ(0)
qm.expectation.PauliZ(1)
qm.expectation.PauliZ(3) which would then automatically return a vector to the user. (Or perhaps we could use a return statement? more of an interface decision). However, for multiple expectation values on the same wire, I would rather the user define a new device/qnode altogether, like @mariaschuld does in her VQE example: def ansatz(params):
qm.Rot(...)
qm.CNOT(...)
@qfunc
def circuit_meas_x(params):
ansatz(params)
qm.expectation.PauliX(1)
@qfunc
def circuit_meas_z(params):
ansatz(params)
qm.expectation.PauliZ(1) This already works with the current refactor, and has the benefits that:
Thoughts? |
Concerning multiple measurements on different wires: Keep in mind that some backends/plugins only support one measurement. IBM for example only allows to measure all qubits at once and only exactly one time. For this backend in the ProjectQ plugin is thus only have one observable available wich I would call "AllPauliZ" and I somehow need to "return" (i.e., save to |
Good point. |
I'm hearing arguments in favour of one Qnode <-> one measurement, and there are also suggestions (my own included) to have multiple measurements per Qnode. As @cgogolin points out, we will be constrained by what the plugins/backends actually allow. Since our main qubit plugin only supports one measurement, I'm thinking it it is the path of least resistance to force Qnodes to only have one measurement for our initial version. Multiple measurements can be stitched together from multiple Qnode outputs. Any contrary opinions on this particular point? |
+1 for only one measurement, but keep in mind that even a sigle measurement can return multiple values. |
Maybe we can speak about (maximum) one measurement per subsystem per QNode. We do not get into trouble with non-commuting measurements then. |
Ok let's go with one Qnode <-> max one expectation value per subsystem as Maria suggested |
Commit 8a3237b added the return statement |
@josh146 told me that the plan is to "enforce" the Maybe like this?
This relates to my question on the lifecycle of plugins. Are they expected to be able to cope with multiple calls to |
The way it currently works, where def circuit():
...
return [qm.expectation.PauliZ(0), qm.expectation.PauliZ(1), qm.expectation.PauliZ(3)] On the function return, Python will evaluate the three expectation calls from left-to-right. The simplest solution is to modify Since Python will always evaluate them from left-to-right, this will preserve the order provided by the user, and the plugin will return the resulting expectation values in the same order when looping through |
I would suggest making the return statement functional. This can be done with the syntax
Edit: |
@smite I am currently implementing exactly that! |
Closing this issue as the API is largely established at this point. If there are any bugs, report them in separate issues |
* Structure * Struct jax interface * First draft * Single measurement is working * First derivative * tests * Add tests * x64 Jax testing * Cleanup * more cleanup * More tests * More tests * QNode testing * More tests pass * Typos in tests * Test JVP structure. * More tests * More tests * More tests * Typoo * Coverage * test * Jax import test * Typo * Trigger CI * Update param shift * Docstrings * Add tests * JVP * Docstrings * Docstrings * more line * Pragma * First draft * Add tests * Apply suggestions from code review Co-authored-by: Edward Jiang <34989448+eddddddy@users.noreply.github.com> Co-authored-by: antalszava <antalszava@gmail.com> * Apply suggestions from code review Co-authored-by: Edward Jiang <34989448+eddddddy@users.noreply.github.com> * Update from revie * typo * Update tests * missing test * Apply suggestions from code review Co-authored-by: antalszava <antalszava@gmail.com> Co-authored-by: Edward Jiang <34989448+eddddddy@users.noreply.github.com> * error tracer * Apply suggestions from code review Co-authored-by: antalszava <antalszava@gmail.com> * Review * Comment from review * More tests * Test * Add device back * Update with finite diff * Add ShotTuple * Shots default is non * Add more tests * Black * Trigger CI * Review * Review * Update tests/returntypes/test_jax_qnode_new.py Co-authored-by: antalszava <antalszava@gmail.com> * Update pennylane/interfaces/jax.py Co-authored-by: antalszava <antalszava@gmail.com> * black * Separate tests * Tests jac * move finites shots to the same cases as shots=None * skip adjoint finite shots * no more warning Co-authored-by: Edward Jiang <34989448+eddddddy@users.noreply.github.com> Co-authored-by: antalszava <antalszava@gmail.com>
* Fix test * Fix test * Small fix * Add _Expectation class * Add changelog entry * Coverage * Refactor MeasurementProcess * Fix tests * Fix tests * Use child classes * More refactors * Address comments * Add interface tests * Change state type * Change state type * Add test * Remove error * Remove comment * Add tests * Add tests * Some changes * Small fix * Fix types * Fix types * Remove numpy usage * Fix types * Fix types * Add mark * Revert * Remove return_type argument * Small Fix * Small Fix * Fix classical shadow * Small fix * Small fix * Use if self.wires * Small fix * Fix wire_map * Fix wire_map * Fix wire_map * Revert * Revert * Add classical shadow logic to the MP class * Small fix * Fix tests * Fix tests * Fix tests * Fix tests * Fix tests * Fix tests * Fix tests * Add test * Add wire_order to process_samples * Docstring * Add wire_order to process_samples * Small fix * Small fix * Add wire_order to process_samples * Add wire_order to process_samples * Change tests * Add wire_order to process_samples * Add breaking change message * Change tests * Change tests * Add wire_order to process_samples * Change tests * Fix tests * Fix tests * Add wire_order to process_samples * Change tests * Coverage * Coverage * Fix tests * Add tests * Revert * Add tests * Add tests * Remove duplicated test * Add tests * Small changes * Fix tests * black * Change name * Add CustomMeasurement * Small fix * Small fix * Small fix * test (ClassicalShadow): 🧪 Test ClassicalShadow.process method * Add changelog entry * test (ClassicalShadow): 🧪 Fix mocker.spy * revert (ClassicalShadow): ⏪ Remove process_state. * Revert * feat: ✨ Add _ShadowExpval class. * Fix tests * Fix test * Fix * Fix docs * Fix docs * Fix types * Fix types * Fix docstrings * fix test * Add changelog entry * Remove docs * Remove docs * Fix docstrings * Small change * Remove return_type * Fix * Fix * Fix * fix * fix * fix * Fix tests * Remove mocker * Add tests * Fix tests * Coverage * Update doc/releases/changelog-dev.md * Revert merge * Revert tests * Revert * Copy hamiltonian * Use seed * Change docstrings * Change instance check. * Fix * Fix * Remove dead code * Fix tests * Add deprecation warning * Fix docstring * Add changelog entry * Fix docstring * Add type * Remove type * Change warning type * Revert * Add changelog entry * Add changelog entry * Make return_type None by default * Coverage * Update tests/measurements/test_counts.py * Remove unused imports * Add import * Move import * Update tests/measurements/test_measurements.py * Move ABC inheritannce * move ABC * Move import
* Add _Expectation class * Add changelog entry * Coverage * Refactor MeasurementProcess * Fix tests * Fix tests * Use child classes * More refactors * Address comments * Add interface tests * Change state type * Change state type * Add test * Remove error * Remove comment * Add tests * Add tests * Some changes * Small fix * Fix types * Fix types * Remove numpy usage * Fix types * Fix types * Add mark * Revert * Remove return_type argument * Small Fix * Small Fix * Fix classical shadow * Small fix * Small fix * Use if self.wires * Small fix * Fix wire_map * Fix wire_map * Fix wire_map * Revert * Revert * Add classical shadow logic to the MP class * Small fix * Fix tests * Fix tests * Fix tests * Fix tests * Fix tests * Fix tests * Fix tests * Add test * Add wire_order to process_samples * Docstring * Add wire_order to process_samples * Small fix * Small fix * Add wire_order to process_samples * Add wire_order to process_samples * Change tests * Add wire_order to process_samples * Add breaking change message * Change tests * Change tests * Add wire_order to process_samples * Change tests * Fix tests * Fix tests * Add wire_order to process_samples * Change tests * Coverage * Coverage * Fix tests * Add tests * Revert * Add tests * Add tests * Remove duplicated test * Add tests * Small changes * Fix tests * black * Change name * Add CustomMeasurement * Small fix * Small fix * Small fix * test (ClassicalShadow): 🧪 Test ClassicalShadow.process method * Add changelog entry * test (ClassicalShadow): 🧪 Fix mocker.spy * revert (ClassicalShadow): ⏪ Remove process_state. * Revert * feat: ✨ Add _ShadowExpval class. * Fix tests * Fix test * Fix * Fix docs * Fix docs * Fix types * Fix types * Fix docstrings * fix test * Add changelog entry * Remove docs * Remove docs * Fix docstrings * Small change * Remove return_type * Fix * Fix * Fix * fix * fix * fix * Fix tests * Remove mocker * Add tests * Fix tests * Coverage * Update doc/releases/changelog-dev.md * Revert merge * Revert tests * Revert * Copy hamiltonian * Use seed * Change docstrings * Change instance check. * Fix * Fix * Remove dead code * Fix tests * Add deprecation warning * Fix docstring * Add changelog entry * Fix docstring * Add type * Remove type * Change warning type * Revert * Add changelog entry * Refactor MP * Add changelog entry * Make return_type None by default * Coverage * Update tests/measurements/test_counts.py * Remove unused imports * Add import * Move import * Update tests/measurements/test_measurements.py * Move ABC inheritannce * move ABC * Move import
* Refactor MeasurementProcess * Fix tests * Fix tests * Use child classes * More refactors * Address comments * Add interface tests * Change state type * Change state type * Add test * Remove error * Remove comment * Add tests * Add tests * Some changes * Small fix * Fix types * Fix types * Remove numpy usage * Fix types * Fix types * Add mark * Revert * Remove return_type argument * Small Fix * Small Fix * Fix classical shadow * Small fix * Small fix * Use if self.wires * Small fix * Fix wire_map * Fix wire_map * Fix wire_map * Revert * Revert * Add classical shadow logic to the MP class * Small fix * Fix tests * Fix tests * Fix tests * Fix tests * Fix tests * Fix tests * Fix tests * Add test * Add wire_order to process_samples * Docstring * Add wire_order to process_samples * Small fix * Small fix * Add wire_order to process_samples * Add wire_order to process_samples * Change tests * Add wire_order to process_samples * Add breaking change message * Change tests * Change tests * Add wire_order to process_samples * Change tests * Fix tests * Fix tests * Add wire_order to process_samples * Change tests * Coverage * Coverage * Fix tests * Add tests * Revert * Add tests * Add tests * Remove duplicated test * Add tests * Small changes * Fix tests * black * Change name * Add CustomMeasurement * Small fix * Small fix * Small fix * test (ClassicalShadow): 🧪 Test ClassicalShadow.process method * Add changelog entry * test (ClassicalShadow): 🧪 Fix mocker.spy * revert (ClassicalShadow): ⏪ Remove process_state. * Revert * feat: ✨ Add _ShadowExpval class. * Fix tests * Fix test * Fix * Fix docs * Fix docs * Fix types * Fix types * Fix docstrings * fix test * Add changelog entry * Remove docs * Remove docs * Fix docstrings * Small change * Remove return_type * Fix * Fix * Fix * fix * fix * fix * Fix tests * Remove mocker * Add tests * Fix tests * Coverage * Update doc/releases/changelog-dev.md * Revert merge * Revert tests * Revert * Copy hamiltonian * Use seed * Change docstrings * Change instance check. * Fix * Fix * Remove dead code * Fix tests * Add deprecation warning * Fix docstring * Add changelog entry * Fix docstring * Add type * Remove type * Change warning type * Revert * Add changelog entry * Refactor MP * Refactor MP * Coverage * Add changelog entry * Make return_type None by default * Coverage * Update tests/measurements/test_counts.py * Remove unused imports * Add import * Move import * Update tests/measurements/test_measurements.py * Move ABC inheritannce * Update tests/measurements/test_vn_entropy.py Co-authored-by: Edward Jiang <34989448+eddddddy@users.noreply.github.com> * Call _shape_new * refactor (State): Raise error later in shape method. * refactor (State): Raise error later in shape method. * test: 🧪 Change skip message * move ABC * Move import Co-authored-by: Edward Jiang <34989448+eddddddy@users.noreply.github.com>
This issue is related to how we pass/call the quantum function associated with a
QNode
. As far as I can see, we want to provide maximum freedom to the user, but are constrained by autograd/design decisions/python decorators.From @mariaschuld:
The text was updated successfully, but these errors were encountered: