1use quantrs2_circuit::builder::{Circuit, Simulator};
8use quantrs2_core::{
9 buffer_pool::BufferPool,
10 error::{QuantRS2Error, QuantRS2Result},
11 gate::GateOp,
12 qubit::QubitId,
13};
14use scirs2_core::parallel_ops::{IndexedParallelIterator, ParallelIterator}; use memmap2::{MmapMut, MmapOptions};
21use scirs2_core::ndarray::{Array1, Array2, Array3, ArrayView1, ArrayView2};
22use scirs2_core::Complex64;
23use serde::{Deserialize, Serialize};
24use std::collections::{BTreeMap, HashMap, HashSet, VecDeque};
25use std::fmt;
26use std::fs::{File, OpenOptions};
27use std::io::{BufReader, BufWriter, Read, Seek, SeekFrom, Write};
28use std::path::{Path, PathBuf};
29use std::sync::{Arc, Mutex, RwLock};
30use uuid::Uuid;
31
32#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct LargeScaleSimulatorConfig {
35 pub max_qubits: usize,
37
38 pub enable_sparse_representation: bool,
40
41 pub enable_compression: bool,
43
44 pub enable_memory_mapping: bool,
46
47 pub enable_chunked_processing: bool,
49
50 pub chunk_size: usize,
52
53 pub sparsity_threshold: f64,
55
56 pub compression_threshold: usize,
58
59 pub memory_mapping_threshold: usize,
61
62 pub working_directory: PathBuf,
64
65 pub enable_scirs2_optimizations: bool,
67
68 pub memory_budget: usize,
70
71 pub enable_adaptive_precision: bool,
73
74 pub precision_tolerance: f64,
76}
77
78impl Default for LargeScaleSimulatorConfig {
79 fn default() -> Self {
80 Self {
81 max_qubits: 50,
82 enable_sparse_representation: true,
83 enable_compression: true,
84 enable_memory_mapping: true,
85 enable_chunked_processing: true,
86 chunk_size: 1024 * 1024, sparsity_threshold: 0.1, compression_threshold: 1024 * 1024 * 8, memory_mapping_threshold: 1024 * 1024 * 64, working_directory: std::env::temp_dir().join("quantrs_large_scale"),
91 enable_scirs2_optimizations: true,
92 memory_budget: 8 * 1024 * 1024 * 1024, enable_adaptive_precision: true,
94 precision_tolerance: 1e-12,
95 }
96 }
97}
98
99#[derive(Debug, Clone)]
101pub struct SimpleSparseMatrix {
102 values: Vec<Complex64>,
104 col_indices: Vec<usize>,
106 row_ptr: Vec<usize>,
108 rows: usize,
110 cols: usize,
111}
112
113impl SimpleSparseMatrix {
114 #[must_use]
115 pub fn from_dense(dense: &[Complex64], threshold: f64) -> Self {
116 let rows = dense.len();
117 let cols = 1; let mut values = Vec::new();
119 let mut col_indices = Vec::new();
120 let mut row_ptr = vec![0];
121
122 for (i, &val) in dense.iter().enumerate() {
123 if val.norm() > threshold {
124 values.push(val);
125 col_indices.push(0); }
127 row_ptr.push(values.len());
128 }
129
130 Self {
131 values,
132 col_indices,
133 row_ptr,
134 rows,
135 cols,
136 }
137 }
138
139 #[must_use]
140 pub fn to_dense(&self) -> Vec<Complex64> {
141 let mut dense = vec![Complex64::new(0.0, 0.0); self.rows];
142
143 for row in 0..self.rows {
150 if row + 1 < self.row_ptr.len() {
151 let start = self.row_ptr[row];
152 let end = self.row_ptr[row + 1];
153 if start < end && start < self.values.len() {
154 dense[row] = self.values[start];
155 }
156 }
157 }
158
159 dense
160 }
161
162 #[must_use]
163 pub fn nnz(&self) -> usize {
164 self.values.len()
165 }
166
167 #[must_use]
169 pub fn get_amplitude(&self, row: usize) -> Complex64 {
170 if row >= self.rows || row + 1 >= self.row_ptr.len() {
171 return Complex64::new(0.0, 0.0);
172 }
173 let start = self.row_ptr[row];
174 let end = self.row_ptr[row + 1];
175 if start < end && start < self.values.len() {
176 self.values[start]
177 } else {
178 Complex64::new(0.0, 0.0)
179 }
180 }
181
182 #[must_use]
184 pub fn from_sparse_map(
185 amplitudes: &HashMap<usize, Complex64>,
186 dimension: usize,
187 threshold: f64,
188 ) -> Self {
189 let rows = dimension;
190 let cols = 1;
191 let mut values = Vec::new();
192 let mut col_indices = Vec::new();
193 let mut row_ptr = vec![0usize; rows + 1];
194
195 for (&idx, &val) in amplitudes {
197 if idx < rows && val.norm() > threshold {
198 row_ptr[idx + 1] = 1;
199 }
200 }
201
202 for i in 1..=rows {
204 row_ptr[i] += row_ptr[i - 1];
205 }
206
207 let nnz = row_ptr[rows];
208 values.resize(nnz, Complex64::new(0.0, 0.0));
209 col_indices.resize(nnz, 0usize);
210
211 let mut fill_pos = vec![0usize; rows];
213 fill_pos[..rows].copy_from_slice(&row_ptr[..rows]);
214
215 for (&idx, &val) in amplitudes {
216 if idx < rows && val.norm() > threshold {
217 let pos = fill_pos[idx];
218 values[pos] = val;
219 col_indices[pos] = 0;
220 fill_pos[idx] += 1;
221 }
222 }
223
224 Self {
225 values,
226 col_indices,
227 row_ptr,
228 rows,
229 cols,
230 }
231 }
232
233 #[must_use]
235 pub fn to_sparse_map(&self) -> HashMap<usize, Complex64> {
236 let mut map = HashMap::new();
237 for row in 0..self.rows {
238 if row + 1 < self.row_ptr.len() {
239 let start = self.row_ptr[row];
240 let end = self.row_ptr[row + 1];
241 if start < end && start < self.values.len() {
242 let val = self.values[start];
243 if val.norm() > 0.0 {
244 map.insert(row, val);
245 }
246 }
247 }
248 }
249 map
250 }
251}
252
253#[derive(Debug)]
255pub struct SparseQuantumState {
256 sparse_amplitudes: SimpleSparseMatrix,
258
259 num_qubits: usize,
261
262 dimension: usize,
264
265 nonzero_indices: HashMap<usize, usize>,
267
268 sparsity_ratio: f64,
270}
271
272impl SparseQuantumState {
273 pub fn new(num_qubits: usize) -> QuantRS2Result<Self> {
275 let dimension = 1usize << num_qubits;
276
277 let mut dense = vec![Complex64::new(0.0, 0.0); dimension];
279 dense[0] = Complex64::new(1.0, 0.0);
280
281 let sparse_amplitudes = SimpleSparseMatrix::from_dense(&dense, 1e-15);
282
283 let mut nonzero_indices = HashMap::new();
284 nonzero_indices.insert(0, 0);
285
286 Ok(Self {
287 sparse_amplitudes,
288 num_qubits,
289 dimension,
290 nonzero_indices,
291 sparsity_ratio: 1.0 / dimension as f64,
292 })
293 }
294
295 pub fn from_dense(amplitudes: &[Complex64], threshold: f64) -> QuantRS2Result<Self> {
297 let num_qubits = (amplitudes.len() as f64).log2() as usize;
298 let dimension = amplitudes.len();
299
300 let mut nonzero_indices = HashMap::new();
302 let mut nonzero_count = 0;
303
304 for (i, &litude) in amplitudes.iter().enumerate() {
305 if amplitude.norm() > threshold {
306 nonzero_indices.insert(i, nonzero_count);
307 nonzero_count += 1;
308 }
309 }
310
311 let sparse_amplitudes = SimpleSparseMatrix::from_dense(amplitudes, threshold);
312 let sparsity_ratio = nonzero_count as f64 / dimension as f64;
313
314 Ok(Self {
315 sparse_amplitudes,
316 num_qubits,
317 dimension,
318 nonzero_indices,
319 sparsity_ratio,
320 })
321 }
322
323 pub fn to_dense(&self) -> QuantRS2Result<Vec<Complex64>> {
325 Ok(self.sparse_amplitudes.to_dense())
326 }
327
328 pub fn apply_sparse_gate(&mut self, gate: &dyn GateOp) -> QuantRS2Result<()> {
340 let qubits = gate.qubits();
341 match qubits.len() {
342 1 => {
343 let target = qubits[0].id() as usize;
344 let matrix = gate.matrix()?;
345 self.apply_single_qubit_sparse(&matrix, target)?;
346 }
347 2 => {
348 let q0 = qubits[0].id() as usize;
349 let q1 = qubits[1].id() as usize;
350 let matrix = gate.matrix()?;
351 self.apply_two_qubit_sparse(&matrix, q0, q1)?;
352 }
353 _ => {
354 let matrix = gate.matrix()?;
356 self.apply_dense_gate(&matrix, &qubits)?;
357 }
358 }
359
360 Ok(())
361 }
362
363 fn apply_single_qubit_sparse(
371 &mut self,
372 matrix: &[Complex64],
373 target: usize,
374 ) -> QuantRS2Result<()> {
375 if matrix.len() < 4 {
376 return Err(QuantRS2Error::InvalidInput(
377 "Single-qubit gate matrix must have 4 elements".to_string(),
378 ));
379 }
380 if target >= self.num_qubits {
381 return Err(QuantRS2Error::InvalidInput(format!(
382 "Target qubit {} out of range (num_qubits={})",
383 target, self.num_qubits
384 )));
385 }
386
387 const THRESHOLD: f64 = 1e-12;
388
389 let current: HashMap<usize, Complex64> = self.sparse_amplitudes.to_sparse_map();
391
392 let target_mask = 1usize << target;
393 let mut visited: HashSet<usize> = HashSet::new();
396 let mut new_amplitudes: HashMap<usize, Complex64> = HashMap::new();
397
398 for &idx in current.keys() {
399 let i0 = idx & !target_mask;
401 if visited.contains(&i0) {
402 continue;
403 }
404 visited.insert(i0);
405 let i1 = i0 | target_mask;
406
407 let a0 = current
408 .get(&i0)
409 .copied()
410 .unwrap_or(Complex64::new(0.0, 0.0));
411 let a1 = current
412 .get(&i1)
413 .copied()
414 .unwrap_or(Complex64::new(0.0, 0.0));
415
416 let new0 = matrix[0] * a0 + matrix[1] * a1;
417 let new1 = matrix[2] * a0 + matrix[3] * a1;
418
419 if new0.norm() > THRESHOLD {
420 new_amplitudes.insert(i0, new0);
421 }
422 if new1.norm() > THRESHOLD {
423 new_amplitudes.insert(i1, new1);
424 }
425 }
426
427 self.nonzero_indices.clear();
429 for (pos, (&idx, _)) in new_amplitudes.iter().enumerate() {
430 self.nonzero_indices.insert(idx, pos);
431 }
432 self.sparse_amplitudes =
433 SimpleSparseMatrix::from_sparse_map(&new_amplitudes, self.dimension, THRESHOLD);
434 self.sparsity_ratio = new_amplitudes.len() as f64 / self.dimension as f64;
435
436 Ok(())
437 }
438
439 fn apply_two_qubit_sparse(
445 &mut self,
446 matrix: &[Complex64],
447 q0: usize,
448 q1: usize,
449 ) -> QuantRS2Result<()> {
450 if matrix.len() < 16 {
451 return Err(QuantRS2Error::InvalidInput(
452 "Two-qubit gate matrix must have 16 elements".to_string(),
453 ));
454 }
455 if q0 >= self.num_qubits || q1 >= self.num_qubits {
456 return Err(QuantRS2Error::InvalidInput(format!(
457 "Qubit indices {},{} out of range (num_qubits={})",
458 q0, q1, self.num_qubits
459 )));
460 }
461
462 const THRESHOLD: f64 = 1e-12;
463
464 let current: HashMap<usize, Complex64> = self.sparse_amplitudes.to_sparse_map();
465 let mask0 = 1usize << q0;
466 let mask1 = 1usize << q1;
467 let both_mask = mask0 | mask1;
468
469 let mut visited: HashSet<usize> = HashSet::new();
471 let mut new_amplitudes: HashMap<usize, Complex64> = HashMap::new();
472
473 for &idx in current.keys() {
474 let base = idx & !both_mask;
475 if visited.contains(&base) {
476 continue;
477 }
478 visited.insert(base);
479
480 let i00 = base;
482 let i01 = base | mask1;
483 let i10 = base | mask0;
484 let i11 = base | both_mask;
485
486 let a00 = current
487 .get(&i00)
488 .copied()
489 .unwrap_or(Complex64::new(0.0, 0.0));
490 let a01 = current
491 .get(&i01)
492 .copied()
493 .unwrap_or(Complex64::new(0.0, 0.0));
494 let a10 = current
495 .get(&i10)
496 .copied()
497 .unwrap_or(Complex64::new(0.0, 0.0));
498 let a11 = current
499 .get(&i11)
500 .copied()
501 .unwrap_or(Complex64::new(0.0, 0.0));
502
503 let inputs = [a00, a01, a10, a11];
504 let indices = [i00, i01, i10, i11];
505
506 for (row, &out_idx) in indices.iter().enumerate() {
507 let mut new_val = Complex64::new(0.0, 0.0);
508 for (col, &inp) in inputs.iter().enumerate() {
509 new_val += matrix[row * 4 + col] * inp;
510 }
511 if new_val.norm() > THRESHOLD {
512 new_amplitudes.insert(out_idx, new_val);
513 }
514 }
515 }
516
517 self.nonzero_indices.clear();
518 for (pos, (&idx, _)) in new_amplitudes.iter().enumerate() {
519 self.nonzero_indices.insert(idx, pos);
520 }
521 self.sparse_amplitudes =
522 SimpleSparseMatrix::from_sparse_map(&new_amplitudes, self.dimension, THRESHOLD);
523 self.sparsity_ratio = new_amplitudes.len() as f64 / self.dimension as f64;
524
525 Ok(())
526 }
527
528 fn apply_pauli_x_sparse(&mut self, target: usize) -> QuantRS2Result<()> {
530 let mut new_nonzero_indices = HashMap::new();
532 let target_mask = 1usize << target;
533
534 for (&old_idx, &pos) in &self.nonzero_indices {
535 let new_idx = old_idx ^ target_mask;
536 new_nonzero_indices.insert(new_idx, pos);
537 }
538
539 self.nonzero_indices = new_nonzero_indices;
540
541 self.update_sparse_matrix()?;
543
544 Ok(())
545 }
546
547 fn apply_hadamard_sparse(&mut self, target: usize) -> QuantRS2Result<()> {
549 let dense = self.to_dense()?;
553 let mut new_dense = vec![Complex64::new(0.0, 0.0); self.dimension];
554
555 let h_00 = Complex64::new(1.0 / 2.0_f64.sqrt(), 0.0);
556 let h_01 = Complex64::new(1.0 / 2.0_f64.sqrt(), 0.0);
557 let h_10 = Complex64::new(1.0 / 2.0_f64.sqrt(), 0.0);
558 let h_11 = Complex64::new(-1.0 / 2.0_f64.sqrt(), 0.0);
559
560 let target_mask = 1usize << target;
561
562 for i in 0..self.dimension {
563 let paired_idx = i ^ target_mask;
564 let bit_val = (i >> target) & 1;
565
566 if bit_val == 0 {
567 new_dense[i] = h_00 * dense[i] + h_01 * dense[paired_idx];
568 new_dense[paired_idx] = h_10 * dense[i] + h_11 * dense[paired_idx];
569 }
570 }
571
572 *self = Self::from_dense(&new_dense, 1e-15)?;
573
574 Ok(())
575 }
576
577 fn apply_dense_gate(&mut self, matrix: &[Complex64], qubits: &[QubitId]) -> QuantRS2Result<()> {
586 let mut dense = self.to_dense()?;
588
589 let k = qubits.len();
590 if k == 0 {
591 return Err(QuantRS2Error::InvalidInput(
592 "Gate must act on at least one qubit".to_string(),
593 ));
594 }
595 let gate_dim = 1usize << k;
596 if matrix.len() != gate_dim * gate_dim {
597 return Err(QuantRS2Error::InvalidInput(format!(
598 "Gate matrix has {} elements, expected {} for {} qubit(s)",
599 matrix.len(),
600 gate_dim * gate_dim,
601 k
602 )));
603 }
604
605 let mut qubit_indices = Vec::with_capacity(k);
606 let mut combined_mask = 0usize;
607 for q in qubits {
608 let idx = q.id() as usize;
609 if idx >= self.num_qubits {
610 return Err(QuantRS2Error::InvalidInput(format!(
611 "Target qubit {} out of range (num_qubits={})",
612 idx, self.num_qubits
613 )));
614 }
615 qubit_indices.push(idx);
616 combined_mask |= 1usize << idx;
617 }
618
619 let mut indices = vec![0usize; gate_dim];
620 let mut inputs = vec![Complex64::new(0.0, 0.0); gate_dim];
621 for base in 0..self.dimension {
622 if base & combined_mask != 0 {
625 continue;
626 }
627 for (g, (idx_slot, in_slot)) in indices.iter_mut().zip(inputs.iter_mut()).enumerate() {
628 let mut idx = base;
629 for (b, &q) in qubit_indices.iter().enumerate() {
630 if (g >> (k - 1 - b)) & 1 == 1 {
631 idx |= 1usize << q;
632 }
633 }
634 *idx_slot = idx;
635 *in_slot = dense[idx];
636 }
637 for row in 0..gate_dim {
638 let mut acc = Complex64::new(0.0, 0.0);
639 for (col, &inp) in inputs.iter().enumerate() {
640 acc += matrix[row * gate_dim + col] * inp;
641 }
642 dense[indices[row]] = acc;
643 }
644 }
645
646 let nonzero_count = dense.iter().filter(|&&x| x.norm() > 1e-15).count();
648 let new_sparsity = nonzero_count as f64 / self.dimension as f64;
649
650 if new_sparsity < 0.5 {
651 *self = Self::from_dense(&dense, 1e-15)?;
653 } else {
654 return Err(QuantRS2Error::ComputationError(
656 "State no longer sparse".to_string(),
657 ));
658 }
659
660 Ok(())
661 }
662
663 fn update_sparse_matrix(&mut self) -> QuantRS2Result<()> {
665 let mut dense = vec![Complex64::new(0.0, 0.0); self.dimension];
667
668 for &idx in self.nonzero_indices.keys() {
669 if idx < dense.len() {
670 dense[idx] = Complex64::new(1.0 / (self.nonzero_indices.len() as f64).sqrt(), 0.0);
672 }
673 }
674
675 self.sparse_amplitudes = SimpleSparseMatrix::from_dense(&dense, 1e-15);
676 self.sparsity_ratio = self.nonzero_indices.len() as f64 / self.dimension as f64;
677
678 Ok(())
679 }
680
681 #[must_use]
683 pub const fn sparsity_ratio(&self) -> f64 {
684 self.sparsity_ratio
685 }
686
687 #[must_use]
689 pub fn memory_usage(&self) -> usize {
690 self.nonzero_indices.len()
691 * (std::mem::size_of::<usize>() + std::mem::size_of::<Complex64>())
692 }
693}
694
695#[derive(Debug)]
697pub struct SimpleCompressionEngine {
698 buffer: Vec<u8>,
700}
701
702impl Default for SimpleCompressionEngine {
703 fn default() -> Self {
704 Self::new()
705 }
706}
707
708impl SimpleCompressionEngine {
709 #[must_use]
710 pub const fn new() -> Self {
711 Self { buffer: Vec::new() }
712 }
713
714 pub fn compress_lz4(&self, data: &[u8]) -> Result<Vec<u8>, String> {
716 oxiarc_deflate::zlib::zlib_compress(data, 6).map_err(|e| format!("Compression failed: {e}"))
717 }
718
719 pub fn decompress_lz4(&self, data: &[u8]) -> Result<Vec<u8>, String> {
721 oxiarc_deflate::zlib::zlib_decompress(data)
722 .map_err(|e| format!("Decompression failed: {e}"))
723 }
724
725 pub fn compress_huffman(&self, data: &[u8]) -> Result<Vec<u8>, String> {
727 self.compress_lz4(data)
729 }
730
731 pub fn decompress_huffman(&self, data: &[u8]) -> Result<Vec<u8>, String> {
733 self.decompress_lz4(data)
735 }
736}
737
738#[derive(Debug)]
740pub struct CompressedQuantumState {
741 compressed_data: Vec<u8>,
743
744 compression_metadata: CompressionMetadata,
746
747 compression_engine: SimpleCompressionEngine,
749
750 num_qubits: usize,
752
753 original_size: usize,
755}
756
757#[derive(Debug, Clone, Serialize, Deserialize)]
759pub struct CompressionMetadata {
760 pub algorithm: CompressionAlgorithm,
762
763 pub compression_ratio: f64,
765
766 pub original_size: usize,
768
769 pub compressed_size: usize,
771
772 pub checksum: u64,
774}
775
776#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
778pub enum CompressionAlgorithm {
779 Huffman,
781 LZ4,
783 QuantumAmplitude,
785 None,
787}
788
789impl CompressedQuantumState {
790 pub fn from_dense(
792 amplitudes: &[Complex64],
793 algorithm: CompressionAlgorithm,
794 ) -> QuantRS2Result<Self> {
795 let num_qubits = (amplitudes.len() as f64).log2() as usize;
796 let original_size = std::mem::size_of_val(amplitudes);
797
798 let amplitude_bytes: &[u8] =
800 unsafe { std::slice::from_raw_parts(amplitudes.as_ptr().cast::<u8>(), original_size) };
801
802 let compression_engine = SimpleCompressionEngine::new();
804
805 let (compressed_data, metadata) = match algorithm {
806 CompressionAlgorithm::Huffman => {
807 let compressed = compression_engine
808 .compress_huffman(amplitude_bytes)
809 .map_err(|e| {
810 QuantRS2Error::ComputationError(format!("Huffman compression failed: {e}"))
811 })?;
812
813 let metadata = CompressionMetadata {
814 algorithm: CompressionAlgorithm::Huffman,
815 compression_ratio: original_size as f64 / compressed.len() as f64,
816 original_size,
817 compressed_size: compressed.len(),
818 checksum: Self::calculate_checksum(amplitude_bytes),
819 };
820
821 (compressed, metadata)
822 }
823 CompressionAlgorithm::LZ4 => {
824 let compressed = compression_engine
825 .compress_lz4(amplitude_bytes)
826 .map_err(|e| {
827 QuantRS2Error::ComputationError(format!("LZ4 compression failed: {e}"))
828 })?;
829
830 let metadata = CompressionMetadata {
831 algorithm: CompressionAlgorithm::LZ4,
832 compression_ratio: original_size as f64 / compressed.len() as f64,
833 original_size,
834 compressed_size: compressed.len(),
835 checksum: Self::calculate_checksum(amplitude_bytes),
836 };
837
838 (compressed, metadata)
839 }
840 CompressionAlgorithm::QuantumAmplitude => {
841 let compressed = Self::compress_quantum_amplitudes(amplitudes)?;
843
844 let metadata = CompressionMetadata {
845 algorithm: CompressionAlgorithm::QuantumAmplitude,
846 compression_ratio: original_size as f64 / compressed.len() as f64,
847 original_size,
848 compressed_size: compressed.len(),
849 checksum: Self::calculate_checksum(amplitude_bytes),
850 };
851
852 (compressed, metadata)
853 }
854 CompressionAlgorithm::None => {
855 let metadata = CompressionMetadata {
856 algorithm: CompressionAlgorithm::None,
857 compression_ratio: 1.0,
858 original_size,
859 compressed_size: original_size,
860 checksum: Self::calculate_checksum(amplitude_bytes),
861 };
862
863 (amplitude_bytes.to_vec(), metadata)
864 }
865 };
866
867 Ok(Self {
868 compressed_data,
869 compression_metadata: metadata,
870 compression_engine,
871 num_qubits,
872 original_size,
873 })
874 }
875
876 pub fn to_dense(&self) -> QuantRS2Result<Vec<Complex64>> {
878 let decompressed_bytes = match self.compression_metadata.algorithm {
879 CompressionAlgorithm::Huffman => self
880 .compression_engine
881 .decompress_huffman(&self.compressed_data)
882 .map_err(|e| {
883 QuantRS2Error::ComputationError(format!("Huffman decompression failed: {e}"))
884 })?,
885 CompressionAlgorithm::LZ4 => self
886 .compression_engine
887 .decompress_lz4(&self.compressed_data)
888 .map_err(|e| {
889 QuantRS2Error::ComputationError(format!("LZ4 decompression failed: {e}"))
890 })?,
891 CompressionAlgorithm::QuantumAmplitude => {
892 Self::decompress_quantum_amplitudes(&self.compressed_data, self.num_qubits)?
893 }
894 CompressionAlgorithm::None => self.compressed_data.clone(),
895 };
896
897 let checksum = Self::calculate_checksum(&decompressed_bytes);
899 if checksum != self.compression_metadata.checksum {
900 return Err(QuantRS2Error::ComputationError(
901 "Checksum verification failed".to_string(),
902 ));
903 }
904
905 let amplitudes = unsafe {
907 std::slice::from_raw_parts(
908 decompressed_bytes.as_ptr().cast::<Complex64>(),
909 decompressed_bytes.len() / std::mem::size_of::<Complex64>(),
910 )
911 }
912 .to_vec();
913
914 Ok(amplitudes)
915 }
916
917 fn compress_quantum_amplitudes(amplitudes: &[Complex64]) -> QuantRS2Result<Vec<u8>> {
919 let mut compressed = Vec::new();
921
922 for &litude in amplitudes {
923 let magnitude = amplitude.norm();
924 let phase = amplitude.arg();
925
926 let quantized_magnitude = (magnitude * 65_535.0) as u16;
928 let quantized_phase =
929 ((phase + std::f64::consts::PI) / (2.0 * std::f64::consts::PI) * 65_535.0) as u16;
930
931 compressed.extend_from_slice(&quantized_magnitude.to_le_bytes());
932 compressed.extend_from_slice(&quantized_phase.to_le_bytes());
933 }
934
935 Ok(compressed)
936 }
937
938 fn decompress_quantum_amplitudes(data: &[u8], num_qubits: usize) -> QuantRS2Result<Vec<u8>> {
940 let dimension = 1usize << num_qubits;
941 let mut amplitudes = Vec::with_capacity(dimension);
942
943 for i in 0..dimension {
944 let offset = i * 4; if offset + 4 <= data.len() {
946 let magnitude_bytes = [data[offset], data[offset + 1]];
947 let phase_bytes = [data[offset + 2], data[offset + 3]];
948
949 let quantized_magnitude = u16::from_le_bytes(magnitude_bytes);
950 let quantized_phase = u16::from_le_bytes(phase_bytes);
951
952 let magnitude = f64::from(quantized_magnitude) / 65_535.0;
953 let phase = ((f64::from(quantized_phase) / 65_535.0) * 2.0)
954 .mul_add(std::f64::consts::PI, -std::f64::consts::PI);
955
956 let amplitude = Complex64::new(magnitude * phase.cos(), magnitude * phase.sin());
957 amplitudes.push(amplitude);
958 }
959 }
960
961 let amplitude_bytes = unsafe {
963 std::slice::from_raw_parts(
964 amplitudes.as_ptr().cast::<u8>(),
965 amplitudes.len() * std::mem::size_of::<Complex64>(),
966 )
967 };
968
969 Ok(amplitude_bytes.to_vec())
970 }
971
972 fn calculate_checksum(data: &[u8]) -> u64 {
974 data.iter()
976 .enumerate()
977 .map(|(i, &b)| (i as u64).wrapping_mul(u64::from(b)))
978 .sum()
979 }
980
981 #[must_use]
983 pub const fn compression_ratio(&self) -> f64 {
984 self.compression_metadata.compression_ratio
985 }
986
987 #[must_use]
989 pub fn memory_usage(&self) -> usize {
990 self.compressed_data.len()
991 }
992}
993
994#[derive(Debug)]
996pub struct MemoryMappedQuantumState {
997 mmap: MmapMut,
999
1000 file_path: PathBuf,
1002
1003 num_qubits: usize,
1005
1006 dimension: usize,
1008
1009 chunk_size: usize,
1011}
1012
1013impl MemoryMappedQuantumState {
1014 pub fn new(num_qubits: usize, chunk_size: usize, working_dir: &Path) -> QuantRS2Result<Self> {
1016 let dimension = 1usize << num_qubits;
1017 let file_size = dimension * std::mem::size_of::<Complex64>();
1018
1019 std::fs::create_dir_all(working_dir).map_err(|e| {
1021 QuantRS2Error::InvalidInput(format!("Failed to create working directory: {e}"))
1022 })?;
1023
1024 let file_path = working_dir.join(format!("quantum_state_{}.tmp", Uuid::new_v4()));
1025
1026 let file = OpenOptions::new()
1027 .read(true)
1028 .write(true)
1029 .create(true)
1030 .open(&file_path)
1031 .map_err(|e| QuantRS2Error::InvalidInput(format!("Failed to create temp file: {e}")))?;
1032
1033 file.set_len(file_size as u64)
1034 .map_err(|e| QuantRS2Error::InvalidInput(format!("Failed to set file size: {e}")))?;
1035
1036 let mmap = unsafe {
1037 MmapOptions::new().map_mut(&file).map_err(|e| {
1038 QuantRS2Error::InvalidInput(format!("Failed to create memory map: {e}"))
1039 })?
1040 };
1041
1042 let mut state = Self {
1043 mmap,
1044 file_path,
1045 num_qubits,
1046 dimension,
1047 chunk_size,
1048 };
1049
1050 state.initialize_zero_state()?;
1052
1053 Ok(state)
1054 }
1055
1056 fn initialize_zero_state(&mut self) -> QuantRS2Result<()> {
1058 let amplitudes = self.get_amplitudes_mut();
1059
1060 for amplitude in amplitudes.iter_mut() {
1062 *amplitude = Complex64::new(0.0, 0.0);
1063 }
1064
1065 if !amplitudes.is_empty() {
1067 amplitudes[0] = Complex64::new(1.0, 0.0);
1068 }
1069
1070 Ok(())
1071 }
1072
1073 fn get_amplitudes_mut(&mut self) -> &mut [Complex64] {
1075 unsafe {
1076 std::slice::from_raw_parts_mut(
1077 self.mmap.as_mut_ptr().cast::<Complex64>(),
1078 self.dimension,
1079 )
1080 }
1081 }
1082
1083 fn get_amplitudes(&self) -> &[Complex64] {
1085 unsafe {
1086 std::slice::from_raw_parts(self.mmap.as_ptr().cast::<Complex64>(), self.dimension)
1087 }
1088 }
1089
1090 pub fn apply_gate_chunked(&mut self, gate: &dyn GateOp) -> QuantRS2Result<()> {
1092 let num_chunks = self.dimension.div_ceil(self.chunk_size);
1093
1094 for chunk_idx in 0..num_chunks {
1095 let start = chunk_idx * self.chunk_size;
1096 let end = (start + self.chunk_size).min(self.dimension);
1097
1098 self.apply_gate_to_chunk(gate, start, end)?;
1099 }
1100
1101 Ok(())
1102 }
1103
1104 fn apply_gate_to_chunk(
1106 &mut self,
1107 gate: &dyn GateOp,
1108 start: usize,
1109 end: usize,
1110 ) -> QuantRS2Result<()> {
1111 let dimension = self.dimension;
1113 let amplitudes = self.get_amplitudes_mut();
1114
1115 match gate.name() {
1116 "X" => {
1117 if let Some(target) = gate.qubits().first() {
1118 let target_idx = target.id() as usize;
1119 let target_mask = 1usize << target_idx;
1120
1121 for i in start..end {
1122 if (i & target_mask) == 0 {
1123 let paired_idx = i | target_mask;
1124 if paired_idx < dimension {
1125 amplitudes.swap(i, paired_idx);
1126 }
1127 }
1128 }
1129 }
1130 }
1131 "H" => {
1132 if let Some(target) = gate.qubits().first() {
1133 let target_idx = target.id() as usize;
1134 let target_mask = 1usize << target_idx;
1135 let inv_sqrt2 = Complex64::new(1.0 / 2.0_f64.sqrt(), 0.0);
1136
1137 for i in start..end {
1146 if (i & target_mask) == 0 {
1147 let paired_idx = i | target_mask;
1148 if paired_idx < dimension {
1149 let old_0 = amplitudes[i];
1150 let old_1 = amplitudes[paired_idx];
1151
1152 amplitudes[i] = inv_sqrt2 * (old_0 + old_1);
1153 amplitudes[paired_idx] = inv_sqrt2 * (old_0 - old_1);
1154 }
1155 }
1156 }
1157 }
1158 }
1159 _ => {
1160 return Err(QuantRS2Error::UnsupportedOperation(format!(
1161 "Chunked operation not implemented for gate {}",
1162 gate.name()
1163 )));
1164 }
1165 }
1166
1167 Ok(())
1168 }
1169
1170 #[must_use]
1172 pub const fn memory_usage(&self) -> usize {
1173 std::mem::size_of::<Self>()
1174 }
1175
1176 #[must_use]
1178 pub const fn file_size(&self) -> usize {
1179 self.dimension * std::mem::size_of::<Complex64>()
1180 }
1181}
1182
1183impl Drop for MemoryMappedQuantumState {
1184 fn drop(&mut self) {
1185 let _ = std::fs::remove_file(&self.file_path);
1187 }
1188}
1189
1190#[derive(Debug)]
1192pub struct LargeScaleQuantumSimulator {
1193 config: LargeScaleSimulatorConfig,
1195
1196 state: QuantumStateRepresentation,
1198
1199 buffer_pool: Arc<Mutex<Vec<Vec<Complex64>>>>,
1201
1202 memory_stats: Arc<Mutex<MemoryStatistics>>,
1204}
1205
1206#[derive(Debug)]
1208pub enum QuantumStateRepresentation {
1209 Dense(Vec<Complex64>),
1211
1212 Sparse(SparseQuantumState),
1214
1215 Compressed(CompressedQuantumState),
1217
1218 MemoryMapped(MemoryMappedQuantumState),
1220}
1221
1222#[derive(Debug, Default, Clone)]
1224pub struct MemoryStatistics {
1225 pub current_usage: usize,
1227
1228 pub peak_usage: usize,
1230
1231 pub allocations: u64,
1233
1234 pub deallocations: u64,
1236
1237 pub compression_ratio: f64,
1239
1240 pub sparsity_ratio: f64,
1242
1243 pub memory_operation_time_us: u64,
1245}
1246
1247impl LargeScaleQuantumSimulator {
1248 pub fn new(config: LargeScaleSimulatorConfig) -> QuantRS2Result<Self> {
1250 let buffer_pool = Arc::new(Mutex::new(Vec::new()));
1251 let memory_stats = Arc::new(Mutex::new(MemoryStatistics::default()));
1252
1253 let state = QuantumStateRepresentation::Dense(vec![Complex64::new(1.0, 0.0)]);
1255
1256 Ok(Self {
1257 config,
1258 state,
1259 buffer_pool,
1260 memory_stats,
1261 })
1262 }
1263
1264 pub fn initialize_state(&mut self, num_qubits: usize) -> QuantRS2Result<()> {
1266 if num_qubits > self.config.max_qubits {
1267 return Err(QuantRS2Error::InvalidInput(format!(
1268 "Number of qubits {} exceeds maximum {}",
1269 num_qubits, self.config.max_qubits
1270 )));
1271 }
1272
1273 let dimension = 1usize << num_qubits;
1274 let memory_required = dimension * std::mem::size_of::<Complex64>();
1275
1276 self.state = if memory_required > self.config.memory_mapping_threshold {
1278 QuantumStateRepresentation::MemoryMapped(MemoryMappedQuantumState::new(
1280 num_qubits,
1281 self.config.chunk_size,
1282 &self.config.working_directory,
1283 )?)
1284 } else if memory_required > self.config.compression_threshold
1285 && self.config.enable_compression
1286 {
1287 let amplitudes = vec![Complex64::new(0.0, 0.0); dimension];
1289 let mut amplitudes = amplitudes;
1290 amplitudes[0] = Complex64::new(1.0, 0.0); QuantumStateRepresentation::Compressed(CompressedQuantumState::from_dense(
1293 &litudes,
1294 CompressionAlgorithm::LZ4,
1295 )?)
1296 } else if self.config.enable_sparse_representation {
1297 QuantumStateRepresentation::Sparse(SparseQuantumState::new(num_qubits)?)
1299 } else {
1300 let mut amplitudes = vec![Complex64::new(0.0, 0.0); dimension];
1302 amplitudes[0] = Complex64::new(1.0, 0.0); QuantumStateRepresentation::Dense(amplitudes)
1304 };
1305
1306 self.update_memory_stats()?;
1307
1308 Ok(())
1309 }
1310
1311 pub fn apply_gate(&mut self, gate: &dyn GateOp) -> QuantRS2Result<()> {
1313 let start_time = std::time::Instant::now();
1314
1315 let mut needs_state_change = None;
1317
1318 match &mut self.state {
1319 QuantumStateRepresentation::Dense(amplitudes) => {
1320 let mut amplitudes_copy = amplitudes.clone();
1322 Self::apply_gate_dense(&mut amplitudes_copy, gate, &self.config)?;
1323 *amplitudes = amplitudes_copy;
1324 }
1325 QuantumStateRepresentation::Sparse(sparse_state) => {
1326 sparse_state.apply_sparse_gate(gate)?;
1327
1328 if sparse_state.sparsity_ratio() > self.config.sparsity_threshold {
1330 let dense = sparse_state.to_dense()?;
1332 needs_state_change = Some(QuantumStateRepresentation::Dense(dense));
1333 }
1334 }
1335 QuantumStateRepresentation::Compressed(compressed_state) => {
1336 let mut dense = compressed_state.to_dense()?;
1338 Self::apply_gate_dense(&mut dense, gate, &self.config)?;
1339
1340 let new_compressed =
1342 CompressedQuantumState::from_dense(&dense, CompressionAlgorithm::LZ4)?;
1343 if new_compressed.compression_ratio() > 1.5 {
1344 needs_state_change =
1345 Some(QuantumStateRepresentation::Compressed(new_compressed));
1346 } else {
1347 needs_state_change = Some(QuantumStateRepresentation::Dense(dense));
1348 }
1349 }
1350 QuantumStateRepresentation::MemoryMapped(mmap_state) => {
1351 mmap_state.apply_gate_chunked(gate)?;
1352 }
1353 }
1354
1355 if let Some(new_state) = needs_state_change {
1357 self.state = new_state;
1358 }
1359
1360 let elapsed = start_time.elapsed();
1361 if let Ok(mut stats) = self.memory_stats.lock() {
1362 stats.memory_operation_time_us += elapsed.as_micros() as u64;
1363 }
1364
1365 Ok(())
1366 }
1367
1368 fn apply_gate_dense(
1370 amplitudes: &mut [Complex64],
1371 gate: &dyn GateOp,
1372 config: &LargeScaleSimulatorConfig,
1373 ) -> QuantRS2Result<()> {
1374 match gate.name() {
1375 "X" => {
1376 if let Some(target) = gate.qubits().first() {
1377 let target_idx = target.id() as usize;
1378 Self::apply_pauli_x_dense(amplitudes, target_idx)?;
1379 }
1380 }
1381 "H" => {
1382 if let Some(target) = gate.qubits().first() {
1383 let target_idx = target.id() as usize;
1384 Self::apply_hadamard_dense(amplitudes, target_idx, config)?;
1385 }
1386 }
1387 "CNOT" => {
1388 if gate.qubits().len() >= 2 {
1389 let control_idx = gate.qubits()[0].id() as usize;
1390 let target_idx = gate.qubits()[1].id() as usize;
1391 Self::apply_cnot_dense(amplitudes, control_idx, target_idx)?;
1392 }
1393 }
1394 _ => {
1395 return Err(QuantRS2Error::UnsupportedOperation(format!(
1396 "Gate {} not implemented in large-scale simulator",
1397 gate.name()
1398 )));
1399 }
1400 }
1401
1402 Ok(())
1403 }
1404
1405 fn apply_pauli_x_dense(amplitudes: &mut [Complex64], target: usize) -> QuantRS2Result<()> {
1407 let target_mask = 1usize << target;
1408
1409 for i in 0..amplitudes.len() {
1411 if (i & target_mask) == 0 {
1412 let paired_idx = i | target_mask;
1413 if paired_idx < amplitudes.len() {
1414 amplitudes.swap(i, paired_idx);
1415 }
1416 }
1417 }
1418
1419 Ok(())
1420 }
1421
1422 fn apply_hadamard_dense(
1424 amplitudes: &mut [Complex64],
1425 target: usize,
1426 _config: &LargeScaleSimulatorConfig,
1427 ) -> QuantRS2Result<()> {
1428 let target_mask = 1usize << target;
1429 let inv_sqrt2 = Complex64::new(1.0 / 2.0_f64.sqrt(), 0.0);
1430
1431 let mut temp = vec![Complex64::new(0.0, 0.0); amplitudes.len()];
1433 temp.copy_from_slice(amplitudes);
1434
1435 for i in 0..amplitudes.len() {
1437 if (i & target_mask) == 0 {
1438 let paired_idx = i | target_mask;
1439 if paired_idx < amplitudes.len() {
1440 let old_0 = temp[i];
1441 let old_1 = temp[paired_idx];
1442
1443 amplitudes[i] = inv_sqrt2 * (old_0 + old_1);
1444 amplitudes[paired_idx] = inv_sqrt2 * (old_0 - old_1);
1445 }
1446 }
1447 }
1448
1449 Ok(())
1450 }
1451
1452 fn apply_cnot_dense(
1454 amplitudes: &mut [Complex64],
1455 control: usize,
1456 target: usize,
1457 ) -> QuantRS2Result<()> {
1458 let control_mask = 1usize << control;
1459 let target_mask = 1usize << target;
1460
1461 for i in 0..amplitudes.len() {
1463 if (i & control_mask) != 0 && (i & target_mask) == 0 {
1464 let flipped_idx = i | target_mask;
1465 if flipped_idx < amplitudes.len() {
1466 amplitudes.swap(i, flipped_idx);
1467 }
1468 }
1469 }
1470
1471 Ok(())
1472 }
1473
1474 pub fn get_dense_state(&self) -> QuantRS2Result<Vec<Complex64>> {
1476 match &self.state {
1477 QuantumStateRepresentation::Dense(amplitudes) => Ok(amplitudes.clone()),
1478 QuantumStateRepresentation::Sparse(sparse_state) => sparse_state.to_dense(),
1479 QuantumStateRepresentation::Compressed(compressed_state) => compressed_state.to_dense(),
1480 QuantumStateRepresentation::MemoryMapped(mmap_state) => {
1481 Ok(mmap_state.get_amplitudes().to_vec())
1482 }
1483 }
1484 }
1485
1486 fn update_memory_stats(&self) -> QuantRS2Result<()> {
1488 if let Ok(mut stats) = self.memory_stats.lock() {
1489 let current_usage = match &self.state {
1490 QuantumStateRepresentation::Dense(amplitudes) => {
1491 amplitudes.len() * std::mem::size_of::<Complex64>()
1492 }
1493 QuantumStateRepresentation::Sparse(sparse_state) => sparse_state.memory_usage(),
1494 QuantumStateRepresentation::Compressed(compressed_state) => {
1495 compressed_state.memory_usage()
1496 }
1497 QuantumStateRepresentation::MemoryMapped(mmap_state) => mmap_state.memory_usage(),
1498 };
1499
1500 stats.current_usage = current_usage;
1501 if current_usage > stats.peak_usage {
1502 stats.peak_usage = current_usage;
1503 }
1504
1505 match &self.state {
1507 QuantumStateRepresentation::Compressed(compressed_state) => {
1508 stats.compression_ratio = compressed_state.compression_ratio();
1509 }
1510 QuantumStateRepresentation::Sparse(sparse_state) => {
1511 stats.sparsity_ratio = sparse_state.sparsity_ratio();
1512 }
1513 _ => {}
1514 }
1515 }
1516
1517 Ok(())
1518 }
1519
1520 #[must_use]
1522 pub fn get_memory_stats(&self) -> MemoryStatistics {
1523 self.memory_stats
1524 .lock()
1525 .map(|stats| stats.clone())
1526 .unwrap_or_default()
1527 }
1528
1529 #[must_use]
1531 pub const fn get_config(&self) -> &LargeScaleSimulatorConfig {
1532 &self.config
1533 }
1534
1535 #[must_use]
1537 pub const fn can_simulate(&self, num_qubits: usize) -> bool {
1538 if num_qubits > self.config.max_qubits {
1539 return false;
1540 }
1541
1542 let dimension = 1usize << num_qubits;
1543 let memory_required = dimension * std::mem::size_of::<Complex64>();
1544
1545 memory_required <= self.config.memory_budget
1546 }
1547
1548 #[must_use]
1550 pub fn estimate_memory_requirements(&self, num_qubits: usize) -> usize {
1551 let dimension = 1usize << num_qubits;
1552 let base_memory = dimension * std::mem::size_of::<Complex64>();
1553
1554 let overhead_factor = 1.5;
1556 (base_memory as f64 * overhead_factor) as usize
1557 }
1558}
1559
1560impl<const N: usize> Simulator<N> for LargeScaleQuantumSimulator {
1561 fn run(&self, circuit: &Circuit<N>) -> QuantRS2Result<quantrs2_core::register::Register<N>> {
1562 let mut simulator = Self::new(self.config.clone())?;
1563 simulator.initialize_state(N)?;
1564
1565 for gate in circuit.gates() {
1567 simulator.apply_gate(gate.as_ref())?;
1568 }
1569
1570 let final_state = simulator.get_dense_state()?;
1572 quantrs2_core::register::Register::with_amplitudes(final_state)
1573 }
1574}
1575
1576#[cfg(test)]
1577mod tests {
1578 use super::*;
1579 use quantrs2_core::gate::multi::CNOT;
1580 use quantrs2_core::gate::single::{Hadamard, PauliX};
1581 use quantrs2_core::qubit::QubitId;
1582
1583 #[test]
1584 fn test_sparse_quantum_state() {
1585 let mut sparse_state =
1586 SparseQuantumState::new(3).expect("Sparse state creation should succeed in test");
1587 assert_eq!(sparse_state.num_qubits, 3);
1588 assert_eq!(sparse_state.dimension, 8);
1589 assert!(sparse_state.sparsity_ratio() < 0.2);
1590
1591 let dense = sparse_state
1592 .to_dense()
1593 .expect("Sparse to dense conversion should succeed in test");
1594 assert_eq!(dense.len(), 8);
1595 assert!((dense[0] - Complex64::new(1.0, 0.0)).norm() < 1e-10);
1596 }
1597
1598 #[test]
1599 fn test_compressed_quantum_state() {
1600 let amplitudes = vec![
1601 Complex64::new(1.0, 0.0),
1602 Complex64::new(0.0, 0.0),
1603 Complex64::new(0.0, 0.0),
1604 Complex64::new(0.0, 0.0),
1605 ];
1606
1607 let compressed = CompressedQuantumState::from_dense(&litudes, CompressionAlgorithm::LZ4)
1608 .expect("Compression should succeed in test");
1609 let decompressed = compressed
1610 .to_dense()
1611 .expect("Decompression should succeed in test");
1612
1613 assert_eq!(decompressed.len(), 4);
1614 assert!((decompressed[0] - Complex64::new(1.0, 0.0)).norm() < 1e-10);
1615 }
1616
1617 #[test]
1618 fn test_large_scale_simulator() {
1619 let config = LargeScaleSimulatorConfig::default();
1620 let mut simulator = LargeScaleQuantumSimulator::new(config)
1621 .expect("Simulator creation should succeed in test");
1622
1623 simulator
1625 .initialize_state(10)
1626 .expect("State initialization should succeed in test");
1627 assert!(simulator.can_simulate(10));
1628
1629 let x_gate = PauliX { target: QubitId(0) };
1631 simulator
1632 .apply_gate(&x_gate)
1633 .expect("X gate application should succeed in test");
1634
1635 let h_gate = Hadamard { target: QubitId(1) };
1636 simulator
1637 .apply_gate(&h_gate)
1638 .expect("H gate application should succeed in test");
1639
1640 let final_state = simulator
1641 .get_dense_state()
1642 .expect("State retrieval should succeed in test");
1643 assert_eq!(final_state.len(), 1024); }
1645
1646 #[test]
1647 fn test_memory_stats() {
1648 let config = LargeScaleSimulatorConfig::default();
1649 let mut simulator = LargeScaleQuantumSimulator::new(config)
1650 .expect("Simulator creation should succeed in test");
1651
1652 simulator
1653 .initialize_state(5)
1654 .expect("State initialization should succeed in test");
1655 let stats = simulator.get_memory_stats();
1656
1657 assert!(stats.current_usage > 0);
1658 assert_eq!(stats.peak_usage, stats.current_usage);
1659 }
1660
1661 #[test]
1665 fn test_apply_dense_gate_three_qubit_toffoli() {
1666 let mut dense = vec![Complex64::new(0.0, 0.0); 8];
1669 dense[3] = Complex64::new(1.0, 0.0);
1670 let mut sparse = SparseQuantumState::from_dense(&dense, 1e-15)
1671 .expect("sparse state construction should succeed");
1672
1673 let mut matrix = vec![Complex64::new(0.0, 0.0); 64];
1676 for i in 0..8 {
1677 matrix[i * 8 + i] = Complex64::new(1.0, 0.0);
1678 }
1679 matrix[6 * 8 + 6] = Complex64::new(0.0, 0.0);
1680 matrix[7 * 8 + 7] = Complex64::new(0.0, 0.0);
1681 matrix[6 * 8 + 7] = Complex64::new(1.0, 0.0);
1682 matrix[7 * 8 + 6] = Complex64::new(1.0, 0.0);
1683
1684 let qubits = [QubitId(0), QubitId(1), QubitId(2)];
1685 sparse
1686 .apply_dense_gate(&matrix, &qubits)
1687 .expect("dense gate application should succeed");
1688
1689 let result = sparse.to_dense().expect("dense conversion should succeed");
1690 assert!(
1692 (result[7] - Complex64::new(1.0, 0.0)).norm() < 1e-10,
1693 "Toffoli must move amplitude from index 3 to index 7, got {result:?}"
1694 );
1695 assert!(
1696 result[3].norm() < 1e-10,
1697 "index 3 must be empty after Toffoli"
1698 );
1699 }
1700
1701 #[test]
1706 fn test_memory_mapped_hadamard_crosses_chunk_boundary() {
1707 let dir = std::env::temp_dir();
1708 let mut mmap = MemoryMappedQuantumState::new(5, 4, &dir)
1709 .expect("memory-mapped state creation should succeed");
1710
1711 let h_gate = Hadamard { target: QubitId(4) };
1712 mmap.apply_gate_chunked(&h_gate)
1713 .expect("chunked Hadamard should succeed");
1714
1715 let amps = mmap.get_amplitudes();
1716 let inv_sqrt2 = 1.0 / 2.0_f64.sqrt();
1717 assert!(
1720 (amps[0].re - inv_sqrt2).abs() < 1e-10,
1721 "amplitude at index 0 should be 1/sqrt2, got {}",
1722 amps[0].re
1723 );
1724 assert!(
1725 (amps[16].re - inv_sqrt2).abs() < 1e-10,
1726 "cross-chunk amplitude at index 16 should be 1/sqrt2, got {} (was silently skipped before the fix)",
1727 amps[16].re
1728 );
1729 let norm: f64 = amps.iter().map(|a| a.norm_sqr()).sum();
1730 assert!((norm - 1.0).abs() < 1e-9, "state must remain normalised");
1731 }
1732}