Skip to main content

qft/
lib.rs

1//! # qft-rs - Native Rust SDK for Quantum File Type (.qft)
2//!
3//! Production-grade Rust library for working with quantum states in the .qft format.
4//!
5//! ## Table of Contents
6//! 1. Core Types - QftFile, QftState, QftError
7//! 2. Builder API - Fluent interface for state construction
8//! 3. I/O Operations - Load, save, streaming
9//! 4. State Operations - Normalization, fidelity, inner products
10//! 5. Conversion - To/from ndarray, Vec, iterators
11//! 6. Async Support - Tokio-based async I/O (feature-gated)
12//!
13//! ## Quick Start
14//!
15//! ```rust
16//! use qft::{QftFile, Result};
17//!
18//! fn main() -> Result<()> {
19//!     // Create a 4-qubit state
20//!     let mut state = QftFile::new(4)?;
21//!     
22//!     // Set to |0000⟩ + |1111⟩ (unnormalized)
23//!     state.set_amplitude(0, 1.0.into())?;
24//!     state.set_amplitude(15, 1.0.into())?;
25//!     state.normalize()?;
26//!     
27//!     // Save to disk
28//!     state.save("state.qft")?;
29//!     
30//!     // Load and verify
31//!     let loaded = QftFile::load("state.qft")?;
32//!     assert!((state.fidelity(&loaded)? - 1.0).abs() < 1e-10);
33//!     
34//!     Ok(())
35//! }
36//! ```
37
38#![cfg_attr(docsrs, feature(doc_cfg))]
39
40use num_complex::Complex64;
41use serde::{Deserialize, Serialize};
42use std::collections::HashMap;
43use std::fs::File;
44use std::io::{BufReader, BufWriter, Read, Write};
45use std::path::Path;
46use thiserror::Error;
47
48// =============================================================================
49// Section 1: Error Types
50// =============================================================================
51
52/// Error type for QFT operations
53#[derive(Error, Debug)]
54pub enum QftError {
55    #[error("Invalid number of qubits: {0} (must be 1-30)")]
56    InvalidQubits(usize),
57
58    #[error("Index {0} out of range for {1} qubits")]
59    IndexOutOfRange(usize, usize),
60
61    #[error("Dimension mismatch: expected {expected}, got {actual}")]
62    DimensionMismatch { expected: usize, actual: usize },
63
64    #[error("Cannot normalize zero state")]
65    ZeroNorm,
66
67    #[error("Invalid file format: {0}")]
68    InvalidFormat(String),
69
70    #[error("I/O error: {0}")]
71    Io(#[from] std::io::Error),
72
73    #[error("Serialization error: {0}")]
74    Serialization(String),
75
76    #[error("Checksum mismatch")]
77    ChecksumMismatch,
78
79    #[error("Golay decode failed: too many errors")]
80    GolayDecodeFailed,
81}
82
83/// Result type alias for QFT operations
84pub type Result<T> = std::result::Result<T, QftError>;
85
86// =============================================================================
87// Section 2: Core Types
88// =============================================================================
89
90/// Configuration for QFT file operations
91#[derive(Debug, Clone, Serialize, Deserialize)]
92pub struct QftConfig {
93    /// Bond dimension for MPS compression (1-1024)
94    pub bond_dimension: usize,
95    /// Enable Golay error correction
96    pub golay_enabled: bool,
97    /// Truncation threshold for SVD
98    pub truncation_threshold: f64,
99}
100
101impl Default for QftConfig {
102    fn default() -> Self {
103        Self {
104            bond_dimension: 64,
105            golay_enabled: true,
106            truncation_threshold: 1e-10,
107        }
108    }
109}
110
111/// A quantum state file in .qft format
112#[derive(Debug, Clone)]
113pub struct QftFile {
114    num_qubits: usize,
115    amplitudes: Vec<Complex64>,
116    metadata: HashMap<String, String>,
117    config: QftConfig,
118}
119
120impl QftFile {
121    /// Create a new QFT file with the specified number of qubits.
122    /// Initializes to the |0...0⟩ state.
123    ///
124    /// # Arguments
125    /// * `num_qubits` - Number of qubits (1-30)
126    ///
127    /// # Example
128    /// ```
129    /// use qft::QftFile;
130    /// let state = QftFile::new(4).unwrap();
131    /// assert_eq!(state.num_qubits(), 4);
132    /// assert_eq!(state.dimension(), 16);
133    /// ```
134    pub fn new(num_qubits: usize) -> Result<Self> {
135        if num_qubits == 0 || num_qubits > 30 {
136            return Err(QftError::InvalidQubits(num_qubits));
137        }
138
139        let dim = 1usize << num_qubits;
140        let mut amplitudes = vec![Complex64::new(0.0, 0.0); dim];
141        amplitudes[0] = Complex64::new(1.0, 0.0); // |0...0⟩
142
143        Ok(Self {
144            num_qubits,
145            amplitudes,
146            metadata: HashMap::new(),
147            config: QftConfig::default(),
148        })
149    }
150
151    /// Create a QFT file with custom configuration.
152    pub fn with_config(num_qubits: usize, config: QftConfig) -> Result<Self> {
153        let mut file = Self::new(num_qubits)?;
154        file.config = config;
155        Ok(file)
156    }
157
158    /// Create from a vector of complex amplitudes.
159    ///
160    /// # Example
161    /// ```
162    /// use qft::QftFile;
163    /// use num_complex::Complex64;
164    ///
165    /// let sqrt2_inv = 1.0 / 2.0_f64.sqrt();
166    /// let amplitudes = vec![
167    ///     Complex64::new(sqrt2_inv, 0.0),
168    ///     Complex64::new(0.0, 0.0),
169    ///     Complex64::new(0.0, 0.0),
170    ///     Complex64::new(sqrt2_inv, 0.0),
171    /// ];
172    /// let bell = QftFile::from_amplitudes(amplitudes).unwrap();
173    /// assert_eq!(bell.num_qubits(), 2);
174    /// ```
175    pub fn from_amplitudes(amplitudes: Vec<Complex64>) -> Result<Self> {
176        let dim = amplitudes.len();
177        if dim == 0 || (dim & (dim - 1)) != 0 {
178            return Err(QftError::InvalidFormat(
179                "Amplitude count must be a power of 2".to_string(),
180            ));
181        }
182
183        let num_qubits = dim.trailing_zeros() as usize;
184        if num_qubits > 30 {
185            return Err(QftError::InvalidQubits(num_qubits));
186        }
187
188        Ok(Self {
189            num_qubits,
190            amplitudes,
191            metadata: HashMap::new(),
192            config: QftConfig::default(),
193        })
194    }
195
196    /// Create from separate real and imaginary arrays.
197    pub fn from_real_imag(real: &[f64], imag: &[f64]) -> Result<Self> {
198        if real.len() != imag.len() {
199            return Err(QftError::DimensionMismatch {
200                expected: real.len(),
201                actual: imag.len(),
202            });
203        }
204
205        let amplitudes: Vec<Complex64> = real
206            .iter()
207            .zip(imag.iter())
208            .map(|(&r, &i)| Complex64::new(r, i))
209            .collect();
210
211        Self::from_amplitudes(amplitudes)
212    }
213
214    // =========================================================================
215    // Properties
216    // =========================================================================
217
218    /// Get the number of qubits.
219    #[inline]
220    pub fn num_qubits(&self) -> usize {
221        self.num_qubits
222    }
223
224    /// Get the state vector dimension (2^num_qubits).
225    #[inline]
226    pub fn dimension(&self) -> usize {
227        1 << self.num_qubits
228    }
229
230    /// Get the configuration.
231    pub fn config(&self) -> &QftConfig {
232        &self.config
233    }
234
235    /// Get mutable reference to configuration.
236    pub fn config_mut(&mut self) -> &mut QftConfig {
237        &mut self.config
238    }
239
240    // =========================================================================
241    // Amplitude Access
242    // =========================================================================
243
244    /// Get all amplitudes as a slice.
245    #[inline]
246    pub fn amplitudes(&self) -> &[Complex64] {
247        &self.amplitudes
248    }
249
250    /// Get mutable access to amplitudes.
251    #[inline]
252    pub fn amplitudes_mut(&mut self) -> &mut [Complex64] {
253        &mut self.amplitudes
254    }
255
256    /// Get a single amplitude by basis state index.
257    pub fn get_amplitude(&self, index: usize) -> Result<Complex64> {
258        if index >= self.dimension() {
259            return Err(QftError::IndexOutOfRange(index, self.num_qubits));
260        }
261        Ok(self.amplitudes[index])
262    }
263
264    /// Set a single amplitude by basis state index.
265    pub fn set_amplitude(&mut self, index: usize, value: Complex64) -> Result<()> {
266        if index >= self.dimension() {
267            return Err(QftError::IndexOutOfRange(index, self.num_qubits));
268        }
269        self.amplitudes[index] = value;
270        Ok(())
271    }
272
273    /// Set all amplitudes from a slice.
274    pub fn set_amplitudes(&mut self, amplitudes: &[Complex64]) -> Result<()> {
275        if amplitudes.len() != self.dimension() {
276            return Err(QftError::DimensionMismatch {
277                expected: self.dimension(),
278                actual: amplitudes.len(),
279            });
280        }
281        self.amplitudes.copy_from_slice(amplitudes);
282        Ok(())
283    }
284
285    // =========================================================================
286    // Metadata
287    // =========================================================================
288
289    /// Set a metadata key-value pair.
290    pub fn set_metadata(&mut self, key: impl Into<String>, value: impl Into<String>) {
291        self.metadata.insert(key.into(), value.into());
292    }
293
294    /// Get a metadata value by key.
295    pub fn get_metadata(&self, key: &str) -> Option<&str> {
296        self.metadata.get(key).map(|s| s.as_str())
297    }
298
299    /// Get all metadata.
300    pub fn metadata(&self) -> &HashMap<String, String> {
301        &self.metadata
302    }
303
304    /// Get mutable access to metadata.
305    pub fn metadata_mut(&mut self) -> &mut HashMap<String, String> {
306        &mut self.metadata
307    }
308
309    // =========================================================================
310    // State Operations
311    // =========================================================================
312
313    /// Calculate the norm squared (sum of |amplitude|²).
314    pub fn norm_squared(&self) -> f64 {
315        self.amplitudes.iter().map(|a| a.norm_sqr()).sum()
316    }
317
318    /// Calculate the norm.
319    pub fn norm(&self) -> f64 {
320        self.norm_squared().sqrt()
321    }
322
323    /// Check if the state is normalized within tolerance.
324    pub fn is_normalized(&self, tolerance: f64) -> bool {
325        (self.norm_squared() - 1.0).abs() < tolerance
326    }
327
328    /// Normalize the state vector in place.
329    pub fn normalize(&mut self) -> Result<()> {
330        let norm = self.norm();
331        if norm < 1e-15 {
332            return Err(QftError::ZeroNorm);
333        }
334        for a in &mut self.amplitudes {
335            *a /= norm;
336        }
337        Ok(())
338    }
339
340    /// Calculate the inner product ⟨self|other⟩.
341    pub fn inner_product(&self, other: &QftFile) -> Result<Complex64> {
342        if self.num_qubits != other.num_qubits {
343            return Err(QftError::DimensionMismatch {
344                expected: self.num_qubits,
345                actual: other.num_qubits,
346            });
347        }
348
349        let result: Complex64 = self
350            .amplitudes
351            .iter()
352            .zip(other.amplitudes.iter())
353            .map(|(a, b)| a.conj() * b)
354            .sum();
355
356        Ok(result)
357    }
358
359    /// Calculate the fidelity |⟨self|other⟩|².
360    pub fn fidelity(&self, other: &QftFile) -> Result<f64> {
361        let overlap = self.inner_product(other)?;
362        Ok(overlap.norm_sqr())
363    }
364
365    /// Calculate the trace distance to another state.
366    pub fn trace_distance(&self, other: &QftFile) -> Result<f64> {
367        let fid = self.fidelity(other)?;
368        Ok((1.0 - fid).sqrt())
369    }
370
371    // =========================================================================
372    // I/O Operations
373    // =========================================================================
374
375    /// Load a QFT file from disk.
376    ///
377    /// # Example
378    /// ```no_run
379    /// use qft::QftFile;
380    /// let state = QftFile::load("state.qft").unwrap();
381    /// ```
382    pub fn load(path: impl AsRef<Path>) -> Result<Self> {
383        let file = File::open(path)?;
384        let mut reader = BufReader::new(file);
385        Self::read_from(&mut reader)
386    }
387
388    /// Save the QFT file to disk.
389    ///
390    /// # Example
391    /// ```no_run
392    /// use qft::QftFile;
393    /// let state = QftFile::new(4).unwrap();
394    /// state.save("state.qft").unwrap();
395    /// ```
396    pub fn save(&self, path: impl AsRef<Path>) -> Result<()> {
397        let file = File::create(path)?;
398        let mut writer = BufWriter::new(file);
399        self.write_to(&mut writer)
400    }
401
402    /// Read from any reader.
403    pub fn read_from<R: Read>(reader: &mut R) -> Result<Self> {
404        let mut header = [0u8; 16];
405        reader.read_exact(&mut header)?;
406
407        // Verify magic number
408        if &header[0..4] != b"QFT\x01" {
409            return Err(QftError::InvalidFormat("Invalid magic number".to_string()));
410        }
411
412        let num_qubits = header[4] as usize;
413        if num_qubits == 0 || num_qubits > 30 {
414            return Err(QftError::InvalidQubits(num_qubits));
415        }
416
417        let bond_dimension = header[5] as usize;
418        let golay_enabled = header[6] != 0;
419
420        let dim = 1usize << num_qubits;
421        let mut amplitudes = Vec::with_capacity(dim);
422
423        for _ in 0..dim {
424            let mut buf = [0u8; 16];
425            reader.read_exact(&mut buf)?;
426            let real = f64::from_le_bytes(buf[0..8].try_into().unwrap());
427            let imag = f64::from_le_bytes(buf[8..16].try_into().unwrap());
428            amplitudes.push(Complex64::new(real, imag));
429        }
430
431        Ok(Self {
432            num_qubits,
433            amplitudes,
434            metadata: HashMap::new(),
435            config: QftConfig {
436                bond_dimension: if bond_dimension == 0 { 64 } else { bond_dimension },
437                golay_enabled,
438                truncation_threshold: 1e-10,
439            },
440        })
441    }
442
443    /// Write to any writer.
444    pub fn write_to<W: Write>(&self, writer: &mut W) -> Result<()> {
445        // Magic number
446        writer.write_all(b"QFT\x01")?;
447
448        // Header
449        writer.write_all(&[
450            self.num_qubits as u8,
451            self.config.bond_dimension.min(255) as u8,
452            if self.config.golay_enabled { 1 } else { 0 },
453            0, // reserved
454        ])?;
455
456        // Padding to 16 bytes
457        writer.write_all(&[0u8; 8])?;
458
459        // Amplitudes
460        for a in &self.amplitudes {
461            writer.write_all(&a.re.to_le_bytes())?;
462            writer.write_all(&a.im.to_le_bytes())?;
463        }
464
465        writer.flush()?;
466        Ok(())
467    }
468
469    /// Serialize to bytes.
470    pub fn to_bytes(&self) -> Result<Vec<u8>> {
471        let mut buf = Vec::new();
472        self.write_to(&mut buf)?;
473        Ok(buf)
474    }
475
476    /// Deserialize from bytes.
477    pub fn from_bytes(data: &[u8]) -> Result<Self> {
478        let mut cursor = std::io::Cursor::new(data);
479        Self::read_from(&mut cursor)
480    }
481
482    /// Export to JSON.
483    pub fn to_json(&self) -> Result<String> {
484        #[derive(Serialize)]
485        struct JsonExport {
486            num_qubits: usize,
487            config: QftConfig,
488            amplitudes_real: Vec<f64>,
489            amplitudes_imag: Vec<f64>,
490            metadata: HashMap<String, String>,
491        }
492
493        let export = JsonExport {
494            num_qubits: self.num_qubits,
495            config: self.config.clone(),
496            amplitudes_real: self.amplitudes.iter().map(|a| a.re).collect(),
497            amplitudes_imag: self.amplitudes.iter().map(|a| a.im).collect(),
498            metadata: self.metadata.clone(),
499        };
500
501        serde_json::to_string_pretty(&export)
502            .map_err(|e| QftError::Serialization(e.to_string()))
503    }
504
505    /// Import from JSON.
506    pub fn from_json(json: &str) -> Result<Self> {
507        #[derive(Deserialize)]
508        struct JsonImport {
509            num_qubits: usize,
510            config: Option<QftConfig>,
511            amplitudes_real: Vec<f64>,
512            amplitudes_imag: Vec<f64>,
513            metadata: Option<HashMap<String, String>>,
514        }
515
516        let import: JsonImport =
517            serde_json::from_str(json).map_err(|e| QftError::Serialization(e.to_string()))?;
518
519        let amplitudes: Vec<Complex64> = import
520            .amplitudes_real
521            .iter()
522            .zip(import.amplitudes_imag.iter())
523            .map(|(&r, &i)| Complex64::new(r, i))
524            .collect();
525
526        let expected_dim = 1usize << import.num_qubits;
527        if amplitudes.len() != expected_dim {
528            return Err(QftError::DimensionMismatch {
529                expected: expected_dim,
530                actual: amplitudes.len(),
531            });
532        }
533
534        Ok(Self {
535            num_qubits: import.num_qubits,
536            amplitudes,
537            metadata: import.metadata.unwrap_or_default(),
538            config: import.config.unwrap_or_default(),
539        })
540    }
541}
542
543// =============================================================================
544// Section 3: Builder API
545// =============================================================================
546
547/// Builder for constructing QFT files with a fluent interface.
548///
549/// # Example
550/// ```
551/// use qft::QftBuilder;
552///
553/// let state = QftBuilder::new(4)
554///     .bond_dimension(128)
555///     .golay(true)
556///     .metadata("author", "Alice")
557///     .metadata("experiment", "VQE")
558///     .build()
559///     .unwrap();
560/// ```
561pub struct QftBuilder {
562    num_qubits: usize,
563    config: QftConfig,
564    metadata: HashMap<String, String>,
565    amplitudes: Option<Vec<Complex64>>,
566}
567
568impl QftBuilder {
569    /// Create a new builder for the specified number of qubits.
570    pub fn new(num_qubits: usize) -> Self {
571        Self {
572            num_qubits,
573            config: QftConfig::default(),
574            metadata: HashMap::new(),
575            amplitudes: None,
576        }
577    }
578
579    /// Set the bond dimension.
580    pub fn bond_dimension(mut self, dim: usize) -> Self {
581        self.config.bond_dimension = dim;
582        self
583    }
584
585    /// Enable or disable Golay error correction.
586    pub fn golay(mut self, enabled: bool) -> Self {
587        self.config.golay_enabled = enabled;
588        self
589    }
590
591    /// Set the truncation threshold.
592    pub fn truncation_threshold(mut self, threshold: f64) -> Self {
593        self.config.truncation_threshold = threshold;
594        self
595    }
596
597    /// Add a metadata entry.
598    pub fn metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
599        self.metadata.insert(key.into(), value.into());
600        self
601    }
602
603    /// Set initial amplitudes.
604    pub fn amplitudes(mut self, amplitudes: Vec<Complex64>) -> Self {
605        self.amplitudes = Some(amplitudes);
606        self
607    }
608
609    /// Build the QFT file.
610    pub fn build(self) -> Result<QftFile> {
611        let mut file = QftFile::with_config(self.num_qubits, self.config)?;
612        file.metadata = self.metadata;
613
614        if let Some(amps) = self.amplitudes {
615            file.set_amplitudes(&amps)?;
616        }
617
618        Ok(file)
619    }
620}
621
622// =============================================================================
623// Section 4: Common States
624// =============================================================================
625
626/// Create a Bell state (|00⟩ + |11⟩) / √2.
627pub fn bell_state() -> Result<QftFile> {
628    let sqrt2_inv = 1.0 / 2.0_f64.sqrt();
629    QftFile::from_amplitudes(vec![
630        Complex64::new(sqrt2_inv, 0.0),
631        Complex64::new(0.0, 0.0),
632        Complex64::new(0.0, 0.0),
633        Complex64::new(sqrt2_inv, 0.0),
634    ])
635}
636
637/// Create a GHZ state (|0...0⟩ + |1...1⟩) / √2.
638pub fn ghz_state(num_qubits: usize) -> Result<QftFile> {
639    let mut state = QftFile::new(num_qubits)?;
640    let sqrt2_inv = 1.0 / 2.0_f64.sqrt();
641    let last_idx = state.dimension() - 1;
642    state.amplitudes[0] = Complex64::new(sqrt2_inv, 0.0);
643    state.amplitudes[last_idx] = Complex64::new(sqrt2_inv, 0.0);
644    Ok(state)
645}
646
647/// Create a uniform superposition state.
648pub fn uniform_state(num_qubits: usize) -> Result<QftFile> {
649    let dim = 1usize << num_qubits;
650    let amp = 1.0 / (dim as f64).sqrt();
651    let amplitudes = vec![Complex64::new(amp, 0.0); dim];
652    QftFile::from_amplitudes(amplitudes)
653}
654
655/// Create a computational basis state |i⟩.
656pub fn basis_state(num_qubits: usize, index: usize) -> Result<QftFile> {
657    let mut state = QftFile::new(num_qubits)?;
658    if index >= state.dimension() {
659        return Err(QftError::IndexOutOfRange(index, num_qubits));
660    }
661    state.amplitudes[0] = Complex64::new(0.0, 0.0);
662    state.amplitudes[index] = Complex64::new(1.0, 0.0);
663    Ok(state)
664}
665
666// =============================================================================
667// Section 5: Async Support
668// =============================================================================
669
670#[cfg(feature = "async")]
671#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
672pub mod async_io {
673    //! Async I/O operations for QFT files.
674
675    use super::*;
676    use tokio::fs::File;
677    use tokio::io::{AsyncReadExt, AsyncWriteExt, BufReader, BufWriter};
678
679    impl QftFile {
680        /// Asynchronously load a QFT file.
681        pub async fn load_async(path: impl AsRef<Path>) -> Result<Self> {
682            let file = File::open(path).await?;
683            let mut reader = BufReader::new(file);
684
685            let mut header = [0u8; 16];
686            reader.read_exact(&mut header).await?;
687
688            if &header[0..4] != b"QFT\x01" {
689                return Err(QftError::InvalidFormat("Invalid magic number".to_string()));
690            }
691
692            let num_qubits = header[4] as usize;
693            if num_qubits == 0 || num_qubits > 30 {
694                return Err(QftError::InvalidQubits(num_qubits));
695            }
696
697            let bond_dimension = header[5] as usize;
698            let golay_enabled = header[6] != 0;
699
700            let dim = 1usize << num_qubits;
701            let mut amplitudes = Vec::with_capacity(dim);
702
703            for _ in 0..dim {
704                let mut buf = [0u8; 16];
705                reader.read_exact(&mut buf).await?;
706                let real = f64::from_le_bytes(buf[0..8].try_into().unwrap());
707                let imag = f64::from_le_bytes(buf[8..16].try_into().unwrap());
708                amplitudes.push(Complex64::new(real, imag));
709            }
710
711            Ok(Self {
712                num_qubits,
713                amplitudes,
714                metadata: HashMap::new(),
715                config: QftConfig {
716                    bond_dimension: if bond_dimension == 0 { 64 } else { bond_dimension },
717                    golay_enabled,
718                    truncation_threshold: 1e-10,
719                },
720            })
721        }
722
723        /// Asynchronously save a QFT file.
724        pub async fn save_async(&self, path: impl AsRef<Path>) -> Result<()> {
725            let file = File::create(path).await?;
726            let mut writer = BufWriter::new(file);
727
728            // Magic number
729            writer.write_all(b"QFT\x01").await?;
730
731            // Header
732            writer
733                .write_all(&[
734                    self.num_qubits as u8,
735                    self.config.bond_dimension.min(255) as u8,
736                    if self.config.golay_enabled { 1 } else { 0 },
737                    0,
738                ])
739                .await?;
740
741            // Padding
742            writer.write_all(&[0u8; 8]).await?;
743
744            // Amplitudes
745            for a in &self.amplitudes {
746                writer.write_all(&a.re.to_le_bytes()).await?;
747                writer.write_all(&a.im.to_le_bytes()).await?;
748            }
749
750            writer.flush().await?;
751            Ok(())
752        }
753    }
754}
755
756// =============================================================================
757// Section 6: Trait Implementations
758// =============================================================================
759
760impl std::fmt::Display for QftFile {
761    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
762        write!(
763            f,
764            "QftFile({} qubits, dim={}, norm={:.6})",
765            self.num_qubits,
766            self.dimension(),
767            self.norm()
768        )
769    }
770}
771
772impl std::ops::Index<usize> for QftFile {
773    type Output = Complex64;
774
775    fn index(&self, index: usize) -> &Self::Output {
776        &self.amplitudes[index]
777    }
778}
779
780impl std::ops::IndexMut<usize> for QftFile {
781    fn index_mut(&mut self, index: usize) -> &mut Self::Output {
782        &mut self.amplitudes[index]
783    }
784}
785
786impl IntoIterator for QftFile {
787    type Item = Complex64;
788    type IntoIter = std::vec::IntoIter<Complex64>;
789
790    fn into_iter(self) -> Self::IntoIter {
791        self.amplitudes.into_iter()
792    }
793}
794
795impl<'a> IntoIterator for &'a QftFile {
796    type Item = &'a Complex64;
797    type IntoIter = std::slice::Iter<'a, Complex64>;
798
799    fn into_iter(self) -> Self::IntoIter {
800        self.amplitudes.iter()
801    }
802}
803
804// =============================================================================
805// Section 7: Re-exports
806// =============================================================================
807
808/// Prelude for convenient imports.
809pub mod prelude {
810    pub use super::{
811        basis_state, bell_state, ghz_state, uniform_state, QftBuilder, QftConfig, QftError,
812        QftFile, Result,
813    };
814    pub use num_complex::Complex64;
815}
816
817// =============================================================================
818// Section 8: Tests
819// =============================================================================
820
821#[cfg(test)]
822mod tests {
823    use super::*;
824
825    #[test]
826    fn test_create() {
827        let state = QftFile::new(4).unwrap();
828        assert_eq!(state.num_qubits(), 4);
829        assert_eq!(state.dimension(), 16);
830        assert!(state.is_normalized(1e-10));
831    }
832
833    #[test]
834    fn test_invalid_qubits() {
835        assert!(QftFile::new(0).is_err());
836        assert!(QftFile::new(31).is_err());
837    }
838
839    #[test]
840    fn test_amplitudes() {
841        let mut state = QftFile::new(2).unwrap();
842        assert_eq!(state[0], Complex64::new(1.0, 0.0));
843
844        state[0] = Complex64::new(0.0, 0.0);
845        state[3] = Complex64::new(1.0, 0.0);
846
847        assert_eq!(state.get_amplitude(3).unwrap(), Complex64::new(1.0, 0.0));
848    }
849
850    #[test]
851    fn test_normalization() {
852        let mut state = QftFile::new(2).unwrap();
853        state[0] = Complex64::new(1.0, 0.0);
854        state[1] = Complex64::new(1.0, 0.0);
855
856        assert!(!state.is_normalized(1e-10));
857        state.normalize().unwrap();
858        assert!(state.is_normalized(1e-10));
859    }
860
861    #[test]
862    fn test_fidelity() {
863        let state1 = QftFile::new(2).unwrap();
864        let state2 = QftFile::new(2).unwrap();
865
866        let fid = state1.fidelity(&state2).unwrap();
867        assert!((fid - 1.0).abs() < 1e-10);
868
869        let orthogonal = basis_state(2, 1).unwrap();
870        let fid = state1.fidelity(&orthogonal).unwrap();
871        assert!(fid.abs() < 1e-10);
872    }
873
874    #[test]
875    fn test_bell_state() {
876        let bell = bell_state().unwrap();
877        assert_eq!(bell.num_qubits(), 2);
878        assert!(bell.is_normalized(1e-10));
879    }
880
881    #[test]
882    fn test_ghz_state() {
883        let ghz = ghz_state(4).unwrap();
884        assert_eq!(ghz.num_qubits(), 4);
885        assert!(ghz.is_normalized(1e-10));
886    }
887
888    #[test]
889    fn test_builder() {
890        let state = QftBuilder::new(4)
891            .bond_dimension(128)
892            .golay(false)
893            .metadata("test", "value")
894            .build()
895            .unwrap();
896
897        assert_eq!(state.config().bond_dimension, 128);
898        assert!(!state.config().golay_enabled);
899        assert_eq!(state.get_metadata("test"), Some("value"));
900    }
901
902    #[test]
903    fn test_serialization() {
904        let state = bell_state().unwrap();
905        let bytes = state.to_bytes().unwrap();
906        let restored = QftFile::from_bytes(&bytes).unwrap();
907
908        assert!((state.fidelity(&restored).unwrap() - 1.0).abs() < 1e-10);
909    }
910
911    #[test]
912    fn test_json() {
913        let state = bell_state().unwrap();
914        let json = state.to_json().unwrap();
915        let restored = QftFile::from_json(&json).unwrap();
916
917        assert!((state.fidelity(&restored).unwrap() - 1.0).abs() < 1e-10);
918    }
919}