use super::*;
use crate::element::Element;
use crate::index::ResidueIndex;
use crate::optional::OptionalSymbol;
use crate::symbol::AltId;
use num_traits::ToPrimitive;
fn atom(residue: u32, element: Element, serial: u32) -> AtomRecord {
AtomRecord {
position: Some([serial.to_f32().expect("small serial"), 0.0, 0.0]),
element,
atom_name: SymbolId::from_raw(0),
auth_atom_name: OptionalSymbol::NONE,
alternate_component_id: OptionalSymbol::NONE,
alt_id: AltId::BLANK,
residue: ResidueIndex::new(residue),
occupancy: (1.0, Presence::Present),
b_factor: (
10.0 + serial.to_f32().expect("small serial"),
Presence::Present,
),
formal_charge: (0, Presence::Inapplicable),
atom_site_id: serial,
}
}
#[test]
fn a_chunk_never_ends_in_the_middle_of_a_residue() {
let mut builder = ChunkBuilder::with_target(10);
for residue in 0..6u32 {
for offset in 0..7u32 {
builder.push(atom(residue, Element::CARBON, residue * 7 + offset));
}
}
let (chunks, _) = builder.finish();
assert!(chunks.len() > 1, "the target should have closed a chunk");
for chunk in &chunks {
let first = chunk.atoms().start;
let last = chunk.atoms().end - 1;
assert_eq!(first % 7, 0, "chunk starts at a residue boundary");
assert_eq!(last % 7, 6, "chunk ends at a residue boundary");
}
}
#[test]
fn chunks_cover_every_atom_once_and_in_order() {
let mut builder = ChunkBuilder::with_target(8);
for serial in 0..100u32 {
builder.push(atom(serial / 3, Element::CARBON, serial));
}
let (chunks, coords) = builder.finish();
assert_eq!(coords.len(), 100);
let mut expected = 0u32;
for chunk in &chunks {
assert_eq!(chunk.atoms().start, expected);
expected = chunk.atoms().end;
}
assert_eq!(expected, 100);
}
#[test]
fn a_chunks_summary_reports_the_elements_it_actually_contains() {
let mut builder = ChunkBuilder::new();
builder.push(atom(0, Element::CARBON, 0));
builder.push(atom(0, Element::ZINC, 1));
let (chunks, _) = builder.finish();
let stats = chunks.first().map(AtomChunk::stats);
assert_eq!(
stats.map(|stats| stats.elements.contains(Element::ZINC)),
Some(true)
);
assert_eq!(
stats.map(|stats| stats.elements.contains(Element::IRON)),
Some(false)
);
assert_eq!(stats.map(|stats| stats.has_hydrogen), Some(false));
}
#[test]
fn an_atom_with_no_recorded_position_keeps_its_row_and_is_marked_absent() {
let mut builder = ChunkBuilder::new();
let mut without = atom(0, Element::OXYGEN, 0);
without.position = None;
builder.push(without);
builder.push(atom(0, Element::OXYGEN, 1));
let (chunks, coords) = builder.finish();
assert_eq!(coords.len(), 2, "the row exists even without a position");
let Some(chunk) = chunks.first() else {
panic!("expected one chunk")
};
assert!(!chunk.has_position(0));
assert!(chunk.has_position(1));
assert!(chunk.stats().has_missing_coords);
}
#[test]
fn a_missing_position_does_not_poison_the_bounding_box() {
let mut builder = ChunkBuilder::new();
let mut without = atom(0, Element::OXYGEN, 0);
without.position = None;
builder.push(without);
builder.push(atom(0, Element::OXYGEN, 5));
let (chunks, _) = builder.finish();
let Some(bounds) = chunks.first().map(|chunk| chunk.stats().bounds) else {
panic!("expected one chunk")
};
assert_eq!(
bounds.min.map(f32::to_bits),
[5.0, 0.0, 0.0].map(f32::to_bits)
);
assert_eq!(
bounds.max.map(f32::to_bits),
[5.0, 0.0, 0.0].map(f32::to_bits)
);
}
#[test]
fn a_new_model_starts_a_new_chunk_so_the_model_column_can_vanish() {
let mut builder = ChunkBuilder::new();
builder.start_model(0);
builder.push(atom(0, Element::CARBON, 0));
builder.start_model(1);
builder.push(atom(1, Element::CARBON, 1));
let (chunks, _) = builder.finish();
assert_eq!(chunks.len(), 2);
assert_eq!(chunks.first().map(AtomChunk::model), Some(0));
assert_eq!(chunks.get(1).map(AtomChunk::model), Some(1));
}
#[test]
fn a_chunk_reads_back_every_value_it_was_given() {
let mut builder = ChunkBuilder::new();
for serial in 0..20u32 {
builder.push(atom(
serial / 4,
Element::from_atomic_number(6 + u8::try_from(serial).expect("small serial")),
serial,
));
}
let (chunks, coords) = builder.finish();
let Some(chunk) = chunks.first() else {
panic!("expected one chunk")
};
for local in 0..20u32 {
assert_eq!(
chunk.element(local),
Some(Element::from_atomic_number(
6 + u8::try_from(local).expect("small local index"),
))
);
assert_eq!(chunk.atom_site_id(local), Some(local));
assert_eq!(
chunk.b_factor(local).map(|value| value.0),
Some(10.0 + local.to_f32().expect("small local index"))
);
assert_eq!(chunk.alt_id(local), Some(AltId::BLANK));
}
assert_eq!(chunk.positions(&coords).map(<[[f32; 3]]>::len), Some(20));
}
#[test]
fn the_depositor_atom_name_column_is_absent_when_no_file_carried_one() {
let mut builder = ChunkBuilder::new();
builder.push(atom(0, Element::CARBON, 0));
let (chunks, _) = builder.finish();
assert_eq!(
chunks.first().and_then(|chunk| chunk.auth_atom_name(0)),
None
);
}
#[test]
fn an_optional_atom_name_stays_absent_beside_a_present_one() {
let mut builder = ChunkBuilder::new();
let mut named = atom(0, Element::CARBON, 0);
named.auth_atom_name = OptionalSymbol::some(SymbolId::from_raw(7));
builder.push(named);
builder.push(atom(0, Element::CARBON, 1));
let (chunks, _) = builder.finish();
let Some(chunk) = chunks.first() else {
panic!("expected one chunk")
};
assert_eq!(chunk.auth_atom_name(0), Some(SymbolId::from_raw(7)));
assert_eq!(chunk.auth_atom_name(1), None);
}