diff --git a/src/stk/molecular/molecules/constructed_molecule.py b/src/stk/molecular/molecules/constructed_molecule.py index e4c7e802f..2d14cccbd 100644 --- a/src/stk/molecular/molecules/constructed_molecule.py +++ b/src/stk/molecular/molecules/constructed_molecule.py @@ -70,19 +70,10 @@ def __init__(self, topology_graph): """ - construction_result = topology_graph.construct() - super().__init__( - atoms=construction_result.get_atoms(), - bonds=construction_result.get_bonds(), - position_matrix=construction_result.get_position_matrix(), + self._init_from_construction_result( + obj=self, + construction_result=topology_graph.construct(), ) - self._atom_infos = construction_result.get_atom_infos() - self._bond_infos = construction_result.get_bond_infos() - self._num_building_blocks = { - building_block: - topology_graph.get_num_building_block(building_block) - for building_block in topology_graph.get_building_blocks() - } @classmethod def init( @@ -132,6 +123,74 @@ def init( molecule._num_building_blocks = dict(num_building_blocks) return molecule + @classmethod + def init_from_construction_result( + cls, + construction_result, + ): + """ + Initialize a :class:`.ConstructedMolecule`. + + Parameters + ---------- + construction_result : :class:`.ConstructionResult` + The result of a construction, from which the + :class:`.ConstructedMolecule` should be initialized. + + Returns + ------- + :class:`.ConstructedMolecule` + The constructed molecule. + + """ + + return cls._init_from_construction_result( + obj=cls.__new__(cls), + construction_result=construction_result, + ) + + @staticmethod + def _init_from_construction_result( + obj, + construction_result, + ): + """ + Initialize a :class:`.ConstructedMolecule`. + + This modifies `obj`. + + Parameters + ---------- + obj : :class:`.ConstructedMolecule` + The constructed molecule to initialize. + + construction_result : :class:`.ConstructionResult` + The result of a construction, from which the + :class:`.ConstructedMolecule` should be initialized. + + Returns + ------- + :class:`.ConstructedMolecule` + The `obj` instance. + + """ + + super(ConstructedMolecule, obj).__init__( + atoms=construction_result.get_atoms(), + bonds=construction_result.get_bonds(), + position_matrix=construction_result.get_position_matrix(), + ) + obj._atom_infos = construction_result.get_atom_infos() + obj._bond_infos = construction_result.get_bond_infos() + obj._num_building_blocks = { + building_block: construction_result.get_num_building_block( + building_block=building_block, + ) + for building_block + in construction_result.get_building_blocks() + } + return obj + def clone(self): clone = super().clone() clone._atom_infos = self._atom_infos diff --git a/src/stk/molecular/topology_graphs/cof/cof.py b/src/stk/molecular/topology_graphs/cof/cof.py index a6536821e..2a86cb806 100644 --- a/src/stk/molecular/topology_graphs/cof/cof.py +++ b/src/stk/molecular/topology_graphs/cof/cof.py @@ -32,7 +32,12 @@ from functools import partial from operator import getitem -from ..topology_graph import TopologyGraph, NullOptimizer, EdgeGroup +from ..topology_graph import ( + TopologyGraph, + NullOptimizer, + EdgeGroup, + PeriodicConstructionResult, +) from .vertices import _UnaligningVertex from .edge import _CofEdge from ...reactions import GenericReactionFactory @@ -95,10 +100,12 @@ class Cof(TopologyGraph): For :class:`.Cof` topologies, it is recommend to use the :class:`.Collapser` optimizer if using the nonperiodic form. - However, no optimizer is valid for periodic systems currently. + For periodic systems, the :class:`.PeriodicCollapser` is + recommended. .. code-block:: python + # Nonperiodic. cof = stk.ConstructedMolecule( topology_graph=stk.cof.Honeycomb( building_blocks=(bb1, bb2), @@ -109,12 +116,21 @@ class Cof(TopologyGraph): ), ) - *Accessing the Periodic Unit Cell* + # Periodic. + cof = stk.ConstructedMolecule( + topology_graph=stk.cof.PeriodicHoneycomb( + building_blocks=(bb1, bb2), + lattice_size=(3, 3, 1), + optimizer=stk.PeriodicCollapser(), + ), + ) - The same :class:`.Cof` instance can be built as a periodic - structure, which has a unit cell (assuming as P1 space group) that - can be accessed at any time from the :class:`.TopologyGraph` - instance. + *Accessing the Periodic Information* + + When building periodic :class:`.Cof` instances, the periodic + information, such as the unit cell, can be accessed if you use the + :class:`.PeriodicConstructionResult` returned by calling + :meth:`.Cof.construct` .. code-block:: python @@ -122,8 +138,11 @@ class Cof(TopologyGraph): building_blocks=(bb1, bb2), lattice_size=(3, 3, 1), ) - cof = stk.ConstructedMolecule(topology_graph) - periodic_info = topology.get_periodic_info() + construction_result = topology_graph.construct() + cof = stk.ConstructedMolecule.init_from_construction_result( + construction_result=construction_result, + ) + periodic_info = construction_result.get_periodic_info() cell_matrix = periodic_info.get_cell_matrix() # Can access all unit-cell parameters. a = periodic_info.get_a() @@ -132,6 +151,13 @@ class Cof(TopologyGraph): alpha = periodic_info.get_alpha() beta = periodic_info.get_beta() gamma = periodic_info.get_gamma() + # Write to .pdb file. + writer = stk.PdbWriter() + writer.write( + molecule=cof, + path='cof.pdb', + periodic_info=periodic_info, + ) *Structural Isomer Construction* @@ -663,6 +689,22 @@ def _get_building_block_vertices( def _get_lattice_constants(self): return self._lattice_constants + def construct(self): + """ + Construct a :class:`.ConstructedMolecule`. + + Returns + ------- + :class:`.PeriodicConstructionResult` + The data describing the :class:`.ConstructedMolecule`. + + """ + + return super().construct() + + def _get_construction_result(self, state): + return PeriodicConstructionResult(state, self._lattice_size) + def _get_scale(self, building_block_vertices): return 5*max( bb.get_maximum_diameter() diff --git a/src/stk/molecular/topology_graphs/cof/periodic_hexagonal.py b/src/stk/molecular/topology_graphs/cof/periodic_hexagonal.py index 9a7d951d8..6b7f92dad 100644 --- a/src/stk/molecular/topology_graphs/cof/periodic_hexagonal.py +++ b/src/stk/molecular/topology_graphs/cof/periodic_hexagonal.py @@ -5,6 +5,8 @@ """ import numpy as np +import warnings + from ...reactions import GenericReactionFactory from .cof import Cof from .vertices import _LinearCofVertex, _NonLinearCofVertex @@ -28,8 +30,8 @@ class PeriodicHexagonal(Cof): | 6-functional groups: 0 to 3 | 2-functional groups: 4 to 15 - Note that :class:`.Optimizer` does not optimize the - :class:`.PeriodicInfo`. + Note that optimizers may not optimize the :class:`.PeriodicInfo`. + The documentation of the optimizer will state if it does. See :class:`.Cof` for more details and examples. @@ -133,6 +135,15 @@ def get_periodic_info(self): """ + warnings.warn( + 'You called get_periodic_info() on a topology graph ' + 'instance. This method will be removed in any version ' + 'of stk released on, or after, 21/10/21. Please call ' + 'the construct() method instead. This will return a ' + 'PeriodicConstructionResult which provides the new ' + 'get_periodic_info() method.' + ) + lattice_constants = self._get_lattice_constants() return PeriodicInfo( diff --git a/src/stk/molecular/topology_graphs/cof/periodic_honeycomb.py b/src/stk/molecular/topology_graphs/cof/periodic_honeycomb.py index f57457d97..d93e44e9e 100644 --- a/src/stk/molecular/topology_graphs/cof/periodic_honeycomb.py +++ b/src/stk/molecular/topology_graphs/cof/periodic_honeycomb.py @@ -5,6 +5,8 @@ """ import numpy as np +import warnings + from ...reactions import GenericReactionFactory from .cof import Cof from .vertices import _LinearCofVertex, _NonLinearCofVertex @@ -28,8 +30,8 @@ class PeriodicHoneycomb(Cof): | 3-functional groups: 0 to 1 | 2-functional groups: 2 to 4 - Note that :class:`.Optimizer` does not optimize the - :class:`.PeriodicInfo`. + Note that optimizers may not optimize the :class:`.PeriodicInfo`. + The documentation of the optimizer will state if it does. See :class:`.Cof` for more details and examples. @@ -133,6 +135,15 @@ def get_periodic_info(self): """ + warnings.warn( + 'You called get_periodic_info() on a topology graph ' + 'instance. This method will be removed in any version ' + 'of stk released on, or after, 21/10/21. Please call ' + 'the construct() method instead. This will return a ' + 'PeriodicConstructionResult which provides the new ' + 'get_periodic_info() method.' + ) + lattice_constants = self._get_lattice_constants() return PeriodicInfo( diff --git a/src/stk/molecular/topology_graphs/cof/periodic_kagome.py b/src/stk/molecular/topology_graphs/cof/periodic_kagome.py index 219f27878..abab824dc 100644 --- a/src/stk/molecular/topology_graphs/cof/periodic_kagome.py +++ b/src/stk/molecular/topology_graphs/cof/periodic_kagome.py @@ -5,6 +5,8 @@ """ import numpy as np +import warnings + from ...reactions import GenericReactionFactory from .cof import Cof from .vertices import _LinearCofVertex, _NonLinearCofVertex @@ -28,8 +30,8 @@ class PeriodicKagome(Cof): | 4-functional groups: 0 to 2 | 2-functional groups: 3 to 8 - Note that :class:`.Optimizer` does not optimize the - :class:`.PeriodicInfo`. + Note that optimizers may not optimize the :class:`.PeriodicInfo`. + The documentation of the optimizer will state if it does. See :class:`.Cof` for more details and examples. @@ -133,6 +135,15 @@ def get_periodic_info(self): """ + warnings.warn( + 'You called get_periodic_info() on a topology graph ' + 'instance. This method will be removed in any version ' + 'of stk released on, or after, 21/10/21. Please call ' + 'the construct() method instead. This will return a ' + 'PeriodicConstructionResult which provides the new ' + 'get_periodic_info() method.' + ) + lattice_constants = self._get_lattice_constants() return PeriodicInfo( diff --git a/src/stk/molecular/topology_graphs/cof/periodic_linkerless_honeycomb.py b/src/stk/molecular/topology_graphs/cof/periodic_linkerless_honeycomb.py index dfd4c9c22..d8a2c9873 100644 --- a/src/stk/molecular/topology_graphs/cof/periodic_linkerless_honeycomb.py +++ b/src/stk/molecular/topology_graphs/cof/periodic_linkerless_honeycomb.py @@ -5,6 +5,8 @@ """ import numpy as np +import warnings + from ...reactions import GenericReactionFactory from .cof import Cof from .vertices import _NonLinearCofVertex @@ -27,8 +29,8 @@ class PeriodicLinkerlessHoneycomb(Cof): | 3-functional groups: 0 to 1 - Note that :class:`.Optimizer` does not optimize the - :class:`.PeriodicInfo`. + Note that optimizers may not optimize the :class:`.PeriodicInfo`. + The documentation of the optimizer will state if it does. See :class:`.Cof` for more details and examples. @@ -132,6 +134,15 @@ def get_periodic_info(self): """ + warnings.warn( + 'You called get_periodic_info() on a topology graph ' + 'instance. This method will be removed in any version ' + 'of stk released on, or after, 21/10/21. Please call ' + 'the construct() method instead. This will return a ' + 'PeriodicConstructionResult which provides the new ' + 'get_periodic_info() method.' + ) + lattice_constants = self._get_lattice_constants() return PeriodicInfo( diff --git a/src/stk/molecular/topology_graphs/cof/periodic_square.py b/src/stk/molecular/topology_graphs/cof/periodic_square.py index 43b49ca98..a40039fa7 100644 --- a/src/stk/molecular/topology_graphs/cof/periodic_square.py +++ b/src/stk/molecular/topology_graphs/cof/periodic_square.py @@ -5,6 +5,7 @@ """ import numpy as np +import warnings from ...reactions import GenericReactionFactory from .cof import Cof from .vertices import _LinearCofVertex, _NonLinearCofVertex @@ -28,8 +29,8 @@ class PeriodicSquare(Cof): | 4-functional groups: 0 | 2-functional groups: 1 to 2 - Note that :class:`.Optimizer` does not optimize the - :class:`.PeriodicInfo`. + Note that optimizers may not optimize the :class:`.PeriodicInfo`. + The documentation of the optimizer will state if it does. See :class:`.Cof` for more details and examples. @@ -133,6 +134,15 @@ def get_periodic_info(self): """ + warnings.warn( + 'You called get_periodic_info() on a topology graph ' + 'instance. This method will be removed in any version ' + 'of stk released on, or after, 21/10/21. Please call ' + 'the construct() method instead. This will return a ' + 'PeriodicConstructionResult which provides the new ' + 'get_periodic_info() method.' + ) + lattice_constants = self._get_lattice_constants() return PeriodicInfo( diff --git a/src/stk/molecular/topology_graphs/topology_graph/__init__.py b/src/stk/molecular/topology_graphs/topology_graph/__init__.py index 09fc7c9f0..0c7880999 100644 --- a/src/stk/molecular/topology_graphs/topology_graph/__init__.py +++ b/src/stk/molecular/topology_graphs/topology_graph/__init__.py @@ -2,3 +2,4 @@ from .vertex import * # noqa from .topology_graph import * # noqa from .optimizers import * # noqa +from .construction_result import * # noqa diff --git a/src/stk/molecular/topology_graphs/topology_graph/construction_result/__init__.py b/src/stk/molecular/topology_graphs/topology_graph/construction_result/__init__.py new file mode 100644 index 000000000..9a5f221ae --- /dev/null +++ b/src/stk/molecular/topology_graphs/topology_graph/construction_result/__init__.py @@ -0,0 +1,8 @@ +from .construction_result import ConstructionResult +from .periodic import PeriodicConstructionResult + + +__all__ = [ + 'ConstructionResult', + 'PeriodicConstructionResult', +] diff --git a/src/stk/molecular/topology_graphs/topology_graph/construction_result.py b/src/stk/molecular/topology_graphs/topology_graph/construction_result/construction_result.py similarity index 62% rename from src/stk/molecular/topology_graphs/topology_graph/construction_result.py rename to src/stk/molecular/topology_graphs/topology_graph/construction_result/construction_result.py index 3a40ae1f4..40a9e8c96 100644 --- a/src/stk/molecular/topology_graphs/topology_graph/construction_result.py +++ b/src/stk/molecular/topology_graphs/topology_graph/construction_result/construction_result.py @@ -17,6 +17,7 @@ class ConstructionResult: '_atom_infos', '_bond_infos', '_position_matrix', + '_num_building_blocks', ] def __init__(self, construction_state): @@ -38,6 +39,13 @@ def __init__(self, construction_state): self._bonds = tuple(construction_state.get_bonds()) self._atom_infos = tuple(construction_state.get_atom_infos()) self._bond_infos = tuple(construction_state.get_bond_infos()) + self._num_building_blocks = { + building_block: construction_state.get_num_building_block( + building_block=building_block, + ) + for building_block + in construction_state.get_building_blocks() + } def get_position_matrix(self): """ @@ -103,3 +111,42 @@ def get_bond_infos(self): """ return self._bond_infos + + def get_num_building_block(self, building_block): + """ + Get the number of times `building_block` is present. + + Parameters + ---------- + building_block : :class:`.BuildingBlock` + The building block whose frequency in the constructed + molecule is desired. + + Returns + ------- + :class:`int` + The number of times `building_block` was used in the + construction of the constructed molecule. + + """ + + return self._num_building_blocks[building_block] + + def get_building_blocks(self): + """ + Yield the building blocks. + + Building blocks are yielded in an order based on their + position in the constructed molecule. For two topologically + equivalent constructed molecules, but with different building + blocks, equivalently positioned building blocks will be + yielded at the same time. + + Yields + ------ + :class:`.BuildingBlock` + A building block of the topology graph. + + """ + + yield from self._num_building_blocks diff --git a/src/stk/molecular/topology_graphs/topology_graph/construction_result/periodic.py b/src/stk/molecular/topology_graphs/topology_graph/construction_result/periodic.py new file mode 100644 index 000000000..cf102fe87 --- /dev/null +++ b/src/stk/molecular/topology_graphs/topology_graph/construction_result/periodic.py @@ -0,0 +1,61 @@ +""" +Periodic Construction Result +============================ + +""" + +from .construction_result import ConstructionResult +from ....periodic_info import PeriodicInfo + + +class PeriodicConstructionResult(ConstructionResult): + """ + The result of :meth:`.TopologyGraph.construct` with periodic info. + + """ + + __slots__ = [ + '_periodic_info', + ] + + def __init__( + self, + construction_state, + lattice_size, + ): + """ + Initialize a :class:`.ConstructionResult`. + + Parameters + ---------- + construction_state : :class:`.ConstructionState` + The state from which the result is initialized. + + lattice_size : :class:`tuple` of :class:`int` + The size of the lattice in the x, y and z directions. + + """ + + super().__init__(construction_state) + self._periodic_info = PeriodicInfo( + *( + lattice_constant*dim + for lattice_constant, dim in zip( + construction_state.get_lattice_constants(), + lattice_size, + ) + ) + ) + + def get_periodic_info(self): + """ + Get the periodic cell information of the constructed molecule. + + Returns + ------- + :class:`.PeriodicInfo` + The periodic cell information of the constructed molecule. + + """ + + return self._periodic_info diff --git a/src/stk/molecular/topology_graphs/topology_graph/construction_state/construction_state.py b/src/stk/molecular/topology_graphs/topology_graph/construction_state/construction_state.py index 22be6a4a9..365c571dc 100644 --- a/src/stk/molecular/topology_graphs/topology_graph/construction_state/construction_state.py +++ b/src/stk/molecular/topology_graphs/topology_graph/construction_state/construction_state.py @@ -127,6 +127,19 @@ def with_placement_results( results=results, ) + def get_lattice_constants(self): + """ + Get the lattice constants of the state. + + Returns + ------- + :class:`tuple` of :class:`numpy.ndarray` + The lattice constants. + + """ + + return self._graph_state.get_lattice_constants() + def get_building_block(self, vertex_id): """ Get the building block to be placed on a given vertex. @@ -285,6 +298,39 @@ def with_reaction_results(self, reactions, results): return self.clone()._with_reaction_results(reactions, results) + def _with_lattice_constants(self, lattice_constants): + """ + Modify the instance. + + """ + + self._graph_state = ( + self._graph_state.with_lattice_constants( + lattice_constants=lattice_constants, + ) + ) + return self + + def with_lattice_constants(self, lattice_constants): + """ + Return a clone holding the `lattice_constants`. + + Parameters + ---------- + lattice_constants : :class:`tuple` of :class:`numpy.ndarray` + The lattice constants of the clone. Requires 3 arrays of + size``(3, )``. + + Returns + ------- + :class:`.ConstructionState` + The clone holding the new lattice constants. Has the same + type as the original instance. + + """ + + return self.clone()._with_lattice_constants(lattice_constants) + def _with_position_matrix(self, position_matrix): """ Modify the instance. @@ -409,3 +455,42 @@ def get_bond_infos(self): """ yield from self._molecule_state.get_bond_infos() + + def get_num_building_block(self, building_block): + """ + Get the number of times `building_block` is present. + + Parameters + ---------- + building_block : :class:`.BuildingBlock` + The building block whose frequency in the topology graph + is desired. + + Returns + ------- + :class:`int` + The number of times `building_block` is present in the + topology graph. + + """ + + return self._graph_state.get_num_building_block(building_block) + + def get_building_blocks(self): + """ + Yield the building blocks. + + Building blocks are yielded in an order based on their + position in the topology graph. For two equivalent + topology graphs, but with different building blocks, + equivalently positioned building blocks will be yielded at the + same time. + + Yields + ------ + :class:`.BuildingBlock` + A building block of the topology graph. + + """ + + yield from self._graph_state.get_building_blocks() diff --git a/src/stk/molecular/topology_graphs/topology_graph/construction_state/graph_state.py b/src/stk/molecular/topology_graphs/topology_graph/construction_state/graph_state.py index e527c5594..307d30e4a 100644 --- a/src/stk/molecular/topology_graphs/topology_graph/construction_state/graph_state.py +++ b/src/stk/molecular/topology_graphs/topology_graph/construction_state/graph_state.py @@ -14,6 +14,7 @@ class _GraphState: '_edges', '_lattice_constants', '_vertex_edges', + '_num_building_blocks', ] def __init__( @@ -48,13 +49,21 @@ def __init__( in building_block_vertices.items() for vertex in vertices } + self._num_building_blocks = { + building_block: len(vertices) + for building_block, vertices + in building_block_vertices.items() + } self._vertices = { vertex.get_id(): vertex for vertices in building_block_vertices.values() for vertex in vertices } self._edges = edges - self._lattice_constants = lattice_constants + self._lattice_constants = tuple(map( + np.array, + lattice_constants, + )) self._vertex_edges = self._get_vertex_edges() def _get_vertex_edges(self): @@ -152,6 +161,11 @@ def clone(self): clone._vertices = dict(self._vertices) clone._vertex_edges = dict(self._vertex_edges) clone._edges = self._edges + clone._lattice_constants = tuple(map( + np.array, + self._lattice_constants, + )) + clone._num_building_blocks = dict(self._num_building_blocks) return clone def get_building_block(self, vertex_id): @@ -243,6 +257,19 @@ def get_num_edges(self): return len(self._edges) + def get_lattice_constants(self): + """ + Get the lattice constants of the state. + + Returns + ------- + :class:`tuple` of :class:`numpy.ndarray` + The lattice constants. + + """ + + return tuple(map(np.array, self._lattice_constants)) + def get_edges(self, vertex_id): """ Get the edges connect to a vertex. @@ -289,3 +316,79 @@ def with_vertices(self, vertices): """ return self.clone()._with_vertices(vertices) + + def _with_lattice_constants(self, lattice_constants): + """ + Modify the instance. + + """ + + self._lattice_constants = tuple(map( + np.array, + lattice_constants, + )) + return self + + def with_lattice_constants(self, lattice_constants): + """ + Return a clone holding the `lattice_constants`. + + Parameters + ---------- + lattice_constants : :class:`tuple` of :class:`numpy.ndarray` + The lattice constants of the clone. Requires 3 arrays of + size``(3, )``. + + Returns + ------- + :class:`._GraphState` + The clone holding the new lattice constants. Has the same + type as the original instance. + + """ + + return self.clone()._with_lattice_constants(lattice_constants) + + def get_num_building_block(self, building_block): + """ + Get the number of times `building_block` is present. + + Parameters + ---------- + building_block : :class:`.BuildingBlock` + The building block whose frequency in the topology graph + is desired. + + Returns + ------- + :class:`int` + The number of times `building_block` is present in the + topology graph. + + """ + + return self._num_building_blocks[building_block] + + def get_building_blocks(self): + """ + Yield the building blocks. + + Building blocks are yielded in an order based on their + position in the topology graph. For two equivalent + topology graphs, but with different building blocks, + equivalently positioned building blocks will be yielded at the + same time. + + Yields + ------ + :class:`.BuildingBlock` + A building block of the topology graph. + + """ + + yielded = set() + for vertex_id in range(max(self._vertex_building_blocks)+1): + building_block = self._vertex_building_blocks[vertex_id] + if building_block not in yielded: + yielded.add(building_block) + yield building_block diff --git a/src/stk/molecular/topology_graphs/topology_graph/optimizers/__init__.py b/src/stk/molecular/topology_graphs/topology_graph/optimizers/__init__.py index 38a6f0937..1d0ba2e1e 100644 --- a/src/stk/molecular/topology_graphs/topology_graph/optimizers/__init__.py +++ b/src/stk/molecular/topology_graphs/topology_graph/optimizers/__init__.py @@ -2,4 +2,5 @@ from .optimizer import * # noqa from .mchammer import * # noqa from .collapser import * # noqa +from .periodic_collapser import * # noqa from .utilities import * # noqa diff --git a/src/stk/molecular/topology_graphs/topology_graph/optimizers/collapser.py b/src/stk/molecular/topology_graphs/topology_graph/optimizers/collapser.py index 2418b3d61..d872aea22 100644 --- a/src/stk/molecular/topology_graphs/topology_graph/optimizers/collapser.py +++ b/src/stk/molecular/topology_graphs/topology_graph/optimizers/collapser.py @@ -18,8 +18,8 @@ class Collapser(Optimizer): -------- *Structure Optimization* - Using :class:`.MCHammer` will lead to :class:`.ConstructedMolecule` - structures without long bonds. + Using :class:`.Collapser` will lead to + :class:`.ConstructedMolecule` structures without long bonds. .. code-block:: python diff --git a/src/stk/molecular/topology_graphs/topology_graph/optimizers/optimizer.py b/src/stk/molecular/topology_graphs/topology_graph/optimizers/optimizer.py index fcbe8062b..7fa481a3a 100644 --- a/src/stk/molecular/topology_graphs/topology_graph/optimizers/optimizer.py +++ b/src/stk/molecular/topology_graphs/topology_graph/optimizers/optimizer.py @@ -7,6 +7,11 @@ Collapser <\ stk.molecular.topology_graphs.topology_graph.optimizers.collapser\ +> + + Periodic Collapser <\ +stk.molecular.topology_graphs.topology_graph.optimizers.\ +periodic_collapser\ > MCHammer <\ diff --git a/src/stk/molecular/topology_graphs/topology_graph/optimizers/periodic_collapser.py b/src/stk/molecular/topology_graphs/topology_graph/optimizers/periodic_collapser.py new file mode 100644 index 000000000..d09379ab8 --- /dev/null +++ b/src/stk/molecular/topology_graphs/topology_graph/optimizers/periodic_collapser.py @@ -0,0 +1,130 @@ +""" +Periodic Collapser +================== + +""" + +from .optimizer import Optimizer +from .utilities import get_mch_bonds, get_long_bond_ids, get_subunits + +import mchammer as mch + + +class PeriodicCollapser(Optimizer): + """ + Performs rigid-body collapse of molecules [1]_. + + This :class:`.Optimizer` will also update the `.PeriodicInfo`. + + Examples + -------- + *Structure Optimization* + + Using :class:`.PeriodicCollapser` will lead to + :class:`.ConstructedMolecule` structures without long bonds and + match the unit-cell to the new structure. + + .. code-block:: python + + import stk + + bb1 = stk.BuildingBlock('BrCCBr', [stk.BromoFactory()]) + bb2 = stk.BuildingBlock('BrCC(CBr)CBr', [stk.BromoFactory()]) + + topology_graph = stk.cof.PeriodicHoneycomb( + building_blocks=(bb1, bb2), + lattice_size=(1, 2, 3), + optimizer=stk.PeriodicCollapser(), + ) + cof = stk.ConstructedMolecule(topology_graph) + periodic_info = topology_graph.get_periodic_info() + stk.PdbWriter().write( + molecule=cof, + path='temp.pdb', + periodic_info=periodic_info, + ) + + Optimisation with :mod:`stk` simply collects the final position + matrix and periodic info. The optimisation's trajectory can be + output using the :mod:`MCHammer` implementation if required by the + user [1]_. + + The open-source optimization code :mod:`MCHammer` specializes in + the `collapsing` of molecules with long bonds like those + constructed by :mod:`stk`. This code is entirely nonphysical and + is, therefore, completely general to any chemistry. + + References + ---------- + .. [1] https://github.com/andrewtarzia/MCHammer + + """ + + def __init__( + self, + step_size=0.1, + distance_threshold=1.5, + scale_steps=False, + ): + """ + Initialize an instance of :class:`.PeriodicCollapser`. + + Parameters + ---------- + step_size : :class:`float`, optional + The relative size of the step to take during collapse in + Angstrom. + + distance_threshold : :class:`float`, optional + Distance between distinct building blocks to use as + threshold for halting collapse in Angstrom. + + scale_steps : :class:`bool`, optional + Whether to scale the step of each distinct building block + by its relative distance from the molecules centroid. + + """ + + self._optimizer = mch.Collapser( + step_size=step_size, + distance_threshold=distance_threshold, + scale_steps=scale_steps, + ) + + def optimize(self, state): + mch_mol = mch.Molecule( + atoms=( + mch.Atom( + id=atom.get_id(), + element_string=atom.__class__.__name__, + ) for atom in state.get_atoms() + ), + bonds=get_mch_bonds(state), + position_matrix=state.get_position_matrix(), + ) + + mch_mol, result = self._optimizer.get_result( + mol=mch_mol, + bond_pair_ids=tuple(get_long_bond_ids(state)), + subunits=get_subunits(state), + ) + + old_pos_mat = state.get_position_matrix() + new_pos_mat = mch_mol.get_position_matrix() + old_extents = ( + abs(max(old_pos_mat[:, i])-min(old_pos_mat[:, i])) + for i in range(3) + ) + new_extents = ( + abs(max(new_pos_mat[:, i])-min(new_pos_mat[:, i])) + for i in range(3) + ) + ratios = (n/o for n, o in zip(new_extents, old_extents)) + old_lattice = state.get_lattice_constants() + new_lattice = tuple( + old_lattice[i]*ratio for i, ratio in enumerate(ratios) + ) + state = state.with_lattice_constants(new_lattice) + return state.with_position_matrix( + position_matrix=mch_mol.get_position_matrix() + ) diff --git a/src/stk/molecular/topology_graphs/topology_graph/topology_graph/topology_graph.py b/src/stk/molecular/topology_graphs/topology_graph/topology_graph/topology_graph.py index 14ab12f9b..ef95e69c6 100644 --- a/src/stk/molecular/topology_graphs/topology_graph/topology_graph/topology_graph.py +++ b/src/stk/molecular/topology_graphs/topology_graph/topology_graph/topology_graph.py @@ -404,6 +404,24 @@ def construct(self): state = self._place_building_blocks(state) state = self._run_reactions(state) state = self._optimizer.optimize(state) + return self._get_construction_result(state) + + def _get_construction_result(self, state): + """ + Get the result of the construction. + + Parameters + ---------- + state : :class:`.ConstructionState` + The state of the molecule being constructed. + + Returns + ------- + :class:`.ConstructionResult` + The data describing the :class:`.ConstructedMolecule`. + + """ + return ConstructionResult(state) def _get_construction_state(self): diff --git a/src/stk/molecular/writers/pdb.py b/src/stk/molecular/writers/pdb.py index 5b31c78fc..3192d842d 100644 --- a/src/stk/molecular/writers/pdb.py +++ b/src/stk/molecular/writers/pdb.py @@ -31,7 +31,7 @@ class PdbWriter: writer = stk.PdbWriter() writer.write( molecule=cof, - file='cof.pdb', + path='cof.pdb', periodic_info=topology_graph.get_periodic_info() ) diff --git a/tests/molecular/molecules/constructed_molecule/fixtures/cof.py b/tests/molecular/molecules/constructed_molecule/fixtures/cof.py index 5c4511894..5732e2c3e 100644 --- a/tests/molecular/molecules/constructed_molecule/fixtures/cof.py +++ b/tests/molecular/molecules/constructed_molecule/fixtures/cof.py @@ -92,6 +92,72 @@ def __init__( } self.building_blocks = building_blocks + @classmethod + def init_from_construction_result( + cls, + topology_graph, + building_blocks, + lattice_size, + vertex_alignments, + num_new_atoms, + num_new_bonds, + num_building_blocks, + ): + """ + Initialize a :class:`.CofData` instance. + + This method creates the constructed molecule using + :meth:`.ConstructedMolecule.init_from_construction_result`. + + Parameters + ---------- + topology_graph : :class:`type` + A COF class. + + building_blocks : :class:`tuple` of :class:`.BuildingBlock` + The building blocks of the COF. + + lattice_size : :class:`tuple` of :class:`int` + The size of the lattice. + + vertex_alignments : :class:`dict` + Passed to the `vertex_alignments` parameter of the COF + initializer. + + num_new_atoms : :class:`int` + The number of new atoms added by the construction process. + + num_new_bonds : :class:`int` + The number of new bonds added by the construction process. + + num_building_blocks : :class:`dict` + For each building block in `building_blocks`, maps its + index to the number of times its used in the construction + of the COF. + + """ + + obj = cls.__new__(cls) + topology_graph_instance = topology_graph( + building_blocks=building_blocks, + lattice_size=lattice_size, + vertex_alignments=vertex_alignments, + ) + construction_result = topology_graph_instance.construct() + obj.constructed_molecule = ( + stk.ConstructedMolecule.init_from_construction_result( + construction_result=construction_result, + ) + ) + obj.num_new_atoms = num_new_atoms + obj.num_new_bonds = num_new_bonds + obj.num_building_blocks = { + building_blocks[index]: num + for index, num in num_building_blocks.items() + } + obj.building_blocks = building_blocks + return obj + @pytest.fixture( params=( @@ -115,6 +181,26 @@ def __init__( num_new_bonds=20, num_building_blocks={0: 8, 1: 12}, ), + CofData.init_from_construction_result( + topology_graph=stk.cof.Honeycomb, + building_blocks=( + stk.BuildingBlock( + smiles=( + 'Br[C+]1[C+2][C+](Br)[C+](F)[C+](Br)[C+2]1' + ), + functional_groups=[stk.BromoFactory()], + ), + stk.BuildingBlock( + smiles='Br[C+]=NC#CBr', + functional_groups=[stk.BromoFactory()], + ), + ), + lattice_size=(2, 2, 1), + vertex_alignments=None, + num_new_atoms=0, + num_new_bonds=20, + num_building_blocks={0: 8, 1: 12}, + ), CofData( topology_graph=stk.cof.Honeycomb, building_blocks=( diff --git a/tests/molecular/molecules/molecule/fixtures/cof/periodic_hexagonal.py b/tests/molecular/molecules/molecule/fixtures/cof/periodic_hexagonal.py index cb0545817..b8c78fdfd 100644 --- a/tests/molecular/molecules/molecule/fixtures/cof/periodic_hexagonal.py +++ b/tests/molecular/molecules/molecule/fixtures/cof/periodic_hexagonal.py @@ -95,6 +95,96 @@ ']N81' ), ), + CaseData( + molecule=stk.ConstructedMolecule( + topology_graph=stk.cof.PeriodicHexagonal( + building_blocks={ + stk.BuildingBlock( + smiles='BrC1=C(Br)[C+]=N1', + functional_groups=[stk.BromoFactory()], + ): ( + 4, 5, 6, 7, 8, 9, 20, 21, 23, 24, 30, 36, + 38, 40, 41, 42, 43, 46, 47, 52, 53, 60, 61, + ), + stk.BuildingBlock( + smiles='BrN1N(Br)[C+]=N1', + functional_groups=[stk.BromoFactory()], + ): ( + 10, 11, 12, 13, 14, 15, 22, 25, 26, 27, 28, + 29, 37, 39, 44, 45, 54, 55, 56, 57, 58, 59, + 31, 62, 63, + ), + stk.BuildingBlock( + smiles=( + 'Br[C+]1[C+]2[N+][C+2]C2(Br)[C+](I)[C+' + '](I)[C+](Br)[C+]1Br' + ), + functional_groups=[ + stk.BromoFactory(), + stk.IodoFactory(), + stk.FluoroFactory(), + ], + ): (0, 1, 18, 50, 51), + stk.BuildingBlock( + smiles=( + 'Br[C+]1[C+]2[S][C+2]C2(Br)[C+](I)[C+]' + '(I)[C+](Br)[C+]1Br' + ), + functional_groups=[ + stk.BromoFactory(), + stk.IodoFactory(), + stk.FluoroFactory(), + ], + ): (2, 16, 34, 49), + stk.BuildingBlock( + smiles=( + 'Br[C+]1[C+]2[S][O]C2(Br)[C+](I)[C+](I' + ')[C+](Br)[C+]1Br' + ), + functional_groups=[ + stk.BromoFactory(), + stk.IodoFactory(), + stk.FluoroFactory(), + ], + ): (3, 17, 19, 32, 33, 35, 48), + }, + lattice_size=(2, 2, 1), + vertex_alignments={0: 5}, + optimizer=stk.PeriodicCollapser(), + ), + ), + smiles=( + '[C+]1=NC2=C1[C+]1[C+]3[C+]4C5=C(N=[C+]5)[C+]5[C+]6[C+' + ']7[C+]8[C+]9C%10=C(N=[C+]%10)[C+]%10[C+]%11C%12=C([C+' + ']=N%12)[C+]%12[C+]%13[C+]%14[C+]%15C%16=C(N=[C+]%16)[' + 'C+]%16[C+]%17C%18=C([C+]=N%18)[C+]%18[C+]([C+]%19[NH2' + '+][C+2]C%19([C+]%19[C+]%20C%21=C(N=[C+]%21)[C+]%21[C+' + ']%22C%23=C(N=[C+]%23)[C+]%23[C+]%24C%25=C([C+]=N%25)[' + 'C+]%25[C+]%26[C+]%27C%28=C([C+]=N%28)C%28%29OS[C+]%28' + '[C+]%28C%30=C(N=[C+]%30)[C+]([C+]%30[C+]%21N%21[C+]=N' + 'N%21[C+]%21[C+]%31C%32=C(N=[C+]%32)C%32%33[C+2][NH2+]' + '[C+]%32[C+]%32C%34=C(N=[C+]%34)[C+]%34[C+]([C+](C%35=' + 'C(N=[C+]%35)C5%35[C+2][NH2+][C+]9%35)[C+]5[C+]9[C+]%3' + '5SOC%35%34C%34=C(N=[C+]%34)[C+]%31[C+]%31C%34=C(N=[C+' + ']%34)C%34%35OS[C+]%34[C+]%34C%36=C(N=[C+]%36)[C+]([C+' + ']%17N%17N=[C+]N%17[C+]%31[C+]%17S[C+2]C%17%21N%17N=[C' + '+]N%17[C+]%18%20)[C+]%17SOC%17%18[C+]%16C%16=C([C+]=N' + '%16)[C+]%16[C+]%17C%20=C([C+]=N%20)C%20%21OS[C+]%20[C' + '+]%20C%31=C(N=[C+]%31)[C+]%31[C+]4N4[C+]=NN4[C+]([C+]' + '%24N4N=[C+]N4[C+]%20[C+]4[C+](C%20=C(N=[C+]%20)[C+]%3' + '4[C+]%20[C+]([C+]%35N%24N=[C+]N9%24)N9[C+]=NN9[C+]([C' + '+]%10N9N=[C+]N59)[C+](N5N=[C+]N5[C+]%27[C+](N5N=[C+]N' + '%205)C5([C+2][NH2+][C+]%255)N5N=[C+]N45)C4(OS[C+]%114' + ')N4[C+]=NN4[C+]%29[C+]([C+]4[C+]%28N5[C+]=NN5[C+]([C+' + '](C5=C([C+]=N5)[C+]([C+]%17C5=C(N=[C+]5)C%315[C+2][NH' + '2+][C+]15)[C+]1S[C+2]C1([C+]%16N1N=[C+]N%141)N1N=[C+]' + 'N41)[C+]2%32)[C+]%33N1N=[C+]N%301)N1[C+]=NN%131)[C+]%' + '21N1N=[C+]N1%18)[C+](N1N=[C+]N61)C1([C+2]S[C+]%231)N1' + '[C+]=NN%191)N1[C+]=NN31)C1(OS[C+]%221)N1N=[C+]N%261)N' + '1[C+]=NN71)N1[C+]=NN1[C+]%15C1([C+2]S[C+]%121)N1N=[C+' + ']N81' + ), + ), ), ) def cof_periodic_hexagonal(request): diff --git a/tests/molecular/molecules/molecule/fixtures/cof/periodic_honeycomb.py b/tests/molecular/molecules/molecule/fixtures/cof/periodic_honeycomb.py index dc124dc4f..b4051855f 100644 --- a/tests/molecular/molecules/molecule/fixtures/cof/periodic_honeycomb.py +++ b/tests/molecular/molecules/molecule/fixtures/cof/periodic_honeycomb.py @@ -22,7 +22,41 @@ functional_groups=[stk.BromoFactory()], ), ), - lattice_size=(2, 2, 1)), + lattice_size=(2, 2, 1), + ), + ), + smiles=( + '[H]C12[C+]=NC13C1=C(N=[C+]1)C14N=[C+]C1([H])[C+]1[C+2' + '][C+](C5=C([C+]=N5)[C+]5[C+2][C+]6C7=C(N=[C+]7)[C+]7[' + 'C+2][C+]8C9=C([C+]=N9)[C+]9[C+2][C+](C%10=C1N=[C+]%10' + ')C1([H])[C+]=NC1(C1=C(N=[C+]1)C1%10N=[C+]C1([H])[C+]1' + '[C+2][C+](C%11=C([C+]=N%11)[C+]%11[C+2][C+](C%12=C(N=' + '[C+]%12)[C+]%12[C+2][C+](C%13=C([C+]=N%13)[C+]([C+2][' + 'C+]2C2=C1N=[C+]2)[C+]3F)[C+](F)C1(N=[C+]C%121[H])C1=C' + '([C+]=N1)C1(N=[C+]C61[H])[C+]5F)C1([H])[C+]=NC1(C1=C(' + 'N=[C+]1)C1(N=[C+]C71[H])[C+]8F)[C+]%11F)[C+]%10F)[C+]' + '9F)[C+]4F' + ), + ), + CaseData( + molecule=stk.ConstructedMolecule( + topology_graph=stk.cof.PeriodicHoneycomb( + building_blocks=( + stk.BuildingBlock( + smiles='BrC1=C(Br)[C+]=N1', + functional_groups=[stk.BromoFactory()], + ), + stk.BuildingBlock( + smiles=( + 'Br[C+]1C2[C+]=NC2(Br)[C+](F)[C+](Br)' + '[C+2]1' + ), + functional_groups=[stk.BromoFactory()], + ), + ), + lattice_size=(2, 2, 1), + optimizer=stk.PeriodicCollapser(), + ), ), smiles=( '[H]C12[C+]=NC13C1=C(N=[C+]1)C14N=[C+]C1([H])[C+]1[C+2' diff --git a/tests/molecular/molecules/molecule/fixtures/cof/periodic_kagome.py b/tests/molecular/molecules/molecule/fixtures/cof/periodic_kagome.py index 9e6944d94..debfb76a3 100644 --- a/tests/molecular/molecules/molecule/fixtures/cof/periodic_kagome.py +++ b/tests/molecular/molecules/molecule/fixtures/cof/periodic_kagome.py @@ -44,6 +44,45 @@ '211)C1=C3[C+]=N1)C1=C([C+]=N1)C%141[C+]=N[C+]%121' ), ), + CaseData( + molecule=stk.ConstructedMolecule( + topology_graph=stk.cof.PeriodicKagome( + building_blocks=( + stk.BuildingBlock( + smiles='BrC1=C(Br)[C+]=N1', + functional_groups=[stk.BromoFactory()], + ), + stk.BuildingBlock( + smiles=( + 'Br[C+]1C2(Br)[C+]=N[C+]2[C+](Br)[C+](' + 'Br)[C+2]1' + ), + functional_groups=[stk.BromoFactory()], + ), + ), + lattice_size=(2, 2, 1), + optimizer=stk.PeriodicCollapser(), + ), + ), + smiles=( + '[C+]1=NC2=C1[C+]1[C+]3[C+2][C+]4C5=C(N=[C+]5)C56[C+]=' + 'N[C+]5[C+]5C7=C([C+]=N7)[C+]7[C+]8[C+2][C+]9C%10=C(N=' + '[C+]%10)[C+]%10[C+2][C+]%11C%12=C([C+]=N%12)[C+]%12[C' + '+]%13[C+2][C+]%14C%15=C(N=[C+]%15)C%15%16[C+]=N[C+]%1' + '5[C+]%15C%17=C([C+]=N%17)[C+]%17[C+]%18[C+2][C+]%19C%' + '20=C(N=[C+]%20)[C+]%20[C+2][C+]2[C+]2C%21=C([C+]=N%21' + ')[C+]%21[C+]([C+2][C+](C%22=C(N=[C+]%22)[C+]%16[C+2][' + 'C+]%15C%15=C([C+]=N%15)[C+]%15[C+]([C+2][C+](C%16=C(N' + '=[C+]%16)C%10%16[C+]=N[C+]%16[C+]%11C%10=C([C+]=N%10)' + '[C+]%10[C+]([C+2][C+](C%11=C(N=[C+]%11)[C+]6[C+2][C+]' + '5C5=C([C+]=N5)[C+]5[C+]([C+2][C+](C6=C(N=[C+]6)C%206[' + 'C+]=N[C+]26)C2([C+]=N[C+]52)C2=C%18N=[C+]2)C2=C(N=[C+' + ']2)C92[C+]=N[C+]72)C2([C+]=N[C+]%102)C2=C%13[C+]=N2)C' + '2=C([C+]=N2)C42[C+]=N[C+]12)C1([C+]=N[C+]%151)C1=C8N=' + '[C+]1)C1=C(N=[C+]1)C%191[C+]=N[C+]%171)C1([C+]=N[C+]%' + '211)C1=C3[C+]=N1)C1=C([C+]=N1)C%141[C+]=N[C+]%121' + ), + ), ), ) def cof_periodic_kagome(request): diff --git a/tests/molecular/molecules/molecule/fixtures/cof/periodic_linkerless_honeycomb.py b/tests/molecular/molecules/molecule/fixtures/cof/periodic_linkerless_honeycomb.py index 1f30e71ce..113f2636a 100644 --- a/tests/molecular/molecules/molecule/fixtures/cof/periodic_linkerless_honeycomb.py +++ b/tests/molecular/molecules/molecule/fixtures/cof/periodic_linkerless_honeycomb.py @@ -30,6 +30,31 @@ '1[H])[C+]4F)C1([H])[C+]=NC13[C+]8F)[C+]7F' ), ), + CaseData( + molecule=stk.ConstructedMolecule( + topology_graph=stk.cof.PeriodicLinkerlessHoneycomb( + building_blocks=( + stk.BuildingBlock( + smiles=( + 'Br[C+]1C2[C+]=NC2(Br)[C+](F)[C+](Br)' + '[C+2]1' + ), + functional_groups=[stk.BromoFactory()], + ), + ), + lattice_size=(2, 2, 1), + optimizer=stk.PeriodicCollapser(), + ), + ), + smiles=( + '[H]C12[C+]=NC13[C+](F)[C+]1[C+2][C+]2[C+]2[C+2][C+]4[' + 'C+]5[C+2][C+]6[C+]7[C+2][C+]1[C+](F)C1(N=[C+]C71[H])C' + '17N=[C+]C1([H])[C+]1[C+2][C+]([C+]8[C+2][C+]([C+]9[C+' + '2][C+]([C+]%10[C+2][C+]1C1([H])[C+]=NC1([C+]%10F)C1(N' + '=[C+]C61[H])[C+]5F)[C+](F)C1(N=[C+]C91[H])C1(N=[C+]C2' + '1[H])[C+]4F)C1([H])[C+]=NC13[C+]8F)[C+]7F' + ), + ), ), ) def cof_periodic_linkerless_honeycomb(request): diff --git a/tests/molecular/molecules/molecule/fixtures/cof/periodic_square.py b/tests/molecular/molecules/molecule/fixtures/cof/periodic_square.py index e3af4dd3b..b9f78a422 100644 --- a/tests/molecular/molecules/molecule/fixtures/cof/periodic_square.py +++ b/tests/molecular/molecules/molecule/fixtures/cof/periodic_square.py @@ -29,6 +29,30 @@ ')C75F' ), ), + CaseData( + molecule=stk.ConstructedMolecule( + topology_graph=stk.cof.PeriodicSquare( + building_blocks=( + stk.BuildingBlock( + smiles='BrC1=C(Br)[C+]=N1', + functional_groups=[stk.BromoFactory()], + ), + stk.BuildingBlock( + smiles='BrC1=C(Br)C(F)(Br)[C+]1Br', + functional_groups=[stk.BromoFactory()], + ), + ), + lattice_size=(2, 2, 1), + optimizer=stk.PeriodicCollapser(), + ), + ), + smiles=( + 'FC12C3=C(N=[C+]3)C3=C4C5=C([C+]=N5)[C+]5C6=C7C8=C([C+' + ']=N8)[C+]3C4(F)C3=C(N=[C+]3)C3=C1C1=C([C+]=N1)[C+]1C(' + '=C(C4=C([C+]=N4)[C+]32)C1(F)C1=C6N=[C+]1)C1=C([C+]=N1' + ')C75F' + ), + ), ), ) def cof_periodic_square(request): diff --git a/tests/molecular/molecules/molecule/fixtures/position_matrices/cof_periodic_hexagonal1.npy b/tests/molecular/molecules/molecule/fixtures/position_matrices/cof_periodic_hexagonal1.npy new file mode 100644 index 000000000..26f83d60f Binary files /dev/null and b/tests/molecular/molecules/molecule/fixtures/position_matrices/cof_periodic_hexagonal1.npy differ diff --git a/tests/molecular/molecules/molecule/fixtures/position_matrices/cof_periodic_honeycomb1.npy b/tests/molecular/molecules/molecule/fixtures/position_matrices/cof_periodic_honeycomb1.npy new file mode 100644 index 000000000..8a2b42999 Binary files /dev/null and b/tests/molecular/molecules/molecule/fixtures/position_matrices/cof_periodic_honeycomb1.npy differ diff --git a/tests/molecular/molecules/molecule/fixtures/position_matrices/cof_periodic_kagome1.npy b/tests/molecular/molecules/molecule/fixtures/position_matrices/cof_periodic_kagome1.npy new file mode 100644 index 000000000..b598815fa Binary files /dev/null and b/tests/molecular/molecules/molecule/fixtures/position_matrices/cof_periodic_kagome1.npy differ diff --git a/tests/molecular/molecules/molecule/fixtures/position_matrices/cof_periodic_linkerless_honeycomb1.npy b/tests/molecular/molecules/molecule/fixtures/position_matrices/cof_periodic_linkerless_honeycomb1.npy new file mode 100644 index 000000000..a259c6de7 Binary files /dev/null and b/tests/molecular/molecules/molecule/fixtures/position_matrices/cof_periodic_linkerless_honeycomb1.npy differ diff --git a/tests/molecular/molecules/molecule/fixtures/position_matrices/cof_periodic_square1.npy b/tests/molecular/molecules/molecule/fixtures/position_matrices/cof_periodic_square1.npy new file mode 100644 index 000000000..54ebf6a81 Binary files /dev/null and b/tests/molecular/molecules/molecule/fixtures/position_matrices/cof_periodic_square1.npy differ diff --git a/tests/molecular/topology_graphs/conftest.py b/tests/molecular/topology_graphs/conftest.py index e15308f35..4ded15d53 100644 --- a/tests/molecular/topology_graphs/conftest.py +++ b/tests/molecular/topology_graphs/conftest.py @@ -22,15 +22,51 @@ ), ), CaseData( - topology_graph=stk.cof.Honeycomb( + topology_graph=stk.cof.PeriodicHoneycomb( + building_blocks=(bb1, bb2), + lattice_size=(3, 1, 2), + optimizer=stk.PeriodicCollapser(), + ), + cell=( + np.array([109.29499828, 0., 0.]), + np.array([18.21583305, 31.54982284, 0.]), + np.array([0., 0., 210.33234855]) + ), + ), + ), +) +def unscaled_periodic_case(request): + + return request.param + + +@pytest.fixture( + params=( + CaseData( + topology_graph=stk.cof.PeriodicHoneycomb( building_blocks=(bb1, bb2), - lattice_size=(3, 3, 1), - periodic=False, + lattice_size=(3, 1, 2), + ), + cell=( + np.array([109.29499828, 0., 0.]), + np.array([18.21583305, 31.54982284, 0.]), + np.array([0., 0., 210.33234855]) + ), + ), + CaseData( + topology_graph=stk.cof.PeriodicHoneycomb( + building_blocks=(bb1, bb2), + lattice_size=(3, 1, 2), + optimizer=stk.PeriodicCollapser(), + ), + cell=( + np.array([40.32953729, 0., 0.]), + np.array([7.88334589, 13.65395508, 0.]), + np.array([0., 0., 76.68756542]) ), - cell=None, ), ), ) -def periodic_case(request): +def scaled_periodic_case(request): return request.param diff --git a/tests/molecular/topology_graphs/construction_state/utilities.py b/tests/molecular/topology_graphs/construction_state/utilities.py index 196e9e6ed..d2c62a3c5 100644 --- a/tests/molecular/topology_graphs/construction_state/utilities.py +++ b/tests/molecular/topology_graphs/construction_state/utilities.py @@ -72,3 +72,12 @@ def is_clone(construction_state, clone): assert (counts1.keys() | counts2.keys()) == counts1.keys() for building_block, count1 in counts1.items(): assert count1 == counts2[building_block] + + assert all( + np.all(np.equal(actual_constant, expected_constant)) + for actual_constant, expected_constant + in it.zip_longest( + construction_state.get_lattice_constants(), + clone.get_lattice_constants(), + ) + ) diff --git a/tests/molecular/topology_graphs/test_get_periodic_info.py b/tests/molecular/topology_graphs/test_get_periodic_info.py index 249c9190d..61c2dfb59 100644 --- a/tests/molecular/topology_graphs/test_get_periodic_info.py +++ b/tests/molecular/topology_graphs/test_get_periodic_info.py @@ -1,9 +1,10 @@ import numpy as np +import itertools -def test_get_periodic_info(periodic_case): +def test_get_periodic_info(unscaled_periodic_case): """ - Test collection of periodic cell from `.TopologyGraph`. + Test the collection of the periodic cell from a `.TopologyGraph`. Parameters ---------- @@ -17,8 +18,8 @@ def test_get_periodic_info(periodic_case): """ _test_get_periodic_info( - topology_graph=periodic_case.topology_graph, - cell=periodic_case.cell, + topology_graph=unscaled_periodic_case.topology_graph, + cell=unscaled_periodic_case.cell, ) @@ -41,16 +42,41 @@ def _test_get_periodic_info(topology_graph, cell): """ - try: - if not topology_graph._periodic: - assert cell is None - except AttributeError: - # Topology must be a PeriodicClass to access periodic info. - assert hasattr(topology_graph, '_internal') - test_cell = ( - topology_graph.get_periodic_info().get_cell_matrix() + actual_cell = ( + topology_graph.get_periodic_info().get_cell_matrix() + ) + assert np.all(np.array([ + np.allclose(i, j, atol=1e-4) + for i, j in itertools.zip_longest(actual_cell, cell) + ])) + + +def test_get_periodic_info_2(scaled_periodic_case): + """ + Test getting of :class:`.PeriodicInfo`. + + Parameters + ---------- + periodic_case : :class:`.CaseData` + The test case. Includes the topology graph and the expected + cell. + + Returns + ------- + None : :class:`NoneType` + + """ + + construction_result = ( + scaled_periodic_case.topology_graph.construct() + ) + actual_cell = ( + construction_result.get_periodic_info().get_cell_matrix() + ) + assert np.all(np.array([ + np.allclose(i, j, atol=1e-4) + for i, j in itertools.zip_longest( + actual_cell, + scaled_periodic_case.cell, ) - assert np.all(np.array([ - np.allclose(i, j, atol=1e-4) - for i, j in zip(test_cell, cell) - ])) + ]))