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

Have BlueprintCircuit.copy_empty_like return an empty QuantumCircuit #13782

Merged
merged 2 commits into from
Feb 4, 2025

Conversation

Cryoris
Copy link
Contributor

@Cryoris Cryoris commented Feb 3, 2025

Summary

Fixes #13535.

Details and comments

The BlueprintCircuit.copy_empty_like method currently returns another instance of a BlueprintCircuit with it's definition wiped. That means that the definition would just be re-built if any circuit method was called, and the circuit wasn't really empty. As discussed in #13535 fixes this by returning a QuantumCircuit type, not a BlueprintCircuit anymore.

@Cryoris Cryoris added stable backport potential The bug might be minimal and/or import enough to be port to stable Changelog: Bugfix Include in the "Fixed" section of the changelog labels Feb 3, 2025
@Cryoris Cryoris added this to the 2.0.0 milestone Feb 3, 2025
@Cryoris Cryoris requested a review from a team as a code owner February 3, 2025 15:57
@qiskit-bot
Copy link
Collaborator

One or more of the following people are relevant to this code:

  • @Cryoris
  • @Qiskit/terra-core
  • @ajavadia

Copy link
Member

@jakelishman jakelishman left a comment

Choose a reason for hiding this comment

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

thanks, I hate it (lol)

This definitely does seem to be the right way to go - it's a very awkward interaction with a method called copy_empty_like and the BlueprintCircuit, because you could argue that you'd expect to receive an unbuilt object of the same class, but in practice, I think (almost) nobody actually intends to use the BlueprintCircuit for the blueprint-y stuff, it just gets used because of an implementation detail of the circuit library.

Regardless, 2.0 is probably the right time to make that change.

Comment on lines 217 to 226
circuit = EfficientSU2(2)
circuit.global_phase = -2
circuit.metadata = {"my_legacy": "i was a blueprintcircuit once"}

cpy = circuit.copy_empty_like()
expected = QuantumCircuit(
circuit.num_qubits, global_phase=circuit.global_phase, metadata=circuit.metadata
)
self.assertEqual(cpy, expected)
self.assertNotIsInstance(cpy, BlueprintCircuit)
Copy link
Member

Choose a reason for hiding this comment

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

I don't think the metadata is included in the equality check, so I don't think we'll actually be testing that.

I think we need to trigger the build of circuit and do something that would trigger the build of cpy (if it were a BlueprintCircuit) as well like adding a single gate to it - the not isinstance check is ok, but it's a round-about way of testing the behaviour we actually want, which is "the circuit should actually be empty".

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The assertEqual check should trigger the build -- with the old version of the copy_empty_like that test failed 🙂 The final instance check was just to be very clear it is no longer a blueprint circuit 😛 I can add a method call that would trigger the build explicitly, if it were a blueprint circuit

Copy link
Member

Choose a reason for hiding this comment

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

(I answered the other chain before I read this one.)

Ok, if assertEqual did actually trigger the build, then I guess it is testing it. I guess my now-implied point is that it wasn't obvious to me that assertEqual would necessarily have triggered the build, so it wasn't 100% clear to me that the test was correct, and having that certainty is a nice property of tests.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll add a bit to ensure it would trigger the building and add a comment 🙂

Comment on lines 233 to 238
cpy = circuit.copy_empty_like()

circuit.num_qubits = 3 # change the original circuit

expected = QuantumCircuit(2) # we still expect an empty 2-qubit circuit
self.assertEqual(cpy, expected)
Copy link
Member

Choose a reason for hiding this comment

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

It's good to test mutating circuit to ensure no modifications sneak through in shared state, but it's the _build method getting called on cpy which would be the full test, I think?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There should be no _build on cpy since that's just a plain QuantumCircuit 🙂

Copy link
Member

Choose a reason for hiding this comment

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

Yeah I know, because you've fixed it, but the point is that this test doesn't necessarily test the regression behaviour, because there's no code in here that super obviously would have caused a build if your PR didn't actually fix the problem. __eq__ might do it accidentally, but you could easily imagine an optimisation in BlueprintCircuit that would cause it not to.

Might be good to do something to cpy which is API guaranteed to cause a build if it were a BlueprintCircuit before testing for emptiness.

@jakelishman jakelishman removed the stable backport potential The bug might be minimal and/or import enough to be port to stable label Feb 3, 2025
@jakelishman
Copy link
Member

I've untagged the "stable backport potential" for now, unless there's a pressing reason to really want to backport this, because it does feel like changing one technically valid interpretation of copy_empty_like into another. We're calling it a "bugfix", but it could be considered an API break, so unless it's critical to backport (which I doubt, since I think this has been around for ever), I think the backport risk is greater than the benefit.

@Cryoris
Copy link
Contributor Author

Cryoris commented Feb 3, 2025

This definitely does seem to be the right way to go - it's a very awkward interaction with a method called copy_empty_like and the BlueprintCircuit, because you could argue that you'd expect to receive an unbuilt object of the same class, but in practice, I think (almost) nobody actually intends to use the BlueprintCircuit for the blueprint-y stuff, it just gets used because of an implementation detail of the circuit library.

Yep, the copy_empty_like is very confusing here -- I'd also think it works correctly now, it's just very unintuitive and very unexpected if one is expecting a plain circuit 😅 But I also don't care so much since we'll deprecate the blueprint anyways...

I've untagged the "stable backport potential" for now, unless there's a pressing reason to really want to backport this, because it does feel like changing one technically valid interpretation of copy_empty_like into another. We're calling it a "bugfix", but it could be considered an API break, so unless it's critical to backport (which I doubt, since I think this has been around for ever), I think the backport risk is greater than the benefit.

Sounds good, it does feel more like an API change than a bugfix tbh -- if we don't backport it, I'd be happy to just label it that.

@Cryoris Cryoris added Changelog: API Change Include in the "Changed" section of the changelog and removed Changelog: Bugfix Include in the "Fixed" section of the changelog labels Feb 4, 2025
@Cryoris
Copy link
Contributor Author

Cryoris commented Feb 4, 2025

I added explicit triggers to the building process (with comments) and changed to API Upgrade in c3bd386 🙂

@coveralls
Copy link

Pull Request Test Coverage Report for Build 13133425249

Warning: This coverage report may be inaccurate.

This pull request's base commit is no longer the HEAD commit of its target branch. This means it includes changes from outside the original pull request, including, potentially, unrelated coverage changes.

Details

  • 14 of 14 (100.0%) changed or added relevant lines in 1 file are covered.
  • 10 unchanged lines in 3 files lost coverage.
  • Overall coverage decreased (-0.005%) to 88.797%

Files with Coverage Reduction New Missed Lines %
crates/accelerate/src/two_qubit_decompose.rs 1 92.08%
crates/qasm2/src/lex.rs 3 91.73%
crates/qasm2/src/parse.rs 6 96.22%
Totals Coverage Status
Change from base Build 13115148220: -0.005%
Covered Lines: 79746
Relevant Lines: 89807

💛 - Coveralls

@jakelishman jakelishman self-assigned this Feb 4, 2025
Copy link
Member

@jakelishman jakelishman left a comment

Choose a reason for hiding this comment

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

Solid, this looks much more obvious to me now, thanks!

@jakelishman jakelishman enabled auto-merge February 4, 2025 11:36
@jakelishman jakelishman added this pull request to the merge queue Feb 4, 2025
Merged via the queue into Qiskit:main with commit a3876f6 Feb 4, 2025
18 checks passed
@Cryoris Cryoris deleted the fix-blueprint-copy-empty branch February 4, 2025 12:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Changelog: API Change Include in the "Changed" section of the changelog
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Unexpected behavior of copy_empty_like() for BlueprintCircuits
4 participants