use std::io::Write;
use crate::errors::{SelectError, WriteNdxError};
use crate::select::Select;
use crate::structures::container::AtomContainer;
use crate::structures::shape::Shape;
use crate::system::iterating::get_molecule_indices;
use crate::system::System;
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(deny_unknown_fields))]
pub struct Group {
atoms: AtomContainer,
pub print_ndx: bool,
}
impl Group {
pub fn get_atoms(&self) -> &AtomContainer {
&self.atoms
}
pub fn from_query(query: &str, system: &System) -> Result<Self, SelectError> {
let select = Select::parse_query(query)?;
Group::from_select(*select, system)
}
pub fn from_query_and_geometry(
query: &str,
geometry: impl Shape,
system: &System,
) -> Result<Self, SelectError> {
let group = Group::from_query(query, system)?;
Ok(group.apply_geometry(geometry, system))
}
pub fn from_query_and_geometries(
query: &str,
geometries: Vec<Box<dyn Shape>>,
system: &System,
) -> Result<Self, SelectError> {
let group = Group::from_query(query, system)?;
Ok(group.apply_geometries(geometries, system))
}
pub fn from_ranges(atom_ranges: Vec<(usize, usize)>, n_atoms: usize) -> Self {
Group {
atoms: AtomContainer::from_ranges(atom_ranges, n_atoms),
print_ndx: true,
}
}
pub fn from_indices(atom_indices: Vec<usize>, n_atoms: usize) -> Self {
Group {
atoms: AtomContainer::from_indices(atom_indices, n_atoms),
print_ndx: true,
}
}
pub(crate) fn from_select(select: Select, system: &System) -> Result<Self, SelectError> {
let select = Box::new(select.expand_regex_group_label(system)?);
let indices: Vec<usize> = (0usize..system.get_n_atoms())
.filter_map(|i| {
if Group::matches_select(i, &select, system).ok()? {
Some(i)
} else {
None
}
})
.collect();
Ok(Group {
atoms: AtomContainer::from_indices(indices, system.get_n_atoms()),
print_ndx: true,
})
}
fn apply_geometry(self, geometry: impl Shape, system: &System) -> Self {
let mut indices = Vec::new();
let atoms = system.get_atoms();
let simbox = system.get_box();
for index in self.atoms.iter() {
let atom = unsafe { atoms.get_unchecked(index) };
if let Some(pos) = atom.get_position() {
if geometry.inside(pos, simbox.expect("FATAL GROAN ERROR | Group::apply_geometry | System does not have a simulation box.")) {
indices.push(index);
}
}
}
Group {
atoms: AtomContainer::from_indices(indices, system.get_n_atoms()),
print_ndx: true,
}
}
fn apply_geometries(self, geometries: Vec<Box<dyn Shape>>, system: &System) -> Self {
let mut indices = Vec::new();
let atoms = system.get_atoms();
let simbox = system.get_box();
for index in self.atoms.iter() {
let atom = unsafe { atoms.get_unchecked(index) };
let mut inside = true;
for geom in &geometries {
if let Some(pos) = atom.get_position() {
if !geom.inside(pos, simbox.expect("FATAL GROAN ERROR | Group::apply_geometries | System does not have a simulation box.")) {
inside = false;
break;
}
} else {
inside = false;
break;
}
}
if inside {
indices.push(index);
}
}
Group {
atoms: AtomContainer::from_indices(indices, system.get_n_atoms()),
print_ndx: true,
}
}
pub(super) fn matches_select(
atom_index: usize,
select: &Select,
system: &System,
) -> Result<bool, SelectError> {
match select {
Select::ResidueName(names) => Ok(names
.iter()
.any(|name| name == system.get_atoms()[atom_index].get_residue_name())),
Select::AtomName(names) => Ok(names
.iter()
.any(|name| name == system.get_atoms()[atom_index].get_atom_name())),
Select::ResidueNumber(numbers) => {
let resnum = system.get_atoms()[atom_index].get_residue_number();
Ok(numbers
.iter()
.any(|&(start, end)| resnum >= start && resnum <= end))
}
Select::GmxAtomNumber(numbers) => Ok(numbers
.iter()
.any(|&(start, end)| atom_index + 1 >= start && atom_index < end)),
Select::AtomNumber(numbers) => {
let atomnum = system.get_atoms()[atom_index].get_atom_number();
Ok(numbers
.iter()
.any(|&(start, end)| atomnum >= start && atomnum <= end))
}
Select::Chain(identifiers) => {
let chain = match system.get_atoms()[atom_index].get_chain() {
None => return Ok(false),
Some(x) => x,
};
Ok(identifiers.contains(&chain))
}
Select::GroupName(names) => {
for name in names.iter() {
match name.match_groups(system, atom_index) {
Ok(true) => return Ok(true),
Ok(false) => (),
Err(e) => return Err(e),
}
}
Ok(false)
}
Select::LabeledAtom(names) => {
for name in names.iter() {
match name.match_labels(system, atom_index) {
Ok(true) => return Ok(true),
Ok(false) => (),
Err(e) => return Err(e),
}
}
Ok(false)
}
Select::ElementName(names) => {
let elname = match system.get_atoms()[atom_index].get_element_name() {
None => return Ok(false),
Some(x) => x,
};
Ok(names.iter().any(|target| target == elname))
}
Select::ElementSymbol(symbols) => {
let elsymbol = match system.get_atoms()[atom_index].get_element_symbol() {
None => return Ok(false),
Some(x) => x,
};
Ok(symbols.iter().any(|target| target == elsymbol))
}
Select::Molecule(operand) => {
let molecule_indices = get_molecule_indices(system, atom_index)
.expect("FATAL GROAN ERROR | Group::matches_select | Atom index should exist.");
for index in molecule_indices {
if Group::matches_select(index, operand, system)? {
return Ok(true);
}
}
Ok(false)
}
Select::And(left, right) => Ok(Group::matches_select(atom_index, left, system)?
&& Group::matches_select(atom_index, right, system)?),
Select::Or(left, right) => Ok(Group::matches_select(atom_index, left, system)?
|| Group::matches_select(atom_index, right, system)?),
Select::Not(operand) => Ok(!Group::matches_select(atom_index, operand, system)?),
}
}
pub fn get_n_atoms(&self) -> usize {
self.atoms.get_n_atoms()
}
pub fn write_ndx(&self, stream: &mut impl Write, name: &str) -> Result<(), WriteNdxError> {
writeln!(stream, "[ {} ]", name).map_err(|_| WriteNdxError::CouldNotWrite)?;
let last_index = match self.atoms.last() {
Some(x) => x,
None => return Ok(()),
};
for (iterator, index) in self.atoms.iter().enumerate() {
if (iterator + 1) % 15 == 0 || index == last_index {
writeln!(stream, "{:4}", index + 1).map_err(|_| WriteNdxError::CouldNotWrite)?;
} else {
write!(stream, "{:4} ", index + 1).map_err(|_| WriteNdxError::CouldNotWrite)?;
}
}
Ok(())
}
pub fn union(group1: &Group, group2: &Group) -> Group {
let container = AtomContainer::union(group1.get_atoms(), group2.get_atoms());
Group {
atoms: container,
print_ndx: true,
}
}
pub fn intersection(group1: &Group, group2: &Group) -> Group {
let container = AtomContainer::intersection(group1.get_atoms(), group2.get_atoms());
Group {
atoms: container,
print_ndx: true,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn from_ranges() {
let ranges = vec![
(10, 15),
(17, 25),
(11, 11),
(7, 3),
(9, 10),
(15, 15),
(16, 18),
(2, 5),
(10, 15),
];
let group = Group::from_ranges(ranges, 20);
assert_eq!(group.get_n_atoms(), 15);
let expected_indices: Vec<usize> =
vec![2, 3, 4, 5, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19];
let observed_indices: Vec<usize> = group.atoms.iter().collect();
assert_eq!(expected_indices, observed_indices);
}
#[test]
fn from_indices() {
let indices = vec![11, 1, 2, 3, 20, 5, 0, 5, 4, 18, 6, 19, 1, 13, 20, 27];
let group = Group::from_indices(indices, 20);
assert_eq!(group.get_n_atoms(), 11);
let expected_indices: Vec<usize> = vec![0, 1, 2, 3, 4, 5, 6, 11, 13, 18, 19];
let observed_indices: Vec<usize> = group.atoms.iter().collect();
assert_eq!(expected_indices, observed_indices);
}
#[test]
fn get_n_atoms_empty() {
let atom_ranges = vec![];
let group = Group::from_ranges(atom_ranges, 100);
assert_eq!(group.get_n_atoms(), 0);
}
#[test]
fn get_n_atoms_basic() {
let atom_ranges = vec![
(64, 128),
(5, 32),
(1, 25),
(129, 133),
(133, 200),
(35, 78),
(10, 15),
];
let group = Group::from_ranges(atom_ranges, 1028);
assert_eq!(group.get_n_atoms(), 198);
}
#[test]
fn union() {
let indices1 = vec![11, 1, 2, 3, 20, 5, 0, 5, 4, 18, 6, 19, 1, 13, 20, 27];
let group1 = Group::from_indices(indices1, 20);
let indices2 = vec![13, 1, 2, 7, 5, 19, 21, 1, 9, 10, 11];
let group2 = Group::from_indices(indices2, 15);
let union = Group::union(&group1, &group2);
assert_eq!(
union,
Group::from_indices(vec![0, 1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 13, 14, 18, 19], 20)
);
}
#[test]
fn intersection() {
let indices1 = vec![11, 1, 2, 3, 20, 5, 0, 5, 4, 18, 6, 19, 1, 13, 20, 27];
let group1 = Group::from_indices(indices1, 20);
let indices2 = vec![13, 1, 2, 7, 5, 19, 21, 1, 9, 10, 11];
let group2 = Group::from_indices(indices2, 15);
let union = Group::intersection(&group1, &group2);
assert_eq!(union, Group::from_indices(vec![1, 2, 5, 11, 13], 20));
}
}
#[cfg(test)]
#[cfg(feature = "serde")]
mod serde_tests {
use super::*;
#[test]
fn group_to_yaml() {
let ranges = vec![(20, 32), (64, 64), (84, 143)];
let group = Group::from_ranges(ranges, 1028);
let string = serde_yaml::to_string(&group).unwrap();
let expected = "atoms:
atom_blocks:
- start: 20
end: 32
- start: 64
end: 64
- start: 84
end: 143
print_ndx: true
";
assert_eq!(string, expected);
}
#[test]
fn group_from_yaml() {
let string = "atoms:
atom_blocks:
- start: 20
end: 32
- start: 64
end: 64
- start: 84
end: 143
print_ndx: true
";
let group: Group = serde_yaml::from_str(string).unwrap();
let ranges = vec![(20, 32), (64, 64), (84, 143)];
let expected = Group::from_ranges(ranges, 1028);
assert_eq!(group.atoms, expected.atoms);
assert_eq!(group.print_ndx, expected.print_ndx);
}
}