def _add_dodecane_to_topology(self, topology):

        # Carbon element
        carbon_element = Element.getBySymbol('C')
        hydrogen_element = Element.getBySymbol('H')

        chain = topology.addChain("{}-C12".format(topology.getNumChains() + 1))
        residue = topology.addResidue("C12", chain)
        prev_atom = topology.addAtom("C", carbon_element, residue)
        if self.forceField_str == "TraPPE-UA":
            for i in range(11):
                curr_atom = topology.addAtom("C{}".format(i + 1),
                                             carbon_element, residue)
                topology.addBond(prev_atom, curr_atom)
                prev_atom = curr_atom
        else:
            H_counter = 0
            for _ in range(3):
                H = topology.addAtom("H{}".format(H_counter), hydrogen_element,
                                     residue)
                topology.addBond(H, prev_atom)
            for i in range(11):
                curr_atom = topology.addAtom("C{}".format(i + 1),
                                             carbon_element, residue)
                topology.addBond(prev_atom, curr_atom)
                for _ in range(2):
                    H = topology.addAtom("H{}".format(H_counter),
                                         hydrogen_element, residue)
                    topology.addBond(H, curr_atom)
                    prev_atom = curr_atom
            H = topology.addAtom("H{}".format(H_counter), hydrogen_element,
                                 residue)
            topology.addBond(H, prev_atom)

        return chain.id
Exemplo n.º 2
0
def openmm_topology(atoms, bonds):
    '''Make OpenMM topology from ChimeraX atoms and bonds.'''
    a = atoms
    n = len(a)
    r = a.residues
    aname = a.names
    ename = a.element_names
    rname = r.names
    rnum = r.numbers
    cids = r.chain_ids
    from simtk.openmm.app import Topology, Element
    top = Topology()
    cmap = {}
    rmap = {}
    atoms = {}
    for i in range(n):
        cid = cids[i]
        if not cid in cmap:
            cmap[cid] = top.addChain()  # OpenMM chains have no name.
        rid = (rname[i], rnum[i], cid)
        if not rid in rmap:
            rmap[rid] = top.addResidue(rname[i], cmap[cid])
        element = Element.getBySymbol(ename[i])
        atoms[i] = top.addAtom(aname[i], element, rmap[rid])
    a1, a2 = bonds.atoms
    for i1, i2 in zip(a.indices(a1), a.indices(a2)):
        top.addBond(atoms[i1], atoms[i2])
    return top
Exemplo n.º 3
0
def openmm_topology_from_chimerax_model(model):
    '''
  Take an AtomicStructure model from ChimeraX and return an OpenMM
  topology (e.g. for use with the OpenMM Modeller class).
  '''
    a = model.atoms
    b = model.bonds
    n = len(a)
    r = a.residues
    aname = a.names
    ename = a.element_names
    rname = r.names
    rnum = r.numbers
    cids = r.chain_ids
    from simtk.openmm.app import Topology, Element
    from simtk import unit
    top = Topology()
    cmap = {}
    rmap = {}
    atoms = {}
    for i in range(n):
        cid = cids[i]
        if not cid in cmap:
            cmap[cid] = top.addChain()  # OpenMM chains have no name
        rid = (rname[i], rnum[i], cid)
        if not rid in rmap:
            rmap[rid] = top.addResidue(rname[i], cmap[cid])
        element = Element.getBySymbol(ename[i])
        atoms[i] = top.addAtom(aname[i], element, rmap[rid])
    a1, a2 = b.atoms
    for i1, i2 in zip(a.indices(a1), a.indices(a2)):
        if -1 not in [i1, i2]:
            top.addBond(atoms[i1], atoms[i2])
    return top
Exemplo n.º 4
0
    def _apply_init(self, result: ComponentResult) -> None:

        from simtk.openmm.app import Element

        self._cache["elements"] = [
            Element.getBySymbol(ele).atomic_number
            if isinstance(ele, str) else ele for ele in self.allowed_elements
        ]
Exemplo n.º 5
0
    def openmm_topology_and_external_forces(self,
                                            sim_construct,
                                            sim_bonds,
                                            fix_shell_backbones=False,
                                            tug_hydrogens=False,
                                            hydrogens_feel_maps=False,
                                            logging=False,
                                            log=None):
        a = sim_construct
        n = len(a)
        r = a.residues
        aname = a.names
        ename = a.element_names
        rname = r.names
        rnum = r.numbers
        cids = r.chain_ids
        from simtk.openmm.app import Topology, Element
        from simtk import unit
        top = self._simulation_topology = Topology()
        cmap = {}
        rmap = {}
        atoms = self._atoms = {}
        for i in range(n):
            cid = cids[i]
            if not cid in cmap:
                cmap[cid] = top.addChain()  # OpenMM chains have no name
            rid = (rname[i], rnum[i], cid)
            if not rid in rmap:
                rmap[rid] = top.addResidue(rname[i], cmap[cid])
            element = Element.getBySymbol(ename[i])
            atoms[i] = top.addAtom(aname[i], element, rmap[rid])

            # Register atoms with forces
            if ename is not 'H' or (ename is 'H' and tug_hydrogens):
                # All CustomExternalForces
                for key, ff in self._custom_external_forces.items():
                    f = ff[0]
                    per_particle_param_vals = ff[3]
                    index_map = ff[4]
                    index_map[i] = f.addParticle(i, per_particle_param_vals)

            if ename is not 'H' or (ename is 'H' and hydrogens_feel_maps):
                # All map forces
                for m in self._maps:
                    self.couple_atom_to_map(i, m)

        a1, a2 = sim_bonds.atoms
        for i1, i2 in zip(a.indices(a1), a.indices(a2)):
            if -1 not in [i1, i2]:
                top.addBond(atoms[i1], atoms[i2])

        from simtk.openmm import Vec3
        pos = a.coords  # in Angstrom (convert to nm for OpenMM)
        return top, pos
Exemplo n.º 6
0
    def check_allowed_elements(cls, element: Union[str,
                                                   int]) -> Union[str, int]:
        """
        Check that each item can be cast to a valid element.

        Parameters:
            element: The element that should be checked.

        Raises:
            ValueError: If the element number or symbol passed could not be converted into a valid element.
        """
        from simtk.openmm.app import Element

        if isinstance(element, int):
            return element
        else:
            try:
                _ = Element.getBySymbol(element)
                return element
            except KeyError:
                raise KeyError(
                    f"An element could not be determined from symbol {element}, please enter symbols only."
                )
Exemplo n.º 7
0
class BranchedChainOptions(ChainOptions):

    _SECTION_NAME = "BranchedChain"

    # =========================================================================

    PHOSPHORUS = Element.getBySymbol('P')

    # =========================================================================

    def __init__(self, topology_options):
        super(BranchedChainOptions, self).__init__(topology_options)
        self.backbone = None
        self.branches = []
        self.pdb = None

    def _create_options(self):
        super(BranchedChainOptions, self)._create_options()
        self._OPTIONS['backbone'] = self._parse_backbone

    def _create_sections(self):
        super(BranchedChainOptions, self)._create_sections()
        self._SECTIONS['Branch'] = self._parse_branch

    # =========================================================================

    def _check_for_incomplete_input(self):
        if self.backbone is None:
            self._incomplete_error('backbone')

    # =========================================================================

    def _parse_backbone(self, *args):
        self.backbone = evaluate_output(create_stack(tokenize_expr(args[0])))

    def _parse_branch(self, *args):
        line_deque = args[1]
        branch_options = BranchOptions()
        branch_options.parse(line_deque.popleft())
        self.branches.append((branch_options.sequence, branch_options.indices))

    # =========================================================================

    def add_chain_to_topology(self, topology):
        for _ in range(self.num):
            self._add_chain(topology)

    @staticmethod
    def _find_atom_by_name(name, residue):
        for atom in residue.atoms():
            if atom.name == name:
                return atom
        raise ValueError("No atom with name {} found.".format(name))

    def _add_chain(self, topology):

        # create chain
        chain = topology.addChain()

        # determine residue adding function
        if self.forceField_str == 'TraPPE-UA':
            add_residue_function = self._add_residue_trappeua
        elif self.forceField_str == 'OPLS-AA':
            add_residue_function = self._add_residue_to_chain_oplsaa
        else:
            raise ValueError("Invalid force field.")

        # initialize previous residue atom
        prev_res_atom = None

        # add backbone
        for i, monomer in enumerate(self.backbone):

            # determine whether or not the residue is a terminal one
            left_ter = False
            right_ter = False
            if i == 0:
                left_ter = True
            if i == len(self.backbone) - 1:
                right_ter = True

            # add residue
            prev_res_atom = add_residue_function(topology, chain,
                                                 prev_res_atom, monomer,
                                                 left_ter, right_ter)

        # add branches
        for sequence, indices in self.branches:
            residues = list(chain.residues())
            for index in indices:
                atom = self._find_atom_by_name('C1', residues[index])
                atom.element = self.PHOSPHORUS
                prev_res_atom = atom
                for i, monomer in enumerate(sequence):
                    right_ter = i == len(sequence) - 1
                    prev_res_atom = add_residue_function(topology,
                                                         chain,
                                                         prev_res_atom,
                                                         monomer,
                                                         right_ter=right_ter)

        # create pdb file
        if self.pdb is None:
            positions = self._create_positions_trappeua()
            topology_pdb = Topology()
            topology_pdb._chains.append(chain)
            bc_num = 0
            dirname = os.path.join(os.path.dirname(__file__), "data")
            while os.path.exists(
                    os.path.join(dirname,
                                 "branched_chain_{}.pdb".format(bc_num))):
                bc_num += 1
            self.pdb = os.path.join(dirname,
                                    "branched_chain_{}.pdb".format(bc_num))
            PDBFile.writeFile(topology_pdb, positions, open(self.pdb, 'w'))

    def _create_positions_trappeua(self):

        positions = None

        # load positions for mA12
        mA12_pos = self._mA12_positions_trappeua()

        # initialize list mapping residue number to atom index
        res2atom = []

        # number of atoms in each monomer type
        Natoms_dict = {'A4': 9, 'A12': 17, 'mA12': 18}

        # add backbone positions
        for i, monomer in enumerate(self.backbone):

            # add atom indices to res2atom
            if res2atom:
                res2atom.append(
                    np.arange(Natoms_dict[monomer]) + res2atom[-1][-1] + 1)
            else:
                res2atom.append(np.arange(Natoms_dict[monomer]))

            # create monomer positions
            pos = mA12_pos[:]
            if monomer.startswith('A'):
                pos = np.delete(pos, 2, axis=0)
            if monomer.endswith('4'):
                pos = pos[:-8]

            # add monomer positions to chain positions
            if positions is None:
                positions = pos
            else:
                cos19 = np.cos(np.deg2rad(19.5))
                cos30 = np.cos(np.deg2rad(30.0))
                pos += 1.54 * np.array([cos19 * cos30, 0., 0.]) + np.array([
                    positions[res2atom[i - 1][1]][0], 0., 0.
                ]) - np.array([mA12_pos[0][0], 0., 0.])
                positions = np.vstack((positions, pos))

        # rotate methacrylate monomer positions
        Rz = np.array([[0., 1., 0.], [-1., 0., 0.], [0., 0., 1.]])
        mA12_pos_rot = mA12_pos.dot(Rz)

        # add side chain positions
        for sequence, indices in self.branches:
            for res_index in indices:
                prev_res_pos = positions[res2atom[res_index][1]]
                for monomer in sequence:

                    # create monomer positions
                    pos = mA12_pos_rot[:] + np.array([
                        positions[res2atom[res_index][1]][0] -
                        mA12_pos_rot[1][0], 0., 0.
                    ])
                    if monomer.startswith('A'):
                        pos = np.delete(pos, 2, axis=0)
                    if monomer.endswith('4'):
                        pos = pos[:-8]

                    # add monomer positions
                    cos19 = np.cos(np.deg2rad(19.5))
                    cos30 = np.cos(np.deg2rad(30.0))
                    pos += 1.54 * np.array([0., cos19 * cos30, 0.]) + np.array(
                        [0., prev_res_pos[1], 0.]) - np.array(
                            [0., pos[0][1], 0.])
                    positions = np.vstack((positions, pos))

                    prev_res_pos = pos[1]

        return positions

    @staticmethod
    def _mA12_positions_trappeua():

        # sines and cosines
        sin19 = np.sin(np.deg2rad(19.5))
        cos19 = np.cos(np.deg2rad(19.5))
        sin30 = np.sin(np.deg2rad(30.0))
        cos30 = np.cos(np.deg2rad(30.0))
        sin54 = np.sin(np.deg2rad(54.75))
        cos54 = np.cos(np.deg2rad(54.75))

        # methacrylate positions
        pos = 1.54 * np.array([cos19 * cos30, -cos19 * sin30, sin19])
        pos = np.vstack(
            (pos,
             pos + 1.54 * np.array([cos19 * cos30, cos19 * sin30, -sin19])))
        pos = np.vstack((pos, pos[1] + 1.54 * np.array([0., cos19, sin19])))
        pos = np.vstack((pos, pos[1] + 1.52 * np.array([0., 0., -1.])))
        pos = np.vstack((pos, pos[3] + 1.20 * np.array([0., cos30, -sin30])))
        pos = np.vstack((pos, pos[3] + 1.344 * np.array([0., -cos30, -sin30])))

        # C12 tail positions
        for i in range(12):
            if i == 0:
                pos = np.vstack(
                    (pos, pos[-1] +
                     1.41 * np.array([0.0, (-1)**(i + 1) * cos54, -sin54])))
            else:
                pos = np.vstack(
                    (pos, pos[-1] +
                     1.54 * np.array([0.0, (-1)**(i + 1) * cos54, -sin54])))

        return pos

    def _add_residue_trappeua(self,
                              topology,
                              chain,
                              prev_res_atom,
                              monomer,
                              left_ter=False,
                              right_ter=False):

        # determine monomer type
        if monomer.startswith('mA'):
            monomer_type = 'mA'
            is_methyl = True
        else:
            monomer_type = 'A'
            is_methyl = False

        # determine chain length
        end_chain_length = literal_eval(monomer.replace(monomer_type, ''))

        # add residue to topology
        residue = topology.addResidue(monomer, chain)

        # add first two carbons
        if left_ter and right_ter:
            c_element = self.NITROGEN
            c1_element = self.NITROGEN
        elif left_ter:
            c_element = self.BORON
            c1_element = self.CARBON
        elif right_ter:
            c_element = self.NITROGEN
            c1_element = self.CARBON
        else:
            c_element = self.CARBON
            c1_element = self.CARBON
        c = topology.addAtom('C', c_element, residue)
        c1 = topology.addAtom('C1', c1_element, residue)

        # add bond to previous residue if applicable
        if prev_res_atom is not None:
            topology.addBond(prev_res_atom, c)

        # add bond between first two carbons
        topology.addBond(c, c1)

        # add methyl group if methacryalte
        if is_methyl:
            cm = topology.addAtom('Cm', self.CARBON, residue)
            topology.addBond(c1, cm)

        # add ester group atoms
        c2 = topology.addAtom('C2', self.CARBON, residue)
        topology.addBond(c1, c2)
        oc = topology.addAtom('O', self.OXYGEN, residue)
        topology.addBond(c2, oc)
        oe = topology.addAtom('O1', self.OXYGEN, residue)
        topology.addBond(c2, oe)

        # add carbon chain
        prev_atom = oe
        for i in range(end_chain_length):
            curr_atom = topology.addAtom('C{}'.format(i + 3), self.CARBON,
                                         residue)
            topology.addBond(prev_atom, curr_atom)
            prev_atom = curr_atom

        return c1

    def _add_residue_oplsaa(self,
                            topology,
                            chain,
                            prev_res_atom,
                            monomer,
                            left_ter=False,
                            right_ter=False):
        raise NotImplementedError(
            "OPLS-AA not implemented for branched chain.")
Exemplo n.º 8
0
class ChainOptions(_Options):

    _SECTION_NAME = "Chain"

    # =========================================================================

    HYDROGEN = Element.getBySymbol('H')
    BORON = Element.getBySymbol('B')
    CARBON = Element.getBySymbol('C')
    NITROGEN = Element.getBySymbol('N')
    OXYGEN = Element.getBySymbol('O')

    # =========================================================================

    def __init__(self, topology_options):
        super(ChainOptions, self).__init__()
        self.topology_options = topology_options
        self.forceField_str = topology_options.forceField_str
        self.id = None
        self.num = 1
        self.sequence = None
        self.create_pdb = True
        self.overwrite_pdb = True
        self.instructions = None
        self.sequence_str = None

    def _create_options(self):
        super(ChainOptions, self)._create_options()
        self._OPTIONS['id'] = self._parse_id
        self._OPTIONS['num'] = self._parse_num
        self._OPTIONS['sequence'] = self._parse_sequence
        self._OPTIONS['createPDB'] = self._parse_create_pdb
        self._OPTIONS['overwritePDB'] = self._parse_overwrite_pdb
        self._OPTIONS['instructions'] = self._parse_instructions

    # =========================================================================

    def _check_for_incomplete_input(self):
        if self.sequence is None:
            self._incomplete_error('structure')

    # =====================================================================[====

    def _parse_id(self, *args):
        self.id = args[0]

    def _parse_num(self, *args):
        self.num = literal_eval(args[0])

    def _parse_sequence(self, *args):
        structure_parser = _StructureParser(args[0])
        self.sequence = structure_parser.get_chain_sequence()
        self.sequence_str = str(structure_parser)

    def _parse_create_pdb(self, *args):
        self.create_pdb = literal_eval(args[0])

    def _parse_overwrite_pdb(self, *args):
        self.overwrite_pdb = literal_eval(args[0])

    def _parse_instructions(self, *args):
        self.instructions = [
            instruction.strip() for instruction in args[0].split('/')
        ]

    # =========================================================================

    def add_chain_to_topology(self, topology):

        # Map chain id to sequence
        id_to_sequence = {}

        # Add specified number of chains
        for _ in range(self.num):

            # Initialize topology and positions array for creating chain pdb
            topology_pdb = Topology()
            positions = []

            # Determine chain pdb
            if self.id is not None:
                chain_id = "{}-{}".format(topology.getNumChains() + 1, self.id)
            else:
                chain_id = "{}-{}".format(topology.getNumChains() + 1,
                                          self.sequence_str)

            # Create chain
            chain = topology.addChain(id=chain_id)
            chain_pdb = topology_pdb.addChain(id=chain_id)

            # Add chain id and sequence to dictionary
            id_to_sequence[chain.id] = self.sequence_str

            # Initialize atom from previous residue
            prev_res_atom = None
            prev_res_atom_pdb = None

            # Initialize pos of atom on previous residue
            prev_res_atom_pos = np.array([0.0, 0.0, 0.0])

            # Iterate through sequence to add residues to chain
            for j in range(len(self.sequence)):

                # String representation of monomer
                monomer = self.sequence[j]

                # Determine whether or not the residue is a terminal one
                left_ter = False
                right_ter = False
                if j == 0:
                    left_ter = True
                if j == len(self.sequence) - 1:
                    right_ter = True

                # Add residue to chain
                if self.forceField_str == 'TraPPE-UA':
                    add_residue_function = self._add_residue_to_chain_trappeua
                elif self.forceField_str == 'OPLS-AA':
                    add_residue_function = self._add_residue_to_chain_oplsaa
                else:
                    raise ValueError("Invalid force field.")
                prev_res_atom, prev_res_atom_pdb, prev_res_atom_pos = add_residue_function(
                    topology, topology_pdb, chain, chain_pdb, prev_res_atom,
                    prev_res_atom_pdb, positions, prev_res_atom_pos, monomer,
                    left_ter, right_ter)

            # Create pdb file for chain
            if self.create_pdb:
                self._create_chain_pdb(topology_pdb, positions)

        return id_to_sequence

    def _add_residue_to_chain_oplsaa(self,
                                     topology,
                                     topology_pdb,
                                     chain,
                                     chain_pdb,
                                     prev_res_atom,
                                     prev_res_atom_pdb,
                                     positions,
                                     prev_res_atom_pos,
                                     monomer,
                                     left_ter=False,
                                     right_ter=False):
        # Determine monomer type
        if monomer.startswith('mA'):
            monomer_type = 'mA'
            is_methyl = True
        else:
            monomer_type = 'A'
            is_methyl = False

        # Determine chain length
        end_chain_length = literal_eval(monomer.replace(monomer_type, ''))

        # Determine residue id
        if left_ter:
            residue_id = "LEFTTER"
        elif right_ter:
            residue_id = "RIGHTTER"
        else:
            residue_id = None

        # Add residue to topology
        residue = topology.addResidue(monomer, chain, id=residue_id)
        residue_pdb = topology_pdb.addResidue(monomer,
                                              chain_pdb,
                                              id=residue_id)

        # Function to add atoms to topology
        def _add_atom_to_topology_oplsaa(name, element, counter):
            # Add atom to topology
            name = "{}{}".format(name, counter)
            atom = topology.addAtom(name, element, residue)
            atom_pdb = topology_pdb.addAtom(name, element, residue_pdb)

            # Return atoms and position
            return atom, atom_pdb, counter + 1

        # Import positions from pdb
        residue_positions = md.load(
            os.path.join(self.topology_options.data_directory,
                         "mA12_aa.pdb")).xyz[0] * 10. + prev_res_atom_pos
        residue_positions = list(residue_positions)
        if end_chain_length < 12:
            residue_positions = residue_positions[:-(12 - end_chain_length) *
                                                  3]
        if not is_methyl:
            residue_positions.pop(9)
            residue_positions.pop(8)
            residue_positions.pop(7)
        if not right_ter or (left_ter and right_ter):
            residue_positions.pop(5)
        if (left_ter and right_ter) or (not left_ter):
            residue_positions.pop(2)
        positions += residue_positions

        # Add first carbon and attached hydrogen atoms
        C_counter = 0
        H_counter = 0
        C0, C0_pdb, C_counter = _add_atom_to_topology_oplsaa(
            "C", self.CARBON, C_counter)
        if left_ter and not right_ter:
            num_H = 3
        else:
            num_H = 2
        for _ in range(num_H):
            H, H_pdb, H_counter = _add_atom_to_topology_oplsaa(
                "H", self.HYDROGEN, H_counter)
            self._add_bond_to_topology(H, H_pdb, C0, C0_pdb, topology,
                                       topology_pdb)

        # Add bond to previous residue if applicable
        if prev_res_atom is not None:
            self._add_bond_to_topology(C0, C0_pdb, prev_res_atom,
                                       prev_res_atom_pdb, topology,
                                       topology_pdb)

        # Add second carbon
        C1, C1_pdb, C_counter = _add_atom_to_topology_oplsaa(
            "C", self.CARBON, C_counter)
        self._add_bond_to_topology(C1, C1_pdb, C0, C0_pdb, topology,
                                   topology_pdb)
        if right_ter and not left_ter:
            H, H_pdb, H_counter = _add_atom_to_topology_oplsaa(
                "H", self.HYDROGEN, H_counter)
            self._add_bond_to_topology(H, H_pdb, C1, C1_pdb, topology,
                                       topology_pdb)

        # Add methyl group or hydrogen if not methyl
        if is_methyl:
            Cm, Cm_pdb, C_counter = _add_atom_to_topology_oplsaa(
                "C", self.CARBON, C_counter)
            for _ in range(3):
                H, H_pdb, H_counter = _add_atom_to_topology_oplsaa(
                    "H", self.HYDROGEN, H_counter)
                self._add_bond_to_topology(H, H_pdb, Cm, Cm_pdb, topology,
                                           topology_pdb)
            self._add_bond_to_topology(Cm, Cm_pdb, C1, C1_pdb, topology,
                                       topology_pdb)
        else:
            H, H_pdb, H_counter = _add_atom_to_topology_oplsaa(
                "H", self.HYDROGEN, H_counter)
            self._add_bond_to_topology(H, H_pdb, C1, C1_pdb, topology,
                                       topology_pdb)

        # Add carbonyl carbon
        Cc, Cc_pdb, C_counter = _add_atom_to_topology_oplsaa(
            "C", self.CARBON, C_counter)
        self._add_bond_to_topology(Cc, Cc_pdb, C1, C1_pdb, topology,
                                   topology_pdb)

        # Add carbonyl oxygen
        Oc, Oc_pdb, _, = _add_atom_to_topology_oplsaa("O", self.OXYGEN, 0)
        self._add_bond_to_topology(Oc, Oc_pdb, Cc, Cc_pdb, topology,
                                   topology_pdb)

        # Add ether oxygen
        Oe, Oe_pdb, _ = _add_atom_to_topology_oplsaa("O", self.OXYGEN, 1)
        self._add_bond_to_topology(Oe, Oe_pdb, Cc, Cc_pdb, topology,
                                   topology_pdb)

        # Add alkyl chain
        prev_atom = Oe
        prev_atom_pdb = Oe_pdb
        for _ in range(end_chain_length):
            curr_atom, curr_atom_pdb, C_counter = _add_atom_to_topology_oplsaa(
                "C", self.CARBON, C_counter)
            for _ in range(2):
                H, H_pdb, H_counter = _add_atom_to_topology_oplsaa(
                    "H", self.HYDROGEN, H_counter)
                self._add_bond_to_topology(H, H_pdb, curr_atom, curr_atom_pdb,
                                           topology, topology_pdb)
            self._add_bond_to_topology(curr_atom, curr_atom_pdb, prev_atom,
                                       prev_atom_pdb, topology, topology_pdb)
            prev_atom = curr_atom
            prev_atom_pdb = curr_atom_pdb

        # Add hydrogen to last carbon
        H, H_pdb, H_counter = _add_atom_to_topology_oplsaa(
            "H", self.HYDROGEN, H_counter)
        self._add_bond_to_topology(H, H_pdb, prev_atom, prev_atom_pdb,
                                   topology, topology_pdb)

        return C1, C1_pdb, prev_res_atom_pos + np.array([2.527, 0, 0])

    def _add_residue_to_chain_trappeua(self,
                                       topology,
                                       topology_pdb,
                                       chain,
                                       chain_pdb,
                                       prev_res_atom,
                                       prev_res_atom_pdb,
                                       positions,
                                       prev_res_atom_pos,
                                       monomer,
                                       left_ter=False,
                                       right_ter=False):
        def _deg_to_rad(ang_deg):
            return ang_deg * 2 * np.pi / 360.0

        # Sines and cosines
        sin19 = np.sin(_deg_to_rad(19.5))
        cos19 = np.cos(_deg_to_rad(19.5))
        sin30 = np.sin(_deg_to_rad(30.0))
        cos30 = np.cos(_deg_to_rad(30.0))
        sin54 = np.sin(_deg_to_rad(54.75))
        cos54 = np.cos(_deg_to_rad(54.75))

        # Determine monomer type
        if monomer.startswith('mA'):
            monomer_type = 'mA'
            is_methyl = True
        else:
            monomer_type = 'A'
            is_methyl = False

        # Determine chain length
        end_chain_length = literal_eval(monomer.replace(monomer_type, ''))

        # Determine residue id
        if left_ter:
            residue_id = "LEFTTER"
        elif right_ter:
            residue_id = "RIGHTTER"
        else:
            residue_id = None

        # Add residue to topology
        residue = topology.addResidue(monomer, chain, id=residue_id)
        residue_pdb = topology_pdb.addResidue(monomer,
                                              chain_pdb,
                                              id=residue_id)

        # Add first two carbons
        if left_ter and right_ter:
            carbon_element = self.NITROGEN
            carbon_1_element = self.NITROGEN
        elif left_ter:
            carbon_element = self.BORON
            carbon_1_element = self.CARBON
        elif right_ter:
            carbon_element = self.NITROGEN
            carbon_1_element = self.CARBON
        else:
            carbon_element = self.CARBON
            carbon_1_element = self.CARBON
        carbon_pos = prev_res_atom_pos + 1.54 * np.array(
            [cos19 * cos30, -cos19 * sin30, sin19])
        carbon, carbon_pdb = self._add_atom_to_topology_trappeua(
            'C', carbon_element, residue, residue_pdb, topology, topology_pdb,
            positions, carbon_pos)
        carbon_1_pos = carbon_pos + 1.54 * np.array(
            [cos19 * cos30, cos19 * sin30, -sin19])
        carbon_1, carbon_1_pdb = self._add_atom_to_topology_trappeua(
            'C1', carbon_1_element, residue, residue_pdb, topology,
            topology_pdb, positions, carbon_1_pos)

        # Add bond to previous residue if applicable
        if prev_res_atom is not None:
            self._add_bond_to_topology(carbon, carbon_pdb, prev_res_atom,
                                       prev_res_atom_pdb, topology,
                                       topology_pdb)

        # Add bond between first two carbons
        self._add_bond_to_topology(carbon, carbon_pdb, carbon_1, carbon_1_pdb,
                                   topology, topology_pdb)

        # Add methyl group if methacrylate monomer
        if is_methyl:
            carbon_methyl_pos = carbon_1_pos + 1.54 * np.array([0.0, 1.0, 0.0])
            carbon_methyl, carbon_methyl_pdb = self._add_atom_to_topology_trappeua(
                'Cm', self.CARBON, residue, residue_pdb, topology,
                topology_pdb, positions, carbon_methyl_pos)
            self._add_bond_to_topology(carbon_1, carbon_1_pdb, carbon_methyl,
                                       carbon_methyl_pdb, topology,
                                       topology_pdb)

        # Add ester group atoms
        carbon_2_pos = carbon_1_pos + 1.52 * np.array([0.0, 0.0, -1.0])
        carbon_2, carbon_2_pdb = self._add_atom_to_topology_trappeua(
            'C2', self.CARBON, residue, residue_pdb, topology, topology_pdb,
            positions, carbon_2_pos)
        self._add_bond_to_topology(carbon_1, carbon_1_pdb, carbon_2,
                                   carbon_2_pdb, topology, topology_pdb)
        oxygen_carbonyl_pos = carbon_2_pos + 1.20 * np.array(
            [0.0, cos30, -sin30])
        oxygen_carbonyl, oxygen_carbonyl_pdb = self._add_atom_to_topology_trappeua(
            'O', self.OXYGEN, residue, residue_pdb, topology, topology_pdb,
            positions, oxygen_carbonyl_pos)
        self._add_bond_to_topology(carbon_2, carbon_2_pdb, oxygen_carbonyl,
                                   oxygen_carbonyl_pdb, topology, topology_pdb)
        oxygen_ether_pos = carbon_2_pos + 1.344 * np.array(
            [0.0, -cos30, -sin30])
        oxygen_ether, oxygen_ether_pdb = self._add_atom_to_topology_trappeua(
            'O', self.OXYGEN, residue, residue_pdb, topology, topology_pdb,
            positions, oxygen_ether_pos)
        self._add_bond_to_topology(carbon_2, carbon_2_pdb, oxygen_ether,
                                   oxygen_ether_pdb, topology, topology_pdb)

        # Add carbon chain
        prev_atom_pos = oxygen_ether_pos
        prev_atom = oxygen_ether
        prev_atom_pdb = oxygen_ether_pdb
        for i in range(end_chain_length):
            if i == 0:
                curr_atom_pos = prev_atom_pos + 1.41 * np.array(
                    [0.0, (-1)**(i + 1) * cos54, -sin54])
            else:
                curr_atom_pos = prev_atom_pos + 1.54 * np.array(
                    [0.0, (-1)**(i + 1) * cos54, -sin54])
            curr_atom, curr_atom_pdb = self._add_atom_to_topology_trappeua(
                'C{}'.format(i + 3), self.CARBON, residue, residue_pdb,
                topology, topology_pdb, positions, curr_atom_pos)
            self._add_bond_to_topology(prev_atom, prev_atom_pdb, curr_atom,
                                       curr_atom_pdb, topology, topology_pdb)
            prev_atom_pos = curr_atom_pos
            prev_atom = curr_atom
            prev_atom_pdb = curr_atom_pdb

        return carbon_1, carbon_1_pdb, carbon_1_pos

    @staticmethod
    def _add_atom_to_topology_trappeua(name, element, residue, residue_pdb,
                                       topology, topology_pdb, positions,
                                       atom_pos):

        # Add atom to topology
        atom = topology.addAtom(name, element, residue)
        atom_pdb = topology_pdb.addAtom(name, element, residue_pdb)

        # Add position to array
        positions.append(atom_pos)

        # Return atoms and position
        return atom, atom_pdb

    @staticmethod
    def _add_bond_to_topology(atom1, atom1_pdb, atom2, atom2_pdb, topology,
                              topology_pdb):
        topology.addBond(atom1, atom2)
        topology_pdb.addBond(atom1_pdb, atom2_pdb)

    def _create_chain_pdb(self, topology_pdb, positions):
        dirname = os.path.dirname(__file__)
        filename = "{}.pdb".format(self.sequence_str)
        if self.forceField_str == 'OPLS-AA':
            filename = "{}_aa.pdb".format(self.sequence_str)
        file_path = os.path.join(dirname, "data/{}".format(filename))
        if not os.path.isfile(file_path) or self.overwrite_pdb:
            self.overwrite_pdb = False
            PDBFile.writeFile(topology_pdb, positions, open(file_path, 'w'))
    def _add_squalane_to_topology(self, topology):

        # Carbon element
        carbon_element = Element.getBySymbol('C')
        hydrogen_element = Element.getBySymbol('H')

        chain = topology.addChain(
            "{}-squalane".format(topology.getNumChains() + 1))
        residue = topology.addResidue("squalane", chain)
        prev_atom = None
        if self.forceField_str == "TraPPE-UA":
            atom_index = 0
            for _ in range(3):
                C1 = topology.addAtom("C{}".format(atom_index), carbon_element,
                                      residue)
                atom_index += 1
                if prev_atom is not None:
                    topology.addBond(prev_atom, C1)
                C2 = topology.addAtom("C{}".format(atom_index), carbon_element,
                                      residue)
                atom_index += 1
                topology.addBond(C1, C2)
                C3 = topology.addAtom("C{}".format(atom_index), carbon_element,
                                      residue)
                atom_index += 1
                topology.addBond(C2, C3)
                C4 = topology.addAtom("C{}".format(atom_index), carbon_element,
                                      residue)
                atom_index += 1
                topology.addBond(C2, C4)
                C5 = topology.addAtom("C{}".format(atom_index), carbon_element,
                                      residue)
                atom_index += 1
                topology.addBond(C4, C5)
                prev_atom = C5
            for _ in range(3):
                C1 = topology.addAtom("C{}".format(atom_index), carbon_element,
                                      residue)
                atom_index += 1
                topology.addBond(prev_atom, C1)
                C2 = topology.addAtom("C{}".format(atom_index), carbon_element,
                                      residue)
                atom_index += 1
                topology.addBond(C1, C2)
                C3 = topology.addAtom("C{}".format(atom_index), carbon_element,
                                      residue)
                atom_index += 1
                topology.addBond(C2, C3)
                C4 = topology.addAtom("C{}".format(atom_index), carbon_element,
                                      residue)
                atom_index += 1
                topology.addBond(C3, C4)
                C5 = topology.addAtom("C{}".format(atom_index), carbon_element,
                                      residue)
                atom_index += 1
                topology.addBond(C3, C5)
                prev_atom = C5
        else:
            raise NotImplementedError("OPLS-AA not implemented for squalane")

        return chain.id