pub struct DensityMatrixBackend { /* private fields */ }Expand description
Exact density-matrix simulator. See the module docs for the state layout.
Implementations§
Source§impl DensityMatrixBackend
impl DensityMatrixBackend
pub fn new(seed: u64) -> Self
Sourcepub fn with_gpu(self, context: Arc<GpuContext>) -> Self
Available on crate feature gpu only.
pub fn with_gpu(self, context: Arc<GpuContext>) -> Self
gpu only.Hold the mixture on the device bound to context.
Backend::init then allocates the 4^n buffer in device memory after a
budget check against the free VRAM, and every sweep runs as a kernel. A
device that cannot hold the state is an error at init, never a host
fallback. The channel entry points keep their infallible signatures, so
a kernel launch that fails under one of them panics.
Sourcepub fn density_matrix(&self) -> Result<Vec<Complex64>>
pub fn density_matrix(&self) -> Result<Vec<Complex64>>
The full 4^n buffer, row-major as the module docs lay it out. Reads
back from the device when the mixture is resident there.
Sourcepub fn apply_1q_kraus(&mut self, qubit: usize, kraus: &[[[Complex64; 2]; 2]])
pub fn apply_1q_kraus(&mut self, qubit: usize, kraus: &[[[Complex64; 2]; 2]])
Apply a general single-qubit channel rho -> sum_k K_k rho K_k^dagger
on qubit. The Kraus set is compiled once into a 4x4 block
superoperator acting on each (row-bit, col-bit) block of rho, so the
buffer is swept once with no per-element allocation.
Sourcepub fn apply_2q_depolarizing(&mut self, q0: usize, q1: usize, p: f64)
pub fn apply_2q_depolarizing(&mut self, q0: usize, q1: usize, p: f64)
Apply symmetric two-qubit depolarizing on (q0, q1):
rho -> (1-p) rho + (p/15) sum_{P != I(x)I} P rho P, summed over the 15
non-identity two-qubit Paulis.
The Pauli twirl sum_P P B P = 4 Tr(B) I4 over all 16 two-qubit Paulis
holds for any 4x4 B, so on each (q0, q1) block the map collapses to
B -> alpha B + beta Tr(B) I4 with alpha = 1 - 16p/15 and
beta = 4p/15. That is one real scale per amplitude against the 16
complex multiply-accumulates the equivalent 16x16 superoperator pays in
DensityMatrixBackend::apply_2q_kraus.
§Panics
If q0 and q1 are equal or outside the register, as
DensityMatrixBackend::apply_2q_kraus does.
Sourcepub fn apply_2q_kraus(
&mut self,
q0: usize,
q1: usize,
kraus: &[[[Complex64; 4]; 4]],
)
pub fn apply_2q_kraus( &mut self, q0: usize, q1: usize, kraus: &[[[Complex64; 4]; 4]], )
Apply a general two-qubit channel rho -> sum_k K_k rho K_k^dagger on
(q0, q1). Each operator is indexed K[t][t'] with
t = 2 * bit(q0) + bit(q1), matching Gate::matrix_4x4.
The Kraus set is compiled once into a 16x16 block superoperator over the
four-bit (q0-row, q1-row, q0-col, q1-col) block, so the buffer is swept
once. Block index 4*tr + tc orders the two-qubit row value tr against
the column value tc. An all-diagonal set compiles to a diagonal
superoperator and is applied as one complex multiply per amplitude in a
contiguous pass instead of the dense sweep. On hosts with detected AVX2
and FMA the dense sweep issues each row inner product two complex terms
per FMA through simd::PreparedKraus2q.
§Panics
If q0 and q1 are equal or outside the register. A model built through
NoiseModel is rejected before it reaches here.
Sourcepub fn expectations_pauli(&self, masks: &[(usize, usize, u32)]) -> Vec<f64>
pub fn expectations_pauli(&self, masks: &[(usize, usize, u32)]) -> Vec<f64>
Exact Tr(rho P) for every joint Pauli reduced to (xmask, zmask, num_y),
where P|j> = i^{num_y} * (-1)^{popcount(j & zmask)} * |j ^ xmask>. The
trace collapses to a diagonal-offset sum:
Tr(rho P) = i^{num_y} sum_j (-1)^{popcount(j & zmask)} rho[j][j ^ xmask].
Each row of the 4^n buffer is visited once and contributes one entry per
observable, so the strided sweep is paid once instead of once per observable.
Trait Implementations§
Source§impl Backend for DensityMatrixBackend
impl Backend for DensityMatrixBackend
Source§fn overlap_sq(&self, _other: &dyn Backend) -> Result<f64>
fn overlap_sq(&self, _other: &dyn Backend) -> Result<f64>
Declined: the fidelity of two mixtures is not an inner product, and a mixture holds no statevector for the dense route to export.
Source§fn reduced_density_matrix(
&mut self,
subsystem: &[usize],
) -> Result<Vec<Complex64>>
fn reduced_density_matrix( &mut self, subsystem: &[usize], ) -> Result<Vec<Complex64>>
Partial trace of the 4^n buffer, 2^(n + k) reads. At k = n the
buffer itself comes back, its rows and columns in subsystem order.
Source§fn init(&mut self, num_qubits: usize, num_classical_bits: usize) -> Result<()>
fn init(&mut self, num_qubits: usize, num_classical_bits: usize) -> Result<()>
With a device attached the host cap does not apply: the mixture is budgeted against free VRAM instead, before anything is allocated.
Source§fn init_from_amplitudes(
&mut self,
amplitudes: Vec<Complex64>,
num_classical_bits: usize,
) -> Result<()>
fn init_from_amplitudes( &mut self, amplitudes: Vec<Complex64>, num_classical_bits: usize, ) -> Result<()>
Starts from the pure mixture |psi><psi|, so the buffer is the 4^n
outer product of amplitudes with its own conjugate. The cap check in
init runs first, before that buffer is sized.
Source§fn supports_fused_gates(&self) -> bool
fn supports_fused_gates(&self) -> bool
MultiFused and Multi2q apply their constituents one at a time here
rather than through the tiled kernels, which the ket register’s shifted
indices would reorder.
Source§fn fusion_state_qubits(&self, num_qubits: usize) -> usize
fn fusion_state_qubits(&self, num_qubits: usize) -> usize
The mixture is a 2n-qubit statevector, so every fusion floor is
reached at half the circuit width the statevector needs.
Source§fn pauli_expectations(&self, observables: &[Vec<PauliTerm>]) -> Result<Vec<f64>>
fn pauli_expectations(&self, observables: &[Vec<PauliTerm>]) -> Result<Vec<f64>>
Tr(rho P_k) per observable, the mixed-state reading of the trait’s
<psi|P_k|psi>. rho is trace-one by construction, so no
normalization divide is needed.
Source§fn reduced_density_matrix_1q(&self, qubit: usize) -> Result<[[Complex64; 2]; 2]>
fn reduced_density_matrix_1q(&self, qubit: usize) -> Result<[[Complex64; 2]; 2]>
On the device the four entries come from the Pauli sums T, Z, X,
and Y on qubit: the diagonal is (T +- Z) / 2 and the off-diagonal
pair is (X +- Y) / 2, where Y carries the row sign and no i.
Source§fn apply_1q_matrix(
&mut self,
qubit: usize,
matrix: &[[Complex64; 2]; 2],
) -> Result<()>
fn apply_1q_matrix( &mut self, qubit: usize, matrix: &[[Complex64; 2]; 2], ) -> Result<()>
Evolve rho -> K rho K^dagger for an arbitrary K, on the same kernel
selection as the one-qubit branch of apply_unitary. Trajectories never
route here, since supports_noisy_per_shot excludes the density matrix in
favour of apply_1q_kraus on the mixture; this keeps the trait method
allocation-free on every backend that holds a state.
Source§fn name(&self) -> &'static str
fn name(&self) -> &'static str
Source§fn as_any(&self) -> Option<&dyn Any>
fn as_any(&self) -> Option<&dyn Any>
&dyn Backend, so Backend::overlap_sq
can recognize its own representation on the other side of the inner
product. An implementor that wants the fast paths writes Some(self);
the default hides the concrete type, which costs only the dense route.Source§fn resolved(&self) -> ResolvedBackend
fn resolved(&self) -> ResolvedBackend
Backend::name.Source§fn schmidt_values(&mut self, _subsystem: &[usize]) -> Result<Vec<f64>>
fn schmidt_values(&mut self, _subsystem: &[usize]) -> Result<Vec<f64>>
subsystem and its
complement: descending, numerically zero values dropped, squares summing
to 1 whatever norm the representation carries. Read moreSource§fn placement(&self) -> Placement
fn placement(&self) -> Placement
gpu feature is on.Source§fn supports_initial_state(&self) -> bool
fn supports_initial_state(&self) -> bool
Backend::init_from_amplitudes can start this backend from a
caller-supplied state.Source§fn apply(&mut self, instruction: &Instruction) -> Result<()>
fn apply(&mut self, instruction: &Instruction) -> Result<()>
Source§fn classical_results(&self) -> &[bool]
fn classical_results(&self) -> &[bool]
Source§fn probabilities(&self) -> Result<Vec<f64>>
fn probabilities(&self) -> Result<Vec<f64>>
Source§fn num_qubits(&self) -> usize
fn num_qubits(&self) -> usize
Source§fn qubit_probability(&self, qubit: usize) -> Result<f64>
fn qubit_probability(&self, qubit: usize) -> Result<f64>
Source§fn reset(&mut self, qubit: usize) -> Result<()>
fn reset(&mut self, qubit: usize) -> Result<()>
Source§fn supports_pauli_expectation(&self) -> bool
fn supports_pauli_expectation(&self) -> bool
Backend::pauli_expectations evaluates observables on this
backend’s own representation.Source§fn exactness(&self) -> Exactness
fn exactness(&self) -> Exactness
Source§fn block_probabilities(&self) -> Option<Probabilities>
fn block_probabilities(&self) -> Option<Probabilities>
2^n Kronecker expansion
Backend::probabilities would materialize. Read moreSource§fn apply_instructions(&mut self, instructions: &[Instruction]) -> Result<()>
fn apply_instructions(&mut self, instructions: &[Instruction]) -> Result<()>
Source§fn apply_region(&mut self, region: &GuardedRegion) -> Result<()>
fn apply_region(&mut self, region: &GuardedRegion) -> Result<()>
Source§fn supports_qft_block(&self) -> bool
fn supports_qft_block(&self) -> bool
Gate::QftBlock. Read moreSource§fn supports_pauli_rotation(&self) -> bool
fn supports_pauli_rotation(&self) -> bool
Gate::PauliRot. Read moreSource§fn export_statevector(&self) -> Result<Vec<Complex64>>
fn export_statevector(&self) -> Result<Vec<Complex64>>
Source§fn supports_two_qubit_kraus(&self) -> bool
fn supports_two_qubit_kraus(&self) -> bool
NoiseChannel::Kraus2q
branch, which needs both Backend::reduced_density_matrix_2q and a
Gate::Fused2q kernel. Checked before a shot starts, so an incapable
backend is named at dispatch rather than part way through a trajectory.Source§fn reduced_density_matrix_2q(
&self,
_q0: usize,
_q1: usize,
) -> Result<[[Complex64; 4]; 4]>
fn reduced_density_matrix_2q( &self, _q0: usize, _q1: usize, ) -> Result<[[Complex64; 4]; 4]>
Source§fn supports_native_sampling(&self) -> bool
fn supports_native_sampling(&self) -> bool
Backend::sample_basis_states draws from this backend’s own
representation. Read moreSource§fn sample_basis_states(
&mut self,
_num_shots: usize,
_seed: u64,
) -> Result<BasisSamples>
fn sample_basis_states( &mut self, _num_shots: usize, _seed: u64, ) -> Result<BasisSamples>
num_shots computational-basis outcomes from the current state. Read moreSource§fn entanglement_entropy(&mut self, subsystem: &[usize]) -> Result<f64>
fn entanglement_entropy(&mut self, subsystem: &[usize]) -> Result<f64>
subsystem in nats: -sum p ln p over
p = s^2 / sum s^2 for the Backend::schmidt_values s, which is
where the default reads it from.Auto Trait Implementations§
impl !Freeze for DensityMatrixBackend
impl !RefUnwindSafe for DensityMatrixBackend
impl !Sync for DensityMatrixBackend
impl Send for DensityMatrixBackend
impl Unpin for DensityMatrixBackend
impl UnsafeUnpin for DensityMatrixBackend
impl UnwindSafe for DensityMatrixBackend
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
impl<T, U> Imply<T> for U
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more