chemfiles 0.10.3

Modern library for chemistry trajectories reading and writing
Documentation
// Chemfiles, a modern library for chemistry file reading and writing
// Copyright (C) 2015-2018 Guillaume Fraux -- BSD licensed
use std::ops::{Drop, Index};
use std::iter::IntoIterator;
use std::slice::Iter;

use chemfiles_sys::*;
use errors::{check, check_not_null, check_success, Error, Status};
use strings;
use frame::Frame;

#[derive(Debug, Clone, PartialEq, Eq)]
/// A `Match` is a set of atomic indexes matching a given selection. It can
/// mostly be used like a `&[usize]`.
pub struct Match {
    size: usize,
    atoms: [usize; 4],
}

#[allow(clippy::len_without_is_empty)]
impl Match {
    /// Get the length of the Match.
    ///
    /// # Example
    ///
    /// ```
    /// # use chemfiles::Match;
    /// let atomic_match = Match::new(&[3, 4, 5]);
    /// assert_eq!(atomic_match.len(), 3);
    /// ```
    pub fn len(&self) -> usize {
        self.size
    }

    /// Create a new match containing the atoms in the `atoms` slice.
    ///
    /// # Panics
    ///
    /// If the slice contains more than 4 elements, which is the maximal size
    /// of a match.
    ///
    /// # Example
    ///
    /// ```
    /// # use chemfiles::Match;
    /// let atomic_match = Match::new(&[3, 4, 5]);
    /// assert_eq!(atomic_match.len(), 3);
    /// assert_eq!(atomic_match[0], 3);
    /// assert_eq!(atomic_match[1], 4);
    /// assert_eq!(atomic_match[2], 5);
    /// ```
    pub fn new(atoms: &[usize]) -> Match {
        assert!(atoms.len() <= 4);
        let size = atoms.len();
        let mut matches = [usize::max_value(); 4];
        for (i, atom) in atoms.iter().enumerate() {
            matches[i] = *atom;
        }
        Match {
            size,
            atoms: matches,
        }
    }

    /// Iterate over the atomic indexes in the match.
    ///
    /// # Example
    ///
    /// ```
    /// # use chemfiles::Match;
    /// let atomic_match = Match::new(&[3, 4, 5]);
    /// let mut iter = atomic_match.iter();
    ///
    /// assert_eq!(iter.next(), Some(&3));
    /// assert_eq!(iter.next(), Some(&4));
    /// assert_eq!(iter.next(), Some(&5));
    /// assert_eq!(iter.next(), None);
    /// ```
    pub fn iter(&self) -> Iter<usize> {
        self.atoms[..self.len()].iter()
    }
}

impl Index<usize> for Match {
    type Output = usize;
    fn index(&self, i: usize) -> &Self::Output {
        assert!(i < self.len());
        &self.atoms[i]
    }
}

impl<'a> IntoIterator for &'a Match {
    type Item = &'a usize;
    type IntoIter = Iter<'a, usize>;
    fn into_iter(self) -> Iter<'a, usize> {
        self.atoms[..self.len()].iter()
    }
}

/// A `Selection` allow to select atoms in a `Frame`, from a selection
/// language. The selection language is built by combining basic operations.
/// Each basic operation follows the `<selector>[(<variable>)] <operator>
/// <value>` structure, where `<operator>` is a comparison operator in
/// `== != < <= > >=`.
pub struct Selection {
    handle: *mut CHFL_SELECTION,
}

impl Clone for Selection {
    fn clone(&self) -> Selection {
        unsafe {
            let new_handle = chfl_selection_copy(self.as_ptr());
            Selection::from_ptr(new_handle)
        }
    }
}

impl Drop for Selection {
    fn drop(&mut self) {
        unsafe {
            let _ = chfl_free(self.as_ptr().cast());
        }
    }
}

impl Selection {
    /// Create a `Selection` from a C pointer.
    ///
    /// This function is unsafe because no validity check is made on the pointer.
    #[inline]
    pub(crate) unsafe fn from_ptr(ptr: *mut CHFL_SELECTION) -> Selection {
        check_not_null(ptr);
        Selection {
            handle: ptr
        }
    }

    /// Get the underlying C pointer as a const pointer.
    #[inline]
    pub(crate) fn as_ptr(&self) -> *const CHFL_SELECTION {
        self.handle
    }

    /// Get the underlying C pointer as a mutable pointer.
    #[inline]
    pub(crate) fn as_mut_ptr(&mut self) -> *mut CHFL_SELECTION {
        self.handle
    }

    /// Create a new selection from the given selection string.
    ///
    /// # Errors
    ///
    /// This function fails if the selection string is invalid.
    ///
    /// # Example
    /// ```
    /// # use chemfiles::Selection;
    /// let selection = Selection::new("pairs: name(#1) H and name(#2) O").unwrap();
    /// ```
    pub fn new<'a, S: Into<&'a str>>(selection: S) -> Result<Selection, Error> {
        let buffer = strings::to_c(selection.into());
        unsafe {
            let handle = chfl_selection(buffer.as_ptr());
            if handle.is_null() {
                Err(Error {
                    status: Status::SelectionError,
                    message: Error::last_error()
                })
            } else {
                Ok(Selection::from_ptr(handle))
            }
        }
    }

    /// Get the size of the selection, i.e. the number of atoms we are selecting
    /// together.
    ///
    /// This value is 1 for the 'atom' context, 2 for the 'pair' and 'bond'
    /// context, 3 for the 'three' and 'angles' context and 4 for the 'four'
    /// and 'dihedral' context.
    ///
    /// # Example
    /// ```
    /// # use chemfiles::Selection;
    /// let selection = Selection::new("pairs: name(#1) H and name(#2) O").unwrap();
    /// assert_eq!(selection.size(), 2);
    /// ```
    pub fn size(&self) -> usize {
        let mut size = 0;
        unsafe {
            check_success(chfl_selection_size(self.as_ptr(), &mut size));
        }
        #[allow(clippy::cast_possible_truncation)]
        return size as usize;
    }

    /// Get the selection string used to create this selection.
    ///
    /// # Example
    /// ```
    /// # use chemfiles::Selection;
    /// let selection = Selection::new("name H").unwrap();
    /// assert_eq!(selection.string(), "name H");
    /// ```
    pub fn string(&self) -> String {
        let get_string = |ptr, len| unsafe { chfl_selection_string(self.as_ptr(), ptr, len) };
        let selection = strings::call_autogrow_buffer(1024, get_string).expect("failed to get selection string");
        return strings::from_c(selection.as_ptr());
    }

    /// Evaluate a selection for a given frame, and return the corresponding
    /// matches.
    ///
    /// # Example
    /// ```
    /// # use chemfiles::{Selection, Frame, Atom};
    /// let mut frame = Frame::new();
    /// frame.add_atom(&Atom::new("H"), [1.0, 0.0, 0.0], None);
    /// frame.add_atom(&Atom::new("O"), [0.0, 0.0, 0.0], None);
    /// frame.add_atom(&Atom::new("H"), [-1.0, 0.0, 0.0], None);
    ///
    /// let mut selection = Selection::new("pairs: name(#1) H and name(#2) O").unwrap();
    /// let matches = selection.evaluate(&frame);
    ///
    /// assert_eq!(matches.len(), 2);
    ///
    /// assert_eq!(matches[0].len(), 2);
    /// assert_eq!(matches[0][0], 0);
    /// assert_eq!(matches[0][1], 1);
    ///
    /// assert_eq!(matches[1].len(), 2);
    /// assert_eq!(matches[1][0], 2);
    /// assert_eq!(matches[1][1], 1);
    /// ```
    pub fn evaluate(&mut self, frame: &Frame) -> Vec<Match> {
        #![allow(clippy::cast_possible_truncation)]
        let mut count = 0;
        unsafe {
            check(chfl_selection_evaluate(
                self.as_mut_ptr(), frame.as_ptr(), &mut count
            )).expect("failed to evaluate selection");
        }

        let size = count as usize;
        let mut chfl_matches = vec![chfl_match { size: 0, atoms: [0; 4] }; size];
        unsafe {
            check(chfl_selection_matches(
                self.handle,
                chfl_matches.as_mut_ptr(),
                count
            )).expect("failed to extract matches");
        }

        return chfl_matches.into_iter()
            .map(|chfl_match| Match {
                size: chfl_match.size as usize,
                atoms: [
                    chfl_match.atoms[0] as usize,
                    chfl_match.atoms[1] as usize,
                    chfl_match.atoms[2] as usize,
                    chfl_match.atoms[3] as usize,
                ],
            })
            .collect();
    }

    /// Evaluates a selection of size 1 on a given `frame`. This function
    /// returns the list of atomic indexes in the frame matching this selection.
    ///
    /// # Panics
    ///
    /// If the selection size is not 1
    ///
    /// # Example
    /// ```
    /// # use chemfiles::{Selection, Frame, Atom};
    /// let mut frame = Frame::new();
    /// frame.add_atom(&Atom::new("H"), [1.0, 0.0, 0.0], None);
    /// frame.add_atom(&Atom::new("O"), [0.0, 0.0, 0.0], None);
    /// frame.add_atom(&Atom::new("H"), [-1.0, 0.0, 0.0], None);
    ///
    /// let mut selection = Selection::new("name H").unwrap();
    /// let matches = selection.list(&frame);
    ///
    /// assert_eq!(matches.len(), 2);
    /// assert_eq!(matches[0], 0);
    /// assert_eq!(matches[1], 2);
    /// ```
    pub fn list(&mut self, frame: &Frame) -> Vec<usize> {
        if self.size() != 1 {
            panic!("can not call `Selection::list` on a multiple selection");
        }
        return self.evaluate(frame)
            .into_iter()
            .map(|m| m[0] as usize)
            .collect();
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use Frame;
    use Topology;
    use Atom;

    #[test]
    fn clone() {
        let selection = Selection::new("name H").unwrap();

        let copy = selection.clone();
        assert_eq!(selection.size(), 1);
        assert_eq!(copy.size(), 1);
    }

    fn testing_frame() -> Frame {
        let mut topology = Topology::new();

        topology.add_atom(&Atom::new("H"));
        topology.add_atom(&Atom::new("O"));
        topology.add_atom(&Atom::new("O"));
        topology.add_atom(&Atom::new("H"));

        topology.add_bond(0, 1);
        topology.add_bond(1, 2);
        topology.add_bond(2, 3);

        let mut frame = Frame::new();
        frame.resize(4);
        frame.set_topology(&topology).unwrap();
        return frame;
    }

    mod matches {
        use super::*;

        #[test]
        fn index() {
            let m = Match::new(&[1, 2, 3, 4]);
            assert_eq!(m[0], 1);
            assert_eq!(m[1], 2);
            assert_eq!(m[2], 3);
            assert_eq!(m[3], 4);

            let m = Match::new(&[1, 2]);
            assert_eq!(m[0], 1);
            assert_eq!(m[1], 2);
        }

        #[test]
        fn iter() {
            let match_ = Match::new(&[1, 2, 3, 4]);
            assert_eq!(match_.iter().copied().collect::<Vec<usize>>(), vec![1, 2, 3, 4]);

            let v = vec![1, 2, 3, 4];
            for (i, &m) in match_.iter().enumerate() {
                assert_eq!(v[i], m);
            }
        }

        #[test]
        #[should_panic]
        fn out_of_bound() {
            let m = Match::new(&[1, 2]);
            let _ = m[2];
        }

        #[test]
        #[should_panic]
        fn too_big() {
            let _ = Match::new(&[1, 2, 3, 5, 4]);
        }
    }

    #[test]
    fn size() {
        let selection = Selection::new("name H").unwrap();
        assert_eq!(selection.size(), 1);

        let selection = Selection::new("angles: name(#1) H").unwrap();
        assert_eq!(selection.size(), 3);

        let selection = Selection::new("four: name(#1) H").unwrap();
        assert_eq!(selection.size(), 4);
    }

    #[test]
    fn string() {
        let selection = Selection::new("name H").unwrap();
        assert_eq!(selection.string(), "name H");

        let selection = Selection::new("angles: name(#1) H").unwrap();
        assert_eq!(selection.string(), "angles: name(#1) H");
    }

    #[test]
    fn evaluate() {
        let frame = testing_frame();

        let mut selection = Selection::new("name H").unwrap();
        let res = selection.evaluate(&frame);
        assert_eq!(res, &[Match::new(&[0]), Match::new(&[3])]);

        let mut selection = Selection::new("angles: all").unwrap();
        let res = selection.evaluate(&frame);
        for m in &[Match::new(&[0, 1, 2]), Match::new(&[1, 2, 3])] {
            assert!(res.iter().any(|r| r == m));
        }
    }

    #[test]
    fn list() {
        let frame = testing_frame();

        let mut selection = Selection::new("name H").unwrap();
        let res = selection.list(&frame);
        assert_eq!(res, vec![0, 3]);
    }

    #[test]
    #[should_panic = "can not call `Selection::list` on a multiple selection"]
    fn list_on_size_1_selection() {
        let frame = testing_frame();
        let mut selection = Selection::new("pairs: name(#1) H").unwrap();
        let _list = selection.list(&frame);
    }
}