1use std::sync::Arc;
2
3use num_complex::Complex64;
4
5use crate::operator::{LinearOperator, MatrixFormat, check_apply_shape, materialize_dense};
6use crate::{QmbedError, Result};
7
8pub struct LindbladGenerator {
10 hamiltonian: Vec<Complex64>,
11 jumps: Vec<LindbladJump>,
12 dimension: usize,
13}
14
15struct LindbladJump {
16 operator: Vec<Complex64>,
17 adjoint: Vec<Complex64>,
18 product: Vec<Complex64>,
19}
20
21impl LindbladGenerator {
22 pub fn new(
23 hamiltonian: Arc<dyn LinearOperator>,
24 jumps: Vec<Arc<dyn LinearOperator>>,
25 ) -> Result<Self> {
26 let shape = hamiltonian.shape();
27 if shape.0 != shape.1 {
28 return Err(QmbedError::DimensionMismatch(
29 "the Lindblad Hamiltonian must be square".into(),
30 ));
31 }
32 if jumps.iter().any(|jump| jump.shape() != shape) {
33 return Err(QmbedError::DimensionMismatch(
34 "all Lindblad jumps must match the Hamiltonian".into(),
35 ));
36 }
37 let dimension = shape.0;
38 let hamiltonian = materialize_dense(hamiltonian.as_ref())?;
39 let jumps = jumps
40 .into_iter()
41 .map(|jump| {
42 let operator = materialize_dense(jump.as_ref())?;
43 let adjoint = adjoint(&operator, dimension);
44 let product = multiply(&adjoint, &operator, dimension);
45 Ok(LindbladJump {
46 operator,
47 adjoint,
48 product,
49 })
50 })
51 .collect::<Result<Vec<_>>>()?;
52 Ok(Self {
53 hamiltonian,
54 jumps,
55 dimension,
56 })
57 }
58}
59
60fn multiply(left: &[Complex64], right: &[Complex64], dimension: usize) -> Vec<Complex64> {
61 let mut product = vec![Complex64::new(0.0, 0.0); dimension * dimension];
62 for row in 0..dimension {
63 for middle in 0..dimension {
64 for column in 0..dimension {
65 product[row * dimension + column] +=
66 left[row * dimension + middle] * right[middle * dimension + column];
67 }
68 }
69 }
70 product
71}
72
73fn adjoint(matrix: &[Complex64], dimension: usize) -> Vec<Complex64> {
74 let mut result = vec![Complex64::new(0.0, 0.0); dimension * dimension];
75 for row in 0..dimension {
76 for column in 0..dimension {
77 result[row * dimension + column] = matrix[column * dimension + row].conj();
78 }
79 }
80 result
81}
82
83fn column_major_to_row_major(vector: &[Complex64], dimension: usize) -> Vec<Complex64> {
84 let mut matrix = vec![Complex64::new(0.0, 0.0); vector.len()];
85 for row in 0..dimension {
86 for column in 0..dimension {
87 matrix[row * dimension + column] = vector[row + column * dimension];
88 }
89 }
90 matrix
91}
92
93fn row_major_to_column_major(matrix: &[Complex64], dimension: usize) -> Vec<Complex64> {
94 let mut vector = vec![Complex64::new(0.0, 0.0); matrix.len()];
95 for row in 0..dimension {
96 for column in 0..dimension {
97 vector[row + column * dimension] = matrix[row * dimension + column];
98 }
99 }
100 vector
101}
102
103impl LinearOperator for LindbladGenerator {
104 fn shape(&self) -> (usize, usize) {
105 let size = self.dimension * self.dimension;
106 (size, size)
107 }
108
109 fn format(&self) -> MatrixFormat {
110 MatrixFormat::MatrixFree
111 }
112
113 fn apply(&self, input: &[Complex64], output: &mut [Complex64]) -> Result<()> {
114 check_apply_shape(self.shape(), input, output)?;
115 let dimension = self.dimension;
116 let density = column_major_to_row_major(input, dimension);
117 let h_rho = multiply(&self.hamiltonian, &density, dimension);
118 let rho_h = multiply(&density, &self.hamiltonian, dimension);
119 let mut derivative: Vec<_> = h_rho
120 .iter()
121 .zip(&rho_h)
122 .map(|(left, right)| Complex64::new(0.0, -1.0) * (*left - *right))
123 .collect();
124 for jump in &self.jumps {
125 let gain = multiply(
126 &multiply(&jump.operator, &density, dimension),
127 &jump.adjoint,
128 dimension,
129 );
130 let loss_left = multiply(&jump.product, &density, dimension);
131 let loss_right = multiply(&density, &jump.product, dimension);
132 for index in 0..derivative.len() {
133 derivative[index] += gain[index] - 0.5 * (loss_left[index] + loss_right[index]);
134 }
135 }
136 output.copy_from_slice(&row_major_to_column_major(&derivative, dimension));
137 Ok(())
138 }
139}
140
141#[derive(Clone, Debug)]
142pub struct StateTrackingResult {
143 pub permutation: Vec<usize>,
145 pub phases: Vec<Complex64>,
147 pub overlaps: Vec<f64>,
148 pub ambiguous: Vec<usize>,
149}
150
151fn state_inner(left: &[Complex64], right: &[Complex64]) -> Complex64 {
152 left.iter()
153 .zip(right)
154 .map(|(left, right)| left.conj() * *right)
155 .sum()
156}
157
158pub fn track_states(
161 previous: &[Vec<Complex64>],
162 current: &[Vec<Complex64>],
163 ambiguity_tolerance: f64,
164) -> Result<StateTrackingResult> {
165 let rank = previous.len();
166 if rank == 0
167 || current.len() != rank
168 || !ambiguity_tolerance.is_finite()
169 || ambiguity_tolerance < 0.0
170 {
171 return Err(QmbedError::InvalidOptions(
172 "state tracking requires equal nonzero ranks and a nonnegative tolerance".into(),
173 ));
174 }
175 let dimension = previous[0].len();
176 if dimension == 0
177 || previous.iter().any(|vector| vector.len() != dimension)
178 || current.iter().any(|vector| vector.len() != dimension)
179 {
180 return Err(QmbedError::DimensionMismatch(
181 "tracked state vectors must have equal nonzero dimensions".into(),
182 ));
183 }
184 let overlaps: Vec<Vec<_>> = previous
185 .iter()
186 .map(|left| {
187 current
188 .iter()
189 .map(|right| state_inner(left, right))
190 .collect()
191 })
192 .collect();
193
194 let mut row_potential = vec![0.0_f64; rank + 1];
196 let mut column_potential = vec![0.0_f64; rank + 1];
197 let mut matched_row = vec![0_usize; rank + 1];
198 let mut predecessor = vec![0_usize; rank + 1];
199 for row in 1..=rank {
200 matched_row[0] = row;
201 let mut column = 0;
202 let mut minimum = vec![f64::INFINITY; rank + 1];
203 let mut used = vec![false; rank + 1];
204 loop {
205 used[column] = true;
206 let active_row = matched_row[column];
207 let mut delta = f64::INFINITY;
208 let mut next_column = 0;
209 for candidate in 1..=rank {
210 if used[candidate] {
211 continue;
212 }
213 let cost = -overlaps[active_row - 1][candidate - 1].norm()
214 - row_potential[active_row]
215 - column_potential[candidate];
216 if cost < minimum[candidate] {
217 minimum[candidate] = cost;
218 predecessor[candidate] = column;
219 }
220 if minimum[candidate] < delta {
221 delta = minimum[candidate];
222 next_column = candidate;
223 }
224 }
225 for candidate in 0..=rank {
226 if used[candidate] {
227 row_potential[matched_row[candidate]] += delta;
228 column_potential[candidate] -= delta;
229 } else {
230 minimum[candidate] -= delta;
231 }
232 }
233 column = next_column;
234 if matched_row[column] == 0 {
235 break;
236 }
237 }
238 loop {
239 let previous_column = predecessor[column];
240 matched_row[column] = matched_row[previous_column];
241 column = previous_column;
242 if column == 0 {
243 break;
244 }
245 }
246 }
247 let mut permutation = vec![0_usize; rank];
248 for column in 1..=rank {
249 permutation[matched_row[column] - 1] = column - 1;
250 }
251 let mut phases = Vec::with_capacity(rank);
252 let mut assigned_overlaps = Vec::with_capacity(rank);
253 let mut ambiguous = Vec::new();
254 for row in 0..rank {
255 let overlap = overlaps[row][permutation[row]];
256 let magnitude = overlap.norm();
257 phases.push(if magnitude > f64::EPSILON {
258 overlap.conj() / magnitude
259 } else {
260 Complex64::new(1.0, 0.0)
261 });
262 assigned_overlaps.push(magnitude);
263 let alternative = overlaps[row]
264 .iter()
265 .enumerate()
266 .filter(|(column, _)| *column != permutation[row])
267 .map(|(_, value)| value.norm())
268 .fold(0.0_f64, f64::max);
269 if magnitude - alternative <= ambiguity_tolerance {
270 ambiguous.push(row);
271 }
272 }
273 Ok(StateTrackingResult {
274 permutation,
275 phases,
276 overlaps: assigned_overlaps,
277 ambiguous,
278 })
279}