use crate::error::{InchiError, Result, Status};
use crate::molecule::{BondOrder, Parity, Stereo};
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub struct StructureAtom {
pub element: String,
pub position: [f64; 3],
pub charge: i8,
pub isotope: Option<u16>,
pub implicit_hydrogens: u8,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub struct StructureBond {
pub from: usize,
pub to: usize,
pub order: BondOrder,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Structure {
atoms: Vec<StructureAtom>,
bonds: Vec<StructureBond>,
stereo: Vec<Stereo>,
}
impl Structure {
#[must_use]
pub fn atoms(&self) -> &[StructureAtom] {
&self.atoms
}
#[must_use]
pub fn bonds(&self) -> &[StructureBond] {
&self.bonds
}
#[must_use]
pub fn stereo(&self) -> &[Stereo] {
&self.stereo
}
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub struct ExtendedStructure {
pub structure: Structure,
pub polymer_units: Vec<crate::polymer::PolymerUnit>,
}
struct StructGuard {
raw: inchi_sys::inchi_OutputStruct,
}
impl StructGuard {
fn new() -> Self {
StructGuard {
raw: unsafe { std::mem::zeroed() },
}
}
}
impl Drop for StructGuard {
fn drop(&mut self) {
unsafe { inchi_sys::FreeStructFromINCHI(&mut self.raw) }
}
}
pub fn struct_from_inchi(inchi: impl AsRef<str>) -> Result<Structure> {
let src = crate::raw::to_cstring(inchi.as_ref())?;
let empty = crate::raw::to_cstring("")?;
let mut input = inchi_sys::inchi_InputINCHI {
szInChI: src.as_ptr() as *mut std::os::raw::c_char,
szOptions: empty.as_ptr() as *mut std::os::raw::c_char,
};
let _guard = crate::raw::lock();
let mut out = StructGuard::new();
let rc = unsafe { inchi_sys::GetStructFromINCHI(&mut input, &mut out.raw) };
drop(src);
drop(empty);
let status = Status::from_code(rc);
if !status.is_success() {
let message = unsafe { crate::raw::cstr_to_string(out.raw.szMessage) };
return Err(InchiError::Failed { status, message });
}
unsafe {
convert_raw(
out.raw.atom,
out.raw.num_atoms,
out.raw.stereo0D,
out.raw.num_stereo0D,
)
}
}
struct StructExGuard {
raw: inchi_sys::inchi_OutputStructEx,
}
impl StructExGuard {
fn new() -> Self {
StructExGuard {
raw: unsafe { std::mem::zeroed() },
}
}
}
impl Drop for StructExGuard {
fn drop(&mut self) {
unsafe { inchi_sys::FreeStructFromINCHIEx(&mut self.raw) }
}
}
pub fn struct_from_inchi_ex(inchi: impl AsRef<str>) -> Result<ExtendedStructure> {
let src = crate::raw::to_cstring(inchi.as_ref())?;
let empty = crate::raw::to_cstring("")?;
let mut input = inchi_sys::inchi_InputINCHI {
szInChI: src.as_ptr() as *mut std::os::raw::c_char,
szOptions: empty.as_ptr() as *mut std::os::raw::c_char,
};
let _guard = crate::raw::lock();
let mut out = StructExGuard::new();
let rc = unsafe { inchi_sys::GetStructFromINCHIEx(&mut input, &mut out.raw) };
drop(src);
drop(empty);
let status = Status::from_code(rc);
if !status.is_success() {
let message = unsafe { crate::raw::cstr_to_string(out.raw.szMessage) };
return Err(InchiError::Failed { status, message });
}
let structure = unsafe {
convert_raw(
out.raw.atom,
out.raw.num_atoms,
out.raw.stereo0D,
out.raw.num_stereo0D,
)
}?;
let polymer_units = unsafe { read_polymer(out.raw.polymer) };
Ok(ExtendedStructure {
structure,
polymer_units,
})
}
unsafe fn read_polymer(
polymer: *const inchi_sys::inchi_Input_Polymer,
) -> Vec<crate::polymer::PolymerUnit> {
if polymer.is_null() {
return Vec::new();
}
let p = unsafe { &*polymer };
let n = usize::try_from(p.n).unwrap_or(0);
if n == 0 || p.units.is_null() {
return Vec::new();
}
let units = unsafe { std::slice::from_raw_parts(p.units, n) };
units
.iter()
.filter_map(|&u| unsafe { crate::polymer::read_unit(u) })
.collect()
}
pub fn struct_from_aux_info(aux_info: impl AsRef<str>, add_hydrogens: bool) -> Result<Structure> {
let aux = crate::raw::to_cstring(aux_info.as_ref())?;
let _guard = crate::raw::lock();
let mut guard = InputGuard::new();
let b_do_not_add_h = i32::from(!add_hydrogens);
let rc = unsafe {
inchi_sys::Get_inchi_Input_FromAuxInfo(
aux.as_ptr() as *mut std::os::raw::c_char,
b_do_not_add_h,
0,
&mut guard.data,
)
};
drop(aux);
let status = Status::from_code(rc);
if !status.is_success() {
let message = unsafe { read_err_msg(&guard.data.szErrMsg) };
return Err(InchiError::Failed { status, message });
}
let inp = &guard.input;
let s = unsafe { convert_raw(inp.atom, inp.num_atoms, inp.stereo0D, inp.num_stereo0D) }?;
if s.atoms.is_empty() {
return Err(InchiError::EmptyResult);
}
Ok(s)
}
struct InputGuard {
input: Box<inchi_sys::inchi_Input>,
data: inchi_sys::InchiInpData,
}
impl InputGuard {
fn new() -> Self {
let mut input = Box::new(unsafe { std::mem::zeroed::<inchi_sys::inchi_Input>() });
let mut data: inchi_sys::InchiInpData = unsafe { std::mem::zeroed() };
data.pInp = input.as_mut();
InputGuard { input, data }
}
}
impl Drop for InputGuard {
fn drop(&mut self) {
unsafe { inchi_sys::Free_inchi_Input(self.input.as_mut()) }
}
}
unsafe fn read_err_msg(buf: &[std::os::raw::c_char]) -> String {
crate::raw::cstr_to_string(buf.as_ptr())
}
unsafe fn convert_raw(
atom: *mut inchi_sys::inchi_Atom,
num_atoms: inchi_sys::AT_NUM,
stereo0d: *mut inchi_sys::inchi_Stereo0D,
num_stereo0d: inchi_sys::AT_NUM,
) -> Result<Structure> {
let num_atoms = usize::try_from(num_atoms).unwrap_or(0);
if num_atoms == 0 || atom.is_null() {
return Ok(Structure {
atoms: Vec::new(),
bonds: Vec::new(),
stereo: Vec::new(),
});
}
let c_atoms = unsafe { std::slice::from_raw_parts(atom, num_atoms) };
let mut atoms = Vec::with_capacity(num_atoms);
let mut bonds = Vec::new();
for (i, ca) in c_atoms.iter().enumerate() {
atoms.push(StructureAtom {
element: read_elname(&ca.elname),
position: [ca.x, ca.y, ca.z],
charge: ca.charge,
isotope: read_isotope(ca.isotopic_mass),
implicit_hydrogens: read_implicit_h(&ca.num_iso_H),
});
let degree = (ca.num_bonds.max(0) as usize).min(ca.neighbor.len());
for slot in 0..degree {
let (Some(&nbr), Some(&bt)) = (ca.neighbor.get(slot), ca.bond_type.get(slot)) else {
continue;
};
let j = usize::try_from(nbr).unwrap_or(usize::MAX);
if j < num_atoms && i < j {
bonds.push(StructureBond {
from: i,
to: j,
order: decode_order(bt),
});
}
}
}
let stereo = unsafe { read_stereo(stereo0d, num_stereo0d, num_atoms) };
Ok(Structure {
atoms,
bonds,
stereo,
})
}
unsafe fn read_stereo(
stereo0d: *mut inchi_sys::inchi_Stereo0D,
num_stereo0d: inchi_sys::AT_NUM,
num_atoms: usize,
) -> Vec<Stereo> {
let count = usize::try_from(num_stereo0d).unwrap_or(0);
if count == 0 || stereo0d.is_null() {
return Vec::new();
}
let c_stereo = unsafe { std::slice::from_raw_parts(stereo0d, count) };
let idx = |v: inchi_sys::AT_NUM| usize::try_from(v).ok().filter(|&i| i < num_atoms);
let mut out = Vec::with_capacity(count);
for cs in c_stereo {
let Some(parity) = decode_parity(cs.parity) else {
continue;
};
let Some(ends) = (|| {
Some([
idx(cs.neighbor[0])?,
idx(cs.neighbor[1])?,
idx(cs.neighbor[2])?,
idx(cs.neighbor[3])?,
])
})() else {
continue;
};
let ty = cs.type_ as u32;
if ty == inchi_sys::INCHI_StereoType_Tetrahedral {
if let Some(center) = idx(cs.central_atom) {
out.push(Stereo::Tetrahedral {
center,
neighbors: ends,
parity,
});
}
} else if ty == inchi_sys::INCHI_StereoType_DoubleBond {
out.push(Stereo::DoubleBond { ends, parity });
} else if ty == inchi_sys::INCHI_StereoType_Allene {
if let Some(center) = idx(cs.central_atom) {
out.push(Stereo::Allene {
center,
ends,
parity,
});
}
}
}
out
}
fn read_elname(raw: &[std::os::raw::c_char]) -> String {
let mut s = String::new();
for &c in raw {
if c == 0 {
break;
}
s.push(c as u8 as char);
}
s
}
fn read_isotope(mass: inchi_sys::AT_NUM) -> Option<u16> {
if mass == 0 {
None
} else {
u16::try_from(mass).ok()
}
}
fn read_implicit_h(num_iso_h: &[i8]) -> u8 {
num_iso_h
.iter()
.map(|&n| if n < 0 { 0 } else { n as u16 })
.sum::<u16>()
.min(u8::MAX as u16) as u8
}
fn decode_order(code: i8) -> BondOrder {
let c = code as u32;
if c == inchi_sys::INCHI_BOND_TYPE_DOUBLE {
BondOrder::Double
} else if c == inchi_sys::INCHI_BOND_TYPE_TRIPLE {
BondOrder::Triple
} else if c == inchi_sys::INCHI_BOND_TYPE_ALTERN {
BondOrder::Alternating
} else {
BondOrder::Single
}
}
fn decode_parity(code: i8) -> Option<Parity> {
match (code & 0x07) as u32 {
inchi_sys::INCHI_PARITY_ODD => Some(Parity::Odd),
inchi_sys::INCHI_PARITY_EVEN => Some(Parity::Even),
inchi_sys::INCHI_PARITY_UNKNOWN => Some(Parity::Unknown),
_ => None,
}
}