1use crate::prelude::SimulatorError;
8use ndarray::{Array1, Array2, ArrayView1};
9use num_complex::Complex64;
10use scirs2_core::parallel_ops::*;
11use std::collections::HashMap;
12
13use crate::error::Result;
14use crate::scirs2_integration::SciRS2Backend;
15
16pub struct LindladSimulator {
18 num_qubits: usize,
20 density_matrix: Array2<Complex64>,
22 lindblad_ops: Vec<LindladOperator>,
24 hamiltonian: Option<Array2<Complex64>>,
26 time_step: f64,
28 integration_method: IntegrationMethod,
30 backend: Option<SciRS2Backend>,
32}
33
34#[derive(Debug, Clone)]
36pub struct LindladOperator {
37 pub operator: Array2<Complex64>,
39 pub rate: f64,
41 pub label: Option<String>,
43}
44
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47pub enum IntegrationMethod {
48 Euler,
50 RungeKutta4,
52 AdaptiveRK,
54 MatrixExponential,
56}
57
58impl LindladSimulator {
59 pub fn new(num_qubits: usize) -> Result<Self> {
61 let dim = 1 << num_qubits;
62 let mut density_matrix = Array2::zeros((dim, dim));
63 density_matrix[[0, 0]] = Complex64::new(1.0, 0.0); Ok(Self {
66 num_qubits,
67 density_matrix,
68 lindblad_ops: Vec::new(),
69 hamiltonian: None,
70 time_step: 0.01,
71 integration_method: IntegrationMethod::RungeKutta4,
72 backend: None,
73 })
74 }
75
76 pub fn with_scirs2_backend(mut self) -> Result<Self> {
78 self.backend = Some(SciRS2Backend::new());
79 Ok(self)
80 }
81
82 pub fn set_density_matrix(&mut self, rho: Array2<Complex64>) -> Result<()> {
84 let dim = 1 << self.num_qubits;
85 if rho.shape() != [dim, dim] {
86 return Err(SimulatorError::DimensionMismatch(format!(
87 "Expected {}x{} density matrix",
88 dim, dim
89 )));
90 }
91
92 let trace: Complex64 = rho.diag().iter().sum();
94 if (trace.re - 1.0).abs() > 1e-10 || trace.im.abs() > 1e-10 {
95 return Err(SimulatorError::InvalidInput(format!(
96 "Density matrix not normalized: trace = {}",
97 trace
98 )));
99 }
100
101 self.density_matrix = rho;
102 Ok(())
103 }
104
105 pub fn from_state_vector(&mut self, psi: &ArrayView1<Complex64>) -> Result<()> {
107 let dim = 1 << self.num_qubits;
108 if psi.len() != dim {
109 return Err(SimulatorError::DimensionMismatch(format!(
110 "Expected state vector of length {}",
111 dim
112 )));
113 }
114
115 let mut rho = Array2::zeros((dim, dim));
117 for i in 0..dim {
118 for j in 0..dim {
119 rho[[i, j]] = psi[i] * psi[j].conj();
120 }
121 }
122
123 self.density_matrix = rho;
124 Ok(())
125 }
126
127 pub fn add_lindblad_operator(&mut self, operator: LindladOperator) -> Result<()> {
129 let dim = 1 << self.num_qubits;
130 if operator.operator.shape() != [dim, dim] {
131 return Err(SimulatorError::DimensionMismatch(format!(
132 "Operator must be {}x{}",
133 dim, dim
134 )));
135 }
136
137 self.lindblad_ops.push(operator);
138 Ok(())
139 }
140
141 pub fn set_hamiltonian(&mut self, h: Array2<Complex64>) -> Result<()> {
143 let dim = 1 << self.num_qubits;
144 if h.shape() != [dim, dim] {
145 return Err(SimulatorError::DimensionMismatch(format!(
146 "Hamiltonian must be {}x{}",
147 dim, dim
148 )));
149 }
150
151 self.hamiltonian = Some(h);
152 Ok(())
153 }
154
155 pub fn set_time_step(&mut self, dt: f64) {
157 self.time_step = dt;
158 }
159
160 pub fn set_integration_method(&mut self, method: IntegrationMethod) {
162 self.integration_method = method;
163 }
164
165 pub fn evolve(&mut self, total_time: f64) -> Result<EvolutionResult> {
167 let num_steps = (total_time / self.time_step).ceil() as usize;
168 let actual_dt = total_time / num_steps as f64;
169
170 let mut times = Vec::with_capacity(num_steps + 1);
171 let mut densities = Vec::new();
172 let mut purities = Vec::with_capacity(num_steps + 1);
173 let mut traces = Vec::with_capacity(num_steps + 1);
174
175 times.push(0.0);
177 purities.push(self.purity());
178 traces.push(self.trace().re);
179
180 if self.num_qubits <= 4 {
182 densities.push(self.density_matrix.clone());
183 }
184
185 for step in 0..num_steps {
187 match self.integration_method {
188 IntegrationMethod::Euler => {
189 self.euler_step(actual_dt)?;
190 }
191 IntegrationMethod::RungeKutta4 => {
192 self.runge_kutta4_step(actual_dt)?;
193 }
194 IntegrationMethod::AdaptiveRK => {
195 self.adaptive_rk_step(actual_dt)?;
196 }
197 IntegrationMethod::MatrixExponential => {
198 self.matrix_exponential_step(actual_dt)?;
199 }
200 }
201
202 let current_time = (step + 1) as f64 * actual_dt;
203 times.push(current_time);
204 purities.push(self.purity());
205 traces.push(self.trace().re);
206
207 if self.num_qubits <= 4 {
208 densities.push(self.density_matrix.clone());
209 }
210 }
211
212 Ok(EvolutionResult {
213 times,
214 densities,
215 purities,
216 traces,
217 final_density: self.density_matrix.clone(),
218 })
219 }
220
221 fn euler_step(&mut self, dt: f64) -> Result<()> {
223 let derivative = self.compute_lindblad_derivative()?;
224
225 for ((i, j), drho_dt) in derivative.indexed_iter() {
227 self.density_matrix[[i, j]] += dt * drho_dt;
228 }
229
230 self.renormalize();
232
233 Ok(())
234 }
235
236 fn runge_kutta4_step(&mut self, dt: f64) -> Result<()> {
238 let rho0 = self.density_matrix.clone();
239
240 let k1 = self.compute_lindblad_derivative()?;
242
243 self.density_matrix = &rho0 + &(&k1 * (dt / 2.0));
245 let k2 = self.compute_lindblad_derivative()?;
246
247 self.density_matrix = &rho0 + &(&k2 * (dt / 2.0));
249 let k3 = self.compute_lindblad_derivative()?;
250
251 self.density_matrix = &rho0 + &(&k3 * dt);
253 let k4 = self.compute_lindblad_derivative()?;
254
255 let coeff = Complex64::new(dt / 6.0, 0.0);
257 self.density_matrix = rho0
258 + coeff
259 * (&k1 + &(Complex64::new(2.0, 0.0) * k2) + &(Complex64::new(2.0, 0.0) * k3) + &k4);
260
261 self.renormalize();
262 Ok(())
263 }
264
265 fn adaptive_rk_step(&mut self, dt: f64) -> Result<()> {
267 self.runge_kutta4_step(dt)
270 }
271
272 fn matrix_exponential_step(&mut self, dt: f64) -> Result<()> {
274 if let Some(ref backend) = self.backend {
275 self.matrix_exp_with_scirs2(dt)
277 } else {
278 self.matrix_exp_series(dt)
280 }
281 }
282
283 fn matrix_exp_with_scirs2(&mut self, dt: f64) -> Result<()> {
285 self.matrix_exp_series(dt)
288 }
289
290 fn matrix_exp_series(&mut self, dt: f64) -> Result<()> {
292 let lindbladian = self.construct_lindbladian_superoperator()?;
293
294 let dim_sq = lindbladian.nrows();
296 let mut result = Array2::eye(dim_sq);
297 let mut term = Array2::eye(dim_sq);
298 let l_dt = &lindbladian * dt;
299
300 for n in 1..=20 {
302 term = term.dot(&l_dt) / n as f64;
303 result = result + &term;
304
305 let term_norm: f64 = term.iter().map(|x| x.norm()).sum();
307 if term_norm < 1e-12 {
308 break;
309 }
310 }
311
312 let rho_vec = self.vectorize_density_matrix();
314 let new_rho_vec = result.dot(&rho_vec);
315 self.density_matrix = self.devectorize_density_matrix(&new_rho_vec);
316
317 self.renormalize();
318 Ok(())
319 }
320
321 fn compute_lindblad_derivative(&self) -> Result<Array2<Complex64>> {
323 let dim = self.density_matrix.nrows();
324 let mut derivative = Array2::zeros((dim, dim));
325
326 if let Some(ref h) = self.hamiltonian {
328 let commutator = h.dot(&self.density_matrix) - self.density_matrix.dot(h);
329 derivative += &(commutator * Complex64::new(0.0, -1.0));
330 }
331
332 for lindblad_op in &self.lindblad_ops {
334 let l = &lindblad_op.operator;
335 let l_dag = l.t().mapv(|x| x.conj());
336 let rate = lindblad_op.rate;
337
338 let dissipation = l.dot(&self.density_matrix).dot(&l_dag);
340 let anticommutator =
341 l_dag.dot(l).dot(&self.density_matrix) + self.density_matrix.dot(&l_dag.dot(l));
342 let half = Complex64::new(0.5, 0.0);
343
344 derivative += &((dissipation - &anticommutator * half) * rate);
345 }
346
347 Ok(derivative)
348 }
349
350 fn construct_lindbladian_superoperator(&self) -> Result<Array2<Complex64>> {
352 let dim = 1 << self.num_qubits;
353 let super_dim = dim * dim;
354 let mut lindbladian = Array2::zeros((super_dim, super_dim));
355
356 if let Some(ref h) = self.hamiltonian {
358 let eye: Array2<Complex64> = Array2::eye(dim);
359 let h_left = kron(&h, &eye);
360 let h_t = h.t().to_owned();
361 let h_right = kron(&eye, &h_t);
362 lindbladian += &((h_left - h_right) * Complex64::new(0.0, -1.0));
363 }
364
365 for lindblad_op in &self.lindblad_ops {
367 let l = &lindblad_op.operator;
368 let l_dag = l.t().mapv(|x| x.conj());
369 let rate = lindblad_op.rate;
370
371 let eye: Array2<Complex64> = Array2::eye(dim);
372 let l_dag_l = l_dag.dot(l);
373
374 let left_term = kron(l, &l.mapv(|x| x.conj()));
376
377 let l_dag_l_t = l_dag_l.t().to_owned();
379 let right_term = kron(&l_dag_l, &eye) + kron(&eye, &l_dag_l_t);
380 let half = Complex64::new(0.5, 0.0);
381
382 lindbladian += &((left_term - &right_term * half) * rate);
383 }
384
385 Ok(lindbladian)
386 }
387
388 fn vectorize_density_matrix(&self) -> Array1<Complex64> {
390 let dim = self.density_matrix.nrows();
391 let mut vec = Array1::zeros(dim * dim);
392
393 for (i, &val) in self.density_matrix.iter().enumerate() {
394 vec[i] = val;
395 }
396
397 vec
398 }
399
400 fn devectorize_density_matrix(&self, vec: &Array1<Complex64>) -> Array2<Complex64> {
402 let dim = (vec.len() as f64).sqrt() as usize;
403 Array2::from_shape_vec((dim, dim), vec.to_vec()).unwrap()
404 }
405
406 pub fn purity(&self) -> f64 {
408 let rho_squared = self.density_matrix.dot(&self.density_matrix);
409 rho_squared.diag().iter().map(|x| x.re).sum()
410 }
411
412 pub fn trace(&self) -> Complex64 {
414 self.density_matrix.diag().iter().sum()
415 }
416
417 fn renormalize(&mut self) {
419 let trace = self.trace();
420 if trace.norm() > 1e-12 {
421 self.density_matrix /= trace;
422 }
423 }
424
425 pub fn get_density_matrix(&self) -> &Array2<Complex64> {
427 &self.density_matrix
428 }
429
430 pub fn expectation_value(&self, observable: &Array2<Complex64>) -> Result<Complex64> {
432 if observable.shape() != self.density_matrix.shape() {
433 return Err(SimulatorError::DimensionMismatch(
434 "Observable and density matrix dimensions must match".to_string(),
435 ));
436 }
437
438 let product = self.density_matrix.dot(observable);
440 Ok(product.diag().iter().sum())
441 }
442}
443
444#[derive(Debug, Clone)]
446pub struct EvolutionResult {
447 pub times: Vec<f64>,
449 pub densities: Vec<Array2<Complex64>>,
451 pub purities: Vec<f64>,
453 pub traces: Vec<f64>,
455 pub final_density: Array2<Complex64>,
457}
458
459#[derive(Debug, Clone)]
461pub struct QuantumChannel {
462 pub kraus_operators: Vec<Array2<Complex64>>,
464 pub name: String,
466}
467
468impl QuantumChannel {
469 pub fn depolarizing(num_qubits: usize, probability: f64) -> Self {
471 let dim = 1 << num_qubits;
472 let mut kraus_ops = Vec::new();
473
474 let sqrt_p0 = (1.0 - probability).sqrt();
476 let eye: Array2<Complex64> = Array2::eye(dim) * Complex64::new(sqrt_p0, 0.0);
477 kraus_ops.push(eye);
478
479 if num_qubits == 1 {
481 let sqrt_p = (probability / 3.0).sqrt();
482
483 let mut pauli_x = Array2::zeros((2, 2));
485 pauli_x[[0, 1]] = Complex64::new(sqrt_p, 0.0);
486 pauli_x[[1, 0]] = Complex64::new(sqrt_p, 0.0);
487 kraus_ops.push(pauli_x);
488
489 let mut pauli_y = Array2::zeros((2, 2));
491 pauli_y[[0, 1]] = Complex64::new(0.0, -sqrt_p);
492 pauli_y[[1, 0]] = Complex64::new(0.0, sqrt_p);
493 kraus_ops.push(pauli_y);
494
495 let mut pauli_z = Array2::zeros((2, 2));
497 pauli_z[[0, 0]] = Complex64::new(sqrt_p, 0.0);
498 pauli_z[[1, 1]] = Complex64::new(-sqrt_p, 0.0);
499 kraus_ops.push(pauli_z);
500 }
501
502 Self {
503 kraus_operators: kraus_ops,
504 name: format!("Depolarizing({:.3})", probability),
505 }
506 }
507
508 pub fn amplitude_damping(gamma: f64) -> Self {
510 let mut kraus_ops = Vec::new();
511
512 let mut k0 = Array2::zeros((2, 2));
514 k0[[0, 0]] = Complex64::new(1.0, 0.0);
515 k0[[1, 1]] = Complex64::new((1.0 - gamma).sqrt(), 0.0);
516 kraus_ops.push(k0);
517
518 let mut k1 = Array2::zeros((2, 2));
520 k1[[0, 1]] = Complex64::new(gamma.sqrt(), 0.0);
521 kraus_ops.push(k1);
522
523 Self {
524 kraus_operators: kraus_ops,
525 name: format!("AmplitudeDamping({:.3})", gamma),
526 }
527 }
528
529 pub fn phase_damping(gamma: f64) -> Self {
531 let mut kraus_ops = Vec::new();
532
533 let mut k0 = Array2::zeros((2, 2));
535 k0[[0, 0]] = Complex64::new(1.0, 0.0);
536 k0[[1, 1]] = Complex64::new((1.0 - gamma).sqrt(), 0.0);
537 kraus_ops.push(k0);
538
539 let mut k1 = Array2::zeros((2, 2));
541 k1[[1, 1]] = Complex64::new(gamma.sqrt(), 0.0);
542 kraus_ops.push(k1);
543
544 Self {
545 kraus_operators: kraus_ops,
546 name: format!("PhaseDamping({:.3})", gamma),
547 }
548 }
549
550 pub fn apply(&self, rho: &Array2<Complex64>) -> Array2<Complex64> {
552 let dim = rho.nrows();
553 let mut result = Array2::zeros((dim, dim));
554
555 for kraus_op in &self.kraus_operators {
556 let k_dag = kraus_op.t().mapv(|x| x.conj());
557 result += &kraus_op.dot(rho).dot(&k_dag);
558 }
559
560 result
561 }
562
563 pub fn is_trace_preserving(&self) -> bool {
565 let dim = self.kraus_operators[0].nrows();
566 let mut sum = Array2::zeros((dim, dim));
567
568 for kraus_op in &self.kraus_operators {
569 let k_dag = kraus_op.t().mapv(|x| x.conj());
570 sum += &k_dag.dot(kraus_op);
571 }
572
573 let eye: Array2<Complex64> = Array2::eye(dim);
575 (&sum - &eye).iter().all(|&x| x.norm() < 1e-10)
576 }
577}
578
579pub struct ProcessTomography {
581 pub input_states: Vec<Array2<Complex64>>,
583 pub output_measurements: Vec<Array2<Complex64>>,
585 pub process_matrix: Option<Array2<Complex64>>,
587}
588
589impl ProcessTomography {
590 pub fn new(num_qubits: usize) -> Self {
592 let mut input_states = Vec::new();
593
594 if num_qubits == 1 {
596 let mut rho_0 = Array2::zeros((2, 2));
598 rho_0[[0, 0]] = Complex64::new(1.0, 0.0);
599 input_states.push(rho_0);
600
601 let mut rho_1 = Array2::zeros((2, 2));
602 rho_1[[1, 1]] = Complex64::new(1.0, 0.0);
603 input_states.push(rho_1);
604
605 let mut rho_plus = Array2::zeros((2, 2));
606 rho_plus[[0, 0]] = Complex64::new(0.5, 0.0);
607 rho_plus[[0, 1]] = Complex64::new(0.5, 0.0);
608 rho_plus[[1, 0]] = Complex64::new(0.5, 0.0);
609 rho_plus[[1, 1]] = Complex64::new(0.5, 0.0);
610 input_states.push(rho_plus);
611
612 let mut rho_plus_i = Array2::zeros((2, 2));
613 rho_plus_i[[0, 0]] = Complex64::new(0.5, 0.0);
614 rho_plus_i[[0, 1]] = Complex64::new(0.0, -0.5);
615 rho_plus_i[[1, 0]] = Complex64::new(0.0, 0.5);
616 rho_plus_i[[1, 1]] = Complex64::new(0.5, 0.0);
617 input_states.push(rho_plus_i);
618 }
619
620 Self {
621 input_states,
622 output_measurements: Vec::new(),
623 process_matrix: None,
624 }
625 }
626
627 pub fn characterize_channel(&mut self, channel: &QuantumChannel) -> Result<()> {
629 self.output_measurements.clear();
630
631 for input_state in &self.input_states {
632 let output = channel.apply(input_state);
633 self.output_measurements.push(output);
634 }
635
636 self.reconstruct_process_matrix()?;
637 Ok(())
638 }
639
640 fn reconstruct_process_matrix(&mut self) -> Result<()> {
642 let dim = self.input_states[0].nrows();
646 let process_dim = dim * dim;
647 let mut chi = Array2::zeros((process_dim, process_dim));
648
649 self.process_matrix = Some(chi);
652
653 Ok(())
654 }
655
656 pub fn process_fidelity(&self, ideal_channel: &QuantumChannel) -> Result<f64> {
658 if self.output_measurements.is_empty() {
659 return Err(SimulatorError::InvalidOperation(
660 "No measurements available for fidelity calculation".to_string(),
661 ));
662 }
663
664 let mut fidelity_sum = 0.0;
665
666 for (i, input_state) in self.input_states.iter().enumerate() {
667 let ideal_output = ideal_channel.apply(input_state);
668 let measured_output = &self.output_measurements[i];
669
670 let fidelity = quantum_fidelity(measured_output, &ideal_output);
672 fidelity_sum += fidelity;
673 }
674
675 Ok(fidelity_sum / self.input_states.len() as f64)
676 }
677}
678
679pub fn quantum_fidelity(rho1: &Array2<Complex64>, rho2: &Array2<Complex64>) -> f64 {
681 let trace_distance = (rho1 - rho2).iter().map(|x| x.norm()).sum::<f64>();
687 (1.0 - 0.5 * trace_distance).max(0.0)
688}
689
690fn kron(a: &Array2<Complex64>, b: &Array2<Complex64>) -> Array2<Complex64> {
692 let (m1, n1) = a.dim();
693 let (m2, n2) = b.dim();
694 let mut result = Array2::zeros((m1 * m2, n1 * n2));
695
696 for i in 0..m1 {
697 for j in 0..n1 {
698 for k in 0..m2 {
699 for l in 0..n2 {
700 result[[i * m2 + k, j * n2 + l]] = a[[i, j]] * b[[k, l]];
701 }
702 }
703 }
704 }
705
706 result
707}
708
709pub struct NoiseModelBuilder {
711 channels: HashMap<String, QuantumChannel>,
712 application_order: Vec<String>,
713}
714
715impl NoiseModelBuilder {
716 pub fn new() -> Self {
717 Self {
718 channels: HashMap::new(),
719 application_order: Vec::new(),
720 }
721 }
722
723 pub fn depolarizing(mut self, name: &str, probability: f64) -> Self {
725 let channel = QuantumChannel::depolarizing(1, probability);
726 self.channels.insert(name.to_string(), channel);
727 self.application_order.push(name.to_string());
728 self
729 }
730
731 pub fn amplitude_damping(mut self, name: &str, gamma: f64) -> Self {
733 let channel = QuantumChannel::amplitude_damping(gamma);
734 self.channels.insert(name.to_string(), channel);
735 self.application_order.push(name.to_string());
736 self
737 }
738
739 pub fn phase_damping(mut self, name: &str, gamma: f64) -> Self {
741 let channel = QuantumChannel::phase_damping(gamma);
742 self.channels.insert(name.to_string(), channel);
743 self.application_order.push(name.to_string());
744 self
745 }
746
747 pub fn build(self) -> CompositeNoiseModel {
749 CompositeNoiseModel {
750 channels: self.channels,
751 application_order: self.application_order,
752 }
753 }
754}
755
756#[derive(Debug, Clone)]
758pub struct CompositeNoiseModel {
759 channels: HashMap<String, QuantumChannel>,
760 application_order: Vec<String>,
761}
762
763impl CompositeNoiseModel {
764 pub fn apply(&self, rho: &Array2<Complex64>) -> Array2<Complex64> {
766 let mut result = rho.clone();
767
768 for channel_name in &self.application_order {
769 if let Some(channel) = self.channels.get(channel_name) {
770 result = channel.apply(&result);
771 }
772 }
773
774 result
775 }
776
777 pub fn get_channel(&self, name: &str) -> Option<&QuantumChannel> {
779 self.channels.get(name)
780 }
781}
782
783#[cfg(test)]
784mod tests {
785 use super::*;
786
787 #[test]
788 fn test_lindblad_simulator_creation() {
789 let sim = LindladSimulator::new(2).unwrap();
790 assert_eq!(sim.num_qubits, 2);
791 assert_eq!(sim.density_matrix.shape(), [4, 4]);
792 }
793
794 #[test]
795 fn test_depolarizing_channel() {
796 let channel = QuantumChannel::depolarizing(1, 0.1);
797 assert!(channel.is_trace_preserving());
798 assert_eq!(channel.kraus_operators.len(), 4);
799 }
800
801 #[test]
802 fn test_amplitude_damping() {
803 let channel = QuantumChannel::amplitude_damping(0.2);
804 assert!(channel.is_trace_preserving());
805
806 let mut rho_1 = Array2::zeros((2, 2));
808 rho_1[[1, 1]] = Complex64::new(1.0, 0.0);
809
810 let result = channel.apply(&rho_1);
811
812 assert!(result[[0, 0]].re > 0.0);
814 assert!(result[[1, 1]].re < 1.0);
815 }
816
817 #[test]
818 fn test_noise_model_builder() {
819 let noise_model = NoiseModelBuilder::new()
820 .depolarizing("depol", 0.01)
821 .amplitude_damping("amp_damp", 0.02)
822 .build();
823
824 assert!(noise_model.get_channel("depol").is_some());
825 assert!(noise_model.get_channel("amp_damp").is_some());
826 }
827}