Skip to content

RGD optimization method for quantum state tomography#1485

Draft
HuberyMing wants to merge 31 commits intoqiboteam:masterfrom
HuberyMing:QST
Draft

RGD optimization method for quantum state tomography#1485
HuberyMing wants to merge 31 commits intoqiboteam:masterfrom
HuberyMing:QST

Conversation

@HuberyMing
Copy link

@HuberyMing HuberyMing commented Oct 11, 2024

Checklist:

  • Reviewers confirm new code works as expected.
  • Tests are passing.
  • Coverage does not decrease.
  • Documentation is updated.

To include the optimization method of Riemannian Gradient Descent (RGD) algorithm to do the tomography problem. The implementation follows from the arXiv:2210.04717, which was published in Phys. Rev. Lett. 132, 240804

@renatomello
Copy link
Contributor

renatomello commented Oct 14, 2024

Not entering the other merits of the PR, this would have to be inside the tomography module instead of being a separate tomography_RGB module.
Moreover, tests should follow how tests in qibo are done. Right now they're in a folder that is being ignored by gitignore.

@renatomello renatomello self-requested a review October 16, 2024 10:28
@alecandido
Copy link
Member

@renatomello to follow up what I told you about remotes during the Qibo meeting, you could do the following:

git remote add hubery-ming [email protected]:HuberyMing/qibo.git
git pull hubery-ming
git switch -c QST -t hubery-ming/QST

in this way you would set up a remote named hubery-ming, tracking @HuberyMing's repo, and then create a local branch in your local repo named QST, tracking the https://github.com/HuberyMing/qibo/tree/QST branch.

Of course, you won't have pushing rights (unless @HuberyMing will grant you), but that's in case you want to test it locally.

Maybe you were already familiar with this, but this may also be useful for everyone else who'd like to have a look (and it could be a reference to deal for PRs from forks in general).

HuberyMing and others added 2 commits October 17, 2024 14:48
update random_density_matrix usage

Co-authored-by: Renato Mello <[email protected]>
Comment on lines +218 to +220
2 * np.pi * random.random(),
2 * np.pi * random.random(),
2 * np.pi * random.random(),
Copy link
Contributor

Choose a reason for hiding this comment

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

A tip for the future: This choice of phases does not sample random states uniformly. Please see

def uniform_sampling_U3(ngates: int, seed=None, backend=None):
and https://pennylane.ai/qml/demos/tutorial_haar_measure

from qibo import gates, quantum_info


class State:
Copy link
Contributor

Choose a reason for hiding this comment

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

Correct me if I'm wrong, but it seems like this base class is unnecessary since every method is dependent on a qibo.models.Circuit method. I'd say that if these state classes are needed at all (which I'm not convinced of), you could use Circuit as the base class directly.

Comment on lines +174 to +189
class HadamardState(State):
"""
Constructor for HadamardState class
"""

def __init__(self, n):
State.__init__(self, n)
self.circuit_name = "Hadamard"

def create_circuit(self):
circuit = qibo.Circuit(self.n)

for i in range(self.n):
circuit.add(gates.H(i))

self.circuit = circuit
Copy link
Contributor

@renatomello renatomello Oct 17, 2024

Choose a reason for hiding this comment

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

Since this is just a layer of Hadamards, this is a Hadamard transform of the initial state. A more concise way to implement this would be to modify the following function:

def hadamard_transform(array, implementation: str = "fast", backend=None):

For instance, if the input is a circuit, then a layer of Hadamard is added. That would perform the Hadamard transform of whatever state one wants, including the zero state in your case.

Comment on lines +110 to +168
def build_projector_naive(label, label_format="big_endian"):
"""to directly generate a Pauli matrix from tensor products

Args:
label (str): label of the projection, e.g. 'XXZYZ'
label_format (str, optional): the ordering of the label. Defaults to 'big_endian'.

Raises:
Exception: when the matrix size is too big (i.e. for qubit number > 6)

Returns:
ndarray: a matrix representing the Pauli operator
"""
if label_format == "little_endian":
label = label[::-1]
if len(label) > 6:
raise Exception("Too big matrix to generate!")
projector = reduce(
lambda acc, item: np.kron(acc, item),
[matrix_dict[letter] for letter in label],
[1],
)
return projector


# Generate a projector by computing non-zero coordinates and their values in the matrix, aka the "fast" implementation
def build_projector_fast(label, label_format="big_endian"):
"""to fastly generate a Pauli projection matrix in sparse matrix format

Args:
label (str): label of the projection, e.g. 'XXZYZ'
label_format (str, optional): the ordering of the label. Defaults to 'big_endian'.

Returns:
sparse matrix: sparse matrix of the Pauli operator representing label
"""
if label_format == "little_endian":
label = label[::-1]

n = len(label)
d = 2**n

# map's result NOT subscriptable in py3, just tried map() -> list(map()) for py2 to py3
ij = [
list(map(binarize, y))
for y in [zip(*x) for x in product(*[ij_dict[letter] for letter in label])]
]
values = [
reduce(lambda z, w: z * w, y)
for y in [x for x in product(*[values_dict[letter] for letter in label])]
]
ijv = list(map(lambda x: (x[0][0], x[0][1], x[1]), zip(ij, values)))

i_coords, j_coords, entries = zip(*ijv)

projector = sparse.coo_matrix(
(entries, (i_coords, j_coords)), shape=(d, d), dtype=complex
)
return projector
Copy link
Contributor

Choose a reason for hiding this comment

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

@BrunoLiegiBastonLiegi this is a case where it would be convenient to have the quantum_info.basis.pauli_basis function return a generator because then one could use that to directly access a specific element of the basis.

@mho291
Copy link
Contributor

mho291 commented Oct 17, 2024

Just curious, would it be helpful to have a call to understand how the algorithm works? It might make it easier to review the code.

@mho291
Copy link
Contributor

mho291 commented Oct 17, 2024

@HuberyMing If you're doing the changes locally, you could do pip install pre-commit followed by pre-commit install inside your environment. Each time you do git commit -m "message", the pre-commit will run. If anything fails, you have to do git commit again before git push. Then we we will avoid having extra commits in this PR.

@alecandido
Copy link
Member

Each time you do git commit -m "message", the pre-commit will run. If anything fails, you have to do git commit again before git push.

Specifically, if it fails the involved hook is "declaring" a failure, that may imply that you have to fix something manually. However, most of the hooks we have registered are also auto-fixing the issues they find (e.g. the formatter fails if the result differs from the original - but if it fails, it also reformats the fails).
The auto-fixes are not staged, so you also need to git add the fixed files.

image

However, in these cases, I'm sure that @HuberyMing just applied the suggestions from the review from the PR web interface. In which case there is no chance of having pre-commit running anywhere else...

(as you also prepended

@HuberyMing If you're doing the changes locally,

)

@renatomello
Copy link
Contributor

Just curious, would it be helpful to have a call to understand how the algorithm works? It might make it easier to review the code.

It might me needed, yes. Let me read the paper first though, so I have a better understanding.

@scarrazza scarrazza modified the milestones: Qibo 0.2.19, Qibo 0.2.20 Jun 18, 2025
@HuberyMing
Copy link
Author

To have better compatibility with qibo, now I have changed the expectation value calculation of each Pauli string from the shot measurements by using the package expectation_from_samples. Here is the imported package:

from qibochem.measurement import expectation, expectation_from_samples

However, not all Pauli string can be correctly calculated for its expectation value from the shot measurement from this package. I have raised an issue in #1677

@renatomello
Copy link
Contributor

renatomello commented Jun 20, 2025

To have better compatibility with qibo, now I have changed the expectation value calculation of each Pauli string from the shot measurements by using the package expectation_from_samples. Here is the imported package:

from qibochem.measurement import expectation, expectation_from_samples

However, not all Pauli string can be correctly calculated for its expectation value from the shot measurement from this package. I have raised an issue in #1677

This creates a circular dependency (qibochem depends on qibo that depends on qibochem) that cannot happen. If the function is general and important enough, it should be implemented directly in qibo and imported by qibochem.

On the other hand, maybe this is not true for the SymbolicHamiltonian class that I see you are using, but the Hamiltonian class already has two methods called expectation_from_samples and expectation_from_circuit. Couldn't they be used instead? If not, couldn't they be generalized to fit your needs?

@HuberyMing
Copy link
Author

HuberyMing commented Jun 25, 2025

To have better compatibility with qibo, now I have changed the expectation value calculation of each Pauli string from the shot measurements by using the package expectation_from_samples. Here is the imported package:
from qibochem.measurement import expectation, expectation_from_samples
However, not all Pauli string can be correctly calculated for its expectation value from the shot measurement from this package. I have raised an issue in #1677

This creates a circular dependency (qibochem depends on qibo that depends on qibochem) that cannot happen. If the function is general and important enough, it should be implemented directly in qibo and imported by qibochem.

On the other hand, maybe this is not true for the SymbolicHamiltonian class that I see you are using, but the Hamiltonian class already has two methods called expectation_from_samples and expectation_from_circuit. Couldn't they be used instead? If not, couldn't they be generalized to fit your needs?

Thanks for the comment. Inspired by your comment, I tried something like

from qibo.hamiltonians.hamiltonians import AbstractHamiltonian
from qibo.hamiltonians import Hamiltonian

coef_Pauli_exact = AbstractHamiltonian.expectation(stateGHZ, symbolPauli)    # NotImplementedError: None
coef_Pauli_exact = Hamiltonian.expectation(stateGHZ, symbolPauli)           
coef_Pauli = symbolPauli.expectation_from_samples(frequencies, qubit_map=range(n_qubits)) 

I found none of them work. Basically they are not implemented. However, I figured out there is no quick way to do this. We can get the correct results by using pauli_term_measurement_expectation such as

from qibochem.measurement.result import pauli_term_measurement_expectation

circuit.add(gates.M(0, basis=type(X(0).gate))) # H gate     # for 'X' Pauli term
circuit.add(gates.M(1, basis=type(Y(1).gate))) # RX(0.5*pi) gate
circuit.add(gates.M(2, basis=type(Z(2).gate))) # Computational basis remains unchanged

# Now run the circuit to get the circuit measurements
result = circuit(nshots=10000)
frequencies = result.frequencies(binary=True)
# pauli_term_measurement_expectation is a Qibochem function for calculating the expectation value of Hamiltonians with non-Z terms
shots_term1 = pauli_term_measurement_expectation(term1.terms[0], frequencies, qubit_map=range(n_qubits))

The conclusion is that pauli_term_measurement_expectation works but expectation_from_samples does not. At least this can fit into my need. Thanks.

@HuberyMing
Copy link
Author

To have some updates. The issue #1677 has already solved this problem of getting the correct expectation values by using the SymbolicHamiltonian.expectation_from_circuit function.

@scarrazza scarrazza modified the milestones: Qibo 0.2.20, Qibo 0.2.21 Jul 21, 2025
@scarrazza scarrazza modified the milestones: Qibo 0.2.21, Qibo 0.2.22 Sep 17, 2025
@scarrazza scarrazza modified the milestones: Qibo 0.2.22, Qibo 0.2.23 Nov 7, 2025
@scarrazza scarrazza modified the milestones: Qibo 0.2.23, Qibo 0.2.24 Dec 4, 2025
@scarrazza
Copy link
Member

@HuberyMing what are your plans concerning this PR?

@HuberyMing
Copy link
Author

@HuberyMing what are your plans concerning this PR?

@scarrazza Thank you for your patience regarding this PR. I am currently refactoring the code to remove unnecessary parts and better align it with the qibo framework. After the Lunar New Year, I will devote more time to finalizing it, with the goal of completing the PR within one to two months.

@scarrazza scarrazza modified the milestones: Qibo 0.3.0, Qibo 0.3.1 Mar 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants