o
    Rŀg                    @   s  d Z ddlZddlZddlmZ ddlmZ ddlmZ ddlm	Z	 ddlm
Z
 ddlmZ dd	lmZ dd
lmZ ddlZddlmZ ddlmZ ddlmZ ddlmZ ddlmZ ddlmZ ddlmZ ddlmZ ddlmZ ddlmZ ddlmZ ddlmZ ddlm Z  erddl!m"Z" e#d Z$e#d Z%ee$e%f Z&e#d Z'ej(Z)e#ej(ej(ej(ej(f Z*G dd dZ+G dd dZ,G d d! d!Z-G d"d# d#e-Z.G d$d% d%e-Z/G d&d' d'Z0d(e1d)e1fd*d+Z2G d,d- d-e3Z4G d.d/ d/e3Z5dS )0a1  Classes to support internal coordinates for protein structures.

Internal coordinates comprise Psi, Omega and Phi dihedral angles along the
protein backbone, Chi angles along the sidechains, and all 3-atom angles and
bond lengths defining a protein chain.  These routines can compute internal
coordinates from atom XYZ coordinates, and compute atom XYZ coordinates from
internal coordinates.

Secondary benefits include the ability to align and compare residue
environments in 3D structures, support for 2D atom distance plots, converting a
distance plot plus chirality information to a structure, generating an OpenSCAD
description of a structure for 3D printing, and reading/writing structures as
internal coordinate data files.

**Usage:**
::

    from Bio.PDB.PDBParser import PDBParser
    from Bio.PDB.Chain import Chain
    from Bio.PDB.internal_coords import *
    from Bio.PDB.PICIO import write_PIC, read_PIC, read_PIC_seq
    from Bio.PDB.ic_rebuild import write_PDB, IC_duplicate, structure_rebuild_test
    from Bio.PDB.SCADIO import write_SCAD
    from Bio.Seq import Seq
    from Bio.SeqRecord import SeqRecord
    from Bio.PDB.PDBIO import PDBIO
    import numpy as np

    # load a structure as normal, get first chain
    parser = PDBParser()
    myProtein = parser.get_structure("7rsa", "pdb7rsa.ent")
    myChain = myProtein[0]["A"]

    # compute bond lengths, angles, dihedral angles
    myChain.atom_to_internal_coordinates(verbose=True)

    # check myChain makes sense (can get angles and rebuild same structure)
    resultDict = structure_rebuild_test(myChain)
    assert resultDict['pass'] == True

    # get residue 1 chi2 angle
    r1 = next(myChain.get_residues())
    r1chi2 = r1.internal_coord.get_angle("chi2")

    # rotate residue 1 chi2 angle by 120 degrees (loops w/in +/-180)
    r1.internal_coord.set_angle("chi2", r1chi2 + 120.0)
    # or
    r1.internal_coord.bond_rotate("chi2", 120.0)
    # update myChain XYZ coordinates with chi2 changed
    myChain.internal_to_atom_coordinates()
    # write new conformation with PDBIO
    write_PDB(myProtein, "myChain.pdb")
    # or just the ATOM records without headers:
    io = PDBIO()
    io.set_structure(myProtein)
    io.save("myChain2.pdb")

    # write chain as 'protein internal coordinates' (.pic) file
    write_PIC(myProtein, "myChain.pic")
    # read .pic file
    myProtein2 = read_PIC("myChain.pic")

    # create default structure for random sequence by reading as .pic file
    myProtein3 = read_PIC_seq(
        SeqRecord(
            Seq("GAVLIMFPSTCNQYWDEHKR"),
            id="1RND",
            description="my random sequence",
        )
    )
    myProtein3.internal_to_atom_coordinates()
    write_PDB(myProtein3, "myRandom.pdb")

    # access the all-dihedrals array for the chain, e.g. residue 1 chi2 angle:
    r1chi2_obj = r1.internal_coord.pick_angle("chi2")
    # or same thing: r1chi2_obj = r1.internal_coord.pick_angle("CA:CB:CG:CD")
    r1chi2_key = r1chi2_obj.atomkeys
    # r1chi2_key is tuple of AtomKeys (1_K_CA, 1_K_CB, 1_K_CG, 1_K_CD)
    r1chi2_index = myChain.internal_coord.dihedraNdx[r1chi2_key]
    # or same thing: r1chi2_index = r1chi2_obj.ndx
    r1chi2_value = myChain.internal_coord.dihedraAngle[r1chi2_index]
    # also true: r1chi2_obj == myChain.internal_coord.dihedra[r1chi2_index]

    # access the array of all atoms for the chain, e.g. residue 1 C-beta
    r1_cBeta_index = myChain.internal_coord.atomArrayIndex[AtomKey("1_K_CB")]
    r1_cBeta_coords = myChain.internal_coord.atomArray[r1_cBeta_index]
    # r1_cBeta_coords = [ x, y, z, 1.0 ]

    # the Biopython Atom coord array is now a view into atomArray, so
    assert r1_cBeta_coords[1] == r1["CB"].coord[1]
    r1_cBeta_coords[1] += 1.0  # change the Y coord 1 angstrom
    assert r1_cBeta_coords[1] == r1["CB"].coord[1]
    # they are always the same (they share the same memory)
    r1_cBeta_coords[1] -= 1.0  # restore

    # create a selector to filter just the C-alpha atoms from the all atom array
    atmNameNdx = AtomKey.fields.atm
    atomArrayIndex = myChain.internal_coord.atomArrayIndex
    CaSelect = [
        atomArrayIndex.get(k) for k in atomArrayIndex.keys() if k.akl[atmNameNdx] == "CA"
    ]
    # now the ordered array of C-alpha atom coordinates is:
    CA_coords = myChain.internal_coord.atomArray[CaSelect]
    # note this uses Numpy fancy indexing, so CA_coords is a new copy

    # create a C-alpha distance plot
    caDistances = myChain.internal_coord.distance_plot(CaSelect)
    # display with e.g. MatPlotLib:
    # import matplotlib.pyplot as plt
    # plt.imshow(caDistances, cmap="hot", interpolation="nearest")
    # plt.show()

    # build structure from distance plot:
    ## create the all-atom distance plot
    distances = myChain.internal_coord.distance_plot()
    ## get the sign of the dihedral angles
    chirality = myChain.internal_coord.dihedral_signs()
    ## get new, empty data structure : copy data structure from myChain
    myChain2 = IC_duplicate(myChain)[0]["A"]
    cic2 = myChain2.internal_coord
    ## clear the new atomArray and di/hedra value arrays, just for proof
    cic2.atomArray = np.zeros((cic2.AAsiz, 4), dtype=np.float64)
    cic2.dihedraAngle[:] = 0.0
    cic2.hedraAngle[:] = 0.0
    cic2.hedraL12[:] = 0.0
    cic2.hedraL23[:] = 0.0
    ## copy just the first N-Ca-C coords so structures will superimpose:
    cic2.copy_initNCaCs(myChain.internal_coord)
    ## copy distances to chain arrays:
    cic2.distplot_to_dh_arrays(distances, chirality)
    ## compute angles and dihedral angles from distances:
    cic2.distance_to_internal_coordinates()
    ## generate XYZ coordinates from internal coordinates:
    myChain2.internal_to_atom_coordinates()
    ## confirm result atomArray matches original structure:
    assert np.allclose(cic2.atomArray, myChain.internal_coord.atomArray)

    # superimpose all phe-phe pairs - quick hack just to demonstrate concept
    # for analyzing pairwise residue interactions.  Generates PDB ATOM records
    # placing each PHE at origin and showing all other PHEs in environment
    ## shorthand for key variables:
    cic = myChain.internal_coord
    resNameNdx = AtomKey.fields.resname
    aaNdx = cic.atomArrayIndex
    ## select just PHE atoms:
    pheAtomSelect = [aaNdx.get(k) for k in aaNdx.keys() if k.akl[resNameNdx] == "F"]
    aaF = cic.atomArray[ pheAtomSelect ]  # numpy fancy indexing makes COPY not view

    for ric in cic.ordered_aa_ic_list:  # internal_coords version of get_residues()
        if ric.rbase[2] == "F":  # if PHE, get transform matrices for chi1 dihedral
            chi1 = ric.pick_angle("N:CA:CB:CG")  # chi1 space has C-alpha at origin
            cst = np.transpose(chi1.cst)  # transform TO chi1 space
            # rcst = np.transpose(chi1.rcst)  # transform FROM chi1 space
            cic.atomArray[pheAtomSelect] = aaF.dot(cst)  # transform just the PHEs
            for res in myChain.get_residues():  # print PHEs in new coordinate space
                if res.resname in ["PHE"]:
                    print(res.internal_coord.pdb_residue_string())
            cic.atomArray[pheAtomSelect] = aaF  # restore coordinate space from copy

    # write OpenSCAD program of spheres and cylinders to 3d print myChain backbone
    ## set atom load filter to accept backbone only:
    IC_Residue.accept_atoms = IC_Residue.accept_backbone
    ## delete existing data to force re-read of all atoms:
    myChain.internal_coord = None
    write_SCAD(myChain, "myChain.scad", scale=10.0)

See the `''Internal coordinates module''` section of the `Biopython Tutorial
and Cookbook` for further discussion.

**Terms and key data structures:**
Internal coordinates are defined on sequences of atoms which span
residues or follow accepted nomenclature along sidechains.  To manage these
sequences and support Biopython's disorder mechanisms, :class:`AtomKey`
specifiers are implemented to capture residue, atom and variant identification
in a single object.  A :class:`Hedron` object is specified as three sequential
AtomKeys, comprising two bond lengths and the bond angle between them.  A
:class:`Dihedron` consists of four sequential AtomKeys, linking two Hedra with
a dihedral angle between them.

**Algorithmic overview:**
The Internal Coordinates module combines a specification of connected atoms as
hedra and dihedra in the :mod:`.ic_data` file with routines here to transform
XYZ coordinates of these atom sets between a local coordinate system and the
world coordinates supplied in e.g. a PDB or mmCif data file.  The local
coordinate system places the center atom of a hedron at the origin (0,0,0), one
leg on the +Z axis, and the other leg on the XZ plane (see :class:`Hedron`).
Measurement and creation or manipulation of hedra and dihedra in the local
coordinate space is straightforward, and the calculated transformation matrices
enable assembling these subunits into a protein chain starting from supplied
(PDB) coordinates for the initial N-Ca-C atoms.

Psi and Phi angles are defined on atoms from adjacent residues in a protein
chain, see e.g. :meth:`.pick_angle` and :mod:`.ic_data` for the relevant
mapping between residues and backbone dihedral angles.

Transforms to and from the dihedron local coordinate space described above are
accessible via :data:`IC_Chain.dCoordSpace` and :class:`Dihedron` attributes
.cst and .rcst, and may be applied in the alignment and comparison of residues
and their environments with code along the lines of::

    chi1 = ric0.pick_angle("chi1") # chi1 space defined with CA at origin
    cst = np.transpose(chi1.cst) # transform TO chi1 local space
    newAtomCoords = oldAtomCoords.dot(cst)

The core algorithms were developed independently during 1993-4 for
`''Development and Application of a Three-dimensional Description of Amino Acid
Environments in Protein,'' Miller, Douthart, and Dunker, Advances in Molecular
Bioinformatics, IOS Press, 1994, ISBN 90 5199 172 x, pp. 9-30.
<https://www.google.com/books/edition/Advances_in_Molecular_Bioinformatics/VmFSNNm7k6cC?gbpv=1>`_

A Protein Internal Coordinate (.pic) file format is defined to capture
sufficient detail to reproduce a PDB file from chain starting coordinates
(first residue N, Ca, C XYZ coordinates) and remaining internal coordinates.
These files are used internally to verify that a given structure can be
regenerated from its internal coordinates.  See :mod:`.PICIO` for reading and
writing .pic files and :func:`.structure_rebuild_test` to determine if a
specific PDB or mmCif datafile has sufficient information to interconvert
between cartesian and internal coordinates.

Internal coordinates may also be exported as `OpenSCAD <https://www.openscad.org>`_
data arrays for generating 3D printed protein models.  OpenSCAD software is
provided as a starting point and proof-of-concept for generating such models.
See :mod:`.SCADIO` and this `Thingiverse project <https://www.thingiverse.com/thing:3957471>`_
for a more advanced example.

Refer to :meth:`.distance_plot` and :meth:`.distance_to_internal_coordinates`
for converting structure data to/from 2D distance plots.

The following classes comprise the core functionality for processing internal
coordinates and are sufficiently related and coupled to place them together in
this module:

:class:`IC_Chain`: Extends Biopython Chain on .internal_coord attribute.
    Manages connected sequence of residues and chain breaks; holds numpy arrays
    for all atom coordinates and bond geometries. For 'parallel' processing
    IC_Chain methods operate on these arrays with single numpy commands.

:class:`IC_Residue`: Extends Biopython Residue on .internal_coord attribute.
    Access for per residue views on internal coordinates and methods for serial
    (residue by residue) assembly.

:class:`Dihedron`: four joined atoms forming a dihedral angle.
    Dihedral angle, homogeneous atom coordinates in local coordinate space,
    references to relevant Hedra and IC_Residue.  Getter methods for
    residue dihedral angles, bond angles and bond lengths.

:class:`Hedron`: three joined atoms forming a plane.
    Contains homogeneous atom coordinates in local coordinate space as well as
    bond lengths and angle between them.

:class:`Edron`: base class for Hedron and Dihedron classes.
    Tuple of AtomKeys comprising child, string ID, mainchain membership boolean
    and other routines common for both Hedra and Dihedra.  Implements rich
    comparison.

:class:`AtomKey`: keys (dictionary and string) for referencing atom sequences.
    Capture residue and disorder/occupancy information, provides a
    no-whitespace key for .pic files, and implements rich comparison.

Custom exception classes: :class:`HedronMatchError` and
:class:`MissingAtomError`
    N)deque)
namedtuple)Integral)cast)Optional)TextIO)TYPE_CHECKING)Union)protein_letters_3to1)Atom)DisorderedAtom)dihedra_primary_defaults)hedra_defaults)ic_data_backbone)ic_data_sidechain_extras)ic_data_sidechains)primary_angles)residue_atom_bond_state)coord_space)multi_coord_space)multi_rot_Z)Residue)AtomKeyr   r   )r   r   r   r   r   r   c                   @   s  e Zd ZU dZdZ	 dZ	 dZ	 dZej	e
d< 	 dZ	 dZ	 e	g dZe	g dZdbd
eddfddZdcddZdededededef
ddZdddddee fddZdd Z		dbdddeded
edef
d d!Zdbd
eddfd"d#Zddd$d%Zddd&d'Zd(eeef d)eeef d*eeef d+eeef d,eeef ddfd-d.Zdbd
eddfd/d0Z 				ded
ed1ee! d2ee! ddfd3d4Z"dbd
eddfd5d6Z#ddd7d8Z$dfd9eej% ddfd:d;Z&ddd<d=Z'				ded
ed1ee! d2ee! ddfd>d?Z(dbd
eddfd@dAZ)dddBdCZ*e+dDe,dEej	ddfdFdGZ-e+dDe,dHdIdJedKe.e/ ddf
dLdMZ0dgdDe,dNeddfdOdPZ1	dfdQee2ej%df  dej%fdRdSZ3dej%fdTdUZ4dVej%dWej%ddfdXdYZ5	dhdZee2edf  ddfd[d\Z6did^d_Z7d`da Z8dS )jIC_Chaina%  Class to extend Biopython Chain with internal coordinate data.

    Attributes
    ----------
    chain: object reference
        The Biopython :class:`Bio.PDB.Chain.Chain` object this extends

    MaxPeptideBond: float
        **Class** attribute to detect chain breaks.
        Override for fully contiguous chains with some very long bonds - e.g.
        for 3D printing (OpenSCAD output) a structure with missing residues.
        :data:`MaxPeptideBond`

    ParallelAssembleResidues: bool
        **Class** attribute affecting internal_to_atom_coords.
        Short (50 residue and less) chains are faster to assemble without the
        overhead of creating numpy arrays, and the algorithm is easier to
        understand and trace processing a single residue at a time.  Clearing
        (set to False) this flag will switch to the serial algorithm

    ordered_aa_ic_list: list
        IC_Residue objects internal_coords algorithms can process (e.g. no
        waters)

    initNCaC: List of N, Ca, C AtomKey tuples (NCaCKeys).
        NCaCKeys start chain segments (first residue or after chain break).
        These 3 atoms define the coordinate space for a contiguous chain
        segment, as initially specified by PDB or mmCIF file.

    AAsiz = int
        AtomArray size, number of atoms in this chain

    atomArray: numpy array
        homogeneous atom coords ([x,, y, z, 1.0]) for every atom in chain

    atomArrayIndex: dict
        maps AtomKeys to atomArray indexes

    hedra: dict
        Hedra forming residues in this chain; indexed by 3-tuples of AtomKeys.

    hedraLen: int
        length of hedra dict

    hedraNdx: dict
        maps hedra AtomKeys to numeric index into hedra data arrays e.g.
        hedraL12 below

    a2ha_map: [hedraLen x 3]
        atom indexes in hedraNdx order

    dihedra: dict
        Dihedra forming residues in this chain; indexed by 4-tuples of AtomKeys.

    dihedraLen: int
        length of dihedra dict

    dihedraNdx: dict
        maps dihedra AtomKeys to dihedra data arrays e.g. dihedraAngle

    a2da_map : [dihedraLen x 4]
        AtomNdx's in dihedraNdx order

    d2a_map : [dihedraLen x [4]]
        AtomNdx's for each dihedron (reshaped a2da_map)

    Numpy arrays for vector processing of chain di/hedra:

    hedraL12: numpy array
        bond length between hedron 1st and 2nd atom
    hedraAngle: numpy array
        bond angle for each hedron, in degrees
    hedraL23: numpy array
        bond length between hedron 2nd and 3rd atom

    id3_dh_index: dict
        maps hedron key to list of dihedra starting with hedron, used by
        assemble and bond_rotate to find dihedra with h1 key

    id32_dh_index: dict
        like id3_dh_index, find dihedra from h2 key

    hAtoms: numpy array
        homogeneous atom coordinates (3x4) of hedra, central atom at origin

    hAtomsR: numpy array
        hAtoms in reverse orientation

    hAtoms_needs_update: numpy array of bool
        indicates whether hAtoms represent hedraL12/A/L23

    dihedraAngle: numpy array
        dihedral angles (degrees) for each dihedron

    dAtoms: numpy array
        homogeneous atom coordinates (4x4) of dihedra, second atom at origin

    dAtoms_needs_update: numpy array of bool
        indicates whether dAtoms represent dihedraAngle

    dCoordSpace: numpy array
        forward and reverse transform matrices standardising positions of first
        hedron.  See :data:`dCoordSpace`.

    dcsValid: bool
        indicates dCoordSpace up to date

    See also attributes generated by :meth:`build_edraArrays` for indexing
    di/hedra data elements.

    Methods
    -------
    internal_to_atom_coordinates:
        Process ic data to Residue/Atom coordinates; calls assemble_residues()
    assemble_residues:
        Generate IC_Chain atom coords from internal coordinates (parallel)
    assemble_residues_ser:
        Generate IC_Residue atom coords from internal coordinates (serial)
    atom_to_internal_coordinates:
        Calculate dihedrals, angles, bond lengths (internal coordinates) for
        Atom data
    write_SCAD:
        Write OpenSCAD matrices for internal coordinate data comprising chain;
        this is a support routine, see :func:`.SCADIO.write_SCAD` to generate
        OpenSCAD description of a protein chain.
    distance_plot:
        Generate 2D plot of interatomic distances with optional filter
    distance_to_internal_coordinates:
        Compute internal coordinates from distance plot and array of dihedral
        angle signs.
    make_extended:
        Arbitrarily sets all psi and phi backbone angles to 123 and -104 degrees.

    gffffff?Tr   N	atomArray)TTTF)TTTTFverbosereturnc                 C   sF   || _ g | _g | _ttj| _i | _i | _	i | _
g | _| | dS )zInitialize IC_Chain object, with or without residue/Atom data.

        :param Bio.PDB.Chain parent: Biopython Chain object
            Chain object this extends
        N)chainordered_aa_ic_list	initNCaCsnpsquarer   MaxPeptideBondsqMaxPeptideBondhedradihedraatomArrayIndexbpAtomArray_set_residues)selfparentr    r,   K/var/www/html/myenv/lib/python3.10/site-packages/Bio/PDB/internal_coords.py__init__  s   zIC_Chain.__init__c                    s  | t| d}|r|S t| | j  |t| < |t| j  _t| jj| j_t| jj	| j_	t| j
| _
t| j| _t| j| _| j  _| j  _| j  _t| j| _t| j| _t| j| _t| j| _| j _dg j  _ fddfdd} jD ]7}||j t|j||_t|j||_t|j||_t|j||_t|j||_t|j||_q| j _t| j| _| j _| j  _| j  _| j   _ t| j!| _!| j" _"| j#  _#| j$  _$t| j%| _%| j&  _&| j'  _'| j(  _(| j)  _)| j*  _*| j+  _+| j,  _,| j-  _-| j.  _.| j/  _/| j0  _0| j1  _1| j2  _2| j3  _3| j4  _4 j5 D ]} j3d |j6 |_7 j3d |j6 |_8qs S )	z Implement deepcopy for IC_Chain.FNc                    s8   t | j|} j| } j|ddf |_| j|< d S )Nr      )r   internal_coordr'   r   coordr(   resatmakndx)dupr,   r-   	setAtomVw  s   
z(IC_Chain.__deepcopy__.<locals>.setAtomVwc                    s@   |   D ]}| r|j D ]} | | qq | | qd S N)	get_atomsis_disordered
child_dictvaluesr3   r4   altAtom)r8   r,   r-   setResAtmVws$  s   z+IC_Chain.__deepcopy__.<locals>.setResAtmVwsr      )9getidtype__new__	__class__r   copydeepcopyr<   
child_listaksetaktupler   r'   atomArrayValidr   r%   r&   id3_dh_indexid32_dh_indexAAsizr(   residuerprevrnextak_setakcr$   r    hedraLenhedraL12
hedraAnglehedraL23hedraNdx
dihedraLendihedraAngledihedraAngleRads
dihedraNdxa2da_mapa2d_mapd2a_mapdH1ndxdH2ndxhAtomshAtomsRhAtoms_needs_updatedRevdFwddAtoms_needs_updatedAtomsa4_pre_rotationdCoordSpacedcsValidr=   r6   cstrcst)r*   memoexistingr@   ricdr,   )r7   r8   r-   __deepcopy__  sx   
	
zIC_Chain.__deepcopy__a0a1cutoffsqCutoffc                 C   s   |t t |j|j kS r9   )r!   sumr"   r1   )r*   rt   ru   rv   rw   r,   r,   r-   _atm_dist_chk`  s   zIC_Chain._atm_dist_chkprevr   currc                 C   s  dt |jkr	d S dt |jkrdt |jkrdS |jjsdS |jdd }|jdd }|d u s5|d u rDd|d u r?d dS d dS |jd	d }|jdd }|d u sZ|d u r\d
S tjrm| rf|j}| rm|j}tjsx| s| s| 	||t
j| j}|rd S dt
j dS g }g }	| r||j  n|g}| r|	|j  n|g}	|D ]}
|	D ]}| 	|
|t
j| jr  d S qqdt
j dS )Nr   zPIC data missing atomsz1previous residue not standard/accepted amino acidNCzmissing z
previous Cz atomCAz previous residue missing N or CazMaxPeptideBond (z angstroms) exceeded)lenr<   r0   isAcceptrB   
IC_Residue	no_altlocr;   selected_childry   r   r#   r$   extendr=   )r*   rz   r{   NatompCatompCAatompNatomdcNlistpClistncr,   r,   r-   _peptide_checkd  s^   zIC_Chain._peptide_checkc                 C   s   | j  D ]}d|_qdS )z5Clear residue internal_coord settings for this chain.N)r   get_residuesr0   )r*   r3   r,   r,   r-   clear_ic  s   zIC_Chain.clear_icr3   last_reslast_ord_resc           	         s  t   _|  j_ j}dt|k r5||kr5| |d j du r5|D ]}|j j |j| q#dS t	 fdddD r|rlt|dkrl||krTd|
  }ntt| |d j }td|
  d	|  |t|d
t|dt|df}| j| dS dS )a.  Set rprev, rnext, manage chain break.

        Returns True for no chain break or residue has sufficient data to
        restart at this position after a chain break (sets initNCaC AtomKeys
        in this case).  False return means insufficient data to extend chain
        with this residue.
        r   NTc                 3       | ]}| j v V  qd S r9   )r<   .0r4   r3   r,   r-   	<genexpr>      z(IC_Chain._add_residue.<locals>.<genexpr>r|   r~   r}   zdisordered residues after zchain break at z due to r|   r~   r}   F)r   r0   cicr   r   rP   rR   appendrQ   all
pretty_strr   strprint	split_aklr   r    r   )	r*   r3   r   r   r   rq   rz   reasoniNCaCr,   r   r-   _add_residue  s0   
zIC_Chain._add_residuec                 C   s   g }g }t  }| j D ]_}|jd dks|jd tjv rkg }d| krFtjsF|j	 D ]}| 
||||rD||j ||jj q-n| 
||||r[||j ||jj dt|k ri| j| |}|}q|| _t| j| _dS )a  Initialize .internal_coord for loaded Biopython Residue objects.

        Add IC_Residue as .internal_coord attribute for each :class:`.Residue`
        in parent :class:`Bio.PDB.Chain.Chain`; populate ordered_aa_ic_list with
        :class:`IC_Residue` references for residues which can be built (amino
        acids and some hetatms); set rprev and rnext on each sequential
        IC_Residue, populate initNCaC at start and after chain breaks.

        Generates:
            self.akset : set of :class:`.AtomKey` s in this chain
        r       N)setr   r   rC   r   accept_resnamesr;   r   r<   r=   r   r   r0   updaterS   r   r   r   rJ   sortedr    )r*   r   r   r   rJ   r3   this_resrr,   r,   r-   r)     s.   zIC_Chain._set_residuesc                    s    fddfdd}t  j _tt j _tt jt j _	t
j jtd _t
j jdft
jd _d jddd	f< dg j  _ jD ]}||j |ji kr^|  qNdS )
a  Build :class:`IC_Chain` numpy coordinate array from biopython atoms.

        See also :meth:`.init_edra` for more complete initialization of IC_Chain.

        Inputs:
            self.akset : set
                :class:`AtomKey` s in this chain

        Generates:
            self.AAsiz : int
                number of atoms in chain (len(akset))
            self.aktuple : AAsiz x AtomKeys
                sorted akset AtomKeys
            self.atomArrayIndex : [AAsiz] of int
                numerical index for each AtomKey in aktuple
            self.atomArrayValid : AAsiz x bool
                atomArray coordinates current with internal coordinates if True
            self.atomArray : AAsiz x np.float64[4]
                homogeneous atom coordinates; Biopython :class:`.Atom`
                coordinates are view into this array after execution
            rak_cache : dict
                lookup cache for AtomKeys for each residue

        c                    sp   t | j|}z j| }W n
 ty   Y d S w |j j|ddf<  j|ddf |_d j|< | j|< d S )Nr   r/   T)r   r0   r'   KeyErrorr1   r   rL   r(   r2   r*   r,   r-   setAtom  s   
z)IC_Chain.build_atomArray.<locals>.setAtomc                    sT   |   D ]#}| r"tjr | |j q|j D ]} | | qq | | qd S r9   )r:   r;   r   r   r   r<   r=   r>   )r   r,   r-   
setResAtms(  s   z,IC_Chain.build_atomArray.<locals>.setResAtmsdtype         ?Nr/   )r   rJ   rO   tupler   rK   dictzipranger'   r!   zerosboolrL   float64r   r(   r   rP   rT   _build_rak_cache)r*   r   rq   r,   )r*   r   r-   build_atomArray  s   


zIC_Chain.build_atomArrayc              	   C   s  t jd| jddft jd| _t j| jtd| _t j| jddft jd| _	d| j	dddddf< t 
| j	| _t | jd| _i }dd	 t| jD | _d
d	 t| jD }| j D ]?\}}|d }tdD ]}| j||  }|||| < qg|| j| _| j| jD ]}| j| }	|| |	 | j|	 | qq[t t| | _t || _t j| jddft jd| _d| jdddddf< t | jdf| _i }
dd	 t| jD }t j| jtd| _t j| jt j d| _!t j| jt j d| _"dd	 t| jD | _#dd | j$% D | _&dd | j$% D | _'| j$ D ]\}}|d }|dd }|dd }| j(| }tdD ]"}| j||  }||
|| < || d | || d | q9z||_)||_*| j|j) }W n( t+y   |ddd |_)|ddd |_*| j|j) }d| j|< d|_,Y nw | j|j* }| j|j) |_-| j|j* |_.|| j!|< || j"|< | j#| | ||_| jd | |_/| jd | |_0| j&| | | j'| | qt t|
 | _1| j12dd| _3| jdk| _4dd	 |D | _5t | jd| _6dS )aG  Build chain level hedra and dihedra arrays.

        Used by :meth:`init_edra` and :meth:`_hedraDict2chain`.  Should be
        private method but exposed for documentation.

        Inputs:
            self.dihedraLen : int
                number of dihedra needed
            self.hedraLen : int
                number of hedra needed
            self.AAsiz : int
                length of atomArray
            self.hedraNdx : dict
                maps hedron keys to range(hedraLen)
            self.dihedraNdx : dict
                maps dihedron keys to range(dihedraLen)
            self.hedra : dict
                maps Hedra keys to Hedra for chain
            self.atomArray : AAsiz x np.float64[4]
                homogeneous atom coordinates for chain
            self.atomArrayIndex : dict
                maps AtomKeys to atomArray
            self.atomArrayValid : AAsiz x bool
                indicates coord is up-to-date

        Generates:
            self.dCoordSpace : [2][dihedraLen][4][4]
                transforms to/from dihedron coordinate space
            self.dcsValid : dihedraLen x bool
                indicates dCoordSpace is current
            self.hAtoms : hedraLen x 3 x np.float64[4]
                atom coordinates in hCoordSpace
            self.hAtomsR : hedraLen x 3 x np.float64[4]
                hAtoms in reverse order (trading space for time)
            self.hAtoms_needs_update : hedraLen x bool
                indicates hAtoms, hAtoms current
            self.a2h_map : AAsiz x [int ...]
                maps atomArrayIndex to hedraNdx's with that atom
            self.a2ha_map : [hedraLen x 3]
                AtomNdx's in hedraNdx order
            self.h2aa : hedraLen x [int ...]
                maps hedraNdx to atomNdx's in hedron (reshaped later)
            Hedron.ndx : int
                self.hedraNdx value stored inside Hedron object
            self.dRev : dihedraLen x bool
                dihedron reversed if true
            self.dH1ndx, dH2ndx : [dihedraLen]
                hedraNdx's for 1st and 2nd hedra
            self.h1d_map : hedraLen x []
                hedraNdx -> [dihedra using hedron]
            Dihedron.h1key, h2key : [AtomKey ...]
                hedron keys for dihedron, reversed as needed
            Dihedron.hedron1, hedron2 : Hedron
                references inside dihedron to hedra
            Dihedron.ndx : int
                self.dihedraNdx info inside Dihedron object
            Dihedron.cst, rcst : np.float64p4][4]
                dCoordSpace references inside Dihedron
            self.a2da_map : [dihedraLen x 4]
                AtomNdx's in dihedraNdx order
            self.d2a_map : [dihedraLen x [4]]
                AtomNdx's for each dihedron (reshaped a2da_map)
            self.dFwd : bool
                dihedron is not Reversed if True
            self.a2d_map : AAsiz x [[dihedraNdx]
                [atom ndx 0-3 of atom in dihedron]], maps atom indexes to
                dihedra and atoms in them
            self.dAtoms_needs_update : dihedraLen x bool
                atoms in h1, h2 are current if False

        r   r   r   r/   r   NTc                 S      g | ]}g qS r,   r,   r   _r,   r,   r-   
<listcomp>      z-IC_Chain.build_edraArrays.<locals>.<listcomp>c                 S   r   r,   r,   r   r,   r,   r-   r     r   c                 S   s   g | ]}g g gqS r,   r,   r   r,   r,   r-   r         c                 S   r   r,   r,   r   r,   r,   r-   r     r   c                 S      i | ]	}|d d g qS )r   r/   r,   r   kr,   r,   r-   
<dictcomp>      z-IC_Chain.build_edraArrays.<locals>.<dictcomp>c                 S   r   )rA   r   r,   r   r,   r,   r-   r     r   r   rA   c                 S   s(   g | ]}t |d  t |d fqS r   rA   )r!   array)r   xir,   r,   r-   r     s   ( )7r!   emptyrZ   r   rk   r   r   rl   rU   rc   rG   rd   fullre   r   rO   a2h_maprY   itemsr'   r%   r6   atomkeysr   r   r   r=   a2ha_maph2aari   rj   rf   int64ra   rb   h1d_mapr]   keysrM   rN   r&   h1keyh2keyr   reversehedron1hedron2rm   rn   r^   reshaper`   rg   r_   rh   )r*   r   r   hkhndxhstepir6   r5   akndxr^   r_   dkdndxdstepdid3did32rr   h1ndxh2ndxr,   r,   r-   build_edraArraysB  s   I





zIC_Chain.build_edraArrayshl12hahl23dabfacsc                 C   s  | j D ]j}g }|j D ]8}d| kr7tjr!|t||j q|j	
 D ]}	|	jdur5|t||	 q&q|jdurD|t|| q|g krQ| jt| g |_|j|t|dt|dt|df |  q| jg kr| j d }|t|dt|dt|df}
| j|
 |   t| j| _tj\}}}}}}d}| j D ]\}}|jj}|j| |j| }}|j| du rdnt|j| }||jd}|dur|d	 n|d	 }d}||r|| }|du sd| kr?||s?t|| j| dd
 |||du rdn||||d }|du r9|du r&| | qt!|}| | |"| |#  q|"| qd| krQ||rQ|$| |%| |&| |' }qt(|| _)t*j+|
 t*j,d| _-t*j+|
 t*j,d| _.t*j+|
 t*j,d| _/t0t1t|2 t3| j)| _4t(|| _5t*j+|
 t*j,d| _6t*7| j6| _8t0t1t|2 t3| j5| _9| :  dS )a  Generate chain numpy arrays from :func:`.read_PIC` dicts.

        On entry:
            * chain internal_coord has ordered_aa_ic_list built, akset;
            * residues have rnext, rprev, ak_set and di/hedra dicts initialised
            * Chain, residues do NOT have NCaC info, id3_dh_index
            * Di/hedra have cic, atomkeys set
            * Dihedra do NOT have valid reverse flag, h1/2 info

        r   Nr|   r~   r}   r   r           rA   r/   r   r   );r   rP   r:   r;   r   r   r   r   r   r<   r=   r1   r    r   NCaCKeyr   r   _link_dihedrar   r   fieldsr'   r   rq   aklfloatrB   rC   has_iddisordered_has_idr   r   addr   disordered_addflag_disordereddisordered_selectset_bfactorset_occupancyget_serial_numberr   rU   r!   fromiterr   rV   rW   rX   r   r   r   r   rY   rZ   r[   deg2radr\   r]   r   )r*   r   r   r   r   r   rq   initNCaCr4   r?   r   spNdxicNdxresnNdxatmNdx	altlocNdxoccNdxsnr5   r6   r3   altlococcbfacbpAtmnewAtomdisordered_atomr,   r,   r-   _hedraDict2chain  s   





 












zIC_Chain._hedraDict2chainc              	   C   s  | j }| j}| j}| j}| j}| j}| jd }| j}	|| ddd| _	| j	}
|| dd| _
| j
}|| jkjdd}d| jt|< d}|rO|j|  }tj}||kjdd}d}t|rt|}|
| }||df d }t|	| r~|| }nt|t|d	d }|| ddd}td
||dddf ||< d	||< d|dd< |D ]#}|| |
|| < || }|d D ]}|||  }||k ||< qq|d7 }t|sa|r| jj}t|d  d|d  d| d| d dS dS )a7  Generate atom coords from internal coords (vectorised).

        This is the 'Numpy parallel' version of :meth:`.assemble_residues_ser`.

        Starting with dihedra already formed by :meth:`.init_atom_coords`, transform
        each from dihedron local coordinate space into protein chain coordinate
        space.  Iterate until all dependencies satisfied.

        Does not update :data:`dCoordSpace` as :meth:`assemble_residues_ser`
        does.  Call :meth:`.update_dCoordSpace` if needed.  Faster to do in
        single operation once all atom coordinates finished.

        :param bool verbose: default False.
            Report number of iterations to compute changed dihedra

        generates:
            self.dSet: AAsiz x dihedraLen x 4
                maps atoms in dihedra to atomArray
            self.dSetValid : [dihedraLen][4] of bool
                map of valid atoms into dihedra to detect 3 or 4 atoms valid

        Output coordinates written to :data:`atomArray`.  Biopython
        :class:`Bio.PDB.Atom` coordinates are a view on this data.
        rA   r   r   axisFNr   r/   Tz
ijk,ik->ijr   r   z coordinates for z dihedra updated in z iterations)r^   r_   r`   r   rL   ri   rk   rl   r   dSet	dSetValid
_dihedraOKr   r!   logical_notsizerx   r   _dihedraSelectanywherer   einsumr   full_idr   )r*   r   r^   r_   r`   r   rL   ri   dCoordSpace1rl   r  r  workSelector
dihedraWrktarg	loopCountworkNdxsworkSet	updateMapcspace
initCoordsaadlistrr   dvalidcidr,   r,   r-   assemble_residuesc  s`   




"zIC_Chain.assemble_residuesstartfinc                 C   s   d| j dd< | jD ]4}|r||jjd k s |r/||jjd kr/d|_d|_i |j_g |j_q
|j|d}|r>t	|
 |_q
dS )a  Generate IC_Residue atom coords from internal coordinates (serial).

        See :meth:`.assemble_residues` for 'numpy parallel' version.

        Filter positions between start and fin if set, find appropriate start
        coordinates for each residue and pass to :meth:`.assemble`

        :param bool verbose: default False.
            Describe runtime problems
        :param int start,fin: default None.
            Sequence position for begin, end of subregion to generate coords
            for.
        FNrA   r   )rl   r   rP   rC   rS   rT   r<   rI   assembler   r   )r*   r   r)  r*  rq   atom_coordsr,   r,   r-   assemble_residues_ser  s    
zIC_Chain.assemble_residues_serc                 C   s  | j d ji kr| j D ]}|j|d qt| ds|   t| ds|t| j| _tj| jtj	d| _
tj| jtj	d| _tj| jtj	d| _ttt| j tt| j| _t| j| _t| j| _t| j| _ttt| j t| j| _t| ds|   dS dS )a  Create chain and residue di/hedra structures, arrays, atomArray.

        Inputs:
            self.ordered_aa_ic_list : list of IC_Residue
        Generates:
            * edra objects, self.di/hedra (executes :meth:`._create_edra`)
            * atomArray and support (executes :meth:`.build_atomArray`)
            * self.hedraLen : number of hedra in structure
            * hedraL12 : numpy arrays for lengths, angles (empty)
            * hedraAngle ..
            * hedraL23 ..
            * self.hedraNdx : dict mapping hedrakeys to hedraL12 etc
            * self.dihedraLen : number of dihedra in structure
            * dihedraAngle ..
            * dihedraAngleRads : np arrays for angles (empty)
            * self.dihedraNdx : dict mapping dihedrakeys to dihedraAngle
        r   r+  rL   rU   r   re   N)r   r%   _create_edrahasattrr   r   rU   r!   r   r   rV   rW   rX   r   r   r   r   r   rY   r&   rZ   r[   r\   r]   r   )r*   r   rq   r,   r,   r-   	init_edra  s(   


$
zIC_Chain.init_edrac                    s  t  js"  j j j  j j B O  _  jt  jM  _ j j@ } j	 j@ } j j } j	 j }	 t 
 jrqt d j j  }t |}t |d }	  j j  jddddf  j< | j j   jddddf  j< | j j   jddddf  j< 	  j j  jddddf  j< | j j   jddddf  j< | j j   jddddf  j< 	 d jd< t  j} j jdf |  j|<  j jdf |  j|< t  jdddf  j d jdddf  j< t |}	 j j | |	|<  j j | |	|< t  jdddf  j |	 jdddf  j< 	  j j }
 j j }|
|  jddddf |< |dddddf |  jddddf |< 	 	 t j j }t | j j dd dd	d
dd	}||  jdddf |< ||  jdddf |< 	 d jd< 	  jD ]=}d}t  j fdd|D  rd}|r j j|  }t dD ]} j!||  }||  j"|< d j|< qڐqdS )a  Set chain level di/hedra initial coords from angles and distances.

        Initializes atom coordinates in local coordinate space for hedra and
        dihedra, will be transformed appropriately later by :data:`dCoordSpace`
        matrices for assembly.
             f@r   Nr   r   F.r/   r   rA   Tc                       g | ]} j | qS r,   r'   r   r5   r   r,   r-   r         z-IC_Chain.init_atom_coords.<locals>.<listcomp>)#r!   r   rh   re   ra   rb   rl   r  rg   rf   r  r   rW   sincosrX   rc   rV   rd   rx   rj   multiplyr   r   ri   r   r\   matmulr   r    rL   rY   r   r'   r   )r*   mdFwdmdRevudFwdudRevsarsinSarcosSarNdhlena4shiftdH1atoms	dH1atomsRrza4rotr   invalidhatomsr   andxr,   r   r-   init_atom_coords"  s   



0

 zIC_Chain.init_atom_coordsr  c                 C   sT   |du r|    t| j}| j| }t|t|d| jdd|f< d| j|< dS )a(  Compute/update coordinate space transforms for chain dihedra.

        Requires all atoms updated so calls :meth:`.assemble_residues`
        (returns immediately if all atoms already assembled).

        :param [bool] workSelector:
            Optional mask to select dihedra for update
        NT)r(  r!   r  rl   r  r   rx   rk   )r*   r  r   r,   r,   r-   update_dCoordSpace  s   	
zIC_Chain.update_dCoordSpacec                 C   s(  d}t | j}tjj}tjj}t }||k r| j| d }| j| }|d }||kr.| j}	n| j| d }
| j|
 }	t	||	D ]H}| j
| s| j| }|j| }|j| }|dv rbd| j
||	<  n&||vr|dkrt	||	D ]}| j| j| |krd| j
|< qo || q?|d7 }||k sdS dS )z5Track through di/hedra to invalidate dependent atoms.r   rA   r   FHN)r   r    r   r   r4   resposr   r'   rO   r   rL   rK   r   r   )r*   csNdxcsLenr  posNdxdonestartAKcsStartcsnTrycsNextfinAKrJ  r5   r4   posr   r,   r,   r-   propagate_changes  s<   







zIC_Chain.propagate_changesc              	   C   s$  t | dsdS tjr\|s\|s\|   |   | j|d |rVt| jsX| j| j	 
dd}| jD ](}|j D ]}||j  sTtd|jjj d|  d|j  q8q1dS dS dS |r| jD ]"}||jjd	 krlqa|t|d
t|dt|df}| j| qa|   | j|||d dS )a  Process IC data to Residue/Atom coords.

        :param bool verbose: default False.
            Describe runtime problems
        :param int start,fin:
            Optional sequence positions for begin, end of subregion
            to process.

        .. note::
            Setting start or fin activates serial :meth:`.assemble_residues_ser`
            instead of (Numpy parallel) :meth:`.assemble_residues`.
            Start C-alpha will be at origin.

        .. seealso::
            :data:`ParallelAssembleResidues`

        rh   Nr+  r   r   zmissing coordinates for chain r   z dihedral: rA   r|   r~   r}   )r   r)  r*  )r0  r   ParallelAssembleResiduesrY  rK  r(  r!   r   rL   r^   r   r   r&   r=   r6   r   r   r   rC   r   rP   r   r   r    r   r.  )r*   r   r)  r*  r  rq   rr   r   r,   r,   r-   internal_to_atom_coordinates  sN   


	

z%IC_Chain.internal_to_atom_coordinatesc                 C   s  | j g krdS | j|d | ji krdS | j| j ddd}tjj|dddf |dddf  dd| _	tjj|dddf |ddd	f  dd| _
tjj|dddf |ddd	f  dd}tjtt| j	t| j
 t| d	| j	 | j
  | jd
 | j| j ddd}t|| jd| jd< d| jdd< t| jd |dddf ddddd}tj|dddf |dddf | jd
 tj| j| jd
 t| dr|   dS dS )al  Calculate dihedrals, angles, bond lengths for Atom data.

        Generates atomArray (through init_edra), value arrays for hedra and
        dihedra, and coordinate space transforms for dihedra.

        Generates Gly C-beta if specified, see :data:`IC_Residue.gly_Cbeta`

        :param bool verbose: default False.
            describe runtime problems
        Nr+  r   r/   r   r   rA   r  r   outT.gcb)r   r1  r&   r   r   r   r!   linalgnormrV   rX   rad2degarccosr"   rW   r^   r   rZ   rk   rl   r:  arctan2r\   r[   r0  _spec_glyCB)r*   r   r   h_a0a2dhado4r,   r,   r-   atom_to_internal_coordinatesV  s<   

..,

0*
z%IC_Chain.atom_to_internal_coordinatesc                 C   s:  d}t | dr|| j9 }| j D ]}|d }d| j| j| < |j}|d|d|d|df\}}}}| j| }	|	j	}
|	j
j	}|| j|< d	| j|< | j| j|||f  | j|< d
| j|	j
j	< |	j
jD ]
}d| j| j| < qg| j||||fd}|rd| j|j	  }|dkr|n|d | j|
< qd| j|
< qdS )zPopulate values for Gly C-beta.gb?scaler/   Fr|   r~   r}   OguT5[@TNg!> ^@r2       v@x   )r0  ri  r^  r=   rL   r'   rq   rakr&   r6   r   rV   rW   rY   rX   re   r   rB   r[   )r*   	Ca_Cb_Lengcbdcbakrq   rNrCArCrOgCBdr   r   r5   refvalanglr,   r,   r-   rd    s6   




zIC_Chain._spec_glyCBfpmtxc                 C   s   |  d d}|D ]2}|r|  d n|  d d}d}|D ]}|r,|  dt|  q|  t| d}q|  d q	|  d d S )N[ Fz, [ T,  ])writer   )rx  ry  rowsStartedrowcolsStartedcolr,   r,   r-   
_write_mtx  s   

zIC_Chain._write_mtxrr   DihedronrY   hedraSetc                 C   s   |  d|jdd||j  d||j  d|jrdnd d	 |  |j|v r(dnd d|j|v r2dnd d |  d|j|jj|jj|jrIdnd |  d	 t	
| |j |  d
 d S )Nrz  9.5fr{  rA   r   z    // {} [ {} -- {} ] {}
reversed z        r|  )r}  angler   r   r   formatrC   r   r   r   r  rn   )rx  rr   rY   r  r,   r,   r-   _writeSCAD_dihed  s&   6
zIC_Chain._writeSCAD_dihedbackboneOnlyc           ,   
      s  | d jj d i } jD ]*}|jjd }|dur#||d k r#q|dur,||kr,q|j D ]\}}	|	||< q1qt }
i }t }d}i }t|D ]
}|||< |d7 }qK| d i }i }d}d} jD ]}|jjd }|durw||k rwqf|dur||krqfd|j	vr|j
d	kr|j
d
krtd| d|j
 d |||< |r| d nd}| dt| d t|jj d |j
 d  |d7 }|  |jdd d}d}t|rdndD ]}|dkr|rdnd}| | d|jjd|j
 d d}t|j D ]b\}}|j|v rb|dkr| s|dkrb| sb|jj|j rU|r0| d nd}| d t|||| |||< ||j ||j |d7 }qtd|j d|j d qqqf| d | d | d  t|D ]}|| }| d! | t|jd"d#t|jd"d#t|jd" d}d}d}|j D ]}|j!t"j#j$ }|j!t"j#j% }t&d$ } | '|d}!d%|d krd&}!|!du rt&'|d} | dur| '|d}!nd}!|d'|! d( 7 }||
v r|d)7 }ng||v rXt(|d*st(|d+r$|dkr$|dkr|d)7 }nG|dkr#|d,7 }|
| n8t(|d-s0t(|d.rN|dkrN|dkr?|d)7 }n|dkrM|d,7 }|
| n|d,7 }|
| n|d)7 }|d7 }q| | | | g }"|")|j d jd |j d j  |")|j d jd |j d j  d}#|"D ]z}$d}%|$|v r||$ d/krd)}%n_||v rd/}&|#rt(|d*rd0}&n5t(|d+rd1}&n,t(|d2rd3}&n#t(|d4rd5}&nt(|d-rd0}&nt(|d.rd1}&nt(|d6rd5}&|$|v rd/||$< n|&||$< d#t|& }%nd)}%| |% d}#q|j d j!}'| d'|'t"j#j%  d7 |'t"j#j*  d' |j+ d(  | d8t| d9  qy| d: d j,dd<  -  | d; d} jD ]}|jjd }|durj||k rjqV|durv||krvqV|j D ]	\}}	|	||< q{t|j.D ]g}(d})dt/|j0k r fd<d=|(D }*t1|*d |*d |*d d\}+})nt2j3d>t2j4d?})|r| d nd}| d!t||  d' t|jjd   | |j
d@ t|( d9  t5||) | dA qqV| dB dS )CzWrite self to file fp as OpenSCAD data matrices.

        See `OpenSCAD <https://www.openscad.org>`_.
        Works with :func:`.write_SCAD` and embedded OpenSCAD routines therein.
        z   "z", // chain id
rA   Nr   z!   [  // residue array of dihedraFrj  GAz*Unable to generate complete sidechain for r   z missing O atomz
     ],Tz
     [ // z : z
 backbone
)resetLocationr   ,r  z
       // z sidechain
z,
z      zAtom missing for -z%, OpenSCAD chain may be discontiguousz   ],z
  ],
z   [  //hedra
z     [ r  r{  XrM  Hsbz, ""z, 0flex_female_1flex_male_1z, 1flex_female_2flex_male_2StdBondFemaleJoinBondMaleJoinBondskinny_1
SkinnyBondhbond_1HBondhbond_2z", z ], // 
z   ],
z0
[  // chain - world transform for each residue
c                    s   g | ]
} j  j|  qS r,   )r   r'   r5  r   r,   r-   r     s    z(IC_Chain._write_SCAD.<locals>.<listcomp>r   r   z", //r|  z
   ]
)6r}  r   rC   r   rP   r%   r   r   r   rT   lcr   r   clear_transformsr,  r   r&   r   is_backboner   rl   r6   r   r  r   r   id3id32set_accuracy_95len12r  len23r   r   r   r   r4   resnamer   rB   r0  r   rN  e_classrL   r[  r   r   rQ   r   r!   identityr   r  ),r*   rx  r  r)  r*  r%   rq   rN  r   hatomSetbondDictr  r6   rY   r   resNdxr]   
chnStartedndx2startedr   cmar   rr   hedatom_stratom_done_strr   r5   r4   r3   ab_state_resab_statebondb0bwstrbondTyper   r   mtraclmtr,   r   r-   _write_SCAD  s  






	(




*

















""





	


 *zIC_Chain._write_SCADfilterc                 C   sR   |du r| j }n| j | }tjj|dddddf |dddddf  ddS )a  Generate 2D distance plot from atomArray.

        Default is to calculate distances for all atoms.  To generate the
        classic C-alpha distance plot, pass a boolean mask array like::

            atmNameNdx = internal_coords.AtomKey.fields.atm
            CaSelect = [
                atomArrayIndex.get(k)
                for k in atomArrayIndex.keys()
                if k.akl[atmNameNdx] == "CA"
            ]
            plot = cic.distance_plot(CaSelect)

        Alternatively, this will select all backbone atoms::

            backboneSelect = [
                atomArrayIndex.get(k)
                for k in atomArrayIndex.keys()
                if k.is_backbone()
            ]

        :param [bool] filter: restrict atoms for calculation

        .. seealso::
            :meth:`.distance_to_internal_coordinates`, which requires the
            default all atom distance plot.

        Nr   r  )r   r!   r_  r`  )r*   r  r  r,   r,   r-   distance_plot  s   
8zIC_Chain.distance_plotc                 C   s   t | jS )zGet sign array (+1/-1) for each element of chain dihedraAngle array.

        Required for :meth:`.distance_to_internal_coordinates`
        )r!   signr[   r   r,   r,   r-   dihedral_signs  s   zIC_Chain.dihedral_signsdistplotdihedra_signsc                 C   s   | j dd}||dddf |dddf f | _||dddf |dddf f | _||dddf |dddf f | _| j}||dddf |dddf f | _|| _dS )a  Load di/hedra distance arrays from distplot.

        Fill :class:`IC_Chain` arrays hedraL12, L23, L13 and dihedraL14
        distance value arrays from input distplot, dihedra_signs array from
        input dihedra_signs.  Distplot and di/hedra distance arrays must index
        according to AtomKey mappings in :class:`IC_Chain` .hedraNdx and .dihedraNdx
        (created in :meth:`IC_Chain.init_edra`)

        Call :meth:`atom_to_internal_coordinates` (or at least :meth:`init_edra`)
        to generate a2ha_map and d2a_map before running this.

        Explicitly removed from :meth:`.distance_to_internal_coordinates` so
        user may populate these chain di/hedra arrays by other
        methods.
        r   r/   Nr   rA   r   )r   r   rV   rX   hedraL13r`   
dihedraL14r  )r*   r  r  r   r   r,   r,   r-   distplot_to_dh_arrays  s   &&&&
zIC_Chain.distplot_to_dh_arrays
resetAtomsc                 C   s  | j | j }| j| j | j || j< | j| j }| j | j | j || j< | j | j }| j| j | j || j< | j| j }| j| j }| j}|| | d }|| | d }	|||  ||  ||  }
|	|	|  |	|  |	|  }d| | | | t|| ||  || ||    d }	 t	|
}t	|}|
| | d| |  }d||dk < d||dk< tj
|| jtjd |  j| j9  _tj| j| jd tjt
t| j t| j t| j d| j  | j  | jd |rd| jd	d	< d
| jd	d	< d
| jd	d	< d	S d	S )a  Compute chain di/hedra from from distance and chirality data.

        Distance properties on hedra L12, L23, L13 and dihedra L14 configured
        by :meth:`.distplot_to_dh_arrays` or alternative loader.

        dihedraAngles result is multiplied by dihedra_signs at final step
        recover chirality information lost in distance plot (mirror image of
        structure has same distances but opposite sign dihedral angles).

        Note that chain breaks will cause errors in rebuilt structure, use
        :meth:`.copy_initNCaCs` to avoid this

        Based on Blue, the Hedronometer's answer to `The dihedral angles of a tetrahedron
        in terms of its edge lengths <https://math.stackexchange.com/a/49340/972353>`_
        on `math.stackexchange.com <https://math.stackexchange.com/>`_.  See also:
        `"Heron-like Hedronometric Results for Tetrahedral Volume"
        <http://daylateanddollarshort.com/mathdocs/Heron-like-Results-for-Tetrahedral-Volume.pdf>`_.

        Other values from that analysis included here as comments for
        completeness:

        * oa = hedron1 L12 if reverse else hedron1 L23
        * ob = hedron1 L23 if reverse else hedron1 L12
        * ac = hedron2 L12 if reverse else hedron2 L23
        * ab = hedron1 L13 = law of cosines on OA, OB (hedron1 L12, L23)
        * oc = hedron2 L13 = law of cosines on OA, AC (hedron2 L12, L23)
        * bc = dihedron L14

        target is OA, the dihedral angle along edge oa.

        :param bool resetAtoms: default True.
            Mark all atoms in di/hedra and atomArray for updating by
            :meth:`.internal_to_atom_coordinates`.  Alternatively set this to
            False and manipulate `atomArrayValid`, `dAtoms_needs_update` and
            `hAtoms_needs_update` directly to reduce computation.
        r   r      g      r   )r]  r   r\  FNT)rV   ra   rX   rg   rb   r  r  r!   r"   sqrtrb  r\   
longdoubler  ra  r[   rW   rL   rh   re   )r*   r  oaobacabocbcYsZsYsqrZsqrHsqrYZcosOAr,   r,   r-    distance_to_internal_coordinates  sT   '8





z)IC_Chain.distance_to_internal_coordinatesotherc                    s2    fdd|j D }|j|  j|< d j|< dS )aF  Copy atom coordinates for initNCaC atoms from other IC_Chain.

        Copies the coordinates and sets atomArrayValid flags True for initial
        NCaC and after any chain breaks.

        Needed for :meth:`.distance_to_internal_coordinates` if target has
        chain breaks (otherwise each fragment will start at origin).

        Also useful if copying internal coordinates from another chain.

        N.B. :meth:`IC_Residue.set_angle()` and :meth:`IC_Residue.set_length()`
        invalidate their relevant atoms, so apply them before calling this
        function.
        c                        g | ]}|D ]} j | qqS r,   r4  )r   r   r5   r   r,   r-   r          z+IC_Chain.copy_initNCaCs.<locals>.<listcomp>TN)r    r   rL   )r*   r  r6   r,   r   r-   copy_initNCaCs  s   zIC_Chain.copy_initNCaCsc                 C   s(   | j D ]}|dd |dd qdS )z@Set all psi and phi angles to extended conformation (123, -104).psi{   phiiN)r   	set_angle)r*   rq   r,   r,   r-   make_extended  s   
zIC_Chain.make_extendedF)r   r   r   N)FNNr9   NNT)r  r   r   N)9__name__
__module____qualname____doc__r#   rZ  rO   r   r!   r   __annotations__rk   rl   r  r  r   r.   rs   r   r   ry   r   r   r   r   listr   r)   r   r   r   r  r(  intr.  r1  rK  ndarrayrL  rY  r[  rh  rd  staticmethodr   r  r   EKTr  r  r	   r  r  r  r  r  r  r,   r,   r,   r-   r   8  s   
  	
e?

1
*
? '





{l
$
1 -
1
F
5$ }
(


tr   c                   @   s  e Zd ZU dZdZ	 dZeed< 	 dZeed< 	 dZ	eed< 	 dZ
eed< d	Zd
Zee ZdZdZee Z	 dddZdd ZdddefddZdeeef ddfddZdddZdeddfddZdefd d!Zdefd"d#Zdd$eddfd%d&Zdd'd(Zdd)d*Zdede j!f fd+d,Z"dede j!f fd-d.Z#d/d0 Z$		dd1ed$edeede j!f ee%e j!f df fd2d3Z&	dd4ee'd5 e(d f d6ede(e'd5  fd7d8Z)d4ee'd5 e(d f ddfd9d:Z*dd$eddfd;d<Z+dZ,dZ-e.dded=edefd>d?Z/defd@dAZ0e.dBddefdCdDZ1e2dEg dFZ3dGdH e4dID Z5e5dJ e5dK B e5dL B e5dM B e5dN B Z6e5dO e5dP B e5dQ B e5dR B Z7e7e6B Z8e5dS e5dT B e5dU B Z9e3e5dO e5dV e5dP e5dQ e5dJ e5dK e5dL e5dM e5dN e5dR e6e7e8e5dS e5dT e5dU e9e5dW e5dX Z:	 e:j;e:j<B e:j=B Z>	 e:? Z@	 dedYedZeAde'eeAf fd[d\ZBd]d^e>ddfd_ed`edaeAdbeCeeDdf  dceCeeDdf  defdddeZEdfedeCe'd5  fdgdhZFeGHdiZIdjeJdeCedk  fdldmZKdjeeJef deCedk  fdndoZLdjeeJef deCeD fdpdqZMddjeeJef dseDfdtduZNdvdwdxeDfdydzZOdjeeJef dxeDfd{d|ZPdjeeJef d}eDfd~dZQdeeeRf de'eCe(d  eCeR f fddZSdeeeRf deCeD fddZTdeeeRf d}eDddfddZUde j!ddfddZVdS )r   aH  Class to extend Biopython Residue with internal coordinate data.

    Parameters
    ----------
    parent: biopython Residue object this class extends

    Attributes
    ----------
    no_altloc: bool default False
        **Class** variable, disable processing of ALTLOC atoms if True, use
        only selected atoms.

    accept_atoms: tuple
        **Class** variable :data:`accept_atoms`, list of PDB atom names to use
        when generating internal coordinates.
        Default is::

            accept_atoms = accept_mainchain + accept_hydrogens

        to exclude hydrogens in internal coordinates and generated PDB files,
        override as::

            IC_Residue.accept_atoms = IC_Residue.accept_mainchain

        to get only mainchain atoms plus amide proton, use::

            IC_Residue.accept_atoms = IC_Residue.accept_mainchain + ('H',)

        to convert D atoms to H, set :data:`AtomKey.d2h` = True and use::

            IC_Residue.accept_atoms = (
                accept_mainchain + accept_hydrogens + accept_deuteriums
            )

        Note that `accept_mainchain = accept_backbone + accept_sidechain`.
        Thus to generate sequence-agnostic conformational data for e.g.
        structure alignment in dihedral angle space, use::

            IC_Residue.accept_atoms = accept_backbone

        or set gly_Cbeta = True and use::

            IC_Residue.accept_atoms = accept_backbone + ('CB',)

        Changing accept_atoms will cause the default `structure_rebuild_test` in
        :mod:`.ic_rebuild` to fail if some atoms are filtered (obviously).  Use
        the `quick=True` option to test only the coordinates of filtered atoms
        to avoid this.

        There is currently no option to output internal coordinates with D
        instead of H.

    accept_resnames: tuple
        **Class** variable :data:`accept_resnames`, list of 3-letter residue
        names for HETATMs to accept when generating internal coordinates from
        atoms.  HETATM sidechain will be ignored, but normal backbone atoms (N,
        CA, C, O, CB) will be included.  Currently only CYG, YCM and UNK;
        override at your own risk.  To generate sidechain, add appropriate
        entries to `ic_data_sidechains` in :mod:`.ic_data` and support in
        :meth:`IC_Chain.atom_to_internal_coordinates`.

    gly_Cbeta: bool default False
        **Class** variable :data:`gly_Cbeta`, override to True to generate
        internal coordinates for glycine CB atoms in
        :meth:`IC_Chain.atom_to_internal_coordinates` ::

            IC_Residue.gly_Cbeta = True

    pic_accuracy: str default "17.13f"
        **Class** variable :data:`pic_accuracy` sets accuracy for numeric values
        (angles, lengths) in .pic files.  Default set high to support mmCIF file
        accuracy in rebuild tests.  If you find rebuild tests fail with
        'ERROR -COORDINATES-' and verbose=True shows only small discrepancies,
        try raising this value (or lower it to 9.5 if only working with PDB
        format files).  ::

            IC_Residue.pic_accuracy = "9.5f"

    residue: Biopython Residue object reference
        The :class:`.Residue` object this extends
    hedra: dict indexed by 3-tuples of AtomKeys
        Hedra forming this residue
    dihedra: dict indexed by 4-tuples of AtomKeys
        Dihedra forming (overlapping) this residue
    rprev, rnext: lists of IC_Residue objects
        References to adjacent (bonded, not missing, possibly disordered)
        residues in chain
    atom_coords: AtomKey indexed dict of numpy [4] arrays
        **removed**
        Use AtomKeys and atomArrayIndex to build if needed
    ak_set: set of AtomKeys in dihedra
        AtomKeys in all dihedra overlapping this residue (see __contains__())
    alt_ids: list of char
        AltLoc IDs from PDB file
    bfactors: dict
        AtomKey indexed B-factors as read from PDB file
    NCaCKey: List of tuples of AtomKeys
        List of tuples of N, Ca, C backbone atom AtomKeys; usually only 1
        but more if backbone altlocs.
    is20AA: bool
        True if residue is one of 20 standard amino acids, based on
        Residue resname
    isAccept: bool
        True if is20AA or in accept_resnames below
    rbase: tuple
        residue position, insert code or none, resname (1 letter if standard
        amino acid)
    cic: IC_Chain default None
        parent chain :class:`IC_Chain` object
    scale: optional float
        used for OpenSCAD output to generate gly_Cbeta bond length

    Methods
    -------
    assemble(atomCoordsIn, resetLocation, verbose)
        Compute atom coordinates for this residue from internal coordinates
    get_angle()
        Return angle for passed key
    get_length()
        Return bond length for specified pair
    pick_angle()
        Find Hedron or Dihedron for passed key
    pick_length()
        Find hedra for passed AtomKey pair
    set_angle()
        Set angle for passed key (no position updates)
    set_length()
        Set bond length in all relevant hedra for specified pair
    bond_rotate(delta)
        adjusts related dihedra angles by delta, e.g. rotating psi (N-Ca-C-N)
        will adjust the adjacent N-Ca-C-O by the same amount to avoid clashes
    bond_set(angle)
        uses bond_rotate to set specified dihedral to angle and adjust related
        dihedra accordingly
    rak(atom info)
        cached AtomKeys for this residue
    )CYGYCMUNKF	_AllBondsr   	gly_Cbetaz17.13fpic_accuracy)r|   r~   r}   rj  OXT) CBCGCG1OG1OGSGCG2CDCD1SDOD1ND1CD2ND2CECE1NEOE1NE1CE2OE2NE2CE3CZNZCZ2CZ3OD2OHCH2NH1NH2)1rM  H1H2H3HAHA2HA3HBHB1HB2HB3HG2HG3HD2HD3HE2HE3HZ1HZ2HZ3HG11HG12HG13HG21HG22HG23HZHD1HE1HD11HD12HD13HGHG1HD21HD22HD23r  r  HEHH11HH12HH21HH22HE21HE22r*  HHHH2)1DD1D2D3DADA2DA3DBDB1DB2DB3DG2DG3DD2DD3DE2DE3DZ1DZ2DZ3DG11DG12DG13DG21DG22DG23DZDD1DE1DD11DD12DD13DGDG1DD21DD22DD23r  r	  DEDH11DH12DH21DH22DE21DE22rX  DHDH2r+   r   r   Nc                 C   sD  || _ |  i | _i | _i | _t | _g | _g | _i | _t	j
r dng | _d| _d| _|j}|d d|d kr9|d nd|jg}z
t|d  |d< W n ty^   d| _|d | jvr\d| _Y nw t|| _|d | _| jr| D ]$}t|drt	j
r| |j qp|j D ]}| | qqp| | qp| jr|   dS dS dS )zInitialize IC_Residue with parent Biopython Residue.

        :param Residue parent: Biopython Residue object.
            The Biopython Residue this object extends
        NTrA   r   r   Fr<   )rP   r%   r&   rT   r   rS   rQ   rR   bfactorsr   r   alt_idsis20AAr   rC   r  r
   r   r   r   rbaser  r:   r0  	_add_atomr   r<   r=   r   )r*   r+   ridrz  atomr4   r,   r,   r-   r.   
  sJ   $



zIC_Residue.__init__c                 C   sf   | t| d}|r|S t| | j}||t| < |j| j |t| j |_|t| j |_|S )z(Deep copy implementation for IC_Residue.F)	rB   rC   rD   rE   rF   __dict__r   r   rP   r*   ro   rp   r7   r,   r,   r-   rs   ;
  s   zIC_Residue.__deepcopy__r5   r   c                 C   sR   || j v r'|j}t|d | jd kr'|d | jd kr'|d | jd kr'dS dS )z*Return True if atomkey is in this residue.r   rA   r   TF)rS   r   r  rz  )r*   r5   r   r,   r,   r-   __contains__H
  s   
zIC_Residue.__contains__r4   c                 C   sP   z| j | }W |S  ty'   t| | }| j |< t|tr$d|_Y |S Y |S w )z(Cache calls to AtomKey for this residue.T)rT   r   r   
isinstancer   missingr*   r4   r5   r,   r,   r-   rm  T
  s   
zIC_Residue.rakc                 C   s8   t | jD ]}|jd }| j|du r|| j|< qdS )a?  Create explicit entries for for atoms so don't miss altlocs.

        This ensures that self.akc (atom key cache) has an entry for selected
        atom name (e.g. "CA") amongst any that have altlocs.  Without this,
        rak() on the other altloc atom first may result in the main atom being
        missed.
        r/   N)r   rS   r   rT   rB   )r*   r5   atmNamer,   r,   r-   r   ^
  s   

zIC_Residue._build_rak_cachec                 C   sZ   d|j d krd|j krd|_ nd|j krd|_ |j | jvr dS | |}| j| dS )zrFilter Biopython Atom with accept_atoms; set ak_set.

        Arbitrarily renames O' and O'' to O and OXT
        rj  r   zO'zO''r  N)nameaccept_atomsrm  rS   r   r  r,   r,   r-   r{  k
  s   


zIC_Residue._add_atomc                 C   s   t | jjS )z"Print string is parent Residue ID.)r   rP   r  r   r,   r,   r-   __repr__|
  s   zIC_Residue.__repr__c                 C   s.   | j j}| j j d|d  |d |d  S )zNice string for residue ID.r   r   rA   r   )rP   rC   r  )r*   rC   r,   r,   r-   r   
  s   &zIC_Residue.pretty_strr   c              	   C   s   | j  D ]}| |_| j|_| j|j q| j D ]}| j|j | j|_q| js0| 	  g | _
| j
| t| dt| dt| df dS )zHousekeeping after loading all residues and dihedra.

        - Link dihedra to this residue
        - form id3_dh_index
        - form ak_set
        - set NCaCKey to be available AtomKeys

        called for loading PDB / atom coords
        r|   r~   r}   N)r&   r=   rq   r   rS   r   r   r%   rT   r   r   r   r   r   )r*   r   dhr  r,   r,   r-   r   
  s   

zIC_Residue._link_dihedrac                 C   sx   | j  D ]4}|jdkrd|_d|_q|jdrd|_q|jdr1|jd j	d dkr1d|_
q|jdkr9d|_qd	S )
znFor OpenSCAD, mark N-CA and CA-C bonds to be flexible joints.

        See :func:`.SCADIO.write_SCAD`
        NCACTNCACACrA   r/   r}   CBCACN)r%   r=   r  r  r  endswithr  
startswithr   r   r  r  r*   r  r,   r,   r-   set_flexible
  s   
 
zIC_Residue.set_flexiblec                 C   s6   | j  D ]}|jdkrd|_q|jdkrd|_qdS )zmFor OpenSCAD, mark H-N and C-O bonds to be hbonds (magnets).

        See :func:`.SCADIO.write_SCAD`
        HNCATCACON)r%   r=   r  r  r  r  r,   r,   r-   	set_hbond
  s   

zIC_Residue.set_hbondc                    sx   i }| j   fddt| jD }dd |D } fdd|D }|D ]}t|jD ]\}} j|j | ||< q*q#|S )z?Generate default N-Ca-C coordinates to build this residue from.c                    s   g | ]	} j |d qS r9   )rM   rB   )r   r   r   r,   r-   r   
  r   z0IC_Residue._default_startpos.<locals>.<listcomp>c                 S   s   g | ]}|d ur|qS r9   r,   )r   rr   r,   r,   r-   r   
      c                    r  r,   )r&   )r   sublistvalr  r,   r-   r   
  r  )r   r   r   	enumerater   ri   r6   )r*   
atomCoordsdlist0dlist1dlistrr   r   r$  r,   r  r-   _default_startpos
  s   zIC_Residue._default_startposc                    sf   i }| j  | jD ] }t j fdd|D  r(|D ]} j j|  ||< qq|i kr1|  }|S )z3Find N-Ca-C coordinates to build this residue from.c                    r3  r,   r4  r5  r  r,   r-   r   
  r6  z,IC_Residue._get_startpos.<locals>.<listcomp>)r   r   r!   r   rL   r   r'   r  )r*   startPosncacr5   r,   r  r-   _get_startpos
  s   
zIC_Residue._get_startposc                 C   s"   | j  D ]	}d| jj|j< qdS )zInvalidate dihedra coordinate space attributes before assemble().

        Coordinate space attributes are Dihedron.cst and .rcst, and
        :data:`IC_Chain.dCoordSpace`
        FN)r&   r=   r   rl   r6   )r*   rr   r,   r,   r-   r  
  s   zIC_Residue.clear_transformsr  c              	      s  | j }|j}|j}|j}|j}| jsdS t| j}| jd }	| 	| 
d| 
d| 
df}
d| jv rH|
| 	| 
d| 
d| 
df d| jv ra|
| 	| 
d| 
d| 
df |
| t|
}|rq|   n|   |rs	 tt| }|j|d}	 |durq|D ]}|j| }t|jd jtjj }|j|j |_dt|jkrh|jd	 durh|j j}|jd	 }	 t fd
d|jD }d|kr||	kr|!| 	 ||j s fdd|D }t"|d |d |d d\|_#|_$d||j< qd	|krS	 t%& fdd|D }t"|d |d |d d\|_#|_$d||j< 	 |j$'|jd	 }	 | |< |||| < d||| < 	 ||	krR|!| q|rgt(d| t( fdd|jD  q|rpt(d| q|sx S )a	  Compute atom coordinates for this residue from internal coordinates.

        This is the IC_Residue part of the :meth:`.assemble_residues_ser` serial
        version, see :meth:`.assemble_residues` for numpy vectorized approach
        which works at the :class:`IC_Chain` level.

        Join prepared dihedra starting from N-CA-C and N-CA-CB hedrons,
        computing protein space coordinates for backbone and sidechain atoms

        Sets forward and reverse transforms on each Dihedron to convert from
        protein coordinates to dihedron space coordinates for first three
        atoms (see :data:`IC_Chain.dCoordSpace`)

        Call :meth:`.init_atom_coords` to update any modified di/hedra before
        coming here, this only assembles dihedra into protein coordinate space.

        **Algorithm**

        Form double-ended queue, start with c-ca-n, o-c-ca, n-ca-cb, n-ca-c.

        if resetLocation=True, use initial coords from generating dihedron
        for n-ca-c initial positions (result in dihedron coordinate space)

        while queue not empty
            get 3-atom hedron key

            for each dihedron starting with hedron key (1st hedron of dihedron)

                if have coordinates for all 4 atoms already
                    add 2nd hedron key to back of queue
                else if have coordinates for 1st 3 atoms
                    compute forward and reverse transforms to take 1st 3 atoms
                    to/from dihedron initial coordinate space

                    use reverse transform to get position of 4th atom in
                    current coordinates from dihedron initial coordinates

                    add 2nd hedron key to back of queue
                else
                    ordering failed, put hedron key at back of queue and hope
                    next time we have 1st 3 atom positions (should not happen)

        loop terminates (queue drains) as hedron keys which do not start any
        dihedra are removed without action

        :param bool resetLocation: default False.
            - Option to ignore start location and orient so initial N-Ca-C
            hedron at origin.

        :returns:
            Dict of AtomKey -> homogeneous atom coords for residue in protein
            space relative to previous residue

            **Also** directly updates :data:`IC_Chain.atomArray` as
            :meth:`.assemble_residues` does.

        Nr   r}   r~   r|   r  rj  r   r/   c                    s   g | ]}| v r|qS r,   r,   r   r$  r  r,   r-   r   k  r  z'IC_Residue.assemble.<locals>.<listcomp>c                       g | ]} | qS r,   r,   r  r  r,   r-   r   x  r   rA   r   Tc                    r  r,   r,   r  r  r,   r-   r     r   zno coords to startc                    s    g | ]}  |d d ur|qS r9   )rB   r  r  r,   r-   r     s
    zno initial coords for))r   rl   rL   r'   r   rS   r   r   rz  r   rm  rT   r   r   r  r  r   HKTpoprM   rB   r&   r  r   r   r   r   rN  ri   r6   initial_coordsr   r   
appendleftr   rm   rn   r!   asarraydotr   )r*   r  r   r   rl   aaValidaaNdxaar   rseqposstartLstqh1kdihedraKeysr   rr   dseqposd_h2keyr5   acountacsacak3r,   r  r-   r,  
  s   C

"
 
 


	
 




"


	



ezIC_Residue.assemblelst)r   .	missingOKc                 C   s  t jj}t jj}g }t }i }i }|D ]S}	t ||	< |	| jv r4|	j| du r4|	j| du r4||	f qg }
| jD ]%}|	|r^|
| |	||< |j| }|dur^|	| ||	 	| q9|t
|
 qd}|D ]}t|}d|kr{|s{g   S ||k r|}qkd|krg }|D ]}|r||d  qt
|gS g }|D ]J}g }|D ]<}t|}d|krqd|kr||d  q||||d   vr|t|d  q|D ]}	|	j| |kr||	 qq|t
| q|S )aJ  Get AtomKeys for this residue (ak_set) for generic list of AtomKeys.

        Changes and/or expands a list of 'generic' AtomKeys (e.g. 'N, C, C') to
        be specific to this Residue's altlocs etc., e.g.
        '(N-Ca_A_0.3-C, N-Ca_B_0.7-C)'

        Given a list of AtomKeys for a Hedron or Dihedron,
          return:
                list of matching atomkeys that have id3_dh in this residue
                (ak may change if occupancy != 1.00)

            or
                multiple lists of matching atomkeys expanded for all atom altlocs

            or
                empty list if any of atom_coord(ak) missing and not missingOK

        :param list lst: list[3] or [4] of AtomKeys.
            Non-altloc AtomKeys to match to specific AtomKeys for this residue
        :param bool missingOK: default False, see above.
        Nr   rA   )r   r   r  r  r   rS   r   r   altloc_matchr   r   r   r   )r*   r  r  
altloc_ndxocc_ndxedraLstaltlocsposnAltlocsakMapr5   ak2_lstak2r  maxcr   lenAKLnewAKLnew_edraLstalalhlr,   r,   r-   r     sp   








zIC_Residue.split_aklc           
      C   s   |D ]}|j r
 dS qt|}d|kr| jj| jt}}}n| jj| jt}}}t|tr4t	|}n|}| 
|}|D ]!}	t|	|kr^|	|vrO||	||	< |	|vrY||	 ||	< d||	 _q=dS )aR  Populate hedra/dihedra given edron ID tuple.

        Given list of AtomKeys defining hedron or dihedron
          convert to AtomKeys with coordinates in this residue
          add appropriately to self.di/hedra, expand as needed atom altlocs

        :param list lst: tuple of AtomKeys.
            Specifies Hedron or Dihedron
        Nr   T)r  r   r   r%   Hedronr&   r  r  r  r   r   needs_update)
r*   r  r5   lenLstcdctdctobjtlsthltnlstr,   r,   r-   	_gen_edra  s*   




zIC_Residue._gen_edrac              
      s$   j sdS  d d d}}} jdkr  d}dt jk r߈ jd j r߈ jD ]}|d|d|d}}}	|j|||	fdd	}
|
D ]$}|D ]}||j v ra j | qS|j D ]}||rq j | qdqSqO ||||f  ||||f  ||||	f  |||f  |||f  |||	f z|j	d
 }W q0 t
y   |j	dd}|dur||j v r܈ j |  |||f  ||||f Y q0w dt jkr |||f t}|D ]}t fdd|D r fdd|D } | q jdurmt jg }|D ]#}|dd }t fdd|D r@ fdd|D } | qtjrmt jg }|D ]}t fdd|D rk fdd|D } | qO jrd jkrƈ j t d  d}d|_ jj|  d
}|||f} | ||||f} |  j| } |_|  t jdsi  j_| jj|<  | |r d
 g } j	 D ]\}}t|t r|jr|!| q|r j"j#}|j$}t|j%j&}t'd| d| d|  dS dS dS )zCreate IC_Chain and IC_Residue di/hedra for atom coordinates.

        AllBonds handled here.

        :param bool verbose: default False.
            Warn about missing N, Ca, C backbone atoms.
        Nr|   r~   r}   r  r  r   T)r  rj  c                 3   r   r9   rT   r   r   r,   r-   r     r   z*IC_Residue._create_edra.<locals>.<genexpr>c                       g | ]}  |qS r,   rm  r   r}  r   r,   r-   r     r6  z+IC_Residue._create_edra.<locals>.<listcomp>r   c                 3   r   r9   r  r   r   r,   r-   r     r   c                    r  r,   r  r  r   r,   r-   r     r6  c                 3   r   r9   r  r   r   r,   r-   r     r   c                    r  r,   r  r  r   r,   r-   r     r6  Fr^  zchain z len z missing atom(s): )(rS   rm  r  r   rR   r   r   r  r  rT   r   rB   rQ   r   r   r   r   r  r   r  r   r  r   rJ   r&   rq   
_set_hedrar0  r^  r   r   r  r   r   rP   r+   rC   r0   r   r   )r*   r   sNsCAsCsCBrnnNnCAnCnextNCaCtplr5   rn_aknOnCBbackboneedrar_edra	sidechainedraLongsOhtpldtplrr   r  akkakvchnchn_idchn_lenr,   r   r-   r/  C  s   	"


"















zIC_Residue._create_edra
cif_extendc                 C   s   d|   kr$tjrt| j|S d}| j D ]
}|t||7 }q|S | j}|j}d}|r0d}|dtj	dur;tj	n| j
| j| j|jtjdurKtjn|j|jd |jd | jd | jd | jd | j| j| j}|S )	aF  Generate PDB ATOM record.

        :param Atom atm: Biopython Atom object reference
        :param IC_Residue.atom_sernum: Class variable default None.
            override atom serial number if not None
        :param IC_Residue.atom_chain: Class variable default None.
            override atom chain id if not None
        r   r  zW{:6}{:5d} {:4}{:1}{:3} {:1}{:4}{:1}   {:8.3f}{:8.3f}{:8.3f}{:6.2f}{:6.2f}        {:>4}
zZ{:6}{:5d} {:4}{:1}{:3} {:1}{:4}{:1}   {:10.5f}{:10.5f}{:10.5f}{:7.3f}{:6.2f}        {:>4}
ATOMNrA   r   )r;   r   r   _pdb_atom_stringr   r<   r=   r+   r  atom_sernumserial_numberfullnamer  r  
atom_chainrC   r1   	occupancybfactorelement)r4   r  sr$  r3   r  fmtr,   r,   r-   r    s>   

zIC_Residue._pdb_atom_stringc                 C   sz   d}| j j}| j j}| jd }tjj}t| jD ]"}t	|j
| |kr:|t|||  7 }tjdur:t jd7  _q|S )a  Generate PDB ATOM records for this residue as string.

        Convenience method for functionality not exposed in PDBIO.py.
        Increments :data:`IC_Residue.atom_sernum` if not None

        :param IC_Residue.atom_sernum: Class variable default None.
            Override and increment atom serial number if not None
        :param IC_Residue.atom_chain: Class variable.
            Override atom chain id if not None

        .. todo::
            move to PDBIO
        r  r   NrA   )r   r'   r(   rz  r   r   rN  r   rS   r  r   r   r  r  )r*   r   r'   r(   rN  	resposNdxr5   r,   r,   r-   pdb_residue_string  s   

zIC_Residue.pdb_residue_stringr3   c                 C   sH   |   }| sd|krd}nd| d }t|  d | j | d S )zGenerate PIC Residue string.

        Enough to create Biopython Residue object without actual Atoms.

        :param Residue res: Biopython Residue object reference
        r  z []r   r  )	get_segidisspacer   get_full_idr  )r3   segidr,   r,   r-   _residue_string  s
   zIC_Residue._residue_string_pfDef)r  omgr  tauchi1chi2chi3chi4chi5pomgchi	classic_bclassicr%   primary	secondaryr   	initAtomsbFactorsc                 C   s   g | ]}d |> qS )rA   r,   r   r   r,   r,   r-   r   =  r   zIC_Residue.<listcomp>r  r               r   r   r/   	   
         rA         r  r  c                 C   s`   |  |}d|d kr|d7 }|d|j d | d 7 }|d7 }d|d kr,|d7 }||fS )Nr   r  zBFAC:r   z6.2frA   r  )rm  rC   get_bfactor)r*   r4   r  r  r5   r,   r,   r-   _write_pic_bfac`  s   
zIC_Residue._write_pic_bfac0PDBr  pdbidchainidpicFlagshCutpCutc              	   C   s|  t j}|du r	d}|du rd}t }|| j}||jj@ r}dt| jkr}t| dr}| j	dur}t
| jd j| jd jd ks}| | j	d }	|	dur}z(t j| jd dd}
|
t j| jd	 dd7 }
|
t j| jd
 dd7 }
||
7 }W n	 ty|   Y nw |d | d }| j}||jj@ s||jj@ rt| j D ]b}||jj@ s||jj@ r|jdkrq|durt|dr|jn|j}|tv rt| d |krq|j}z&|||j d |j| | d|j| | d|j| |  d 7 }W q ty   Y qw t| j D ]]}|jr||jj@ s|| @ sqn	||jj @ s%q|dur@|jr@|j!t"v r@t"|j! d |kr@qz|||j d |j#|j |  d 7 }W q tyb   Y qw ||jj$@ rd}t| j% D ]=}d|& krt j's| j(du r| )|j*||\}}qs|j+ D ]}| )|||\}}qqs| )|||\}}qsd|d kr|d7 }|S )a   Write PIC format lines for this residue.

        See :func:`.PICIO.write_PIC`.

        :param str pdbid: PDB idcode string; default 0PDB
        :param str chainid: PDB Chain ID character; default A
        :param int picFlags: control details written to PIC file; see
            :meth:`.PICIO.write_PIC`
        :param float hCut: only write hedra with ref db angle std dev > this
            value; default None
        :param float pCut: only write primary dihedra with ref db angle
            std dev > this value; default None
        Nr"  r  r   r   r|   T)r  r~   r}   r   r  	xrh_classrA   r  r   r  ),r   r  r  rP   	pic_flagsr  r   rQ   r0  r   r!   r   r1   
pick_angler  r   r   r%   r  r   r=   r  r(  r   r6   rC   rV   rW   rX   r&   r  bitsr  pclassr   r[   r  r:   r;   r   rx  r!  r   r<   )r*   r#  r$  r%  r&  r'  pAccicrr  
NCaChedrontsbaser   r  hcr   rr   r  r$  r4   r,   r,   r-   
_write_PICj  s   

 




0
.zIC_Residue._write_PICak_strc           	      C   s   t }| }g }|d}t|}|D ]_}| j|}|rh|ddkr8dt|jk r7|||jd |d q|ddkrUdt|jk rT|||jd |d q|ddkrg|| 	|d q|| 	| qt||krydS t
|S )	zConvert atom pair string to AtomKey tuple.

        :param str ak_str:
            Two atom names separated by ':', e.g. 'N:CA'
            Optional position specifier relative to self,
            e.g. '-1C:N' for preceding peptide bond.
        :rA   z-1r   r   10N)r   splitr   _relative_atom_rematchgrouprQ   r   rR   rm  r   )	r*   r4  AKS
angle_key2
akstr_listlenInputr$  mr,   r,   r-   _get_ak_tuple  s.   
zIC_Residue._get_ak_tuplez^(-?[10])([A-Z]+)$	angle_key)r  r  c                 C   sL   t |}d|kr| jtt|d }|S d|kr$| jtt|d }|S d S )Nr   r/   )r   r&   rB   r   DKTr%   r  )r*   rC  len_mkeyrvalr,   r,   r-   _get_angle_for_tuple  s   zIC_Residue._get_angle_for_tuplec                    s  d}t |tr |}|du r jr jd |}|S d|v rHtt tt|}|du r2dS  |}|du rF jrF jd |}|S d|kr}dt j	krUdS  j	d } 
d 
d 
d}}}|
d} j||||fd}|S d|krdt jkrdS  jd }|
d 
d 
d}	}} 
d}|j|	|||fd}|S d	|ksd
|krdt jkrdS  jd }|
d|
d 
d}
}	} 
d}|j|
|	||fd}|S d|kr) 
d 
d 
d}}} j|||fd}|du r'dt jkr' jd }|j|||fd}|S |drt jd}|du r=dS dt|d  d }z,|| }|d |krq fdd|dd D }ttt|} j|d}W |S W dS  ty~   Y dS w |S )a  Get Hedron or Dihedron for angle_key.

        :param angle_key:
            - tuple of 3 or 4 AtomKeys
            - string of atom names ('CA') separated by :'s
            - string of [-1, 0, 1]<atom name> separated by ':'s. -1 is
              previous residue, 0 is this residue, 1 is next residue
            - psi, phi, omg, omega, chi1, chi2, chi3, chi4, chi5
            - tau (N-CA-C angle) see Richardson1981
            - tuples of AtomKeys is only access for alternate disordered atoms

        Observe that a residue's phi and omega dihedrals, as well as the hedra
        comprising them (including the N:Ca:C `tau` hedron), are stored in the
        n-1 di/hedra sets; this overlap is handled here, but may be an issue if
        accessing directly.

        The following print commands are equivalent (except for sidechains with
        non-carbon atoms for chi2)::

            ric = r.internal_coord
            print(
                r,
                ric.get_angle("psi"),
                ric.get_angle("phi"),
                ric.get_angle("omg"),
                ric.get_angle("tau"),
                ric.get_angle("chi2"),
            )
            print(
                r,
                ric.get_angle("N:CA:C:1N"),
                ric.get_angle("-1C:N:CA:C"),
                ric.get_angle("-1CA:-1C:N:CA"),
                ric.get_angle("N:CA:C"),
                ric.get_angle("CA:CB:CG:CD"),
            )

        See ic_data.py for detail of atoms in the enumerated sidechain angles
        and the backbone angles which do not span the peptide bond. Using 's'
        for current residue ('self') and 'n' for next residue, the spanning
        (overlapping) angles are::

                (sN, sCA, sC, nN)   # psi
                (sCA, sC, nN, nCA)  # omega i+1
                (sC, nN, nCA, nC)   # phi i+1
                (sCA, sC, nN)
                (sC, nN, nCA)
                (nN, nCA, nC)       # tau i+1

        :return: Matching Hedron, Dihedron, or None.
        Nr   r5  r  r|   r~   r}   r  r  omegar  r  r   r   rA   r   c                    r  r,   r  r  r   r,   r-   r   p  r6  z)IC_Residue.pick_angle.<locals>.<listcomp>)r  r   rG  rQ   r   r  rB  r   r   rR   rm  r&   rB   r%   r  r   r  r  rD  
IndexError)r*   rC  rF  r  r  r  r  r  rppCpCAsclistr6   r   klsttklstr,   r   r-   r*    s|   6

3
,
"
%
"

"

"

zIC_Residue.pick_anglec                 C   s   |  |}|rt|jS dS )zqGet dihedron or hedron angle for specified key.

        See :meth:`.pick_angle` for key specifications.
        N)r*  r   r  )r*   rC  edronr,   r,   r-   	get_anglez  s   

zIC_Residue.get_angleTvc                 C   sL   |  |}|du rdS t|ts|s||_dS t|j|}| || dS )a3  Set dihedron or hedron angle for specified key.

        If angle is a `Dihedron` and `overlap` is True (default), overlapping
        dihedra are also changed as appropriate.  The overlap is a result of
        protein chain definitions in :mod:`.ic_data` and :meth:`_create_edra`
        (e.g. psi overlaps N-CA-C-O).

        The default overlap=True is probably what you want for:
        `set_angle("chi1", val)`

        The default is probably NOT what you want when processing all dihedrals
        in a chain or residue (such as copying from another structure), as the
        overlapping dihedra will likely be in the set as well.

        N.B. setting e.g. PRO chi2 is permitted without error or warning!

        See :meth:`.pick_angle` for angle_key specifications.
        See :meth:`.bond_rotate` to change a dihedral by a number of degrees

        :param angle_key: angle identifier.
        :param float v: new angle in degrees (result adjusted to +/-180).
        :param bool overlap: default True.
            Modify overlapping dihedra as needed
        N)r*  r  r  r  r  	angle_dif_do_bond_rotate)r*   rC  rR  overlaprP  deltar,   r,   r-   r    s   

zIC_Residue.set_angler1  r  rV  c              	   C   s   z?| j j|j D ]4}| j j| }| j|7  _z| j j|jddd  D ]}| j j|  j|7  _q$W q ty<   Y qw W dS  tyJ   tdw )z5Find and modify related dihedra through id3_dh_index.Nr   z.bond_rotate, bond_set only for dihedral angles)	r   rM   r  r&   r  r  r   AttributeErrorRuntimeError)r*   r1  rV  r   dihedd2rkr,   r,   r-   rT    s   zIC_Residue._do_bond_rotatec                 C   s&   |  |}|dur| || dS dS )aX  Rotate set of overlapping dihedrals by delta degrees.

        Changes a dihedral angle by a given delta, i.e.
        new_angle = current_angle + delta
        Values are adjusted so new_angle will be within +/-180.

        Changes overlapping dihedra as in :meth:`.set_angle`

        See :meth:`.pick_angle` for key specifications.
        N)r*  rT  )r*   rC  rV  r1  r,   r,   r-   bond_rotate  s   
zIC_Residue.bond_rotater  c                 C   s4   |  |}|durt|j|}| || dS dS )a<  Set dihedron to val, update overlapping dihedra by same amount.

        Redundant to :meth:`.set_angle`, retained for compatibility.  Unlike
        :meth:`.set_angle` this is for dihedra only and no option to not update
        overlapping dihedra.

        See :meth:`.pick_angle` for key specifications.
        N)r*  r  rS  r  rT  )r*   rC  r  r1  rV  r,   r,   r-   bond_set  s
   
	zIC_Residue.bond_setak_specr  c                    s   g }t |trtt| |}|du rdS | j D ]\ }t fdd|D r.|| q| j	D ]}|j D ]\ }t fdd|D rM|| q9q2||fS )a  Get list of hedra containing specified atom pair.

        :param ak_spec:
            - tuple of two AtomKeys
            - string: two atom names separated by ':', e.g. 'N:CA' with
              optional position specifier relative to self, e.g. '-1C:N' for
              preceding peptide bond.  Position specifiers are -1, 0, 1.

        The following are equivalent::

            ric = r.internal_coord
            print(
                r,
                ric.get_length("0C:1N"),
            )
            print(
                r,
                None
                if not ric.rnext
                else ric.get_length((ric.rak("C"), ric.rnext[0].rak("N"))),
            )

        If atom not found on current residue then will look on rprev[0] to
        handle cases like Gly N:CA.  For finer control please access
        `IC_Chain.hedra` directly.

        :return: list of hedra containing specified atom pair as tuples of
                AtomKeys
        Nr  c                 3       | ]}| v V  qd S r9   r,   r5  hed_keyr,   r-   r         z)IC_Residue.pick_length.<locals>.<genexpr>c                 3   r^  r9   r,   r5  r_  r,   r-   r      ra  )
r  r   r   BKTrB  r%   r   r   r   rQ   )r*   r]  rlsthed_valrJ  r,   r_  r-   pick_length  s     



zIC_Residue.pick_lengthc                 C   sJ   |  |\}}|du s|du rdS |D ]}||}|dur"|  S qdS )zlGet bond length for specified atom pair.

        See :meth:`.pick_length` for ak_spec and details.
        N)re  
get_length)r*   r]  hed_lstak_spec2r  r  r,   r,   r-   rf    s   
zIC_Residue.get_lengthc                 C   s@   |  |\}}|dur|dur|D ]}||| qdS dS dS )z`Set bond length for specified atom pair.

        See :meth:`.pick_length` for ak_spec.
        N)re  
set_length)r*   r]  r  rg  rh  r  r,   r,   r-   ri    s   zIC_Residue.set_lengthry  c                    s^   | j j}| j j tjjt| jd  fdd  D }|| }|	|
 ||< dS )z0Apply matrix to atom_coords for this IC_Residue.r   c                    s$   g | ]}|j  kr |qS r,   )r   rB   r   aairJ  rpndxr,   r-   r   #  s   $ z'IC_Residue.applyMtx.<locals>.<listcomp>N)r   r   r'   r   r   rN  r   rz  r   r  	transpose)r*   ry  r  aselectaasr,   rj  r-   applyMtx  s   zIC_Residue.applyMtx)r+   r   r   Nr  r  FFr  )Wr  r  r  r  r   r  r   r  r   r  r  r   accept_backboneaccept_sidechainaccept_mainchainaccept_hydrogensaccept_deuteriumsr  r.   rs   r  r	   r   rm  r   r{  r  r   r   r  r  r   r!   r   r  r  r  r  r,  r   r  r   r  r/  r  r  r  r  r  r  r   r  r   _b_bChi_bClassB_bClass_bAllr)  r   r  r  picFlagsDefault_asdictpicFlagsDictr  r!  r   r   r3  rB  recompiler9  r  rG  r*  rQ  r  rT  r[  r\  rb  re  rf  ri  rp  r,   r,   r,   r-   r     s  
   
#33
7



 
 R

"h( .( "
s





o
"

0
r   c                   @   s<  e Zd ZdZedZ	 eded de	fddZ
ede	defd	d
Zdeed ef de	ddfddZdd ZdddefddZdefddZde	fddZdefddZdd deed ef fddZdedefdd Zdedefd!d"Zdedefd#d$Zdedefd%d&Zdedefd'd(Zdedefd)d*ZdS )+Edrona{  Base class for Hedron and Dihedron classes.

    Supports rich comparison based on lists of AtomKeys.

    Attributes
    ----------
    atomkeys: tuple
        3 (hedron) or 4 (dihedron) :class:`.AtomKey` s defining this di/hedron
    id: str
        ':'-joined string of AtomKeys for this di/hedron
    needs_update: bool
        indicates di/hedron local atom_coords do NOT reflect current di/hedron
        angle and length values in hedron local coordinate space
    e_class: str
        sequence of atoms (no position or residue) comprising di/hedron
        for statistics
    re_class: str
        sequence of residue, atoms comprising di/hedron for statistics
    cre_class: str
        sequence of covalent radii classes comprising di/hedron for statistics
    edron_re: compiled regex (Class Attribute)
        A compiled regular expression matching string IDs for Hedron
        and Dihedron objects
    cic: IC_Chain reference
        Chain internal coords object containing this hedron
    ndx: int
        index into IC_Chain level numpy data arrays for di/hedra.
        Set in :meth:`IC_Chain.init_edra`
    rc: int
        number of residues involved in this edron

    Methods
    -------
    gen_key([AtomKey, ...] or AtomKey, ...) (Static Method)
        generate a ':'-joined string of AtomKey Ids
    is_backbone()
        Return True if all atomkeys atoms are N, Ca, C or O

    z^(?P<pdbid>\w+)?\s(?P<chn>[\w|\s])?\s(?P<a1>[\w\-\.]+):(?P<a2>[\w\-\.]+):(?P<a3>[\w\-\.]+)(:(?P<a4>[\w\-\.]+))?\s+(((?P<len12>\S+)\s+(?P<angle>\S+)\s+(?P<len23>\S+)\s*$)|((?P<dihedral>\S+)\s*$))r  r   r   c                 C   sd   dt | kr| d j d| d j d| d j d| d j S | d j d| d j d| d j S )zGenerate string of ':'-joined AtomKey strings from input.

        Generate '2_A_C:3_P_N:3_P_CA' from (2_A_C, 3_P_N, 3_P_CA)
        :param list lst: list of AtomKey objects
        r   r   r5  rA   r   r/   )r   rC   )r  r,   r,   r-   gen_keyk  s   2&zEdron.gen_keyakstrc                 C   s   t dd | dD S )zGenerate AtomKey tuple for ':'-joined AtomKey string.

        Generate (2_A_C, 3_P_N, 3_P_CA) from '2_A_C:3_P_N:3_P_CA'
        :param str akstr: string of ':'-separated AtomKey strings
        c                 S   s   g | ]}t |qS r,   )r   r  r,   r,   r-   r   ~  r   z#Edron.gen_tuple.<locals>.<listcomp>r5  )r   r8  )r  r,   r,   r-   	gen_tuplew  s   zEdron.gen_tupleargskwargsNc                    s  g }|D ]}t |tr|}qt |trt|}q|dur!|| qg |krUt fdddD rUt d t d t d g}d v rU d durU|t d  t|| _t|| _	t
| j| _d	| _|  d
| _d
| _d
| _t }tjj}tjj}tjj}tjj}	|D ]1}
|
j}|  j|| 7  _|  j|| ||  7  _||| ||	 pd
  |  j|
 7  _qt|| _dS )a  Initialize Edron with sequence of AtomKeys.

        Acceptable input:

            [ AtomKey, ... ]  : list of AtomKeys
            AtomKey, ...      : sequence of AtomKeys as args
            {'a1': str, 'a2': str, ... }  : dict of AtomKeys as 'a1', 'a2' ...
        Nc                 3   r^  r9   r,   r   r  r,   r-   r     ra  z!Edron.__init__.<locals>.<genexpr>)ru   a2a3ru   r  r  a4Tr  )r  r  r   r   r   r   r   r  r  rC   hash_hashr  r  re_class	cre_classr   r   r4   r  rN  icoder   r   cr_classr   rc)r*   r  r  r   argrsetr  r  resPosr  r5   r   r,   r  r-   r.     sH   	







zEdron.__init__c                 C   sf   | t| d}|r|S t| | j}||t| < |j| j |t| j |_t	| j
||_
|S )z#Deep copy implementation for Edron.F)rB   rC   rD   rE   rF   r~  r   r   rG   rH   r   r  r,   r,   r-   rs     s   zEdron.__deepcopy__r5   c                 C   s
   || j v S )z(Return True if atomkey is in this edron.r   r*   r5   r,   r,   r-   r       
zEdron.__contains__c                 C   s   t dd | jD S )z3Report True for contains only N, C, CA, O, H atoms.c                 s   s    | ]}|  V  qd S r9   )r  r5  r,   r,   r-   r     ra  z$Edron.is_backbone.<locals>.<genexpr>)r   r   r   r,   r,   r-   r    s   zEdron.is_backbonec                 C   s
   t | jS )z)Tuple of AtomKeys is default repr string.)r   r   r   r,   r,   r-   r    r  zEdron.__repr__c                 C      | j S )z,Hash calculated at init from atomkeys tuple.r  r   r,   r,   r-   __hash__     zEdron.__hash__r  r   c                 C   s0   t | j|jD ]\}}||kr||f  S qdS )z|Comparison function ranking self vs. other; False on equal.

        Priority is lowest value for sort: psi < chi1.
        F)r   r   )r*   r  ak_sak_or,   r,   r-   _cmp  s
   z
Edron._cmpc                 C   s   t |t| s	tS | j|jkS zTest for equality.r  rD   NotImplementedrC   r*   r  r,   r,   r-   __eq__     zEdron.__eq__c                 C   s   t |t| s	tS | j|jkS zTest for inequality.r  r  r,   r,   r-   __ne__  r  zEdron.__ne__c                 C   sF   t |t| s	tS | |}|r!ttttf |}|d |d kS dS )Test greater than.r   rA   Fr  rD   r  r  r   r   r   r*   r  rsltr,   r,   r-   __gt__     
zEdron.__gt__c                 C   sF   t |t| s	tS | |}|r!ttttf |}|d |d kS dS )Test greater or equal.r   rA   Tr  r  r,   r,   r-   __ge__  r  zEdron.__ge__c                 C   sF   t |t| s	tS | |}|r!ttttf |}|d |d k S dS )Test less than.r   rA   Fr  r  r,   r,   r-   __lt__  r  zEdron.__lt__c                 C   sF   t |t| s	tS | |}|r!ttttf |}|d |d kS dS )Test less or equal.r   rA   Tr  r  r,   r,   r-   __le__  r  zEdron.__le__)r  r  r  r  r  r  edron_rer  r  r   r  r   r  r	   r  r.   rs   r   r  r  r  r  r  r  objectr  r  r  r  r  r  r,   r,   r,   r-   r  0  s.    *"	;



r  c                       s   e Zd ZdZdeed ef deddf fddZdefd	d
Z	e
defddZdd ZejdddZe
dd Zejdd Ze
defddZejdd Zdedee fddZdedefddZ  ZS )r  a  Class to represent three joined atoms forming a plane.

    Contains atom coordinates in local coordinate space: central atom
    at origin, one terminal atom on XZ plane, and the other on the +Z axis.
    Stored in two orientations, with the 3rd (forward) or first (reversed)
    atom on the +Z axis.  See :class:`Dihedron` for use of forward and
    reverse orientations.

    Attributes
    ----------
    len12: float
        distance between first and second atoms
    len23: float
        distance between second and third atoms
    angle: float
        angle (degrees) formed by three atoms in hedron
    xrh_class: string
        only for hedron spanning 2 residues, will have 'X' for residue
        contributing only one atom

    Methods
    -------
    get_length()
        get bond length for specified atom pair
    set_length()
        set bond length for specified atom pair
    angle(), len12(), len23()
        setters for relevant attributes (angle in degrees)
    r  r   r  r   Nc                    s   t  j|i | | jdkrqtjj}tjj}tjj}tjj}| j	d j
| j	d j
}}|| || ks;|| || krGd| jdd  | _dS d}	tdD ]}
|	| j	|
 j
| | j	|
 j
|  7 }	qM|	d | j	d j
|  | _dS dS )zInitialize Hedron with sequence of AtomKeys, kwargs.

        Acceptable input:
            As for Edron, plus optional 'len12', 'angle', 'len23'
            keyworded values.
        r   r   rA   r  Nr  )superr.   r  r   r   rN  r  r  r4   r   r   r  r(  r   )r*   r  r  r  r  r  r  akl0akl1xrhcr   rF   r,   r-   r.   6  s   
 &zHedron.__init__c              
   C   s,   d| j  d| j d| jd| jd| j
S )zPrint string for Hedron object.z3-r   )rC   r  r  r  r  r   r,   r,   r-   r  N  s   zHedron.__repr__c                 C   &   z| j j| j W S  ty   Y dS w )zGet this hedron angle.r   )r   rW   r6   rW  r   r,   r,   r-   r  U  
   zHedron.anglec                 C   s2   d| j j| j< | jD ]}d| j j| j j| < q
d S )NTF)r   re   r6   r   rL   r'   r  r,   r,   r-   _invalidate_atoms]  s   
zHedron._invalidate_atomsc                 C   :   || j j| j< d| j j| j< d| j j| j j| jd  < dS )z)Set this hedron angle; sets needs_update.TFr   N)r   rW   r6   re   rL   r'   r   )r*   	angle_degr,   r,   r-   r  b     c                 C   r  )zGet first length for Hedron.r   )r   rV   r6   rW  r   r,   r,   r-   r  i  r  zHedron.len12c                 C   sT   || j j| j< d| j j| j< d| j j| j j| jd  < d| j j| j j| jd  < dS )z/Set first length for Hedron; sets needs_update.TFrA   r   N)r   rV   r6   re   rL   r'   r   r*   r   r,   r,   r-   r  q  s   c                 C   r  )zGet second length for Hedron.r   )r   rX   r6   rW  r   r,   r,   r-   r  y  r  zHedron.len23c                 C   r  )z0Set second length for Hedron; sets needs_update.TFr   N)r   rX   r6   re   rL   r'   r   r  r,   r,   r-   r    r  ak_tplc                    s\   dt |krdS t fdd|D r jj j S t fdd|D r, jj j S dS )zGet bond length for specified atom pair.

        :param tuple ak_tpl: tuple of AtomKeys.
            Pair of atoms in this Hedron
        r   Nc                 3   "    | ]}| j d d v V  qd S Nr   r  r5  r   r,   r-   r          z$Hedron.get_length.<locals>.<genexpr>c                 3   "    | ]}| j d d v V  qdS rA   Nr  r5  r   r,   r-   r     r  )r   r   r   rV   r6   rX   )r*   r  r,   r   r-   rf    s   zHedron.get_length	newLengthc                    s   dt |krtd|t fdd|D r | jj j< nt fdd|D r3| jj j< n
tdt| f    dS )zSet bond length for specified atom pair; sets needs_update.

        :param tuple .ak_tpl: tuple of AtomKeys
            Pair of atoms in this Hedron
        r   zRequire exactly 2 AtomKeys: c                 3   r  r  r  r5  r   r,   r-   r     r  z$Hedron.set_length.<locals>.<genexpr>c                 3   r  r  r  r5  r   r,   r-   r     r  z%s not found in %sN)	r   	TypeErrorr   r   rV   r6   rX   r   r  )r*   r  r  r,   r   r-   ri    s   zHedron.set_lengthr  )r  r  r  r  r	   r  r  r   r.   r  propertyr   r  r  setterr  r  rb  r   rf  ri  __classcell__r,   r,   r  r-   r    s&    &


r  c                       s2  e Zd ZdZdeed ef deddf fddZdefd	d
Z	e
dededee fddZdefddZdeeeef fddZedefddZejdeddfddZe
deeejf deeejf fddZe
d)dededefdd Ze
ded!efd"d#Zd$d defd%d&Zdefd'd(Z  Z S )*r  az  Class to represent four joined atoms forming a dihedral angle.

    Attributes
    ----------
    angle: float
        Measurement or specification of dihedral angle in degrees; prefer
        :meth:`IC_Residue.bond_set` to set
    hedron1, hedron2: Hedron object references
        The two hedra which form the dihedral angle
    h1key, h2key: tuples of AtomKeys
        Hash keys for hedron1 and hedron2
    id3,id32: tuples of AtomKeys
        First 3 and second 3 atoms comprising dihedron; hxkey orders may differ
    ric: IC_Residue object reference
        :class:`.IC_Residue` object containing this dihedral
    reverse: bool
        Indicates order of atoms in dihedron is reversed from order of atoms
        in hedra
    primary: bool
        True if this is psi, phi, omega or a sidechain chi angle
    pclass: string (primary angle class)
        re_class with X for adjacent residue according to nomenclature
        (psi, omega, phi)
    cst, rcst: numpy [4][4] arrays
        transformations to (cst) and from (rcst) Dihedron coordinate space
        defined with atom 2 (Hedron 1 center atom) at the origin.  Views on
        :data:`IC_Chain.dCoordSpace`.

    Methods
    -------
    angle()
        getter/setter for dihdral angle in degrees; prefer
        :meth:`IC_Residue.bond_set`
    bits()
        return :data:`IC_Residue.pic_flags` bitmask for dihedron psi, omega, etc
    r  r   r  r   Nc                    sl   t  j|i | |  |  |  |  ttt| jdd | _ttt| jdd | _|   |  d| _	dS )zInit Dihedron with sequence of AtomKeys and optional dihedral angle.

        Acceptable input:
            As for Edron, plus optional 'dihedral' keyworded angle value.
        r   r/   rA   r   FN)
r  r.   r   r  r   r   r  r  _setPrimaryr   )r*   r  r  r  r,   r-   r.     s   
zDihedron.__init__c                 C   s$   d| j d| j d| jd| jS )z!Print string for Dihedron object.z4-r   )rC   r  r  rq   r   r,   r,   r-   r    s   $zDihedron.__repr__ic_resr  c                 C   s   | j |d}|s#dt| jk r#| jD ]}|j |d}|dur" nq|s@dt| jk r@| jD ]}|j |d}|dur? |S q/|S )z@Find specified hedron on this residue or its adjacent neighbors.Nr   )r%   rB   r   rQ   rR   )r  r  hedronrJ  r  r,   r,   r-   _get_hedron  s   

zDihedron._get_hedronc                 C   s   | j }|dkr| jdd d | _d| _d	S |dkr)d| jdd	  | _d| _d	S |d
kr<d| jdd	  | _d| _d	S |dkrOd| jdd	  | _d| _d	S |tv r\d| _| j| _d	S d| _d	S )z@Mark dihedra required for psi, phi, omega, chi and other angles.NCACNr   r  XNTCACNCAXCAXCr  NCNCACXCr   CNCACBF)r  r  r,  r  altCB_classr   )r*   dhcr,   r,   r-   r    s"   




zDihedron._setPrimaryc                 C   s   z	| j | j| jfW S  ty   Y nw d}| j}| j}t||}|sFd}tt	t
| jddd }t||}tt	t
| jddd }n| j}|sUtd| d	|  t||}|sgtd
| d	|  || _|| _|| _|| _|| _|||fS )z%Work out hedra keys and set rev flag.FTr   Nr   r/   r   zcan't find 1st hedron for key z
 dihedron zcan't find 2nd hedron for key )revr   r   rW  rq   r  r  r  r   r  r   r   r  HedronMatchErrorr   r   r   )r*   r  r3   r   r   r   r   r,   r,   r-   r    s<   
zDihedron._set_hedrac                 C   sD   z| j j| j W S  ty!   z| jW  Y S  ty    Y Y dS w w )zGet dihedral angle.rk  )r   r[   r6   rW  	_dihedralr   r,   r,   r-   r  ;  s   zDihedron.angledangle_deg_inc                 C   s~   |dkr	|d }n|dk r|d }n|}|| _ d| _| j}| j}||j|< t||j|< d|j|< d|j	|j
| jd  < dS )a{  Save new dihedral angle; sets needs_update.

        Faster to modify IC_Chain level arrays directly.

        This is probably not the routine you are looking for.  See
        :meth:`IC_Residue.set_angle` or :meth:`IC_Residue.bond_rotate` to change
        a dihedral angle along with its overlapping dihedra, i.e. without
        clashing atoms.

        N.B. dihedron (i-1)C-N-CA-CB is ignored if O exists.
        C-beta is by default placed using O-C-CA-CB, but O is missing
        in some PDB file residues, which means the sidechain cannot be
        placed.  The alternate CB path (i-1)C-N-CA-CB is provided to
        circumvent this, but if this is needed then it must be adjusted in
        conjunction with PHI ((i-1)C-N-CA-C) as they overlap.  This is handled
        by the `IC_Residue` routines above.

        :param float dangle_deg: new dihedral angle in degrees
        r2  rk  g     fTFr/   N)r  r  r   r6   r[   r!   r   r\   rh   rL   r'   r   )r*   r  
dangle_degr   r   r,   r,   r-   r  F  s   



ru   r  c                 C   s   dd| |  d  S )zoGet angle difference between two +/- 180 angles.

        https://stackoverflow.com/a/36001014/2783487
        r2  rk  r,   )ru   r  r,   r,   r-   rS  l  s   zDihedron.angle_difFalstin_radsout_radsc                 C   sH   |r| nt | }t t t |t t |}|r|S t |S )zGet average of list of +/-180 angles.

        :param List alst: list of angles to average
        :param bool in_rads: input values are in radians
        :param bool out_rads: report result in radians
        )r!   r   rc  rx   r7  r8  ra  )r  r  r  walstravgr,   r,   r-   	angle_avgt  s   $zDihedron.angle_avgavgc              
   C   s&   t t t t| |t|  S )zGet population standard deviation for list of +/-180 angles.

        should be sample std dev but avoid len(alst)=1 -> div by 0
        )r!   r  rx   r"   r  rS  r   )r  r  r,   r,   r-   angle_pop_sd  s   &zDihedron.angle_pop_sdr  c                 C   s   t | j|jS )z;Get angle difference between this and other +/- 180 angles.)r  rS  r  r  r,   r,   r-   
difference  s   zDihedron.differencec                    s   t }| jdkr|jjS t| dr| jdkr|jj|jjB S | jdkr&|jjS | jdkr/|jjS t	j
j t| jj}t fdd| jD }|D ]!}t|dkrQqH||d	d
 kri|jjt|d
 d d >   S qHd	S )zPGet :data:`IC_Residue.pic_flags` bitmasks for self is psi, omg, phi, pomg, chiX.r  r,  
XCAXCPNPCAr  r  c                 3   s    | ]}|j   V  qd S r9   )r   r5  r  r,   r-   r     r   z Dihedron.bits.<locals>.<genexpr>r  r   r   r   rA   )r   r  r)  r  r0  r,  r  r  r  r   r   r4   r   rB   rq   r  r   r   r   r  r  )r*   r.  scListaLster,   r  r-   r+    s&   


 zDihedron.bitsrq  )!r  r  r  r  r	   r  rD  r   r.   r  r  r   r  r   r  r  r   r  r   r  r  r   r  r  r!   r  rS  r  r  r  r  r+  r  r,   r,   r  r-   r    s(    &%(
%(r  c                   @   sZ  e Zd ZdZedZ	 edZdZe	dg dZ
e
dddd	d
dZ	 dZ	 deeeeeef deddfddZdd ZdefddZdefddZdddd	dZi ddddddddddddd d	d!d
d"d
d#d
d$d
d%d
d&dd'dd(dd)d*d+d*i d,d*d-d*d.d*d/d0d1d0d2d0d3d4d5d6d7d6d8d6d9d:d;d:d<d:d=d>d?d>d@dAdBdCZdddd	d
dd*dDZdEd defdFdGZdefdHdIZdefdJdKZdeedf fdLdMZdEd de eef fdNdOZ!dEe"defdPdQZ#dEe"defdRdSZ$dEe"defdTdUZ%dEe"defdVdWZ&dEe"defdXdYZ'dEe"defdZd[Z(dS )\r   a
  Class for dict keys to reference atom coordinates.

    AtomKeys capture residue and disorder information together, and
    provide a no-whitespace string key for .pic files.

    Supports rich comparison and multiple ways to instantiate.

    AtomKeys contain:
     residue position (respos), insertion code (icode), 1 or 3 char residue
     name (resname), atom name (atm), altloc (altloc), and occupancy (occ)

    Use :data:`AtomKey.fields` to get the index to the component of interest by
    name:

    Get C-alpha atoms from IC_Chain atomArray and atomArrayIndex with
    AtomKeys::

        atmNameNdx = internal_coords.AtomKey.fields.atm
        CaSelection = [
            atomArrayIndex.get(k)
            for k in atomArrayIndex.keys()
            if k.akl[atmNameNdx] == "CA"
        ]
        AtomArrayCa = atomArray[CaSelection]

    Get all phenylalanine atoms in a chain::

        resNameNdx = internal_coords.AtomKey.fields.resname
        PheSelection = [
            atomArrayIndex.get(k)
            for k in atomArrayIndex.keys()
            if k.akl[resNameNdx] == "F"
        ]
        AtomArrayPhe = atomArray[PheSelection]

    'resname' will be the uppercase 1-letter amino acid code if one of the 20
    standard residues, otherwise the supplied 3-letter code.  Supplied as input
    or read from .rbase attribute of :class:`IC_Residue`.

    Attributes
    ----------
    akl: tuple
        All six fields of AtomKey
    fieldNames: tuple (Class Attribute)
        Mapping of key index positions to names
    fields: namedtuple (Class Attribute)
        Mapping of field names to index positions.
    id: str
        '_'-joined AtomKey fields, excluding 'None' fields
    atom_re: compiled regex (Class Attribute)
        A compiled regular expression matching the string form of the key
    d2h: bool (Class Attribute) default False
        Convert D atoms to H on input if True; must also modify
        :data:`IC_Residue.accept_atoms`
    missing: bool default False
        AtomKey __init__'d from string is probably missing, set this flag to
        note the issue.  Set by :meth:`.IC_Residue.rak`
    ric: IC_Residue default None
        *If* initialised with IC_Residue, this references the IC_residue

    Methods
    -------
    altloc_match(other)
        Returns True if this AtomKey matches other AtomKey excluding altloc
        and occupancy fields
    is_backbone()
        Returns True if atom is N, CA, C, O or H
    atm()
        Returns atom name, e.g. N, CA, CB, etc.
    cr_class()
        Returns covalent radii class e.g. Csb

    z^(?P<respos>-?\d+)(?P<icode>[A-Za-z])?_(?P<resname>[a-zA-Z]+)_(?P<atm>[A-Za-z0-9]+)(?:_(?P<altloc>\w))?(?:_(?P<occ>-?\d\.\d+?))?$z	\D+(\d+)$)rN  r  r  r4   r  r  
_fieldsDefr   rA   r   r/   r   r  Fr  r  r   Nc                 O   sr  g }d| _ |D ]}t|tr6d|v r0| j|}|dur/|g kr&td| tt|jt	j
}q|| qt|trL|g krCtdt|j}|| _ qt|trdt|kr[td||j tjs|j}||dkro|nd t|j}||dkrt|nd q|ddg7 }qt|ttfr||7 }qt|trt	j
D ]}|||d qqtd	tt|d
D ]}	t||	kr|t	j
|	 }
|
dur||
 qt|d trt|d |d< | jrt	jj}|| d dkrtjdd|| dd||< ddt d|dd t|d dt d|dd g| _!|dgd
t|  7 }t|| _"t#| j"| _$d| _%dS )af  Initialize AtomKey with residue and atom data.

        Examples of acceptable input::

            (<IC_Residue>, 'CA', ...)    : IC_Residue with atom info
            (<IC_Residue>, <Atom>)       : IC_Residue with Biopython Atom
            ([52, None, 'G', 'CA', ...])  : list of ordered data fields
            (52, None, 'G', 'CA', ...)    : multiple ordered arguments
            ({respos: 52, icode: None, atm: 'CA', ...}) : dict with fieldNames
            (respos: 52, icode: None, atm: 'CA', ...) : kwargs with fieldNames
            52_G_CA, 52B_G_CA, 52_G_CA_0.33, 52_G_CA_B_0.33  : id strings
        Nr   z+Atom Key init full key not first argument: z(Atom Key init Residue not first argumentr/   z&Atom Key init Atom before Residue infor   r   zAtom Key init not recognisedr  r   rI  rM  rA   )countr  r   F)&rq   r  r   atom_rer:  	Exceptionr  mapr;  r   
fieldNamesr   r   rz  r   r   r  r   r  r   r  r   r   rB   r   r   d2hr   r4   r  subjoinr  rC   r   r  r  r  )r*   r  r  r   r  rA  r  r  r   r   fldr  r,   r,   r-   r.   	  st   











zAtomKey.__init__c                 C   s`   | t| d}|r|S t| | j}||t| < |j| j | jdur.|t| j |_|S )z%Deep copy implementation for AtomKey.FN)rB   rC   rD   rE   rF   r~  r   rq   r  r,   r,   r-   rs   `  s   
zAtomKey.__deepcopy__c                 C   r  )zRepr string from id.)rC   r   r,   r,   r-   r  n  r  zAtomKey.__repr__c                 C   r  )z'Hash calculated at init from akl tuple.r  r   r,   r,   r-   r  r  r  zAtomKey.__hash__)r|   r~   r}   rj  r  r  r  r   r  r  r  r  r  r  r  r  r  r	  r  r
  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  rM  r  )r  Br  rI  Er  rM  r  c                 C   s.   t |t| r| jdd |jdd kS tS )z=Test AtomKey match to other discounting occupancy and altloc.Nr   r  rD   r   r  r  r,   r,   r-   r    s   zAtomKey.altloc_matchc                 C   s   | j | jj dv S )z$Return True if is N, C, CA, O, or H.)r|   r}   r~   rj  rM  r   r   r4   r   r,   r,   r-   r    s   zAtomKey.is_backbonec                 C   s   | j | jj S )z$Return atom name : N, CA, CB, O etc.r  r   r,   r,   r-   r4     s   zAtomKey.atmc                 C   s   | j }| jj}z	td ||  W S  ty@   z| jj}t||  ||  W  Y S  ty?   || d dkr9dnd Y  Y S w w )z-Return covalent radii class for atom or None.r  r   rM  r  N)r   r   r4   r   r   r  )r*   r   r  r  r,   r,   r-   r    s    zAtomKey.cr_classc                 C   s  t dD ]z}| j| |j| }}||kr|du r"|dur" dS |du r-|dur- dS tjj|krtjj|krOtt|d }tt|d }||f  S tjj|kr_t|t|f  S tjj	|kr| jtjj
 |jtjj
 }}|dur|durt|t|f  S  dS |dur dS t|t|f  S | j|d}	| j|d}
|	dur|
dur|	|
f  S |	dur|
du r dS |	du r|
dur dS | j|d}| j|d}|dur|dur||f  S |dur|du r dS |du r|dur dS |d |d |d |d f\}}}}| | }}d|krud|kru||ks.|r]|r]| j|}| j|}|g krR|g krRt|d t|d f  S |g krZ dS  dS |rc dS |ri dS | j| | j| f  S t|t|f  S qd	S )
z~Comparison function ranking self vs. other.

        Priority is lower value, i.e. (CA, CB) gives (0, 1) for sorting.
        r  Nr   )rA   r   d   r   rA   rM  )rA   rA   )r   r   r   r   r4   r  r  r   rN  r  r  ord_backbone_sort_keysrB   _sidechain_sort_keysisdigit
_endnum_refindall_greek_sort_keys)r*   r  r   r  ooisisacoacsbr  ssoss0s1o0o1s1do1denmSenmOr,   r,   r-   r    s|   
$
TzAtomKey._cmpc                 C   s   t |t| r| j|jkS tS r  r  r  r,   r,   r-   r       zAtomKey.__ne__c                 C   s   t |t| r| j|jkS tS r  r  r  r,   r,   r-   r     r  zAtomKey.__eq__c                 C   s,   t |t| r| |}|d |d kS tS )r  r   rA   r  rD   r  r  r  r,   r,   r-   r  '     
zAtomKey.__gt__c                 C   s,   t |t| r| |}|d |d kS tS )r  r   rA   r  r  r,   r,   r-   r  /  r  zAtomKey.__ge__c                 C   s,   t |t| r| |}|d |d k S tS )r  r   rA   r  r  r,   r,   r-   r  7  r  zAtomKey.__lt__c                 C   s,   t |t| r| |}|d |d kS tS )r  r   rA   r  r  r,   r,   r-   r  ?  r  zAtomKey.__le__))r  r  r  r  r  r  r  r  r  r   r  r   r  r	   r   r   r  r   r   r.   rs   r  r  r  r  r  r  r   r  r  r4   r  r   r  r  r  r  r  r  r  r  r,   r,   r,   r-   r     s    J

W	
 !"%]r   numr   c                 C   s   t | dS )zReduce floating point accuracy to 9.5 (xxxx.xxxxx).

    Used by :class:`IC_Residue` class writing PIC and SCAD
    files.

    :param float num: input number
    :returns: float with specified accuracy
    r  )r   )r  r,   r,   r-   r  H  s   
r  c                   @      e Zd ZdZdS )r  z,Cannot find hedron in residue for given key.Nr  r  r  r  r,   r,   r,   r-   r  V      r  c                   @   r  )MissingAtomErrorz0Missing atom coordinates for hedron or dihedron.Nr  r,   r,   r,   r-   r  Z  r  r  )6r  rG   r  collectionsr   r   numbersr   typingr   r   r   r   r	   numpyr!   Bio.Data.PDBDatar
   Bio.PDB.Atomr   r   Bio.PDB.ic_datar   r   r   r   r   r   r   Bio.PDB.vectorsr   r   r   Bio.PDB.Residuer   r   r  rD  r  rb  r   HACSDACSr   r   r  r  r  r   r   r  r  r  r  r,   r,   r,   r-   <module>   s     	                             h      #