mimiqcircuits.backends.measure_analysis

Final-block analysis. Reverse-scan a Circuit, absorb its trailing “projection block” (Measures, Resets, post-measure X gates) into a small projection_circuit of classical-bit instructions, and return the remaining quantum_circuit plus that projection.

The two-circuit decomposition is the post-evolution analog of the old MeasureInfo enum:

quantum_circuit, projection_circuit = extract_projection(c) # evolve through quantum_circuit once # for each shot: # sample = quantum_state.sample() # cstate = evaluate_projection(projection_circuit, sample)

projection_circuit only contains classical-bit operations:

  • Measure(q, b) — cstate[b] = sample[q]

  • SetBit0(b) / SetBit1(b) — bit is classically known

  • Not(b) — cstate[b] = !cstate[b]

  • And / Or / Xor / ParityCheck — bits[0] is the target, the rest are read

Evaluate it one shot at a time with evaluate_projection, or a whole block at a time with CompiledProjection, which is the same semantics compiled once instead of interpreted per shot.

Mirrors the Julia AbstractQCSs.extract_projection. The two ports must stay behavioural-parity — if one is fixed, fix the other.

Functions

any_mixed_unitary(circuit)

Return True if circuit contains a mixed-unitary krauschannel whose ismixedunitary() is true.

evaluate_projection(projection, sample)

Run the projection circuit for one shot.

extract_projection(circuit)

Reverse-scan circuit, absorbing every trailing operation that does not affect the qubit observables of the post-evolution state into a projection_circuit of classical-bit instructions.

needs_loss_sampling(circuit)

Return True if circuit contains loss operations (Loss, Reload, Check, MeasureCheck) that must be resolved into primitives before the simulator runs.

needs_trajectories(circuit)

Return True if circuit still contains any non-unitary op that requires per-shot evolution.

remap_projection_qubits(projection, ...)

Rewrite every Measure(q, b) instruction in projection so that q → qubit_order[q].

mimiqcircuits.backends.measure_analysis.extract_projection(circuit)[source]

Reverse-scan circuit, absorbing every trailing operation that does not affect the qubit observables of the post-evolution state into a projection_circuit of classical-bit instructions. The remaining operations are returned as quantum_circuit — the part the simulator must evolve.

Absorbed operations:

  • Trailing Measure(q, b) / MeasureReset(q, b) → Measure(q, b).

  • Trailing classical logic: Not, And, Or, Xor, ParityCheck, SetBit0, SetBit1. These read other classical bits, so what comes out is a straight-line program over the register rather than a per-bit map, emitted in source order. A logic op is absorbed only when no operation left in quantum_circuit touches any of its bits, otherwise moving it past that operation would change what it reads.

  • Trailing GateID — dropped as a no-op. GateX / GateY / GateZ are NOT absorbed: Y and Z carry phases that would corrupt amplitude lookups, and X absorbed alone would require XOR-ing the qubit-flip pattern into every user-supplied bitstrings entry for amplitudes to stay consistent. Keeping all Paulis in quantum_circuit means the projection only contains phase-free transformations and amplitude lookups need no compensation.

  • Reset(q) whose qubit has captured pending bits → those bits become classical constants (SetBit0 / SetBit1 based on absorbed X parity). A Reset whose qubit has no captured bits is harmless and dropped.

Anything that cannot be absorbed (other gates, Kraus channels, IfStatement, Amplitude, ExpectationValue, …) blocks its qubits and survives into quantum_circuit.

If the source has no classical register, the projection is synthesised over an identity bit↔qubit mapping (bit i mirrors qubit i, length = circuit.num_qubits()).

A write to a classical bit that a later absorbed operation overwrites without reading is dropped from the projection, so reusing a classical bit does not cost a per-shot write that nothing observes.

mimiqcircuits.backends.measure_analysis.evaluate_projection(projection, sample)[source]

Run the projection circuit for one shot.

Parameters:
  • projection (Circuit) – the projection circuit returned by extract_projection().

  • sample (BitString) – one computational-basis outcome, one bit per qubit of the evolved state. Not a quantum state: it is a single shot already drawn from it.

Returns:

the classical register after the projection, of length projection.num_bits().

Return type:

BitString

Raises:

ValueError – if projection holds an instruction outside the classical language listed below.

The classical language is Measure, SetBit0, SetBit1, Not, And, Or, Xor and ParityCheck. The logic ops take their target as the first bit and read the rest, and the whole right-hand side is evaluated before the target is written, so a target that is also one of its own operands reads its old value.

A Measure(q, b) whose qubit falls outside sample leaves bit b untouched, which is how a projection that references a qubit the state never spanned reads back as zero.

This is the single-shot reference implementation of the projection semantics. CompiledProjection is the fast path, and is tested against this.

mimiqcircuits.backends.measure_analysis.needs_trajectories(circuit)[source]

Return True if circuit still contains any non-unitary op that requires per-shot evolution. Operations that don’t touch qubits (Amplitude on a z-register, Tick, …) or that declare themselves unitary (Gates, AbstractAnnotation, ExpectationValue, …) are ignored.

Mirrors the Julia AbstractQCSs.needs_trajectories: num_qubits(op) != 0 && !isunitary(op).

mimiqcircuits.backends.measure_analysis.needs_loss_sampling(circuit)[source]

Return True if circuit contains loss operations (Loss, Reload, Check, MeasureCheck) that must be resolved into primitives before the simulator runs.

mimiqcircuits.backends.measure_analysis.any_mixed_unitary(circuit)[source]

Return True if circuit contains a mixed-unitary krauschannel whose ismixedunitary() is true. Used as the default predicate for the per-trajectory recompile decision in LocalBackend.recompile_per_trajectory().

Mirrors the Julia AbstractQCSs.any_mixed_unitary.

mimiqcircuits.backends.measure_analysis.remap_projection_qubits(projection, qubit_order, do_remap)[source]

Rewrite every Measure(q, b) instruction in projection so that q → qubit_order[q]. Used by the driver when the pass pipeline reordered the qubits and the projection was synthesised in the reordered frame.

Returns a new Circuit; the input is not mutated.