1use crate::{
7 error::{QuantRS2Error, QuantRS2Result},
8 gate::GateOp,
9};
10use scirs2_core::ndarray::Array2;
11use scirs2_core::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 const 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 const 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 const 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 const 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 const 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 const 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 const fn displacement(mode: usize, alpha: Complex64, truncation: usize) -> Self {
118 Self::new(BosonOperatorType::Displacement, mode, alpha, truncation)
119 }
120
121 pub const 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 #[must_use]
240 pub fn dagger(&self) -> Self {
241 let conj_coeff = self.coefficient.conj();
242 match self.op_type {
243 BosonOperatorType::Creation => Self::new(
244 BosonOperatorType::Annihilation,
245 self.mode,
246 conj_coeff,
247 self.truncation,
248 ),
249 BosonOperatorType::Annihilation => Self::new(
250 BosonOperatorType::Creation,
251 self.mode,
252 conj_coeff,
253 self.truncation,
254 ),
255 BosonOperatorType::Number => Self::new(
256 BosonOperatorType::Number,
257 self.mode,
258 conj_coeff,
259 self.truncation,
260 ),
261 BosonOperatorType::Position => Self::new(
262 BosonOperatorType::Position,
263 self.mode,
264 conj_coeff,
265 self.truncation,
266 ),
267 BosonOperatorType::Momentum => Self::new(
268 BosonOperatorType::Momentum,
269 self.mode,
270 -conj_coeff,
271 self.truncation,
272 ), BosonOperatorType::Identity => Self::new(
274 BosonOperatorType::Identity,
275 self.mode,
276 conj_coeff,
277 self.truncation,
278 ),
279 BosonOperatorType::Displacement => Self::new(
280 BosonOperatorType::Displacement,
281 self.mode,
282 conj_coeff,
283 self.truncation,
284 ),
285 BosonOperatorType::Squeeze => Self::new(
286 BosonOperatorType::Squeeze,
287 self.mode,
288 conj_coeff,
289 self.truncation,
290 ),
291 }
292 }
293}
294
295#[derive(Debug, Clone)]
297pub struct BosonTerm {
298 pub operators: Vec<BosonOperator>,
300 pub coefficient: Complex64,
302}
303
304impl BosonTerm {
305 pub const fn new(operators: Vec<BosonOperator>, coefficient: Complex64) -> Self {
307 Self {
308 operators,
309 coefficient,
310 }
311 }
312
313 pub const fn identity(_truncation: usize) -> Self {
315 Self {
316 operators: vec![],
317 coefficient: Complex64::new(1.0, 0.0),
318 }
319 }
320
321 pub fn to_matrix(&self, n_modes: usize) -> QuantRS2Result<Array2<Complex64>> {
323 if self.operators.is_empty() {
324 let total_dim = self
326 .operators
327 .first()
328 .map_or(1, |op| op.truncation.pow(n_modes as u32));
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 && (op1.op_type, op2.op_type)
411 == (BosonOperatorType::Annihilation, BosonOperatorType::Creation)
412 {
413 return Err(QuantRS2Error::UnsupportedOperation(
417 "Commutation that produces multiple terms not yet supported".into(),
418 ));
419 }
420 self.operators.swap(idx, idx + 1);
424 Ok(())
425 }
426
427 #[must_use]
429 pub fn dagger(&self) -> Self {
430 let mut conj_ops = self.operators.clone();
431 conj_ops.reverse();
432 conj_ops = conj_ops.into_iter().map(|op| op.dagger()).collect();
433
434 Self {
435 operators: conj_ops,
436 coefficient: self.coefficient.conj(),
437 }
438 }
439}
440
441#[derive(Debug, Clone)]
443pub struct BosonHamiltonian {
444 pub terms: Vec<BosonTerm>,
446 pub n_modes: usize,
448 pub truncation: usize,
450}
451
452impl BosonHamiltonian {
453 pub const fn new(n_modes: usize, truncation: usize) -> Self {
455 Self {
456 terms: Vec::new(),
457 n_modes,
458 truncation,
459 }
460 }
461
462 pub fn add_term(&mut self, term: BosonTerm) {
464 self.terms.push(term);
465 }
466
467 pub fn add_harmonic_oscillator(&mut self, mode: usize, omega: f64) {
469 let term = BosonTerm::new(
470 vec![BosonOperator::number(mode, self.truncation)],
471 Complex64::new(omega, 0.0),
472 );
473 self.add_term(term);
474 }
475
476 pub fn add_beam_splitter(&mut self, mode1: usize, mode2: usize, g: f64) {
478 let term1 = BosonTerm::new(
480 vec![
481 BosonOperator::creation(mode1, self.truncation),
482 BosonOperator::annihilation(mode2, self.truncation),
483 ],
484 Complex64::new(g, 0.0),
485 );
486 self.add_term(term1);
487
488 let term2 = BosonTerm::new(
490 vec![
491 BosonOperator::annihilation(mode1, self.truncation),
492 BosonOperator::creation(mode2, self.truncation),
493 ],
494 Complex64::new(g, 0.0),
495 );
496 self.add_term(term2);
497 }
498
499 pub fn add_kerr_nonlinearity(&mut self, mode: usize, kappa: f64) {
501 let term = BosonTerm::new(
502 vec![
503 BosonOperator::creation(mode, self.truncation),
504 BosonOperator::creation(mode, self.truncation),
505 BosonOperator::annihilation(mode, self.truncation),
506 BosonOperator::annihilation(mode, self.truncation),
507 ],
508 Complex64::new(kappa, 0.0),
509 );
510 self.add_term(term);
511 }
512
513 pub fn add_two_mode_squeezing(&mut self, mode1: usize, mode2: usize, xi: Complex64) {
515 let term1 = BosonTerm::new(
517 vec![
518 BosonOperator::annihilation(mode1, self.truncation),
519 BosonOperator::annihilation(mode2, self.truncation),
520 ],
521 xi,
522 );
523 self.add_term(term1);
524
525 let term2 = BosonTerm::new(
527 vec![
528 BosonOperator::creation(mode1, self.truncation),
529 BosonOperator::creation(mode2, self.truncation),
530 ],
531 xi.conj(),
532 );
533 self.add_term(term2);
534 }
535
536 pub fn to_matrix(&self) -> QuantRS2Result<Array2<Complex64>> {
538 let total_dim = self.truncation.pow(self.n_modes as u32);
539 let mut result = Array2::zeros((total_dim, total_dim));
540
541 for term in &self.terms {
542 let term_matrix = term.to_matrix(self.n_modes)?;
543 result = &result + &term_matrix;
544 }
545
546 Ok(result)
547 }
548
549 #[must_use]
551 pub fn dagger(&self) -> Self {
552 let conj_terms = self.terms.iter().map(|t| t.dagger()).collect();
553 Self {
554 terms: conj_terms,
555 n_modes: self.n_modes,
556 truncation: self.truncation,
557 }
558 }
559
560 pub fn is_hermitian(&self, tolerance: f64) -> bool {
562 let h = match self.to_matrix() {
564 Ok(mat) => mat,
565 Err(_) => return false,
566 };
567
568 let h_dag = match self.dagger().to_matrix() {
569 Ok(mat) => mat,
570 Err(_) => return false,
571 };
572
573 for i in 0..h.shape()[0] {
575 for j in 0..h.shape()[1] {
576 if (h[[i, j]] - h_dag[[i, j]]).norm() > tolerance {
577 return false;
578 }
579 }
580 }
581
582 true
583 }
584}
585
586#[derive(Debug, Clone)]
588pub struct GaussianState {
589 pub n_modes: usize,
591 pub displacement: Vec<f64>,
593 pub covariance: Array2<f64>,
595}
596
597impl GaussianState {
598 pub fn vacuum(n_modes: usize) -> Self {
600 use scirs2_core::ndarray::Array2;
601
602 Self {
603 n_modes,
604 displacement: vec![0.0; 2 * n_modes],
605 covariance: Array2::eye(2 * n_modes) * 0.5, }
607 }
608
609 pub fn coherent(n_modes: usize, mode: usize, alpha: Complex64) -> Self {
611 let mut state = Self::vacuum(n_modes);
612
613 state.displacement[2 * mode] = alpha.re * 2.0_f64.sqrt(); state.displacement[2 * mode + 1] = alpha.im * 2.0_f64.sqrt(); state
618 }
619
620 pub fn squeezed(n_modes: usize, mode: usize, r: f64, phi: f64) -> Self {
622 let mut state = Self::vacuum(n_modes);
623
624 let c = r.cosh();
626 let s = r.sinh();
627 let cos_2phi = (2.0 * phi).cos();
628 let sin_2phi = (2.0 * phi).sin();
629
630 let idx = 2 * mode;
631 state.covariance[[idx, idx]] = 0.5 * s.mul_add(cos_2phi, c);
632 state.covariance[[idx + 1, idx + 1]] = 0.5 * s.mul_add(-cos_2phi, c);
633 state.covariance[[idx, idx + 1]] = 0.5 * s * sin_2phi;
634 state.covariance[[idx + 1, idx]] = 0.5 * s * sin_2phi;
635
636 state
637 }
638
639 pub fn apply_symplectic(&mut self, s: &Array2<f64>) -> QuantRS2Result<()> {
641 if s.shape() != [2 * self.n_modes, 2 * self.n_modes] {
642 return Err(QuantRS2Error::InvalidInput(
643 "Symplectic matrix has wrong dimensions".into(),
644 ));
645 }
646
647 let d = Array2::from_shape_vec((2 * self.n_modes, 1), self.displacement.clone()).map_err(
649 |e| QuantRS2Error::LinalgError(format!("Failed to create displacement vector: {e}")),
650 )?;
651 let d_prime = s.dot(&d);
652 self.displacement = d_prime.iter().copied().collect();
653
654 self.covariance = s.dot(&self.covariance).dot(&s.t());
656
657 Ok(())
658 }
659
660 pub fn purity(&self) -> f64 {
662 let two_v = &self.covariance * 2.0;
665
666 let n = two_v.nrows();
667
668 if n == 0 {
669 return 1.0;
670 }
671
672 match scirs2_linalg::det::<f64>(&two_v.view(), None) {
674 Ok(det) => {
675 if det > 0.0 {
676 1.0 / det.sqrt()
677 } else {
678 0.0
681 }
682 }
683 Err(_) => {
684 1.0
687 }
688 }
689 }
690}
691
692pub fn boson_to_qubit_encoding(
694 op: &BosonOperator,
695 encoding: &str,
696) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
697 match encoding {
698 "binary" => {
699 let _n_qubits = (op.truncation as f64).log2().ceil() as usize;
702
703 Err(QuantRS2Error::UnsupportedOperation(
705 "Binary encoding not yet implemented".into(),
706 ))
707 }
708 "unary" => {
709 Err(QuantRS2Error::UnsupportedOperation(
712 "Unary encoding not yet implemented".into(),
713 ))
714 }
715 "gray" => {
716 Err(QuantRS2Error::UnsupportedOperation(
718 "Gray code encoding not yet implemented".into(),
719 ))
720 }
721 _ => Err(QuantRS2Error::InvalidInput(format!(
722 "Unknown encoding: {encoding}"
723 ))),
724 }
725}
726
727fn matrix_exponential_complex(a: &Array2<Complex64>) -> QuantRS2Result<Array2<Complex64>> {
729 let n = a.shape()[0];
730 if a.shape()[1] != n {
731 return Err(QuantRS2Error::InvalidInput("Matrix must be square".into()));
732 }
733
734 let norm = a.mapv(|x| x.norm()).sum() / (n as f64);
736 let scale = (norm.log2().ceil() as i32).max(0);
737 let scale_factor = 2.0_f64.powi(scale);
738 let a_scaled = a.mapv(|x| x / scale_factor);
739
740 let mut u = Array2::from_shape_fn((n, n), |(i, j)| {
742 if i == j {
743 Complex64::new(1.0, 0.0)
744 } else {
745 Complex64::new(0.0, 0.0)
746 }
747 });
748 let mut v = Array2::from_shape_fn((n, n), |(i, j)| {
749 if i == j {
750 Complex64::new(1.0, 0.0)
751 } else {
752 Complex64::new(0.0, 0.0)
753 }
754 });
755
756 let a2 = a_scaled.dot(&a_scaled);
757 let a4 = a2.dot(&a2);
758 let a6 = a4.dot(&a2);
759
760 let c = [
762 1.0,
763 1.0 / 2.0,
764 1.0 / 6.0,
765 1.0 / 24.0,
766 1.0 / 120.0,
767 1.0 / 720.0,
768 1.0 / 5040.0,
769 ];
770
771 u = &u * c[0]
772 + &a_scaled * c[1]
773 + &a2 * c[2]
774 + &a_scaled.dot(&a2) * c[3]
775 + &a4 * c[4]
776 + &a_scaled.dot(&a4) * c[5]
777 + &a6 * c[6];
778
779 v = &v * c[0] - &a_scaled * c[1] + &a2 * c[2] - &a_scaled.dot(&a2) * c[3] + &a4 * c[4]
780 - &a_scaled.dot(&a4) * c[5]
781 + &a6 * c[6];
782
783 let v_minus_u = &v - &u;
785 let two_u = &u * 2.0;
786
787 let exp_a_scaled = solve_complex(&v_minus_u, &two_u)?;
789
790 let mut result = exp_a_scaled;
792 for _ in 0..scale {
793 result = result.dot(&result);
794 }
795
796 Ok(result)
797}
798
799fn solve_complex(
801 a: &Array2<Complex64>,
802 b: &Array2<Complex64>,
803) -> QuantRS2Result<Array2<Complex64>> {
804 let n = a.shape()[0];
805 if a.shape()[1] != n || b.shape()[0] != n {
806 return Err(QuantRS2Error::InvalidInput(
807 "Invalid dimensions for solve".into(),
808 ));
809 }
810
811 let mut aug = Array2::zeros((n, n + b.shape()[1]));
813 for i in 0..n {
814 for j in 0..n {
815 aug[[i, j]] = a[[i, j]];
816 }
817 for j in 0..b.shape()[1] {
818 aug[[i, n + j]] = b[[i, j]];
819 }
820 }
821
822 for i in 0..n {
824 let mut max_row = i;
826 for k in i + 1..n {
827 if aug[[k, i]].norm() > aug[[max_row, i]].norm() {
828 max_row = k;
829 }
830 }
831
832 for j in 0..n + b.shape()[1] {
834 let temp = aug[[i, j]];
835 aug[[i, j]] = aug[[max_row, j]];
836 aug[[max_row, j]] = temp;
837 }
838
839 if aug[[i, i]].norm() < 1e-12 {
841 return Err(QuantRS2Error::LinalgError("Singular matrix".into()));
842 }
843
844 for k in i + 1..n {
846 let factor = aug[[k, i]] / aug[[i, i]];
847 for j in i..n + b.shape()[1] {
848 aug[[k, j]] = aug[[k, j]] - factor * aug[[i, j]];
849 }
850 }
851 }
852
853 let mut x = Array2::zeros((n, b.shape()[1]));
855 for i in (0..n).rev() {
856 for j in 0..b.shape()[1] {
857 x[[i, j]] = aug[[i, n + j]];
858 for k in i + 1..n {
859 x[[i, j]] = x[[i, j]] - aug[[i, k]] * x[[k, j]];
860 }
861 x[[i, j]] = x[[i, j]] / aug[[i, i]];
862 }
863 }
864
865 Ok(x)
866}
867
868fn kron_complex(a: &Array2<Complex64>, b: &Array2<Complex64>) -> Array2<Complex64> {
870 let (m, n) = a.dim();
871 let (p, q) = b.dim();
872 let mut result = Array2::zeros((m * p, n * q));
873
874 for i in 0..m {
875 for j in 0..n {
876 for k in 0..p {
877 for l in 0..q {
878 result[[i * p + k, j * q + l]] = a[[i, j]] * b[[k, l]];
879 }
880 }
881 }
882 }
883
884 result
885}
886
887#[derive(Debug, Clone)]
889pub struct SparseBosonOperator {
890 pub rows: Vec<usize>,
892 pub cols: Vec<usize>,
894 pub data: Vec<Complex64>,
896 pub shape: (usize, usize),
898}
899
900impl SparseBosonOperator {
901 pub const fn from_triplets(
903 rows: Vec<usize>,
904 cols: Vec<usize>,
905 data: Vec<Complex64>,
906 shape: (usize, usize),
907 ) -> Self {
908 Self {
909 rows,
910 cols,
911 data,
912 shape,
913 }
914 }
915
916 pub fn to_dense(&self) -> Array2<Complex64> {
918 let mut matrix = Array2::zeros(self.shape);
919 for ((&i, &j), &val) in self.rows.iter().zip(&self.cols).zip(&self.data) {
920 matrix[[i, j]] = val;
921 }
922 matrix
923 }
924
925 pub fn nnz(&self) -> usize {
927 self.data.len()
928 }
929}
930
931#[cfg(test)]
932mod tests {
933 use super::*;
934
935 macro_rules! assert_relative_eq {
937 ($left:expr, $right:expr, epsilon = $epsilon:expr) => {
938 let diff = ($left - $right).abs();
939 assert!(diff < $epsilon, "assertion failed: `(left ≈ right)`\n left: `{:?}`,\n right: `{:?}`,\n diff: `{:?}`,\nepsilon: `{:?}`", $left, $right, diff, $epsilon);
940 };
941 }
942
943 #[test]
944 fn test_creation_annihilation_operators() {
945 let truncation = 5;
946 let a = BosonOperator::annihilation(0, truncation);
947 let a_dag = BosonOperator::creation(0, truncation);
948
949 let a_mat = a
950 .to_matrix()
951 .expect("Annihilation matrix creation should succeed");
952 let a_dag_mat = a_dag
953 .to_matrix()
954 .expect("Creation matrix creation should succeed");
955
956 for i in 0..truncation {
958 for j in 0..truncation {
959 assert_relative_eq!(a_dag_mat[[i, j]].re, a_mat[[j, i]].re, epsilon = 1e-12);
960 assert_relative_eq!(a_dag_mat[[i, j]].im, -a_mat[[j, i]].im, epsilon = 1e-12);
961 }
962 }
963 }
964
965 #[test]
966 fn test_number_operator() {
967 let truncation = 5;
968 let n_op = BosonOperator::number(0, truncation);
969 let n_mat = n_op
970 .to_matrix()
971 .expect("Number operator matrix creation should succeed");
972
973 for i in 0..truncation {
975 assert_relative_eq!(n_mat[[i, i]].re, i as f64, epsilon = 1e-12);
976 assert_relative_eq!(n_mat[[i, i]].im, 0.0, epsilon = 1e-12);
977 }
978
979 for i in 0..truncation {
981 for j in 0..truncation {
982 if i != j {
983 assert_relative_eq!(n_mat[[i, j]].norm(), 0.0, epsilon = 1e-12);
984 }
985 }
986 }
987 }
988
989 #[test]
990 fn test_position_momentum_commutation() {
991 let truncation = 10;
992 let x = BosonOperator::position(0, truncation);
993 let p = BosonOperator::momentum(0, truncation);
994
995 let x_mat = x
996 .to_matrix()
997 .expect("Position operator matrix creation should succeed");
998 let p_mat = p
999 .to_matrix()
1000 .expect("Momentum operator matrix creation should succeed");
1001
1002 let xp = x_mat.dot(&p_mat);
1004 let px = p_mat.dot(&x_mat);
1005 let commutator = &xp - &px;
1006
1007 println!("Commutator[0,0] = {:?}", commutator[[0, 0]]);
1011 println!("Expected canonical [x,p] = i, but we have normalization factors");
1012
1013 let expected_value = 1.0;
1017
1018 for i in 0..truncation - 1 {
1020 for j in 0..truncation - 1 {
1021 if i == j {
1022 assert_relative_eq!(commutator[[i, j]].re, 0.0, epsilon = 1e-10);
1023 assert_relative_eq!(commutator[[i, j]].im, expected_value, epsilon = 1e-10);
1024 } else {
1025 assert_relative_eq!(commutator[[i, j]].norm(), 0.0, epsilon = 1e-10);
1026 }
1027 }
1028 }
1029 }
1030
1031 #[test]
1032 fn test_harmonic_oscillator_hamiltonian() {
1033 let n_modes = 2;
1034 let truncation = 5;
1035 let mut ham = BosonHamiltonian::new(n_modes, truncation);
1036
1037 ham.add_harmonic_oscillator(0, 1.0);
1039 ham.add_harmonic_oscillator(1, 2.0);
1040
1041 ham.add_beam_splitter(0, 1, 0.5);
1043
1044 assert_eq!(ham.terms.len(), 4); assert!(ham.is_hermitian(1e-12));
1048 }
1049
1050 #[test]
1051 fn test_gaussian_state() {
1052 let n_modes = 2;
1053
1054 let vacuum = GaussianState::vacuum(n_modes);
1056 assert_eq!(vacuum.displacement.len(), 2 * n_modes);
1057 assert_relative_eq!(vacuum.purity(), 1.0, epsilon = 1e-12);
1058
1059 let alpha = Complex64::new(1.0, 0.5);
1061 let coherent = GaussianState::coherent(n_modes, 0, alpha);
1062 assert_relative_eq!(
1063 coherent.displacement[0],
1064 alpha.re * 2.0_f64.sqrt(),
1065 epsilon = 1e-12
1066 );
1067 assert_relative_eq!(
1068 coherent.displacement[1],
1069 alpha.im * 2.0_f64.sqrt(),
1070 epsilon = 1e-12
1071 );
1072
1073 let squeezed = GaussianState::squeezed(n_modes, 0, 1.0, 0.0);
1075 assert!(squeezed.covariance[[0, 0]] > 0.5); assert!(squeezed.covariance[[1, 1]] < 0.5); }
1080}