Protocols for overlaps
The Overlap
of the two quantum states
where QubitOperator
i.e. it is written as a linear combination of Pauli strings HadamardTestOverlap
, SwapFactorizedOverlap
, and ComputeUncomputeFactorizedOverlap
protocols, discussed below.
HadamardTestOverlap
Let HadamardTestOverlap
protocol uses a “linear combination of unitaries” approach with a single ancilla on which to control the state preparation unitaries [30].
This protocol offers two measurement options: direct=False
, where measurement is performed on the ancilla qubit only, and direct=True
, where measurement is performed on both
the ancilla and state registers [31]. First, we discuss the direct=False
case.
To calculate the real part of the overlap,
This circuit prepares the quantum state:
where the first ket in each term is the ancilla, and
and the imaginary part is given equivalently by
For an overlap with a kernel
where each Pauli word has been appended to the direct=False
, to compute the complex overlap with a kernel of
In the direct=True
case, the Pauli words in
In this case, measurement of the state register measures direct=True
, to compute the complex overlap with a kernel we require
A simple example of using this protocol is given below:
from inquanto.operators import QubitOperator
from inquanto.states import QubitState, FermionState
from inquanto.ansatzes import FermionSpaceAnsatzUCCSD, HardwareEfficientAnsatz
from inquanto.computables import Overlap
from inquanto.protocols import HadamardTestOverlap
from pytket import OpType
from pytket.extensions.qiskit import AerBackend
from pytket.partition import PauliPartitionStrat
bra = HardwareEfficientAnsatz([OpType.Rx, OpType.Ry], QubitState([1, 1, 0, 0]), 2)
ket = FermionSpaceAnsatzUCCSD(4, FermionState([1, 1, 0, 0], 1))
params = (
bra.state_symbols.construct_random().to_dict()
| ket.state_symbols.construct_random().to_dict()
)
kernel = QubitOperator.from_string("(-0.1, Z0), (0.1, Z1), (0.25, X0 X1)")
ovlp = Overlap(bra, ket, kernel)
protocol = HadamardTestOverlap(
AerBackend(),
shots_per_circuit=int(12e3),
direct=True,
pauli_partition_strategy=PauliPartitionStrat.CommutingSets
)
protocol.build_from(params, ovlp)
protocol.run(seed=0)
circs = protocol.get_circuits()
print("Circuit count: ", len(circs))
ovlp.evaluate(protocol.get_evaluator())
Circuit count: 4
(-0.051508333333333337+0.030033333333333332j)
FactorizedOverlap
Protocols of the abstract type FactorizedOverlap
work on pairs of ansatzes that satisfy the following conditions:
Reference preparation can be factorized out from both state preparation unitaries i.e.
and Both ansatzes have the same reference state preparation circuit
Non-reference factors
and in the state preparation unitaries yield when acting on ; i.e. .
Exploitation of these properties admits more efficient overlap measurement circuits that avoid application of a control to the entire state preparations as is done in HadamardTestOverlap
.
Rather, only the reference parts of the state preparations must be controlled by an ancilla qubit.
The TypeError
upon preparation of the protocol circuits if either of the specified ansatzes is of a type that is not guaranteed to satisfy it.
All ansatz classes derived from FermionSpaceStateExp
and FermionSpaceStateExpChemicallyAware
are compatible with FactorizedOverlap
.
These are:
Note
The chemically aware ansatzes are only supported when both bra and ket state are chemically aware. The reason for this is that the chemically aware ansatz reference circuits are in spatial orbital Jordan–Wigner encoding, and so the reference state preparation circuit is not identical to the spin-orbital Jordan–Wigner encoded reference of the non-chemically aware ansatz, even if both ansatzes share the same fermionic reference state. See here for more details.
Currently, two classes derived from FactorizedOverlap
type are implemented: SwapFactorizedOverlap
and ComputeUncomputeFactorizedOverlap
.
These correspond to the two overlap measurement circuits presented in [32].
SwapFactorizedOverlap
This approach introduces an ancillary state register (lower in figure) to prepare the same linear combinations on the upper state register as in the case of the HadamardTestOverlap
.
Since this final state is identical to that of HadamardTestOverlap
, SwapFactorizedOverlap
is compatible with direct
operator averaging as in the circuit shown below:
A simple example usage of the direct SwapFactorizedOverlap
protocol is given below.
from inquanto.operators import QubitOperator
from inquanto.states import FermionState
from inquanto.ansatzes import FermionSpaceAnsatzkUpCCGSD
from inquanto.computables import Overlap
from inquanto.protocols import SwapFactorizedOverlap
from pytket.extensions.qiskit import AerBackend
from pytket.partition import PauliPartitionStrat
bra = FermionSpaceAnsatzkUpCCGSD(4, FermionState([1, 1, 0, 0], 1), 2)
ket = FermionSpaceAnsatzkUpCCGSD(4, FermionState([1, 1, 0, 0], 1), 2)
bra.symbol_substitution('{}_bra')
ket.symbol_substitution('{}_ket')
params = (
bra.state_symbols.construct_random(seed=1).to_dict()
| ket.state_symbols.construct_random(seed=2).to_dict()
)
kernel = QubitOperator.from_string("(-0.1, Z0), (0.1, Z1), (0.25, X0 X1)")
ovlp = Overlap(bra, ket, kernel)
protocol = SwapFactorizedOverlap(
AerBackend(),
shots_per_circuit=int(12e3),
direct=True,
pauli_partition_strategy=PauliPartitionStrat.CommutingSets
)
protocol.build_from(params, ovlp)
protocol.run(seed=0)
circs = protocol.get_circuits()
print("Circuit count: ", len(circs))
ovlp.evaluate(protocol.get_evaluator())
Circuit count: 4
(-0.040783333333333345+0.0032333333333333337j)
Indirect measurement is also possible with this protocol, for which the required Pauli words are appended to the ket state preparation as shown below.
A simple example usage of the indirect SwapFactorizedOverlap
protocol is given below.
from inquanto.operators import QubitOperator
from inquanto.states import FermionState
from inquanto.ansatzes import FermionSpaceAnsatzkUpCCGSD
from inquanto.computables import Overlap
from inquanto.protocols import SwapFactorizedOverlap
from pytket.extensions.qiskit import AerBackend
bra = FermionSpaceAnsatzkUpCCGSD(4, FermionState([1, 1, 0, 0], 1), 2)
ket = FermionSpaceAnsatzkUpCCGSD(4, FermionState([1, 1, 0, 0], 1), 2)
bra.symbol_substitution('{}_bra')
ket.symbol_substitution('{}_ket')
params = (
bra.state_symbols.construct_random(seed=1).to_dict()
| ket.state_symbols.construct_random(seed=2).to_dict()
)
kernel = QubitOperator.from_string("(-0.1, Z0), (0.1, Z1), (0.25, X0 X1)")
ovlp = Overlap(bra, ket, kernel)
protocol = SwapFactorizedOverlap(AerBackend(), shots_per_circuit=int(12e3), direct=False)
protocol.build_from(params, ovlp)
protocol.run(seed=0)
circs = protocol.get_circuits()
print("Circuit count: ", len(circs))
ovlp.evaluate(protocol.get_evaluator())
Circuit count: 6
(-0.04296666666666668-0.000683333333333333j)
ComputeUncomputeFactorizedOverlap
An alternative protocol in the FactorizedOverlap
family is ComputeUncomputeFactorizedOverlap
, which takes inspiration from the ComputeUncompute
method for overlaps squared,
and prepares the state:
This is accomplished by the following circuit:
Which has the advantage of fewer qubits than SwapFactorizedOverlap
, requiring only a single state register and one ancilla qubit, however it does produce deeper circuits.
Since projection of Pauli words on the final state does not correspond to terms in the matrix element of the kernel, it is not possible to use this protocol in conjunction with the direct
operator averaging scheme described above.
A simple example usage of the direct ComputeUncomputeFactorizedOverlap
protocol is given below.
from inquanto.operators import QubitOperator
from inquanto.states import FermionState
from inquanto.ansatzes import FermionSpaceAnsatzkUpCCGSD
from inquanto.computables import Overlap
from inquanto.protocols import ComputeUncomputeFactorizedOverlap
from pytket.extensions.qiskit import AerBackend
bra = FermionSpaceAnsatzkUpCCGSD(4, FermionState([1, 1, 0, 0], 1), 2)
ket = FermionSpaceAnsatzkUpCCGSD(4, FermionState([1, 1, 0, 0], 1), 2)
bra.symbol_substitution('{}_bra')
ket.symbol_substitution('{}_ket')
params = (
bra.state_symbols.construct_random(seed=1).to_dict()
| ket.state_symbols.construct_random(seed=2).to_dict()
)
kernel = QubitOperator.from_string("(-0.1, Z0), (0.1, Z1), (0.25, X0 X1)")
ovlp = Overlap(bra, ket, kernel)
protocol = ComputeUncomputeFactorizedOverlap(AerBackend(), shots_per_circuit=int(12e3))
protocol.build_from(params, ovlp)
protocol.run(seed=0)
circs = protocol.get_circuits()
print("Circuit count: ", len(circs))
ovlp.evaluate(protocol.get_evaluator())
Circuit count: 6
(-0.04065000000000001+0.004800000000000016j)