1#![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#[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
83pub type Result<T> = std::result::Result<T, QftError>;
85
86#[derive(Debug, Clone, Serialize, Deserialize)]
92pub struct QftConfig {
93 pub bond_dimension: usize,
95 pub golay_enabled: bool,
97 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#[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 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); Ok(Self {
144 num_qubits,
145 amplitudes,
146 metadata: HashMap::new(),
147 config: QftConfig::default(),
148 })
149 }
150
151 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 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 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 #[inline]
220 pub fn num_qubits(&self) -> usize {
221 self.num_qubits
222 }
223
224 #[inline]
226 pub fn dimension(&self) -> usize {
227 1 << self.num_qubits
228 }
229
230 pub fn config(&self) -> &QftConfig {
232 &self.config
233 }
234
235 pub fn config_mut(&mut self) -> &mut QftConfig {
237 &mut self.config
238 }
239
240 #[inline]
246 pub fn amplitudes(&self) -> &[Complex64] {
247 &self.amplitudes
248 }
249
250 #[inline]
252 pub fn amplitudes_mut(&mut self) -> &mut [Complex64] {
253 &mut self.amplitudes
254 }
255
256 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 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 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 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 pub fn get_metadata(&self, key: &str) -> Option<&str> {
296 self.metadata.get(key).map(|s| s.as_str())
297 }
298
299 pub fn metadata(&self) -> &HashMap<String, String> {
301 &self.metadata
302 }
303
304 pub fn metadata_mut(&mut self) -> &mut HashMap<String, String> {
306 &mut self.metadata
307 }
308
309 pub fn norm_squared(&self) -> f64 {
315 self.amplitudes.iter().map(|a| a.norm_sqr()).sum()
316 }
317
318 pub fn norm(&self) -> f64 {
320 self.norm_squared().sqrt()
321 }
322
323 pub fn is_normalized(&self, tolerance: f64) -> bool {
325 (self.norm_squared() - 1.0).abs() < tolerance
326 }
327
328 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 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 pub fn fidelity(&self, other: &QftFile) -> Result<f64> {
361 let overlap = self.inner_product(other)?;
362 Ok(overlap.norm_sqr())
363 }
364
365 pub fn trace_distance(&self, other: &QftFile) -> Result<f64> {
367 let fid = self.fidelity(other)?;
368 Ok((1.0 - fid).sqrt())
369 }
370
371 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 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 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 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 pub fn write_to<W: Write>(&self, writer: &mut W) -> Result<()> {
445 writer.write_all(b"QFT\x01")?;
447
448 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, ])?;
455
456 writer.write_all(&[0u8; 8])?;
458
459 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 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 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 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 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
543pub struct QftBuilder {
562 num_qubits: usize,
563 config: QftConfig,
564 metadata: HashMap<String, String>,
565 amplitudes: Option<Vec<Complex64>>,
566}
567
568impl QftBuilder {
569 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 pub fn bond_dimension(mut self, dim: usize) -> Self {
581 self.config.bond_dimension = dim;
582 self
583 }
584
585 pub fn golay(mut self, enabled: bool) -> Self {
587 self.config.golay_enabled = enabled;
588 self
589 }
590
591 pub fn truncation_threshold(mut self, threshold: f64) -> Self {
593 self.config.truncation_threshold = threshold;
594 self
595 }
596
597 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 pub fn amplitudes(mut self, amplitudes: Vec<Complex64>) -> Self {
605 self.amplitudes = Some(amplitudes);
606 self
607 }
608
609 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(&s)?;
616 }
617
618 Ok(file)
619 }
620}
621
622pub 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
637pub 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
647pub 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
655pub 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#[cfg(feature = "async")]
671#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
672pub mod async_io {
673 use super::*;
676 use tokio::fs::File;
677 use tokio::io::{AsyncReadExt, AsyncWriteExt, BufReader, BufWriter};
678
679 impl QftFile {
680 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 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 writer.write_all(b"QFT\x01").await?;
730
731 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 writer.write_all(&[0u8; 8]).await?;
743
744 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
756impl 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
804pub 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#[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}