use super::atom::{AtomChunk, AtomRecord};
use super::parent::ParentMapping;
use super::stats::AtomChunkStats;
use crate::column::{EncodedColumn, Presence, ValidityMask};
use crate::coords::CoordinateBlock;
use crate::symbol::SymbolId;
pub const TARGET_CHUNK_ATOMS: u32 = 4096;
#[derive(Debug, Default)]
pub struct ChunkBuilder {
chunks: Vec<AtomChunk>,
coords: CoordinateBlock,
target: u32,
model: u32,
pending: Pending,
}
#[derive(Debug, Default)]
struct Pending {
first_atom: u32,
element: Vec<u8>,
atom_name: Vec<SymbolId>,
auth_atom_name: Vec<u32>,
has_auth_names: bool,
alternate_component_id: Vec<u32>,
has_alternate_components: bool,
alt_id: Vec<u32>,
residue: Vec<u32>,
occupancy: Vec<f32>,
occupancy_presence: Vec<Presence>,
b_factor: Vec<f32>,
b_factor_presence: Vec<Presence>,
formal_charge: Vec<i8>,
formal_charge_presence: Vec<Presence>,
atom_site_id: Vec<u32>,
coord_presence: Vec<Presence>,
stats: AtomChunkStats,
}
impl Pending {
fn len(&self) -> usize {
self.element.len()
}
fn is_empty(&self) -> bool {
self.element.is_empty()
}
fn last_residue(&self) -> Option<u32> {
self.residue.last().copied()
}
fn starts_new_residue(&self, residue: u32) -> bool {
self.last_residue() != Some(residue)
}
fn should_close_before(&self, target: u32, residue: u32) -> bool {
self.len() as u64 >= u64::from(target) && self.starts_new_residue(residue)
}
fn begin(&mut self, first_atom: u32, model: u32) {
if !self.is_empty() {
return;
}
self.first_atom = first_atom;
self.stats.model = model;
}
fn push_record(&mut self, record: &AtomRecord) {
self.element.push(record.element.atomic_number());
self.atom_name.push(record.atom_name);
self.push_auth_atom_name(record);
self.push_alternate_component(record);
self.alt_id.push(record.alt_id.get());
self.residue.push(record.residue.get());
push_with_presence(
&mut self.occupancy,
&mut self.occupancy_presence,
record.occupancy,
);
push_with_presence(
&mut self.b_factor,
&mut self.b_factor_presence,
record.b_factor,
);
push_with_presence(
&mut self.formal_charge,
&mut self.formal_charge_presence,
record.formal_charge,
);
self.atom_site_id.push(record.atom_site_id);
self.observe(record);
}
fn push_auth_atom_name(&mut self, record: &AtomRecord) {
match record.auth_atom_name.get() {
Some(name) => {
self.auth_atom_name.push(name.get());
self.has_auth_names = true;
}
None => self.auth_atom_name.push(u32::MAX),
}
}
fn push_alternate_component(&mut self, record: &AtomRecord) {
match record.alternate_component_id.get() {
Some(component) => {
self.alternate_component_id.push(component.get());
self.has_alternate_components = true;
}
None => self.alternate_component_id.push(u32::MAX),
}
}
fn observe(&mut self, record: &AtomRecord) {
self.stats.elements.insert(record.element);
self.stats.has_hydrogen |= record.element.is_hydrogen();
self.stats.has_altloc |= !record.alt_id.is_blank();
if record.occupancy.1.is_present() {
self.stats.occupancy.observe(record.occupancy.0);
}
if record.b_factor.1.is_present() {
self.stats.b_factor.observe(record.b_factor.0);
}
self.observe_residue(record.residue.get());
}
fn observe_residue(&mut self, residue: u32) {
if self.residue.len() == 1 {
self.stats.residue_min = residue;
self.stats.residue_max = residue;
return;
}
self.stats.residue_min = self.stats.residue_min.min(residue);
self.stats.residue_max = self.stats.residue_max.max(residue);
}
fn reset(&mut self) {
self.first_atom = 0;
self.has_auth_names = false;
self.has_alternate_components = false;
self.element.clear();
self.atom_name.clear();
self.auth_atom_name.clear();
self.alternate_component_id.clear();
self.alt_id.clear();
self.residue.clear();
self.occupancy.clear();
self.occupancy_presence.clear();
self.b_factor.clear();
self.b_factor_presence.clear();
self.formal_charge.clear();
self.formal_charge_presence.clear();
self.atom_site_id.clear();
self.coord_presence.clear();
self.stats = AtomChunkStats::default();
}
}
impl ChunkBuilder {
#[must_use]
pub fn new() -> Self {
Self {
target: TARGET_CHUNK_ATOMS,
..Self::default()
}
}
#[must_use]
pub fn with_target(target: u32) -> Self {
Self {
target: target.max(1),
..Self::default()
}
}
pub fn reserve(&mut self, atoms: usize) {
self.coords = CoordinateBlock::with_capacity(atoms);
}
pub fn start_model(&mut self, model: u32) {
self.close();
self.model = model;
}
pub fn push(&mut self, record: AtomRecord) {
self.close_before(&record);
self.pending.begin(self.coords.len(), self.model);
self.push_position(record.position);
self.pending.push_record(&record);
}
fn close_before(&mut self, record: &AtomRecord) {
if self
.pending
.should_close_before(self.target, record.residue.get())
{
self.close();
}
}
fn push_position(&mut self, position: Option<[f32; 3]>) {
if let Some(position) = position {
self.coords.push(position);
self.pending.stats.bounds.extend(position);
self.pending.coord_presence.push(Presence::Present);
} else {
self.coords.push([f32::NAN; 3]);
self.pending.coord_presence.push(Presence::Unknown);
self.pending.stats.has_missing_coords = true;
}
}
fn close(&mut self) {
if self.pending.is_empty() {
return;
}
let chunk = self.build_chunk();
self.chunks.push(chunk);
self.pending.reset();
}
fn build_chunk(&mut self) -> AtomChunk {
let end = self.coords.len();
let pending = &mut self.pending;
let first = pending.first_atom;
let len = end - first;
let occupancy_validity = compact_validity(&pending.occupancy_presence, len);
let b_factor_validity = compact_validity(&pending.b_factor_presence, len);
let formal_charge_validity = compact_validity(&pending.formal_charge_presence, len);
let coord_validity = compact_validity(&pending.coord_presence, len);
let element = EncodedColumn::encode(&pending.element);
let atom_name = EncodedColumn::plain(&pending.atom_name);
let auth_atom_name = pending
.has_auth_names
.then(|| EncodedColumn::plain(&pending.auth_atom_name));
let alternate_component_id = pending
.has_alternate_components
.then(|| EncodedColumn::encode(&pending.alternate_component_id));
let alt_id = EncodedColumn::encode(&pending.alt_id);
let residue = ParentMapping::block_indexed(&pending.residue);
let occupancy = EncodedColumn::plain(&pending.occupancy);
let b_factor = EncodedColumn::plain(&pending.b_factor);
let formal_charge = EncodedColumn::encode(&pending.formal_charge);
let atom_site_id = EncodedColumn::encode(&pending.atom_site_id);
let stats = std::mem::take(&mut pending.stats);
AtomChunk {
atoms: first..end,
model: self.model,
element,
atom_name,
auth_atom_name,
alternate_component_id,
alt_id,
residue,
occupancy,
occupancy_validity,
b_factor,
b_factor_validity,
formal_charge,
formal_charge_validity,
atom_site_id,
coord_validity,
stats,
}
}
#[must_use]
pub fn finish(mut self) -> (Vec<AtomChunk>, CoordinateBlock) {
self.close();
(self.chunks, self.coords)
}
}
fn push_with_presence<T>(values: &mut Vec<T>, presences: &mut Vec<Presence>, entry: (T, Presence)) {
let (value, presence) = entry;
values.push(value);
presences.push(presence);
}
fn compact_validity(presences: &[Presence], len: u32) -> ValidityMask {
ValidityMask::from_bounded_iter(presences.iter().copied(), len)
}
#[cfg(test)]
#[path = "builder_tests.rs"]
mod tests;