<jupyter_start><jupyter_text>Optimize Measurements by grouping into commuting cliquesQuantum mechanics allows simultaneuous measurements of all mutually commuting operators. VQE however permits only the measurements of a much more restricted set of operators, the qubit-wise commuting operators (Verteletskyi et al., “Measurement Optimization in the Variational Quantum Eigensolver Using a Minimum Clique Cover" [J. Chem. Phys. 152, 124114 (2020)](https://doi.org/10.1063/1.5141458)). But through certain unitary transformations any group of mutually commuting operators can be tranformed into their qubit-wise commuting form (Yen et al., "Measuring all compatible operators in one series of single-qubit measurements using unitary transformations" [Chem. Theory Comput. 2020, 16, 4, 2400–2409](https://doi.org/10.1021/acs.jctc.0c00008)).  How to Use:This is how the technique can be used in tequila. Note that you will only benefit from optimizing measurements when you are running on a real quantum backend or when you simulate with finite samples. The difference in timings is just an indicator that different processes happen, in this specific example we have an Hamiltonian with 4 terms where optimization of the measurements reduces those to 2 commuting groups (see the next section for details)<jupyter_code>import tequila as tq
import time
tq.show_available_simulators()
backend = None

H = tq.paulis.Z([0,1]) + tq.paulis.Y([0,1]) + tq.paulis.X([0,1]) + tq.paulis.X(0) + tq.paulis.Z([0,1,2,3,4,5,6])

U = tq.gates.ExpPauli(angle = "a", paulistring=tq.PauliString.from_string('X(0)Y(1)'))
U += tq.gates.X(target=[0,1,2,3,4,5,6])

E1 = tq.ExpectationValue(H=H, U=U) 
E2 = tq.ExpectationValue(H=H, U=U, optimize_measurements = True)

print(H)
start = time.time()
print(tq.simulate(E1, variables={"a":1.0}, samples=100000, backend=backend))
print("time : {}s".format(time.time()-start))

start = time.time()
print(tq.simulate(E2, variables={"a":1.0}, samples=100000, backend=backend))
print("time : {}s".format(time.time()-start))<jupyter_output>backend         | wfn        | sampling   | noise      | installed 
--------------------------------------------------------------------
qulacs          | True       | True       | True       | True      
qiskit          | True       | True       | True       | True      
cirq            | True       | True       | True       | True      
pyquil          | True       | True       | True       | True      
symbolic        | True       | False      | False      | True      
+1.0000Z(0)Z(1)+1.0000Y(0)Y(1)+1.0000X(0)X(1)+1.0000X(0)+1.0000Z(0)Z(1)Z(2)Z(3)Z(4)Z(5)Z(6)
0.0006999999999999229
time : 1.0035152435302734s
0.00050002337
time : 0.47684693336486816s<jupyter_text>Behind the Scenes<jupyter_code>import tequila as tq
import numpy as np
from tequila.hamiltonian import QubitHamiltonian, paulis
from tequila.grouping.binary_rep import BinaryHamiltonian<jupyter_output><empty_output><jupyter_text>The following examples shows how to partition a given Hamiltonian into commuting parts and how to find the unitary transformation needed to transform the commuting terms into qubit-wise commuting form that is easy to measure. The Hamiltonian is simply $$ H = \sigma_z(0)\sigma_z(1) + \sigma_y(0)\sigma_y(1) + \sigma_x(0)\sigma_x(1) + \sigma_x(0)$$where $\sigma_z(0)\sigma_z(1)$, $\sigma_y(0)\sigma_y(1)$ does not commute with $\sigma_x(0)$, so two separate measurements are needed.<jupyter_code>H = paulis.Z(0) * paulis.Z(1) + paulis.Y(0) * paulis.Y(1) + \
    paulis.X(0) * paulis.X(1) + paulis.X(0)<jupyter_output><empty_output><jupyter_text>Here we use the binary representation of the Hamiltonian for partitioning. The method commuting_groups gets back a list of BinaryHamiltonian whose terms are mutually commuting. Call to_qubit_hamiltonian to visualize.<jupyter_code>binary_H = BinaryHamiltonian.init_from_qubit_hamiltonian(H)
commuting_parts = binary_H.commuting_groups()
print(len(commuting_parts)) # Number of measurements needed
print(commuting_parts[0].to_qubit_hamiltonian())
print(commuting_parts[1].to_qubit_hamiltonian())<jupyter_output>2
+1.0000X(0)X(1)+1.0000X(0)
+1.0000Z(0)Z(1)+1.0000Y(0)Y(1)<jupyter_text>The second group of terms $H_2$ are not currently qubit-wise commuting and cannot be directly measured on current hardware. They require further unitary transformation $U$ to become qubit-wise commuting. The following code identifies two bases (list of BinaryPauliString) that encodes the unitary transformation as$$ U = \prod_i \frac{1}{\sqrt{2}} (\text{old_basis}[i] + \text{new_basis}[i])$$such that $UH_2U$ is qubit-wise commuting.<jupyter_code>qubit_wise_parts, old_basis, new_basis = commuting_parts[1].single_qubit_form()
def display_basis(basis):
    for term in basis:
        print(QubitHamiltonian.from_paulistrings(term.to_pauli_strings()))
print('Old Basis')
display_basis(old_basis)
print('\nNew Basis')
display_basis(new_basis)<jupyter_output>Old Basis
+1.0000Z(0)Z(1)
+1.0000X(0)X(1)

New Basis
+1.0000X(0)
+1.0000Z(1)<jupyter_text>The transfromed term $UH_2U$ is qubit-wise commuting.<jupyter_code>print(qubit_wise_parts.to_qubit_hamiltonian())<jupyter_output>+1.0000X(0)-1.0000X(0)Z(1)<jupyter_text>Get the circuit for the unitary transformation to implement the measurement scheme.The next function illustrates what is happening if expectation values are formed with the `optimize_measurements` keyword meaning it does the same as `tq.ExpectationValue(H=H,U=U, optimize_measurements=True)`<jupyter_code>def optimize_measurements(H, U):
    binary_H = BinaryHamiltonian.init_from_qubit_hamiltonian(H)
    commuting_parts = binary_H.commuting_groups()
    result = tq.Objective()
    for cH in commuting_parts:
        qwc, Um = cH.get_qubit_wise()
        Etmp = tq.ExpectationValue(H=qwc, U=U+Um)
        result += Etmp
    
    return result<jupyter_output><empty_output><jupyter_text>The new measurement scheme produces the same expectation values<jupyter_code>U = tq.gates.ExpPauli(angle = "a", paulistring=tq.PauliString.from_string('X(0)Y(1)'))
E1 = tq.ExpectationValue(H=commuting_parts[1].to_qubit_hamiltonian(), U=U)
E2 = optimize_measurements(H=commuting_parts[1].to_qubit_hamiltonian(), U=U)

variables = {"a" : np.random.rand(1) * 2 * np.pi}
print('Without measurement grouping')
print(tq.simulate(E1, variables))
print('\nWith measurement grouping')
print(tq.simulate(E2, variables))<jupyter_output>Without measurement grouping
1.2040990959786004

With measurement grouping
1.204099<jupyter_text>The get_qubit_wise methods always transforms the hamiltonian into the qubit-wise commuting form with only z on each qubit. This is done via extra single-qubit unitaries.<jupyter_code>print('The qubit-wise commuting hamiltonian, but does not have all z')
print(commuting_parts[0].to_qubit_hamiltonian())

qwc, U = commuting_parts[0].get_qubit_wise()
print('\nThe same qubit-wise commuting hamiltonian with all z')
print(qwc)
print('\nThe corresponding single qubit unitaries')
print(U)<jupyter_output>The qubit-wise commuting hamiltonian, but does not have all z
+1.0000X(0)X(1)+1.0000X(0)

The same qubit-wise commuting hamiltonian with all z
+1.0000Z(0)Z(1)+1.0000Z(0)

The corresponding single qubit unitaries
circuit: 
Rz(target=(0,), parameter=-1.5707963267948966)
Rx(target=(0,), parameter=-1.5707963267948966)
Rz(target=(0,), parameter=-1.5707963267948966)
Rz(target=(1,), parameter=-1.5707963267948966)
Rx(target=(1,), parameter=-1.5707963267948966)
Rz(target=(1,), parameter=-1.5707963267948966)
{'fraction_non_alphanumeric': 0.12373468300479488, 'fraction_numerical': 0.062466702184336705, 'mean_word_length': 5.085089141004862, 'pattern_counts': {'":': 2, '<': 31, '<?xml version=': 0, '>': 31, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 1, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '9', 'dir': 'jupyter-structured-clean-dedup', 'id': '131721', 'license': 'MIT', 'n_tokens_mistral': 2888, 'n_tokens_neox': 2535, 'n_words': 643, 'path': 'tutorials/MeasurementGroups.ipynb', 'repo_name': 'naomicurnow/tequila', 'size': '7508'}