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;
use std::ptr;
use std::slice;

use super::{Atom, AtomMut, AtomRef};
use super::{BondOrder, Residue, Topology, TopologyRef};
use super::{UnitCell, UnitCellMut, UnitCellRef};
use chemfiles_sys::*;
use errors::{check, check_not_null, check_success, Error};
use property::{PropertiesIter, Property, RawProperty};
use strings;

/// A `Frame` contains data from one simulation step: the current unit
/// cell, the topology, the positions, and the velocities of the particles in
/// the system. If some information is missing (topology or velocity or unit
/// cell), the corresponding data is filled with a default value.
pub struct Frame {
    handle: *mut CHFL_FRAME,
}

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

pub struct AtomIter<'a> {
    frame: &'a Frame,
    index: usize,
    size: usize,
}

impl Frame {
    /// Create a `Frame` from a C pointer.
    ///
    /// This function is unsafe because no validity check is made on the pointer,
    /// except for it being non-null.
    #[inline]
    pub(crate) unsafe fn from_ptr(ptr: *mut CHFL_FRAME) -> Frame {
        check_not_null(ptr);
        Frame { handle: ptr }
    }

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

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

    /// Get the underlying C pointer as a mutable pointer FROM A SHARED REFERENCE.
    ///
    /// For uses with functions of the C API using mut pointers for both read
    /// and write access. Users should check that this does not lead to multiple
    /// mutable borrows
    #[inline]
    #[allow(non_snake_case)]
    pub(crate) fn as_mut_ptr_MANUALLY_CHECKING_BORROW(&self) -> *mut CHFL_FRAME {
        self.handle
    }

    /// Create an empty frame. It will be resized by the library as needed.
    ///
    /// # Example
    /// ```
    /// # use chemfiles::Frame;
    /// let frame = Frame::new();
    ///
    /// assert_eq!(frame.size(), 0);
    /// ```
    pub fn new() -> Frame {
        unsafe { Frame::from_ptr(chfl_frame()) }
    }

    /// Get a reference to the atom at the given `index` in this frame.
    ///
    /// # Panics
    ///
    /// If `index` is out of bounds.
    ///
    /// # Example
    /// ```
    /// # use chemfiles::{Frame, Atom};
    /// let mut frame = Frame::new();
    /// frame.add_atom(&Atom::new("Zn"), [0.0; 3], None);
    ///
    /// let atom = frame.atom(0);
    /// assert_eq!(atom.name(), "Zn");
    /// ```
    pub fn atom(&self, index: usize) -> AtomRef {
        unsafe {
            let handle =
                chfl_atom_from_frame(self.as_mut_ptr_MANUALLY_CHECKING_BORROW(), index as u64);
            Atom::ref_from_ptr(handle)
        }
    }

    /// Get a mutable reference to the atom at the given `index` in this frame.
    ///
    /// # Panics
    ///
    /// If `index` is out of bounds.
    ///
    /// # Example
    /// ```
    /// # use chemfiles::{Frame, Atom};
    /// let mut frame = Frame::new();
    /// frame.add_atom(&Atom::new("Zn"), [0.0; 3], None);
    ///
    /// assert_eq!(frame.atom(0).name(), "Zn");
    ///
    /// frame.atom_mut(0).set_name("Fe");
    /// assert_eq!(frame.atom(0).name(), "Fe");
    /// ```
    pub fn atom_mut(&mut self, index: usize) -> AtomMut {
        unsafe {
            let handle = chfl_atom_from_frame(self.as_mut_ptr(), index as u64);
            Atom::ref_mut_from_ptr(handle)
        }
    }

    /// Get the current number of atoms in this frame.
    ///
    /// # Example
    /// ```
    /// # use chemfiles::Frame;
    /// let mut frame = Frame::new();
    /// assert_eq!(frame.size(), 0);
    ///
    /// frame.resize(67);
    /// assert_eq!(frame.size(), 67);
    /// ```
    pub fn size(&self) -> usize {
        let mut size = 0;
        unsafe {
            check_success(chfl_frame_atoms_count(self.as_ptr(), &mut size));
        }
        #[allow(clippy::cast_possible_truncation)]
        return size as usize;
    }

    /// Resize the positions and the velocities in this frame, to make space for
    /// `natoms` atoms. Previous data is conserved, as well as the presence of
    /// absence of velocities.
    ///
    /// # Example
    /// ```
    /// # use chemfiles::Frame;
    /// let mut frame = Frame::new();
    /// frame.resize(67);
    /// assert_eq!(frame.size(), 67);
    /// ```
    pub fn resize(&mut self, natoms: usize) {
        unsafe {
            check_success(chfl_frame_resize(self.as_mut_ptr(), natoms as u64));
        }
    }

    /// Add an `Atom` and the corresponding position and optionally velocity
    /// data to this frame.
    ///
    /// # Example
    /// ```
    /// # use chemfiles::{Frame, Atom};
    /// let mut frame = Frame::new();
    /// frame.add_atom(&Atom::new("Zn"), [1.0, 1.0, 2.0], None);
    ///
    /// frame.add_velocities();
    /// frame.add_atom(&Atom::new("Zn"), [-1.0, 1.0, 2.0], [0.2, 0.1, 0.0]);
    /// ```
    pub fn add_atom(
        &mut self,
        atom: &Atom,
        position: [f64; 3],
        velocity: impl Into<Option<[f64; 3]>>,
    ) {
        let velocity = velocity.into();
        let velocity_ptr = match velocity {
            Some(ref data) => data.as_ptr(),
            None => ptr::null(),
        };

        unsafe {
            check_success(chfl_frame_add_atom(
                self.as_mut_ptr(),
                atom.as_ptr(),
                position.as_ptr(),
                velocity_ptr,
            ));
        }
    }

    /// Remove the atom at index `i` in this frame.
    ///
    /// # Example
    /// ```
    /// # use chemfiles::{Frame, Atom};
    /// let mut frame = Frame::new();
    /// frame.add_atom(&Atom::new("Zn"), [0.0; 3], None);
    /// frame.add_atom(&Atom::new("Fe"), [0.0; 3], None);
    /// frame.add_atom(&Atom::new("Sn"), [0.0; 3], None);
    /// assert_eq!(frame.size(), 3);
    ///
    /// frame.remove(1);
    /// assert_eq!(frame.size(), 2);
    /// assert_eq!(frame.atom(1).name(), "Sn");
    /// ```
    pub fn remove(&mut self, i: usize) {
        unsafe {
            check_success(chfl_frame_remove(self.as_mut_ptr(), i as u64));
        }
    }

    /// Add a bond between the atoms at indexes `i` and `j` in the frame.
    ///
    /// The bond order is set to `BondOrder::Unknown`.
    ///
    /// # Example
    /// ```
    /// # use chemfiles::{Frame, BondOrder};
    /// let mut frame = Frame::new();
    /// assert_eq!(frame.topology().bonds_count(), 0);
    /// frame.resize(5);
    ///
    /// frame.add_bond(0, 1);
    /// frame.add_bond(3, 1);
    /// frame.add_bond(2, 4);
    /// assert_eq!(frame.topology().bonds_count(), 3);
    ///
    /// assert_eq!(frame.topology().bond_order(0, 1), BondOrder::Unknown);
    /// assert_eq!(frame.topology().bonds(), vec![[0, 1], [1, 3], [2, 4]]);
    /// ```
    pub fn add_bond(&mut self, i: usize, j: usize) {
        unsafe {
            check_success(chfl_frame_add_bond(self.as_mut_ptr(), i as u64, j as u64));
        }
    }

    /// Add a bond between the atoms at indexes `i` and `j` in the frame
    /// with the given bond `order`.
    ///
    /// # Example
    /// ```
    /// # use chemfiles::{Frame, BondOrder};
    /// let mut frame = Frame::new();
    /// assert_eq!(frame.topology().bonds_count(), 0);
    /// frame.resize(2);
    ///
    /// frame.add_bond_with_order(0, 1, BondOrder::Double);
    /// assert_eq!(frame.topology().bond_order(0, 1), BondOrder::Double);
    /// ```
    pub fn add_bond_with_order(&mut self, i: usize, j: usize, order: BondOrder) {
        unsafe {
            check_success(chfl_frame_bond_with_order(
                self.as_mut_ptr(),
                i as u64,
                j as u64,
                order.as_raw(),
            ));
        }
    }

    /// Remove any existing bond between the atoms at indexes `i` and `j` in
    /// the frame.
    ///
    /// This function does nothing if there is no bond between `i` and `j`.
    ///
    /// # Example
    /// ```
    /// # use chemfiles::Frame;
    /// let mut frame = Frame::new();
    /// frame.resize(5);
    ///
    /// frame.add_bond(0, 1);
    /// frame.add_bond(3, 1);
    /// frame.add_bond(2, 4);
    ///
    /// let bonds = frame.topology().bonds();
    /// assert_eq!(bonds, vec![[0, 1], [1, 3], [2, 4]]);
    ///
    /// frame.remove_bond(2, 4);
    /// let bonds = frame.topology().bonds();
    /// assert_eq!(bonds, vec![[0, 1], [1, 3]]);
    /// ```
    pub fn remove_bond(&mut self, i: usize, j: usize) {
        unsafe {
            check_success(chfl_frame_remove_bond(
                self.as_mut_ptr(),
                i as u64,
                j as u64,
            ));
        }
    }

    /// Add a copy of `residue` to this frame.
    ///
    /// # Errors
    ///
    /// This function fails is the residue id is already in this frame's
    /// topology, or if the residue contain atoms that are already in another
    /// residue.
    ///
    /// # Example
    /// ```
    /// # use chemfiles::{Frame, Residue};
    /// let mut frame = Frame::new();
    ///
    /// let residue = Residue::new("foo");
    /// frame.add_residue(&residue).unwrap();
    ///
    /// let topology = frame.topology();
    /// assert_eq!(topology.residues_count(), 1);
    /// assert_eq!(topology.residue(0).unwrap().name(), "foo");
    /// ```
    pub fn add_residue(&mut self, residue: &Residue) -> Result<(), Error> {
        unsafe { check(chfl_frame_add_residue(self.as_mut_ptr(), residue.as_ptr())) }
    }

    /// Get the distance between the atoms at indexes `i` and `j` in this frame,
    /// accounting for periodic boundary conditions. The result is expressed in
    /// Angstroms.
    ///
    /// # Example
    /// ```
    /// # use chemfiles::{Frame, Atom};
    /// let mut frame = Frame::new();
    /// frame.add_atom(&Atom::new("A"), [0.0, 0.0, 0.0], None);
    /// frame.add_atom(&Atom::new("B"), [1.0, 2.0, 3.0], None);
    ///
    /// assert_eq!(frame.distance(0, 1), f64::sqrt(14.0));
    /// ```
    pub fn distance(&self, i: usize, j: usize) -> f64 {
        let mut distance = 0.0;
        unsafe {
            check_success(chfl_frame_distance(
                self.as_ptr(),
                i as u64,
                j as u64,
                &mut distance,
            ));
        }
        return distance;
    }

    /// Get the angle formed by the atoms at indexes `i`, `j` and `k` in this
    /// frame, accounting for periodic boundary conditions. The result is
    /// expressed in radians.
    ///
    /// # Example
    /// ```
    /// # use chemfiles::{Frame, Atom};
    /// # use std::f64;
    /// let mut frame = Frame::new();
    /// frame.add_atom(&Atom::new("A"), [1.0, 0.0, 0.0], None);
    /// frame.add_atom(&Atom::new("B"), [0.0, 0.0, 0.0], None);
    /// frame.add_atom(&Atom::new("C"), [0.0, 1.0, 0.0], None);
    ///
    /// assert_eq!(frame.angle(0, 1, 2), f64::consts::PI / 2.0);
    /// ```
    pub fn angle(&self, i: usize, j: usize, k: usize) -> f64 {
        let mut angle = 0.0;
        unsafe {
            check_success(chfl_frame_angle(
                self.as_ptr(),
                i as u64,
                j as u64,
                k as u64,
                &mut angle,
            ));
        }
        return angle;
    }

    /// Get the dihedral angle formed by the atoms at indexes `i`, `j`, `k` and
    /// `m` in this frame, accounting for periodic boundary conditions. The
    /// result is expressed in radians.
    ///
    /// # Example
    /// ```
    /// # use chemfiles::{Frame, Atom};
    /// # use std::f64;
    /// let mut frame = Frame::new();
    /// frame.add_atom(&Atom::new("A"), [1.0, 0.0, 0.0], None);
    /// frame.add_atom(&Atom::new("B"), [0.0, 0.0, 0.0], None);
    /// frame.add_atom(&Atom::new("C"), [0.0, 1.0, 0.0], None);
    /// frame.add_atom(&Atom::new("D"), [0.0, 1.0, 1.0], None);
    ///
    /// assert_eq!(frame.dihedral(0, 1, 2, 3), f64::consts::PI / 2.0);
    /// ```
    pub fn dihedral(&self, i: usize, j: usize, k: usize, m: usize) -> f64 {
        let mut dihedral = 0.0;
        unsafe {
            check_success(chfl_frame_dihedral(
                self.as_ptr(),
                i as u64,
                j as u64,
                k as u64,
                m as u64,
                &mut dihedral,
            ));
        }
        return dihedral;
    }

    /// Get the out of plane distance formed by the atoms at indexes `i`, `j`,
    /// `k` and `m` in this frame, accounting for periodic boundary conditions.
    /// The result is expressed in angstroms.
    ///
    /// This is the distance between the atom j and the ikm plane. The j atom
    /// is the center of the improper dihedral angle formed by i, j, k and m.
    ///
    /// # Example
    /// ```
    /// # use chemfiles::{Frame, Atom};
    /// let mut frame = Frame::new();
    /// frame.add_atom(&Atom::new("A"), [0.0, 0.0, 0.0], None);
    /// frame.add_atom(&Atom::new("B"), [0.0, 0.0, 2.0], None);
    /// frame.add_atom(&Atom::new("C"), [1.0, 0.0, 0.0], None);
    /// frame.add_atom(&Atom::new("D"), [0.0, 1.0, 0.0], None);
    ///
    /// assert_eq!(frame.out_of_plane(0, 1, 2, 3), 2.0);
    /// ```
    pub fn out_of_plane(&self, i: usize, j: usize, k: usize, m: usize) -> f64 {
        let mut distance = 0.0;
        unsafe {
            check_success(chfl_frame_out_of_plane(
                self.as_ptr(),
                i as u64,
                j as u64,
                k as u64,
                m as u64,
                &mut distance,
            ));
        }
        return distance;
    }

    /// Get a view into the positions of this frame.
    ///
    /// # Example
    /// ```
    /// # use chemfiles::Frame;
    /// let mut frame = Frame::new();
    /// frame.resize(67);
    ///
    /// let positions = frame.positions();
    /// assert_eq!(positions.len(), 67);
    /// assert_eq!(positions[0], [0.0, 0.0, 0.0]);
    /// ```
    pub fn positions(&self) -> &[[f64; 3]] {
        let mut ptr = ptr::null_mut();
        let mut natoms = 0;
        unsafe {
            check_success(chfl_frame_positions(
                self.as_mut_ptr_MANUALLY_CHECKING_BORROW(),
                &mut ptr,
                &mut natoms,
            ));
        }

        #[allow(clippy::cast_possible_truncation)]
        let size = natoms as usize;
        unsafe {
            return slice::from_raw_parts(ptr, size);
        }
    }

    /// Get a mutable view into the positions of this frame.
    ///
    /// # Example
    /// ```
    /// # use chemfiles::Frame;
    /// let mut frame = Frame::new();
    /// frame.resize(67);
    /// {
    ///     let positions = frame.positions_mut();
    ///     assert_eq!(positions[0], [0.0, 0.0, 0.0]);
    ///     positions[0] = [1.0, 2.0, 3.0];
    /// }
    ///
    /// let positions = frame.positions();
    /// assert_eq!(positions[0], [1.0, 2.0, 3.0]);
    /// ```
    pub fn positions_mut(&mut self) -> &mut [[f64; 3]] {
        let mut ptr = ptr::null_mut();
        let mut natoms = 0;
        unsafe {
            check_success(chfl_frame_positions(
                self.as_mut_ptr(),
                &mut ptr,
                &mut natoms,
            ));
        }
        #[allow(clippy::cast_possible_truncation)]
        let size = natoms as usize;
        unsafe {
            return slice::from_raw_parts_mut(ptr, size);
        }
    }

    /// Get a view into the velocities of this frame.
    ///
    /// # Example
    /// ```
    /// # use chemfiles::Frame;
    /// let mut frame = Frame::new();
    /// frame.resize(67);
    /// frame.add_velocities();
    ///
    /// let velocities = frame.velocities().expect("missing velocities");
    /// assert_eq!(velocities.len(), 67);
    /// assert_eq!(velocities[0], [0.0, 0.0, 0.0]);
    /// ```
    pub fn velocities(&self) -> Option<&[[f64; 3]]> {
        if !self.has_velocities() {
            return None;
        }

        let mut ptr = ptr::null_mut();
        let mut natoms = 0;
        unsafe {
            check_success(chfl_frame_velocities(
                self.as_mut_ptr_MANUALLY_CHECKING_BORROW(),
                &mut ptr,
                &mut natoms,
            ));
        }
        #[allow(clippy::cast_possible_truncation)]
        let size = natoms as usize;
        unsafe {
            return Some(slice::from_raw_parts(ptr, size));
        }
    }

    /// Get a mutable view into the velocities of this frame.
    ///
    /// # Example
    /// ```
    /// # use chemfiles::Frame;
    /// let mut frame = Frame::new();
    /// frame.resize(67);
    /// frame.add_velocities();
    /// {
    ///     let velocities = frame.velocities_mut().expect("missing velocities");
    ///     assert_eq!(velocities[0], [0.0, 0.0, 0.0]);
    ///     velocities[0] = [1.0, 2.0, 3.0];
    /// }
    ///
    /// let velocities = frame.velocities().expect("missing velocities");
    /// assert_eq!(velocities[0], [1.0, 2.0, 3.0]);
    /// ```
    pub fn velocities_mut(&mut self) -> Option<&mut [[f64; 3]]> {
        if !self.has_velocities() {
            return None;
        }

        let mut ptr = ptr::null_mut();
        let mut natoms = 0;
        unsafe {
            check_success(chfl_frame_velocities(
                self.as_mut_ptr(),
                &mut ptr,
                &mut natoms,
            ));
        }
        #[allow(clippy::cast_possible_truncation)]
        let size = natoms as usize;
        unsafe {
            return Some(slice::from_raw_parts_mut(ptr, size));
        }
    }

    /// Check if this frame contains velocity data.
    ///
    /// # Example
    /// ```
    /// # use chemfiles::Frame;
    /// let mut frame = Frame::new();
    /// assert_eq!(frame.has_velocities(), false);
    ///
    /// frame.add_velocities();
    /// assert_eq!(frame.has_velocities(), true);
    /// ```
    pub fn has_velocities(&self) -> bool {
        let mut res = 0;
        unsafe {
            check_success(chfl_frame_has_velocities(self.as_ptr(), &mut res));
        }
        return res != 0;
    }

    /// Add velocity data to this frame. If the frame already have velocities,
    /// this does nothing.
    ///
    /// # Example
    /// ```
    /// # use chemfiles::Frame;
    /// let mut frame = Frame::new();
    /// assert_eq!(frame.has_velocities(), false);
    ///
    /// frame.add_velocities();
    /// assert_eq!(frame.has_velocities(), true);
    /// ```
    pub fn add_velocities(&mut self) {
        unsafe {
            check_success(chfl_frame_add_velocities(self.as_mut_ptr()));
        }
    }

    /// Get a reference to the `UnitCell` from this frame.
    ///
    /// # Example
    /// ```
    /// # use chemfiles::{Frame, CellShape};
    /// let frame = Frame::new();
    ///
    /// let cell = frame.cell();
    /// assert_eq!(cell.shape(), CellShape::Infinite);
    /// ```
    pub fn cell(&self) -> UnitCellRef {
        unsafe {
            let handle = chfl_cell_from_frame(self.as_mut_ptr_MANUALLY_CHECKING_BORROW());
            UnitCell::ref_from_ptr(handle)
        }
    }

    /// Get a mutable reference to the `UnitCell` from this frame.
    ///
    /// # Example
    /// ```
    /// # use chemfiles::{Frame, CellShape};
    /// let mut frame = Frame::new();
    ///
    /// assert_eq!(frame.cell().shape(), CellShape::Infinite);
    ///
    /// frame.cell_mut().set_shape(CellShape::Triclinic).unwrap();
    /// assert_eq!(frame.cell().shape(), CellShape::Triclinic);
    /// ```
    pub fn cell_mut(&mut self) -> UnitCellMut {
        unsafe {
            let handle = chfl_cell_from_frame(self.as_mut_ptr());
            UnitCell::ref_mut_from_ptr(handle)
        }
    }

    /// Set the `UnitCell` of this frame to `cell`.
    ///
    /// # Example
    /// ```
    /// # use chemfiles::{Frame, UnitCell, CellShape};
    /// let mut frame = Frame::new();
    ///
    /// frame.set_cell(&UnitCell::new([10.0, 10.0, 10.0]));
    ///
    /// let cell = frame.cell();
    /// assert_eq!(cell.shape(), CellShape::Orthorhombic);
    /// assert_eq!(cell.lengths(), [10.0, 10.0, 10.0]);
    /// ```
    pub fn set_cell(&mut self, cell: &UnitCell) {
        unsafe {
            check_success(chfl_frame_set_cell(self.as_mut_ptr(), cell.as_ptr()));
        }
    }

    /// Get a reference to the `Topology` of this frame.
    ///
    /// # Example
    /// ```
    /// # use chemfiles::Frame;
    /// let mut frame = Frame::new();
    /// frame.resize(42);
    ///
    /// let topology = frame.topology();
    /// assert_eq!(topology.size(), 42);
    /// ```
    pub fn topology(&self) -> TopologyRef {
        unsafe {
            let handle = chfl_topology_from_frame(self.as_ptr());
            Topology::ref_from_ptr(handle)
        }
    }

    /// Set the `Topology` of this frame to `topology`.
    ///
    /// # Errors
    ///
    /// This function fails if the topology contains a different number of atoms
    /// than this frame.
    ///
    /// # Example
    /// ```
    /// # use chemfiles::{Frame, Topology, Atom};
    /// let mut frame = Frame::new();
    /// frame.resize(2);
    ///
    /// let mut topology = Topology::new();
    /// topology.add_atom(&Atom::new("Cl"));
    /// topology.add_atom(&Atom::new("Cl"));
    /// topology.add_bond(0, 1);
    ///
    /// frame.set_topology(&topology).unwrap();
    /// assert_eq!(frame.atom(0).name(), "Cl");
    /// ```
    pub fn set_topology(&mut self, topology: &Topology) -> Result<(), Error> {
        unsafe {
            check(chfl_frame_set_topology(
                self.as_mut_ptr(),
                topology.as_ptr(),
            ))
        }
    }

    /// Get this frame step, i.e. the frame number in the trajectory
    ///
    /// # Example
    /// ```
    /// # use chemfiles::Frame;
    /// let frame = Frame::new();
    /// assert_eq!(frame.step(), 0);
    /// ```
    pub fn step(&self) -> usize {
        let mut step = 0;
        unsafe {
            check_success(chfl_frame_step(self.as_ptr(), &mut step));
        }
        #[allow(clippy::cast_possible_truncation)]
        return step as usize;
    }

    /// Set this frame step to `step`.
    ///
    /// # Example
    /// ```
    /// # use chemfiles::Frame;
    /// let mut frame = Frame::new();
    /// assert_eq!(frame.step(), 0);
    ///
    /// frame.set_step(10);
    /// assert_eq!(frame.step(), 10);
    /// ```
    pub fn set_step(&mut self, step: usize) {
        unsafe {
            check_success(chfl_frame_set_step(self.as_mut_ptr(), step as u64));
        }
    }

    /// Guess the bonds, angles and dihedrals in this `frame`.
    ///
    /// The bonds are guessed using a distance-based algorithm, and then angles
    /// and dihedrals are guessed from the bonds.
    ///
    /// # Errors
    ////
    /// This function can fail if the covalent radius is unknown for some atoms
    /// in the frame.
    ///
    /// # Example
    /// ```
    /// # use chemfiles::{Frame, Atom};
    /// let mut frame = Frame::new();
    ///
    /// frame.add_atom(&Atom::new("Cl"), [0.0, 0.0, 0.0], None);
    /// frame.add_atom(&Atom::new("Cl"), [1.5, 0.0, 0.0], None);
    /// assert_eq!(frame.topology().bonds_count(), 0);
    ///
    /// frame.guess_bonds().unwrap();
    /// assert_eq!(frame.topology().bonds_count(), 1);
    /// ```
    pub fn guess_bonds(&mut self) -> Result<(), Error> {
        unsafe { check(chfl_frame_guess_bonds(self.as_mut_ptr())) }
    }

    /// Remove all existing bonds, angles, dihedral angles and improper
    /// dihedral angles in the topology of the frame.
    ///
    /// # Example
    /// ```
    /// # use chemfiles::{Atom, Frame};
    /// 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"), [0.0, 1.0, 0.0], None);
    ///
    /// frame.add_bond(0, 1);
    /// frame.add_bond(1, 2);
    ///
    /// assert_eq!(frame.topology().bonds().len(), 2);
    /// assert_eq!(frame.topology().angles().len(), 1);
    ///
    /// frame.clear_bonds();
    /// assert!(frame.topology().bonds().is_empty());
    /// assert!(frame.topology().angles().is_empty());
    /// ```
    pub fn clear_bonds(&mut self) {
        unsafe {
            check_success(chfl_frame_clear_bonds(self.as_mut_ptr()));
        }
    }

    /// Add a new `property` with the given `name` to this frame.
    ///
    /// If a property with the same name already exists, this function override
    /// the existing property with the new one.
    ///
    /// # Examples
    /// ```
    /// # use chemfiles::{Frame, Property};
    /// let mut frame = Frame::new();
    /// frame.set("a string", "hello");
    /// frame.set("a double", 4.3);
    ///
    /// assert_eq!(frame.get("a string"), Some(Property::String("hello".into())));
    /// assert_eq!(frame.get("a double"), Some(Property::Double(4.3)));
    /// ```
    pub fn set(&mut self, name: &str, property: impl Into<Property>) {
        let buffer = strings::to_c(name);
        let property = property.into().as_raw();
        unsafe {
            check_success(chfl_frame_set_property(
                self.as_mut_ptr(),
                buffer.as_ptr(),
                property.as_ptr(),
            ));
        }
    }

    /// Get a property with the given `name` in this frame, if it exist.
    ///
    /// # Examples
    /// ```
    /// # use chemfiles::{Frame, Property};
    /// let mut frame = Frame::new();
    /// frame.set("foo", Property::Double(22.2));
    ///
    /// assert_eq!(frame.get("foo"), Some(Property::Double(22.2)));
    /// assert_eq!(frame.get("Bar"), None);
    /// ```
    pub fn get(&self, name: &str) -> Option<Property> {
        let buffer = strings::to_c(name);
        unsafe {
            let handle = chfl_frame_get_property(self.as_ptr(), buffer.as_ptr());
            if handle.is_null() {
                None
            } else {
                let raw = RawProperty::from_ptr(handle);
                Some(Property::from_raw(raw))
            }
        }
    }

    /// Get an iterator over all (name, property) pairs for this frame
    ///
    /// # Examples
    /// ```
    /// # use chemfiles::{Frame, Property};
    /// let mut frame = Frame::new();
    /// frame.set("foo", Property::Double(22.2));
    /// frame.set("bar", Property::Bool(false));
    ///
    /// for (name, property) in frame.properties() {
    ///     if name == "foo" {
    ///         assert_eq!(property, Property::Double(22.2));
    ///     } else if name == "bar" {
    ///         assert_eq!(property, Property::Bool(false));
    ///     }
    /// }
    /// ```
    pub fn properties(&self) -> PropertiesIter {
        let mut count = 0;
        unsafe {
            check_success(chfl_frame_properties_count(self.as_ptr(), &mut count));
        }

        #[allow(clippy::cast_possible_truncation)]
        let size = count as usize;
        let mut c_names = vec![ptr::null_mut(); size];
        unsafe {
            check_success(chfl_frame_list_properties(
                self.as_ptr(),
                c_names.as_mut_ptr(),
                count,
            ));
        }

        let mut names = Vec::new();
        for ptr in c_names {
            names.push(strings::from_c(ptr));
        }

        PropertiesIter {
            names: names.into_iter(),
            getter: Box::new(move |name| self.get(name).expect("failed to get property")),
        }
    }

    /// Gets an iterator over atoms
    ///
    /// # Example
    /// ```
    /// # use chemfiles::{Atom, AtomRef, Frame};
    /// let mut frame = Frame::new();
    ///
    /// 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 atoms: Vec<AtomRef> = Vec::new();
    ///
    /// for atom in frame.iter_atoms() {
    ///     atoms.push(atom);
    /// }
    ///
    /// assert_eq!(atoms.len(), 2);
    /// ```
    pub fn iter_atoms(&self) -> AtomIter<'_> {
        AtomIter {
            frame: self,
            index: 0,
            size: self.size(),
        }
    }
}

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

impl<'a> Iterator for AtomIter<'a> {
    type Item = AtomRef<'a>;

    fn next(&mut self) -> Option<Self::Item> {
        if self.size <= self.index {
            return None;
        }
        let atom = self.frame.atom(self.index);
        self.index += 1;
        Some(atom)
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use {Atom, Topology, UnitCell};

    #[test]
    fn clone() {
        let mut frame = Frame::new();
        assert_eq!(frame.size(), 0);
        let copy = frame.clone();
        assert_eq!(copy.size(), 0);

        frame.resize(42);
        assert_eq!(frame.size(), 42);
        assert_eq!(copy.size(), 0);
    }

    #[test]
    fn size() {
        let mut frame = Frame::new();
        assert_eq!(frame.size(), 0);

        frame.resize(12);
        assert_eq!(frame.size(), 12);
    }

    #[test]
    fn add_atom() {
        let mut frame = Frame::new();

        frame.add_atom(&Atom::new("U"), [1.0, 1.0, 2.0], None);
        assert_eq!(frame.size(), 1);
        assert_eq!(frame.atom(0).name(), "U");

        let positions = &[[1.0, 1.0, 2.0]];
        assert_eq!(frame.positions(), positions);

        frame.add_velocities();
        frame.add_atom(&Atom::new("F"), [1.0, 1.0, 2.0], [4.0, 3.0, 2.0]);
        assert_eq!(frame.size(), 2);
        assert_eq!(frame.atom(0).name(), "U");
        assert_eq!(frame.atom(1).name(), "F");

        let positions = &[[1.0, 1.0, 2.0], [1.0, 1.0, 2.0]];
        assert_eq!(frame.positions(), positions);

        let velocities = &[[0.0, 0.0, 0.0], [4.0, 3.0, 2.0]];
        assert_eq!(frame.velocities().unwrap(), velocities);
    }

    #[test]
    #[should_panic]
    fn out_of_bounds_atom() {
        let mut frame = Frame::new();
        frame.resize(22);
        let _atom = frame.atom(23);
    }

    #[test]
    fn remove_atom() {
        let mut frame = Frame::new();
        frame.add_atom(&Atom::new("U"), [1.0, 1.0, 2.0], None);
        frame.add_atom(&Atom::new("F"), [1.0, 1.0, 2.0], None);

        assert_eq!(frame.size(), 2);
        assert_eq!(frame.atom(0).name(), "U");

        frame.remove(0);
        assert_eq!(frame.size(), 1);
        assert_eq!(frame.atom(0).name(), "F");
    }

    #[test]
    #[should_panic]
    fn remove_out_of_bounds() {
        let mut frame = Frame::new();
        frame.resize(32);

        frame.remove(100);
    }

    #[test]
    fn positions() {
        let mut frame = Frame::new();
        frame.resize(4);
        let expected = &[
            [1.0, 2.0, 3.0],
            [4.0, 5.0, 6.0],
            [7.0, 8.0, 9.0],
            [10.0, 11.0, 12.0],
        ];

        frame.positions_mut().clone_from_slice(expected);
        assert_eq!(frame.positions(), expected);
    }

    #[test]
    fn velocities() {
        let mut frame = Frame::new();
        frame.resize(4);
        assert!(!frame.has_velocities());
        frame.add_velocities();
        assert!(frame.has_velocities());

        let expected = &[
            [1.0, 2.0, 3.0],
            [4.0, 5.0, 6.0],
            [7.0, 8.0, 9.0],
            [10.0, 11.0, 12.0],
        ];

        frame.velocities_mut().unwrap().clone_from_slice(expected);
        assert_eq!(frame.velocities().unwrap(), expected);
    }

    #[test]
    fn cell() {
        let mut frame = Frame::new();
        frame.set_cell(&UnitCell::new([3.0, 4.0, 5.0]));
        let cell = frame.cell();
        assert_eq!(cell.lengths(), [3.0, 4.0, 5.0]);
    }

    #[test]
    fn topology() {
        let mut frame = Frame::new();
        frame.resize(2);
        let mut topology = Topology::new();

        topology.add_atom(&Atom::new("Zn"));
        topology.add_atom(&Atom::new("Ar"));

        assert!(frame.set_topology(&topology).is_ok());

        let topology = frame.topology();

        assert_eq!(topology.atom(0).name(), "Zn");
        assert_eq!(topology.atom(1).name(), "Ar");

        assert_eq!(frame.atom(0).name(), "Zn");
        assert_eq!(frame.atom(1).name(), "Ar");
    }

    #[test]
    fn bonds() {
        let mut frame = Frame::new();
        frame.resize(12);
        assert_eq!(frame.topology().bonds_count(), 0);

        frame.add_bond(0, 1);
        frame.add_bond(9, 2);
        frame.add_bond_with_order(3, 7, BondOrder::Aromatic);
        assert_eq!(frame.topology().bonds_count(), 3);

        assert_eq!(frame.topology().bonds(), vec![[0, 1], [2, 9], [3, 7]]);
        let expected = vec![BondOrder::Unknown, BondOrder::Unknown, BondOrder::Aromatic];
        assert_eq!(frame.topology().bond_orders(), expected);

        assert_eq!(frame.topology().bond_order(0, 1), BondOrder::Unknown);
        assert_eq!(frame.topology().bond_order(3, 7), BondOrder::Aromatic);

        frame.remove_bond(3, 7);
        // Removing unexisting bond is OK if both indexes are in bounds
        frame.remove_bond(8, 7);
        assert_eq!(frame.topology().bonds_count(), 2);

        frame.clear_bonds();
        assert_eq!(frame.topology().bonds_count(), 0);
    }

    #[test]
    #[should_panic]
    fn out_of_bounds_bonds() {
        let mut frame = Frame::new();
        frame.resize(12);
        frame.add_bond(300, 7);
    }

    #[test]
    #[should_panic]
    fn out_of_bounds_remove_bond() {
        let mut frame = Frame::new();
        frame.resize(12);
        frame.remove_bond(300, 7);
    }

    #[test]
    #[should_panic]
    fn out_of_bounds_bonds_with_order() {
        let mut frame = Frame::new();
        frame.resize(12);
        frame.add_bond_with_order(300, 7, BondOrder::Unknown);
    }

    #[test]
    fn residues() {
        let mut frame = Frame::new();
        assert_eq!(frame.topology().residues_count(), 0);

        let residue = &Residue::new("foobar");
        frame.add_residue(residue).unwrap();
        frame.add_residue(residue).unwrap();
        frame.add_residue(residue).unwrap();

        assert_eq!(frame.topology().residues_count(), 3);
        assert_eq!(frame.topology().residue(0).unwrap().name(), "foobar");
    }

    #[test]
    fn step() {
        let mut frame = Frame::new();
        assert_eq!(frame.step(), 0);
        frame.set_step(42);
        assert_eq!(frame.step(), 42);
    }

    #[test]
    fn property() {
        let mut frame = Frame::new();
        frame.set("foo", -22.0);
        assert_eq!(frame.get("foo"), Some(Property::Double(-22.0)));
        assert_eq!(frame.get("bar"), None);

        frame.set("bar", Property::String("here".into()));
        for (name, property) in frame.properties() {
            if name == "foo" {
                assert_eq!(property, Property::Double(-22.0));
            } else if name == "bar" {
                assert_eq!(property, Property::String("here".into()));
            }
        }
    }

    #[test]
    fn pbc_geometry() {
        use std::f64::consts::PI;

        let mut frame = Frame::new();
        let atom = &Atom::new("");

        frame.add_atom(atom, [1.0, 0.0, 0.0], None);
        frame.add_atom(atom, [0.0, 0.0, 0.0], None);
        frame.add_atom(atom, [0.0, 1.0, 0.0], None);
        frame.add_atom(atom, [0.0, 1.0, 1.0], None);
        frame.add_atom(atom, [0.0, 0.0, 2.0], None);

        assert_eq!(frame.distance(0, 2), f64::sqrt(2.0));
        assert_eq!(frame.angle(0, 1, 2), PI / 2.0);
        assert_eq!(frame.dihedral(0, 1, 2, 3), PI / 2.0);
        assert_eq!(frame.out_of_plane(1, 4, 0, 2), 2.0);
    }

    #[test]
    fn atom_iterator() {
        let mut frame = Frame::new();

        frame.add_atom(&Atom::new("H1"), [1.0, 0.0, 0.0], None);
        frame.add_atom(&Atom::new("H2"), [0.0, 1.0, 0.0], None);
        frame.add_atom(&Atom::new("H3"), [0.0, 0.0, 1.0], None);
        frame.add_atom(&Atom::new("H4"), [1.0, 1.0, 1.0], None);

        let mut items: Vec<(AtomRef, &[f64; 3])> = Vec::new();

        for item in frame.iter_atoms().zip(frame.positions()) {
            items.push(item);
        }

        assert_eq!(items[0].0.name(), "H1");
        assert_eq!(items[2].0.name(), "H3");

        assert_eq!(items[1].1, &[0.0_f64, 1.0_f64, 0.0_f64]);
        assert_eq!(items[3].1, &[1.0_f64, 1.0_f64, 1.0_f64]);
    }
}