Skip to content

Commit

Permalink
Add branch recognition (#15)
Browse files Browse the repository at this point in the history
  • Loading branch information
pumken authored Mar 21, 2023
1 parent c81faa1 commit 047033b
Show file tree
Hide file tree
Showing 10 changed files with 358 additions and 176 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "chemcreator"
version = "1.1.0"
version = "1.2.0"
description = "A text-based tool for identifying organic molecules."
authors = ["Gavin Tran"]
readme = "README.md"
Expand Down
47 changes: 44 additions & 3 deletions src/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ pub(crate) fn get_all_chains(graph: &GridState) -> Fallible<Vec<Vec<Atom>>> {
let mut out: Vec<Vec<Atom>> = vec![];

for endpoint in endpoints {
out.extend(endpoint_head_chains(endpoint.to_owned(), graph)?);
out.extend(endpoint_head_chains(endpoint.to_owned(), graph, None)?);
}
Ok(out)
}
Expand All @@ -46,10 +46,14 @@ pub(crate) fn get_all_chains(graph: &GridState) -> Fallible<Vec<Vec<Atom>>> {
/// ## Errors
///
/// Returns [`InvalidGraphError`] if any invalid structures are found while traversing the graph.
fn endpoint_head_chains(endpoint: Atom, graph: &GridState) -> Fallible<Vec<Vec<Atom>>> {
pub(crate) fn endpoint_head_chains(
endpoint: Atom,
graph: &GridState,
previous_pos: Option<Vec2>,
) -> Fallible<Vec<Vec<Atom>>> {
let mut accumulator = vec![vec![]];

accumulate_carbons(endpoint.pos, None, 0usize, &mut accumulator, graph)?;
accumulate_carbons(endpoint.pos, previous_pos, 0usize, &mut accumulator, graph)?;

Ok(accumulator)
}
Expand Down Expand Up @@ -230,6 +234,7 @@ mod tests {
pos: Vec2::zero(),
},
&graph,
None,
)
.unwrap();

Expand All @@ -239,6 +244,42 @@ mod tests {
assert_ne!(accumulator[0], accumulator[1]);
}

#[test]
fn endpoint_head_chains_ignores_previous() {
let graph = graph_with!(5, 3,
[0, 0; A(C)],
[1, 0; A(C)],
[2, 0; A(C)], [2, 1; A(C)], [2, 2; A(C)],
[3, 0; A(C)],
[4, 0; A(C)]
);
let accumulator = endpoint_head_chains(
Atom {
element: C,
pos: Vec2::xy(1, 0),
},
&graph,
Some(Vec2::zero()),
)
.unwrap();

assert_eq!(accumulator.len(), 2);
assert_eq!(accumulator[0].len(), 4);
assert_eq!(accumulator[1].len(), 4);
assert_ne!(accumulator[0], accumulator[1]);
assert!(!accumulator
.into_iter()
.flatten()
.collect::<Vec<Atom>>()
.contains({
if let Cell::Atom(it) = graph.get(Vec2::zero()).unwrap() {
it
} else {
panic!("")
}
}));
}

#[test]
fn create_branches_copies_correctly() {
let atom = Atom {
Expand Down
103 changes: 79 additions & 24 deletions src/groups.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
//! The `groups` module provides functionality for identifying functional groups on a branch.
use crate::chain;
use crate::chain::{endpoint_head_chains, longest_chain};
use crate::groups::InvalidGraphError::{Other, UnrecognizedGroup};
use crate::molecule::Group::{
Alkene, Alkyne, Bromo, Carbonyl, Carboxyl, Chloro, Fluoro, Hydroxyl, Iodo,
Expand All @@ -15,37 +16,64 @@ use thiserror::Error;

/// Generates a [`Branch`] from the given `chain` containing all functional groups attached
/// to it.
pub(crate) fn link_groups(graph: &GridState, chain: Vec<Atom>) -> Fallible<Branch> {
pub(crate) fn link_groups(
graph: &GridState,
chain: Vec<Atom>,
parent: Option<Atom>,
) -> Fallible<Branch> {
let mut branch = Branch::new(chain);

fn accumulate_groups(
graph: &GridState,
accumulator: &mut Branch,
index: usize,
parent: Option<Atom>,
) -> Fallible<()> {
if index >= accumulator.chain.len() {
return Ok(());
}

let group_nodes = group_directions(graph, accumulator, index)?
.iter()
.map(|&dir| group_node_tree(graph, accumulator.chain[index].pos, dir))
let directions = group_directions(graph, accumulator, index, parent.clone())?;

let group_nodes = directions
.0
.into_iter()
.map(|dir| group_node_tree(graph, accumulator.chain[index].pos, dir))
.collect::<Fallible<Vec<GroupNode>>>()?;

let groups = convert_nodes(group_nodes)?;
let mut chain_nodes = directions
.1
.into_iter()
.map(|dir| branch_node_tree(graph, accumulator.chain[index].pos, dir).unwrap())
.map(|chain| {
link_groups(
graph,
chain,
Some(Atom {
element: Element::C,
pos: accumulator.chain[index].pos,
}),
)
.unwrap()
})
.map(Substituent::Branch)
.collect::<Vec<Substituent>>();

let mut groups = convert_nodes(group_nodes)?;
groups.append(&mut chain_nodes);
accumulator.groups.push(groups);

accumulate_groups(graph, accumulator, index + 1)
accumulate_groups(graph, accumulator, index + 1, parent)
}

accumulate_groups(graph, &mut branch, 0usize)?;
accumulate_groups(graph, &mut branch, 0usize, parent)?;
Ok(branch)
}

pub(crate) fn debug_branches(graph: &GridState) -> Fallible<Branch> {
let all_chains = chain::get_all_chains(graph)?;
let chain = chain::longest_chain(all_chains)?;
link_groups(graph, chain)
link_groups(graph, chain, None)
}

/// Converts and combines the given `group_nodes` into [`Substituent`]s.
Expand Down Expand Up @@ -143,6 +171,16 @@ pub(crate) fn group_node_tree(
})
}

pub(crate) fn branch_node_tree(
graph: &GridState,
pos: Vec2,
direction: Direction,
) -> Fallible<Vec<Atom>> {
let atom = Pointer::new(graph, pos).traverse_bond(direction)?;
let chains = endpoint_head_chains(atom, graph, Some(pos))?;
longest_chain(chains)
}

/// Returns a [`Vec`] of [`Direction`] from the [`Atom`] at the given `pos` to bonded atoms
/// not including the one at the `previous_pos`.
///
Expand Down Expand Up @@ -174,7 +212,7 @@ fn next_directions(graph: &GridState, pos: Vec2, previous_pos: Vec2) -> Fallible
}

/// Returns a [`Vec`] of [`Direction`]s from the [`Atom`] at the given `index` to functional
/// groups.
/// groups and side chains, respectively.
///
/// ## Errors
///
Expand All @@ -184,17 +222,19 @@ fn group_directions(
graph: &GridState,
accumulator: &Branch,
index: usize,
) -> Fallible<Vec<Direction>> {
parent: Option<Atom>,
) -> Fallible<(Vec<Direction>, Vec<Direction>)> {
let ptr = Pointer::new(graph, accumulator.chain[index].pos);
let directions = ptr
.connected_directions()
.into_iter()
.filter(|&direction| {
let first_element = ptr.traverse_bond(direction).unwrap().element;
let opposite_atom = ptr.traverse_bond(direction).unwrap();
let single_bond = matches!(ptr.bond_order(direction).unwrap(), BondOrder::Single);
let hydrocarbon =
matches!(first_element, Element::C) || matches!(first_element, Element::H);
!single_bond || !hydrocarbon
let hydrogen = matches!(opposite_atom.element, Element::H);
let in_chain = accumulator.chain.contains(&opposite_atom);
let parent = Some(opposite_atom) == parent;
!(hydrogen || parent || in_chain && single_bond)
})
.filter(|&direction| {
if index > 0 {
Expand All @@ -208,7 +248,12 @@ fn group_directions(
true
}
})
.collect::<Vec<Direction>>();
.partition(|&direction| {
let opposite_atom = ptr.traverse_bond(direction).unwrap();
let carbon = matches!(opposite_atom.element, Element::C);
let in_chain = accumulator.chain.contains(&opposite_atom);
in_chain || !carbon
});

Ok(directions)
}
Expand Down Expand Up @@ -266,13 +311,14 @@ mod tests {
pos: Vec2::xy(3, 1),
},
];
let branch = link_groups(&graph, chain.clone()).unwrap();
let branch = link_groups(&graph, chain.clone(), None).unwrap();
let expected = Branch {
chain,
groups: vec![
vec![Substituent::Group(Chloro)],
vec![Substituent::Group(Hydroxyl)],
],
parent_alpha: None,
};

assert_eq!(branch, expected);
Expand Down Expand Up @@ -418,15 +464,23 @@ mod tests {
[2, 0; A(C)], [2, 1; B(Double)], [2, 2; A(C)], [2, 3; A(O)], [2, 4; A(H)]
);
let branch = Branch {
chain: vec![Atom {
element: C,
pos: Vec2::xy(2, 2),
}],
chain: vec![
Atom {
element: C,
pos: Vec2::xy(0, 2),
},
Atom {
element: C,
pos: Vec2::xy(2, 2),
},
],
groups: vec![],
parent_alpha: None,
};
let directions = group_directions(&graph, &branch, 0usize).unwrap();
let directions = group_directions(&graph, &branch, 1usize, None).unwrap();

assert_eq!(directions, vec![Direction::Up, Direction::Down]);
assert_eq!(directions.0, vec![Direction::Up]);
assert_eq!(directions.1, vec![Direction::Down]);
}

#[test]
Expand All @@ -444,9 +498,10 @@ mod tests {
pos: Vec2::xy(1, 1),
}],
groups: vec![],
parent_alpha: None,
};
let directions = group_directions(&graph, &branch, 0usize).unwrap();
let directions = group_directions(&graph, &branch, 0usize, None).unwrap();

assert_eq!(directions, Direction::all());
assert_eq!(directions.0, Direction::all());
}
}
9 changes: 2 additions & 7 deletions src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@
//! Not to be confused with Rust's `macro_rules!` declarations, the `macros` module contains
//! common actions that should be automatically performed for the user when they make an input.
use crate::molecule::BondOrder::Single;
use crate::molecule::ComponentType;
use crate::molecule::Element::{C, H};
use crate::pointer::Pointer;
use crate::spatial::{EnumAll, GridState, ToVec2};
use crate::spatial::{EnumAll, GridState};
use ruscii::spatial::Direction;

pub fn invoke_macro(graph: &mut GridState) {
Expand All @@ -31,13 +30,9 @@ pub fn fill_hydrogen(graph: &mut GridState) {
fn hydrogen_arm(graph: &mut GridState, direction: Direction) {
let mut ptr = Pointer::new(graph, graph.cursor);
let first_neighbor = ptr.move_ptr(direction) && !ptr.borrow().unwrap().is_empty();
let second_neighbor = ptr.move_ptr(direction) && !ptr.borrow().unwrap().is_empty();
let pos = ptr.pos;

if first_neighbor && second_neighbor {
graph.put(pos, ComponentType::Element(H));
graph.put(pos - direction.to_vec2(), ComponentType::Order(Single));
} else if first_neighbor && !second_neighbor {
if first_neighbor {
graph.put(pos, ComponentType::Element(H));
}
}
2 changes: 1 addition & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ mod validation;
fn main() {
let mut app = App::new();
let version = env!("CARGO_PKG_VERSION");
let mut graph = GridState::new(20, 10);
let mut graph = GridState::new(21, 11);
let mut state = AppState::default();

app.run(|app_state: &mut State, window: &mut Window| {
Expand Down
Loading

0 comments on commit 047033b

Please sign in to comment.