-
Notifications
You must be signed in to change notification settings - Fork 61
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
RGD optimization method for quantum state tomography #1485
base: master
Are you sure you want to change the base?
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/QST in 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). |
update random_density_matrix usage Co-authored-by: Renato Mello <renato.msf@gmail.com>
for more information, see https://pre-commit.ci
2 * np.pi * random.random(), | ||
2 * np.pi * random.random(), | ||
2 * np.pi * random.random(), |
There was a problem hiding this comment.
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): |
from qibo import gates, quantum_info | ||
|
||
|
||
class State: |
There was a problem hiding this comment.
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.
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.
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:
qibo/src/qibo/quantum_info/utils.py
Line 118 in 958b11b
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.
class GHZState(State): | ||
""" | ||
Constructor for GHZState class | ||
""" | ||
|
||
def __init__(self, n): | ||
State.__init__(self, n) | ||
self.circuit_name = "GHZ" | ||
|
||
def create_circuit(self): | ||
circuit = qibo.Circuit(self.n) | ||
|
||
circuit.add(gates.H(0)) | ||
for i in range(1, self.n): | ||
circuit.add(gates.CNOT(0, i)) | ||
|
||
self.circuit = circuit |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This fits as a function in qibo.models.encodings
receiving nqubits
as input and returning the corresponding circuit as output.
EDIT: I'd recommend doing this on a separate PR. It keeps things concise and it's good training on how to implement Qibo code in a smaller scale.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just want to clarify. A separate PR you mean that I have to create a new branch, say QST2 or QST_GHZ, and then pre-commit this new branch? Do I understand correctly?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A new branch starting from master
, completely unrelated to this one. You can name whatever, but probably ghz
is simpler and better. Then in that branch, you only implement the GHZ circuit as a function in qibo.models.encodings
and the corresponding tests. Nothing else. After that is merged into master
, we change the code here accordingly so it can communicate with the newly existing function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the instruction. I have created a new branch called ghz and push it with sync fork. I have tested some functions inside the directory src/qibo/test. However, I do not understand well the qibo.models.encodings you mentioned. There are some encoders in the qibo.models.encodings but probably we don't need them at the moment. I am not sure if we can add some user-defined properties in the circuit. The way I did it now is to create a new class. Probably some properties do not fit into qibo's standards. I am curious if creating some list as a recorded processed result is allowed. Or do you prefer on-the-fly calculation during processing? Thanks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think what @renatomello is that the GHZ state should be added as a function to qibo.models.encodings
. It will probably look something like this:
def GHZ_circuit(nqubits):
c = QiboCircuit(nqubits)
c.add(gates.H(0))
for _i in range(nqubits-1):
c.add(gates.CNOT(_i, _i+1))
return c
I don't think it will take so much time to add it in, I can do it after dinner.
Anyway, would qibo.models.utils
be a more suitable location than qibo.models.encodings
?
EDIT: Upon closer inspection, I see that qibo.models.encodings
has a lot of "create circuit" functions. I think qibo.models.encodings
will be the suitable location. :)
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.
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.
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. |
update random_density_matrix usage Co-authored-by: Renato Mello <renato.msf@gmail.com>
for more information, see https://pre-commit.ci
update random_density_matrix usage Co-authored-by: Renato Mello <renato.msf@gmail.com>
for more information, see https://pre-commit.ci
@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? |
simplify generating all labels Co-authored-by: Renato Mello <renato.msf@gmail.com>
for more information, see https://pre-commit.ci
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. |
for more information, see https://pre-commit.ci
|
||
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rho = qu.rand_dm_ginibre(2**n, dims=[[2] * n, [2] * n], rank=Nr) | |
rho = random_density_matrix(2**n, rank=Nr, metric="ginibre") |
import methodsRGD_core | ||
import numpy as np | ||
import projectors | ||
import qutip as qu |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
import qutip as qu |
for more information, see https://pre-commit.ci
for more information, see https://pre-commit.ci
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! |
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