Пример #1
0
def _atoms_bonds_models(objects, full_residues = False):
    # Treat selecting molecular surface as selecting atoms.
    # Returned models list does not include atomic models
    atoms = objects.atoms
    bonds = objects.bonds
    pbonds = objects.pseudobonds
    satoms = []
    models = []
    from chimerax.atomic import MolecularSurface, Structure, PseudobondGroup
    for m in objects.models:
        if isinstance(m, MolecularSurface):
            if m.has_atom_patches():
                satoms.append(m.atoms)
            else:
                models.append(m)
        elif not isinstance(m, (Structure, PseudobondGroup)):
            models.append(m)
    if satoms:
        from chimerax.atomic import concatenate
        atoms = concatenate([atoms] + satoms)
    if full_residues:
        if len(bonds) > 0 or len(pbonds) > 0:
            a1, a2 = bonds.atoms
            pa1, pa2 = pbonds.atoms
            from chimerax.atomic import concatenate
            atoms = concatenate([atoms, a1, a2, pa1, pa2])
        atoms = atoms.unique_residues.atoms
        bonds = atoms.intra_bonds
    return atoms, bonds, pbonds, models
Пример #2
0
 def atoms(self):
     '''
     Returns an unsorted `Atoms` object encompassing all atoms in chi dihedrals. Read only.
     '''
     from chimerax.atomic import concatenate, Atoms
     dihedrals = concatenate([r.chi_dihedrals for r in self])
     return concatenate([Atoms(d.atoms) for d in dihedrals]).unique()
Пример #3
0
def paired_atoms(atoms, to_atoms, match_chain_ids, match_numbering,
                 match_atom_names):
    # TODO: return summary string of all dropped atoms.
    if match_chain_ids:
        pat = pair_chains(atoms, to_atoms)
    elif match_numbering:
        # Pair chains in order of matching sequence numbers but not chain ids.
        ca, cta = atoms.by_chain, to_atoms.by_chain
        n = min(len(ca), len(cta))
        # TODO: Warn if unequal number of sequences.
        pat = [(ca[i][2], cta[i][2]) for i in range(n)]
    else:
        pat = [(atoms, to_atoms)]

    if match_numbering:
        pat = sum([pair_sequence_numbers(a, ta) for a, ta in pat], [])

    if match_atom_names:
        pat = sum([pair_atom_names(a, ta) for a, ta in pat], [])

    for pa, pta in pat:
        if len(pa) != len(pta):
            msg = 'Unequal number of atoms to pair, %d and %d' % (len(pa),
                                                                  len(pta))
            if match_chain_ids:
                msg += ', chain %s' % pa[0].chain_id
            if match_numbering:
                msg += ', residue %d' % pa[0].residue.number
            raise UserError(msg)

    from chimerax.atomic import Atoms, concatenate
    pas = concatenate([pa[0] for pa in pat], Atoms)
    ptas = concatenate([pa[1] for pa in pat], Atoms)

    return pas, ptas
Пример #4
0
def _hbondatoms_selector(session, models, results):
    from chimerax.atomic import Pseudobonds, PseudobondGroup, concatenate
    pbonds = concatenate([pbg.pseudobonds for pbg in models
              if isinstance(pbg, PseudobondGroup) and pbg.name == 'hydrogen bonds'], Pseudobonds)
    if len(pbonds) > 0:
        atoms = concatenate(pbonds.atoms)
        results.add_atoms(atoms)
        for m in atoms.unique_structures:
            results.add_model(m)
Пример #5
0
 def atoms(self):
     '''
     Returns an unsorted `Atoms` object encompassing all atoms in phi, psi and omega (if present).
     '''
     from chimerax.atomic import Atoms, concatenate
     return concatenate([
         concatenate(dihedrals.atoms) for dihedrals in
         [self.phi_dihedrals, self.psi_dihedrals, self.omega_dihedrals]
     ]).unique()
Пример #6
0
def rota(session, structures=None, report=False):
    '''
    Add a live rotamer validator to each of the given structures, and optionally
    report a summary of current outliers.
    '''
    from chimerax.isolde import session_extensions as sx
    if structures is None:
        from chimerax.atomic import AtomicStructure
        structures = [
            m for m in session.models.list() if type(m) == AtomicStructure
        ]
    for structure in structures:
        sx.get_rota_annotator(structure)
    if report:
        from chimerax.atomic import Residues, concatenate
        residues = concatenate([m.residues for m in structures])
        mgr = sx.get_rotamer_mgr(session)
        rotamers = mgr.get_rotamers(residues)
        report_str = 'NON-FAVOURED ROTAMERS: \n'
        nf, scores = mgr.non_favored_rotamers(rotamers)
        for r, score in zip(nf, scores):
            report_str += '#{:<6} {}:\t{} {} (P={:.4f})\n'.format(
                r.residue.structure.id_string, r.residue.chain_id,
                r.residue.name, r.residue.number, score)
        session.logger.info(report_str)
Пример #7
0
def assign_unbound_residues(m, chain_map, unclassified):
    from chimerax.atomic import Residue, concatenate
    for cid, residues in chain_map.items():
        first_ligand_number = next_available_ligand_number(m, cid)
        residues.chain_ids = cid + 'tmp'
        residues = concatenate([
            residues[residues.names != 'HOH'],
            residues[residues.names == 'HOH']
        ])
        m.renumber_residues(residues, first_ligand_number)
        residues.chain_ids = cid
    if len(unclassified):
        # Try reclassifying with a more permissive cutoff, now that all the other ligands
        # are assigned to chains
        chain_map, unclassified = cluster_unbound_ligands(m,
                                                          unclassified,
                                                          cutoff=8)
        assign_unbound_residues(m, chain_map, [])
        if len(unclassified):
            session = m.session
            warn_str = (
                '{} residues could not be automatically assigned to chains. '
                'These have been given the chain ID UNK.').format(
                    len(unclassified))
            session.logger.warning(warn_str)
            unclassified.chain_ids = 'UNK'
            m.renumber_residues(unclassified, 1)
Пример #8
0
def _pbonds_selector(session, models, results):
    from chimerax.atomic import Pseudobonds, PseudobondGroup, concatenate
    pbonds = concatenate([pbg.pseudobonds for pbg in models if isinstance(pbg, PseudobondGroup)],
                         Pseudobonds)
    results.add_pseudobonds(pbonds)
    for m in pbonds.unique_groups:
        results.add_model(m)
Пример #9
0
def collate_results(results, return_collection):
    if return_collection:
        from chimerax.atomic import concatenate
        return concatenate(results)
    ret_val = []
    for result in results:
        ret_val.extend(result)
    return ret_val
Пример #10
0
def _link_specs(links):
    m1,m2 = links.atoms
    from chimerax.atomic import concatenate
    markers = concatenate((m1,m2))
    mlinks = markers.intra_bonds
    if len(mlinks) == len(links):
        lspecs = [_markers_spec(markers)]
    else:
        lspecs = [_markers_spec(Atoms(mpair)) for mpair in zip(m1,m2)]
    return lspecs
Пример #11
0
 def __init__(self, structures):
     self.name = structures[0].name + " (combined)"
     from chimerax.atomic import concatenate
     self.atoms = concatenate([s.atoms for s in structures])
     self.bonds = concatenate([s.bonds for s in structures])
     self.residues = concatenate([s.residues for s in structures])
     self.pbg_map = {
         Structure.PBG_METAL_COORDINATION: JPBGroup(self.atoms)
     }
     # if combining single-residue structures,
     # can be more informative to use model name
     # instead of residue type for substructure
     if len(structures) == len(self.residues):
         rnames = self.residues.names
         if len(set(rnames)) < len(rnames):
             snames = [s.name for s in structures]
             if len(set(snames)) == len(snames):
                 self.substructure_names = dict(
                     zip(self.residues, snames))
Пример #12
0
def extend_to_chains(atoms):
    '''Return atoms extended to all atoms in same chains (ie same chain id / structure).'''
    catoms = []
    from numpy import in1d
    for s, a in atoms.by_structure:
        satoms = s.atoms
        catoms.append(satoms.filter(in1d(satoms.chain_ids,
                                         a.unique_chain_ids)))
    from chimerax.atomic import concatenate, Atoms
    return concatenate(catoms, Atoms)
Пример #13
0
def show_pseudobonds(session, objects, only, undo_state):
    pbonds = objects.pseudobonds
    undo_state.add(pbonds, "displays", pbonds.displays, True)
    pbonds.displays = True
    a1, a2 = pbonds.atoms
    undo_state.add(a1, "displays", a1.displays, True)
    a1.displays = True  # Atoms need to be displayed for bond to appear
    undo_state.add(a2, "displays", a1.displays, True)
    a2.displays = True
    if only:
        from chimerax.atomic import concatenate
        atoms = concatenate([a1, a2])
        pbs = sum([[pbg.pseudobonds for pbg in m.pbg_map.values()]
                   for m in atoms.unique_structures], [])
        if pbs:
            all_pbonds = concatenate(pbs)
            other_pbonds = all_pbonds - pbonds
            undo_state.add(other_pbonds, "displays", other_pbonds.displays,
                           False)
            other_pbonds.displays = False
Пример #14
0
    def __init__(self,
                 session,
                 contact,
                 flip=False,
                 interface_residue_area_cutoff=15):

        self.contact = c = contact

        # Interface residues
        g1, g2 = (c.group2, c.group1) if flip else (c.group1, c.group2)
        min_area = interface_residue_area_cutoff
        self.residues1 = r1 = c.contact_residues(g1, min_area)
        self.residues2 = r2 = c.contact_residues(g2, min_area)
        from chimerax.atomic import concatenate
        self.residues = res = concatenate((r1, r2))

        # Non-interface residues
        self.atoms1, self.atoms2 = a1, a2 = g1.atoms, g2.atoms
        self.noninterface_residues1 = a1.unique_residues.subtract(r1)
        self.noninterface_residues2 = a2.unique_residues.subtract(r2)
        allres = concatenate((a1, a2)).unique_residues
        self.noninterface_residues = allres.subtract(res)

        # Create matplotlib panel
        nodes = tuple(ResidueNode(r) for r in res)
        bnodes = tuple(
            ResidueNode(r, size=800, color=(.7, .7, .7, 1), background=True)
            for r in self.noninterface_residues1)
        edges = ()
        title = '%s %d residues with %s %d residues' % (g2.name, len(r2),
                                                        g1.name, len(r1))
        Graph.__init__(self,
                       session,
                       nodes + bnodes,
                       edges,
                       "Chain Contacts",
                       title=title)
        self.font_size = 8

        self._interface_shown = False
        self.draw_graph()
Пример #15
0
def pseudobond_connections(structures):
    pcon = {}
    from chimerax.atomic import concatenate, Atoms, interatom_pseudobonds
    from chimerax.geometry import distance
    satoms = concatenate([s.atoms for s in structures], Atoms)
    for pb in interatom_pseudobonds(satoms):
        a1, a2 = pb.atoms
        if pb.shown and pb.group.display:
            d12 = distance(a1.scene_coord, a2.scene_coord)
            pcon.setdefault(a1, []).append((a2,d12))
            pcon.setdefault(a2, []).append((a1,d12))
    return pcon
Пример #16
0
 def _mdff_changed_cb(self, trigger_name, changes):
     mgr, changes = changes
     change_types = list(changes.keys())
     from chimerax.atomic import concatenate
     changeds = []
     if 'enabled/disabled' in change_types:
         changeds.append(changes['enabled/disabled'])
     if 'spring constant changed' in change_types:
         changeds.append(changes['spring constant changed'])
     if len(changeds):
         all_changeds = concatenate(changeds, remove_duplicates=True)
         all_changeds = all_changeds[all_changeds.sim_indices != -1]
         self.sim_handler.update_mdff_atoms(all_changeds, mgr.volume)
Пример #17
0
def shortcut_atoms(session):
    matoms = []
    sel = session.selection
    atoms_list = sel.items('atoms')
    from chimerax.atomic import concatenate, Atoms
    if atoms_list:
        atoms = concatenate(atoms_list)
    elif sel.empty():
        # Nothing selected, so operate on all atoms
        from chimerax.atomic import all_atoms
        atoms = all_atoms(session)
    else:
        atoms = Atoms()
    return atoms
Пример #18
0
def process_clashes(session, residue, by_alt_loc, overlap, hbond_allow,
                    score_method, make_pbs, pb_color, pb_radius,
                    ignore_others):
    if make_pbs:
        pbg = session.pb_manager.get_group("clashes")
        pbg.clear()
        pbg.radius = pb_radius
        pbg.color = pb_color.uint8x4()
    else:
        pbg = session.pb_manager.get_group("clashes", create=False)
        if pbg:
            session.models.close([pbg])
    from chimerax.atomic import concatenate
    from chimerax.clashes import find_clashes
    CA = residue.find_atom("CA")
    alt_locs = CA.alt_locs if CA.alt_locs else [' ']
    res_atoms = set(residue.atoms)
    with CA.suppress_alt_loc_change_notifications():
        for alt_loc, rots in by_alt_loc.items():
            CA.alt_loc = alt_loc
            test_atoms = concatenate([rot.atoms for rot in rots])
            clash_info = find_clashes(session,
                                      test_atoms,
                                      clash_threshold=overlap,
                                      hbond_allowance=hbond_allow)
            for rot in rots:
                score = 0
                for ra in rot.atoms:
                    if ra.name in ("CA", "N", "CB"):
                        # any clashes of CA/N/CB are already clashes of base residue (and may
                        # mistakenly be thought to clash with "bonded" atoms in nearby residues)
                        continue
                    if ra not in clash_info:
                        continue
                    for ca, clash in clash_info[ra].items():
                        if ca in res_atoms:
                            continue
                        if ignore_others and ca.structure != residue.structure:
                            continue
                        if score_method == "num":
                            score += 1
                        else:
                            score += clash
                        if make_pbs:
                            pbg.new_pseudobond(ra, ca)
                rot.clash_score = score
    if score_method == "num":
        return "%2d"
    return "%4.2f"
Пример #19
0
def cmd_centroid(session, atoms=None, *, mass_weighting=False, name="centroid", color=None, radius=2.0):
    """Wrapper to be called by command line.

       Use chimerax.centroids.centroid for other programming applications.
    """
    from chimerax.core.errors import UserError

    from chimerax.atomic import AtomicStructure, concatenate, Structure
    if atoms is None:
        structures_atoms = [m.atoms for m in session.models if isinstance(m, AtomicStructure)]
        if structures_atoms:
            atoms = concatenate(structures_atoms)
        else:
            raise UserError("Atom specifier selects no atoms")

    structures = atoms.unique_structures
    if len(structures) > 1:
        crds = atoms.scene_coords
    else:
        crds = atoms.coords
    if mass_weighting:
        masses = atoms.elements.masses
        avg_mass = masses.sum() / len(masses)
        import numpy
        weights = masses[:, numpy.newaxis] / avg_mass
    else:
        weights = None
    xyz = centroid(crds, weights=weights)
    s = Structure(session, name=name)
    r = s.new_residue('centroid', 'centroid', 1)
    from chimerax.atomic.struct_edit import add_atom
    a = add_atom('cent', 'C', r, xyz)
    if color:
        a.color = color.uint8x4()
    else:
        from chimerax.atomic.colors import element_color, predominant_color
        color = predominant_color(atoms)
        if color is None:
            a.color = element_color(a.element.number)
        else:
            a.color = color
    a.radius = radius
    if len(structures) > 1:
        session.models.add([s])
    else:
        structures[0].add([s])

    session.logger.info("Centroid '%s' placed at %s" % (name, xyz))
    return a
Пример #20
0
def info_selection(session, level=None, attribute=None):
    if level is None or level == "atom":
        if attribute is None:
            attribute = "idatm_type"
        atoms = session.selection.items("atoms")
        if atoms:
            from chimerax.atomic import concatenate
            report_atoms(session.logger, concatenate(atoms), attribute)
    elif level == "residue":
        if attribute is None:
            attribute = "name"
        atoms = session.selection.items("atoms")
        if atoms:
            from chimerax.atomic import concatenate
            residues = concatenate([a.unique_residues for a in atoms])
            report_residues(session.logger, residues, attribute)
    elif level == "chain":
        if attribute is None:
            attribute = "chain_id"
        atoms = session.selection.items("atoms")
        if atoms:
            from chimerax.atomic import concatenate
            chains = concatenate([a.residues.unique_chains for a in atoms])
            report_chains(session.logger, chains, attribute)
    elif level == "structure":
        if attribute is None:
            attribute = "name"
        atoms = session.selection.items("atoms")
        if atoms:
            from chimerax.atomic import concatenate
            mols = concatenate([a.unique_structures for a in atoms])
            report_models(session.logger, mols, attribute)
    elif level == "model":
        if attribute is None:
            attribute = "name"
        report_models(session.logger, session.selection.models(), attribute)
Пример #21
0
def _surface_atoms(session, objects):
    if objects is None:
        from chimerax.atomic import all_atoms
        atoms = all_atoms(session)
    else:
        atoms = objects.atoms
        from chimerax.atomic import MolecularSurface
        satoms = [
            s.atoms for s in objects.models if isinstance(s, MolecularSurface)
        ]
        if satoms:
            from chimerax.atomic import concatenate, Atoms
            atoms = concatenate([atoms] + satoms,
                                Atoms,
                                remove_duplicates=True)
    return atoms
Пример #22
0
 def _pr_changed_cb(self, trigger_name, changes):
     mgr, changes = changes
     change_types = list(changes.keys())
     from chimerax.atomic import concatenate
     changeds = []
     if 'target changed' in change_types:
         changeds.append(changes['target changed'])
     if 'enabled/disabled' in change_types:
         changeds.append(changes['enabled/disabled'])
     if 'spring constant changed' in change_types:
         changeds.append(changes['spring constant changed'])
     if len(changeds):
         all_changeds = concatenate(changeds, remove_duplicates=True)
         # limit to restraints that are actually in the simulation
         # TODO: might be better to just ignore -1 indices in the update_... functions
         all_changeds = all_changeds[all_changeds.sim_indices != -1]
         self.sim_handler.update_position_restraints(all_changeds)
Пример #23
0
def show_bonds(session, objects, only, undo_state):
    bonds = objects.bonds
    undo_state.add(bonds, "displays", bonds.displays, True)
    bonds.displays = True
    a1, a2 = bonds.atoms
    undo_state.add(a1, "displays", a1.displays, True)
    a1.displays = True  # Atoms need to be displayed for bond to appear
    undo_state.add(a2, "displays", a2.displays, True)
    a2.displays = True
    if only:
        mbonds = [m.bonds for m in atoms.unique_structures]
        if mbonds:
            from chimerax.atomic import concatenate
            all_bonds = concatenate(mbonds)
            other_bonds = all_bonds - bonds
            undo_state.add(other_bonds, "displays", other_bonds.displays,
                           False)
            other_bonds.displays = False
Пример #24
0
 def _show_contact_residues(self, g, color=(180, 180, 180, 255)):
     from .cmd import neighbors
     ng = neighbors(g, self.contacts)  # Map neighbor node to Contact
     min_area = self.interface_residue_area_cutoff
     gca = []
     for h in self.groups:
         if h in ng:
             c = ng[h]
             atoms = c.contact_residue_atoms(h, min_area)
             h.show(False)
             atoms.displays = True  # Show only contacting residues
             atoms.draw_modes = atoms.STICK_STYLE
             gatoms = c.contact_residue_atoms(g, min_area)
             #                gatoms.draw_modes = gatoms.STICK_STYLE
             gca.append(gatoms)
         else:
             h.show(h is g)
     # Color non-contact atoms gray.
     from chimerax.atomic import concatenate, Atoms
     g.color_atoms(g.atoms - concatenate(gca, Atoms), color)
Пример #25
0
 def get_protein_backbone_problems(structure, outliers_only=True):
     from chimerax.isolde import session_extensions as sx
     rmgr = sx.get_ramachandran_mgr(structure.session)
     residues = structure.residues
     if outliers_only:
         f = rmgr.outliers
     else:
         f = rmgr.non_favored
     problems = f(residues)
     # Also count twisted and cis-nonPro peptide bonds here
     cis = rmgr.cis(residues)
     cis_nonpro = cis[cis.names != 'PRO']
     # Note: this will probably lead to double-counting since
     # twisted peptides will usually also be captured by
     # get_torsion_restraint_problems()... but ultimately I
     # don't think that's a huge problem.
     twisted = rmgr.twisted(residues)
     from chimerax.atomic import concatenate, Residues
     twisted = Residues([t[0] for t in twisted])
     problems = concatenate([problems, cis_nonpro, twisted])
     problem_ramas = rmgr.get_ramas(problems)
     return problem_ramas
Пример #26
0
def split_atoms(atoms, asubsets):

    # Eliminate subset atoms not in atoms
    asubsets = [asub.intersect(atoms) for asub in asubsets]

    # Remove empty subsets
    asubsets = [asub for asub in asubsets if len(asub) > 0]

    # Find atoms not in any subset
    from chimerax.atomic import concatenate, Atoms
    a0 = atoms.subtract(concatenate(asubsets, Atoms))

    # Return groups of atoms
    if len(a0) == len(atoms):
        pieces = [('',atoms)]
    elif len(a0) == 0 and len(asubsets) == 1:
        pieces = [('',atoms)]
    else:
        alists = (asubsets + [a0]) if len(a0) > 0 else asubsets
        pieces = [(str(i+1),a) for i,a in enumerate(alists)]

    return pieces
Пример #27
0
def rama(session, structures=None, show_favored=True, report=False):
    '''
    Add a live Ramachandran validator to each of the given structures, and
    optionally report a summary of current outliers and cis/twisted peptide
    bonds.
    '''
    from chimerax.isolde import session_extensions as sx
    if structures is None:
        from chimerax.atomic import AtomicStructure
        structures = [
            m for m in session.models.list() if type(m) == AtomicStructure
        ]
    for structure in structures:
        ra = sx.get_RamaAnnotator(structure)
        ra.hide_favored = not show_favored
    if report:
        from chimerax.atomic import Residues, concatenate
        residues = concatenate([m.residues for m in structures])
        mgr = sx.get_ramachandran_mgr(session)
        report_str = 'RAMACHANDRAN OUTLIERS: \n'
        outliers = mgr.outliers(residues)
        for outlier in outliers:
            report_str += '#{:<6} {}:\t{} {}\n'.format(
                outlier.structure.id_string, outlier.chain_id, outlier.name,
                outlier.number)
        report_str += '\nCIS PEPTIDE BONDS: \n'
        cispeps = mgr.cis(residues)
        for cis in cispeps:
            report_str += '#{:<6} {}:\t{} {}\n'.format(cis.structure.id_string,
                                                       cis.chain_id, cis.name,
                                                       cis.number)
        report_str += '\nTWISTED PEPTIDE BONDS: \n'
        twisteds = mgr.twisted(residues)
        for twisted, angle in twisteds:
            report_str += '#{:<6} {}:\t{} {} ({:.1f}°)\n'.format(
                twisted.structure.id_string, twisted.chain_id, twisted.name,
                twisted.number, angle)
        session.logger.info(report_str)
Пример #28
0
def move_atoms(atoms, to_atoms, tf, move):

    if move == 'structures' or move is True:
        for m in atoms.unique_structures:
            m.scene_position = tf * m.scene_position
    else:
        from chimerax.atomic import Atoms
        if move == 'atoms':
            matoms = atoms
        elif move == 'residues':
            matoms = atoms.unique_residues.atoms
        elif move == 'chains':
            matoms = extend_to_chains(atoms)
        elif move == 'structure atoms':
            from chimerax.atomic import concatenate, Atoms
            matoms = concatenate([m.atoms for m in atoms.unique_structures],
                                 Atoms)
        elif isinstance(move, Atoms):
            matoms = move
        else:
            return  # Move nothing

        matoms.scene_coords = tf * matoms.scene_coords
Пример #29
0
def show_surfaces(session, objects, only, undo_state):
    # TODO: fill in undo data
    atoms = objects.atoms
    if len(atoms) == 0:
        return

    # Show existing surfaces
    from chimerax.atomic import molsurf, concatenate, Atoms
    surfs = molsurf.show_surface_atom_patches(atoms, only=only)

    # Create new surfaces if they don't yet exist.
    if surfs:
        patoms, all_small = molsurf.remove_solvent_ligands_ions(atoms)
        extra_atoms = patoms - concatenate([s.atoms for s in surfs], Atoms)
        if extra_atoms:
            # Handle case where atoms were added to existing surfaces
            # so those surfaces will be replaced.  Bug #2603.
            extra_atoms = atoms
    else:
        extra_atoms = atoms
    if extra_atoms:
        from chimerax.surface import surface
        surface(session, extra_atoms)
Пример #30
0
def assign_bound_ligand_chain_ids_and_residue_numbers(m, bound_map):
    # The wwPDB steering committee has dictated that for protein-linked glycans,
    # the following rules apply:
    #   - if the modelled portion of the glycan is a single residue, it should have
    #     the same chain ID as the protein.
    #   - if more than one residue, it should have a unique chain ID.
    from chimerax.atomic import Residues, Residue, concatenate
    import numpy
    for cid, bound_residues in bound_map.items():
        first_ligand_number = next_available_ligand_number(m, cid)
        new_glycan_cid = []
        assign_to_chain = []
        groups = independent_groupings(bound_residues)
        for g in groups:
            if len(g) == 1:
                assign_to_chain.append(g)
            elif any(r.name in known_sugars for r in g):
                new_glycan_cid.append(g)
            else:
                assign_to_chain.append(g)
        new_glycan_cid = list(
            sorted(new_glycan_cid,
                   key=lambda g: linked_polymer_residue(g).number))
        assign_to_chain = list(
            sorted(assign_to_chain,
                   key=lambda g: linked_polymer_residue(g).number))
        for i, g in enumerate(new_glycan_cid):
            gid = cid + 'gl' + str(i)
            residues = sort_glycan_residues(g)
            residues.chain_ids = gid
            m.renumber_residues(residues, 1)
        if len(assign_to_chain):
            assign_to_chain = concatenate(
                [Residues(g) for g in assign_to_chain])
            assign_to_chain.chain_ids = 'XXtmp'
            m.renumber_residues(assign_to_chain, first_ligand_number)
            assign_to_chain.chain_ids = cid