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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
//! Types of the syntax tree
//!
//! A molecule is a tree whose nodes are atoms. Several bonds can go out from one atom,
//! and each one leads to another atom.
//! Branches and the main chain are stored in the same form. A branch is written in parentheses,
//! but in the tree it is just one of the bonds that go out.
//!
//! Only a ring does not form a tree, because its last bond goes back to its first atom.
//! A ring is held, as a [`Ring`], by the node that is its first atom.
/// Kind of bond
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Kind {
/// Single bond -
Single,
/// Double bond =
Double,
/// Triple bond ~
Triple,
/// Wedge >. The wide side is where the bond arrives
Wedge,
/// Wedge <. The wide side is where the bond leaves
WedgeBack,
/// Hashed wedge >:. The wide side is where the bond arrives
Hash,
/// Hashed wedge <:. The wide side is where the bond leaves
HashBack,
/// Unfilled wedge >|. The wide side is where the bond arrives
Hollow,
/// Unfilled wedge <|. The wide side is where the bond leaves
HollowBack,
}
impl Kind {
/// Whether this is one of the wedge kinds
pub fn is_cram(&self) -> bool {
!matches!(self, Kind::Single | Kind::Double | Kind::Triple)
}
/// Whether the wide side is where the bond leaves
pub fn is_reversed(&self) -> bool {
matches!(self, Kind::WedgeBack | Kind::HashBack | Kind::HollowBack)
}
}
/// How the direction of a bond is set
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Angle {
/// Not written. In a chain it points sideways (east), and from a ring vertex it points
/// out of the ring
Default,
/// Angle with east at 0 degrees and counterclockwise as positive
Absolute(f64),
/// Difference from the direction of the previous bond
Relative(f64),
}
/// Direction of a bond whose direction is not written
pub const DEFAULT_ANGLE: Angle = Angle::Default;
/// One bond
#[derive(Debug, Clone, PartialEq)]
pub struct Bond {
pub kind: Kind,
pub angle: Angle,
/// Factor applied to the bond length
pub length: f64,
/// Number of the part where the bond leaves (starting at 1). If not written, the whole label
pub from: Option<usize>,
/// Number of the part where the bond arrives (starting at 1). If not written, the whole label
pub to: Option<usize>,
/// Position of this bond in the original string
pub position: usize,
}
impl Bond {
/// A bond with neither a direction nor a length written
pub fn new(kind: Kind, position: usize) -> Bond {
Bond {kind, angle: DEFAULT_ANGLE, length: 1.0, from: None, to: None, position}
}
/// Whether a direction or a length is written. Neither can be written inside a ring
pub fn is_placed(&self) -> bool {
self.angle != DEFAULT_ANGLE || self.length != 1.0
}
}
/// Ring closure mark
///
/// When a mark with the same name appears twice, a bond is drawn between those two atoms.
/// Fused rings and large rings are closed this way.
#[derive(Debug, Clone, PartialEq)]
pub struct Hook {
/// Name of the mark. If none is written, the name a is used
pub name: String,
/// Kind of bond drawn on closing. Of the two marks, the one with a kind written is used
pub kind: Option<Kind>,
/// Position of this mark in the original string
pub position: usize,
}
/// Name used when no name is written for a mark
pub const DEFAULT_HOOK: &str = "a";
/// An atom. It holds the original string of its label as it is
#[derive(Debug, Clone, PartialEq)]
pub struct Atom {
/// The label as written. An empty string means a node with no mark
pub source: String,
/// Ring closure marks attached to this atom
pub hook: Vec<Hook>,
/// Position of this atom in the original string
pub position: usize,
}
/// A ring
///
/// The first atom is the node that holds this ring. member has the atoms after it,
/// one fewer than size. bond has size bonds, and the last one goes back to the first atom.
#[derive(Debug, Clone, PartialEq)]
pub struct Ring {
/// Size of the ring. The number of vertices
pub size: usize,
/// Whether to draw a circle inside. The ** of an aromatic ring sets this
pub circle: bool,
pub bond: Vec<Bond>,
pub member: Vec<Node>,
/// Position of this ring in the original string
pub position: usize,
}
/// A node of the syntax tree
#[derive(Debug, Clone, PartialEq)]
pub struct Node {
pub atom: Atom,
/// The bonds that go out from this atom, each with the node at its other end
pub branch: Vec<(Bond, Node)>,
/// The ring whose first vertex is this atom
pub ring: Option<Ring>,
}
impl Node {
/// A node with only a label
pub fn new(source: impl Into<String>, position: usize) -> Node {
Node {
atom: Atom {source: source.into(), hook: Vec::new(), position},
branch: Vec::new(),
ring: None,
}
}
}
/// Number of atoms in the tree
pub fn count(node: &Node) -> usize {
let branch: usize = node.branch.iter().map(|(_, child)| count(child)).sum();
let ring: usize = match &node.ring {
Some(found) => found.member.iter().map(count).sum(),
None => 0,
};
1 + branch + ring
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn counts_the_atoms_of_a_tree() {
let mut root = Node::new("C", 0);
root.branch.push((Bond::new(Kind::Single, 1), Node::new("H", 2)));
root.branch.push((Bond::new(Kind::Double, 3), Node::new("O", 4)));
assert_eq!(count(&root), 3);
}
#[test]
fn counts_the_atoms_of_a_ring() {
let mut root = Node::new("", 0);
root.ring = Some(Ring {
size: 6,
circle: false,
bond: (0..6).map(|at| Bond::new(Kind::Single, at)).collect(),
member: (1..6).map(|at| Node::new("", at)).collect(),
position: 0,
});
assert_eq!(count(&root), 6);
}
#[test]
fn a_bond_without_brackets_goes_east() {
let bond = Bond::new(Kind::Single, 0);
assert_eq!(bond.angle, Angle::Default);
assert_eq!(bond.length, 1.0);
assert_eq!(bond.from, None);
assert!(!bond.is_placed());
}
#[test]
fn the_cram_bonds_know_their_wide_side() {
assert!(Kind::Wedge.is_cram());
assert!(!Kind::Single.is_cram());
assert!(!Kind::Wedge.is_reversed());
assert!(Kind::WedgeBack.is_reversed());
assert!(Kind::HashBack.is_reversed());
assert!(!Kind::Hollow.is_reversed());
}
}