qml.taper_operation¶
- taper_operation(operation, generators, paulixops, paulix_sector, wire_order, op_wires=None, op_gen=None)[source]¶
Transform a gate operation with a Clifford operator and then taper qubits.
The qubit operator for the generator of the gate operation is computed either internally or can be provided manually via the
op_gen
argument. If this operator commutes with all the \(\mathbb{Z}_2\) symmetries of the molecular Hamiltonian, then this operator is transformed using the Clifford operators \(U\) and tapered; otherwise it is discarded. Finally, the tapered generator is exponentiated usingExp
for building the tapered unitary.- Parameters
operation (Operation or Callable) – qubit operation to be tapered, or a function that applies that operation
generators (list[Hamiltonian]) – generators expressed as PennyLane Hamiltonians
paulixops (list[Operation]) – list of single-qubit Pauli-X operators
paulix_sector (list[int]) – eigenvalues of the Pauli-X operators
wire_order (Sequence[Any]) – order of the wires in the quantum circuit
op_wires (Sequence[Any]) – wires for the operation in case any of the provided
operation
orop_gen
are callablesop_gen (Hamiltonian or PauliSentence or Callable) – generator of the operation, or a function that returns it in case it cannot be computed internally.
- Returns
list of operations of type
Exp
implementing tapered unitary operation- Return type
list[Operation]
- Raises
ValueError – optional argument
op_wires
is not provided when the provided operation is a callableTypeError – optional argument
op_gen
is a callable but does not havewires
as its only keyword argumentNotImplementedError – generator of the operation cannot be constructed internally
ValueError – optional argument
op_gen
is either not aHamiltonian
or a valid generator of the operation
Example
>>> symbols, geometry = ['He', 'H'], np.array([[0.0, 0.0, 0.0], [0.0, 0.0, 1.4589]]) >>> mol = qchem.Molecule(symbols, geometry, charge=1) >>> H, n_qubits = qchem.molecular_hamiltonian(symbols, geometry, charge=1) >>> generators = qchem.symmetry_generators(H) >>> paulixops = qchem.paulix_ops(generators, n_qubits) >>> paulix_sector = qchem.optimal_sector(H, generators, mol.n_electrons) >>> tap_op = qchem.taper_operation(qml.SingleExcitation, generators, paulixops, ... paulix_sector, wire_order=H.wires, op_wires=[0, 2]) >>> tap_op(3.14159) [Exp(1.5707949999999993j PauliY), Exp(0j Identity)]
The obtained tapered operation function can then be used within a
QNode
:>>> dev = qml.device('default.qubit', wires=[0, 1]) >>> @qml.qnode(dev) ... def circuit(params): ... tap_op(params[0]) ... return qml.expval(qml.Z(0)@qml.Z(1)) >>> drawer = qml.draw(circuit, show_all_wires=True) >>> print(drawer(params=[3.14159])) 0: ──Exp(0.00+1.57j Y)─┤ ╭<Z@Z> 1: ────────────────────┤ ╰<Z@Z>
Usage Details
qml.taper_operation
can also be used with the quantum operations, in which case one does not need to specifyop_wires
args:>>> qchem.taper_operation(qml.SingleExcitation(3.14159, wires=[0, 2]), generators, ... paulixops, paulix_sector, wire_order=H.wires) [Exp(1.570795j PauliY)]
Moreover, it can also be used within a
QNode
directly:>>> dev = qml.device('default.qubit', wires=[0, 1]) >>> @qml.qnode(dev) ... def circuit(params): ... qchem.taper_operation(qml.DoubleExcitation(params[0], wires=[0, 1, 2, 3]), ... generators, paulixops, paulix_sector, H.wires) ... return qml.expval(qml.Z(0)@qml.Z(1)) >>> drawer = qml.draw(circuit, show_all_wires=True) >>> print(drawer(params=[3.14159])) 0: ─╭Exp(-0.00-0.79j X@Y)─╭Exp(-0.00-0.79j Y@X)─┤ ╭<Z@Z> 1: ─╰Exp(-0.00-0.79j X@Y)─╰Exp(-0.00-0.79j Y@X)─┤ ╰<Z@Z>
For more involved gates operations such as the ones constructed from matrices, users would need to provide their generators manually via the
op_gen
argument. The generator can be passed as aHamiltonian
,PauliSentence
or any arithmetic operator:>>> op_fun = qml.QubitUnitary(np.array([[0.+0.j, 0.+0.j, 0.+0.j, 0.-1.j], ... [0.+0.j, 0.+0.j, 0.-1.j, 0.+0.j], ... [0.+0.j, 0.-1.j, 0.+0.j, 0.+0.j], ... [0.-1.j, 0.+0.j, 0.+0.j, 0.+0.j]]), wires=[0, 2]) >>> op_gen = qml.Hamiltonian([-0.5 * np.pi], ... [qml.X(0) @ qml.X(2)]) >>> qchem.taper_operation(op_fun, generators, paulixops, paulix_sector, ... wire_order=H.wires, op_gen=op_gen) [Exp(1.5707963267948957j PauliX)]
Alternatively, generators can also be specified as a function which returns
Hamiltonian
or an arithmetic operator, and useswires
as its only required keyword argument:>>> op_gen = lambda wires: qml.Hamiltonian( ... [0.25, -0.25], ... [qml.X(wires[0]) @ qml.Y(wires[1]), ... qml.Y(wires[0]) @ qml.X(wires[1])]) >>> qchem.taper_operation(qml.SingleExcitation, generators, paulixops, paulix_sector, ... wire_order=H.wires, op_wires=[0, 2], op_gen=op_gen)(3.14159) [Exp(1.570795j PauliY)]
Theory
Consider \(G\) to be the generator of a unitrary \(V(\theta)\), i.e.,
\[V(\theta) = e^{i G \theta}.\]Then, for \(V\) to have a non-trivial and compatible tapering with the generators of symmetry \(\tau\), we should have \([V, \tau_i] = 0\) for all \(\theta\) and \(\tau_i\). This would hold only when its generator itself commutes with each \(\tau_i\),
\[[V, \tau_i] = 0 \iff [G, \tau_i]\quad \forall \theta, \tau_i.\]By ensuring this, we can taper the generator \(G\) using the Clifford operators \(U\), and exponentiate the transformed generator \(G^{\prime}\) to obtain a tapered unitary \(V^{\prime}\),
\[V^{\prime} \equiv e^{i U^{\dagger} G U \theta} = e^{i G^{\prime} \theta}.\]