cqlib_core/visualization/state/data.rs
1// This code is part of Cqlib.
2//
3// (C) Copyright China Telecom Quantum Group 2026
4//
5// This code is licensed under the Apache License, Version 2.0. You may
6// obtain a copy of this license in the LICENSE.txt file in the root directory
7// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8//
9// Any modifications or derivative works of this code must retain this
10// copyright notice, and modified files need to carry a notice indicating
11// that they have been altered from the originals.
12
13//! State validation and density-matrix conversion for visualization.
14//!
15//! Functions in this module normalize core QIS state types into row-major density
16//! matrices used by state plotting backends.
17
18use crate::qis::{DensityMatrix, Statevector};
19use crate::visualization::VisualizationError;
20use num_complex::Complex64;
21
22/// Core state types accepted by state visualization functions.
23///
24/// This trait is an adapter over Cqlib's QIS state objects, not a separate visualization
25/// data container. Public plotting APIs stay generic so callers can pass `Statevector` and
26/// `DensityMatrix` directly.
27///
28/// Implementors must return row-major density-matrix data with length `4^num_qubits`.
29/// Invalid dimensions should be reported as [`VisualizationError::InvalidInput`].
30pub trait StateVisualizationSource {
31 /// Number of qubits represented by the state.
32 fn num_qubits(&self) -> usize;
33
34 /// Return row-major density-matrix data for visualization.
35 fn density_matrix_data(&self) -> Result<Vec<Complex64>, VisualizationError>;
36}
37
38impl StateVisualizationSource for Statevector {
39 fn num_qubits(&self) -> usize {
40 self.num_qubits
41 }
42
43 fn density_matrix_data(&self) -> Result<Vec<Complex64>, VisualizationError> {
44 let num_qubits = self.num_qubits;
45 let data = self.data();
46 let dim = checked_dim(num_qubits)?;
47 let density_len = checked_density_len(num_qubits)?;
48 if data.len() != dim {
49 return Err(VisualizationError::InvalidInput(format!(
50 "statevector length {} does not match 2^{num_qubits}",
51 data.len()
52 )));
53 }
54 let mut rho = vec![Complex64::new(0.0, 0.0); density_len];
55 for row in 0..dim {
56 for col in 0..dim {
57 rho[row * dim + col] = data[row] * data[col].conj();
58 }
59 }
60 Ok(rho)
61 }
62}
63
64impl StateVisualizationSource for DensityMatrix {
65 fn num_qubits(&self) -> usize {
66 self.num_qubits
67 }
68
69 fn density_matrix_data(&self) -> Result<Vec<Complex64>, VisualizationError> {
70 let num_qubits = self.num_qubits;
71 let density_len = checked_density_len(num_qubits)?;
72 let data = self.data();
73 if data.len() != density_len {
74 return Err(VisualizationError::InvalidInput(format!(
75 "density matrix length {} does not match 4^{num_qubits}",
76 data.len()
77 )));
78 }
79 Ok(data.to_vec())
80 }
81}
82
83/// Convert a core state object into a row-major density matrix.
84///
85/// For a pure state, this forms $\rho = |\psi\rangle\langle\psi|$.
86///
87/// # Arguments
88///
89/// * `state` - Input state object with dimensions validated against `num_qubits`.
90///
91/// # Returns
92///
93/// A tuple `(num_qubits, rho)` where `rho` is stored in row-major order with length
94/// `4^num_qubits`.
95///
96/// # Errors
97///
98/// Returns [`VisualizationError::InvalidInput`] when qubit counts or buffer lengths are
99/// inconsistent.
100///
101/// # Examples
102///
103/// ```rust
104/// use cqlib_core::qis::Statevector;
105/// use cqlib_core::visualization::state_to_density_matrix;
106/// use num_complex::Complex64;
107///
108/// let state = Statevector::from_state(
109/// 1,
110/// vec![Complex64::new(1.0, 0.0), Complex64::new(0.0, 0.0)],
111/// ).unwrap();
112/// let (num_qubits, rho) = state_to_density_matrix(&state).unwrap();
113/// assert_eq!(num_qubits, 1);
114/// assert_eq!(rho.len(), 4);
115/// ```
116pub fn state_to_density_matrix<S: StateVisualizationSource + ?Sized>(
117 state: &S,
118) -> Result<(usize, Vec<Complex64>), VisualizationError> {
119 let num_qubits = state.num_qubits();
120 let rho = state.density_matrix_data()?;
121 let density_len = checked_density_len(num_qubits)?;
122 if rho.len() != density_len {
123 return Err(VisualizationError::InvalidInput(format!(
124 "density matrix length {} does not match 4^{num_qubits}",
125 rho.len()
126 )));
127 }
128 Ok((num_qubits, rho))
129}
130
131/// Compute Hilbert-space dimension `2^num_qubits` with overflow protection.
132pub(crate) fn checked_dim(num_qubits: usize) -> Result<usize, VisualizationError> {
133 if num_qubits >= usize::BITS as usize {
134 return Err(VisualizationError::InvalidInput(
135 "too many qubits for state visualization".to_string(),
136 ));
137 }
138 Ok(1usize << num_qubits)
139}
140
141/// Computes density-matrix storage length `4^num_qubits` with overflow protection.
142///
143/// State-city and Pauli-vector plots operate on flattened density matrices. This helper
144/// keeps all matrix-size checks on a single path so large states fail with a clear
145/// visualization error before allocation or indexing.
146pub(crate) fn checked_density_len(num_qubits: usize) -> Result<usize, VisualizationError> {
147 let dim = checked_dim(num_qubits)?;
148 dim.checked_mul(dim).ok_or_else(|| {
149 VisualizationError::InvalidInput("too many qubits for state visualization".to_string())
150 })
151}
152
153/// Build computational-basis labels in display order.
154///
155/// Labels are generated from little-endian basis indices and optionally reversed for
156/// frontends that display the most-significant qubit on the opposite side.
157pub(crate) fn basis_labels(num_qubits: usize, reverse_bits: bool) -> Vec<String> {
158 let dim = 1usize << num_qubits;
159 (0..dim)
160 .map(|value| {
161 let raw = format!("{value:0num_qubits$b}");
162 if reverse_bits {
163 raw.chars().rev().collect()
164 } else {
165 raw
166 }
167 })
168 .collect()
169}