use std::{collections::BTreeMap, fmt};
use crate::{AtomId, BondQueryPredicate, QueryNode};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct BondId(usize);
impl BondId {
#[must_use]
pub const fn new(index: usize) -> Self {
Self(index)
}
#[must_use]
pub const fn index(self) -> usize {
self.0
}
}
impl fmt::Display for BondId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BondOrder {
Null,
Single,
Double,
Triple,
Quadruple,
Quintuple,
Hextuple,
OneAndHalf,
TwoAndHalf,
ThreeAndHalf,
FourAndHalf,
FiveAndHalf,
Aromatic,
Ionic,
Dative,
DativeOne,
DativeLeft,
DativeRight,
Hydrogen,
ThreeCenter,
Other,
Zero,
Unspecified,
}
impl BondOrder {
#[must_use]
pub const fn rdkit_name(self) -> &'static str {
match self {
Self::Null | Self::Unspecified => "UNSPECIFIED",
Self::Single => "SINGLE",
Self::Double => "DOUBLE",
Self::Triple => "TRIPLE",
Self::Quadruple => "QUADRUPLE",
Self::Quintuple => "QUINTUPLE",
Self::Hextuple => "HEXTUPLE",
Self::OneAndHalf => "ONEANDAHALF",
Self::TwoAndHalf => "TWOANDAHALF",
Self::ThreeAndHalf => "THREEANDAHALF",
Self::FourAndHalf => "FOURANDAHALF",
Self::FiveAndHalf => "FIVEANDAHALF",
Self::Aromatic => "AROMATIC",
Self::Ionic => "IONIC",
Self::Dative => "DATIVE",
Self::DativeOne => "DATIVEONE",
Self::DativeLeft => "DATIVEL",
Self::DativeRight => "DATIVER",
Self::Hydrogen => "HYDROGEN",
Self::ThreeCenter => "THREECENTER",
Self::Other => "OTHER",
Self::Zero => "ZERO",
}
}
#[must_use]
pub fn from_rdkit_name(name: &str) -> Option<Self> {
match name {
"UNSPECIFIED" | "ZERO" => Some(Self::Unspecified),
"SINGLE" => Some(Self::Single),
"DOUBLE" => Some(Self::Double),
"TRIPLE" => Some(Self::Triple),
"QUADRUPLE" => Some(Self::Quadruple),
"QUINTUPLE" => Some(Self::Quintuple),
"HEXTUPLE" => Some(Self::Hextuple),
"ONEANDAHALF" => Some(Self::OneAndHalf),
"TWOANDAHALF" => Some(Self::TwoAndHalf),
"THREEANDAHALF" => Some(Self::ThreeAndHalf),
"FOURANDAHALF" => Some(Self::FourAndHalf),
"FIVEANDAHALF" => Some(Self::FiveAndHalf),
"AROMATIC" => Some(Self::Aromatic),
"IONIC" => Some(Self::Ionic),
"DATIVE" => Some(Self::Dative),
"DATIVEONE" => Some(Self::DativeOne),
"DATIVEL" => Some(Self::DativeLeft),
"DATIVER" => Some(Self::DativeRight),
"HYDROGEN" => Some(Self::Hydrogen),
"THREECENTER" => Some(Self::ThreeCenter),
"OTHER" => Some(Self::Other),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BondDirection {
None,
BeginWedge,
BeginDash,
EndUpRight,
EndDownRight,
EitherDouble,
Unknown,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BondStereo {
None,
Any,
Z,
E,
Cis,
Trans,
AtropCw,
AtropCcw,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BondSpec {
begin: AtomId,
end: AtomId,
order: BondOrder,
is_aromatic: bool,
is_conjugated: bool,
direction: BondDirection,
stereo: BondStereo,
stereo_atoms: Option<[AtomId; 2]>,
unknown_stereo: bool,
query: Option<QueryNode<BondQueryPredicate>>,
props: BTreeMap<String, String>,
}
impl BondSpec {
#[must_use]
pub const fn new(begin: AtomId, end: AtomId, order: BondOrder) -> Self {
Self {
begin,
end,
order,
is_aromatic: false,
is_conjugated: false,
direction: BondDirection::None,
stereo: BondStereo::None,
stereo_atoms: None,
unknown_stereo: false,
query: None,
props: BTreeMap::new(),
}
}
#[must_use]
pub const fn begin(&self) -> AtomId {
self.begin
}
#[must_use]
pub const fn end(&self) -> AtomId {
self.end
}
#[must_use]
pub const fn order(&self) -> BondOrder {
self.order
}
#[must_use]
pub const fn with_order(mut self, order: BondOrder) -> Self {
self.order = order;
self
}
#[must_use]
pub const fn direction(&self) -> BondDirection {
self.direction
}
#[must_use]
pub const fn stereo(&self) -> BondStereo {
self.stereo
}
#[must_use]
pub const fn is_aromatic(&self) -> bool {
self.is_aromatic
}
#[must_use]
pub const fn is_conjugated(&self) -> bool {
self.is_conjugated
}
#[must_use]
pub const fn with_aromatic(mut self, is_aromatic: bool) -> Self {
self.is_aromatic = is_aromatic;
self
}
#[must_use]
pub const fn with_conjugated(mut self, is_conjugated: bool) -> Self {
self.is_conjugated = is_conjugated;
self
}
#[must_use]
pub const fn with_direction(mut self, direction: BondDirection) -> Self {
self.direction = direction;
self
}
#[must_use]
pub const fn with_stereo(mut self, stereo: BondStereo) -> Self {
self.stereo = stereo;
self
}
#[must_use]
pub const fn with_stereo_atoms(mut self, begin_ref: AtomId, end_ref: AtomId) -> Self {
self.stereo_atoms = Some([begin_ref, end_ref]);
self
}
#[must_use]
pub const fn without_stereo_atoms(mut self) -> Self {
self.stereo_atoms = None;
self
}
#[must_use]
pub const fn stereo_atoms(&self) -> Option<[AtomId; 2]> {
self.stereo_atoms
}
#[must_use]
pub const fn unknown_stereo(&self) -> bool {
self.unknown_stereo
}
#[must_use]
pub const fn with_unknown_stereo(mut self, unknown_stereo: bool) -> Self {
self.unknown_stereo = unknown_stereo;
self
}
#[must_use]
pub fn with_query(mut self, query: QueryNode<BondQueryPredicate>) -> Self {
self.query = Some(query);
self
}
#[must_use]
pub fn without_query(mut self) -> Self {
self.query = None;
self
}
#[must_use]
pub const fn query(&self) -> Option<&QueryNode<BondQueryPredicate>> {
self.query.as_ref()
}
#[must_use]
pub fn with_prop(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.props.insert(key.into(), value.into());
self
}
#[must_use]
pub fn props(&self) -> &BTreeMap<String, String> {
&self.props
}
#[must_use]
pub fn prop(&self, key: &str) -> Option<&str> {
self.props.get(key).map(String::as_str)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Bond {
id: BondId,
begin: AtomId,
end: AtomId,
order: BondOrder,
is_aromatic: bool,
is_conjugated: bool,
direction: BondDirection,
stereo: BondStereo,
stereo_atoms: Option<[AtomId; 2]>,
unknown_stereo: bool,
query: Option<QueryNode<BondQueryPredicate>>,
props: BTreeMap<String, String>,
}
impl Bond {
pub(crate) fn from_spec(id: BondId, spec: BondSpec) -> Self {
Self {
id,
begin: spec.begin,
end: spec.end,
order: spec.order,
is_aromatic: spec.is_aromatic,
is_conjugated: spec.is_conjugated,
direction: spec.direction,
stereo: spec.stereo,
stereo_atoms: spec.stereo_atoms,
unknown_stereo: spec.unknown_stereo,
query: spec.query,
props: spec.props,
}
}
#[allow(dead_code)]
pub(crate) fn remapped(
mut self,
id: BondId,
begin: AtomId,
end: AtomId,
stereo_atoms: Option<[AtomId; 2]>,
) -> Self {
self.id = id;
self.begin = begin;
self.end = end;
self.stereo_atoms = stereo_atoms;
self
}
#[must_use]
pub const fn id(&self) -> BondId {
self.id
}
pub(crate) fn set_id_for_construction(&mut self, id: BondId) {
self.id = id;
}
#[must_use]
pub const fn begin(&self) -> AtomId {
self.begin
}
#[must_use]
pub const fn end(&self) -> AtomId {
self.end
}
#[must_use]
pub const fn order(&self) -> BondOrder {
self.order
}
#[must_use]
pub const fn is_aromatic(&self) -> bool {
self.is_aromatic
}
#[must_use]
pub const fn is_conjugated(&self) -> bool {
self.is_conjugated
}
#[must_use]
pub const fn direction(&self) -> BondDirection {
self.direction
}
#[must_use]
pub const fn stereo(&self) -> BondStereo {
self.stereo
}
#[must_use]
pub const fn stereo_atoms(&self) -> Option<[AtomId; 2]> {
self.stereo_atoms
}
#[must_use]
pub const fn unknown_stereo(&self) -> bool {
self.unknown_stereo
}
#[must_use]
pub const fn query(&self) -> Option<&QueryNode<BondQueryPredicate>> {
self.query.as_ref()
}
#[must_use]
pub fn props(&self) -> &BTreeMap<String, String> {
&self.props
}
#[must_use]
pub fn prop(&self, key: &str) -> Option<&str> {
self.props.get(key).map(String::as_str)
}
#[allow(dead_code)]
pub(crate) fn set_order(&mut self, order: BondOrder) {
self.order = order;
}
#[allow(dead_code)]
pub(crate) fn set_endpoints(&mut self, begin: AtomId, end: AtomId) {
self.begin = begin;
self.end = end;
}
#[allow(dead_code)]
pub(crate) fn set_aromatic(&mut self, is_aromatic: bool) {
self.is_aromatic = is_aromatic;
}
#[allow(dead_code)]
pub(crate) fn set_conjugated(&mut self, is_conjugated: bool) {
self.is_conjugated = is_conjugated;
}
#[allow(dead_code)]
pub(crate) fn set_direction(&mut self, direction: BondDirection) {
self.direction = direction;
}
#[allow(dead_code)]
pub(crate) fn set_stereo(&mut self, stereo: BondStereo) {
debug_assert!(
!matches!(stereo, BondStereo::Cis | BondStereo::Trans) || self.stereo_atoms.is_some(),
"stereo atoms should be specified before CIS/TRANS bond stereochemistry"
);
self.stereo = stereo;
}
#[allow(dead_code)]
pub(crate) fn set_stereo_atoms(&mut self, stereo_atoms: Option<[AtomId; 2]>) {
self.stereo_atoms = stereo_atoms;
}
#[allow(dead_code)]
pub(crate) fn set_unknown_stereo(&mut self, unknown_stereo: bool) {
self.unknown_stereo = unknown_stereo;
}
#[allow(dead_code)]
pub(crate) fn set_query(&mut self, query: Option<QueryNode<BondQueryPredicate>>) {
self.query = query;
}
#[allow(dead_code)]
pub(crate) fn set_prop(&mut self, key: impl Into<String>, value: impl Into<String>) {
self.props.insert(key.into(), value.into());
}
#[allow(dead_code)]
pub(crate) fn clear_prop(&mut self, key: &str) {
self.props.remove(key);
}
}