Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix measurement #290

Open
wants to merge 3 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions examples/PauliSumOp/pauli_sum_op.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,17 +44,16 @@ def __init__(self):
self.rz0 = tq.RZ(has_params=True, trainable=True)
self.crx0 = tq.CRX(has_params=True, trainable=True)

self.measure = tq.MeasureMultiPauliSum(
self.measure = tq.MeasureMultiQubitPauliSum(
obs_list=[
{'coefficient': [0.2, 0.5]},
{
"wires": [0, 2, 3, 1],
"observables": ["x", "y", "z", "i"],
"coefficient": [1, 0.5, 0.4, 0.3],
},
{
"wires": [0, 2, 3, 1],
"observables": ["x", "x", "z", "i"],
"coefficient": [1, 0.5, 0.4, 0.3],
},
]
)
Expand Down

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this file be removed?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is an empty file in the examples section, I do not really see what function it is accomplishing, so I removed it, but I can put it back if you do not feel comfortable removing it.

Empty file.
57 changes: 57 additions & 0 deletions test/measurement/test_measuremultipletimes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import numpy as np
import torchquantum as tq


def test_non_trivial_pauli_expectation():
class TestCircuit(tq.QuantumModule):
def __init__(self):
super().__init__()

self.meas = tq.measurement.MeasureMultipleTimes([
{'wires': range(2), 'observables': 'xx'},
{'wires': range(2), 'observables': 'yy'},
{'wires': range(2), 'observables': 'zz'},
])

def forward(self, qdev: tq.QuantumDevice):
"""
Prepare and measure the expexctation value of the state
exp(-i pi/8)/sqrt(2) * (cos pi/12,
-i sin pi/12,
-i sin pi/12 * exp(i pi/4),
-i sin pi/12 * exp(i pi/4))
"""
# prepare bell state
tq.h(qdev, 0)
tq.cnot(qdev, [0, 1])

# add some phases
tq.rz(qdev, wires=0, params=np.pi / 4)
tq.rx(qdev, wires=1, params=np.pi / 6)
return self.meas(qdev)

test_circuit = TestCircuit()
qdev = tq.QuantumDevice(bsz=1, n_wires=2) # Batch size 1 for testing

# Run the circuit
meas_results = test_circuit(qdev)[0]

# analytical results for XX, YY, ZZ expval respectively
expval_xx = np.cos(np.pi / 4)
expval_yy = -np.cos(np.pi / 4) * np.cos(np.pi / 6)
expval_zz = np.cos(np.pi / 6)

atol = 1e-6

assert np.isclose(meas_results[0].item(), expval_xx, atol=atol), \
f"Expected {expval_xx}, got {meas_results[0].item()}"
assert np.isclose(meas_results[1].item(), expval_yy, atol=atol), \
f"Expected {expval_yy}, got {meas_results[1].item()}"
assert np.isclose(meas_results[2].item(), expval_zz, atol=atol), \
f"Expected {expval_zz}, got {meas_results[2].item()}"

print("Test passed!")


if __name__ == "__main__":
test_non_trivial_pauli_expectation()
48 changes: 9 additions & 39 deletions torchquantum/measurement/measurements.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
"expval",
"MeasureAll",
"MeasureMultipleTimes",
"MeasureMultiPauliSum",

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we no longer adding this as a function to export?

Copy link
Author

@yannick-couzinie yannick-couzinie Nov 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed the function, because it was not correctly implemented. You can check the example pauli_sum_op.py, changing the coefficients has no effect on the outcome of the measurement (see my first post). The coefficient part of the input is not taken into account.

Thinking about it, I think we could implement this easily by using

class Hamiltonian(object):
"""Hamiltonian class."""
def __init__(self,
coeffs: List[float],
paulis: List[str],
endianness: str = "big",
) -> None:
"""Initialize the Hamiltonian.
Args:
coeffs: The coefficients of the Hamiltonian.
paulis: The operators of the Hamiltonian, described in strings.
endianness: The endianness of the operators. Default is big. Qubit 0 is the most significant bit.
Example:
.. code-block:: python
coeffs = [1.0, 1.0]
paulis = ["ZZ", "ZX"]
hamil = tq.Hamiltonian(coeffs, paulis)
"""

and using the matrix of the hamiltonian.

Alternatively, we could change expval_joint_analytical to not only take the string of observables, but also coefficient info and multiply the coefficients at the point where the kronecker product is taken here:

for op in observable[1:]:
hamiltonian = torch.kron(hamiltonian, pauli_dict[op].to(states.device))

But then we would have to change the other expval functions as well to match this. If you think there is a real use case for this measurement and it is worth changing the expval functions we can do that, otherwise just remove as done here.

"MeasureMultiQubitPauliSum",
"gen_bitstrings",
"measure",
Expand Down Expand Up @@ -367,15 +366,14 @@ def forward(self, qdev: tq.QuantumDevice):

observables = []
for wire in range(qdev.n_wires):
observables.append(tq.I())
observables.append("I")

for wire, observable in zip(layer["wires"], layer["observables"]):
observables[wire] = tq.op_name_dict[observable]()
observables[wire] = observable

res = expval(
res = expval_joint_analytical(
qdev_new,
wires=list(range(qdev.n_wires)),
observables=observables,
observable="".join(observables),
)

if self.v_c_reg_mapping is not None:
Expand All @@ -390,42 +388,13 @@ def forward(self, qdev: tq.QuantumDevice):
res = res[:, perm]
res_all.append(res)

return torch.cat(res_all)

return torch.stack(res_all, dim=-1)

def set_v_c_reg_mapping(self, mapping):
self.v_c_reg_mapping = mapping


class MeasureMultiPauliSum(tq.QuantumModule):
"""
similar to qiskit.opflow PauliSumOp
obs list:
list of dict: example
[{'wires': [0, 2, 3, 1],
'observables': ['x', 'y', 'z', 'i'],
'coefficient': [1, 0.5, 0.4, 0.3]
},
{'wires': [0, 2, 3, 1],
'observables': ['x', 'y', 'z', 'i'],
'coefficient': [1, 0.5, 0.4, 0.3]
},
]
"""

def __init__(self, obs_list, v_c_reg_mapping=None):
super().__init__()
self.obs_list = obs_list
self.v_c_reg_mapping = v_c_reg_mapping
self.measure_multiple_times = MeasureMultipleTimes(
obs_list=obs_list, v_c_reg_mapping=v_c_reg_mapping
)

def forward(self, qdev: tq.QuantumDevice):
res_all = self.measure_multiple_times(qdev).prod(-1)

return res_all.sum(-1)


class MeasureMultiQubitPauliSum(tq.QuantumModule):
"""obs list:
list of dict: example
Expand All @@ -449,8 +418,9 @@ def __init__(self, obs_list, v_c_reg_mapping=None):
)

def forward(self, qdev: tq.QuantumDevice):
res_all = self.measure_multiple_times(qdev).prod(-1)

# returns batch x len(obs_list) object, return sum times coefficient
res_all = self.measure_multiple_times(qdev)

return (res_all * torch.tensor(self.obs_list[0]["coefficient"])).sum(-1)


Expand Down
Loading