qiskit
qiskit copied to clipboard
[WIP] Gradients with the primitives
Co-authored-by: Ikko Hamamura [email protected] Co-authored-by: Takashi Imamichi [email protected]
Summary
This PR adds Finite Difference/Parameter Shift/Linear Comb. of Unitaries gradients with the primitives. close #8496
Details and comments
There are 3 types of gradient methods for a sampler and estimator
- [x]
FiniteDiffSamplerGradient - [x]
ParamShiftSamplerGradient - [x]
LinCombSamplerGradient - [x]
FiniteDiffEstimatorGradient - [x]
ParamShiftEstimatorGradient - [x]
LinCombEstimatorGradient - [ ] unittests
Thank you for opening a new pull request.
Before your PR can be merged it will first need to pass continuous integration tests and be reviewed. Sometimes the review process can be slow, so please be patient.
While you're waiting, please feel free to review other open PRs. While only a subset of people are authorized to approve pull requests for merging, everyone is encouraged to review open pull requests. Doing reviews helps reduce the burden on the core team and helps make the project's code better for everyone.
One or more of the the following people are requested to review this:
- @Qiskit/terra-core
- @manoelmarques
- @woodsp-ibm
Pull Request Test Coverage Report for Build 2992392546
- 656 of 679 (96.61%) changed or added relevant lines in 14 files are covered.
- No unchanged relevant lines lost coverage.
- Overall coverage increased (+0.1%) to 84.361%
| Changes Missing Coverage | Covered Lines | Changed/Added Lines | % |
|---|---|---|---|
| qiskit/algorithms/gradients/base_sampler_gradient.py | 34 | 36 | 94.44% |
| qiskit/algorithms/gradients/finite_diff_estimator_gradient.py | 39 | 41 | 95.12% |
| qiskit/algorithms/gradients/finite_diff_sampler_gradient.py | 42 | 44 | 95.45% |
| qiskit/algorithms/gradients/lin_comb_estimator_gradient.py | 60 | 62 | 96.77% |
| qiskit/algorithms/gradients/lin_comb_sampler_gradient.py | 58 | 60 | 96.67% |
| qiskit/algorithms/gradients/param_shift_estimator_gradient.py | 44 | 46 | 95.65% |
| qiskit/algorithms/gradients/param_shift_sampler_gradient.py | 45 | 47 | 95.74% |
| qiskit/algorithms/gradients/spsa_estimator_gradient.py | 44 | 46 | 95.65% |
| qiskit/algorithms/gradients/spsa_sampler_gradient.py | 49 | 51 | 96.08% |
| qiskit/algorithms/gradients/utils.py | 166 | 168 | 98.81% |
| <!-- | Total: | 656 | 679 |
| Totals | |
|---|---|
| Change from base Build 2990361532: | 0.1% |
| Covered Lines: | 57844 |
| Relevant Lines: | 68567 |
💛 - Coveralls
@Cryoris (I couldn't make a reply to this comment.)
I have one main question: could we build the gradients in such a way that we export the logic to make all parameters in a circuit unique in a separate function? This is something that the classically efficient gradients will also need (and the linear combination of unitaries, too?) so if it's inside the parameter shift rule that's a bit hidden 🙂
make_param_shift_gradient_circuit_data in gradients/utils.py does it. It adds virtual variables for the parameter shift rule if necessary. Maybe, we can make that functionality optional and reuse it for the classically efficient gradients?
https://github.com/a-matsuo/qiskit-terra/blob/9abbd2ac79ef1177308c236c0be344d2333384c6/qiskit/algorithms/gradients/utils.py#L75
Thank you so much, everyone! I fixed the codes based on the review comments.
It's good to use ``...`` instead of `...` for make code part of docstrings, e.g., ``circuit`` and ``run``.
Update: the following example is a problem of CU gate #7326
The following example works fine with param shift and lcu, but finite diff and SPSA raise an error. Could you try it?
from qiskit import QuantumCircuit
from qiskit.circuit import ParameterVector
from qiskit.algorithms.gradients import (
FiniteDiffEstimatorGradient,
LinCombEstimatorGradient,
ParamShiftEstimatorGradient,
SPSAEstimatorGradient,
)
from qiskit.primitives import Estimator
from qiskit.quantum_info import SparsePauliOp
qc = QuantumCircuit(2)
param = ParameterVector("θ", 4)
qc.h(0)
qc.cu(param[0], param[1], param[2], param[3], 0, 1)
print(qc)
op = SparsePauliOp(["IZ", "XY"])
estimator = Estimator()
for grad in [ParamShiftEstimatorGradient, LinCombEstimatorGradient]:
print(grad)
g = grad(estimator)
result = g.run([qc], [op], [[1, 2, 3, 4]]).result()
print(result.gradients)
print()
for grad in [FiniteDiffEstimatorGradient, SPSAEstimatorGradient]:
print(grad)
g = grad(estimator, 1e-6)
result = g.run([qc], [op], [[1, 2, 3, 4]]).result()
print(result.gradients)
print()
output
┌───┐
q_0: ┤ H ├────────────■─────────────
└───┘┌───────────┴────────────┐
q_1: ─────┤ U(θ[0],θ[1],θ[2],θ[3]) ├
└────────────────────────┘
<class 'qiskit.algorithms.gradients.param_shift_estimator_gradient.ParamShiftEstimatorGradient'>
[array([-1.22605084e-01, 4.60330157e-01, 1.66533454e-16, 4.60330157e-01])]
<class 'qiskit.algorithms.gradients.lin_comb_estimator_gradient.LinCombEstimatorGradient'>
[array([-0.12260508, 0.46033016, 0. , 0.46033016])]
<class 'qiskit.algorithms.gradients.finite_diff_estimator_gradient.FiniteDiffEstimatorGradient'>
Traceback (most recent call last):
File "/Users/ima/envs/dev39/lib/python3.9/site-packages/qiskit/circuit/parameterexpression.py", line 459, in __float__
return float(self._symbol_expr)
File "symengine_wrapper.pyx", line 1151, in symengine.lib.symengine_wrapper.Basic.__float__
File "symengine_wrapper.pyx", line 976, in symengine.lib.symengine_wrapper.Basic.n
File "symengine_wrapper.pyx", line 4346, in symengine.lib.symengine_wrapper.evalf
RuntimeError: Symbol cannot be evaluated.
The above exception was the direct cause of the following exception:
...
TypeError: ParameterExpression with unbound parameters ({ParameterVectorElement(θ[0])}) cannot be cast to a float.
@t-imamichi This seems to be a problem with the primitives themselves, not the gradient code:
from qiskit import QuantumCircuit
from qiskit.circuit import ParameterVector
from qiskit.primitives import Estimator
from qiskit.quantum_info import SparsePauliOp
qc = QuantumCircuit(2)
param = ParameterVector("θ", 4)
qc.h(0)
qc.cu(param[0], param[1], param[2], param[3], 0, 1)
print(qc)
op = SparsePauliOp(["IZ", "XY"])
estimator = Estimator()
res = estimator.run([qc], [op], [[1, 2, 3, 4]]).result()
print(res)
This gives the same error...
@Cryoris Thanks. I didn't notice it. I look into primitives.
I found that CU gate has an issue of parameter binding #7326. We cannot apply primitives to circuits with CU gates...