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}