1111 angle_between_vectors ,
1212 center_of_mass ,
1313 helix_axis ,
14+ load_structure ,
1415 pairwise_distance ,
1516)
17+ from confostate .features .rmsd import (
18+ LEUT_REFERENCE_STRUCTURES ,
19+ _resolve_reference_path ,
20+ )
1621
1722LEUT_TM_HELICES : dict [str , tuple [int , int ]] = {
1823 "TM1" : (22 , 52 ),
3641 ("TM3" , "TM10" ),
3742)
3843
44+ _DOMAIN_METRIC_KEYS = (
45+ "domain_TM1_TM7_distance" ,
46+ "domain_TM1_TM7_angle" ,
47+ "domain_TM1_TM6_distance" ,
48+ "domain_TM1_TM6_angle" ,
49+ "domain_TM5_TM7_distance" ,
50+ "domain_TM5_TM7_angle" ,
51+ "domain_TM3_TM10_distance" ,
52+ "domain_TM3_TM10_angle" ,
53+ "domain_gate_TM1_TM6_distance" ,
54+ )
3955
40- def extract_domain_features (
56+
57+ def _domain_geometry (
4158 structure : StructureData ,
42- tm_helices : Optional [ dict [str , tuple [int , int ]]] = None ,
43- domain_pairs : tuple [tuple [str , str ], ...] = LEUT_DOMAIN_PAIRS ,
59+ tm_helices : dict [str , tuple [int , int ]],
60+ domain_pairs : tuple [tuple [str , str ], ...],
4461) -> dict [str , float ]:
45- """
46- Compute pairwise helix COM distances and inter-helix angles.
47-
48- Uses MDAnalysis selections and ``AtomGroup.center_of_mass()``.
49- """
50- helices = tm_helices or LEUT_TM_HELICES
51- features : dict [str , float ] = {}
52-
62+ """Compute absolute inter-helix distances and angles."""
5363 coms : dict [str , np .ndarray ] = {}
5464 axes : dict [str , np .ndarray ] = {}
55- for name , (start , end ) in helices .items ():
65+ for name , (start , end ) in tm_helices .items ():
5666 ag = structure .select_ca_range (start , end )
5767 if len (ag ) == 0 :
5868 continue
5969 coms [name ] = center_of_mass (ag )
6070 axes [name ] = helix_axis (ag )
6171
72+ features : dict [str , float ] = {}
6273 for helix_a , helix_b in domain_pairs :
6374 key_base = f"domain_{ helix_a } _{ helix_b } "
6475 if helix_a not in coms or helix_b not in coms :
@@ -80,3 +91,69 @@ def extract_domain_features(
8091 features ["domain_gate_TM1_TM6_distance" ] = float ("nan" )
8192
8293 return features
94+
95+
96+ def _domain_deltas (
97+ base_features : dict [str , float ],
98+ reference_dir : str ,
99+ reference_structures : dict [str , str ],
100+ tm_helices : dict [str , tuple [int , int ]],
101+ domain_pairs : tuple [tuple [str , str ], ...],
102+ ) -> dict [str , float ]:
103+ """Compute domain metric deltas vs each unique reference PDB."""
104+ deltas : dict [str , float ] = {}
105+ unique_refs = sorted (set (reference_structures .values ()))
106+
107+ for ref_pdb_id in unique_refs :
108+ try :
109+ ref_path = _resolve_reference_path (ref_pdb_id , reference_dir )
110+ except FileNotFoundError :
111+ continue
112+
113+ ref_structure = load_structure (str (ref_path ), pdb_id = ref_pdb_id )
114+ ref_features = _domain_geometry (
115+ ref_structure , tm_helices , domain_pairs
116+ )
117+
118+ for key in _DOMAIN_METRIC_KEYS :
119+ if key not in base_features or key not in ref_features :
120+ continue
121+ base_val = base_features [key ]
122+ ref_val = ref_features [key ]
123+ if np .isnan (base_val ) or np .isnan (ref_val ):
124+ deltas [f"{ key } _delta_vs_{ ref_pdb_id } " ] = float ("nan" )
125+ else :
126+ deltas [f"{ key } _delta_vs_{ ref_pdb_id } " ] = float (
127+ base_val - ref_val
128+ )
129+
130+ return deltas
131+
132+
133+ def extract_domain_features (
134+ structure : StructureData ,
135+ tm_helices : Optional [dict [str , tuple [int , int ]]] = None ,
136+ domain_pairs : tuple [tuple [str , str ], ...] = LEUT_DOMAIN_PAIRS ,
137+ reference_dir : Optional [str ] = None ,
138+ reference_structures : Optional [dict [str , str ]] = None ,
139+ include_deltas : bool = True ,
140+ ) -> dict [str , float ]:
141+ """
142+ Compute pairwise helix COM distances, angles, and optional deltas.
143+
144+ When ``reference_dir`` is set, also returns deltas vs each curated
145+ reference PDB (same references as ``rmsd.py``), e.g.
146+ ``domain_TM1_TM7_distance_delta_vs_3F3E``.
147+ """
148+ helices = tm_helices or LEUT_TM_HELICES
149+ features = _domain_geometry (structure , helices , domain_pairs )
150+
151+ if include_deltas and reference_dir :
152+ refs = reference_structures or LEUT_REFERENCE_STRUCTURES
153+ features .update (
154+ _domain_deltas (
155+ features , reference_dir , refs , helices , domain_pairs
156+ )
157+ )
158+
159+ return features
0 commit comments