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

Fix complex samples #1414

Merged
merged 3 commits into from
Aug 8, 2024
Merged

Fix complex samples #1414

merged 3 commits into from
Aug 8, 2024

Conversation

stavros11
Copy link
Member

This should fix #1412, however I am not sure if it causes any other issues as I don't understand why this cast was introduced and the workflows are failing for other reasons.

Checklist:

  • Reviewers confirm new code works as expected.
  • Tests are passing.
  • Coverage does not decrease.
  • Documentation is updated.

@scarrazza scarrazza mentioned this pull request Aug 7, 2024
4 tasks
src/qibo/result.py Outdated Show resolved Hide resolved
@scarrazza
Copy link
Member

@renatomello do you remember why the cast was introduced? Due to torch?

@alecandido
Copy link
Member

While I agree with the fix, it seems a bit of a patch: in principle, NumpyBackend.cast() is just using np.array(ar, dtype=ar.dtype), so I don't see why a cast to complex should happen...

I should try myself to reproduce, but since you already did it: which was the original object being cast?

@stavros11 stavros11 force-pushed the fix-complex-samples branch from 206db01 to c32fe97 Compare August 7, 2024 19:24
@stavros11
Copy link
Member Author

stavros11 commented Aug 7, 2024

While I agree with the fix, it seems a bit of a patch:

It is a patch, it is attempting to fix the issue but may not necessarily be the best overall solution.

in principle, NumpyBackend.cast() is just using np.array(ar, dtype=ar.dtype), so I don't see why a cast to complex should happen...

I don't think this is correct. If you look at the NumpyBackend.cast:

def cast(self, x, dtype=None, copy=False):
if dtype is None:
dtype = self.dtype
if isinstance(x, self.tensor_types):
return x.astype(dtype, copy=copy)
elif self.is_sparse(x):
return x.astype(dtype, copy=copy)
return np.array(x, dtype=dtype, copy=copy)

if a dtype is not passed it is not maintaining the dtype of the array but rather using self.dtype which is complex64 or complex128 depending on the precision set by the user. QibolabBackend is inheriting all this from NumpyBackend.

I should try myself to reproduce, but since you already did it: which was the original object being cast?

I tried to stop with the debugger right before the cast and the object created by qibolab (in this case gate.result.samples()) has dtype=uint32 which seems right. Therefore, I do not believe the issue is caused by qibolab. It is easy to reproduce, it happens also with the dummy platform which works locally, you don't need the cluster or a real QPU.

I guess it only appears with qibolab because this part of the code is normally not used when simulating. It handles the case where the samples are registered externally, in qibolab's case using what was returned from the instruments. For simulation the samples are generated by the backend (using RNG) so self.measurements[0].result.has_samples() is usually False and thus we fall in the other case of the if statement. However, I am guessing that there are cases even in simulation triggering this line, which is probably why the cast was introduced in the first place, but let's wait for @renatomello to tell us the exact reason.

@alecandido
Copy link
Member

if a dtype is not passed it is not maintaining the dtype of the array but rather using self.dtype which is complex64 or complex128 depending on the precision set by the user. QibolabBackend is inheriting all this from NumpyBackend.

You're right, I just got confused with self, and I naively considered it to be the array. But that's true, self in this context is the backend itself.

At this point, I really wonder why it was introduced...

I tried to stop with the debugger right before the cast and the object created by qibolab (in this case gate.result.samples()) has dtype=uint32 which seems right. Therefore, I do not believe the issue is caused by qibolab. It is easy to reproduce, it happens also with the dummy platform which works locally, you don't need the cluster or a real QPU.

Yes, this was just because of my former confusion. But you should be able to reproduce this even with the NumpyBackend itself, just patching the result to contain the samples.
We may introduce a test working in this way.

However, I am guessing that there are cases even in simulation triggering this line, which is probably why the cast was introduced in the first place, but let's wait for @renatomello to tell us the exact reason.

Yes, if there is a more likely way to incur in this, we should use that for testing as well.

Copy link

codecov bot commented Aug 7, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 99.94%. Comparing base (259380d) to head (c32fe97).

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #1414   +/-   ##
=======================================
  Coverage   99.94%   99.94%           
=======================================
  Files          78       78           
  Lines       11225    11225           
=======================================
  Hits        11219    11219           
  Misses          6        6           
Flag Coverage Δ
unittests 99.94% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@renatomello
Copy link
Contributor

renatomello commented Aug 8, 2024

@renatomello do you remember why the cast was introduced? Due to torch?

Yes, it is due to torch. Without the cast, the MeasurementOutcumes.samples breaks for torch because it returns a list of np.ndarrays and that breaks further torch functions. torch needs it to be a list of torch.Tensors. I do believe that @stavros11's patch is the easiest, most painless way to solve this.

Copy link
Member

@alecandido alecandido left a comment

Choose a reason for hiding this comment

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

Ok, I'm in favor of the patch. Maybe we should add the test I was proposing above, but given the urgency, we may do it even in a different PR.

Yes, it is due to torch. Without the cast, the MeasurementOutcumes.samples breaks for torch because it returns a list of np.ndarrays and that breaks further torch functions. torch needs it to be a list of torch.Tensors. I do believe that @stavros11's patch is the easiest, most painless way to solve this.

Ok, but this problem would be better solved at the level of MeasurementOutcomes itself, which should return a tensor of the given backend (which is then cast to NumPy explicitly, when needed).

I would open an issue to discuss it.

@alecandido
Copy link
Member

Btw, as soon as we'll release v0.2.11 with this patch on PyPI, we should consider yanking v0.2.10, since it's broken for Qibolab and should be avoided (if there is no critical feature in there, we could even yank immediately - but since there are more Qibo users than Qibolab users, maybe it's worth to wait for the new one) @scarrazza

@scarrazza scarrazza merged commit a2f7424 into master Aug 8, 2024
29 checks passed
@alecandido alecandido mentioned this pull request Aug 30, 2024
4 tasks
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.

Complex numbers in samples and frequencies
4 participants