1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
/// Represents an atom in a molecule.
#[derive(Debug, Clone, PartialEq)]
pub struct Atom {
/// Atom index (0-based).
pub index: usize,
/// Element symbol (e.g., "C", "N", "O").
pub element: String,
/// X coordinate in Angstroms.
pub x: f64,
/// Y coordinate in Angstroms.
pub y: f64,
/// Z coordinate in Angstroms.
pub z: f64,
/// Formal charge (-15 to +15, 0 = uncharged).
pub formal_charge: i8,
/// Mass difference from monoisotopic mass (-3 to +4).
pub mass_difference: i8,
/// Stereo parity (0 = not stereo, 1 = odd, 2 = even, 3 = either/unknown).
pub stereo_parity: Option<u8>,
/// Hydrogen count (0 = use default, 1 = H0, 2 = H1, etc.).
pub hydrogen_count: Option<u8>,
/// Valence (0 = use default, 15 = zero valence).
pub valence: Option<u8>,
/// Original V3000 atom ID (for round-trip preservation).
pub v3000_id: Option<u32>,
/// Atom-atom mapping number for reactions (V3000).
pub atom_atom_mapping: Option<u32>,
/// R-group label (1-32 for R1-R32).
pub rgroup_label: Option<u8>,
/// Radical state (0=none, 1=singlet, 2=doublet, 3=triplet).
pub radical: Option<u8>,
}
impl Atom {
/// Creates a new atom with the given element and coordinates.
pub fn new(index: usize, element: &str, x: f64, y: f64, z: f64) -> Self {
Self {
index,
element: element.to_string(),
x,
y,
z,
formal_charge: 0,
mass_difference: 0,
stereo_parity: None,
hydrogen_count: None,
valence: None,
v3000_id: None,
atom_atom_mapping: None,
rgroup_label: None,
radical: None,
}
}
/// Returns the 3D coordinates as a tuple.
pub fn coords(&self) -> (f64, f64, f64) {
(self.x, self.y, self.z)
}
/// Returns the distance to another atom.
pub fn distance_to(&self, other: &Atom) -> f64 {
let dx = self.x - other.x;
let dy = self.y - other.y;
let dz = self.z - other.z;
(dx * dx + dy * dy + dz * dz).sqrt()
}
/// Returns true if this atom has a non-zero formal charge.
pub fn is_charged(&self) -> bool {
self.formal_charge != 0
}
}
impl Default for Atom {
fn default() -> Self {
Self {
index: 0,
element: String::new(),
x: 0.0,
y: 0.0,
z: 0.0,
formal_charge: 0,
mass_difference: 0,
stereo_parity: None,
hydrogen_count: None,
valence: None,
v3000_id: None,
atom_atom_mapping: None,
rgroup_label: None,
radical: None,
}
}
}