molgfx-geometry 0.3.5

GPU geometry generation: impostor packing, cartoons, labels, bounding hierarchies.
Documentation
use super::*;
use crate::{pack_atoms, packing::pack::tests::fixture_structure};
use molgfx_core::{
    AtomSelection, BondTopologyFrame, BondTopologySegment, EntityKind, RepresentationKind, Scene,
    TopologyBond,
};
use std::sync::Arc;

#[test]
fn compaction_maps_source_rows_to_packed_indices() {
    let structure = fixture_structure();
    let mut scene = Scene::from_structure(&structure).unwrap_or_else(|error| panic!("{error}"));
    let selection = scene.add_selection(AtomSelection::Sparse(vec![0, 2]));
    let handle = scene
        .represent(selection, RepresentationKind::Spacefill)
        .unwrap_or_else(|error| panic!("{error}"));
    let table = scene.first_atoms().unwrap_or_else(|| panic!("atoms"));
    let representation = scene
        .representation(handle)
        .unwrap_or_else(|| panic!("representation"));
    let mut atoms = Vec::new();
    pack_atoms(
        table,
        representation,
        &AtomSelection::Sparse(vec![0, 2]),
        &mut atoms,
    )
    .unwrap_or_else(|error| panic!("{error}"));
    let mut map = Vec::new();
    build_compaction_map(&atoms, table.len(), &mut map).unwrap_or_else(|error| panic!("{error}"));
    assert_eq!(map, vec![0, u32::MAX, 1]);
}

#[test]
fn bond_packing_remaps_endpoints_and_keeps_only_selected_edges() {
    let structure = fixture_structure();
    let mut scene = Scene::from_structure(&structure).unwrap_or_else(|error| panic!("{error}"));
    let selection = scene.add_selection(AtomSelection::Sparse(vec![1, 2]));
    let handle = scene
        .represent(selection, RepresentationKind::BallAndStick)
        .unwrap_or_else(|error| panic!("{error}"));
    let table = scene.first_atoms().unwrap_or_else(|| panic!("atoms"));
    let representation = scene
        .representation(handle)
        .unwrap_or_else(|| panic!("representation"));
    let mut atoms = Vec::new();
    pack_atoms(
        table,
        representation,
        &AtomSelection::Sparse(vec![1, 2]),
        &mut atoms,
    )
    .unwrap_or_else(|error| panic!("{error}"));
    let mut map = Vec::new();
    build_compaction_map(&atoms, table.len(), &mut map).unwrap_or_else(|error| panic!("{error}"));
    let placed = scene
        .structures()
        .next()
        .map_or_else(|| panic!("structure"), |(_, placed)| placed);
    let mut bonds = Vec::new();
    pack_bonds(placed, representation, &map, &mut bonds).unwrap_or_else(|error| panic!("{error}"));
    assert_eq!(bonds.len(), 2);
    assert!(
        bonds
            .iter()
            .all(|bond| (bond.atom_a, bond.atom_b) == (0, 1))
    );
    assert!(bonds.iter().all(|bond| bond.is_aromatic()));
    assert_eq!(bonds[0].order(), 2);
    assert_eq!(bonds[1].order(), 2);
}

#[test]
fn lines_reuse_the_indexed_bond_path() {
    let structure = fixture_structure();
    let mut scene = Scene::from_structure(&structure).unwrap_or_else(|error| panic!("{error}"));
    let selection = scene.add_selection(AtomSelection::All);
    let handle = scene
        .represent(selection, RepresentationKind::Lines)
        .unwrap_or_else(|error| panic!("{error}"));
    let table = scene.first_atoms().unwrap_or_else(|| panic!("atoms"));
    let representation = scene
        .representation(handle)
        .unwrap_or_else(|| panic!("representation"));
    let mut atoms = Vec::new();
    pack_atoms(table, representation, &AtomSelection::All, &mut atoms)
        .unwrap_or_else(|error| panic!("{error}"));
    let mut map = Vec::new();
    build_compaction_map(&atoms, table.len(), &mut map).unwrap_or_else(|error| panic!("{error}"));
    let placed = scene
        .structures()
        .next()
        .map_or_else(|| panic!("structure"), |(_, placed)| placed);
    let mut bonds = Vec::new();
    pack_bonds(placed, representation, &map, &mut bonds).unwrap_or_else(|error| panic!("{error}"));
    assert_eq!(bonds.len(), 3);
    assert_eq!(bonds[0].order(), 1);
    assert_eq!(bonds[1].order(), 2);
    assert_eq!(bonds[2].order(), 2);
}

#[test]
fn dynamic_connectivity_packs_one_weighted_union_with_distinct_identity() {
    let structure = fixture_structure();
    let mut scene = Scene::from_structure(&structure).unwrap_or_else(|error| panic!("{error}"));
    let selection = scene.add_selection(AtomSelection::All);
    let handle = scene
        .represent(selection, RepresentationKind::BallAndStick)
        .unwrap_or_else(|error| panic!("{error}"));
    let owner = scene
        .structures()
        .next()
        .map_or_else(|| panic!("structure"), |(handle, _)| handle);
    let start_bond = TopologyBond::new(0, 1, false).unwrap_or_else(|error| panic!("{error}"));
    let end_bond = TopologyBond::new(1, 2, true).unwrap_or_else(|error| panic!("{error}"));
    let start = BondTopologyFrame::new(0, 0.0, 3, Arc::from([start_bond]), "start")
        .unwrap_or_else(|error| panic!("{error}"));
    let end = BondTopologyFrame::new(1, 1.0, 3, Arc::from([end_bond]), "end")
        .unwrap_or_else(|error| panic!("{error}"));
    let segment =
        BondTopologySegment::new(start, end, 0.25).unwrap_or_else(|error| panic!("{error}"));
    scene
        .set_bond_topology_segment(owner, segment)
        .unwrap_or_else(|error| panic!("{error}"));
    let table = scene.first_atoms().unwrap_or_else(|| panic!("atoms"));
    let representation = scene
        .representation(handle)
        .unwrap_or_else(|| panic!("representation"));
    let mut atoms = Vec::new();
    pack_atoms(table, representation, &AtomSelection::All, &mut atoms)
        .unwrap_or_else(|error| panic!("{error}"));
    let mut map = Vec::new();
    build_compaction_map(&atoms, table.len(), &mut map).unwrap_or_else(|error| panic!("{error}"));
    let placed = scene
        .structures()
        .next()
        .map_or_else(|| panic!("structure"), |(_, placed)| placed);
    let mut bonds = Vec::new();
    pack_bonds(placed, representation, &map, &mut bonds).unwrap_or_else(|error| panic!("{error}"));
    assert_eq!(bonds.len(), 2);
    assert_eq!(
        bonds[0].draw_radius().to_bits(),
        (representation.params.bond_radius * 0.75).to_bits()
    );
    assert_eq!(
        bonds[1].draw_radius().to_bits(),
        (representation.params.bond_radius * 0.25).to_bits()
    );
    assert_eq!(
        bonds[0].entity_id.unpack(),
        Some((EntityKind::DynamicBond, 0))
    );
    assert_eq!(
        bonds[1].entity_id.unpack(),
        Some((EntityKind::DynamicBond, 1))
    );
}

#[test]
fn a_multi_bond_packs_one_strand_per_order_with_sequential_variants() {
    let structure = fixture_structure();
    let mut scene = Scene::from_structure(&structure).unwrap_or_else(|error| panic!("{error}"));
    let selection = scene.add_selection(AtomSelection::All);
    let handle = scene
        .represent(selection, RepresentationKind::BallAndStick)
        .unwrap_or_else(|error| panic!("{error}"));
    let table = scene.first_atoms().unwrap_or_else(|| panic!("atoms"));
    let representation = scene
        .representation(handle)
        .unwrap_or_else(|| panic!("representation"));
    let mut atoms = Vec::new();
    pack_atoms(table, representation, &AtomSelection::All, &mut atoms)
        .unwrap_or_else(|error| panic!("{error}"));
    let mut map = Vec::new();
    build_compaction_map(&atoms, table.len(), &mut map).unwrap_or_else(|error| panic!("{error}"));
    let placed = scene
        .structures()
        .next()
        .map_or_else(|| panic!("structure"), |(_, placed)| placed);
    let mut bonds = Vec::new();
    pack_bonds(placed, representation, &map, &mut bonds).unwrap_or_else(|error| panic!("{error}"));

    // The fixture carries one SING and one AROM edge, so the single bond has
    // one strand and the aromatic has two, each with its own variant index.
    assert_eq!(bonds[0].order(), 1);
    assert_eq!(bonds[0].variant(), 0);
    assert_eq!(bonds[1].order(), 2);
    assert_eq!(bonds[2].order(), 2);
    assert_eq!(
        [bonds[1].variant(), bonds[2].variant()],
        [0, 1],
        "the two strands of one double bond carry distinct variants"
    );
}

#[test]
fn bond_identity_rejects_rows_above_the_chunk_local_limit() {
    let above_limit = usize::try_from(u64::from(EntityId::MAX_INDEX) + 1)
        .unwrap_or_else(|error| panic!("{error}"));
    let error = bond_entity(EntityKind::Bond, above_limit)
        .expect_err("an unencodable source row must fail");
    assert!(matches!(error, PackingError::Entity(_)));

    if let Ok(above_u32) = usize::try_from(u64::from(u32::MAX) + 1) {
        let error = gpu_index("test compaction", above_u32)
            .expect_err("a compacted row wider than u32 must fail");
        assert!(matches!(error, PackingError::IndexOverflow { .. }));
    }
}