use crate::ExecutionContext;
use crate::structure::Structure;
use molframe_chem::{TableError, TableRequest, TableStore};
use molframe_core::diagnostic::Diagnostic;
use molframe_core::index::ModelIndex;
use molframe_surface::SasaError;
pub use molframe_surface::{SasaMethod, SasaOptions, SasaReport, SasaWordError};
#[derive(Debug, thiserror::Error)]
pub enum SurfaceExtError {
#[error("{0}")]
Structure(Box<Diagnostic>),
#[error(transparent)]
Sasa(#[from] SasaError),
#[error(transparent)]
Table(#[from] TableError),
}
pub trait SurfaceExt {
fn sasa(&self, store: &TableStore) -> Result<Vec<f64>, SurfaceExtError>;
fn sasa_with(
&self,
options: &SasaOptions,
context: &ExecutionContext,
) -> Result<Vec<f64>, SurfaceExtError>;
fn sasa_report_with(
&self,
options: &SasaOptions,
context: &ExecutionContext,
) -> Result<SasaReport, SurfaceExtError>;
fn residue_sasa(&self, store: &TableStore) -> Result<Vec<f64>, SurfaceExtError>;
fn residue_sasa_with(
&self,
options: &SasaOptions,
context: &ExecutionContext,
) -> Result<Vec<f64>, SurfaceExtError>;
}
impl SurfaceExt for Structure {
fn sasa(&self, store: &TableStore) -> Result<Vec<f64>, SurfaceExtError> {
let options = SasaOptions::resolve(&TableRequest::profile(), store)?;
self.sasa_with(&options, &ExecutionContext::default())
}
fn sasa_with(
&self,
options: &SasaOptions,
context: &ExecutionContext,
) -> Result<Vec<f64>, SurfaceExtError> {
let engine = self.engine();
engine
.require_resolved_model()
.map_err(|finding| SurfaceExtError::Structure(Box::new(finding)))?;
let positions = self
.model_coordinates(options.model)
.ok_or_else(|| missing_model(self, options.model))?;
Ok(molframe_surface::structure_atom_areas(
engine, positions, options, context,
)?)
}
fn sasa_report_with(
&self,
options: &SasaOptions,
context: &ExecutionContext,
) -> Result<SasaReport, SurfaceExtError> {
Ok(SasaReport {
areas: self.sasa_with(options, context)?,
policy: options.applied_policy(),
})
}
fn residue_sasa(&self, store: &TableStore) -> Result<Vec<f64>, SurfaceExtError> {
let options = SasaOptions::resolve(&TableRequest::profile(), store)?;
self.residue_sasa_with(&options, &ExecutionContext::default())
}
fn residue_sasa_with(
&self,
options: &SasaOptions,
context: &ExecutionContext,
) -> Result<Vec<f64>, SurfaceExtError> {
let areas = self.sasa_with(options, context)?;
let mut totals = vec![0.0_f64; self.residues().len()];
for atom in self.atoms() {
let area = match areas.get(atom.index().as_usize()).copied() {
Some(area) => area,
None => f64::NAN,
};
if !area.is_finite() {
continue;
}
if let Some(slot) = atom
.residue()
.and_then(|residue| totals.get_mut(residue.index().as_usize()))
{
*slot += area;
}
}
Ok(totals)
}
}
fn missing_model(structure: &Structure, model: ModelIndex) -> SurfaceExtError {
match structure.engine().ensemble_positions(Some(&[model])) {
Err(finding) => SurfaceExtError::Structure(Box::new(finding)),
Ok(_) => SurfaceExtError::Sasa(SasaError::NoPoints),
}
}