Skip to content

Commit

Permalink
Add core-arm based topology graph (#512)
Browse files Browse the repository at this point in the history
This commit introduces a new small molecule topology graph for 
adding `N` arms to a central building block with `N` functional 
groups. It is complicated a bit by allowing the same flexibility and 
ordering provided by the linear polymer topology graph.

Currently, I am proposing that the core is aligned into one plane. Then
the arms are aligned, by their user-prescribed pattern, defined by the
rotation around the core.

I have also added minor things to the source code as I went along, fixed
a typo, started adding clearer explanations of exceptions. These can be
moved to another PR, but they came out naturally in this process.
  • Loading branch information
andrewtarzia authored Jul 27, 2023
1 parent a514b46 commit 9242c29
Show file tree
Hide file tree
Showing 42 changed files with 1,004 additions and 62 deletions.
1 change: 1 addition & 0 deletions docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
:maxdepth: 2

Polymers <polymer>
Small Molecules <small>
Organic & Metal-Organic Cages <cage>
Covalent Organic Frameworks <cof>
Metal Complexes <metal_complex>
Expand Down
6 changes: 3 additions & 3 deletions docs/source/polymer.rst
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
Polymer
=======
Polymers
========


.. toctree::
:maxdepth: 1

Linear <_autosummary/stk.polymer.Linear>
Linear <_autosummary/stk.polymer.Linear>
8 changes: 8 additions & 0 deletions docs/source/small.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Small Molecules
===============


.. toctree::
:maxdepth: 1

NCore <_autosummary/stk.small.NCore>
5 changes: 4 additions & 1 deletion docs/source/video_tutorials.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ Video Tutorials
===============

Below is a list of video tutorials made by `Andrew Tarzia`__, which
provide a thorough introduction to :mod:`.stk`.
provide a thorough introduction to :mod:`.stk`. More videos can be found
here__.

__ https://github.com/andrewtarzia
__ https://youtube.com/@andrewtarzia1790

Basics of *stk*
---------------
Expand Down Expand Up @@ -80,3 +82,4 @@ notebook is available here__.
__ https://www.youtube.com/watch?v=1BBhPeIRV_E&list=PLIWYdPQ9hLzVngMF8NOkiApMtgc_ZwZgO&index=7
__ https://github.com/andrewtarzia/SpinDry
__ https://github.com/andrewtarzia/stk-examples/tree/main/notebooks_from_videos

2 changes: 2 additions & 0 deletions src/stk/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
metal_complex,
polymer,
rotaxane,
small,
)
from stk._internal.atom import Atom
from stk._internal.atom_info import AtomInfo
Expand Down Expand Up @@ -460,6 +461,7 @@
"Thiol",
"cage",
"polymer",
"small",
"Collapser",
"MCHammer",
"ReactionFactory",
Expand Down
2 changes: 1 addition & 1 deletion src/stk/_internal/construction_state/construction_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ def get_num_edges(self):

def get_edges(self, vertex_id):
"""
Get the edges connect to a vertex.
Get the edges connected to a vertex.
Parameters
----------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
from stk._internal.atom_info import AtomInfo
from stk._internal.bond import Bond
from stk._internal.bond_info import BondInfo
from stk._internal.functional_groups.functional_group import FunctionalGroup
from stk._internal.functional_groups.functional_group import (
FunctionalGroup,
)
from stk._internal.topology_graphs.topology_graph.utilities import (
_PlacementResult,
)
Expand Down Expand Up @@ -205,10 +207,25 @@ def _with_functional_group_edges(
)
edge_ids = functional_group_edges.values()
functional_group_edges_ = zip(functional_groups, edge_ids)
for functional_group, edge_id in functional_group_edges_:
self._edge_functional_groups[edge_id].append(
functional_group.with_ids(id_map)
try:
for functional_group, edge_id in functional_group_edges_:
self._edge_functional_groups[edge_id].append(
functional_group.with_ids(id_map)
)
except IndexError as error:
error.add_note(
"This error suggests that one of your building blocks "
"does not have the correct number of functional groups "
"for the topology graph:\n"
f"building block: {building_block}\n"
"num. functional groups: "
f"{building_block.get_num_functional_groups()}.\n"
f"expected num.: {len(functional_group_edges)}.\n"
"Alternatively, this may suggest a badly defined "
"topology graph (e.g., the wrong number of edges for a "
"vertex)."
)
raise

def get_atoms(self):
"""
Expand Down
2 changes: 1 addition & 1 deletion src/stk/_internal/functional_groups/functional_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ class FunctionalGroup:
actually get made during construction. Finally, you will pass an
instance of your :class:`.ReactionFactory` subclass to the
chosen :class:`.TopologyGraph` you want to make, for example
:class:`~.polymer.linear.linear.Linear`, and your custom
:class:`~.polymer.linear.Linear`, and your custom
modification will take place.
See Also:
Expand Down
7 changes: 0 additions & 7 deletions src/stk/_internal/topology_graphs/polymer/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +0,0 @@
"""
Polymer
=======
#. :class:`.Linear`
"""
Original file line number Diff line number Diff line change
Expand Up @@ -588,7 +588,7 @@ def clone(self) -> typing.Self:

@staticmethod
def _normalize_repeating_unit(
repeating_unit: typing.Union[str, tuple[int, ...]],
repeating_unit: str | tuple[int, ...],
) -> tuple[int, ...]:
if isinstance(repeating_unit, tuple):
return repeating_unit
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
"""
Linear Polymer Vertices
=======================
"""

import logging

import numpy as np

from stk._internal.building_block import BuildingBlock
from stk._internal.topology_graphs.vertex import Vertex

from ..edge import Edge

logger = logging.getLogger(__name__)


Expand All @@ -17,34 +16,38 @@ class LinearVertex(Vertex):
"""

def __init__(self, id, position, flip):
def __init__(
self,
id: int,
position: tuple[float, float, float] | np.ndarray,
flip: bool,
) -> None:
"""
Initialize a :class:`.LinearVertex` instance.
Parameters
----------
id : :class:`int`
The id of the vertex.
Parameters:
position : :class:`numpy.ndarray`
The position of the vertex.
id:
The id of the vertex.
flip : :class:`bool`
If ``True`` any building block placed by the vertex will
have its orientation along the chain flipped.
position:
The position of the vertex.
flip:
If ``True`` any building block placed by the vertex will
have its orientation along the chain flipped.
"""

super().__init__(id, position)
self._flip = flip

def get_flip(self):
def get_flip(self) -> bool:
"""
Return ``True`` if the vertex flips building blocks it places.
Return whether the vertex flips building blocks it places.
Returns:
Returns
-------
:class:`bool`
``True`` if the vertex flips building blocks it places.
"""
Expand All @@ -56,7 +59,11 @@ def clone(self):
clone._flip = self._flip
return clone

def place_building_block(self, building_block, edges):
def place_building_block(
self,
building_block: BuildingBlock,
edges: tuple[Edge, ...],
) -> np.ndarray:
assert building_block.get_num_functional_groups() == 2, (
f"{building_block} needs to have exactly 2 functional "
"groups but has "
Expand All @@ -75,11 +82,15 @@ def place_building_block(self, building_block, edges):
)
return building_block.with_rotation_between_vectors(
start=fg2_position - fg1_position,
target=[-1 if self._flip else 1, 0, 0],
target=np.array([-1 if self._flip else 1, 0, 0]),
origin=self._position,
).get_position_matrix()

def map_functional_groups_to_edges(self, building_block, edges):
def map_functional_groups_to_edges(
self,
building_block: BuildingBlock,
edges: tuple[Edge, ...],
) -> dict[int, int]:
fg1_id, fg2_id = self._sort_functional_groups(building_block)
edge1_id, edge2_id = self._sort_edges(edges)
return {
Expand All @@ -88,7 +99,9 @@ def map_functional_groups_to_edges(self, building_block, edges):
}

@staticmethod
def _sort_functional_groups(building_block):
def _sort_functional_groups(
building_block: BuildingBlock,
) -> tuple[int, int]:
fg1, fg2 = building_block.get_functional_groups()
x1, y1, z1 = building_block.get_centroid(
atom_ids=fg1.get_placer_ids(),
Expand All @@ -99,7 +112,7 @@ def _sort_functional_groups(building_block):
return (0, 1) if x1 < x2 else (1, 0)

@staticmethod
def _sort_edges(edges):
def _sort_edges(edges: tuple[Edge, ...]) -> tuple[int, ...]:
edge1, edge2 = edges
x1, y1, z1 = edge1.get_position()
x2, y2, z2 = edge2.get_position()
Expand All @@ -125,7 +138,13 @@ class TerminalVertex(LinearVertex):
"""

def place_building_block(self, building_block, edges):
_cap_direction = 0

def place_building_block(
self,
building_block: BuildingBlock,
edges: tuple[Edge, ...],
) -> np.ndarray:
if (
building_block.get_num_functional_groups() != 1
and building_block.get_num_placers() > 1
Expand All @@ -146,11 +165,15 @@ def place_building_block(self, building_block, edges):
return building_block.with_rotation_between_vectors(
start=fg_centroid - core_centroid,
# _cap_direction is defined by a subclass.
target=[self._cap_direction, 0, 0],
target=np.array([self._cap_direction, 0, 0]),
origin=self._position,
).get_position_matrix()

def map_functional_groups_to_edges(self, building_block, edges):
def map_functional_groups_to_edges(
self,
building_block: BuildingBlock,
edges: tuple[Edge, ...],
) -> dict[int, int]:
if building_block.get_num_functional_groups() == 2:
functional_groups = self._sort_functional_groups(
building_block=building_block,
Expand Down Expand Up @@ -196,11 +219,19 @@ class UnaligningVertex(LinearVertex):
"""

def place_building_block(self, building_block, edges):
def place_building_block(
self,
building_block: BuildingBlock,
edges: tuple[Edge, ...],
) -> np.ndarray:
return building_block.with_centroid(
position=self._position,
atom_ids=building_block.get_placer_ids(),
).get_position_matrix()

def map_functional_groups_to_edges(self, building_block, edges):
def map_functional_groups_to_edges(
self,
building_block: BuildingBlock,
edges: tuple[Edge, ...],
) -> dict[int, int]:
return {fg_id: edge.get_id() for fg_id, edge in enumerate(edges)}
Loading

0 comments on commit 9242c29

Please sign in to comment.