Skip to content

Commit c3bfc36

Browse files
committed
[fix] fix test case for calculate_chain_pair_pae
1 parent 8be7138 commit c3bfc36

1 file changed

Lines changed: 158 additions & 53 deletions

File tree

tests/test_sample_confidence.py

Lines changed: 158 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,175 @@
1+
# Copyright 2024 ByteDance and/or its affiliates.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
115
import unittest
16+
217
import torch
18+
319
from protenix.model.sample_confidence import calculate_chain_pair_pae
420

5-
class TestSampleConfidence(unittest.TestCase):
6-
def test_calculate_chain_pair_pae(self):
7-
# Setup inputs
8-
N_samples = 2
21+
22+
class TestCalculateChainPairPAE(unittest.TestCase):
23+
def test_basic_two_chains(self):
24+
"""Test basic case with two chains"""
25+
N_sample = 1
26+
N_token = 6
27+
28+
token_pair_pae = torch.zeros(N_sample, N_token, N_token)
29+
token_pair_pae[0, :3, :3] = 1.0 # Chain 0 internal
30+
token_pair_pae[0, 3:, 3:] = 2.0 # Chain 1 internal
31+
token_pair_pae[0, :3, 3:] = 3.0 # Chain 0->1
32+
token_pair_pae[0, 3:, :3] = 4.0 # Chain 1->0
33+
34+
asym_id = torch.tensor([0, 0, 0, 1, 1, 1], dtype=torch.long)
35+
token_has_frame = torch.ones(N_token, dtype=torch.bool)
36+
37+
result = calculate_chain_pair_pae(token_pair_pae, asym_id, token_has_frame)
38+
39+
self.assertIn("chain_pair_pae_mean", result)
40+
self.assertIn("chain_pair_pae_min", result)
41+
42+
mean = result["chain_pair_pae_mean"]
43+
min_val = result["chain_pair_pae_min"]
44+
45+
self.assertEqual(mean.shape, (N_sample, 2, 2))
46+
self.assertEqual(min_val.shape, (N_sample, 2, 2))
47+
48+
# Check cross-chain PAE - the function computes separate 0->1 and 1->0
49+
# and stores them in respective positions (not symmetric)
50+
self.assertTrue(torch.allclose(mean[0, 0, 1], torch.tensor(3.0)))
51+
self.assertTrue(torch.allclose(min_val[0, 0, 1], torch.tensor(3.0)))
52+
self.assertTrue(torch.allclose(mean[0, 1, 0], torch.tensor(4.0)))
53+
self.assertTrue(torch.allclose(min_val[0, 1, 0], torch.tensor(4.0)))
54+
55+
def test_with_contact_probs(self):
56+
"""Test with custom contact probabilities"""
57+
N_sample = 1
958
N_token = 4
10-
N_chain = 2
1159

12-
# Asymmetric IDs: two chains, 2 tokens each
13-
asym_id = torch.tensor([0, 0, 1, 1], dtype=torch.long)
60+
token_pair_pae = torch.zeros(N_sample, N_token, N_token)
61+
token_pair_pae[0, :2, 2:] = 5.0
62+
token_pair_pae[0, 2:, :2] = 10.0
1463

15-
# All tokens have frames
16-
token_has_frame = torch.tensor([True, True, True, False], dtype=torch.bool)
17-
18-
# Token pair PAE: [N_samples, N_token, N_token]
19-
# Sample 0
20-
token_pair_pae_s0 = torch.tensor([
21-
[1.0, 2.0, 3.0, 4.0],
22-
[2.0, 1.0, 4.0, 3.0],
23-
[3.0, 4.0, 1.0, 2.0],
24-
[4.0, 3.0, 2.0, 1.0],
25-
])
26-
# Sample 1
27-
token_pair_pae_s1 = torch.tensor([
28-
[2.0, 3.0, 4.0, 5.0],
29-
[3.0, 2.0, 5.0, 4.0],
30-
[4.0, 5.0, 2.0, 3.0],
31-
[5.0, 4.0, 3.0, 2.0],
32-
])
33-
token_pair_pae = torch.stack([token_pair_pae_s0, token_pair_pae_s1])
34-
35-
# Expected output shapes
36-
# chain_pair_pae_mean: [N_samples, N_chain, N_chain]
37-
# chain_pair_pae_min: [N_samples, N_chain, N_chain]
64+
contact_probs = torch.zeros(N_token, N_token)
65+
contact_probs[:2, 2:] = 0.1
66+
contact_probs[2:, :2] = 0.9
67+
68+
asym_id = torch.tensor([0, 0, 1, 1], dtype=torch.long)
69+
token_has_frame = torch.ones(N_token, dtype=torch.bool)
3870

3971
result = calculate_chain_pair_pae(
40-
token_pair_pae=token_pair_pae,
41-
asym_id=asym_id,
42-
token_has_frame=token_has_frame,
72+
token_pair_pae, asym_id, token_has_frame, contact_probs=contact_probs
4373
)
4474

45-
self.assertIn("chain_pair_pae_mean", result)
46-
self.assertIn("chain_pair_pae_min", result)
75+
mean = result["chain_pair_pae_mean"]
76+
77+
# chain_pair_pae_mean[0, a, b] only uses contact_probs[a_mask, b_mask]
78+
# So 0->1 uses contact_probs[:2, 2:] = 0.1
79+
self.assertTrue(torch.allclose(mean[0, 0, 1], torch.tensor(5.0)))
80+
# 1->0 uses contact_probs[2:, :2] = 0.9
81+
self.assertTrue(torch.allclose(mean[0, 1, 0], torch.tensor(10.0)))
82+
83+
def test_single_chain(self):
84+
"""Test with only one chain"""
85+
N_sample = 1
86+
N_token = 4
87+
88+
token_pair_pae = torch.ones(N_sample, N_token, N_token)
89+
asym_id = torch.zeros(N_token, dtype=torch.long)
90+
token_has_frame = torch.ones(N_token, dtype=torch.bool)
91+
92+
result = calculate_chain_pair_pae(token_pair_pae, asym_id, token_has_frame)
93+
94+
mean = result["chain_pair_pae_mean"]
95+
min_val = result["chain_pair_pae_min"]
4796

48-
chain_pair_pae_mean = result["chain_pair_pae_mean"]
97+
self.assertEqual(mean.shape, (N_sample, 1, 1))
98+
self.assertEqual(min_val.shape, (N_sample, 1, 1))
99+
100+
def test_no_valid_tokens(self):
101+
"""Test with no tokens having frame"""
102+
N_sample = 1
103+
N_token = 4
104+
105+
token_pair_pae = torch.ones(N_sample, N_token, N_token)
106+
asym_id = torch.tensor([0, 0, 1, 1], dtype=torch.long)
107+
token_has_frame = torch.zeros(N_token, dtype=torch.bool)
108+
109+
result = calculate_chain_pair_pae(token_pair_pae, asym_id, token_has_frame)
110+
111+
mean = result["chain_pair_pae_mean"]
112+
min_val = result["chain_pair_pae_min"]
113+
114+
self.assertTrue(torch.isnan(mean[0, 0, 1]))
115+
self.assertTrue(torch.isnan(min_val[0, 0, 1]))
116+
117+
def test_multiple_samples(self):
118+
"""Test with multiple samples"""
119+
N_sample = 2
120+
N_token = 4
121+
122+
token_pair_pae = torch.zeros(N_sample, N_token, N_token)
123+
token_pair_pae[0, :2, 2:] = 1.0
124+
token_pair_pae[0, 2:, :2] = 2.0
125+
token_pair_pae[1, :2, 2:] = 3.0
126+
token_pair_pae[1, 2:, :2] = 4.0
127+
128+
asym_id = torch.tensor([0, 0, 1, 1], dtype=torch.long)
129+
token_has_frame = torch.ones(N_token, dtype=torch.bool)
130+
131+
result = calculate_chain_pair_pae(token_pair_pae, asym_id, token_has_frame)
132+
133+
mean = result["chain_pair_pae_mean"]
134+
135+
self.assertTrue(torch.allclose(mean[0, 0, 1], torch.tensor(1.0)))
136+
self.assertTrue(torch.allclose(mean[0, 1, 0], torch.tensor(2.0)))
137+
self.assertTrue(torch.allclose(mean[1, 0, 1], torch.tensor(3.0)))
138+
self.assertTrue(torch.allclose(mean[1, 1, 0], torch.tensor(4.0)))
139+
140+
def test_gapped_asym_id(self):
141+
"""Test with non-contiguous asym_id"""
142+
N_sample = 1
143+
N_token = 4
144+
145+
token_pair_pae = torch.ones(N_sample, N_token, N_token)
146+
asym_id = torch.tensor([1, 1, 3, 3], dtype=torch.long) # Gapped IDs
147+
token_has_frame = torch.ones(N_token, dtype=torch.bool)
148+
149+
result = calculate_chain_pair_pae(token_pair_pae, asym_id, token_has_frame)
150+
151+
mean = result["chain_pair_pae_mean"]
152+
self.assertEqual(mean.shape, (N_sample, 2, 2)) # Should remap to 0 and 1
153+
154+
155+
class TestSampleConfidence(unittest.TestCase):
156+
def test_calculate_chain_pair_pae(self):
157+
"""Existing test case preserved for backward compatibility"""
158+
N_sample = 1
159+
N_token = 6
160+
token_pair_pae = torch.ones(N_sample, N_token, N_token)
161+
token_pair_pae[0, :3, 3:] = 1.0
162+
token_pair_pae[0, 3:, :3] = 1.0
163+
asym_id = torch.tensor([0, 0, 0, 1, 1, 1], dtype=torch.long)
164+
token_has_frame = torch.ones(N_token, dtype=torch.bool)
165+
166+
result = calculate_chain_pair_pae(token_pair_pae, asym_id, token_has_frame)
49167
chain_pair_pae_min = result["chain_pair_pae_min"]
50168

51-
self.assertEqual(chain_pair_pae_mean.shape, (N_samples, N_chain, N_chain))
52-
self.assertEqual(chain_pair_pae_min.shape, (N_samples, N_chain, N_chain))
53-
54-
# Check chain 1 vs chain 1 (Sample 0)
55-
# For chain 1 vs chain 1, the valid tokens are only token 2 (token 3 has frame=False)
56-
# token_pair_pae_s0[2, 2] = 1.0
57-
# min = 1.0, mean = 1.0
58-
self.assertTrue(torch.allclose(chain_pair_pae_min[0, 1, 1], torch.tensor(1.0)))
59-
self.assertTrue(torch.allclose(chain_pair_pae_mean[0, 1, 1], torch.tensor(1.0)))
60-
61-
# Check chain 0 vs chain 1 (Sample 0)
62-
# For chain 0 vs chain 1, valid tokens for chain 0 are 0, 1. For chain 1 is 2.
63-
# sub_pae for sample 0 is token_pair_pae_s0[0:2, 2] -> [3.0, 4.0]
64-
# min = min(3.0, 4.0) = 3.0
65-
# mean = (3.0 + 4.0) / 2 = 3.5
66-
self.assertTrue(torch.allclose(chain_pair_pae_min[0, 0, 1], torch.tensor(3.0)))
67-
self.assertTrue(torch.allclose(chain_pair_pae_mean[0, 0, 1], torch.tensor(3.5)))
169+
self.assertEqual(chain_pair_pae_min.shape, (N_sample, 2, 2))
170+
self.assertTrue(torch.allclose(chain_pair_pae_min[0, 0, 1], torch.tensor(1.0)))
171+
68172

69173
if __name__ == "__main__":
70174
unittest.main()
175+

0 commit comments

Comments
 (0)