Module atompack.topology

The internal abstraction for a network of optionally bonded atoms.

Expand source code
"""The internal abstraction for a network of optionally bonded atoms."""

from typing import List, Optional, Tuple

import orjson
from retworkx import PyGraph

from atompack.atom import Atom
from atompack.bond import Bond


class Topology(object):
    """Internal abstraction for a collection of atoms and bonds.
    
    Note:
        End users should not construct Topology objects directly.
    """

    def __init__(self, graph: Optional[PyGraph] = None) -> None:
        if graph is None:
            graph = PyGraph()
        self._graph = graph

    ######################
    #    Constructors    #
    ######################

    @classmethod
    def from_json(cls, s: str) -> 'Topology':
        """Initializes from a JSON string."""
        # load dict from JSON string
        data = orjson.loads(s)

        # validate type
        _type = data.pop("type")
        if _type != cls.__name__:
            raise TypeError(f"cannot deserialize from type `{_type}`")

        # initialize an empty graph
        graph = PyGraph()

        # process atoms
        for atom in data["atoms"]:
            graph.add_node(Atom.from_json(orjson.dumps(atom)))

        # process bonds
        for bond in data["bonds"]:
            bond = Bond.from_json(orjson.dumps(bond))
            graph.add_edge(bond.indices[0], bond.indices[1], edge=bond)

        # return instance
        return cls(graph)

    ####################
    #    Properties    #
    ####################

    @property
    def atoms(self) -> List[Atom]:
        """Returns a list of all atoms in the topology."""
        return self._graph.nodes()

    @property
    def bonds(self) -> List[Bond]:
        """Returns a list of all bonds in the topology."""
        return self._graph.edges()

    ########################
    #    Public Methods    #
    ########################

    def insert_atoms(self, *atoms: Atom) -> List[int]:
        """Inserts one or more atoms and returns their indices."""
        return list(self._graph.add_nodes_from(atoms))

    def remove_atoms(self, *indices: int) -> List[Atom]:
        """Removes and returns one or more atoms."""
        res = [self._graph.get_node_data(index) for index in indices]
        self._graph.remove_nodes_from(indices)
        return res

    def select_atoms(self, *indices: int) -> List[Atom]:
        """Returns a reference to one or more atoms."""
        return [self._graph.get_node_data(index) for index in indices]

    # TODO: update these upon new retworkx release.

    def insert_bond(self, bond: Bond) -> None:
        """Inserts a bond."""
        self._graph.add_edge(*bond.indices, edge=bond)

    def remove_bond(self, indices: Tuple[int, int]) -> Bond:
        """Removes and returns bonds."""
        res = self._graph.get_edge_data(*indices)
        self._graph.remove_edge(*indices)
        return res

    def select_bond(self, indices: Tuple[int, int]) -> Bond:
        """Returns a mutable reference to a bond."""
        return self._graph.get_edge_data(*indices)

    def to_json(self) -> str:
        """Returns the JSON serialized representation."""
        return orjson.dumps(
            {
                "type": type(self).__name__,
                "atoms": [orjson.loads(atom.to_json()) for atom in self.atoms],
                "bonds": [orjson.loads(bond.to_json()) for bond in self.bonds],
            },
            option=orjson.OPT_SERIALIZE_NUMPY)

Classes

class Topology (graph: Union[retworkx.PyGraph, NoneType] = None)

Internal abstraction for a collection of atoms and bonds.

Note

End users should not construct Topology objects directly.

Expand source code
class Topology(object):
    """Internal abstraction for a collection of atoms and bonds.
    
    Note:
        End users should not construct Topology objects directly.
    """

    def __init__(self, graph: Optional[PyGraph] = None) -> None:
        if graph is None:
            graph = PyGraph()
        self._graph = graph

    ######################
    #    Constructors    #
    ######################

    @classmethod
    def from_json(cls, s: str) -> 'Topology':
        """Initializes from a JSON string."""
        # load dict from JSON string
        data = orjson.loads(s)

        # validate type
        _type = data.pop("type")
        if _type != cls.__name__:
            raise TypeError(f"cannot deserialize from type `{_type}`")

        # initialize an empty graph
        graph = PyGraph()

        # process atoms
        for atom in data["atoms"]:
            graph.add_node(Atom.from_json(orjson.dumps(atom)))

        # process bonds
        for bond in data["bonds"]:
            bond = Bond.from_json(orjson.dumps(bond))
            graph.add_edge(bond.indices[0], bond.indices[1], edge=bond)

        # return instance
        return cls(graph)

    ####################
    #    Properties    #
    ####################

    @property
    def atoms(self) -> List[Atom]:
        """Returns a list of all atoms in the topology."""
        return self._graph.nodes()

    @property
    def bonds(self) -> List[Bond]:
        """Returns a list of all bonds in the topology."""
        return self._graph.edges()

    ########################
    #    Public Methods    #
    ########################

    def insert_atoms(self, *atoms: Atom) -> List[int]:
        """Inserts one or more atoms and returns their indices."""
        return list(self._graph.add_nodes_from(atoms))

    def remove_atoms(self, *indices: int) -> List[Atom]:
        """Removes and returns one or more atoms."""
        res = [self._graph.get_node_data(index) for index in indices]
        self._graph.remove_nodes_from(indices)
        return res

    def select_atoms(self, *indices: int) -> List[Atom]:
        """Returns a reference to one or more atoms."""
        return [self._graph.get_node_data(index) for index in indices]

    # TODO: update these upon new retworkx release.

    def insert_bond(self, bond: Bond) -> None:
        """Inserts a bond."""
        self._graph.add_edge(*bond.indices, edge=bond)

    def remove_bond(self, indices: Tuple[int, int]) -> Bond:
        """Removes and returns bonds."""
        res = self._graph.get_edge_data(*indices)
        self._graph.remove_edge(*indices)
        return res

    def select_bond(self, indices: Tuple[int, int]) -> Bond:
        """Returns a mutable reference to a bond."""
        return self._graph.get_edge_data(*indices)

    def to_json(self) -> str:
        """Returns the JSON serialized representation."""
        return orjson.dumps(
            {
                "type": type(self).__name__,
                "atoms": [orjson.loads(atom.to_json()) for atom in self.atoms],
                "bonds": [orjson.loads(bond.to_json()) for bond in self.bonds],
            },
            option=orjson.OPT_SERIALIZE_NUMPY)

Subclasses

Static methods

def from_json(s: str) ‑> Topology

Initializes from a JSON string.

Expand source code
@classmethod
def from_json(cls, s: str) -> 'Topology':
    """Initializes from a JSON string."""
    # load dict from JSON string
    data = orjson.loads(s)

    # validate type
    _type = data.pop("type")
    if _type != cls.__name__:
        raise TypeError(f"cannot deserialize from type `{_type}`")

    # initialize an empty graph
    graph = PyGraph()

    # process atoms
    for atom in data["atoms"]:
        graph.add_node(Atom.from_json(orjson.dumps(atom)))

    # process bonds
    for bond in data["bonds"]:
        bond = Bond.from_json(orjson.dumps(bond))
        graph.add_edge(bond.indices[0], bond.indices[1], edge=bond)

    # return instance
    return cls(graph)

Instance variables

var atoms : List[Atom]

Returns a list of all atoms in the topology.

Expand source code
@property
def atoms(self) -> List[Atom]:
    """Returns a list of all atoms in the topology."""
    return self._graph.nodes()
var bonds : List[Bond]

Returns a list of all bonds in the topology.

Expand source code
@property
def bonds(self) -> List[Bond]:
    """Returns a list of all bonds in the topology."""
    return self._graph.edges()

Methods

def insert_atoms(self, *atoms: Atom) ‑> List[int]

Inserts one or more atoms and returns their indices.

Expand source code
def insert_atoms(self, *atoms: Atom) -> List[int]:
    """Inserts one or more atoms and returns their indices."""
    return list(self._graph.add_nodes_from(atoms))
def insert_bond(self, bond: Bond) ‑> NoneType

Inserts a bond.

Expand source code
def insert_bond(self, bond: Bond) -> None:
    """Inserts a bond."""
    self._graph.add_edge(*bond.indices, edge=bond)
def remove_atoms(self, *indices: int) ‑> List[Atom]

Removes and returns one or more atoms.

Expand source code
def remove_atoms(self, *indices: int) -> List[Atom]:
    """Removes and returns one or more atoms."""
    res = [self._graph.get_node_data(index) for index in indices]
    self._graph.remove_nodes_from(indices)
    return res
def remove_bond(self, indices: Tuple[int, int]) ‑> Bond

Removes and returns bonds.

Expand source code
def remove_bond(self, indices: Tuple[int, int]) -> Bond:
    """Removes and returns bonds."""
    res = self._graph.get_edge_data(*indices)
    self._graph.remove_edge(*indices)
    return res
def select_atoms(self, *indices: int) ‑> List[Atom]

Returns a reference to one or more atoms.

Expand source code
def select_atoms(self, *indices: int) -> List[Atom]:
    """Returns a reference to one or more atoms."""
    return [self._graph.get_node_data(index) for index in indices]
def select_bond(self, indices: Tuple[int, int]) ‑> Bond

Returns a mutable reference to a bond.

Expand source code
def select_bond(self, indices: Tuple[int, int]) -> Bond:
    """Returns a mutable reference to a bond."""
    return self._graph.get_edge_data(*indices)
def to_json(self) ‑> str

Returns the JSON serialized representation.

Expand source code
def to_json(self) -> str:
    """Returns the JSON serialized representation."""
    return orjson.dumps(
        {
            "type": type(self).__name__,
            "atoms": [orjson.loads(atom.to_json()) for atom in self.atoms],
            "bonds": [orjson.loads(bond.to_json()) for bond in self.bonds],
        },
        option=orjson.OPT_SERIALIZE_NUMPY)