use std::marker::PhantomData;
use chemfiles_sys as ffi;
use crate::errors::{check_not_null, check_success};
use crate::property::{PropertiesIter, Property, RawProperty};
use crate::strings;
#[derive(Debug)]
pub struct Atom {
handle: *mut ffi::CHFL_ATOM,
}
#[derive(Debug)]
pub struct AtomRef<'a> {
inner: Atom,
marker: PhantomData<&'a Atom>,
}
impl<'a> std::ops::Deref for AtomRef<'a> {
type Target = Atom;
fn deref(&self) -> &Atom {
&self.inner
}
}
#[derive(Debug)]
pub struct AtomMut<'a> {
inner: Atom,
marker: PhantomData<&'a mut Atom>,
}
impl<'a> std::ops::Deref for AtomMut<'a> {
type Target = Atom;
fn deref(&self) -> &Atom {
&self.inner
}
}
impl<'a> std::ops::DerefMut for AtomMut<'a> {
fn deref_mut(&mut self) -> &mut Atom {
&mut self.inner
}
}
impl Clone for Atom {
fn clone(&self) -> Atom {
unsafe {
let new_handle = ffi::chfl_atom_copy(self.as_ptr());
Atom::from_ptr(new_handle)
}
}
}
impl Atom {
#[inline]
pub(crate) unsafe fn from_ptr(ptr: *mut ffi::CHFL_ATOM) -> Atom {
check_not_null(ptr);
Atom { handle: ptr }
}
#[inline]
#[allow(clippy::ptr_cast_constness)]
pub(crate) unsafe fn ref_from_ptr<'a>(ptr: *const ffi::CHFL_ATOM) -> AtomRef<'a> {
AtomRef {
inner: Atom::from_ptr(ptr as *mut ffi::CHFL_ATOM),
marker: PhantomData,
}
}
#[inline]
pub(crate) unsafe fn ref_mut_from_ptr<'a>(ptr: *mut ffi::CHFL_ATOM) -> AtomMut<'a> {
AtomMut {
inner: Atom::from_ptr(ptr),
marker: PhantomData,
}
}
#[inline]
pub(crate) fn as_ptr(&self) -> *const ffi::CHFL_ATOM {
self.handle
}
#[inline]
pub(crate) fn as_mut_ptr(&mut self) -> *mut ffi::CHFL_ATOM {
self.handle
}
pub fn new<'a>(name: impl Into<&'a str>) -> Atom {
let buffer = strings::to_c(name.into());
unsafe {
let handle = ffi::chfl_atom(buffer.as_ptr());
Atom::from_ptr(handle)
}
}
pub fn mass(&self) -> f64 {
let mut mass = 0.0;
unsafe {
check_success(ffi::chfl_atom_mass(self.as_ptr(), &mut mass));
}
return mass;
}
pub fn set_mass(&mut self, mass: f64) {
unsafe {
check_success(ffi::chfl_atom_set_mass(self.as_mut_ptr(), mass));
}
}
pub fn charge(&self) -> f64 {
let mut charge = 0.0;
unsafe {
check_success(ffi::chfl_atom_charge(self.as_ptr(), &mut charge));
}
return charge;
}
pub fn set_charge(&mut self, charge: f64) {
unsafe {
check_success(ffi::chfl_atom_set_charge(self.as_mut_ptr(), charge));
}
}
pub fn name(&self) -> String {
let get_name = |ptr, len| unsafe { ffi::chfl_atom_name(self.as_ptr(), ptr, len) };
let name = strings::call_autogrow_buffer(10, get_name).expect("getting name failed");
return strings::from_c(name.as_ptr());
}
pub fn atomic_type(&self) -> String {
let get_type = |ptr, len| unsafe { ffi::chfl_atom_type(self.as_ptr(), ptr, len) };
let buffer = strings::call_autogrow_buffer(10, get_type).expect("getting type failed");
return strings::from_c(buffer.as_ptr());
}
pub fn set_name<'a>(&mut self, name: impl Into<&'a str>) {
let buffer = strings::to_c(name.into());
unsafe {
check_success(ffi::chfl_atom_set_name(self.as_mut_ptr(), buffer.as_ptr()));
}
}
pub fn set_atomic_type<'a>(&mut self, atomic_type: impl Into<&'a str>) {
let buffer = strings::to_c(atomic_type.into());
unsafe {
check_success(ffi::chfl_atom_set_type(self.as_mut_ptr(), buffer.as_ptr()));
}
}
pub fn full_name(&self) -> String {
let get_full_name = |ptr, len| unsafe { ffi::chfl_atom_full_name(self.as_ptr(), ptr, len) };
let name = strings::call_autogrow_buffer(10, get_full_name).expect("getting full name failed");
return strings::from_c(name.as_ptr());
}
pub fn vdw_radius(&self) -> f64 {
let mut radius: f64 = 0.0;
unsafe {
check_success(ffi::chfl_atom_vdw_radius(self.as_ptr(), &mut radius));
}
return radius;
}
pub fn covalent_radius(&self) -> f64 {
let mut radius: f64 = 0.0;
unsafe {
check_success(ffi::chfl_atom_covalent_radius(self.as_ptr(), &mut radius));
}
return radius;
}
pub fn atomic_number(&self) -> u64 {
let mut number = 0;
unsafe {
check_success(ffi::chfl_atom_atomic_number(self.as_ptr(), &mut number));
}
return number;
}
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(ffi::chfl_atom_set_property(
self.as_mut_ptr(),
buffer.as_ptr(),
property.as_ptr(),
));
}
}
pub fn get(&self, name: &str) -> Option<Property> {
let buffer = strings::to_c(name);
unsafe {
let handle = ffi::chfl_atom_get_property(self.as_ptr(), buffer.as_ptr());
if handle.is_null() {
None
} else {
let raw = RawProperty::from_ptr(handle);
let property = Property::from_raw(raw);
Some(property)
}
}
}
pub fn properties(&self) -> PropertiesIter {
let mut count = 0;
unsafe {
check_success(ffi::chfl_atom_properties_count(self.as_ptr(), &mut count));
}
#[allow(clippy::cast_possible_truncation)]
let size = count as usize;
let mut c_names = vec![std::ptr::null_mut(); size];
unsafe {
check_success(ffi::chfl_atom_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")),
}
}
}
impl Drop for Atom {
fn drop(&mut self) {
unsafe {
let _ = ffi::chfl_free(self.as_ptr().cast());
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn clone() {
let mut atom = Atom::new("He");
assert_eq!(atom.name(), "He");
let copy = atom.clone();
assert_eq!(copy.name(), "He");
atom.set_name("Na");
assert_eq!(atom.name(), "Na");
assert_eq!(copy.name(), "He");
}
#[test]
fn mass() {
let mut atom = Atom::new("He");
approx::assert_ulps_eq!(atom.mass(), 4.002602);
atom.set_mass(15.0);
assert_eq!(atom.mass(), 15.0);
}
#[test]
fn charge() {
let mut atom = Atom::new("He");
assert_eq!(atom.charge(), 0.0);
atom.set_charge(-1.5);
assert_eq!(atom.charge(), -1.5);
}
#[test]
fn name() {
let mut atom = Atom::new("He");
assert_eq!(atom.name(), "He");
atom.set_name("Zn-12");
assert_eq!(atom.name(), "Zn-12");
}
#[test]
fn atomic_type() {
let mut atom = Atom::new("He");
assert_eq!(atom.atomic_type(), "He");
atom.set_atomic_type("Zn");
assert_eq!(atom.atomic_type(), "Zn");
}
#[test]
fn full_name() {
let mut atom = Atom::new("He");
assert_eq!(atom.full_name(), "Helium");
atom.set_atomic_type("Zn");
assert_eq!(atom.full_name(), "Zinc");
let atom = Atom::new("Unknown");
assert_eq!(atom.full_name(), "");
}
#[test]
fn radii() {
let atom = Atom::new("He");
approx::assert_ulps_eq!(atom.vdw_radius(), 1.4);
approx::assert_ulps_eq!(atom.covalent_radius(), 0.32);
let atom = Atom::new("Unknown");
assert_eq!(atom.vdw_radius(), 0.0);
assert_eq!(atom.covalent_radius(), 0.0);
}
#[test]
fn atomic_number() {
let atom = Atom::new("He");
assert_eq!(atom.atomic_number(), 2);
let atom = Atom::new("Unknown");
assert_eq!(atom.atomic_number(), 0);
}
#[test]
fn property() {
let mut atom = Atom::new("F");
atom.set("foo", -22.0);
assert_eq!(atom.get("foo"), Some(Property::Double(-22.0)));
assert_eq!(atom.get("bar"), None);
atom.set("bar", Property::String("here".into()));
for (name, property) in atom.properties() {
if name == "foo" {
assert_eq!(property, Property::Double(-22.0));
} else if name == "bar" {
assert_eq!(property, Property::String("here".into()));
}
}
}
}