-
Notifications
You must be signed in to change notification settings - Fork 66
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
Conversation
…d_garbage_state
…d_garbage_state
…d_garbage_state
To be discussed:
|
To me, |
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 unsubscribe<https://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). |
I am not 100% confident that I have covered everything, but almost everything is done (like 75%-ish). To be investigated:
|
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.
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
"dephasing" in self.noise_types | ||
or "depolarizing" in self.noise_types |
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.
Is this because they are defined with Pauli matrices? Would it work if we defined them with projectors instead?
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 is for this reason indeed. Defining depolarizing and dephasing noise with projectors would work I guess.
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 am adding a TODO here, and creating an issue
@property | ||
def eigenstates(self) -> list[States]: | ||
"""The eigenstates associated with the basis.""" | ||
return EIGENSTATES[self.basis] |
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 is not a bad idea, my only concern is that we never refer to the XY basis states as u
and d
anywhere
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 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.""" |
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.
nit: Order of appearance where?
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.
In STATES ^^'
This is also unnecessary and can be deleted, otherwise an improvement can be:
"""Available states in their order of appearances.""" | |
"""Available states ranked by their energy levels.""" |
pulser-core/pulser/result.py
Outdated
try: | ||
dist = np.random.multinomial(n_samples, self._weights()) | ||
except ValueError: | ||
dist = np.random.multinomial( | ||
n_samples, np.round(self._weights(), 15) | ||
) |
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.
Why was this necessary now?
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 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]: |
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.
Shouldn't it be called eigenstates
instead?
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.
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)
def eigenbasis(self) -> list[States]: | |
def eigenbasis(self) -> list[States]: | |
"""The basis of eigenstates used for simulation.""" |
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() |
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'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) |
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.
You forgot a print here
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 |
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.
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: |
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 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 |
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.
What if we do this:
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) |
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.
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.
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 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 ?
if operator.shape != (2, 2): | ||
raise NotImplementedError( | ||
"Operator's shape must be (2,2) " f"not {operator.shape}." | ||
) |
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.
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 ?
"dephasing" in self.noise_types | ||
or "depolarizing" in self.noise_types |
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 is for this reason indeed. Defining depolarizing and dephasing noise with projectors would work I guess.
@property | ||
def eigenstates(self) -> list[States]: | ||
"""The eigenstates associated with the basis.""" | ||
return EIGENSTATES[self.basis] |
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 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.""" |
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.
In STATES ^^'
This is also unnecessary and can be deleted, otherwise an improvement can be:
"""Available states in their order of appearances.""" | |
"""Available states ranked by their energy levels.""" |
pulser-core/pulser/result.py
Outdated
try: | ||
dist = np.random.multinomial(n_samples, self._weights()) | ||
except ValueError: | ||
dist = np.random.multinomial( | ||
n_samples, np.round(self._weights(), 15) | ||
) |
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 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).
if not isinstance(operations, list): | ||
operations = [operations] |
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.
It was allowed before, I am also thinking of banning it.
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()}" | ||
) |
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 agree.
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". |
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.
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 is something we want to prevent and is now prevented.
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.
The one with "global" is indeed weird
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()}" | ||
) |
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.
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
if np.array( | ||
config.eff_noise_opers[id], dtype=complex | ||
).shape != (self.dim, self.dim): |
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.
@HGSilveri this was done to be compatible with python3.12
I have added all the changes I wanted to add to build_operator. I created a file |
Closing this Pull Request since it was tackled by multiple sub-PRs, the final one being #720 |
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: