1use crate::{
7 error::{QuantRS2Error, QuantRS2Result},
8 gate::GateOp,
9};
10use ndarray::Array2;
11use num_complex::Complex;
12
13type Complex64 = Complex<f64>;
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
18pub enum BosonOperatorType {
19 Creation,
21 Annihilation,
23 Number,
25 Position,
27 Momentum,
29 Identity,
31 Displacement,
33 Squeeze,
35}
36
37#[derive(Debug, Clone)]
39pub struct BosonOperator {
40 pub op_type: BosonOperatorType,
42 pub mode: usize,
44 pub coefficient: Complex64,
46 pub truncation: usize,
48}
49
50impl BosonOperator {
51 pub fn new(
53 op_type: BosonOperatorType,
54 mode: usize,
55 coefficient: Complex64,
56 truncation: usize,
57 ) -> Self {
58 Self {
59 op_type,
60 mode,
61 coefficient,
62 truncation,
63 }
64 }
65
66 pub fn creation(mode: usize, truncation: usize) -> Self {
68 Self::new(
69 BosonOperatorType::Creation,
70 mode,
71 Complex64::new(1.0, 0.0),
72 truncation,
73 )
74 }
75
76 pub fn annihilation(mode: usize, truncation: usize) -> Self {
78 Self::new(
79 BosonOperatorType::Annihilation,
80 mode,
81 Complex64::new(1.0, 0.0),
82 truncation,
83 )
84 }
85
86 pub fn number(mode: usize, truncation: usize) -> Self {
88 Self::new(
89 BosonOperatorType::Number,
90 mode,
91 Complex64::new(1.0, 0.0),
92 truncation,
93 )
94 }
95
96 pub fn position(mode: usize, truncation: usize) -> Self {
98 Self::new(
99 BosonOperatorType::Position,
100 mode,
101 Complex64::new(1.0, 0.0),
102 truncation,
103 )
104 }
105
106 pub fn momentum(mode: usize, truncation: usize) -> Self {
108 Self::new(
109 BosonOperatorType::Momentum,
110 mode,
111 Complex64::new(1.0, 0.0),
112 truncation,
113 )
114 }
115
116 pub fn displacement(mode: usize, alpha: Complex64, truncation: usize) -> Self {
118 Self::new(BosonOperatorType::Displacement, mode, alpha, truncation)
119 }
120
121 pub fn squeeze(mode: usize, z: Complex64, truncation: usize) -> Self {
123 Self::new(BosonOperatorType::Squeeze, mode, z, truncation)
124 }
125
126 pub fn to_matrix(&self) -> QuantRS2Result<Array2<Complex64>> {
128 match self.op_type {
129 BosonOperatorType::Creation => self.creation_matrix(),
130 BosonOperatorType::Annihilation => self.annihilation_matrix(),
131 BosonOperatorType::Number => self.number_matrix(),
132 BosonOperatorType::Position => self.position_matrix(),
133 BosonOperatorType::Momentum => self.momentum_matrix(),
134 BosonOperatorType::Identity => self.identity_matrix(),
135 BosonOperatorType::Displacement => self.displacement_matrix(),
136 BosonOperatorType::Squeeze => self.squeeze_matrix(),
137 }
138 }
139
140 fn creation_matrix(&self) -> QuantRS2Result<Array2<Complex64>> {
142 let n = self.truncation;
143 let mut matrix = Array2::zeros((n, n));
144
145 for i in 0..n - 1 {
146 matrix[[i + 1, i]] = self.coefficient * ((i + 1) as f64).sqrt();
147 }
148
149 Ok(matrix)
150 }
151
152 fn annihilation_matrix(&self) -> QuantRS2Result<Array2<Complex64>> {
154 let n = self.truncation;
155 let mut matrix = Array2::zeros((n, n));
156
157 for i in 1..n {
158 matrix[[i - 1, i]] = self.coefficient * (i as f64).sqrt();
159 }
160
161 Ok(matrix)
162 }
163
164 fn number_matrix(&self) -> QuantRS2Result<Array2<Complex64>> {
166 let n = self.truncation;
167 let mut matrix = Array2::zeros((n, n));
168
169 for i in 0..n {
170 matrix[[i, i]] = self.coefficient * (i as f64);
171 }
172
173 Ok(matrix)
174 }
175
176 fn position_matrix(&self) -> QuantRS2Result<Array2<Complex64>> {
178 let a = self.annihilation_matrix()?;
179 let a_dag = self.creation_matrix()?;
180 let sqrt2 = 2.0_f64.sqrt();
181
182 Ok((&a + &a_dag) / sqrt2)
183 }
184
185 fn momentum_matrix(&self) -> QuantRS2Result<Array2<Complex64>> {
187 let a = self.annihilation_matrix()?;
188 let a_dag = self.creation_matrix()?;
189 let sqrt2 = 2.0_f64.sqrt();
190 let i = Complex64::new(0.0, 1.0);
191
192 Ok((&a_dag - &a).mapv(|x| i * x / sqrt2))
193 }
194
195 fn identity_matrix(&self) -> QuantRS2Result<Array2<Complex64>> {
197 let n = self.truncation;
198 let mut matrix = Array2::zeros((n, n));
199
200 for i in 0..n {
201 matrix[[i, i]] = self.coefficient;
202 }
203
204 Ok(matrix)
205 }
206
207 fn displacement_matrix(&self) -> QuantRS2Result<Array2<Complex64>> {
209 let a = self.annihilation_matrix()?;
210 let a_dag = self.creation_matrix()?;
211 let alpha = self.coefficient;
212
213 let generator = &a_dag.mapv(|x| alpha * x) - &a.mapv(|x| alpha.conj() * x);
215
216 matrix_exponential_complex(&generator)
218 }
219
220 fn squeeze_matrix(&self) -> QuantRS2Result<Array2<Complex64>> {
222 let a = self.annihilation_matrix()?;
223 let a_dag = self.creation_matrix()?;
224 let z = self.coefficient;
225
226 let a_squared = a.dot(&a);
228 let a_dag_squared = a_dag.dot(&a_dag);
229
230 let generator =
232 &a_squared.mapv(|x| z * x / 2.0) - &a_dag_squared.mapv(|x| z.conj() * x / 2.0);
233
234 matrix_exponential_complex(&generator)
236 }
237
238 pub fn dagger(&self) -> Self {
240 let conj_coeff = self.coefficient.conj();
241 match self.op_type {
242 BosonOperatorType::Creation => Self::new(
243 BosonOperatorType::Annihilation,
244 self.mode,
245 conj_coeff,
246 self.truncation,
247 ),
248 BosonOperatorType::Annihilation => Self::new(
249 BosonOperatorType::Creation,
250 self.mode,
251 conj_coeff,
252 self.truncation,
253 ),
254 BosonOperatorType::Number => Self::new(
255 BosonOperatorType::Number,
256 self.mode,
257 conj_coeff,
258 self.truncation,
259 ),
260 BosonOperatorType::Position => Self::new(
261 BosonOperatorType::Position,
262 self.mode,
263 conj_coeff,
264 self.truncation,
265 ),
266 BosonOperatorType::Momentum => Self::new(
267 BosonOperatorType::Momentum,
268 self.mode,
269 -conj_coeff,
270 self.truncation,
271 ), BosonOperatorType::Identity => Self::new(
273 BosonOperatorType::Identity,
274 self.mode,
275 conj_coeff,
276 self.truncation,
277 ),
278 BosonOperatorType::Displacement => Self::new(
279 BosonOperatorType::Displacement,
280 self.mode,
281 conj_coeff,
282 self.truncation,
283 ),
284 BosonOperatorType::Squeeze => Self::new(
285 BosonOperatorType::Squeeze,
286 self.mode,
287 conj_coeff,
288 self.truncation,
289 ),
290 }
291 }
292}
293
294#[derive(Debug, Clone)]
296pub struct BosonTerm {
297 pub operators: Vec<BosonOperator>,
299 pub coefficient: Complex64,
301}
302
303impl BosonTerm {
304 pub fn new(operators: Vec<BosonOperator>, coefficient: Complex64) -> Self {
306 Self {
307 operators,
308 coefficient,
309 }
310 }
311
312 pub fn identity(truncation: usize) -> Self {
314 Self {
315 operators: vec![],
316 coefficient: Complex64::new(1.0, 0.0),
317 }
318 }
319
320 pub fn to_matrix(&self, n_modes: usize) -> QuantRS2Result<Array2<Complex64>> {
322 if self.operators.is_empty() {
323 let total_dim = self
325 .operators
326 .first()
327 .map(|op| op.truncation.pow(n_modes as u32))
328 .unwrap_or(1);
329 let mut identity = Array2::zeros((total_dim, total_dim));
330 for i in 0..total_dim {
331 identity[[i, i]] = self.coefficient;
332 }
333 return Ok(identity);
334 }
335
336 let mut result = None;
338 let truncation = self.operators[0].truncation;
339
340 for mode in 0..n_modes {
341 let mode_op = self
343 .operators
344 .iter()
345 .find(|op| op.mode == mode)
346 .map(|op| op.to_matrix())
347 .unwrap_or_else(|| {
348 let mut id = Array2::zeros((truncation, truncation));
350 for i in 0..truncation {
351 id[[i, i]] = Complex64::new(1.0, 0.0);
352 }
353 Ok(id)
354 })?;
355
356 result = match result {
357 None => Some(mode_op),
358 Some(prev) => Some(kron_complex(&prev, &mode_op)),
359 };
360 }
361
362 let mut final_result =
363 result.ok_or_else(|| QuantRS2Error::InvalidInput("No operators in term".into()))?;
364 final_result = final_result.mapv(|x| self.coefficient * x);
365 Ok(final_result)
366 }
367
368 pub fn normal_order(&mut self) -> QuantRS2Result<()> {
370 let n = self.operators.len();
373 for i in 0..n {
374 for j in 0..n.saturating_sub(i + 1) {
375 if self.should_swap(j) {
376 self.swap_operators(j)?;
377 }
378 }
379 }
380 Ok(())
381 }
382
383 fn should_swap(&self, idx: usize) -> bool {
385 if idx + 1 >= self.operators.len() {
386 return false;
387 }
388
389 let op1 = &self.operators[idx];
390 let op2 = &self.operators[idx + 1];
391
392 matches!(
394 (op1.op_type, op2.op_type),
395 (BosonOperatorType::Annihilation, BosonOperatorType::Creation)
396 ) && op1.mode == op2.mode
397 }
398
399 fn swap_operators(&mut self, idx: usize) -> QuantRS2Result<()> {
401 if idx + 1 >= self.operators.len() {
402 return Err(QuantRS2Error::InvalidInput("Index out of bounds".into()));
403 }
404
405 let op1 = &self.operators[idx];
406 let op2 = &self.operators[idx + 1];
407
408 if op1.mode == op2.mode {
410 match (op1.op_type, op2.op_type) {
411 (BosonOperatorType::Annihilation, BosonOperatorType::Creation) => {
412 return Err(QuantRS2Error::UnsupportedOperation(
416 "Commutation that produces multiple terms not yet supported".into(),
417 ));
418 }
419 _ => {
420 }
422 }
423 }
424
425 self.operators.swap(idx, idx + 1);
427 Ok(())
428 }
429
430 pub fn dagger(&self) -> Self {
432 let mut conj_ops = self.operators.clone();
433 conj_ops.reverse();
434 conj_ops = conj_ops.into_iter().map(|op| op.dagger()).collect();
435
436 Self {
437 operators: conj_ops,
438 coefficient: self.coefficient.conj(),
439 }
440 }
441}
442
443#[derive(Debug, Clone)]
445pub struct BosonHamiltonian {
446 pub terms: Vec<BosonTerm>,
448 pub n_modes: usize,
450 pub truncation: usize,
452}
453
454impl BosonHamiltonian {
455 pub fn new(n_modes: usize, truncation: usize) -> Self {
457 Self {
458 terms: Vec::new(),
459 n_modes,
460 truncation,
461 }
462 }
463
464 pub fn add_term(&mut self, term: BosonTerm) {
466 self.terms.push(term);
467 }
468
469 pub fn add_harmonic_oscillator(&mut self, mode: usize, omega: f64) {
471 let term = BosonTerm::new(
472 vec![BosonOperator::number(mode, self.truncation)],
473 Complex64::new(omega, 0.0),
474 );
475 self.add_term(term);
476 }
477
478 pub fn add_beam_splitter(&mut self, mode1: usize, mode2: usize, g: f64) {
480 let term1 = BosonTerm::new(
482 vec![
483 BosonOperator::creation(mode1, self.truncation),
484 BosonOperator::annihilation(mode2, self.truncation),
485 ],
486 Complex64::new(g, 0.0),
487 );
488 self.add_term(term1);
489
490 let term2 = BosonTerm::new(
492 vec![
493 BosonOperator::annihilation(mode1, self.truncation),
494 BosonOperator::creation(mode2, self.truncation),
495 ],
496 Complex64::new(g, 0.0),
497 );
498 self.add_term(term2);
499 }
500
501 pub fn add_kerr_nonlinearity(&mut self, mode: usize, kappa: f64) {
503 let term = BosonTerm::new(
504 vec![
505 BosonOperator::creation(mode, self.truncation),
506 BosonOperator::creation(mode, self.truncation),
507 BosonOperator::annihilation(mode, self.truncation),
508 BosonOperator::annihilation(mode, self.truncation),
509 ],
510 Complex64::new(kappa, 0.0),
511 );
512 self.add_term(term);
513 }
514
515 pub fn add_two_mode_squeezing(&mut self, mode1: usize, mode2: usize, xi: Complex64) {
517 let term1 = BosonTerm::new(
519 vec![
520 BosonOperator::annihilation(mode1, self.truncation),
521 BosonOperator::annihilation(mode2, self.truncation),
522 ],
523 xi,
524 );
525 self.add_term(term1);
526
527 let term2 = BosonTerm::new(
529 vec![
530 BosonOperator::creation(mode1, self.truncation),
531 BosonOperator::creation(mode2, self.truncation),
532 ],
533 xi.conj(),
534 );
535 self.add_term(term2);
536 }
537
538 pub fn to_matrix(&self) -> QuantRS2Result<Array2<Complex64>> {
540 let total_dim = self.truncation.pow(self.n_modes as u32);
541 let mut result = Array2::zeros((total_dim, total_dim));
542
543 for term in &self.terms {
544 let term_matrix = term.to_matrix(self.n_modes)?;
545 result = &result + &term_matrix;
546 }
547
548 Ok(result)
549 }
550
551 pub fn dagger(&self) -> Self {
553 let conj_terms = self.terms.iter().map(|t| t.dagger()).collect();
554 Self {
555 terms: conj_terms,
556 n_modes: self.n_modes,
557 truncation: self.truncation,
558 }
559 }
560
561 pub fn is_hermitian(&self, tolerance: f64) -> bool {
563 let h = match self.to_matrix() {
565 Ok(mat) => mat,
566 Err(_) => return false,
567 };
568
569 let h_dag = match self.dagger().to_matrix() {
570 Ok(mat) => mat,
571 Err(_) => return false,
572 };
573
574 for i in 0..h.shape()[0] {
576 for j in 0..h.shape()[1] {
577 if (h[[i, j]] - h_dag[[i, j]]).norm() > tolerance {
578 return false;
579 }
580 }
581 }
582
583 true
584 }
585}
586
587#[derive(Debug, Clone)]
589pub struct GaussianState {
590 pub n_modes: usize,
592 pub displacement: Vec<f64>,
594 pub covariance: Array2<f64>,
596}
597
598impl GaussianState {
599 pub fn vacuum(n_modes: usize) -> Self {
601 use ndarray::Array2;
602
603 Self {
604 n_modes,
605 displacement: vec![0.0; 2 * n_modes],
606 covariance: Array2::eye(2 * n_modes) * 0.5, }
608 }
609
610 pub fn coherent(n_modes: usize, mode: usize, alpha: Complex64) -> Self {
612 let mut state = Self::vacuum(n_modes);
613
614 state.displacement[2 * mode] = alpha.re * 2.0_f64.sqrt(); state.displacement[2 * mode + 1] = alpha.im * 2.0_f64.sqrt(); state
619 }
620
621 pub fn squeezed(n_modes: usize, mode: usize, r: f64, phi: f64) -> Self {
623 let mut state = Self::vacuum(n_modes);
624
625 let c = r.cosh();
627 let s = r.sinh();
628 let cos_2phi = (2.0 * phi).cos();
629 let sin_2phi = (2.0 * phi).sin();
630
631 let idx = 2 * mode;
632 state.covariance[[idx, idx]] = 0.5 * (c + s * cos_2phi);
633 state.covariance[[idx + 1, idx + 1]] = 0.5 * (c - s * cos_2phi);
634 state.covariance[[idx, idx + 1]] = 0.5 * s * sin_2phi;
635 state.covariance[[idx + 1, idx]] = 0.5 * s * sin_2phi;
636
637 state
638 }
639
640 pub fn apply_symplectic(&mut self, s: &Array2<f64>) -> QuantRS2Result<()> {
642 if s.shape() != [2 * self.n_modes, 2 * self.n_modes] {
643 return Err(QuantRS2Error::InvalidInput(
644 "Symplectic matrix has wrong dimensions".into(),
645 ));
646 }
647
648 let d = Array2::from_shape_vec((2 * self.n_modes, 1), self.displacement.clone()).map_err(
650 |e| QuantRS2Error::LinalgError(format!("Failed to create displacement vector: {}", e)),
651 )?;
652 let d_prime = s.dot(&d);
653 self.displacement = d_prime.iter().cloned().collect();
654
655 self.covariance = s.dot(&self.covariance).dot(&s.t());
657
658 Ok(())
659 }
660
661 pub fn purity(&self) -> f64 {
663 1.0
667 }
668}
669
670pub fn boson_to_qubit_encoding(
672 op: &BosonOperator,
673 encoding: &str,
674) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
675 match encoding {
676 "binary" => {
677 let n_qubits = (op.truncation as f64).log2().ceil() as usize;
680
681 Err(QuantRS2Error::UnsupportedOperation(
683 "Binary encoding not yet implemented".into(),
684 ))
685 }
686 "unary" => {
687 Err(QuantRS2Error::UnsupportedOperation(
690 "Unary encoding not yet implemented".into(),
691 ))
692 }
693 "gray" => {
694 Err(QuantRS2Error::UnsupportedOperation(
696 "Gray code encoding not yet implemented".into(),
697 ))
698 }
699 _ => Err(QuantRS2Error::InvalidInput(format!(
700 "Unknown encoding: {}",
701 encoding
702 ))),
703 }
704}
705
706fn matrix_exponential_complex(a: &Array2<Complex64>) -> QuantRS2Result<Array2<Complex64>> {
708 let n = a.shape()[0];
709 if a.shape()[1] != n {
710 return Err(QuantRS2Error::InvalidInput("Matrix must be square".into()));
711 }
712
713 let norm = a.mapv(|x| x.norm()).sum() / (n as f64);
715 let scale = (norm.log2().ceil() as i32).max(0);
716 let scale_factor = 2.0_f64.powi(scale);
717 let a_scaled = a.mapv(|x| x / scale_factor);
718
719 let mut u = Array2::from_shape_fn((n, n), |(i, j)| {
721 if i == j {
722 Complex64::new(1.0, 0.0)
723 } else {
724 Complex64::new(0.0, 0.0)
725 }
726 });
727 let mut v = Array2::from_shape_fn((n, n), |(i, j)| {
728 if i == j {
729 Complex64::new(1.0, 0.0)
730 } else {
731 Complex64::new(0.0, 0.0)
732 }
733 });
734
735 let a2 = a_scaled.dot(&a_scaled);
736 let a4 = a2.dot(&a2);
737 let a6 = a4.dot(&a2);
738
739 let c = [
741 1.0,
742 1.0 / 2.0,
743 1.0 / 6.0,
744 1.0 / 24.0,
745 1.0 / 120.0,
746 1.0 / 720.0,
747 1.0 / 5040.0,
748 ];
749
750 u = &u * c[0]
751 + &a_scaled * c[1]
752 + &a2 * c[2]
753 + &a_scaled.dot(&a2) * c[3]
754 + &a4 * c[4]
755 + &a_scaled.dot(&a4) * c[5]
756 + &a6 * c[6];
757
758 v = &v * c[0] - &a_scaled * c[1] + &a2 * c[2] - &a_scaled.dot(&a2) * c[3] + &a4 * c[4]
759 - &a_scaled.dot(&a4) * c[5]
760 + &a6 * c[6];
761
762 let v_minus_u = &v - &u;
764 let two_u = &u * 2.0;
765
766 let exp_a_scaled = solve_complex(&v_minus_u, &two_u)?;
768
769 let mut result = exp_a_scaled;
771 for _ in 0..scale {
772 result = result.dot(&result);
773 }
774
775 Ok(result)
776}
777
778fn solve_complex(
780 a: &Array2<Complex64>,
781 b: &Array2<Complex64>,
782) -> QuantRS2Result<Array2<Complex64>> {
783 let n = a.shape()[0];
784 if a.shape()[1] != n || b.shape()[0] != n {
785 return Err(QuantRS2Error::InvalidInput(
786 "Invalid dimensions for solve".into(),
787 ));
788 }
789
790 let mut aug = Array2::zeros((n, n + b.shape()[1]));
792 for i in 0..n {
793 for j in 0..n {
794 aug[[i, j]] = a[[i, j]];
795 }
796 for j in 0..b.shape()[1] {
797 aug[[i, n + j]] = b[[i, j]];
798 }
799 }
800
801 for i in 0..n {
803 let mut max_row = i;
805 for k in i + 1..n {
806 if aug[[k, i]].norm() > aug[[max_row, i]].norm() {
807 max_row = k;
808 }
809 }
810
811 for j in 0..n + b.shape()[1] {
813 let temp = aug[[i, j]];
814 aug[[i, j]] = aug[[max_row, j]];
815 aug[[max_row, j]] = temp;
816 }
817
818 if aug[[i, i]].norm() < 1e-12 {
820 return Err(QuantRS2Error::LinalgError("Singular matrix".into()));
821 }
822
823 for k in i + 1..n {
825 let factor = aug[[k, i]] / aug[[i, i]];
826 for j in i..n + b.shape()[1] {
827 aug[[k, j]] = aug[[k, j]] - factor * aug[[i, j]];
828 }
829 }
830 }
831
832 let mut x = Array2::zeros((n, b.shape()[1]));
834 for i in (0..n).rev() {
835 for j in 0..b.shape()[1] {
836 x[[i, j]] = aug[[i, n + j]];
837 for k in i + 1..n {
838 x[[i, j]] = x[[i, j]] - aug[[i, k]] * x[[k, j]];
839 }
840 x[[i, j]] = x[[i, j]] / aug[[i, i]];
841 }
842 }
843
844 Ok(x)
845}
846
847fn kron_complex(a: &Array2<Complex64>, b: &Array2<Complex64>) -> Array2<Complex64> {
849 let (m, n) = a.dim();
850 let (p, q) = b.dim();
851 let mut result = Array2::zeros((m * p, n * q));
852
853 for i in 0..m {
854 for j in 0..n {
855 for k in 0..p {
856 for l in 0..q {
857 result[[i * p + k, j * q + l]] = a[[i, j]] * b[[k, l]];
858 }
859 }
860 }
861 }
862
863 result
864}
865
866#[derive(Debug, Clone)]
868pub struct SparseBosonOperator {
869 pub rows: Vec<usize>,
871 pub cols: Vec<usize>,
873 pub data: Vec<Complex64>,
875 pub shape: (usize, usize),
877}
878
879impl SparseBosonOperator {
880 pub fn from_triplets(
882 rows: Vec<usize>,
883 cols: Vec<usize>,
884 data: Vec<Complex64>,
885 shape: (usize, usize),
886 ) -> Self {
887 Self {
888 rows,
889 cols,
890 data,
891 shape,
892 }
893 }
894
895 pub fn to_dense(&self) -> Array2<Complex64> {
897 let mut matrix = Array2::zeros(self.shape);
898 for ((&i, &j), &val) in self.rows.iter().zip(&self.cols).zip(&self.data) {
899 matrix[[i, j]] = val;
900 }
901 matrix
902 }
903
904 pub fn nnz(&self) -> usize {
906 self.data.len()
907 }
908}
909
910#[cfg(test)]
911mod tests {
912 use super::*;
913
914 macro_rules! assert_relative_eq {
916 ($left:expr, $right:expr, epsilon = $epsilon:expr) => {
917 let diff = ($left - $right).abs();
918 assert!(diff < $epsilon, "assertion failed: `(left ≈ right)`\n left: `{:?}`,\n right: `{:?}`,\n diff: `{:?}`,\nepsilon: `{:?}`", $left, $right, diff, $epsilon);
919 };
920 }
921
922 #[test]
923 fn test_creation_annihilation_operators() {
924 let truncation = 5;
925 let a = BosonOperator::annihilation(0, truncation);
926 let a_dag = BosonOperator::creation(0, truncation);
927
928 let a_mat = a.to_matrix().unwrap();
929 let a_dag_mat = a_dag.to_matrix().unwrap();
930
931 for i in 0..truncation {
933 for j in 0..truncation {
934 assert_relative_eq!(a_dag_mat[[i, j]].re, a_mat[[j, i]].re, epsilon = 1e-12);
935 assert_relative_eq!(a_dag_mat[[i, j]].im, -a_mat[[j, i]].im, epsilon = 1e-12);
936 }
937 }
938 }
939
940 #[test]
941 fn test_number_operator() {
942 let truncation = 5;
943 let n_op = BosonOperator::number(0, truncation);
944 let n_mat = n_op.to_matrix().unwrap();
945
946 for i in 0..truncation {
948 assert_relative_eq!(n_mat[[i, i]].re, i as f64, epsilon = 1e-12);
949 assert_relative_eq!(n_mat[[i, i]].im, 0.0, epsilon = 1e-12);
950 }
951
952 for i in 0..truncation {
954 for j in 0..truncation {
955 if i != j {
956 assert_relative_eq!(n_mat[[i, j]].norm(), 0.0, epsilon = 1e-12);
957 }
958 }
959 }
960 }
961
962 #[test]
963 fn test_position_momentum_commutation() {
964 let truncation = 10;
965 let x = BosonOperator::position(0, truncation);
966 let p = BosonOperator::momentum(0, truncation);
967
968 let x_mat = x.to_matrix().unwrap();
969 let p_mat = p.to_matrix().unwrap();
970
971 let xp = x_mat.dot(&p_mat);
973 let px = p_mat.dot(&x_mat);
974 let commutator = &xp - &px;
975
976 println!("Commutator[0,0] = {:?}", commutator[[0, 0]]);
980 println!("Expected canonical [x,p] = i, but we have normalization factors");
981
982 let expected_value = 1.0;
986
987 for i in 0..truncation - 1 {
989 for j in 0..truncation - 1 {
990 if i == j {
991 assert_relative_eq!(commutator[[i, j]].re, 0.0, epsilon = 1e-10);
992 assert_relative_eq!(commutator[[i, j]].im, expected_value, epsilon = 1e-10);
993 } else {
994 assert_relative_eq!(commutator[[i, j]].norm(), 0.0, epsilon = 1e-10);
995 }
996 }
997 }
998 }
999
1000 #[test]
1001 fn test_harmonic_oscillator_hamiltonian() {
1002 let n_modes = 2;
1003 let truncation = 5;
1004 let mut ham = BosonHamiltonian::new(n_modes, truncation);
1005
1006 ham.add_harmonic_oscillator(0, 1.0);
1008 ham.add_harmonic_oscillator(1, 2.0);
1009
1010 ham.add_beam_splitter(0, 1, 0.5);
1012
1013 assert_eq!(ham.terms.len(), 4); assert!(ham.is_hermitian(1e-12));
1017 }
1018
1019 #[test]
1020 fn test_gaussian_state() {
1021 let n_modes = 2;
1022
1023 let vacuum = GaussianState::vacuum(n_modes);
1025 assert_eq!(vacuum.displacement.len(), 2 * n_modes);
1026 assert_relative_eq!(vacuum.purity(), 1.0, epsilon = 1e-12);
1027
1028 let alpha = Complex64::new(1.0, 0.5);
1030 let coherent = GaussianState::coherent(n_modes, 0, alpha);
1031 assert_relative_eq!(
1032 coherent.displacement[0],
1033 alpha.re * 2.0_f64.sqrt(),
1034 epsilon = 1e-12
1035 );
1036 assert_relative_eq!(
1037 coherent.displacement[1],
1038 alpha.im * 2.0_f64.sqrt(),
1039 epsilon = 1e-12
1040 );
1041
1042 let squeezed = GaussianState::squeezed(n_modes, 0, 1.0, 0.0);
1044 assert!(squeezed.covariance[[0, 0]] > 0.5); assert!(squeezed.covariance[[1, 1]] < 0.5); }
1049}