Skip to content

Use ParameterVector(uuid=...) in QPY deserialisation#16221

Merged
jakelishman merged 1 commit into
Qiskit:mainfrom
jakelishman:pv-uuid-qpy
May 21, 2026
Merged

Use ParameterVector(uuid=...) in QPY deserialisation#16221
jakelishman merged 1 commit into
Qiskit:mainfrom
jakelishman:pv-uuid-qpy

Conversation

@jakelishman
Copy link
Copy Markdown
Member

When QPY deserialises ParameterVectorElements, it first created a dummy ParameterVectorElement with a random UUID, then mutated an internal Python-space cache of the elements individually, as they were encountered, to reset them to the root_uuid + offset form.

The idea that we can find the root vector during deserialisation is predicated on the assumption that the root_uuid + offset form must hold; if it doesn't, then the attempt to look up a cached version of the vector by looking at the element_uuid - offset would fail, so we'd never return the same vector.

When ParameterVectorElement serialisation was introduced in QPY v31, vectors were still being looked up by name, and the elements had completely disconnected UUIDs, which motivated the original system. This is no longer the case on both counts; it is now much easier simply to generate the vector correctly from the start, and never need to mutate internal private state.

Depends on #16216

AI/LLM disclosure

  • I didn't use LLM tooling, or only used it privately.
  • I used the following tool to help write this PR description:
  • I used the following tool to generate or modify code:

Footnotes

  1. 5c4cd2b: Fix ParameterVector handling in QPY serialization (Fix ParameterVector handling in QPY serialization #7381)

@jakelishman jakelishman added this to the 2.5.0 milestone May 21, 2026
@jakelishman jakelishman added on hold Can not fix yet Changelog: None Do not include in the GitHub Release changelog. mod: qpy Related to QPY serialization labels May 21, 2026
@coveralls
Copy link
Copy Markdown

coveralls commented May 21, 2026

Coverage Report for CI Build 26246719048

Warning

Build has drifted: This PR's base is out of sync with its target branch, so coverage data may include unrelated changes.
Quick fix: rebase this PR. Learn more →

Warning

No base build found for commit 0245ea7 on main.
Coverage changes can't be calculated without a base build.
If a base build is processing, this comment will update automatically when it completes.

Coverage: 87.48%

Details

  • Patch coverage: 12 of 12 lines across 2 files are fully covered (100%).

Uncovered Changes

No uncovered changes found.

Coverage Regressions

Requires a base build to compare against. How to fix this →


Coverage Stats

Coverage Status
Relevant Lines: 123690
Covered Lines: 108204
Line Coverage: 87.48%
Coverage Strength: 961681.57 hits per line

💛 - Coveralls

When QPY deserialises `ParameterVectorElement`s, it first created a
dummy `ParameterVectorElement` with a random UUID, then mutated an
internal Python-space cache of the elements individually, as they were
encountered, to reset them to the `root_uuid + offset` form.

The idea that we _can_ find the root vector during deserialisation is
predicated on the assumption that the `root_uuid + offset` form _must_
hold; if it doesn't, then the attempt to look up a cached version of the
vector by looking at the `element_uuid - offset` would fail, so we'd
never return the same vector.

When `ParameterVectorElement` serialisation was introduced in QPY
v3[^1], vectors were still being looked up by name, and the elements had
completely disconnected UUIDs, which motivated the original system.
This is no longer the case on both counts; it is now much easier simply
to generate the vector correctly from the start, and never need to
mutate internal private state.

[^1]: 5c4cd2b: Fix ParameterVector handling in QPY serialization (Qiskitgh-7381)
@jakelishman jakelishman removed the on hold Can not fix yet label May 21, 2026
@jakelishman jakelishman marked this pull request as ready for review May 21, 2026 18:56
@jakelishman jakelishman requested a review from a team as a code owner May 21, 2026 18:56
@jakelishman jakelishman requested a review from raynelfss May 21, 2026 18:56
@qiskit-bot
Copy link
Copy Markdown
Collaborator

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

  • @Qiskit/terra-core
  • @mtreinish

Comment on lines -1174 to -1175
# vector[1] is not part of the circuit
self.assertTrue(vector[1] != new_vector[1])
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This is removed because it's generated equal even though it's not used. It was never an API contract that unused values must be different - that was an undesirable consequence.

Copy link
Copy Markdown
Contributor

@raynelfss raynelfss left a comment

Choose a reason for hiding this comment

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

I don't get to play with QPY much here but it seems we're just creating ParameterVectors that match the serialized vector's UUID since uuid is now public (see #16216). This means we don't need to hack its first element to make it match the original's uuid.

This all makes sense to me so far. I had one comment about how things are being imported here, but other than that I don't have much to add.

Comment thread crates/qpy/src/params.rs
Comment on lines +552 to +554
py.import("uuid")?
.getattr("UUID")?
.call((), Some(&kwargs))?
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why is the import done this way and not through the imports module with a OnceCell?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Honestly, because it gets deleted in two PRs' time (#16228), and this was just me patching together a functional intermediate state to make review clearer.

@jakelishman
Copy link
Copy Markdown
Member Author

Yeah, this is a simple stepping stone that makes the deserialisation a little faster, and avoids non-public internal-state mutation. The next PR (#16222) clears out a warning that (with this in place) is supporting a long-gone representation, and then the last moves all of ParameterVector to Rust, once all the problematic Python-internal mutation is gone.

Copy link
Copy Markdown
Contributor

@raynelfss raynelfss left a comment

Choose a reason for hiding this comment

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

Thank you for explaining the changes here, this LGTM!

@raynelfss raynelfss added this pull request to the merge queue May 21, 2026
@github-merge-queue github-merge-queue Bot removed this pull request from the merge queue due to failed status checks May 21, 2026
@jakelishman jakelishman added this pull request to the merge queue May 21, 2026
Merged via the queue into Qiskit:main with commit 3767817 May 21, 2026
27 checks passed
@jakelishman jakelishman deleted the pv-uuid-qpy branch May 21, 2026 21:45
@github-project-automation github-project-automation Bot moved this from Ready to Done in Qiskit 2.5 May 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Changelog: None Do not include in the GitHub Release changelog. mod: qpy Related to QPY serialization

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants