Skip to content

RGD optimization method for quantum state tomography - #1485

Open
HuberyMing wants to merge 33 commits into
qiboteam:masterfrom
HuberyMing:QST
Open

RGD optimization method for quantum state tomography#1485
HuberyMing wants to merge 33 commits into
qiboteam:masterfrom
HuberyMing:QST

Conversation

@HuberyMing

@HuberyMing HuberyMing commented Oct 11, 2024

Copy link
Copy Markdown

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

renatomello commented Oct 14, 2024

Copy link
Copy Markdown
Contributor

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.

Comment thread src/qibo/tomography_RGD/BasicTools.py Outdated
Comment thread src/qibo/tomography_RGD/Run_Tomo.py Outdated
Comment thread src/qibo/tomography_RGD/Run_Tomo.py Outdated
Comment thread src/qibo/tomography_RGD/qibo_states.py Outdated
@renatomello
renatomello self-requested a review October 16, 2024 10:28
@alecandido

Copy link
Copy Markdown
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 git@github.com: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).

Comment thread src/qibo/tomography_RGD/qibo_states.py Outdated
Comment on lines +218 to +220
2 * np.pi * random.random(),
2 * np.pi * random.random(),
2 * np.pi * random.random(),

Copy link
Copy Markdown
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

Comment thread src/qibo/tomography_RGD/qibo_states.py Outdated
from qibo import gates, quantum_info


class State:

Copy link
Copy Markdown
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 thread src/qibo/tomography_RGD/qibo_states.py Outdated
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

@renatomello renatomello Oct 17, 2024

Copy link
Copy Markdown
Contributor

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 thread src/qibo/tomography_RGD/qibo_states.py Outdated
Comment thread src/qibo/tomography_RGD/projectors.py Outdated
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
Copy Markdown
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

mho291 commented Oct 17, 2024

Copy link
Copy Markdown
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.

@mho291

mho291 commented Oct 17, 2024

Copy link
Copy Markdown
Contributor

@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
Copy Markdown
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
Copy Markdown
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.

@HuberyMing

Copy link
Copy Markdown
Author

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.

Hi @renatomello Thank you for the review. No problem. How do we discuss?

@renatomello

renatomello commented Oct 17, 2024

Copy link
Copy Markdown
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.

Hi @renatomello Thank you for the review. No problem. How do we discuss?

We can try to arrange a chat via Zoom next week. Right now, I'd like to see a separate PR adding the GHZ state to qibo.models.encodings. I think it will be a good lesson on a small scale on how this PR should move forward in terms of code implementation.
You can follow the style of the functions inside qibo.models.encodings as well as the test style inside tests/test_models_encodings.py.

It's a good first issue.

@HuberyMing

HuberyMing commented Oct 17, 2024

Copy link
Copy Markdown
Author

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.

Hi @renatomello Thank you for the review. No problem. How do we discuss?

We can try to arrange a chat via Zoom next week. Right now, I'd like to see a separate PR adding the GHZ state to qibo.models.encodings. I think it will be a good lesson on a small scale on how this PR should move forward in terms of code implementation. You can follow the style of the functions inside qibo.models.encodings as well as the test style inside tests/test_models_encodings.py.

It's a good first issue.

Thanks. I will do these first.

Comment thread src/qibo/tomography_RGD/Run_Tomo.py Outdated

elif circuit_Choice == 2: # directly generate density matrix via qutip
Nr = 1
rho = qu.rand_dm_ginibre(2**n, dims=[[2] * n, [2] * n], rank=Nr)

@renatomello renatomello Oct 17, 2024

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
rho = qu.rand_dm_ginibre(2**n, dims=[[2] * n, [2] * n], rank=Nr)
rho = random_density_matrix(2**n, rank=Nr, metric="ginibre")

Comment thread src/qibo/tomography_RGD/Run_Tomo.py Outdated
@MatteoRobbiati

Copy link
Copy Markdown
Contributor

Do you plan to continue with this PR @HuberyMing? :)

@HuberyMing

Copy link
Copy Markdown
Author

Do you plan to continue with this PR @HuberyMing? :)

Sorry for not pushing forward much. I will continue the PR. Hope to finish it soon.

@MatteoRobbiati

Copy link
Copy Markdown
Contributor

Sorry for not pushing forward much. I will continue the PR. Hope to finish it soon.

No problem! Take your time :) I just wanted to check the status!

@scarrazza scarrazza added this to the Qibo 0.2.16 milestone Jan 20, 2025
@scarrazza scarrazza modified the milestones: Qibo 0.2.16, Qibo 0.2.17 Feb 12, 2025
@HuberyMing

Copy link
Copy Markdown
Author

Since 'NumpyBackend' object has no attribute 'np', do we use 'np' (numpy) directly instead?

@alecandido

Copy link
Copy Markdown
Member

Hi @HuberyMing: in #1711 the backends' mechanism was quite modified, which triggered on its own the release of Qibo v0.3.0.

To a first approximation, the self.np attribute has been replaced by the self.engine.

self.engine = None

Though that is meant to be used mainly internally to the backend. If you are a backend user, then you should directly use the functions exposed by the backend
def einsum(
self, subscripts: str, *operands: List[ArrayLike], **kwargs
) -> ArrayLike:
return self.engine.einsum(subscripts, *operands, **kwargs)

In this case it will be backend.einsum(...).

@HuberyMing

Copy link
Copy Markdown
Author

@scarrazza @mho291 I am ready for the PR. Now files are merged to one file src/qibo/tomography_RGD/RGD_Optimized.py. This is in a temporary working directory where I also put two test files. To finish the PR, in which place do you suggest to put RGD_Optimized.py? It may be renamed as state_tomography_RGD.py if needed.

HuberyMing and others added 4 commits April 15, 2026 08:25
Implement PauliMap and RGDOptimizer classes in src/qibo/tomography/state_tomography.py.
Add comprehensive tests in tests/test_tomography_pauli_map.py and tests/test_tomography_rgd_optimizer.py.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@HuberyMing
HuberyMing marked this pull request as ready for review April 20, 2026 07:09
@HuberyMing
HuberyMing requested review from a team April 20, 2026 07:09
@HuberyMing

Copy link
Copy Markdown
Author

The quantum state tomography via RGD algorithm is now implemented as one file at src/qibo/tomography/state_tomography.py with two classes PauliMap and RGDOptimizer.
The test files are tests/test_tomography_pauli_map.py and tests/test_tomography_rgd_optimizer.py.
Ready for PR. Thanks.

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