RGD optimization method for quantum state tomography - #1485
Conversation
|
Not entering the other merits of the PR, this would have to be inside the |
|
@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/QSTin this way you would set up a remote named 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). |
| 2 * np.pi * random.random(), | ||
| 2 * np.pi * random.random(), | ||
| 2 * np.pi * random.random(), |
There was a problem hiding this comment.
A tip for the future: This choice of phases does not sample random states uniformly. Please see
and https://pennylane.ai/qml/demos/tutorial_haar_measure| from qibo import gates, quantum_info | ||
|
|
||
|
|
||
| class State: |
There was a problem hiding this comment.
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.
| 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 |
There was a problem hiding this comment.
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:
qibo/src/qibo/quantum_info/utils.py
Line 118 in 958b11b
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.
| 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 |
There was a problem hiding this comment.
@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.
|
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. |
|
@HuberyMing If you're doing the changes locally, you could do |
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).
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 (as you also prepended
) |
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 It's a good first issue. |
Thanks. I will do these first. |
|
|
||
| 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) |
There was a problem hiding this comment.
| rho = qu.rand_dm_ginibre(2**n, dims=[[2] * n, [2] * n], rank=Nr) | |
| rho = random_density_matrix(2**n, rank=Nr, metric="ginibre") |
|
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. |
No problem! Take your time :) I just wanted to check the status! |
for more information, see https://pre-commit.ci
for more information, see https://pre-commit.ci
for more information, see https://pre-commit.ci
for more information, see https://pre-commit.ci
for more information, see https://pre-commit.ci
for more information, see https://pre-commit.ci
|
Since 'NumpyBackend' object has no attribute 'np', do we use 'np' (numpy) directly instead? |
|
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 qibo/src/qibo/backends/abstract.py Line 25 in e8bca30 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 qibo/src/qibo/backends/abstract.py Lines 332 to 335 in e8bca30 In this case it will be backend.einsum(...).
|
|
@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. |
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>
|
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. |

Checklist:
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