Skip to content

Commit

Permalink
Merge branch 'main' into add-unitary-synth-tests
Browse files Browse the repository at this point in the history
  • Loading branch information
mtreinish authored Mar 29, 2022
2 parents b570f29 + a7e8779 commit 30426f5
Show file tree
Hide file tree
Showing 8 changed files with 597 additions and 143 deletions.
172 changes: 128 additions & 44 deletions qiskit/opflow/gradients/circuit_gradients/lin_comb.py

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion qiskit/opflow/gradients/circuit_gradients/param_shift.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ def convert(
Returns:
An operator corresponding to the gradient resp. Hessian. The order is in accordance with
the order of the given parameters.
Raises:
OpflowError: If the parameters are given in an invalid format.
Expand Down Expand Up @@ -146,6 +147,7 @@ def _parameter_shift(
operator: The operator containing circuits we are taking the derivative of.
params: The parameters (ω) we are taking the derivative with respect to. If
a ParameterVector is provided, each parameter will be shifted.
Returns:
param_shifted_op: An operator object which evaluates to the respective gradients.
Expand Down Expand Up @@ -285,7 +287,7 @@ def _prob_combo_fn(
TypeError: if ``x`` is not DictStateFn, VectorStateFn or their list.
"""
# In the probability gradient case, the amplitudes still need to be converted
# Note: In the probability gradient case, the amplitudes still need to be converted
# into sampling probabilities.

def get_primitives(item):
Expand Down
88 changes: 59 additions & 29 deletions qiskit/opflow/gradients/circuit_qfis/lin_comb_full.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from qiskit.circuit import QuantumCircuit, QuantumRegister, ParameterVector, ParameterExpression
from qiskit.utils.arithmetic import triu_to_dense

from ...operator_base import OperatorBase
from ...list_ops.list_op import ListOp
from ...list_ops.summed_op import SummedOp
from ...operator_globals import I, Z, Y
Expand All @@ -31,10 +32,35 @@ class LinCombFull(CircuitQFI):
r"""Compute the full Quantum Fisher Information (QFI).
Given a pure, parameterized quantum state this class uses the linear combination of unitaries
approach, requiring one additional working qubit.
See also :class:`~qiskit.opflow.QFI`.
"""

# pylint: disable=signature-differs, arguments-differ
def __init__(
self,
aux_meas_op: OperatorBase = Z,
phase_fix: bool = True,
):
"""
Args:
aux_meas_op: The operator that the auxiliary qubit is measured with respect to.
For ``aux_meas_op = Z`` we compute 4Re[(dω⟨ψ(ω)|)O(θ)|ψ(ω)〉],
for ``aux_meas_op = -Y`` we compute 4Im[(dω⟨ψ(ω)|)O(θ)|ψ(ω)〉], and
for ``aux_meas_op = Z - 1j * Y`` we compute 4(dω⟨ψ(ω)|)O(θ)|ψ(ω)〉.
phase_fix: Whether or not to compute and add the additional phase fix term
Re[(dω⟨<ψ(ω)|)|ψ(ω)><ψ(ω)|(dω|ψ(ω))>].
Raises:
ValueError: If the provided auxiliary measurement operator is not supported.
"""
super().__init__()
if aux_meas_op not in [Z, -Y, (Z - 1j * Y)]:
raise ValueError(
"This auxiliary measurement operator is currently not supported. Please choose "
"either Z, -Y, or Z - 1j * Y. "
)
self._aux_meas_op = aux_meas_op
self._phase_fix = phase_fix

def convert(
self,
operator: CircuitStateFn,
Expand All @@ -45,7 +71,6 @@ def convert(
operator: The operator corresponding to the quantum state :math:`|\psi(\omega)\rangle`
for which we compute the QFI.
params: The parameters :math:`\omega` with respect to which we are computing the QFI.
Returns:
A ``ListOp[ListOp]`` where the operator at position ``[k][l]`` corresponds to the matrix
element :math:`k, l` of the QFI.
Expand All @@ -54,11 +79,9 @@ def convert(
TypeError: If ``operator`` is an unsupported type.
"""
# QFI & phase fix observable
qfi_observable = ~StateFn(4 * Z ^ (I ^ operator.num_qubits))
phase_fix_observable = StateFn(
(Z + 1j * Y) ^ (I ^ operator.num_qubits), is_measurement=True
qfi_observable = StateFn(
4 * self._aux_meas_op ^ (I ^ operator.num_qubits), is_measurement=True
)
# see https://arxiv.org/pdf/quant-ph/0108146.pdf

# Check if the given operator corresponds to a quantum state given as a circuit.
if not isinstance(operator, CircuitStateFn):
Expand All @@ -73,20 +96,23 @@ def convert(
elif isinstance(params, ParameterVector):
params = params[:] # unroll to list

# First, the operators are computed which can compensate for a potential phase-mismatch
# between target and trained state, i.e.〈ψ|∂lψ〉
gradient_states = LinComb()._gradient_states(
operator,
meas_op=phase_fix_observable,
target_params=params,
open_ctrl=False,
trim_after_grad_gate=True,
)
# if type(gradient_states) in [ListOp, SummedOp]: # pylint: disable=unidiomatic-typecheck
if type(gradient_states) == ListOp:
phase_fix_states = gradient_states.oplist
else:
phase_fix_states = [gradient_states]
if self._phase_fix:
# First, the operators are computed which can compensate for a potential phase-mismatch
# between target and trained state, i.e.〈ψ|∂lψ〉
phase_fix_observable = I ^ operator.num_qubits
gradient_states = LinComb(aux_meas_op=(Z - 1j * Y))._gradient_states(
operator,
meas_op=phase_fix_observable,
target_params=params,
open_ctrl=False,
trim_after_grad_gate=True,
)

# pylint: disable=unidiomatic-typecheck
if type(gradient_states) == ListOp:
phase_fix_states = gradient_states.oplist
else:
phase_fix_states = [gradient_states]

# Get 4 * Re[〈∂kψ|∂lψ]
qfi_operators = []
Expand Down Expand Up @@ -179,15 +205,19 @@ def convert(

# Compute −4 * Re(〈∂kψ|ψ〉〈ψ|∂lψ〉)
def phase_fix_combo_fn(x):
return 4 * (-0.5) * (x[0] * np.conjugate(x[1]) + x[1] * np.conjugate(x[0]))

phase_fix = ListOp(
[phase_fix_states[i], phase_fix_states[j]], combo_fn=phase_fix_combo_fn
)
# Add the phase fix quantities to the entries of the QFI
# Get 4 * Re[〈∂kψ|∂lψ〉−〈∂kψ|ψ〉〈ψ|∂lψ〉]
qfi_ops += [SummedOp(qfi_op) + phase_fix]
return -4 * np.real(x[0] * np.conjugate(x[1]))

if self._phase_fix:
phase_fix_op = ListOp(
[phase_fix_states[i], phase_fix_states[j]], combo_fn=phase_fix_combo_fn
)
# Add the phase fix quantities to the entries of the QFI
# Get 4 * Re[〈∂kψ|∂lψ〉−〈∂kψ|ψ〉〈ψ|∂lψ〉]
qfi_ops += [SummedOp(qfi_op) + phase_fix_op]
else:
qfi_ops += [SummedOp(qfi_op)]

qfi_operators.append(ListOp(qfi_ops))
# Return the full QFI

# Return estimate of the full QFI -- A QFI is by definition positive semi-definite.
return ListOp(qfi_operators, combo_fn=triu_to_dense)
1 change: 1 addition & 0 deletions qiskit/opflow/gradients/derivative_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ def _erase_operator_coeffs(cls, operator: OperatorBase) -> OperatorBase:
Returns:
An operator which is equal to the input operator but whose coefficients
have all been set to 1.0
Raises:
TypeError: If unknown operator type is reached.
"""
Expand Down
17 changes: 15 additions & 2 deletions qiskit/opflow/gradients/gradient.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,12 @@ def is_coeff_c(coeff, c):
return expr == c
return coeff == c

def is_coeff_c_abs(coeff, c):
if isinstance(coeff, ParameterExpression):
expr = coeff._symbol_expr
return np.abs(expr) == c
return np.abs(coeff) == c

if isinstance(params, (ParameterVector, list)):
param_grads = [self.get_gradient(operator, param) for param in params]
# If get_gradient returns None, then the corresponding parameter was probably not
Expand All @@ -126,10 +132,17 @@ def is_coeff_c(coeff, c):
# By now params is a single parameter
param = params
# Handle Product Rules
if not is_coeff_c(operator._coeff, 1.0):
if not is_coeff_c(operator._coeff, 1.0) and not is_coeff_c(operator._coeff, 1.0j):
# Separate the operator from the coefficient
coeff = operator._coeff
op = operator / coeff
if np.iscomplex(coeff):
from .circuit_gradients.lin_comb import LinComb

if isinstance(self.grad_method, LinComb):
op *= 1j
coeff /= 1j

# Get derivative of the operator (recursively)
d_op = self.get_gradient(op, param)
# ..get derivative of the coeff
Expand All @@ -152,7 +165,7 @@ def is_coeff_c(coeff, c):
if isinstance(operator, ComposedOp):

# Gradient of an expectation value
if not is_coeff_c(operator._coeff, 1.0):
if not is_coeff_c_abs(operator._coeff, 1.0):
raise OpflowError(
"Operator pre-processing failed. Coefficients were not properly "
"collected inside the ComposedOp."
Expand Down
Loading

0 comments on commit 30426f5

Please sign in to comment.