Skip to content

Fix dropped state collapse in FreeQuantumState.measure_multiple#355

Open
HasanTasdiq wants to merge 12 commits into
sequence-toolbox:RnDfrom
HasanTasdiq:fix/measure-multiple-state-collapse
Open

Fix dropped state collapse in FreeQuantumState.measure_multiple#355
HasanTasdiq wants to merge 12 commits into
sequence-toolbox:RnDfrom
HasanTasdiq:fix/measure-multiple-state-collapse

Conversation

@HasanTasdiq
Copy link
Copy Markdown

Summary

FreeQuantumState.measure_multiple (sequence/kernel/quantum_state.py) computes the post-measurement state correctly but then assigns it to attributes that do not exist on FreeQuantumState:

new_state = new_states[res]
for state in entangled_list:
    state.quantum_state = new_state          # FreeQuantumState has no `quantum_state`
    state.entangled_photons = entangled_list # FreeQuantumState has no `entangled_photons`
return res

FreeQuantumState stores its vector in self.state and its entangled partners in self.entangled_states (see the single-qubit measure method, which does s.state = new_state). Writing to quantum_state / entangled_photons silently creates unused attributes and leaves the real state uncollapsed: after measure_multiple, the entangled FreeQuantumState objects keep their pre-measurement superposition.

Demonstration

Two qubits each in |+>, combined, then measured in the computational basis:

res = FreeQuantumState.measure_multiple(basis, [a, b], rng)   # e.g. res == 2  (=> |10>)
a.state   # buggy: still (0.5, 0.5, 0.5, 0.5); expected (0, 0, 1, 0)

This is reached in practice by Photon.measure_multiple, used by PolarizationBSM and TimeBinBSM. The returned index res (which drives detector firing) is correct, so detector outcomes are unaffected; but the measured photons' tracked state is left wrong, which is incorrect for any consumer that inspects photon state after a multi-qubit measurement.

Fix

Assign the collapsed vector to the real fields, mirroring the single-qubit measure method:

new_state = tuple(new_states[res])
for qs in entangled_list:
    if qs is not None:
        qs.state = new_state
        qs.entangled_states = entangled_list
return res

Test plan

  • Added test_measure_multiple_collapses_state, which combines two |+> photons, measures in the computational basis, and asserts the shared state collapses to the measured basis vector (and stays shared).
  • Confirmed the new test fails on the current code and passes with the fix.
  • Existing test_measure_multiple and other photon/kernel tests still pass.

🤖 Generated with Claude Code

dependabot Bot and others added 11 commits May 8, 2026 15:56
Bumps the uv group with 1 update in the / directory: [mistune](https://github.com/lepture/mistune).


Updates `mistune` from 3.1.4 to 3.2.1
- [Release notes](https://github.com/lepture/mistune/releases)
- [Changelog](https://github.com/lepture/mistune/blob/main/docs/changes.rst)
- [Commits](lepture/mistune@v3.1.4...v3.2.1)

---
updated-dependencies:
- dependency-name: mistune
  dependency-version: 3.2.1
  dependency-type: indirect
  dependency-group: uv
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps the uv group with 1 update in the / directory: [jupyter-server](https://github.com/jupyter-server/jupyter_server).


Updates `jupyter-server` from 2.17.0 to 2.18.0
- [Release notes](https://github.com/jupyter-server/jupyter_server/releases)
- [Changelog](https://github.com/jupyter-server/jupyter_server/blob/main/CHANGELOG.md)
- [Commits](jupyter-server/jupyter_server@v2.17.0...v2.18.0)

---
updated-dependencies:
- dependency-name: jupyter-server
  dependency-version: 2.18.0
  dependency-type: indirect
  dependency-group: uv
...

Signed-off-by: dependabot[bot] <support@github.com>
…bot/uv/uv-6dae2e92a4

Bump jupyter-server from 2.17.0 to 2.18.0 in the uv group across 1 directory
…bot/uv/uv-3f08418977

Bump mistune from 3.1.4 to 3.2.1 in the uv group across 1 directory
…337-dependabot/uv/uv-3f08418977

Revert "Bump mistune from 3.1.4 to 3.2.1 in the uv group across 1 directory"
…338-dependabot/uv/uv-6dae2e92a4

Revert "Bump jupyter-server from 2.17.0 to 2.18.0 in the uv group across 1 directory"
After projecting onto the chosen basis vector, measure_multiple wrote the
post-measurement state to attributes that FreeQuantumState does not have:

    for state in entangled_list:
        state.quantum_state = new_state       # should be .state
        state.entangled_photons = entangled_list  # should be .entangled_states

FreeQuantumState stores its vector in `state` and its partners in
`entangled_states` (see the single-qubit `measure` method). Writing to the
wrong names silently created unused attributes and left the actual quantum
state uncollapsed: the entangled photons kept their pre-measurement
superposition after measure_multiple.

Assign the collapsed vector to `state` and update `entangled_states`,
mirroring the single-qubit `measure` method. Adds a regression test that
checks the state collapses to the measured basis vector.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@hayekr hayekr changed the base branch from master to RnD May 22, 2026 04:48
Copy link
Copy Markdown
Collaborator

@hayekr hayekr 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 your contribution. Please see my suggestions before we merge this to RnD.

state.entangled_photons = entangled_list
# project to new state, then reassign quantum state and entangled list
new_state = tuple(new_states[res])
for qs in entangled_list:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

please call this quantum_state. Avoid abbreviations when able.

assert tuple(photon1.quantum_state.state) == expected, "state did not collapse to measured basis vector"
# both photons must keep sharing the same collapsed state object
assert photon1.quantum_state.state is photon2.quantum_state.state

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

It would be good to add assertions preventing these invalid class attributes from being used.

assert not hasattr(photon1.quantum_state, "quantum_state")
assert not hasattr(photon1.quantum_state, "entangled_photons")
assert not hasattr(photon2.quantum_state, "quantum_state")
assert not hasattr(photon2.quantum_state, "entangled_photons")

# both photons must keep sharing the same collapsed state object
assert photon1.quantum_state.state is photon2.quantum_state.state


Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

You check that state is shared but we should also check that entanglement_states is also shared
assert photon1.quantum_state.entangled_states is photon2.quantum_state.entangled_states

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