gmt_lom/
optical_sensitivities.rs

1use std::{fmt::Display, ops::Deref};
2
3use crate::{LinearOpticalModelError, Result};
4#[cfg(feature = "faer")]
5use faer_ext::IntoFaer;
6use nalgebra as na;
7use serde::{Deserialize, Serialize};
8
9/// Optical sensitivities
10///
11/// Linear transformation of M1 and M2 rigid body motions into wavefront and wavefront piston and tip-tilt modes
12#[derive(Debug, Serialize, Deserialize, Clone)]
13pub struct OpticalSensitivities<const N: usize = 84>(Vec<OpticalSensitivity<N>>);
14impl Deref for OpticalSensitivities {
15    type Target = [OpticalSensitivity];
16    fn deref(&self) -> &Self::Target {
17        self.0.as_slice()
18    }
19}
20impl<const N: usize> OpticalSensitivities<N> {
21    /// Returns the wavefront within the exit pupil in `[m]`
22    pub fn masked_wavefront(&self, data: &na::DMatrix<f64>) -> Vec<f64> {
23        self[OpticalSensitivity::<N>::Wavefront(vec![])].into_optics(data)
24    }
25    /// Returns the wavefront in the exit pupil in `[rmm]`
26    pub fn wavefront(&self, data: &na::DMatrix<f64>) -> Vec<f64> {
27        let mut wavefront = self[OpticalSensitivity::<N>::Wavefront(vec![])]
28            .into_optics(data)
29            .into_iter();
30        if let OpticalSensitivity::PupilMask(mask) =
31            &self[OpticalSensitivity::<N>::PupilMask(vec![])]
32        {
33            mask.into_iter()
34                .map(|&mask| {
35                    if mask {
36                        wavefront.next().unwrap()
37                    } else {
38                        0f64
39                    }
40                })
41                .collect()
42        } else {
43            panic!("`PupilMask` is missing from `OpticalSensitivities`")
44        }
45    }
46}
47impl<const N: usize> Display for OpticalSensitivities<N> {
48    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49        writeln!(f, "Optical Sensitivities:")?;
50        for s in &self.0 {
51            writeln!(f, " * {s}")?;
52        }
53        Ok(())
54    }
55}
56/// Optical sensitivity
57///
58/// Linear transformation of M1 and M2 rigid body motions into wavefront and wavefront piston and tip-tilt modes
59#[derive(Debug, Serialize, Deserialize, Clone)]
60pub enum OpticalSensitivity<const N: usize = 84> {
61    /// Wavefront sensitivity `[nxN]` where n in the pupil resolution
62    Wavefront(Vec<f64>),
63    /// Exit pupil tip-tilt sensitivity `[2xN]`
64    TipTilt(Vec<f64>),
65    /// Exit pupil segment tip-tilt `[14xN]`
66    SegmentTipTilt(Vec<f64>),
67    /// Exit pupil segment piston `[7xN]`
68    SegmentPiston(Vec<f64>),
69    SegmentMask(Vec<i32>),
70    PupilMask(Vec<bool>),
71}
72impl<const N: usize> Display for OpticalSensitivity<N> {
73    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
74        match self {
75            OpticalSensitivity::Wavefront(_) => write!(f, "Wavefront"),
76            OpticalSensitivity::TipTilt(_) => write!(f, "TipTilt"),
77            OpticalSensitivity::SegmentTipTilt(_) => write!(f, "SegmentTipTilt"),
78            OpticalSensitivity::SegmentPiston(_) => write!(f, "SegmentPiston"),
79            OpticalSensitivity::SegmentMask(_) => write!(f, "SegmentMask"),
80            OpticalSensitivity::PupilMask(_) => write!(f, "PupilMask"),
81        }
82    }
83}
84impl<'a, const N: usize> From<&'a OpticalSensitivity<N>> for na::DMatrix<f64> {
85    fn from(sens: &'a OpticalSensitivity<N>) -> Self {
86        use OpticalSensitivity::*;
87        match sens {
88            Wavefront(val) => Some(na::DMatrix::from_column_slice(val.len() / N, N, val)),
89            TipTilt(val) => Some(na::DMatrix::from_column_slice(2, N, val)),
90            SegmentTipTilt(val) => Some(na::DMatrix::from_column_slice(14, N, val)),
91            SegmentPiston(val) => Some(na::DMatrix::from_column_slice(7, N, val)),
92            _ => None,
93        }
94        .unwrap()
95    }
96}
97impl<const N: usize> PartialEq<OpticalSensitivity<N>> for OpticalSensitivity<N> {
98    fn eq(&self, other: &OpticalSensitivity<N>) -> bool {
99        use OpticalSensitivity::*;
100        match (self, other) {
101            (Wavefront(_), Wavefront(_)) => true,
102            (TipTilt(_), TipTilt(_)) => true,
103            (SegmentTipTilt(_), SegmentTipTilt(_)) => true,
104            (SegmentPiston(_), SegmentPiston(_)) => true,
105            (SegmentMask(_), SegmentMask(_)) => true,
106            (PupilMask(_), PupilMask(_)) => true,
107            _ => false,
108        }
109    }
110}
111impl<const N: usize> std::ops::Index<OpticalSensitivity<N>> for OpticalSensitivities<N> {
112    type Output = OpticalSensitivity<N>;
113
114    fn index(&self, index: OpticalSensitivity<N>) -> &Self::Output {
115        self.0
116            .iter()
117            // .find_map(|s| if index == *s { Some(s) } else { None })
118            .find(|&s| index == *s)
119            .expect(&format!("cannot find optical sensitivity: {}", index))
120    }
121}
122impl<'a> From<&'a OpticalSensitivity> for &'a [f64] {
123    fn from(sens: &'a OpticalSensitivity) -> Self {
124        use OpticalSensitivity::*;
125        match sens {
126            Wavefront(val) | TipTilt(val) | SegmentTipTilt(val) | SegmentPiston(val) => {
127                Some(val.as_slice())
128            }
129            _ => None,
130        }
131        .unwrap()
132    }
133}
134
135impl<const N: usize> OpticalSensitivity<N> {
136    /// Returns M1 wavefront sensitivities `[nx42]`
137    pub fn m1_wavefront(&self) -> Result<na::DMatrix<f64>> {
138        match self {
139            OpticalSensitivity::Wavefront(sens) => {
140                let n = sens.len() / N;
141                let (_, m1_tr) = sens.split_at(n * 42);
142                Ok(na::DMatrix::from_iterator(
143                    n,
144                    42,
145                    m1_tr.chunks(n).flat_map(|x| x.to_vec()),
146                ))
147            }
148            _ => Err(LinearOpticalModelError::SegmentTipTilt),
149        }
150    }
151    /// Returns M2 segment tip-tilt sensitivities `[14x14]`
152    pub fn m2_rxy(&self) -> Result<na::DMatrix<f64>> {
153        match self {
154            OpticalSensitivity::SegmentTipTilt(sens) => {
155                let (_, m2_tr) = sens.split_at(14 * 42);
156                Ok(na::DMatrix::from_iterator(
157                    14,
158                    14,
159                    m2_tr
160                        .chunks(14 * 3)
161                        .skip(1)
162                        .step_by(2)
163                        .flat_map(|x| (&x[..14 * 2]).to_vec()),
164                ))
165            }
166            _ => Err(LinearOpticalModelError::SegmentTipTilt),
167        }
168    }
169    #[cfg(not(feature = "faer"))]
170    pub fn into_optics(&self, rbm: &na::DMatrix<f64>) -> Vec<f64> {
171        match self {
172            /*OpticalSensitivity::Wavefront(sens) => {
173                let n = sens.len() / N;
174                //println!("n: {}", n);
175                let sensitivity = na::DMatrix::from_column_slice(n, N, sens);
176                //let now = Instant::now();
177                let wfe_var = {
178                    let n_buf = 1_000;
179                    let mut buf = na::DMatrix::<f64>::zeros(n, n_buf);
180                    let mut s = 0;
181                    let mut var = 0f64;
182                    loop {
183                        if s + n_buf > n_sample {
184                            s -= n_buf;
185                            let n_last = n_sample - s;
186                            let mut buf = na::DMatrix::<f64>::zeros(n, n_last);
187                            buf.gemm(1f64, &sensitivity, &rbm.columns(s, n_last), 0f64);
188                            var += buf.row_variance().as_slice().into_iter().sum::<f64>();
189                            break var;
190                        } else {
191                            buf.gemm(1f64, &sensitivity, &rbm.columns(s, n_buf), 0f64);
192                            var += buf.row_variance().as_slice().into_iter().sum::<f64>();
193                        }
194                        s += n_buf;
195                    }
196                };
197                let value = 1e9 * (wfe_var / n_sample as f64).sqrt();
198                OpticalWindLoad::Wavefront(value)
199                /*println!(
200                    "Wavefront: {:6.0}nm in {:.3}s", value,
201                    now.elapsed().as_secs_f64()
202                );*/
203            }*/
204            OpticalSensitivity::TipTilt(sens) => {
205                let sensitivity = na::DMatrix::from_column_slice(2, N, sens);
206                let tip_tilt = sensitivity * rbm;
207                tip_tilt.as_slice().to_owned()
208            }
209            OpticalSensitivity::SegmentTipTilt(sens) => {
210                let sensitivity = na::DMatrix::from_column_slice(14, N, sens);
211                let segment_tip_tilt = sensitivity * rbm;
212                segment_tip_tilt.as_slice().to_owned()
213            }
214            OpticalSensitivity::SegmentPiston(sens) => {
215                let sensitivity = na::DMatrix::from_column_slice(7, N, sens);
216                let segment_piston = sensitivity * rbm;
217                let mut v: Vec<f64> = vec![];
218                for (k, row) in segment_piston.row_iter().take(6).enumerate() {
219                    //println!("{}: {:?}", k, row.shape());
220                    v.extend(
221                        &mut segment_piston
222                            .rows(k + 1, 6 - k)
223                            .row_iter()
224                            .flat_map(|y| (y - row).as_slice().to_owned()),
225                    );
226                }
227                segment_piston.as_slice().to_owned()
228            }
229            OpticalSensitivity::Wavefront(sens) => {
230                let sensitivity = na::DMatrix::from_column_slice(sens.len() / N, N, sens);
231                let wavefront = sensitivity * rbm;
232                wavefront.as_slice().to_owned()
233            }
234            _ => unimplemented!(),
235        }
236    }
237
238    #[cfg(feature = "faer")]
239    pub fn into_optics(&self, rbm: &na::DMatrix<f64>) -> Vec<f64> {
240        log::info!("into optics with faer");
241        let mat = match self {
242            /*OpticalSensitivity::Wavefront(sens) => {
243                let n = sens.len() / N;
244                //println!("n: {}", n);
245                let sensitivity = na::DMatrix::from_column_slice(n, N, sens);
246                //let now = Instant::now();
247                let wfe_var = {
248                    let n_buf = 1_000;
249                    let mut buf = na::DMatrix::<f64>::zeros(n, n_buf);
250                    let mut s = 0;
251                    let mut var = 0f64;
252                    loop {
253                        if s + n_buf > n_sample {
254                            s -= n_buf;
255                            let n_last = n_sample - s;
256                            let mut buf = na::DMatrix::<f64>::zeros(n, n_last);
257                            buf.gemm(1f64, &sensitivity, &rbm.columns(s, n_last), 0f64);
258                            var += buf.row_variance().as_slice().into_iter().sum::<f64>();
259                            break var;
260                        } else {
261                            buf.gemm(1f64, &sensitivity, &rbm.columns(s, n_buf), 0f64);
262                            var += buf.row_variance().as_slice().into_iter().sum::<f64>();
263                        }
264                        s += n_buf;
265                    }
266                };
267                let value = 1e9 * (wfe_var / n_sample as f64).sqrt();
268                OpticalWindLoad::Wavefront(value)
269                /*println!(
270                    "Wavefront: {:6.0}nm in {:.3}s", value,
271                    now.elapsed().as_secs_f64()
272                );*/
273            }*/
274            OpticalSensitivity::TipTilt(sens) => {
275                let sensitivity = faer::MatRef::from_column_major_slice(sens, 2, N);
276                sensitivity * rbm.view_range(.., ..).into_faer()
277            }
278            OpticalSensitivity::SegmentTipTilt(sens) => {
279                let sensitivity = faer::MatRef::from_column_major_slice(sens, 14, N);
280                sensitivity * rbm.view_range(.., ..).into_faer()
281            }
282            OpticalSensitivity::SegmentPiston(sens) => {
283                let sensitivity = faer::MatRef::from_column_major_slice(sens, 7, N);
284                sensitivity * rbm.view_range(.., ..).into_faer()
285                // let mut v: Vec<f64> = vec![];
286                // for (k, row) in segment_piston.row_iter().take(6).enumerate() {
287                //     //println!("{}: {:?}", k, row.shape());
288                //     v.extend(
289                //         &mut segment_piston
290                //             .rows(k + 1, 6 - k)
291                //             .row_iter()
292                //             .flat_map(|y| (y - row).as_slice().to_owned()),
293                //     );
294                // }
295            }
296            OpticalSensitivity::Wavefront(sens) => {
297                let sensitivity = faer::MatRef::from_column_major_slice(sens, sens.len() / N, N);
298                sensitivity * rbm.view_range(.., ..).into_faer()
299            }
300            _ => unimplemented!(),
301        };
302        mat.col_iter()
303            .flat_map(|c| c.iter().cloned().collect::<Vec<_>>())
304            .collect()
305    }
306
307    /*
308       pub fn transform(&self, optics_model: &WindLoadedGmtInner) -> OpticalWindLoad {
309           let n_sample = optics_model.n_sample;
310           let rbm = &optics_model.rbm;
311           match self {
312               OpticalSensitivity::Wavefront(sens) => {
313                   let n = sens.len() / N;
314                   //println!("n: {}", n);
315                   let sensitivity = na::DMatrix::from_column_slice(n, N, sens);
316                   //let now = Instant::now();
317                   let wfe_var = {
318                       let n_buf = 1_000;
319                       let mut buf = na::DMatrix::<f64>::zeros(n, n_buf);
320                       let mut s = 0;
321                       let mut var = 0f64;
322                       loop {
323                           if s + n_buf > n_sample {
324                               s -= n_buf;
325                               let n_last = n_sample - s;
326                               let mut buf = na::DMatrix::<f64>::zeros(n, n_last);
327                               buf.gemm(1f64, &sensitivity, &rbm.columns(s, n_last), 0f64);
328                               var += buf.row_variance().as_slice().into_iter().sum::<f64>();
329                               break var;
330                           } else {
331                               buf.gemm(1f64, &sensitivity, &rbm.columns(s, n_buf), 0f64);
332                               var += buf.row_variance().as_slice().into_iter().sum::<f64>();
333                           }
334                           s += n_buf;
335                       }
336                   };
337                   let value = 1e9 * (wfe_var / n_sample as f64).sqrt();
338                   OpticalWindLoad::Wavefront(value)
339                   /*println!(
340                       "Wavefront: {:6.0}nm in {:.3}s", value,
341                       now.elapsed().as_secs_f64()
342                   );*/
343               }
344               OpticalSensitivity::TipTilt(sens) => {
345                   let sensitivity = na::DMatrix::from_column_slice(2, N, sens);
346                   let tip_tilt = (sensitivity * rbm).map(|x| x.to_mas());
347                   let values = tip_tilt
348                       .column_variance()
349                       .map(|x| x.sqrt())
350                       .as_slice()
351                       .to_owned();
352                   //println!("TT: {:2.0?}mas", &values);
353                   OpticalWindLoad::TipTilt(values)
354               }
355               OpticalSensitivity::SegmentTipTilt(sens) => {
356                   let sensitivity = na::DMatrix::from_column_slice(14, N, sens);
357                   let segment_tip_tilt = (sensitivity * rbm).map(|x| x.to_mas());
358                   let values: Vec<_> = segment_tip_tilt
359                       .column_variance()
360                       .map(|x| x.sqrt())
361                       .as_slice()
362                       .chunks(7)
363                       .map(|x| x.to_owned())
364                       .collect();
365                   //println!("Segment TT: {:2.0?}mas", values,);
366                   OpticalWindLoad::SegmentTipTilt(values)
367               }
368               OpticalSensitivity::SegmentPiston(sens) => {
369                   let sensitivity = na::DMatrix::from_column_slice(7, N, sens);
370                   let segment_piston = (sensitivity * rbm).map(|x| x * 1e9);
371                   let mut v: Vec<f64> = vec![];
372                   for (k, row) in segment_piston.row_iter().take(6).enumerate() {
373                       //println!("{}: {:?}", k, row.shape());
374                       v.extend(
375                           &mut segment_piston
376                               .rows(k + 1, 6 - k)
377                               .row_iter()
378                               .flat_map(|y| (y - row).as_slice().to_owned()),
379                       );
380                   }
381                   let value = (na::DMatrix::from_vec(n_sample, 21, v)
382                       .column_variance()
383                       .sum()
384                       / n_sample as f64)
385                       .sqrt();
386                   let values = segment_piston
387                       .column_variance()
388                       .map(|x| x.sqrt())
389                       .as_slice()
390                       .to_owned();
391                   //println!("Diff. piston std: {:5.0}nm", value,);
392                   //println!("Piston: {:3.0?}nm ; ", &values);
393                   OpticalWindLoad::Piston([
394                       PistonWindLoad::DifferentialSegmentPiston(value),
395                       PistonWindLoad::SegmentPiston(values),
396                   ])
397               }
398               OpticalSensitivity::SegmentMask(_) => OpticalWindLoad::WavefrontWoSegmentPiston(None),
399           }
400       }
401    */
402}
403#[cfg(feature = "crseo")]
404impl OpticalSensitivities {
405    /// Computes optical sensitivities for M1 and M2 rigid body motions
406    ///
407    /// Returns a `Vec<OpticalSensitivity>` containing the linear transformations from M1 and M2 rigid body motions to
408    /// wavefront, tip-tilt, segment tip-tilt and segment piston
409    /// Optionally provides an optical model or uses: [`ceo!(GMT)`](crseo::GMT) and [`ceo!(SOURCE)`](crseo::SOURCE)
410    pub fn compute(model: Option<(crseo::Gmt, crseo::Source)>) -> Result<Self> {
411        use crseo::{Builder, FromBuilder, Gmt, Source};
412        use skyangle::Conversion;
413        println!("Computing optical sensitivities ...");
414        let now = std::time::Instant::now();
415        let (mut gmt, mut src) = model.unwrap_or((
416            Gmt::builder().build().unwrap(),
417            Source::builder().build().unwrap(),
418        ));
419        let stroke_fn = |dof| if dof < 3 { 1e-6 } else { 1f64.from_arcsec() };
420
421        let mut tip_tilt = vec![];
422        let mut segment_piston = vec![];
423        let mut segment_tip_tilt = vec![];
424        let mut phase = vec![];
425        let n = (src.pupil_sampling * src.pupil_sampling) as usize;
426        let mut amplitude = vec![true; n];
427        for sid in 0..7 {
428            for dof in 0..6 {
429                let mut m1_rbm = vec![vec![0.; 6]; 7];
430                let stroke = stroke_fn(dof);
431
432                m1_rbm[sid][dof] = stroke;
433                gmt.update(Some(&m1_rbm), None, None, None);
434
435                src.through(&mut gmt).xpupil();
436                amplitude
437                    .iter_mut()
438                    .zip(src.amplitude().into_iter())
439                    .for_each(|(b, a)| {
440                        *b = a > 0f32 && *b;
441                    });
442                let push_phase = src.phase().to_owned();
443                let push_tip_tilt = src.gradients();
444                let push_segment_piston = src.segment_piston();
445                let push_segment_tip_tilt = src.segment_gradients();
446
447                m1_rbm[sid][dof] = -stroke;
448                gmt.update(Some(&m1_rbm), None, None, None);
449
450                src.through(&mut gmt).xpupil();
451                amplitude
452                    .iter_mut()
453                    .zip(src.amplitude().into_iter())
454                    .for_each(|(b, a)| {
455                        *b = a > 0f32 && *b;
456                    });
457                phase.extend(
458                    src.phase()
459                        .to_owned()
460                        .into_iter()
461                        .zip(push_phase.into_iter())
462                        .map(|(l, r)| 0.5f64 * (r as f64 - l as f64) / stroke),
463                );
464                tip_tilt.extend(
465                    src.gradients()
466                        .into_iter()
467                        .zip(push_tip_tilt.into_iter())
468                        .map(|(l, r)| 0.5f64 * (r as f64 - l as f64) / stroke),
469                );
470                segment_piston.extend(
471                    src.segment_piston()
472                        .into_iter()
473                        .zip(push_segment_piston.into_iter())
474                        .map(|(l, r)| 0.5f64 * (r as f64 - l as f64) / stroke),
475                );
476                segment_tip_tilt.extend(
477                    src.segment_gradients()
478                        .into_iter()
479                        .zip(push_segment_tip_tilt.into_iter())
480                        .map(|(l, r)| 0.5f64 * (r as f64 - l as f64) / stroke),
481                );
482            }
483        }
484        for sid in 0..7 {
485            for dof in 0..6 {
486                let mut m2_rbm = vec![vec![0.; 6]; 7];
487                let stroke = stroke_fn(dof);
488
489                m2_rbm[sid][dof] = stroke;
490                gmt.update(None, Some(&m2_rbm), None, None);
491
492                src.through(&mut gmt).xpupil();
493                amplitude
494                    .iter_mut()
495                    .zip(src.amplitude().into_iter())
496                    .for_each(|(b, a)| {
497                        *b = a > 0f32 && *b;
498                    });
499                let push_phase = src.phase().to_owned();
500                let push_tip_tilt = src.gradients();
501                let push_segment_piston = src.segment_piston();
502                let push_segment_tip_tilt = src.segment_gradients();
503
504                m2_rbm[sid][dof] = -stroke;
505                gmt.update(None, Some(&m2_rbm), None, None);
506
507                src.through(&mut gmt).xpupil();
508                amplitude
509                    .iter_mut()
510                    .zip(src.amplitude().into_iter())
511                    .for_each(|(b, a)| {
512                        *b = a > 0f32 && *b;
513                    });
514                phase.extend(
515                    src.phase()
516                        .to_owned()
517                        .into_iter()
518                        .zip(push_phase.into_iter())
519                        .map(|(l, r)| 0.5f64 * (r as f64 - l as f64) / stroke),
520                );
521                tip_tilt.extend(
522                    src.gradients()
523                        .into_iter()
524                        .zip(push_tip_tilt.into_iter())
525                        .map(|(l, r)| 0.5f64 * (r as f64 - l as f64) / stroke),
526                );
527                segment_piston.extend(
528                    src.segment_piston()
529                        .into_iter()
530                        .zip(push_segment_piston.into_iter())
531                        .map(|(l, r)| 0.5f64 * (r as f64 - l as f64) / stroke),
532                );
533                segment_tip_tilt.extend(
534                    src.segment_gradients()
535                        .into_iter()
536                        .zip(push_segment_tip_tilt.into_iter())
537                        .map(|(l, r)| 0.5f64 * (r as f64 - l as f64) / stroke),
538                );
539            }
540        }
541        let optical_sensitivities = vec![
542            OpticalSensitivity::Wavefront(
543                phase
544                    .chunks(n)
545                    .flat_map(|pp| {
546                        pp.iter()
547                            .zip(amplitude.iter())
548                            .filter(|(_, a)| **a)
549                            .map(|(p, _)| *p)
550                            .collect::<Vec<f64>>()
551                    })
552                    .collect(),
553            ),
554            OpticalSensitivity::TipTilt(tip_tilt),
555            OpticalSensitivity::SegmentPiston(segment_piston),
556            OpticalSensitivity::SegmentTipTilt(segment_tip_tilt),
557            OpticalSensitivity::SegmentMask(
558                src.segment_mask()
559                    .iter()
560                    .zip(amplitude.iter())
561                    .filter(|(_, a)| **a)
562                    .map(|(p, _)| *p)
563                    .collect(),
564            ),
565            OpticalSensitivity::PupilMask(amplitude),
566        ];
567        println!(" ... done in {:.3}s", now.elapsed().as_secs_f64());
568        Ok(Self(optical_sensitivities))
569    }
570}
571pub fn from_opticals<const N: usize>(senses: &[OpticalSensitivity<N>]) -> na::DMatrix<f64> {
572    let mats: Vec<na::DMatrix<f64>> = senses.iter().map(|s| s.into()).collect();
573    let n_rows = mats.iter().map(|m| m.nrows()).sum::<usize>();
574    let cols: Vec<_> = mats
575        .iter()
576        .flat_map(|m| {
577            m.column_iter()
578                .map(|c| na::DVector::from_column_slice(c.as_slice()))
579                .collect::<Vec<na::DVector<f64>>>()
580        })
581        .collect();
582    let data: Vec<_> = (0..N)
583        .flat_map(|k| {
584            (0..senses.len())
585                .flat_map(|l| cols[k + l * N].as_slice().to_vec())
586                .collect::<Vec<f64>>()
587        })
588        .collect();
589    na::DMatrix::from_column_slice(n_rows, N, &data)
590}