nmr_schedule/reconstruction/
mod.rs1use alloc::{boxed::Box, vec::Vec};
6use ndarray::Ix1;
7use rustfft::{
8 FftPlanner,
9 num_complex::{Complex, ComplexFloat},
10};
11
12use crate::{ComplexSequence, DisplayMode, Schedule};
13
14pub trait Reconstructor {
16 fn reconstruct_from_reals(&mut self, data: &[f64], out: &mut [Complex<f64>]) {
22 assert_eq!(data.len(), out.len());
23
24 out.iter_mut()
25 .zip(data)
26 .for_each(|(complex, real)| *complex = Complex::new(*real, 0.));
27
28 self.reconstruct(out);
29 }
30
31 fn reconstruct(&mut self, data: &mut [Complex<f64>]);
35
36 fn quadrature_reconstruct(&mut self, cos: &[f64], sin: &[f64], out: &mut [Complex<f64>]) {
44 assert_eq!(cos.len(), sin.len());
45 assert_eq!(sin.len(), out.len());
46
47 out.iter_mut()
48 .zip(cos.iter())
49 .zip(sin.iter())
50 .for_each(|((complex, cos), sin)| {
51 *complex = Complex::new(*cos, -sin);
52 });
53
54 self.reconstruct(out);
55 }
56}
57
58pub struct Fft {
60 fft_scratch: Vec<Complex<f64>>,
61}
62
63impl core::fmt::Debug for Fft {
64 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
65 f.debug_struct("Fft").finish()
66 }
67}
68
69impl Fft {
70 pub const fn new() -> Fft {
72 Fft {
73 fft_scratch: Vec::new(),
74 }
75 }
76}
77
78impl Default for Fft {
79 fn default() -> Self {
80 Self::new()
81 }
82}
83
84impl Reconstructor for Fft {
85 fn reconstruct(&mut self, data: &mut [Complex<f64>]) {
86 let mut planner = FftPlanner::new();
87 let fft = planner.plan_fft_forward(data.len());
88
89 self.fft_scratch
90 .resize(fft.get_inplace_scratch_len(), Complex::new(0., 0.));
91
92 fft.process_with_scratch(data, &mut self.fft_scratch);
93
94 let normalize = (data.len() as f64).recip().sqrt();
95
96 for v in data.iter_mut() {
97 *v *= normalize;
98 }
99 }
100}
101
102pub struct Ist<'s> {
113 schedule: &'s Schedule<Ix1>,
114 threshold: Box<dyn Fn(f64) -> f64 + 's>,
115 iterations: usize,
116 fft_scratch: Vec<Complex<f64>>,
117 ft_buffer: Vec<Complex<f64>>,
118 mode: DisplayMode,
119}
120
121impl core::fmt::Debug for Ist<'_> {
122 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
123 f.debug_struct("Ist")
124 .field("schedule", self.schedule)
125 .field("iterations", &self.iterations)
126 .field("mode", &self.mode)
127 .finish()
128 }
129}
130
131impl<'s> Ist<'s> {
132 pub fn new(
139 schedule: &'s Schedule<Ix1>,
140 threshold_function: impl Fn(f64) -> f64 + 's,
141 iterations: usize,
142 mode: DisplayMode,
143 ) -> Ist<'s> {
144 Ist {
145 schedule,
146 threshold: Box::new(threshold_function),
147 iterations,
148 fft_scratch: Vec::new(),
149 ft_buffer: Vec::new(),
150 mode,
151 }
152 }
153}
154
155impl Reconstructor for Ist<'_> {
156 fn reconstruct(&mut self, data: &mut [Complex<f64>]) {
157 assert_eq!(data.len(), self.schedule.len());
158
159 if data.is_empty() {
160 return;
161 }
162
163 self.ft_buffer
164 .iter_mut()
165 .for_each(|sample| *sample = Complex::new(0., 0.));
166
167 let mode = self.mode;
168
169 self.ft_buffer.resize(data.len() * 2, Complex::new(0., 0.));
170
171 let ft_buffer = &mut *self.ft_buffer;
172
173 let mut planner = FftPlanner::new();
174 let fft = planner.plan_fft_forward(ft_buffer.len());
175 let ift = planner.plan_fft_inverse(ft_buffer.len());
176
177 self.fft_scratch.resize(
178 fft.get_inplace_scratch_len()
179 .max(ift.get_inplace_scratch_len()),
180 Complex::new(0., 0.),
181 );
182 let fft_scratch = &mut self.fft_scratch;
183
184 let normalize = (ft_buffer.len() as f64).recip().sqrt();
185
186 for i in 0..self.iterations {
187 ft_buffer
190 .iter_mut()
191 .zip(self.schedule.iter())
192 .zip(data.iter())
193 .for_each(|((sample, was_taken), data)| {
194 if *was_taken {
195 *sample = *data;
196 }
197 });
198
199 let (reconstruction, echo) = ft_buffer.split_at_mut(data.len());
200
201 reconstruction
203 .iter()
204 .zip(echo.iter_mut().rev())
205 .for_each(|(sample, echo)| {
206 *echo = sample.conj();
207 });
208
209 fft.process_with_scratch(ft_buffer, fft_scratch);
210 ft_buffer.apply(|v| v * normalize);
211
212 let max = ft_buffer
213 .iter()
214 .map(|v| mode.magnitude(*v))
215 .max_by(|a, b| a.total_cmp(b))
216 .unwrap(); let relative_threshold = (self.threshold)(i as f64 / (self.iterations as f64 - 1.));
219
220 assert!(relative_threshold <= 1.);
221 assert!(relative_threshold >= 0.);
222
223 let threshold = relative_threshold * max;
224
225 mode.threshold(ft_buffer, threshold);
228
229 ift.process_with_scratch(ft_buffer, fft_scratch);
232 ft_buffer.apply(|v| v * normalize);
233
234 }
239
240 let fft = planner.plan_fft_forward(data.len());
241 let normalize = (data.len() as f64).recip().sqrt();
242
243 ft_buffer
244 .iter_mut()
245 .zip(self.schedule.iter())
246 .zip(data.iter())
247 .for_each(|((sample, was_taken), data)| {
248 if *was_taken {
249 *sample = *data;
250 }
251 });
252
253 data.copy_from_slice(&ft_buffer[0..data.len()]);
254
255 fft.process_with_scratch(data, fft_scratch);
256 data.apply(|v| v * normalize);
257
258 if let DisplayMode::RealPart = mode {
259 data.apply(|v| Complex::new(v.re(), 0.))
260 }
261 }
262}