Skip to content

Commit 6ecb4ca

Browse files
committed
fix(io): Correct handling of residue indices in bio topology conversion
1 parent 84bccaf commit 6ecb4ca

File tree

1 file changed

+54
-4
lines changed

1 file changed

+54
-4
lines changed

src/io/util.rs

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,14 @@ pub fn to_bio_topology(system: &System) -> Result<bf::Topology, ConversionError>
186186
type ResidueKey = (String, i32, Option<char>);
187187
let mut chains: IndexMap<String, IndexMap<ResidueKey, bf::Residue>> = IndexMap::new();
188188

189-
for (atom, info) in system.atoms.iter().zip(metadata.atom_info.iter()) {
189+
let mut residue_members: IndexMap<ResidueKey, Vec<usize>> = IndexMap::new();
190+
191+
for (i, (atom, info)) in system
192+
.atoms
193+
.iter()
194+
.zip(metadata.atom_info.iter())
195+
.enumerate()
196+
{
190197
let bio_atom = bf::Atom::new(
191198
&info.atom_name,
192199
convert_element_to_bf(atom.element)?,
@@ -196,7 +203,7 @@ pub fn to_bio_topology(system: &System) -> Result<bf::Topology, ConversionError>
196203
let residue_key = (info.chain_id.clone(), info.residue_id, info.insertion_code);
197204
let residues = chains.entry(info.chain_id.clone()).or_default();
198205

199-
let residue = match residues.entry(residue_key) {
206+
let residue = match residues.entry(residue_key.clone()) {
200207
Entry::Occupied(e) => e.into_mut(),
201208
Entry::Vacant(e) => {
202209
let mut res = bf::Residue::new(
@@ -212,6 +219,7 @@ pub fn to_bio_topology(system: &System) -> Result<bf::Topology, ConversionError>
212219
};
213220

214221
residue.add_atom(bio_atom);
222+
residue_members.entry(residue_key).or_default().push(i);
215223
}
216224

217225
let mut bio_struct = bf::Structure::new();
@@ -225,13 +233,18 @@ pub fn to_bio_topology(system: &System) -> Result<bf::Topology, ConversionError>
225233
bio_struct.add_chain(chain);
226234
}
227235

236+
let mut old_to_new = vec![0usize; system.atoms.len()];
237+
for (flat, &orig) in residue_members.values().flatten().enumerate() {
238+
old_to_new[orig] = flat;
239+
}
240+
228241
let bio_bonds = system
229242
.bonds
230243
.iter()
231244
.map(|bond| {
232245
Ok(bf::Bond::new(
233-
bond.i,
234-
bond.j,
246+
old_to_new[bond.i],
247+
old_to_new[bond.j],
235248
convert_bond_order_to_bf(bond.order)?,
236249
))
237250
})
@@ -572,6 +585,43 @@ mod tests {
572585
assert_eq!(roundtrip.bio_metadata.as_ref(), Some(&metadata));
573586
}
574587

588+
#[test]
589+
fn to_bio_topology_remaps_bonds_for_non_contiguous_atoms() {
590+
let atoms = vec![
591+
Atom::new(Element::C, [0.0, 0.0, 0.0]),
592+
Atom::new(Element::N, [3.0, 0.0, 0.0]),
593+
Atom::new(Element::C, [0.0, 1.5, 0.0]),
594+
];
595+
let metadata = BioMetadata {
596+
atom_info: vec![
597+
AtomResidueInfo::builder("CA", "ALA", 1, "A").build(),
598+
AtomResidueInfo::builder("N", "ALA", 2, "A").build(),
599+
AtomResidueInfo::builder("CB", "ALA", 1, "A").build(),
600+
],
601+
target_ph: None,
602+
};
603+
let bonds = vec![Bond::new(0, 2, BondOrder::Single)];
604+
let system = System {
605+
atoms,
606+
bonds,
607+
box_vectors: None,
608+
bio_metadata: Some(metadata),
609+
};
610+
611+
let bio_topo = to_bio_topology(&system).expect("conversion should succeed");
612+
613+
let bio_bonds = bio_topo.bonds();
614+
assert_eq!(bio_bonds.len(), 1);
615+
assert_eq!(
616+
bio_bonds[0].a1_idx, 0,
617+
"bond endpoint a should be flat 0 (CA)"
618+
);
619+
assert_eq!(
620+
bio_bonds[0].a2_idx, 1,
621+
"bond endpoint b should be flat 1 (CB)"
622+
);
623+
}
624+
575625
#[test]
576626
fn converts_clean_config_fields() {
577627
let mut remove_res = HashSet::new();

0 commit comments

Comments
 (0)