2020 "index_dihedral" ,
2121 "dihedral_backbone" ,
2222 "dihedral_side_chain" ,
23+ "nucleotide_dihedral_backbone" ,
24+ "nucleotide_dihedral_side_chain" ,
2325 "centroid" ,
2426]
2527
2628import functools
2729import numpy as np
2830from biotite .structure .atoms import AtomArray , AtomArrayStack , coord
2931from biotite .structure .box import coord_to_fraction , fraction_to_coord , is_orthogonal
30- from biotite .structure .filter import filter_amino_acids , filter_canonical_amino_acids
32+ from biotite .structure .filter import (
33+ filter_amino_acids ,
34+ filter_canonical_amino_acids ,
35+ filter_nucleotides ,
36+ )
3137from biotite .structure .residues import get_residue_starts
3238from biotite .structure .util import (
3339 coord_for_atom_name_per_residue ,
@@ -600,24 +606,9 @@ def dihedral_backbone(atom_array):
600606 coord_for_omg [..., 0 :- 1 , :, 3 ] = coord_ca [..., 1 :, :]
601607 # fmt: on
602608
603- phi = dihedral (
604- coord_for_phi [..., 0 ],
605- coord_for_phi [..., 1 ],
606- coord_for_phi [..., 2 ],
607- coord_for_phi [..., 3 ],
608- )
609- psi = dihedral (
610- coord_for_psi [..., 0 ],
611- coord_for_psi [..., 1 ],
612- coord_for_psi [..., 2 ],
613- coord_for_psi [..., 3 ],
614- )
615- omg = dihedral (
616- coord_for_omg [..., 0 ],
617- coord_for_omg [..., 1 ],
618- coord_for_omg [..., 2 ],
619- coord_for_omg [..., 3 ],
620- )
609+ phi = dihedral (* (coord_for_phi [..., i ] for i in range (4 )))
610+ psi = dihedral (* (coord_for_psi [..., i ] for i in range (4 )))
611+ omg = dihedral (* (coord_for_omg [..., i ] for i in range (4 )))
621612
622613 return phi , psi , omg
623614
@@ -691,18 +682,12 @@ def dihedral_side_chain(atoms):
691682 res_mask = res_names == res_name
692683 for chi_i , chi_atom_names in enumerate (chi_atom_names_for_all_angles ):
693684 dihedrals = dihedral (
694- chi_atom_coord [
695- chi_atoms_to_coord_index [chi_atom_names [0 ]], ..., res_mask , :
696- ],
697- chi_atom_coord [
698- chi_atoms_to_coord_index [chi_atom_names [1 ]], ..., res_mask , :
699- ],
700- chi_atom_coord [
701- chi_atoms_to_coord_index [chi_atom_names [2 ]], ..., res_mask , :
702- ],
703- chi_atom_coord [
704- chi_atoms_to_coord_index [chi_atom_names [3 ]], ..., res_mask , :
705- ],
685+ * (
686+ chi_atom_coord [
687+ chi_atoms_to_coord_index [atom_name ], ..., res_mask , :
688+ ]
689+ for atom_name in chi_atom_names
690+ )
706691 )
707692 if is_multi_model :
708693 # Swap dimensions due to NumPy's behavior when using advanced indexing
@@ -712,6 +697,149 @@ def dihedral_side_chain(atoms):
712697 return chi_angles
713698
714699
700+ def nucleotide_dihedral_backbone (atom_array ):
701+ r"""
702+ Measure the six characteristic backbone dihedral angles of a nucleotide chain.
703+
704+ Parameters
705+ ----------
706+ atom_array : AtomArray or AtomArrayStack
707+ The nucleic acid structure to measure the dihedral angles for.
708+ For missing backbone atoms the corresponding angles are `NaN`.
709+
710+ Returns
711+ -------
712+ alpha, beta, gamma, delta, epsilon, zeta : ndarray, shape=(m,n) or shape=(n,), dtype=float
713+ An array containing the six backbone dihedral angles for every nucleotide
714+ residue.
715+ :math:`\alpha` is not defined at the 5'-terminus, :math:`\epsilon` and
716+ :math:`\zeta` are not defined at the 3'-terminus.
717+ In these places the arrays have *NaN* values.
718+ If an :class:`AtomArrayStack` is given, the output angles are 2-dimensional,
719+ the first dimension corresponds to the model number.
720+
721+ Notes
722+ -----
723+ The nucleotide backbone dihedral angles are defined as follows
724+ (indices refer to the residue position along the chain):
725+
726+ - :math:`\alpha`: O3'(i-1) - P(i) - O5'(i) - C5'(i)
727+ - :math:`\beta`: P(i) - O5'(i) - C5'(i) - C4'(i)
728+ - :math:`\gamma`: O5'(i) - C5'(i) - C4'(i) - C3'(i)
729+ - :math:`\delta`: C5'(i) - C4'(i) - C3'(i) - O3'(i)
730+ - :math:`\epsilon`: C4'(i) - C3'(i) - O3'(i) - P(i+1)
731+ - :math:`\zeta`: C3'(i) - O3'(i) - P(i+1) - O5'(i+1)
732+ """
733+ coord_p , coord_o5p , coord_c5p , coord_c4p , coord_c3p , coord_o3p = (
734+ coord_for_atom_name_per_residue (
735+ atom_array ,
736+ ("P" , "O5'" , "C5'" , "C4'" , "C3'" , "O3'" ),
737+ filter_nucleotides (atom_array ),
738+ )
739+ )
740+ n_residues = coord_p .shape [- 2 ]
741+
742+ # Coordinates for dihedral angle calculation
743+ # Dim 0: Model index (only for atom array stacks)
744+ # Dim 1: Angle index
745+ # Dim 2: X, Y, Z coordinates
746+ # Dim 3: Atoms involved in dihedral angle
747+ if isinstance (atom_array , AtomArray ):
748+ angle_coord_shape : tuple [int , ...] = (n_residues , 3 , 4 )
749+ elif isinstance (atom_array , AtomArrayStack ):
750+ angle_coord_shape = (atom_array .stack_depth (), n_residues , 3 , 4 )
751+ (
752+ coord_for_alpha ,
753+ coord_for_beta ,
754+ coord_for_gamma ,
755+ coord_for_delta ,
756+ coord_for_epsilon ,
757+ coord_for_zeta ,
758+ ) = [np .full (angle_coord_shape , np .nan , dtype = np .float32 ) for _ in range (6 )]
759+
760+ # fmt: off
761+ coord_for_alpha [..., 1 :, :, 0 ] = coord_o3p [..., 0 :- 1 , :]
762+ coord_for_alpha [..., 1 :, :, 1 ] = coord_p [..., 1 :, :]
763+ coord_for_alpha [..., 1 :, :, 2 ] = coord_o5p [..., 1 :, :]
764+ coord_for_alpha [..., 1 :, :, 3 ] = coord_c5p [..., 1 :, :]
765+
766+ coord_for_beta [..., :, :, 0 ] = coord_p
767+ coord_for_beta [..., :, :, 1 ] = coord_o5p
768+ coord_for_beta [..., :, :, 2 ] = coord_c5p
769+ coord_for_beta [..., :, :, 3 ] = coord_c4p
770+
771+ coord_for_gamma [..., :, :, 0 ] = coord_o5p
772+ coord_for_gamma [..., :, :, 1 ] = coord_c5p
773+ coord_for_gamma [..., :, :, 2 ] = coord_c4p
774+ coord_for_gamma [..., :, :, 3 ] = coord_c3p
775+
776+ coord_for_delta [..., :, :, 0 ] = coord_c5p
777+ coord_for_delta [..., :, :, 1 ] = coord_c4p
778+ coord_for_delta [..., :, :, 2 ] = coord_c3p
779+ coord_for_delta [..., :, :, 3 ] = coord_o3p
780+
781+ coord_for_epsilon [..., 0 :- 1 , :, 0 ] = coord_c4p [..., 0 :- 1 , :]
782+ coord_for_epsilon [..., 0 :- 1 , :, 1 ] = coord_c3p [..., 0 :- 1 , :]
783+ coord_for_epsilon [..., 0 :- 1 , :, 2 ] = coord_o3p [..., 0 :- 1 , :]
784+ coord_for_epsilon [..., 0 :- 1 , :, 3 ] = coord_p [..., 1 :, :]
785+
786+ coord_for_zeta [..., 0 :- 1 , :, 0 ] = coord_c3p [..., 0 :- 1 , :]
787+ coord_for_zeta [..., 0 :- 1 , :, 1 ] = coord_o3p [..., 0 :- 1 , :]
788+ coord_for_zeta [..., 0 :- 1 , :, 2 ] = coord_p [..., 1 :, :]
789+ coord_for_zeta [..., 0 :- 1 , :, 3 ] = coord_o5p [..., 1 :, :]
790+ # fmt: on
791+
792+ alpha = dihedral (* (coord_for_alpha [..., i ] for i in range (4 )))
793+ beta = dihedral (* (coord_for_beta [..., i ] for i in range (4 )))
794+ gamma = dihedral (* (coord_for_gamma [..., i ] for i in range (4 )))
795+ delta = dihedral (* (coord_for_delta [..., i ] for i in range (4 )))
796+ epsilon = dihedral (* (coord_for_epsilon [..., i ] for i in range (4 )))
797+ zeta = dihedral (* (coord_for_zeta [..., i ] for i in range (4 )))
798+
799+ return alpha , beta , gamma , delta , epsilon , zeta
800+
801+
802+ def nucleotide_dihedral_side_chain (atoms ):
803+ r"""
804+ Measure the glycosidic :math:`\chi` dihedral angle of nucleotide residues.
805+
806+ Parameters
807+ ----------
808+ atoms : AtomArray or AtomArrayStack
809+ The nucleic acid structure to measure the glycosidic dihedral angles for.
810+
811+ Returns
812+ -------
813+ chi : ndarray, shape=(m, n) or shape=(n,), dtype=float
814+ An array containing the :math:`\chi` angle for every residue.
815+ Residues that are not nucleotides or lack the required atoms are filled with
816+ :math:`NaN` values.
817+
818+ Notes
819+ -----
820+ The :math:`\chi` angle is defined between the sugar and the base:
821+
822+ - Purines (e.g. ``A``, ``G``): ``O4' - C1' - N9 - C4``
823+ - Pyrimidines (e.g. ``C``, ``U``, ``T``): ``O4' - C1' - N1 - C2``
824+
825+ The base type is inferred from the presence of the ``N9`` atom, so modified
826+ nucleotides are handled as long as they use the canonical glycosidic linkage.
827+ """
828+ coord_o4p , coord_c1p , coord_n9 , coord_c4 , coord_n1 , coord_c2 = (
829+ coord_for_atom_name_per_residue (
830+ atoms ,
831+ ("O4'" , "C1'" , "N9" , "C4" , "N1" , "C2" ),
832+ filter_nucleotides (atoms ),
833+ )
834+ )
835+
836+ purine_chi = dihedral (coord_o4p , coord_c1p , coord_n9 , coord_c4 )
837+ pyrimidine_chi = dihedral (coord_o4p , coord_c1p , coord_n1 , coord_c2 )
838+ # Purines are distinguished from pyrimidines by the presence of the N9 atom
839+ is_pyrimidine = np .isnan (coord_n9 [..., 0 ])
840+ return np .where (is_pyrimidine , pyrimidine_chi , purine_chi )
841+
842+
715843def centroid (atoms ):
716844 """
717845 Measure the centroid of a structure.
0 commit comments