use gmt_dos_clients_fem::{
DiscreteStateSpace, StateSpaceError, fem_io, solvers::ExponentialMatrix,
};
use matio_rs::MatFile;
use nalgebra as na;
use rayon::prelude::*;
use std::{
fmt::Debug,
path::{Path, PathBuf},
rc::Rc,
time::Instant,
};
use crate::builder::Include;
#[derive(Debug, thiserror::Error)]
pub enum FacesheetError {
#[error("Failed to get Matlab ")]
Matio(#[from] matio_rs::MatioError),
#[error("Failed to get FEM Input/Output")]
FEM(#[from] gmt_fem::FemError),
}
#[derive(Debug, Clone, Default)]
struct TransformMat {
path: PathBuf,
var_prefix: String,
}
#[derive(Debug, Clone)]
pub struct Facesheet {
filter_piston_tip_tip: bool,
transforms_path: Option<TransformMat>,
transforms: Option<Vec<na::DMatrix<f64>>>,
pub(crate) options: Rc<dyn FacesheetOptions>,
}
impl Default for Facesheet {
fn default() -> Self {
Self {
filter_piston_tip_tip: Default::default(),
transforms_path: Default::default(),
transforms: Default::default(),
options: Rc::new(FacesheetDefaultOptions),
}
}
}
impl Facesheet {
pub fn new() -> Self {
Default::default()
}
pub fn filter_piston_tip_tilt(mut self) -> Self {
self.filter_piston_tip_tip = true;
self
}
pub fn transforms<P: AsRef<Path>>(mut self, path: P, var: impl ToString) -> Self {
self.transforms_path = Some(TransformMat {
path: path.as_ref().to_owned(),
var_prefix: var.to_string(),
});
self
}
pub(crate) fn build<'a>(&'a mut self, fem: &gmt_fem::FEM) -> Result<(), FacesheetError> {
self.transforms = match (self.transforms_path.as_ref(), self.filter_piston_tip_tip) {
(None, true) => {
println!("Filtering piston,tip and tilt from ASMS facesheets");
let now = Instant::now();
let ptt_free: Vec<_> = (0..7)
.into_par_iter()
.map(|i| {
let output_name = format!("M2_segment_{}_axial_d", i + 1);
let idx = Box::<dyn fem_io::GetOut>::try_from(output_name.clone())
.map(|x| x.position(&fem.outputs))?
.expect(&format!(
"failed to find the index of the output: {output_name}"
));
let xyz = fem.outputs[idx]
.as_ref()
.map(|i| i.get_by(|i| i.properties.location.clone()))
.expect(&format!(
"failed to read nodes locations from {output_name}"
));
let (x, y): (Vec<_>, Vec<_>) =
xyz.into_iter().map(|xyz| (xyz[0], xyz[1])).unzip();
let mut ones = na::DVector::<f64>::zeros(675);
ones.fill(1f64);
let x_vec = na::DVector::<f64>::from_row_slice(&x);
let y_vec = na::DVector::<f64>::from_row_slice(&y);
let t_mat = na::DMatrix::<f64>::from_columns(&[ones, x_vec, y_vec]);
let p_mat = na::DMatrix::<f64>::identity(675, 675)
- &t_mat * t_mat.clone().pseudo_inverse(0f64).unwrap();
Ok(p_mat)
})
.collect::<Result<Vec<_>, FacesheetError>>()?;
println!(" done in {}ms", now.elapsed().as_millis());
Some(ptt_free)
}
(None, false) => None,
(Some(TransformMat { path, var_prefix }), true) => {
let mat_file = MatFile::load(&path)?;
println!("Loading the ASMS facesheet matrix transforms");
let now = Instant::now();
let kl_mat_trans: Vec<na::DMatrix<f64>> = (1..=7)
.map(|i| {
Ok(mat_file
.var(format!("{var_prefix}_{i}"))
.map(|mat: na::DMatrix<f64>| mat.transpose())?)
})
.collect::<Result<Vec<_>, FacesheetError>>()?;
println!(" done in {}ms", now.elapsed().as_millis());
println!("Filtering piston,tip and tilt from ASMS facesheets");
let now = Instant::now();
let ptt_free_kl_mat_trans: Vec<_> = kl_mat_trans
.into_par_iter()
.enumerate()
.map(|(i, kl_mat_trans)| {
let output_name = format!("M2_segment_{}_axial_d", i + 1);
let idx = Box::<dyn fem_io::GetOut>::try_from(output_name.clone())
.map(|x| x.position(&fem.outputs))?
.expect(&format!(
"failed to find the index of the output: {output_name}"
));
let xyz = fem.outputs[idx]
.as_ref()
.map(|i| i.get_by(|i| i.properties.location.clone()))
.expect(&format!(
"failed to read nodes locations from {output_name}"
));
let (x, y): (Vec<_>, Vec<_>) =
xyz.into_iter().map(|xyz| (xyz[0], xyz[1])).unzip();
let mut ones = na::DVector::<f64>::zeros(675);
ones.fill(1f64);
let x_vec = na::DVector::<f64>::from_row_slice(&x);
let y_vec = na::DVector::<f64>::from_row_slice(&y);
let t_mat = na::DMatrix::<f64>::from_columns(&[ones, x_vec, y_vec]);
let p_mat = na::DMatrix::<f64>::identity(675, 675)
- &t_mat * t_mat.clone().pseudo_inverse(0f64).unwrap();
Ok(kl_mat_trans * p_mat)
})
.collect::<Result<Vec<_>, FacesheetError>>()?;
println!(" done in {}ms", now.elapsed().as_millis());
Some(ptt_free_kl_mat_trans)
}
(Some(TransformMat { path, var_prefix }), false) => {
let mat_file = MatFile::load(&path)?;
println!("Loading the ASMS facesheet matrix transforms");
let now = Instant::now();
let kl_mat_trans: Vec<na::DMatrix<f64>> = (1..=7)
.map(|i| {
Ok(mat_file
.var(format!("{var_prefix}_{i}"))
.map(|mat: na::DMatrix<f64>| mat.transpose())?)
})
.collect::<Result<Vec<_>, FacesheetError>>()?;
println!(" done in {}ms", now.elapsed().as_millis());
Some(kl_mat_trans)
}
};
Ok(())
}
pub(crate) fn transforms_view<'a>(&'a mut self) -> Option<Vec<na::DMatrixView<'a, f64>>> {
self.transforms
.as_ref()
.map(|transforms| transforms.iter().map(|t| t.as_view()).collect())
}
pub fn options(mut self, options: Box<dyn FacesheetOptions>) -> Self {
self.options = options.into();
self
}
}
pub trait FacesheetOptions: Debug {
fn remove_rigid_body_motions(&self) -> bool {
true
}
}
#[derive(Debug, Clone)]
pub struct FacesheetDefaultOptions;
impl FacesheetOptions for FacesheetDefaultOptions {}
impl<'a> Include<'a, Facesheet> for DiscreteStateSpace<'a, ExponentialMatrix> {
fn including(self, facesheet: Option<&'a mut Facesheet>) -> Result<Self, StateSpaceError> {
let Some(facesheet) = facesheet else {
return Ok(self);
};
let rbm_flag = facesheet.options.remove_rigid_body_motions();
let this = if let Some(transforms) = facesheet.transforms_view() {
self.outs_with_by_name(
(1..=7).map(|i| format!("M2_segment_{i}_axial_d")).collect(),
transforms,
)?
} else {
self.outs_by_name((1..=7).map(|i| format!("M2_segment_{i}_axial_d")).collect())?
};
Ok(if rbm_flag {
this.set_facesheet_nodes()?
} else {
this
})
}
}