1use crate::helpers::symbol_to_atomic_number;
2use crate::iterators::{self, ConFrameIterator};
3use crate::types::{ConFrame, ConFrameBuilder, meta};
4use crate::writer::ConFrameWriter;
5use std::ffi::{CStr, CString, c_char};
6use std::fs::{self, File};
7use std::path::Path;
8use std::ptr;
9
10pub const RKR_CON_SPEC_VERSION: u32 = 2;
22
23#[unsafe(no_mangle)]
25pub extern "C" fn rkr_con_spec_version() -> u32 {
26 crate::CON_SPEC_VERSION
27}
28
29#[unsafe(no_mangle)]
32pub extern "C" fn rkr_library_version() -> *const c_char {
33 const VERSION_NUL: &[u8] = concat!(env!("CARGO_PKG_VERSION"), "\0").as_bytes();
35 VERSION_NUL.as_ptr() as *const c_char
36}
37
38#[unsafe(no_mangle)]
50pub unsafe extern "C" fn rkr_frame_atom_index_by_id(
51 frame_handle: *const RKRConFrame,
52 atom_id: u64,
53) -> u64 {
54 let frame = match unsafe { (frame_handle as *const ConFrame).as_ref() } {
55 Some(f) => f,
56 None => return u64::MAX,
57 };
58 match frame.atom_index_by_id(atom_id) {
59 Some(idx) => idx as u64,
60 None => u64::MAX,
61 }
62}
63
64#[unsafe(no_mangle)]
73pub unsafe extern "C" fn rkr_symbol_to_z(symbol: *const c_char) -> u64 {
74 if symbol.is_null() {
75 return 0;
76 }
77 match unsafe { CStr::from_ptr(symbol) }.to_str() {
78 Ok(s) => symbol_to_atomic_number(s),
79 Err(_) => 0,
80 }
81}
82
83#[unsafe(no_mangle)]
88pub extern "C" fn rkr_z_to_symbol(z: u64) -> *const c_char {
89 macro_rules! cstrs {
94 ($($lit:literal),* $(,)?) => {
95 [$(concat!($lit, "\0").as_bytes()),*]
96 };
97 }
98 const TABLE: [&[u8]; 93] = cstrs![
99 "X", "H", "He", "Li", "Be", "B", "C", "N", "O", "F", "Ne", "Na", "Mg", "Al", "Si", "P",
100 "S", "Cl", "Ar", "K", "Ca", "Sc", "Ti", "V", "Cr", "Mn", "Fe", "Co", "Ni", "Cu", "Zn",
101 "Ga", "Ge", "As", "Se", "Br", "Kr", "Rb", "Sr", "Y", "Zr", "Nb", "Mo", "Tc", "Ru", "Rh",
102 "Pd", "Ag", "Cd", "In", "Sn", "Sb", "Te", "I", "Xe", "Cs", "Ba", "La", "Ce", "Pr", "Nd",
103 "Pm", "Sm", "Eu", "Gd", "Tb", "Dy", "Ho", "Er", "Tm", "Yb", "Lu", "Hf", "Ta", "W", "Re",
104 "Os", "Ir", "Pt", "Au", "Hg", "Tl", "Pb", "Bi", "Po", "At", "Rn", "Fr", "Ra", "Ac", "Th",
105 "Pa", "U",
106 ];
107 let idx = if (1..=92).contains(&z) { z as usize } else { 0 };
108 TABLE[idx].as_ptr() as *const c_char
109}
110
111#[unsafe(no_mangle)]
114pub extern "C" fn rkr_frame_spec_version(frame_handle: *const RKRConFrame) -> u32 {
115 match unsafe { (frame_handle as *const ConFrame).as_ref() } {
116 Some(f) => f.header.spec_version,
117 None => 0,
118 }
119}
120
121#[unsafe(no_mangle)]
128pub unsafe extern "C" fn rkr_frame_metadata_json(frame_handle: *const RKRConFrame) -> *mut c_char {
129 let frame = match unsafe { (frame_handle as *const ConFrame).as_ref() } {
130 Some(f) => f,
131 None => return ptr::null_mut(),
132 };
133 let mut obj = serde_json::Map::new();
134 obj.insert(
135 meta::CON_SPEC_VERSION.into(),
136 serde_json::Value::from(frame.header.spec_version),
137 );
138 for (k, v) in &frame.header.metadata {
139 obj.insert(k.clone(), v.clone());
140 }
141 let json_str = serde_json::Value::Object(obj).to_string();
142 match CString::new(json_str) {
143 Ok(cs) => cs.into_raw(),
144 Err(_) => ptr::null_mut(),
145 }
146}
147
148#[unsafe(no_mangle)]
150pub extern "C" fn rkr_frame_energy(frame_handle: *const RKRConFrame) -> f64 {
151 match unsafe { (frame_handle as *const ConFrame).as_ref() } {
152 Some(f) => f.header.energy().unwrap_or(f64::NAN),
153 None => f64::NAN,
154 }
155}
156
157#[unsafe(no_mangle)]
164pub unsafe extern "C" fn rkr_frame_potential_type(frame_handle: *const RKRConFrame) -> *mut c_char {
165 let frame = match unsafe { (frame_handle as *const ConFrame).as_ref() } {
166 Some(f) => f,
167 None => return ptr::null_mut(),
168 };
169 match frame.header.potential_type() {
170 Some(pot_type) => match CString::new(pot_type) {
171 Ok(cs) => cs.into_raw(),
172 Err(_) => ptr::null_mut(),
173 },
174 None => ptr::null_mut(),
175 }
176}
177
178#[unsafe(no_mangle)]
180pub extern "C" fn rkr_frame_frame_index(frame_handle: *const RKRConFrame) -> u64 {
181 match unsafe { (frame_handle as *const ConFrame).as_ref() } {
182 Some(f) => f.header.frame_index().unwrap_or(u64::MAX),
183 None => u64::MAX,
184 }
185}
186
187#[unsafe(no_mangle)]
189pub extern "C" fn rkr_frame_time(frame_handle: *const RKRConFrame) -> f64 {
190 match unsafe { (frame_handle as *const ConFrame).as_ref() } {
191 Some(f) => f.header.time().unwrap_or(f64::NAN),
192 None => f64::NAN,
193 }
194}
195
196#[unsafe(no_mangle)]
198pub extern "C" fn rkr_frame_timestep(frame_handle: *const RKRConFrame) -> f64 {
199 match unsafe { (frame_handle as *const ConFrame).as_ref() } {
200 Some(f) => f.header.timestep().unwrap_or(f64::NAN),
201 None => f64::NAN,
202 }
203}
204
205#[unsafe(no_mangle)]
207pub extern "C" fn rkr_frame_neb_bead(frame_handle: *const RKRConFrame) -> u64 {
208 match unsafe { (frame_handle as *const ConFrame).as_ref() } {
209 Some(f) => f.header.neb_bead().unwrap_or(u64::MAX),
210 None => u64::MAX,
211 }
212}
213
214#[unsafe(no_mangle)]
216pub extern "C" fn rkr_frame_neb_band(frame_handle: *const RKRConFrame) -> u64 {
217 match unsafe { (frame_handle as *const ConFrame).as_ref() } {
218 Some(f) => f.header.neb_band().unwrap_or(u64::MAX),
219 None => u64::MAX,
220 }
221}
222
223#[repr(C)]
229#[allow(non_camel_case_types)]
230#[derive(Debug, PartialEq, Eq)]
231pub enum RKRStatus {
232 RKR_STATUS_SUCCESS = 0,
234 RKR_STATUS_NULL_POINTER = -1,
236 RKR_STATUS_INVALID_UTF8 = -2,
238 RKR_STATUS_INVALID_JSON = -3,
240 RKR_STATUS_IO_ERROR = -4,
242 RKR_STATUS_INDEX_OUT_OF_BOUNDS = -5,
244 RKR_STATUS_BUFFER_TOO_SMALL = -6,
246 RKR_STATUS_INTERNAL_ERROR = -7,
248}
249
250#[unsafe(no_mangle)]
253pub extern "C" fn rkr_status_message(status: RKRStatus) -> *const c_char {
254 match status {
255 RKRStatus::RKR_STATUS_SUCCESS => c"success".as_ptr(),
256 RKRStatus::RKR_STATUS_NULL_POINTER => c"null pointer".as_ptr(),
257 RKRStatus::RKR_STATUS_INVALID_UTF8 => c"invalid UTF-8".as_ptr(),
258 RKRStatus::RKR_STATUS_INVALID_JSON => c"invalid JSON".as_ptr(),
259 RKRStatus::RKR_STATUS_IO_ERROR => c"I/O error".as_ptr(),
260 RKRStatus::RKR_STATUS_INDEX_OUT_OF_BOUNDS => c"index out of bounds".as_ptr(),
261 RKRStatus::RKR_STATUS_BUFFER_TOO_SMALL => c"buffer too small".as_ptr(),
262 RKRStatus::RKR_STATUS_INTERNAL_ERROR => c"internal error".as_ptr(),
263 }
264}
265
266#[repr(C)]
269pub struct RKRConFrame {
270 _private: [u8; 0],
271}
272
273#[repr(C)]
276pub struct RKRConFrameWriter {
277 _private: [u8; 0],
278}
279
280#[repr(C)]
284pub struct CFrame {
285 pub atoms: *mut CAtom,
286 pub num_atoms: usize,
287 pub cell: [f64; 3],
288 pub angles: [f64; 3],
289 pub has_velocities: bool,
290 pub has_forces: bool,
291 pub has_energies: bool,
292}
293
294#[repr(C)]
304pub struct CAtom {
305 pub atomic_number: u64,
306 pub x: f64,
307 pub y: f64,
308 pub z: f64,
309 pub atom_id: u64,
310 pub mass: f64,
311 pub is_fixed: bool,
314 pub fixed_x: bool,
315 pub fixed_y: bool,
316 pub fixed_z: bool,
317 pub vx: f64,
318 pub vy: f64,
319 pub vz: f64,
320 pub has_velocity: bool,
321 pub fx: f64,
322 pub fy: f64,
323 pub fz: f64,
324 pub has_forces: bool,
325 pub energy: f64,
328 pub has_energy: bool,
329}
330
331#[repr(C)]
332pub struct CConFrameIterator {
333 iterator: *mut ConFrameIterator<'static>,
334 file_contents: *mut String,
335}
336
337#[unsafe(no_mangle)]
353pub unsafe extern "C" fn read_con_file_iterator(
354 filename_c: *const c_char,
355) -> *mut CConFrameIterator {
356 if filename_c.is_null() {
357 return ptr::null_mut();
358 }
359 let filename = match unsafe { CStr::from_ptr(filename_c).to_str() } {
360 Ok(s) => s,
361 Err(_) => return ptr::null_mut(),
362 };
363 let file_contents_box = match fs::read_to_string(filename) {
364 Ok(contents) => Box::new(contents),
365 Err(_) => return ptr::null_mut(),
366 };
367 let file_contents_ptr = Box::into_raw(file_contents_box);
368 let static_file_contents: &'static str = unsafe { &*file_contents_ptr };
369 let iterator = Box::new(ConFrameIterator::new(static_file_contents));
370 let c_iterator = Box::new(CConFrameIterator {
371 iterator: Box::into_raw(iterator),
372 file_contents: file_contents_ptr,
373 });
374 Box::into_raw(c_iterator)
375}
376
377#[unsafe(no_mangle)]
383pub unsafe extern "C" fn con_frame_iterator_next(
384 iterator: *mut CConFrameIterator,
385) -> *mut RKRConFrame {
386 if iterator.is_null() {
387 return ptr::null_mut();
388 }
389 let iter = unsafe { &mut *(*iterator).iterator };
390 match iter.next() {
391 Some(Ok(frame)) => Box::into_raw(Box::new(frame)) as *mut RKRConFrame,
392 _ => ptr::null_mut(),
393 }
394}
395
396#[unsafe(no_mangle)]
401pub unsafe extern "C" fn free_rkr_frame(frame_handle: *mut RKRConFrame) {
402 if !frame_handle.is_null() {
403 let _ = unsafe { Box::from_raw(frame_handle as *mut ConFrame) };
404 }
405}
406
407#[unsafe(no_mangle)]
412pub unsafe extern "C" fn free_con_frame_iterator(iterator: *mut CConFrameIterator) {
413 if iterator.is_null() {
414 return;
415 }
416 unsafe {
417 let c_iterator_box = Box::from_raw(iterator);
418 let _ = Box::from_raw(c_iterator_box.iterator);
419 let _ = Box::from_raw(c_iterator_box.file_contents);
420 }
421}
422
423#[unsafe(no_mangle)]
433pub unsafe extern "C" fn rkr_frame_to_c_frame(frame_handle: *const RKRConFrame) -> *mut CFrame {
434 let frame = match unsafe { (frame_handle as *const ConFrame).as_ref() } {
435 Some(f) => f,
436 None => return ptr::null_mut(),
437 };
438
439 let masses_iter = frame
440 .header
441 .natms_per_type
442 .iter()
443 .zip(frame.header.masses_per_type.iter())
444 .flat_map(|(num_atoms, mass)| std::iter::repeat_n(*mass, *num_atoms));
445
446 let has_velocities = frame.has_velocities();
447
448 let mut c_atoms: Vec<CAtom> = frame
449 .atom_data
450 .iter()
451 .zip(masses_iter)
452 .map(|(atom_datum, mass)| {
453 let [vx, vy, vz] = atom_datum.velocity.unwrap_or([0.0; 3]);
454 let [fx, fy, fz] = atom_datum.force.unwrap_or([0.0; 3]);
455 CAtom {
456 atomic_number: symbol_to_atomic_number(&atom_datum.symbol),
457 x: atom_datum.x,
458 y: atom_datum.y,
459 z: atom_datum.z,
460 is_fixed: atom_datum.is_fixed(),
461 fixed_x: atom_datum.fixed[0],
462 fixed_y: atom_datum.fixed[1],
463 fixed_z: atom_datum.fixed[2],
464 atom_id: atom_datum.atom_id,
465 mass,
466 vx,
467 vy,
468 vz,
469 has_velocity: atom_datum.has_velocity(),
470 fx,
471 fy,
472 fz,
473 has_forces: atom_datum.has_forces(),
474 energy: atom_datum.energy.unwrap_or(0.0),
475 has_energy: atom_datum.has_energy(),
476 }
477 })
478 .collect();
479
480 let atoms_ptr = c_atoms.as_mut_ptr();
481 let num_atoms = c_atoms.len();
482 std::mem::forget(c_atoms);
483
484 let has_forces = frame.has_forces();
485 let has_energies = frame.has_energies();
486
487 let c_frame = Box::new(CFrame {
488 atoms: atoms_ptr,
489 num_atoms,
490 cell: frame.header.boxl,
491 angles: frame.header.angles,
492 has_velocities,
493 has_forces,
494 has_energies,
495 });
496
497 Box::into_raw(c_frame)
498}
499
500#[unsafe(no_mangle)]
505pub unsafe extern "C" fn free_c_frame(frame: *mut CFrame) {
506 if frame.is_null() {
507 return;
508 }
509 unsafe {
510 let frame_box = Box::from_raw(frame);
511 let _ = Vec::from_raw_parts(frame_box.atoms, frame_box.num_atoms, frame_box.num_atoms);
512 }
513}
514
515#[unsafe(no_mangle)]
534pub unsafe extern "C" fn rkr_frame_get_header_line(
535 frame_handle: *const RKRConFrame,
536 is_prebox: bool,
537 line_index: usize,
538 buffer: *mut c_char,
539 buffer_len: usize,
540) -> RKRStatus {
541 let frame = match unsafe { (frame_handle as *const ConFrame).as_ref() } {
542 Some(f) => f,
543 None => return RKRStatus::RKR_STATUS_NULL_POINTER,
544 };
545 if buffer.is_null() {
546 return RKRStatus::RKR_STATUS_NULL_POINTER;
547 }
548 if buffer_len == 0 {
549 return RKRStatus::RKR_STATUS_BUFFER_TOO_SMALL;
550 }
551 let line_to_copy: Option<&str> = if is_prebox {
552 match line_index {
553 0 => Some(frame.header.prebox_header.user.as_str()),
554 1 => Some(frame.header.prebox_header.metadata_line()),
555 _ => None,
556 }
557 } else {
558 frame.header.postbox_header.get(line_index).map(String::as_str)
559 };
560 if let Some(line) = line_to_copy {
561 let bytes = line.as_bytes();
562 let len_to_copy = std::cmp::min(bytes.len(), buffer_len - 1);
563 unsafe {
564 ptr::copy_nonoverlapping(bytes.as_ptr(), buffer as *mut u8, len_to_copy);
565 *buffer.add(len_to_copy) = 0;
566 }
567 RKRStatus::RKR_STATUS_SUCCESS
568 } else {
569 RKRStatus::RKR_STATUS_INDEX_OUT_OF_BOUNDS
570 }
571}
572
573#[unsafe(no_mangle)]
586pub unsafe extern "C" fn rkr_frame_get_header_line_cpp(
587 frame_handle: *const RKRConFrame,
588 is_prebox: bool,
589 line_index: usize,
590) -> *mut c_char {
591 let frame = match unsafe { (frame_handle as *const ConFrame).as_ref() } {
592 Some(f) => f,
593 None => return ptr::null_mut(),
594 };
595
596 let line_to_copy: Option<&str> = if is_prebox {
597 match line_index {
598 0 => Some(frame.header.prebox_header.user.as_str()),
599 1 => Some(frame.header.prebox_header.metadata_line()),
600 _ => None,
601 }
602 } else {
603 frame.header.postbox_header.get(line_index).map(String::as_str)
604 };
605
606 if let Some(line) = line_to_copy {
607 match CString::new(line) {
609 Ok(c_string) => c_string.into_raw(), Err(_) => ptr::null_mut(), }
612 } else {
613 ptr::null_mut() }
615}
616
617#[unsafe(no_mangle)]
625pub unsafe extern "C" fn rkr_free_string(s: *mut c_char) {
626 if !s.is_null() {
627 let _ = unsafe { CString::from_raw(s) };
629 }
630}
631
632#[unsafe(no_mangle)]
642pub unsafe extern "C" fn create_writer_from_path_c(
643 filename_c: *const c_char,
644) -> *mut RKRConFrameWriter {
645 if filename_c.is_null() {
646 return ptr::null_mut();
647 }
648 let filename = match unsafe { CStr::from_ptr(filename_c).to_str() } {
649 Ok(s) => s,
650 Err(_) => return ptr::null_mut(),
651 };
652 match crate::writer::ConFrameWriter::from_path(filename) {
653 Ok(writer) => Box::into_raw(Box::new(writer)) as *mut RKRConFrameWriter,
654 Err(_) => ptr::null_mut(),
655 }
656}
657
658#[unsafe(no_mangle)]
663pub unsafe extern "C" fn free_rkr_writer(writer_handle: *mut RKRConFrameWriter) {
664 if !writer_handle.is_null() {
665 let _ = unsafe { Box::from_raw(writer_handle as *mut ConFrameWriter<File>) };
666 }
667}
668
669#[unsafe(no_mangle)]
675pub unsafe extern "C" fn rkr_writer_extend(
676 writer_handle: *mut RKRConFrameWriter,
677 frame_handles: *const *const RKRConFrame,
678 num_frames: usize,
679) -> RKRStatus {
680 let writer = match unsafe { (writer_handle as *mut ConFrameWriter<File>).as_mut() } {
681 Some(w) => w,
682 None => return RKRStatus::RKR_STATUS_NULL_POINTER,
683 };
684 if frame_handles.is_null() {
685 return RKRStatus::RKR_STATUS_NULL_POINTER;
686 }
687
688 let handles_slice = unsafe { std::slice::from_raw_parts(frame_handles, num_frames) };
689 let mut rust_frames: Vec<&ConFrame> = Vec::with_capacity(num_frames);
690 if handles_slice.iter().any(|&handle| handle.is_null()) {
691 return RKRStatus::RKR_STATUS_NULL_POINTER;
694 }
695 for &handle in handles_slice.iter() {
696 match unsafe { (handle as *const ConFrame).as_ref() } {
698 Some(frame) => rust_frames.push(frame),
699 None => return RKRStatus::RKR_STATUS_NULL_POINTER,
701 }
702 }
703
704 match writer.extend(rust_frames.into_iter()) {
705 Ok(_) => RKRStatus::RKR_STATUS_SUCCESS,
706 Err(_) => RKRStatus::RKR_STATUS_IO_ERROR,
707 }
708}
709
710#[unsafe(no_mangle)]
720pub unsafe extern "C" fn create_writer_from_path_with_precision_c(
721 filename_c: *const c_char,
722 precision: u8,
723) -> *mut RKRConFrameWriter {
724 if filename_c.is_null() {
725 return ptr::null_mut();
726 }
727 let filename = match unsafe { CStr::from_ptr(filename_c).to_str() } {
728 Ok(s) => s,
729 Err(_) => return ptr::null_mut(),
730 };
731 match ConFrameWriter::from_path_with_precision(filename, precision as usize) {
732 Ok(writer) => Box::into_raw(Box::new(writer)) as *mut RKRConFrameWriter,
733 Err(_) => ptr::null_mut(),
734 }
735}
736
737#[repr(C)]
743pub struct RKRConFrameBuilder {
744 _private: [u8; 0],
745}
746
747#[allow(clippy::too_many_arguments)]
748unsafe fn add_builder_atom(
749 builder_handle: *mut RKRConFrameBuilder,
750 symbol: *const c_char,
751 x: f64,
752 y: f64,
753 z: f64,
754 fixed: [bool; 3],
755 atom_id: u64,
756 mass: f64,
757 velocity: Option<[f64; 3]>,
758 forces: Option<[f64; 3]>,
759) -> RKRStatus {
760 if builder_handle.is_null() || symbol.is_null() {
761 return RKRStatus::RKR_STATUS_NULL_POINTER;
762 }
763 let builder = unsafe { &mut *(builder_handle as *mut ConFrameBuilder) };
764 let sym = match unsafe { CStr::from_ptr(symbol).to_str() } {
765 Ok(s) => s,
766 Err(_) => return RKRStatus::RKR_STATUS_INVALID_UTF8,
767 };
768
769 builder.add_atom(sym, x, y, z, fixed, atom_id, mass);
770 if let Some(v) = velocity {
771 builder.with_velocity(v);
772 }
773 if let Some(f) = forces {
774 builder.with_force(f);
775 }
776
777 RKRStatus::RKR_STATUS_SUCCESS
778}
779
780#[unsafe(no_mangle)]
786pub unsafe extern "C" fn rkr_frame_builder_set_last_velocity(
787 builder_handle: *mut RKRConFrameBuilder,
788 velocity: *const f64,
789) -> RKRStatus {
790 if builder_handle.is_null() || velocity.is_null() {
791 return RKRStatus::RKR_STATUS_NULL_POINTER;
792 }
793 let builder = unsafe { &mut *(builder_handle as *mut ConFrameBuilder) };
794 let v = unsafe { [*velocity, *velocity.add(1), *velocity.add(2)] };
795 builder.with_velocity(v);
796 RKRStatus::RKR_STATUS_SUCCESS
797}
798
799#[unsafe(no_mangle)]
805pub unsafe extern "C" fn rkr_frame_builder_set_last_force(
806 builder_handle: *mut RKRConFrameBuilder,
807 force: *const f64,
808) -> RKRStatus {
809 if builder_handle.is_null() || force.is_null() {
810 return RKRStatus::RKR_STATUS_NULL_POINTER;
811 }
812 let builder = unsafe { &mut *(builder_handle as *mut ConFrameBuilder) };
813 let f = unsafe { [*force, *force.add(1), *force.add(2)] };
814 builder.with_force(f);
815 RKRStatus::RKR_STATUS_SUCCESS
816}
817
818#[unsafe(no_mangle)]
828pub unsafe extern "C" fn rkr_frame_builder_set_last_energy(
829 builder_handle: *mut RKRConFrameBuilder,
830 energy: f64,
831) -> RKRStatus {
832 if builder_handle.is_null() {
833 return RKRStatus::RKR_STATUS_NULL_POINTER;
834 }
835 let builder = unsafe { &mut *(builder_handle as *mut ConFrameBuilder) };
836 builder.with_energy(energy);
837 RKRStatus::RKR_STATUS_SUCCESS
838}
839
840#[unsafe(no_mangle)]
851#[allow(clippy::too_many_arguments)]
852pub unsafe extern "C" fn rkr_frame_add_atom_full(
853 builder_handle: *mut RKRConFrameBuilder,
854 symbol: *const c_char,
855 x: f64,
856 y: f64,
857 z: f64,
858 fixed_x: bool,
859 fixed_y: bool,
860 fixed_z: bool,
861 atom_id: u64,
862 mass: f64,
863 velocity: *const f64,
864 force: *const f64,
865) -> RKRStatus {
866 let velocity = if velocity.is_null() {
867 None
868 } else {
869 Some(unsafe { [*velocity, *velocity.add(1), *velocity.add(2)] })
870 };
871 let force = if force.is_null() {
872 None
873 } else {
874 Some(unsafe { [*force, *force.add(1), *force.add(2)] })
875 };
876 unsafe {
877 add_builder_atom(
878 builder_handle,
879 symbol,
880 x,
881 y,
882 z,
883 [fixed_x, fixed_y, fixed_z],
884 atom_id,
885 mass,
886 velocity,
887 force,
888 )
889 }
890}
891
892#[unsafe(no_mangle)]
908pub unsafe extern "C" fn rkr_frame_new(
909 cell: *const f64,
910 angles: *const f64,
911 prebox0: *const c_char,
912 prebox1: *const c_char,
913 postbox0: *const c_char,
914 postbox1: *const c_char,
915) -> *mut RKRConFrameBuilder {
916 if cell.is_null() || angles.is_null() {
917 return ptr::null_mut();
918 }
919 let cell_arr = unsafe { [*cell, *cell.add(1), *cell.add(2)] };
920 let angles_arr = unsafe { [*angles, *angles.add(1), *angles.add(2)] };
921
922 let get_str = |p: *const c_char| -> String {
923 if p.is_null() {
924 String::new()
925 } else {
926 unsafe { CStr::from_ptr(p) }
927 .to_str()
928 .unwrap_or("")
929 .to_string()
930 }
931 };
932
933 let _ = get_str(prebox1);
936 let mut builder = ConFrameBuilder::new(cell_arr, angles_arr);
937 builder
938 .prebox_header(get_str(prebox0))
939 .postbox_header([get_str(postbox0), get_str(postbox1)]);
940
941 Box::into_raw(Box::new(builder)) as *mut RKRConFrameBuilder
942}
943
944#[unsafe(no_mangle)]
950pub unsafe extern "C" fn rkr_frame_builder_set_metadata_json(
951 builder_handle: *mut RKRConFrameBuilder,
952 metadata_json: *const c_char,
953) -> RKRStatus {
954 if builder_handle.is_null() || metadata_json.is_null() {
955 return RKRStatus::RKR_STATUS_NULL_POINTER;
956 }
957 let builder = unsafe { &mut *(builder_handle as *mut ConFrameBuilder) };
958 let metadata_json = match unsafe { CStr::from_ptr(metadata_json).to_str() } {
959 Ok(s) => s,
960 Err(_) => return RKRStatus::RKR_STATUS_INVALID_UTF8,
961 };
962 match builder.set_metadata_json(metadata_json) {
963 Ok(()) => RKRStatus::RKR_STATUS_SUCCESS,
964 Err(_) => RKRStatus::RKR_STATUS_INVALID_JSON,
965 }
966}
967
968#[unsafe(no_mangle)]
974pub unsafe extern "C" fn rkr_frame_builder_set_scalar_metadata(
975 builder_handle: *mut RKRConFrameBuilder,
976 key: *const c_char,
977 value: f64,
978) -> RKRStatus {
979 if builder_handle.is_null() || key.is_null() {
980 return RKRStatus::RKR_STATUS_NULL_POINTER;
981 }
982 let builder = unsafe { &mut *(builder_handle as *mut ConFrameBuilder) };
983 let key = match unsafe { CStr::from_ptr(key).to_str() } {
984 Ok(s) => s,
985 Err(_) => return RKRStatus::RKR_STATUS_INVALID_UTF8,
986 };
987 builder.set_scalar_metadata(key, value);
988 RKRStatus::RKR_STATUS_SUCCESS
989}
990
991#[unsafe(no_mangle)]
997pub unsafe extern "C" fn rkr_frame_builder_set_string_metadata(
998 builder_handle: *mut RKRConFrameBuilder,
999 key: *const c_char,
1000 value: *const c_char,
1001) -> RKRStatus {
1002 if builder_handle.is_null() || key.is_null() || value.is_null() {
1003 return RKRStatus::RKR_STATUS_NULL_POINTER;
1004 }
1005 let builder = unsafe { &mut *(builder_handle as *mut ConFrameBuilder) };
1006 let key = match unsafe { CStr::from_ptr(key).to_str() } {
1007 Ok(s) => s,
1008 Err(_) => return RKRStatus::RKR_STATUS_INVALID_UTF8,
1009 };
1010 let value = match unsafe { CStr::from_ptr(value).to_str() } {
1011 Ok(s) => s,
1012 Err(_) => return RKRStatus::RKR_STATUS_INVALID_UTF8,
1013 };
1014 builder.set_string_metadata(key, value);
1015 RKRStatus::RKR_STATUS_SUCCESS
1016}
1017
1018#[unsafe(no_mangle)]
1024pub unsafe extern "C" fn rkr_frame_builder_set_energy(
1025 builder_handle: *mut RKRConFrameBuilder,
1026 energy: f64,
1027) -> RKRStatus {
1028 if builder_handle.is_null() {
1029 return RKRStatus::RKR_STATUS_NULL_POINTER;
1030 }
1031 let builder = unsafe { &mut *(builder_handle as *mut ConFrameBuilder) };
1032 builder.set_energy(energy);
1033 RKRStatus::RKR_STATUS_SUCCESS
1034}
1035
1036#[unsafe(no_mangle)]
1042pub unsafe extern "C" fn rkr_frame_builder_set_frame_index(
1043 builder_handle: *mut RKRConFrameBuilder,
1044 idx: u64,
1045) -> RKRStatus {
1046 if builder_handle.is_null() {
1047 return RKRStatus::RKR_STATUS_NULL_POINTER;
1048 }
1049 let builder = unsafe { &mut *(builder_handle as *mut ConFrameBuilder) };
1050 builder.set_frame_index(idx);
1051 RKRStatus::RKR_STATUS_SUCCESS
1052}
1053
1054#[unsafe(no_mangle)]
1060pub unsafe extern "C" fn rkr_frame_builder_set_time(
1061 builder_handle: *mut RKRConFrameBuilder,
1062 time: f64,
1063) -> RKRStatus {
1064 if builder_handle.is_null() {
1065 return RKRStatus::RKR_STATUS_NULL_POINTER;
1066 }
1067 let builder = unsafe { &mut *(builder_handle as *mut ConFrameBuilder) };
1068 builder.set_time(time);
1069 RKRStatus::RKR_STATUS_SUCCESS
1070}
1071
1072#[unsafe(no_mangle)]
1078pub unsafe extern "C" fn rkr_frame_builder_set_timestep(
1079 builder_handle: *mut RKRConFrameBuilder,
1080 dt: f64,
1081) -> RKRStatus {
1082 if builder_handle.is_null() {
1083 return RKRStatus::RKR_STATUS_NULL_POINTER;
1084 }
1085 let builder = unsafe { &mut *(builder_handle as *mut ConFrameBuilder) };
1086 builder.set_timestep(dt);
1087 RKRStatus::RKR_STATUS_SUCCESS
1088}
1089
1090#[unsafe(no_mangle)]
1096pub unsafe extern "C" fn rkr_frame_builder_set_neb_bead(
1097 builder_handle: *mut RKRConFrameBuilder,
1098 bead: u64,
1099) -> RKRStatus {
1100 if builder_handle.is_null() {
1101 return RKRStatus::RKR_STATUS_NULL_POINTER;
1102 }
1103 let builder = unsafe { &mut *(builder_handle as *mut ConFrameBuilder) };
1104 builder.set_neb_bead(bead);
1105 RKRStatus::RKR_STATUS_SUCCESS
1106}
1107
1108#[unsafe(no_mangle)]
1114pub unsafe extern "C" fn rkr_frame_builder_set_neb_band(
1115 builder_handle: *mut RKRConFrameBuilder,
1116 band: u64,
1117) -> RKRStatus {
1118 if builder_handle.is_null() {
1119 return RKRStatus::RKR_STATUS_NULL_POINTER;
1120 }
1121 let builder = unsafe { &mut *(builder_handle as *mut ConFrameBuilder) };
1122 builder.set_neb_band(band);
1123 RKRStatus::RKR_STATUS_SUCCESS
1124}
1125
1126#[unsafe(no_mangle)]
1144pub unsafe extern "C" fn rkr_frame_add_atom(
1145 builder_handle: *mut RKRConFrameBuilder,
1146 symbol: *const c_char,
1147 x: f64,
1148 y: f64,
1149 z: f64,
1150 is_fixed: bool,
1151 atom_id: u64,
1152 mass: f64,
1153) -> RKRStatus {
1154 unsafe {
1155 add_builder_atom(
1156 builder_handle,
1157 symbol,
1158 x,
1159 y,
1160 z,
1161 [is_fixed; 3],
1162 atom_id,
1163 mass,
1164 None,
1165 None,
1166 )
1167 }
1168}
1169
1170#[unsafe(no_mangle)]
1177pub unsafe extern "C" fn rkr_frame_add_atom_with_fixed_mask(
1178 builder_handle: *mut RKRConFrameBuilder,
1179 symbol: *const c_char,
1180 x: f64,
1181 y: f64,
1182 z: f64,
1183 fixed_x: bool,
1184 fixed_y: bool,
1185 fixed_z: bool,
1186 atom_id: u64,
1187 mass: f64,
1188) -> RKRStatus {
1189 unsafe {
1190 add_builder_atom(
1191 builder_handle,
1192 symbol,
1193 x,
1194 y,
1195 z,
1196 [fixed_x, fixed_y, fixed_z],
1197 atom_id,
1198 mass,
1199 None,
1200 None,
1201 )
1202 }
1203}
1204
1205#[unsafe(no_mangle)]
1212pub unsafe extern "C" fn rkr_frame_add_atom_with_velocity(
1213 builder_handle: *mut RKRConFrameBuilder,
1214 symbol: *const c_char,
1215 x: f64,
1216 y: f64,
1217 z: f64,
1218 is_fixed: bool,
1219 atom_id: u64,
1220 mass: f64,
1221 vx: f64,
1222 vy: f64,
1223 vz: f64,
1224) -> RKRStatus {
1225 unsafe {
1226 add_builder_atom(
1227 builder_handle,
1228 symbol,
1229 x,
1230 y,
1231 z,
1232 [is_fixed; 3],
1233 atom_id,
1234 mass,
1235 Some([vx, vy, vz]),
1236 None,
1237 )
1238 }
1239}
1240
1241#[unsafe(no_mangle)]
1248pub unsafe extern "C" fn rkr_frame_add_atom_with_velocity_fixed_mask(
1249 builder_handle: *mut RKRConFrameBuilder,
1250 symbol: *const c_char,
1251 x: f64,
1252 y: f64,
1253 z: f64,
1254 fixed_x: bool,
1255 fixed_y: bool,
1256 fixed_z: bool,
1257 atom_id: u64,
1258 mass: f64,
1259 vx: f64,
1260 vy: f64,
1261 vz: f64,
1262) -> RKRStatus {
1263 unsafe {
1264 add_builder_atom(
1265 builder_handle,
1266 symbol,
1267 x,
1268 y,
1269 z,
1270 [fixed_x, fixed_y, fixed_z],
1271 atom_id,
1272 mass,
1273 Some([vx, vy, vz]),
1274 None,
1275 )
1276 }
1277}
1278
1279#[unsafe(no_mangle)]
1286pub unsafe extern "C" fn rkr_frame_add_atom_with_forces(
1287 builder_handle: *mut RKRConFrameBuilder,
1288 symbol: *const c_char,
1289 x: f64,
1290 y: f64,
1291 z: f64,
1292 is_fixed: bool,
1293 atom_id: u64,
1294 mass: f64,
1295 fx: f64,
1296 fy: f64,
1297 fz: f64,
1298) -> RKRStatus {
1299 unsafe {
1300 add_builder_atom(
1301 builder_handle,
1302 symbol,
1303 x,
1304 y,
1305 z,
1306 [is_fixed; 3],
1307 atom_id,
1308 mass,
1309 None,
1310 Some([fx, fy, fz]),
1311 )
1312 }
1313}
1314
1315#[unsafe(no_mangle)]
1322pub unsafe extern "C" fn rkr_frame_add_atom_with_forces_fixed_mask(
1323 builder_handle: *mut RKRConFrameBuilder,
1324 symbol: *const c_char,
1325 x: f64,
1326 y: f64,
1327 z: f64,
1328 fixed_x: bool,
1329 fixed_y: bool,
1330 fixed_z: bool,
1331 atom_id: u64,
1332 mass: f64,
1333 fx: f64,
1334 fy: f64,
1335 fz: f64,
1336) -> RKRStatus {
1337 unsafe {
1338 add_builder_atom(
1339 builder_handle,
1340 symbol,
1341 x,
1342 y,
1343 z,
1344 [fixed_x, fixed_y, fixed_z],
1345 atom_id,
1346 mass,
1347 None,
1348 Some([fx, fy, fz]),
1349 )
1350 }
1351}
1352
1353#[unsafe(no_mangle)]
1360pub unsafe extern "C" fn rkr_frame_add_atom_with_velocity_and_forces(
1361 builder_handle: *mut RKRConFrameBuilder,
1362 symbol: *const c_char,
1363 x: f64,
1364 y: f64,
1365 z: f64,
1366 is_fixed: bool,
1367 atom_id: u64,
1368 mass: f64,
1369 vx: f64,
1370 vy: f64,
1371 vz: f64,
1372 fx: f64,
1373 fy: f64,
1374 fz: f64,
1375) -> RKRStatus {
1376 unsafe {
1377 add_builder_atom(
1378 builder_handle,
1379 symbol,
1380 x,
1381 y,
1382 z,
1383 [is_fixed; 3],
1384 atom_id,
1385 mass,
1386 Some([vx, vy, vz]),
1387 Some([fx, fy, fz]),
1388 )
1389 }
1390}
1391
1392#[unsafe(no_mangle)]
1399pub unsafe extern "C" fn rkr_frame_add_atom_with_velocity_and_forces_fixed_mask(
1400 builder_handle: *mut RKRConFrameBuilder,
1401 symbol: *const c_char,
1402 x: f64,
1403 y: f64,
1404 z: f64,
1405 fixed_x: bool,
1406 fixed_y: bool,
1407 fixed_z: bool,
1408 atom_id: u64,
1409 mass: f64,
1410 vx: f64,
1411 vy: f64,
1412 vz: f64,
1413 fx: f64,
1414 fy: f64,
1415 fz: f64,
1416) -> RKRStatus {
1417 unsafe {
1418 add_builder_atom(
1419 builder_handle,
1420 symbol,
1421 x,
1422 y,
1423 z,
1424 [fixed_x, fixed_y, fixed_z],
1425 atom_id,
1426 mass,
1427 Some([vx, vy, vz]),
1428 Some([fx, fy, fz]),
1429 )
1430 }
1431}
1432
1433#[unsafe(no_mangle)]
1441pub unsafe extern "C" fn rkr_frame_builder_build(
1442 builder_handle: *mut RKRConFrameBuilder,
1443) -> *mut RKRConFrame {
1444 if builder_handle.is_null() {
1445 return ptr::null_mut();
1446 }
1447 let builder = unsafe { *Box::from_raw(builder_handle as *mut ConFrameBuilder) };
1448 let frame = builder.build();
1449 Box::into_raw(Box::new(frame)) as *mut RKRConFrame
1450}
1451
1452#[unsafe(no_mangle)]
1457pub unsafe extern "C" fn free_rkr_frame_builder(builder_handle: *mut RKRConFrameBuilder) {
1458 if !builder_handle.is_null() {
1459 let _ = unsafe { Box::from_raw(builder_handle as *mut ConFrameBuilder) };
1460 }
1461}
1462
1463#[unsafe(no_mangle)]
1469pub unsafe extern "C" fn create_writer_gzip_c(filename_c: *const c_char) -> *mut RKRConFrameWriter {
1470 if filename_c.is_null() {
1471 return ptr::null_mut();
1472 }
1473 let filename = match unsafe { CStr::from_ptr(filename_c).to_str() } {
1474 Ok(s) => s,
1475 Err(_) => return ptr::null_mut(),
1476 };
1477 match ConFrameWriter::from_path_gzip(filename) {
1478 Ok(writer) => Box::into_raw(Box::new(writer)) as *mut RKRConFrameWriter,
1479 Err(_) => ptr::null_mut(),
1480 }
1481}
1482
1483#[unsafe(no_mangle)]
1496pub unsafe extern "C" fn rkr_read_first_frame(filename_c: *const c_char) -> *mut RKRConFrame {
1497 if filename_c.is_null() {
1498 return ptr::null_mut();
1499 }
1500 let filename = match unsafe { CStr::from_ptr(filename_c).to_str() } {
1501 Ok(s) => s,
1502 Err(_) => return ptr::null_mut(),
1503 };
1504 match iterators::read_first_frame(Path::new(filename)) {
1505 Ok(frame) => Box::into_raw(Box::new(frame)) as *mut RKRConFrame,
1506 Err(_) => ptr::null_mut(),
1507 }
1508}
1509
1510#[unsafe(no_mangle)]
1519pub unsafe extern "C" fn rkr_read_all_frames(
1520 filename_c: *const c_char,
1521 num_frames: *mut usize,
1522) -> *mut *mut RKRConFrame {
1523 if filename_c.is_null() || num_frames.is_null() {
1524 return ptr::null_mut();
1525 }
1526 let filename = match unsafe { CStr::from_ptr(filename_c).to_str() } {
1527 Ok(s) => s,
1528 Err(_) => return ptr::null_mut(),
1529 };
1530 match iterators::read_all_frames(Path::new(filename)) {
1531 Ok(frames) => {
1532 let count = frames.len();
1533 let mut handles: Vec<*mut RKRConFrame> = frames
1537 .into_iter()
1538 .map(|f| Box::into_raw(Box::new(f)) as *mut RKRConFrame)
1539 .collect();
1540 handles.shrink_to_fit();
1541 debug_assert_eq!(handles.len(), handles.capacity());
1542 let ptr = handles.as_mut_ptr();
1543 std::mem::forget(handles);
1544 unsafe { *num_frames = count };
1545 ptr
1546 }
1547 Err(_) => ptr::null_mut(),
1548 }
1549}
1550
1551#[unsafe(no_mangle)]
1557pub unsafe extern "C" fn free_rkr_frame_array(frames: *mut *mut RKRConFrame, num_frames: usize) {
1558 if frames.is_null() {
1559 return;
1560 }
1561 unsafe {
1562 let handles = Vec::from_raw_parts(frames, num_frames, num_frames);
1563 for handle in handles {
1564 if !handle.is_null() {
1565 let _ = Box::from_raw(handle as *mut ConFrame);
1566 }
1567 }
1568 }
1569}
1570
1571#[cfg(test)]
1572mod tests {
1573 use super::*;
1574 use std::ffi::{CStr, CString};
1575
1576 fn test_frame_handle() -> *mut RKRConFrame {
1577 let mut builder = ConFrameBuilder::new([10.0, 10.0, 10.0], [90.0, 90.0, 90.0]);
1578 builder
1579 .prebox_header("Generated by test")
1580 .postbox_header(["0 0".to_string(), "0 0 0".to_string()]);
1581 builder.add_atom("Cu", 0.0, 0.0, 0.0, [false, false, false], 0, 63.546);
1582 Box::into_raw(Box::new(builder.build())) as *mut RKRConFrame
1583 }
1584
1585 #[test]
1586 fn header_line_rejects_null_buffer() {
1587 let frame = test_frame_handle();
1588 let status = unsafe { rkr_frame_get_header_line(frame, true, 0, std::ptr::null_mut(), 16) };
1589 unsafe { free_rkr_frame(frame) };
1590
1591 assert_eq!(status, RKRStatus::RKR_STATUS_NULL_POINTER);
1592 }
1593
1594 #[test]
1595 fn header_line_rejects_empty_buffer() {
1596 let frame = test_frame_handle();
1597 let mut buffer = [0 as c_char; 1];
1598 let status = unsafe { rkr_frame_get_header_line(frame, true, 0, buffer.as_mut_ptr(), 0) };
1599 unsafe { free_rkr_frame(frame) };
1600
1601 assert_eq!(status, RKRStatus::RKR_STATUS_BUFFER_TOO_SMALL);
1602 }
1603
1604 #[test]
1605 fn header_line_truncates_and_terminates_buffer() {
1606 let frame = test_frame_handle();
1607 let mut buffer = [0 as c_char; 10];
1608 let status =
1609 unsafe { rkr_frame_get_header_line(frame, true, 0, buffer.as_mut_ptr(), buffer.len()) };
1610 unsafe { free_rkr_frame(frame) };
1611
1612 assert_eq!(status, RKRStatus::RKR_STATUS_SUCCESS);
1613 let copied = unsafe { CStr::from_ptr(buffer.as_ptr()) };
1614 assert_eq!(copied.to_str().unwrap(), "Generated");
1615 }
1616
1617 fn test_builder_handle() -> *mut RKRConFrameBuilder {
1618 let cell = [10.0, 11.0, 12.0];
1619 let angles = [90.0, 91.0, 92.0];
1620 unsafe {
1621 rkr_frame_new(
1622 cell.as_ptr(),
1623 angles.as_ptr(),
1624 ptr::null(),
1625 ptr::null(),
1626 ptr::null(),
1627 ptr::null(),
1628 )
1629 }
1630 }
1631
1632 fn c_string(s: &str) -> CString {
1633 CString::new(s).unwrap()
1634 }
1635
1636 unsafe fn assert_single_atom(
1637 frame: *mut RKRConFrame,
1638 fixed: [bool; 3],
1639 velocity: Option<[f64; 3]>,
1640 forces: Option<[f64; 3]>,
1641 ) {
1642 let c_frame = unsafe { rkr_frame_to_c_frame(frame) };
1643 assert!(!c_frame.is_null());
1644 let c_frame_ref = unsafe { &*c_frame };
1645 assert_eq!(c_frame_ref.num_atoms, 1);
1646 assert_eq!(c_frame_ref.has_velocities, velocity.is_some());
1647 assert_eq!(c_frame_ref.has_forces, forces.is_some());
1648
1649 let atom = unsafe { &*c_frame_ref.atoms };
1650 assert_eq!(atom.fixed_x, fixed[0]);
1651 assert_eq!(atom.fixed_y, fixed[1]);
1652 assert_eq!(atom.fixed_z, fixed[2]);
1653 assert_eq!(atom.is_fixed, fixed.iter().any(|&value| value));
1654 assert_eq!(atom.has_velocity, velocity.is_some());
1655 assert_eq!(atom.has_forces, forces.is_some());
1656 if let Some([vx, vy, vz]) = velocity {
1657 assert_eq!([atom.vx, atom.vy, atom.vz], [vx, vy, vz]);
1658 }
1659 if let Some([fx, fy, fz]) = forces {
1660 assert_eq!([atom.fx, atom.fy, atom.fz], [fx, fy, fz]);
1661 }
1662
1663 unsafe { free_c_frame(c_frame) };
1664 unsafe { free_rkr_frame(frame) };
1665 }
1666
1667 #[test]
1668 fn builder_preserves_fixed_mask_for_atom_without_velocity_or_forces() {
1669 let builder = test_builder_handle();
1670 let symbol = c_string("Cu");
1671
1672 let status = unsafe {
1673 rkr_frame_add_atom_with_fixed_mask(
1674 builder,
1675 symbol.as_ptr(),
1676 1.0,
1677 2.0,
1678 3.0,
1679 true,
1680 false,
1681 true,
1682 7,
1683 63.546,
1684 )
1685 };
1686 assert_eq!(status, RKRStatus::RKR_STATUS_SUCCESS);
1687
1688 let frame = unsafe { rkr_frame_builder_build(builder) };
1689 unsafe { assert_single_atom(frame, [true, false, true], None, None) };
1690 }
1691
1692 #[test]
1693 fn builder_preserves_fixed_mask_for_atom_with_velocity() {
1694 let builder = test_builder_handle();
1695 let symbol = c_string("H");
1696
1697 let status = unsafe {
1698 rkr_frame_add_atom_with_velocity_fixed_mask(
1699 builder,
1700 symbol.as_ptr(),
1701 1.0,
1702 2.0,
1703 3.0,
1704 false,
1705 true,
1706 false,
1707 9,
1708 1.008,
1709 0.1,
1710 0.2,
1711 0.3,
1712 )
1713 };
1714 assert_eq!(status, RKRStatus::RKR_STATUS_SUCCESS);
1715
1716 let frame = unsafe { rkr_frame_builder_build(builder) };
1717 unsafe { assert_single_atom(frame, [false, true, false], Some([0.1, 0.2, 0.3]), None) };
1718 }
1719
1720 #[test]
1721 fn builder_preserves_fixed_mask_for_atom_with_forces() {
1722 let builder = test_builder_handle();
1723 let symbol = c_string("O");
1724
1725 let status = unsafe {
1726 rkr_frame_add_atom_with_forces_fixed_mask(
1727 builder,
1728 symbol.as_ptr(),
1729 1.0,
1730 2.0,
1731 3.0,
1732 true,
1733 true,
1734 false,
1735 11,
1736 15.999,
1737 -0.1,
1738 -0.2,
1739 -0.3,
1740 )
1741 };
1742 assert_eq!(status, RKRStatus::RKR_STATUS_SUCCESS);
1743
1744 let frame = unsafe { rkr_frame_builder_build(builder) };
1745 unsafe { assert_single_atom(frame, [true, true, false], None, Some([-0.1, -0.2, -0.3])) };
1746 }
1747
1748 #[test]
1749 fn builder_preserves_fixed_mask_for_atom_with_velocity_and_forces() {
1750 let builder = test_builder_handle();
1751 let symbol = c_string("N");
1752
1753 let status = unsafe {
1754 rkr_frame_add_atom_with_velocity_and_forces_fixed_mask(
1755 builder,
1756 symbol.as_ptr(),
1757 1.0,
1758 2.0,
1759 3.0,
1760 false,
1761 true,
1762 true,
1763 13,
1764 14.007,
1765 0.4,
1766 0.5,
1767 0.6,
1768 -0.4,
1769 -0.5,
1770 -0.6,
1771 )
1772 };
1773 assert_eq!(status, RKRStatus::RKR_STATUS_SUCCESS);
1774
1775 let frame = unsafe { rkr_frame_builder_build(builder) };
1776 unsafe {
1777 assert_single_atom(
1778 frame,
1779 [false, true, true],
1780 Some([0.4, 0.5, 0.6]),
1781 Some([-0.4, -0.5, -0.6]),
1782 )
1783 };
1784 }
1785
1786 #[test]
1787 fn builder_bool_fixed_functions_set_all_axes_together() {
1788 let builder = test_builder_handle();
1789 let cu = c_string("Cu");
1790 let h = c_string("H");
1791
1792 let atom_status =
1793 unsafe { rkr_frame_add_atom(builder, cu.as_ptr(), 1.0, 2.0, 3.0, true, 1, 63.546) };
1794 assert_eq!(atom_status, RKRStatus::RKR_STATUS_SUCCESS);
1795
1796 let velocity_status = unsafe {
1797 rkr_frame_add_atom_with_velocity(
1798 builder,
1799 h.as_ptr(),
1800 4.0,
1801 5.0,
1802 6.0,
1803 false,
1804 2,
1805 1.008,
1806 0.7,
1807 0.8,
1808 0.9,
1809 )
1810 };
1811 assert_eq!(velocity_status, RKRStatus::RKR_STATUS_SUCCESS);
1812
1813 let frame = unsafe { rkr_frame_builder_build(builder) };
1814 let c_frame = unsafe { rkr_frame_to_c_frame(frame) };
1815 assert!(!c_frame.is_null());
1816 let c_frame_ref = unsafe { &*c_frame };
1817 assert_eq!(c_frame_ref.num_atoms, 2);
1818
1819 let atoms = unsafe { std::slice::from_raw_parts(c_frame_ref.atoms, c_frame_ref.num_atoms) };
1820 assert_eq!(
1821 [atoms[0].fixed_x, atoms[0].fixed_y, atoms[0].fixed_z],
1822 [true, true, true]
1823 );
1824 assert_eq!(
1825 [atoms[1].fixed_x, atoms[1].fixed_y, atoms[1].fixed_z],
1826 [false, false, false]
1827 );
1828
1829 unsafe { free_c_frame(c_frame) };
1830 unsafe { free_rkr_frame(frame) };
1831 }
1832
1833 #[test]
1834 fn status_message_returns_static_strings_for_all_status_values() {
1835 let cases = [
1836 (RKRStatus::RKR_STATUS_SUCCESS, "success"),
1837 (RKRStatus::RKR_STATUS_NULL_POINTER, "null pointer"),
1838 (RKRStatus::RKR_STATUS_INVALID_UTF8, "invalid UTF-8"),
1839 (RKRStatus::RKR_STATUS_INVALID_JSON, "invalid JSON"),
1840 (RKRStatus::RKR_STATUS_IO_ERROR, "I/O error"),
1841 (
1842 RKRStatus::RKR_STATUS_INDEX_OUT_OF_BOUNDS,
1843 "index out of bounds",
1844 ),
1845 (RKRStatus::RKR_STATUS_BUFFER_TOO_SMALL, "buffer too small"),
1846 (RKRStatus::RKR_STATUS_INTERNAL_ERROR, "internal error"),
1847 ];
1848
1849 for (status, expected) in cases {
1850 let message = unsafe { CStr::from_ptr(rkr_status_message(status)) };
1851 assert_eq!(message.to_str().unwrap(), expected);
1852 }
1853 }
1854}