|
50 | 50 | ''' |
51 | 51 |
|
52 | 52 |
|
| 53 | +# Exact pinned writer's 19-column layout; deliberately inert coordinates and |
| 54 | +# confidence, not a captured prediction. Occupancy is the only omitted column. |
| 55 | +NATIVE_CIF = '''data_fixture |
| 56 | +# |
| 57 | +loop_ |
| 58 | +_atom_site.group_PDB |
| 59 | +_atom_site.type_symbol |
| 60 | +_atom_site.label_atom_id |
| 61 | +_atom_site.label_alt_id |
| 62 | +_atom_site.label_comp_id |
| 63 | +_atom_site.label_asym_id |
| 64 | +_atom_site.label_entity_id |
| 65 | +_atom_site.label_seq_id |
| 66 | +_atom_site.pdbx_PDB_ins_code |
| 67 | +_atom_site.auth_seq_id |
| 68 | +_atom_site.auth_comp_id |
| 69 | +_atom_site.auth_asym_id |
| 70 | +_atom_site.auth_atom_id |
| 71 | +_atom_site.B_iso_or_equiv |
| 72 | +_atom_site.Cartn_x |
| 73 | +_atom_site.Cartn_y |
| 74 | +_atom_site.Cartn_z |
| 75 | +_atom_site.pdbx_PDB_model_num |
| 76 | +_atom_site.id |
| 77 | +ATOM C CA . ALA X 1 7 A 42 ALA A CA 0.5 1 2 3 1 1 |
| 78 | +ATOM C CA . ALA Y 1 1 ? 42 ALA B CA 80 2 3 4 1 2 |
| 79 | +HETATM C C1 . LIG Z 2 . ? 1 LIG L C1 40 3 4 5 1 3 |
| 80 | +HETATM O O1 . LIG Z 2 . ? 1 LIG L O1 40 4 5 6 1 4 |
| 81 | +# |
| 82 | +''' |
| 83 | + |
| 84 | + |
| 85 | +@pytest.mark.parametrize('suffix', ['.cif', '.mmcif']) |
| 86 | +def test_native_cif_compatibility_preserves_geometry_identity_and_confidence(suffix): |
| 87 | + from services.core_protein_result_contract import _structure_confidence |
| 88 | + from services.md.starting_structures import _parse_mmcif_structure |
| 89 | + |
| 90 | + data = NATIVE_CIF.encode() |
| 91 | + original = bytes(data) |
| 92 | + atoms = list(_parse_mmcif_structure(data).get_atoms()) |
| 93 | + assert [atom.occupancy for atom in atoms] == [1.0] * 4 |
| 94 | + np.testing.assert_array_equal([atom.coord for atom in atoms], |
| 95 | + [[1, 2, 3], [2, 3, 4], [3, 4, 5], [4, 5, 6]]) |
| 96 | + assert [atom.get_parent().get_parent().id for atom in atoms] == ['A', 'B', 'L', 'L'] |
| 97 | + assert [atom.name for atom in atoms] == ['CA', 'CA', 'C1', 'O1'] |
| 98 | + assert atoms[0].get_parent().id == (' ', 42, 'A') |
| 99 | + assert [atom.bfactor for atom in atoms] == [.5, 80, 40, 40] |
| 100 | + assert _structure_confidence(data, 'native' + suffix) == (40.25, [.5, 80]) |
| 101 | + assert data == original and b'_atom_site.occupancy' not in data |
| 102 | + |
| 103 | + |
| 104 | +@pytest.mark.parametrize('damage', ['nonnumeric_coordinate', 'nonfinite_coordinate', |
| 105 | + 'missing_coordinate', 'missing_atom_identity', |
| 106 | + 'missing_chain_identity', 'missing_residue_identity', |
| 107 | + 'invalid_present_occupancy']) |
| 108 | +def test_native_occupancy_compatibility_does_not_repair_other_damage(damage): |
| 109 | + from services.core_protein_result_contract import CandidateIntegrityError, _structure_confidence |
| 110 | + |
| 111 | + content = NATIVE_CIF |
| 112 | + if damage in {'nonnumeric_coordinate', 'nonfinite_coordinate'}: |
| 113 | + value = 'broken' if damage == 'nonnumeric_coordinate' else 'nan' |
| 114 | + content = content.replace('0.5 1 2 3', f'0.5 {value} 2 3') |
| 115 | + elif damage == 'invalid_present_occupancy': |
| 116 | + content = content.replace('_atom_site.id\n', '_atom_site.id\n_atom_site.occupancy\n') |
| 117 | + content = '\n'.join(line + ' broken' if line.startswith(('ATOM ', 'HETATM ')) else line |
| 118 | + for line in content.split('\n')) |
| 119 | + else: |
| 120 | + column = {'missing_coordinate': 'Cartn_x', 'missing_atom_identity': 'label_atom_id', |
| 121 | + 'missing_chain_identity': 'auth_asym_id', |
| 122 | + 'missing_residue_identity': 'label_comp_id'}[damage] |
| 123 | + lines = content.splitlines() |
| 124 | + headers = [line for line in lines if line.startswith('_atom_site.')] |
| 125 | + index = headers.index('_atom_site.' + column) |
| 126 | + content = '\n'.join(' '.join(line.split()[:index] + line.split()[index + 1:]) |
| 127 | + if line.startswith(('ATOM ', 'HETATM ')) else line |
| 128 | + for line in lines if line != '_atom_site.' + column) + '\n' |
| 129 | + with pytest.raises(CandidateIntegrityError) as error: |
| 130 | + _structure_confidence(content.encode(), 'native.cif') |
| 131 | + assert error.value.reason['code'] == 'candidate_structure_invalid' |
| 132 | + |
| 133 | + |
53 | 134 | @pytest.fixture(autouse=True) |
54 | 135 | def isolated_artifacts(monkeypatch, tmp_path): |
55 | 136 | monkeypatch.setenv('BMS_SCIENTIFIC_ARTIFACT_ROOT', str(tmp_path / 'scientific_artifacts')) |
56 | 137 |
|
57 | 138 |
|
58 | | -def produce(tmp_path, monkeypatch, *, confidence=True, pae=True): |
| 139 | +def produce(tmp_path, monkeypatch, *, confidence=True, pae=True, cif=CIF): |
59 | 140 | """Run the real CLI writer against an explicitly inert fold result.""" |
60 | 141 | import sys |
61 | 142 | output = tmp_path / 'esmfold2_results' |
62 | 143 | output.mkdir() |
63 | 144 | matrix = np.arange(25, dtype=np.float32).reshape(5, 5) / 2 |
64 | | - samples = [SimpleNamespace(complex=SimpleNamespace(to_mmcif=lambda: CIF), |
| 145 | + samples = [SimpleNamespace(complex=SimpleNamespace(to_mmcif=lambda: cif), |
65 | 146 | plddt=np.array([0, .2, .6, .8, 1], dtype=np.float32), ptm=.7, iptm=0, |
66 | 147 | pae=matrix + index if pae else None, |
67 | 148 | pair_chains_iptm=np.array([[.2, .4], [.8, .6]]), |
@@ -120,8 +201,9 @@ async def session(): |
120 | 201 |
|
121 | 202 | @pytest.mark.asyncio |
122 | 203 | @pytest.mark.parametrize('model_id', ['esmfold2', 'esmfold2_experimental']) |
123 | | -async def test_producer_ingest_fresh_api_sample_binding(tmp_path, monkeypatch, model_id): |
124 | | - root, samples = produce(tmp_path, monkeypatch) |
| 204 | +@pytest.mark.parametrize('cif', [CIF, NATIVE_CIF], ids=['occupancy_present', 'native_occupancy_absent']) |
| 205 | +async def test_producer_ingest_fresh_api_sample_binding(tmp_path, monkeypatch, model_id, cif): |
| 206 | + root, samples = produce(tmp_path, monkeypatch, cif=cif) |
125 | 207 | manifest = json.loads((root / 'manifest.json').read_text()) |
126 | 208 | assert manifest['sample_count'] == 2 |
127 | 209 | for entry, sample in zip(manifest['samples'], samples): |
|
0 commit comments