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

[Draft] Add error state simulation #671

Closed
wants to merge 40 commits into from
Closed

[Draft] Add error state simulation #671

wants to merge 40 commits into from

Conversation

a-corni
Copy link
Collaborator

@a-corni a-corni commented Apr 3, 2024

After meeting with @HGSilveri and discussion with @LucasGitQ, it is suggested to drop the RydbergError as a Channel and rather implement it in SimConfig/NoiseModel:

  • Associate a basis of two eigenvectors to each Channel via a property, and use it to clarify the eigenbasis associated to a SequenceSamples.
  • Refactor the definition of basis and op_matrix from SequenceSamples.eigenbasis
  • Add a RydbergError in SimConfig.
  • Enable the definition of collapse operators in the case of a qutrit states.
  • Test that all previous CI tests still passes (the changes are non destructive)
  • Develop CI tests for the leakage noise
  • Add the new basis in the convention notebook
  • Modify the notebook on effective noise to show how to define noise on a qutrit/qudit basis
  • If RydbergError noise is defined in SimConfig, modify the implementation of SPAM such that it goes from replacing some operators by identity in the hamiltonian to prepare the initial density matrix in the state (1-epsilon)|g><g| + epsilon |x><x|
  • Enable the use of build_operator outside Hamiltonian.

@a-corni a-corni changed the title Add error state simulation [Draft] Add error state simulation Apr 3, 2024
@a-corni a-corni marked this pull request as draft April 3, 2024 16:12
@a-corni
Copy link
Collaborator Author

a-corni commented Apr 4, 2024

To be discussed:

  • Should the keyword to enable noise in error state in SimConfig/NoiseModel be err_state or err_state_ising ?
  • Is it really useful to have the properties eigenbasis/eigenstates in Channels/Device/Sequence ? I think it is useful in SequenceSamples however.
  • I am enabling the error state simulations only if err_state noise is defined and effective noise is defined with an effective noise operator.

@a-corni a-corni requested a review from HGSilveri April 5, 2024 07:20
@HGSilveri
Copy link
Collaborator

  • Should the keyword to enable noise in error state in SimConfig/NoiseModel be err_state or err_state_ising ?

To me, leakage would be the most adequate term.

@LucasGitQ
Copy link
Collaborator

LucasGitQ commented Apr 8, 2024 via email

@a-corni
Copy link
Collaborator Author

a-corni commented Apr 8, 2024

For me it depends if it enables all noise or just the Rydberg decay.

________________________________ De : Henrique Silvério @.> Envoyé : vendredi 5 avril 2024 13:59 À : pasqal-io/Pulser @.> Cc : Lucas Leclerc @.>; Mention @.> Objet : Re: [pasqal-io/Pulser] [Draft] Add error state simulation (PR #671) * Should the keyword to enable noise in error state in SimConfig/NoiseModel be err_state or err_state_ising ? To me, leakage would be the most adequate term. — Reply to this email directly, view it on GitHub<#671 (comment)>, or unsubscribehttps://github.com/notifications/unsubscribe-auth/ARDWYZFRNDGKFYXJAV4ATPLY32G2BAVCNFSM6AAAAABFVT6P3GVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDAMZZGYYTQNZXGY. You are receiving this because you were mentioned.Message ID: @.***>

This is a good question, in principle this extra state can be defined and used for noise simulation in whatever basis we are working in (XY/Ising).

@a-corni a-corni marked this pull request as ready for review April 25, 2024 10:24
@a-corni
Copy link
Collaborator Author

a-corni commented Apr 25, 2024

I am not 100% confident that I have covered everything, but almost everything is done (like 75%-ish). To be investigated:

  • The method get_state in Result with the option to reduce the state in a basis.
  • Enable leakage for XY mode (I think it's just have to be allowed).
  • Using leakage for simulation if only SPAM is asked (I think this should be done in a separate PR, this one is already too big).

Copy link
Collaborator

@HGSilveri HGSilveri left a comment

Choose a reason for hiding this comment

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

It's looking quite good already! I still haven't checked out the tests but I already have a lot of comments so I'll stop for now

Comment on lines 209 to 210
"dephasing" in self.noise_types
or "depolarizing" in self.noise_types
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is this because they are defined with Pauli matrices? Would it work if we defined them with projectors instead?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is for this reason indeed. Defining depolarizing and dephasing noise with projectors would work I guess.

Copy link
Collaborator Author

@a-corni a-corni Jun 6, 2024

Choose a reason for hiding this comment

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

I am adding a TODO here, and creating an issue

Comment on lines +103 to +106
@property
def eigenstates(self) -> list[States]:
"""The eigenstates associated with the basis."""
return EIGENSTATES[self.basis]
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is not a bad idea, my only concern is that we never refer to the XY basis states as u and d anywhere

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I don't think it's necessary for this PR, I agree "u" and "d" are not introduced anywhere but I thought they could be introduced in the convention notebook https://pulser.readthedocs.io/en/stable/conventions.html

If you don't think it adds any value to Pulser/it will confuse people as one information too many, I will delete it.

@@ -250,6 +250,14 @@ def supported_bases(self) -> set[str]:
"""Available electronic transitions for control and measurement."""
return {ch.basis for ch in self.channel_objects}

@property
def supported_states(self) -> list[States]:
"""Available states in their order of appearances."""
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: Order of appearance where?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

In STATES ^^'
This is also unnecessary and can be deleted, otherwise an improvement can be:

Suggested change
"""Available states in their order of appearances."""
"""Available states ranked by their energy levels."""

Comment on lines 82 to 87
try:
dist = np.random.multinomial(n_samples, self._weights())
except ValueError:
dist = np.random.multinomial(
n_samples, np.round(self._weights(), 15)
)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why was this necessary now?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I must say I did not fully understand why this arised in the tests (and in a former test). Now it seems natural that this should be rounded to the epsilon machine (I had a look to the values, it's sort of a miracle it never occured before).

@@ -423,6 +428,16 @@ def used_bases(self) -> set[str]:
if not ch_samples.is_empty()
}

@property
def eigenbasis(self) -> list[States]:
Copy link
Collaborator

Choose a reason for hiding this comment

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

Shouldn't it be called eigenstates instead?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

So to me, eigenstates refer to a list of states, but some can be not used (as in the Sequence) whereas the eigenbasis really refers to the basis of eigenstates used in the simulation (so no eigenstates can be unused)

Suggested change
def eigenbasis(self) -> list[States]:
def eigenbasis(self) -> list[States]:
"""The basis of eigenstates used for simulation."""

Comment on lines +220 to +226
if state_n not in [0, 1]:
raise ValueError("state_n must be 0 or 1.")
if self._basis_name in "ground-rydberg":
# 0 = |g>; 1 = |r>
return qutip.basis(2, 1 - state_n).proj()
return qutip.basis(self._dim, 1 - state_n).proj()

return qutip.basis(2, state_n).proj()
return qutip.basis(self._dim, state_n).proj()
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm not sure this change should happen. The measurement projector is always of size 2 because we only have two possible measurement outcomes.

if meas_basis not in {"ground-rydberg", "digital"}:
raise ValueError(
"`meas_basis` must be 'ground-rydberg' or 'digital'."
)
else:
if meas_basis != self._basis_name:
if meas_basis not in self._basis_name:
print(meas_basis, self._basis_name)
Copy link
Collaborator

Choose a reason for hiding this comment

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

You forgot a print here

Comment on lines +516 to +519
dim = len(SIM_BASIS[self._basis_name])
return (
qutip.basis(2, good).proj() * (1 - err_param)
+ qutip.basis(2, 1 - good).proj() * err_param
qutip.basis(dim, good).proj() * (1 - err_param)
+ qutip.basis(dim, 1 - good).proj() * err_param
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same here, I don't think this dimension should change

if meas_basis not in {"ground-rydberg", "digital"}:
raise ValueError(
"`meas_basis` must be 'ground-rydberg' or 'digital'."
)
else:
if meas_basis != self._basis_name:
if meas_basis not in self._basis_name:
Copy link
Collaborator

Choose a reason for hiding this comment

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

This allows meas_basis="dig", for example

@@ -171,13 +272,24 @@ def set_config(self, cfg: NoiseModel) -> None:
f"Interaction mode '{self._interaction}' does not support "
f"simulation of noise types: {', '.join(not_supported)}."
)
if not hasattr(self, "basis_name"):
with_leakage = "leakage" in cfg.noise_types
Copy link
Collaborator

Choose a reason for hiding this comment

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

What if we do this:

Suggested change
with_leakage = "leakage" in cfg.noise_types
no_resampling = set(cfg.noise_types).issubset(
{ # These should go in a CONSTANT
"dephasing",
"relaxation",
"SPAM",
"depolarizing",
"eff_noise",
"leakage",
})
# Use leakage to emulate state prep errors when resampling the hamiltonian is not needed
with_leakage = "leakage" in cfg.noise_types or ("SPAM" in cfg.noise_types and cfg.state_prep_error > 0 and no_resampling)

Copy link
Collaborator

Choose a reason for hiding this comment

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

Actually, I think it has to be even more involved than this. When the user explicitly adds leakage error, they might define effective noise operators that allow the population of the x state to go back to one of the other basis states, which would make x unsuitable for emulating the SP errors.
We'll want to distinguish with_leakage when it's user selected and when we activate ourselves to use for SP errors.

Copy link
Collaborator Author

@a-corni a-corni left a comment

Choose a reason for hiding this comment

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

I have reviewed a first part of your reviews down to build_operator and have made some changes on this function. We can iterate over these first comments to begin with before tackling the more complex ones ?

Comment on lines 197 to 200
if operator.shape != (2, 2):
raise NotImplementedError(
"Operator's shape must be (2,2) " f"not {operator.shape}."
)
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The shape of the operator must now be consistent with the number of dimensions you are using in your simulation: (2, 2), (3, 3) or (4, 4). This is being checked when defining a Hamiltonian in QutipEmulator. If you feel it's important to keep a test like this here, what I could do is check that operator.shape is in [(2, 2), (3, 3)] if there is no leakage, and in [(3, 3), (4,4)] if there is leakage ?

Comment on lines 209 to 210
"dephasing" in self.noise_types
or "depolarizing" in self.noise_types
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is for this reason indeed. Defining depolarizing and dephasing noise with projectors would work I guess.

Comment on lines +103 to +106
@property
def eigenstates(self) -> list[States]:
"""The eigenstates associated with the basis."""
return EIGENSTATES[self.basis]
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I don't think it's necessary for this PR, I agree "u" and "d" are not introduced anywhere but I thought they could be introduced in the convention notebook https://pulser.readthedocs.io/en/stable/conventions.html

If you don't think it adds any value to Pulser/it will confuse people as one information too many, I will delete it.

@@ -250,6 +250,14 @@ def supported_bases(self) -> set[str]:
"""Available electronic transitions for control and measurement."""
return {ch.basis for ch in self.channel_objects}

@property
def supported_states(self) -> list[States]:
"""Available states in their order of appearances."""
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

In STATES ^^'
This is also unnecessary and can be deleted, otherwise an improvement can be:

Suggested change
"""Available states in their order of appearances."""
"""Available states ranked by their energy levels."""

Comment on lines 82 to 87
try:
dist = np.random.multinomial(n_samples, self._weights())
except ValueError:
dist = np.random.multinomial(
n_samples, np.round(self._weights(), 15)
)
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I must say I did not fully understand why this arised in the tests (and in a former test). Now it seems natural that this should be rounded to the epsilon machine (I had a look to the values, it's sort of a miracle it never occured before).

Comment on lines 95 to 96
if not isinstance(operations, list):
operations = [operations]
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It was allowed before, I am also thinking of banning it.

Comment on lines 111 to 117
qids_set = set(qids)
if len(qids_set) < len(qids):
raise ValueError("Duplicate atom ids in argument list.")
if not qids_set.issubset(qubits.keys()):
raise ValueError(
"Invalid qubit names: " f"{qids_set - qubits.keys()}"
)
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I agree.

Comment on lines 68 to 70
op_matrix: A dict of operators and their labels. If None, it is
composed of all the projectors on the computational basis
(with error state if with_leakage is True) and "I".
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Is this something we want to prevent ?
image
To really have an id you have to define qutip.qeye of correct shape or provide a qubit to target
image

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is something we want to prevent and is now prevented.

Copy link
Collaborator

Choose a reason for hiding this comment

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

The one with "global" is indeed weird

Comment on lines 111 to 117
qids_set = set(qids)
if len(qids_set) < len(qids):
raise ValueError("Duplicate atom ids in argument list.")
if not qids_set.issubset(qubits.keys()):
raise ValueError(
"Invalid qubit names: " f"{qids_set - qubits.keys()}"
)
Copy link
Collaborator Author

@a-corni a-corni Jun 5, 2024

Choose a reason for hiding this comment

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

pulser_simulation.build_operator(sampled_seq, seq.register.qubits, [("sigma_rr", ["q0"]), ("sigma_xx", ["q0"])], with_leakage=True) == pulser_simulation.build_operator(sampled_seq, seq.register.qubits, [("sigma_xx", ["q0"])], with_leakage=True) is True used to be True and is no longer

Comment on lines +279 to +281
if np.array(
config.eff_noise_opers[id], dtype=complex
).shape != (self.dim, self.dim):
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@HGSilveri this was done to be compatible with python3.12

@a-corni
Copy link
Collaborator Author

a-corni commented Jun 7, 2024

I have added all the changes I wanted to add to build_operator. I created a file operator.py to store the functions that enables the user to define easily projectors/combinations of projectors on 1 qutrit. I am not a big fan of the function build_1qubit_operator (nor its name), You tell me if it's very useful.

@a-corni
Copy link
Collaborator Author

a-corni commented Aug 13, 2024

Closing this Pull Request since it was tackled by multiple sub-PRs, the final one being #720

@a-corni a-corni closed this Aug 13, 2024
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.

3 participants