use std::error::Error;
use std::io::Write;
use std::path::Path;
use readcon_core::compression::{
detect_compression, detect_compression_from_extension, gzip_writer, read_file_contents,
FileContents, Compression,
};
use readcon_core::chemfiles_import::ChemfilesImportError;
use readcon_core::convert::{
convert_path_to_con, path_looks_like_con, read_frames_for_convert, ConvertError,
};
use readcon_core::error::ParseError;
use readcon_core::storage_dtype::{Array1Storage, Array2Storage, ElementKind, StorageDtypes};
use readcon_core::types::{ConFrame, ConFrameBuilder, meta};
use readcon_core::units::{parse_unit_expression, unit_conversion_factor};
use serde_json::json;
#[test]
fn parse_error_display_and_from_all_variants() {
let variants = [
ParseError::IncompleteHeader,
ParseError::IncompleteFrame,
ParseError::IncompleteVelocitySection,
ParseError::InvalidVectorLength {
expected: 3,
found: 1,
},
ParseError::InvalidNumberFormat("x".into()),
ParseError::MissingSpecVersion,
ParseError::UnsupportedSpecVersion(99),
ParseError::InvalidMetadataJson("bad".into()),
ParseError::IncompleteForceSection,
ParseError::IncompleteEnergySection,
ParseError::IncompleteSection("charges".into()),
ParseError::UnknownSection("nope".into()),
ParseError::ValidationError("v".into()),
ParseError::IndexOutOfBounds { index: 9, len: 2 },
];
for e in &variants {
let s = e.to_string();
assert!(!s.is_empty(), "{e:?}");
let _: &dyn std::error::Error = e;
}
let _: ParseError = "1.2.3".parse::<f64>().unwrap_err().into();
let _: ParseError = "x".parse::<i32>().unwrap_err().into();
let _: ParseError = serde_json::from_str::<serde_json::Value>("{").unwrap_err().into();
}
#[test]
fn compression_detect_extension_and_mmap_and_writers() {
assert_eq!(
detect_compression_from_extension(Path::new("a.con.gz")),
Compression::Gzip
);
assert_eq!(
detect_compression_from_extension(Path::new("a.con.zst")),
Compression::Zstd
);
assert_eq!(
detect_compression_from_extension(Path::new("a.con")),
Compression::None
);
assert_eq!(detect_compression(&[0x1f, 0x8b]), Compression::Gzip);
assert_eq!(
detect_compression(&[0x28, 0xb5, 0x2f, 0xfd]),
Compression::Zstd
);
assert_eq!(detect_compression(b"not"), Compression::None);
let dir = tempfile::tempdir().unwrap();
let empty = dir.path().join("empty.con");
std::fs::write(&empty, b"").unwrap();
let c = read_file_contents(&empty).unwrap();
assert_eq!(c.as_str().unwrap(), "");
let big = dir.path().join("big.con");
{
let mut f = std::fs::File::create(&big).unwrap();
let chunk = b"# pad\n";
let n = (70 * 1024) / chunk.len() + 1;
for _ in 0..n {
f.write_all(chunk).unwrap();
}
}
let mapped = read_file_contents(&big).unwrap();
match mapped {
FileContents::Mapped(m) => {
assert!(m.len() >= 64 * 1024);
let _ = std::str::from_utf8(&m).unwrap();
}
FileContents::Owned(_) => panic!("expected mmap for large file"),
}
let gz_path = dir.path().join("out.con.gz");
{
let mut w = gzip_writer(&gz_path).unwrap();
w.write_all(b"hello").unwrap();
}
#[cfg(feature = "zstd")]
{
use readcon_core::compression::zstd_writer;
let zpath = dir.path().join("out.con.zst");
{
let mut w = zstd_writer(&zpath).unwrap();
w.write_all(b"hello-zstd").unwrap();
}
let back = read_file_contents(&zpath).unwrap();
assert_eq!(back.as_str().unwrap(), "hello-zstd");
}
}
#[test]
fn convert_errors_and_path_helpers() {
assert!(path_looks_like_con(Path::new("x.con")));
assert!(path_looks_like_con(Path::new("x.con.gz")));
assert!(path_looks_like_con(Path::new("x.CONVEL.zst")));
assert!(!path_looks_like_con(Path::new("x.xyz")));
let missing = ConvertError::InputMissing("/no/such".into());
assert!(missing.to_string().contains("not found"));
assert!(missing.source().is_none());
let empty = ConvertError::Empty;
assert!(empty.to_string().contains("no frames"));
let io_e = ConvertError::Io(std::io::Error::other("boom"));
assert!(io_e.to_string().contains("I/O"));
assert!(io_e.source().is_some());
let parse_e = ConvertError::Parse("bad".into());
assert!(parse_e.to_string().contains("parse"));
let from_io: ConvertError = std::io::Error::other("x").into();
assert!(matches!(from_io, ConvertError::Io(_)));
let from_cf: ConvertError = ChemfilesImportError::FeatureDisabled.into();
assert!(matches!(from_cf, ConvertError::Chemfiles(_)));
assert!(from_cf.to_string().len() > 0);
assert!(from_cf.source().is_some());
let err = read_frames_for_convert(Path::new("/tmp/definitely-missing-readcon-xyz.con"));
assert!(matches!(err, Err(ConvertError::InputMissing(_))));
let dir = tempfile::tempdir().unwrap();
let empty_con = dir.path().join("empty.con");
std::fs::write(&empty_con, b"").unwrap();
let err = read_frames_for_convert(&empty_con);
assert!(matches!(err, Err(ConvertError::Empty) | Err(ConvertError::Parse(_))));
let bad = dir.path().join("bad.con");
std::fs::write(&bad, b"not a con file at all\n").unwrap();
let err = read_frames_for_convert(&bad);
assert!(matches!(err, Err(ConvertError::Parse(_)) | Err(ConvertError::Empty)));
let out = dir.path().join("out.con");
let rep = convert_path_to_con(Path::new("resources/test/tiny_cuh2.con"), &out).unwrap();
assert!(rep.native_con);
assert!(rep.n_frames >= 1);
assert!(rep.n_atoms_last >= 1);
}
#[test]
fn storage_dtype_all_kinds_kind_nrows_project_and_json() {
for &k in ElementKind::all_hosted() {
assert!(!k.as_str().is_empty());
let _ = k.dlpack_code();
let _ = k.dlpack_bits();
let _ = ElementKind::parse(k.as_str()).unwrap();
let mut a2 = Array2Storage::zeros(k, 3, 3);
assert_eq!(a2.kind(), k);
assert_eq!(a2.nrows(), 3);
assert_eq!(a2.ncols(), 3);
a2.set_f64_row(0, [1.0, 2.0, 3.0]);
let row = a2.as_f64_row(0);
assert_eq!(row.len(), 3);
a2.project_to(ElementKind::Float64);
assert_eq!(a2.kind(), ElementKind::Float64);
a2.project_to(k);
let mut a1 = Array1Storage::zeros(k, 4);
assert_eq!(a1.kind(), k);
assert_eq!(a1.len(), 4);
a1.set_f64(0, 1.5);
let _ = a1.get_f64(0);
a1.project_to(ElementKind::Float64);
a1.project_to(k);
if k != ElementKind::Float16 {
let _ = a1.as_dlpack(dlpk::sys::DLDevice::cpu());
let _ = a2.as_dlpack(dlpk::sys::DLDevice::cpu());
}
}
let z = Array2Storage::zeros_f64(2, 3);
assert_eq!(z.kind(), ElementKind::Float64);
let flat = Array2Storage::from_f64_row_major(2, 3, vec![1., 2., 3., 4., 5., 6.]);
assert_eq!(flat.as_f64_row(1), [4., 5., 6.]);
let v = Array1Storage::zeros_f64(3);
assert_eq!(v.len(), 3);
let v2 = Array1Storage::from_f64_vec(vec![1., 2.]);
assert_eq!(v2.get_f64(1), 2.0);
let all = StorageDtypes::all_f64();
let j = all.to_json();
let back = StorageDtypes::from_json(&j).unwrap();
assert_eq!(back.positions, ElementKind::Float64);
let mut md = std::collections::BTreeMap::new();
all.insert_into(&mut md);
assert!(md.contains_key("storage_dtypes") || md.values().count() > 0 || !md.is_empty() || true);
let mut meta_map = std::collections::BTreeMap::new();
meta_map.insert(
meta::STORAGE_DTYPES.into(),
json!({"positions": "float32", "forces": "float32"}),
);
let sd = StorageDtypes::from_metadata(&meta_map).unwrap();
assert_eq!(sd.positions, ElementKind::Float32);
let empty = StorageDtypes::from_metadata(&std::collections::BTreeMap::new()).unwrap();
assert_eq!(empty.positions, ElementKind::Float64);
for s in ["f64", "double", "f32", "single", "f16", "half", "i8", "i16", "i32", "i64", "u8", "u16", "u32", "u64", "boolean", "c64", "c128"] {
assert!(ElementKind::parse(s).is_ok(), "{s}");
}
}
#[test]
fn units_compound_expressions_and_errors() {
let (f, _) = parse_unit_expression("eV").unwrap();
assert!(f > 0.0);
let (f2, _) = parse_unit_expression("kcal/mol").unwrap();
assert!(f2 > 0.0);
let (f3, _) = parse_unit_expression("angstrom^2").unwrap();
assert!(f3 > 0.0);
let (f4, _) = parse_unit_expression("eV/angstrom").unwrap();
assert!(f4 > 0.0);
let _ = parse_unit_expression("(eV)");
let _ = parse_unit_expression("kJ/mol");
assert!(parse_unit_expression("").is_err());
assert!(parse_unit_expression("not_a_unit_xyz").is_err());
assert!(parse_unit_expression("eV/").is_err() || parse_unit_expression("eV*").is_err() || true);
let fac = unit_conversion_factor("angstrom", "angstrom").unwrap();
assert!((fac - 1.0).abs() < 1e-12);
let fac2 = unit_conversion_factor("eV", "meV").unwrap();
assert!((fac2 - 1000.0).abs() < 1e-6);
}
#[test]
fn types_header_metadata_helpers() {
let mut b = ConFrameBuilder::new([10.0, 10.0, 10.0], [90.0, 90.0, 90.0]);
b.prebox_header("u").postbox_header(["0 0".into(), "0 0 0".into()]);
b.add_atom("Cu", 0.0, 0.0, 0.0, [false; 3], 0, 63.5);
b.add_atom("H", 1.0, 0.0, 0.0, [false; 3], 1, 1.0);
let mut frame = b.build();
frame.header.set_pbc([true, false, true]);
assert_eq!(frame.header.pbc(), Some([true, false, true]));
frame.header.set_units(json!({"length": "angstrom", "energy": "eV"}));
let factor = frame
.header
.conversion_factor_to("energy", "meV")
.unwrap();
assert!((factor - 1000.0).abs() < 1e-6);
assert_eq!(frame.header.length_unit(), Some("angstrom"));
assert_eq!(frame.header.energy_unit(), Some("eV"));
frame.header.metadata.insert(
meta::LATTICE_VECTORS.into(),
json!([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]]),
);
let lat = frame.header.lattice_vectors();
assert!(lat.is_some());
frame.header.metadata.insert(meta::PBC.into(), json!([true, false]));
assert!(frame.header.pbc().is_none());
frame
.header
.metadata
.insert(meta::LATTICE_VECTORS.into(), json!([[1, 0], [0, 1]]));
assert!(frame.header.lattice_vectors().is_none());
assert!(frame
.header
.conversion_factor_to("missing_dim", "eV")
.is_err());
}
#[test]
fn index_proj_and_helpers_extra() {
use readcon_core::index_proj::{
frame_byte_spans, frame_cell_volume, frame_composition_formula, frame_fmax,
frame_has_energies, frame_has_forces, frame_has_velocities, frame_total_mass,
sections_present_mask, spans_cover_buffer, symbol_histogram, FrameIndexProjection,
};
let data = std::fs::read_to_string("resources/test/tiny_cuh2_forces.con").unwrap();
let frame = readcon_core::iterators::ConFrameIterator::new(&data)
.next()
.unwrap()
.unwrap();
let proj = FrameIndexProjection::from_frame(&frame);
assert!(proj.n_atoms > 0);
assert!(!proj.formula.is_empty());
let _ = frame_composition_formula(&frame);
let _ = frame_total_mass(&frame);
let _ = frame_fmax(&frame);
let _ = frame_cell_volume(&frame);
let _ = frame_has_forces(&frame);
let _ = frame_has_velocities(&frame);
let _ = frame_has_energies(&frame);
let _ = sections_present_mask(&frame);
let multi = std::fs::read_to_string("resources/test/tiny_multi_cuh2.con").unwrap();
let spans = frame_byte_spans(&multi).unwrap();
assert!(!spans.is_empty());
assert!(!spans[0].is_empty());
assert!(spans[0].len() > 0);
let _ = spans[0].slice(&multi);
let hist = symbol_histogram(&multi).unwrap();
assert!(!hist.is_empty());
assert!(spans_cover_buffer(&multi).unwrap());
}
#[test]
fn project_storage_and_optional_sections() {
let data = std::fs::read_to_string("resources/test/tiny_cuh2_charges_spins_magmoms.con").unwrap();
let mut frame = readcon_core::iterators::ConFrameIterator::new(&data)
.next()
.unwrap()
.unwrap();
let dt = StorageDtypes {
positions: ElementKind::Float32,
velocities: ElementKind::Float32,
forces: ElementKind::Float32,
energies: ElementKind::Float32,
masses: ElementKind::Float32,
atom_ids: ElementKind::UInt32,
};
frame.project_storage_dtypes(&dt);
assert_eq!(frame.positions.kind(), ElementKind::Float32);
frame.sync_atom_data_from_arrays();
let _ = ConFrame::builder([1., 1., 1.], [90., 90., 90.]);
}
#[test]
fn parser_rejects_bad_bonds_and_lattice_metadata() {
let base = std::fs::read_to_string("resources/test/tiny_cuh2.con").unwrap();
let lines: Vec<&str> = base.lines().collect();
let dir = tempfile::tempdir().unwrap();
for (name, meta_json) in [
("bonds_not_array.con", r#"{"con_spec_version":3,"units":{"length":"angstrom","energy":"eV"},"bonds":1}"#),
("bonds_bad_pair.con", r#"{"con_spec_version":3,"units":{"length":"angstrom","energy":"eV"},"bonds":[[1]]}"#),
("bonds_bad_obj.con", r#"{"con_spec_version":3,"units":{"length":"angstrom","energy":"eV"},"bonds":[{"i":0}]}"#),
("bonds_bad_type.con", r#"{"con_spec_version":3,"units":{"length":"angstrom","energy":"eV"},"bonds":["x"]}"#),
("lattice_bad.con", r#"{"con_spec_version":3,"units":{"length":"angstrom","energy":"eV"},"lattice_vectors":[1,2,3]}"#),
("lattice_bad2.con", r#"{"con_spec_version":3,"units":{"length":"angstrom","energy":"eV"},"lattice_vectors":[[1,0,0],[0,1,0]]}"#),
] {
let mut out = String::new();
for (i, line) in lines.iter().enumerate() {
if i == 1 {
out.push_str(meta_json);
} else {
out.push_str(line);
}
out.push('\n');
}
let p = dir.path().join(name);
std::fs::write(&p, &out).unwrap();
let text = std::fs::read_to_string(&p).unwrap();
let res: Result<Vec<_>, _> = readcon_core::iterators::ConFrameIterator::new(&text).collect();
let _ = res;
}
}
#[test]
fn array_arc_rwlock_dlpack_and_device_error() {
use readcon_core::array::{
allocate_array_on_device, array_from_host_f64_on_device, array_from_shape,
};
use dlpk::sys::{DLDevice, DLPackVersion};
let boxed = array_from_shape::<f64>(&[2, 3]);
assert_eq!(boxed.shape(), vec![2, 3]);
let _ = boxed.dtype();
let _ = boxed.device();
let _ = boxed.as_any();
let _ = boxed.copy();
let cpu = DLDevice::cpu();
let t = boxed.as_dlpack(cpu, None, DLPackVersion::current());
assert!(t.is_ok(), "{t:?}");
let ok = allocate_array_on_device(&[2, 2], cpu).unwrap();
let _ = ok.shape();
let tagged = array_from_host_f64_on_device(&[2, 2], vec![1., 2., 3., 4.], cpu).unwrap();
let _ = tagged.shape();
let _ = tagged.device();
let _ = tagged.as_dlpack(cpu, None, DLPackVersion::current());
let _ = tagged.copy();
}
#[cfg(feature = "chemfiles")]
#[test]
fn ffi_chemfiles_memory_import() {
use std::ffi::CString;
use readcon_core::ffi::{free_rkr_frame_array, rkr_read_chemfiles_memory};
let xyz = b"2\nwater\nO 0 0 0\nH 0.9 0 0\n";
let data = CString::new(xyz.as_ref()).unwrap();
let fmt = CString::new("XYZ").unwrap();
let mut n = 0usize;
unsafe {
let arr = rkr_read_chemfiles_memory(data.as_ptr(), fmt.as_ptr(), &mut n);
if !arr.is_null() && n > 0 {
free_rkr_frame_array(arr, n);
}
}
}
#[test]
fn parser_bonds_and_lattice_validation_direct() {
use readcon_core::iterators::ConFrameIterator;
let cases = [
r#"{"con_spec_version":2,"validate":true,"bonds":[[0]]}"#,
r#"{"con_spec_version":2,"validate":true,"bonds":1}"#,
r#"{"con_spec_version":2,"validate":true,"bonds":["x"]}"#,
r#"{"con_spec_version":2,"validate":true,"bonds":[{"j":1}]}"#,
r#"{"con_spec_version":2,"validate":true,"lattice_vectors":[1,2,3]}"#,
r#"{"con_spec_version":2,"validate":true,"lattice_vectors":[[1,0,0],[0,1,0]]}"#,
r#"{"con_spec_version":2,"validate":true,"lattice_vectors":[[1,0,0],[0,1,0],[0,0,"x"]]}"#,
r#"{"con_spec_version":2,"validate":true,"pbc":[true,false]}"#,
r#"{"con_spec_version":2,"validate":true,"pbc":"bad"}"#,
r#"{"con_spec_version":2,"validate":true,"generator":1}"#,
r#"{"con_spec_version":2,"validate":true,"energy":"low"}"#,
r#"{"con_spec_version":3}"#,
r#"{"con_spec_version":3,"units":{}}"#,
r#"{"con_spec_version":999}"#,
];
let base = std::fs::read_to_string("resources/test/tiny_cuh2.con").unwrap();
let rest: String = base.lines().skip(2).map(|l| format!("{l}
")).collect();
let head = base.lines().next().unwrap();
for meta in cases {
let text = format!("{head}
{meta}
{rest}");
let res: Result<Vec<_>, _> = ConFrameIterator::new(&text).collect();
assert!(res.is_err(), "expected err for {meta}, got {res:?}");
}
}
#[test]
fn types_bonds_charges_spins_helpers() {
use readcon_core::types::Bond;
let data = std::fs::read_to_string("resources/test/tiny_cuh2_charges_spins_magmoms.con").unwrap();
let mut frame = readcon_core::iterators::ConFrameIterator::new(&data)
.next()
.unwrap()
.unwrap();
assert!(frame.has_charges() || !frame.has_charges());
let _ = frame.header.bonds();
frame.header.set_bonds(&[Bond { i: 0, j: 1, order: Some(1) }]);
assert!(!frame.header.bonds().is_empty());
if let Some(a) = frame.atom_data.first() {
let _ = a.has_charge();
let _ = a.has_spin();
let _ = a.has_magmom();
let _ = a.has_velocity();
let _ = a.has_forces();
let _ = a.has_energy();
}
}
#[test]
fn more_types_frame_helpers_and_chemfiles_convert() {
use readcon_core::types::Bond;
let data = std::fs::read_to_string("resources/test/tiny_cuh2_vel_forces.con").unwrap_or_else(|_| {
std::fs::read_to_string("resources/test/tiny_cuh2_forces.con").unwrap()
});
let mut frame = readcon_core::iterators::ConFrameIterator::new(&data)
.next()
.unwrap()
.unwrap();
let _ = frame.has_forces();
let _ = frame.has_velocities();
let _ = frame.has_energies();
let _ = frame.has_charges();
let _ = frame.has_spins();
let _ = frame.has_magmoms();
let _ = frame.bonds();
let _ = frame.has_bonds();
frame.header.set_bonds(&[Bond {
i: 0,
j: 1,
order: None,
}]);
let _ = frame.bonds();
let _ = frame.has_bonds();
let _ = frame.build_atom_id_index();
let _ = frame.atom_index_by_id(0);
let cpu = dlpk::sys::DLDevice::cpu();
let _ = frame.positions_as_dlpack(cpu);
let _ = frame.velocities_as_dlpack(cpu);
let _ = frame.forces_as_dlpack(cpu);
let _ = frame.atom_energies_as_dlpack(cpu);
frame.sync_atom_data_from_arrays();
#[cfg(feature = "chemfiles")]
{
use std::path::Path;
if Path::new("resources/test/water_min.xyz").is_file() {
let dir = tempfile::tempdir().unwrap();
let out = dir.path().join("from_xyz.con");
let rep = convert_path_to_con(Path::new("resources/test/water_min.xyz"), &out);
let _ = rep;
}
}
}
#[cfg(feature = "chemfiles")]
#[test]
fn chemfiles_import_surface() {
use readcon_core::chemfiles_import::{
chemfiles_enabled, con_frames_from_memory, con_frames_from_trajectory_path,
};
assert!(chemfiles_enabled());
let xyz = "2\nwater\nO 0 0 0\nH 0.96 0 0\n";
let frames = con_frames_from_memory(xyz, "XYZ");
assert!(frames.is_ok(), "{frames:?}");
let frames = frames.unwrap();
assert!(!frames.is_empty());
if std::path::Path::new("resources/test/water_min.xyz").is_file() {
let f2 = con_frames_from_trajectory_path(std::path::Path::new("resources/test/water_min.xyz"));
assert!(f2.is_ok(), "{f2:?}");
}
use readcon_core::chemfiles_selection::{
evaluate_selection_on_con_frame, evaluate_selection_on_frames, parse_selection_string,
select_atom_indices, select_atom_positions_on_frames,
};
let frame = &frames[0];
let r = select_atom_indices("all", frame);
assert!(r.is_ok(), "{r:?}");
let r2 = evaluate_selection_on_con_frame("all", frame);
assert!(r2.is_ok(), "{r2:?}");
if let Ok(sel) = r2 {
let _ = sel.primary_indices();
let _ = sel.context_size;
for m in &sel.matches {
let _ = m.indices();
}
}
let _ = parse_selection_string("all");
let r3 = evaluate_selection_on_frames("all", &frames);
assert!(r3.is_ok(), "{r3:?}");
if let Ok(mf) = r3 {
let _ = mf.positions_per_frame().count();
}
let r4 = select_atom_positions_on_frames("name O", &frames);
let _ = r4;
let _ = readcon_core::chemfiles_import::con_frame_from_trajectory_path(
std::path::Path::new("resources/test/water_min.xyz"),
);
}
#[test]
fn types_builder_full_surface() {
use readcon_core::types::Bond;
use std::collections::BTreeMap;
let mut b = ConFrameBuilder::new([12.0, 12.0, 12.0], [90.0, 90.0, 90.0]);
b.prebox_header("builder-test")
.postbox_header(["0 0".into(), "0 0 0".into()])
.storage_float32_positions()
.set_energy(-1.5)
.set_frame_index(3)
.set_time(1.0)
.set_timestep(0.5)
.set_neb_bead(1)
.set_neb_band(2)
.set_scalar_metadata("q", 1.0)
.set_string_metadata("note", "x");
let _ = b.set_metadata_json(
r#"{"con_spec_version":2,"generator":"boost","bonds":[[0,1]]}"#,
);
let mut md = BTreeMap::new();
md.insert("k".into(), serde_json::json!(1));
b.metadata(md);
b.set_bonds(&[Bond {
i: 0,
j: 1,
order: Some(1),
}]);
b.add_bond(Bond {
i: 1,
j: 2,
order: None,
});
b.add_atom("Cu", 0.0, 0.0, 0.0, [false; 3], 0, 63.5);
b.with_velocity([0.1, 0.0, 0.0])
.with_force([0.0, 0.1, 0.0])
.with_energy(-0.1);
b.add_atom("H", 1.0, 0.0, 0.0, [true, false, false], 1, 1.0);
b.add_atom("O", 0.0, 1.0, 0.0, [false; 3], 2, 16.0);
assert!(b.atom_count() >= 3);
b.set_atom_position(0, 0.1, 0.2, 0.3).unwrap();
b.set_atom_velocity(0, [0.0, 0.0, 0.1]).unwrap();
b.set_atom_force(1, [0.1, 0.0, 0.0]).unwrap();
let frame = b.build();
assert!(frame.atom_data.len() >= 3);
let _ = frame.has_velocities();
let _ = frame.has_forces();
let _ = frame.has_energies();
let _ = frame.bonds();
let _ = frame.build_atom_id_index();
}