1use std::ffi::CStr;
6use std::os::raw::{c_char, c_int};
7use std::ptr;
8use std::sync::{Arc, Mutex};
9
10use crate::corpus::ConCorpus;
11use crate::keys::{hash_frame_bytes, FrameKey};
12use crate::select::Select;
13
14pub const RKRDB_OK: c_int = 0;
15pub const RKRDB_ERR: c_int = -1;
16pub const RKRDB_NOT_FOUND: c_int = -2;
17pub const RKRDB_NULL: c_int = -3;
18
19pub const RKRDB_ABI_VERSION_MAJOR: u32 = 1;
21pub const RKRDB_ABI_VERSION_MINOR: u32 = 1;
23pub const RKRDB_ABI_LAYOUT_REVISION: u32 = 1;
25
26#[no_mangle]
28pub extern "C" fn rkrdb_abi_version_major() -> u32 {
29 RKRDB_ABI_VERSION_MAJOR
30}
31
32#[no_mangle]
34pub extern "C" fn rkrdb_abi_version_minor() -> u32 {
35 RKRDB_ABI_VERSION_MINOR
36}
37
38#[no_mangle]
40pub extern "C" fn rkrdb_abi_layout_revision() -> u32 {
41 RKRDB_ABI_LAYOUT_REVISION
42}
43
44#[no_mangle]
48pub extern "C" fn rkrdb_abi_stamp() -> *const c_char {
49 const STAMP: &[u8] = b"readcon-db/abi-1.1/layout-1\0";
50 STAMP.as_ptr() as *const c_char
51}
52
53const fn abi_accepts(library: (u32, u32, u32), caller: (u32, u32, u32)) -> bool {
57 caller.0 == library.0 && caller.2 == library.2 && caller.1 <= library.1
58}
59
60#[no_mangle]
64pub extern "C" fn rkrdb_abi_compatible(major: u32, minor: u32, layout_revision: u32) -> c_int {
65 c_int::from(abi_accepts(
66 (
67 RKRDB_ABI_VERSION_MAJOR,
68 RKRDB_ABI_VERSION_MINOR,
69 RKRDB_ABI_LAYOUT_REVISION,
70 ),
71 (major, minor, layout_revision),
72 ))
73}
74
75struct Handle {
76 corpus: Arc<ConCorpus>,
78 last_keys: Vec<FrameKey>,
79 last_error: String,
80 velocity_time_unit: Option<String>,
82}
83
84static HANDLES: Mutex<Vec<Option<Box<Handle>>>> = Mutex::new(Vec::new());
85
86fn push_handle(h: Handle) -> usize {
87 let mut g = HANDLES.lock().unwrap();
88 for (i, slot) in g.iter_mut().enumerate() {
89 if slot.is_none() {
90 *slot = Some(Box::new(h));
91 return i;
92 }
93 }
94 g.push(Some(Box::new(h)));
95 g.len() - 1
96}
97
98fn with_handle<F, T>(id: usize, f: F) -> Result<T, c_int>
100where
101 F: FnOnce(&mut Handle) -> Result<T, c_int>,
102{
103 let mut g = HANDLES.lock().unwrap();
104 let slot = g.get_mut(id).ok_or(RKRDB_NULL)?;
105 let h = slot.as_mut().ok_or(RKRDB_NULL)?;
106 f(h)
107}
108
109fn corpus_arc(id: usize) -> Result<Arc<ConCorpus>, c_int> {
110 let g = HANDLES.lock().unwrap();
111 let slot = g.get(id).ok_or(RKRDB_NULL)?;
112 let h = slot.as_ref().ok_or(RKRDB_NULL)?;
113 Ok(Arc::clone(&h.corpus))
114}
115
116fn set_err_id(id: usize, e: impl ToString) {
117 let mut g = HANDLES.lock().unwrap();
118 if let Some(Some(h)) = g.get_mut(id) {
119 h.last_error = e.to_string();
120 }
121}
122
123fn set_err(h: &mut Handle, e: impl ToString) {
124 h.last_error = e.to_string();
125}
126
127#[no_mangle]
130pub unsafe extern "C" fn rkrdb_open(path: *const c_char, out_id: *mut usize) -> c_int {
131 if path.is_null() || out_id.is_null() {
132 return RKRDB_NULL;
133 }
134 let cpath = unsafe { CStr::from_ptr(path) };
135 let path = match cpath.to_str() {
136 Ok(s) => s,
137 Err(_) => return RKRDB_ERR,
138 };
139 match ConCorpus::open(path) {
140 Ok(corpus) => {
141 let id = push_handle(Handle {
142 corpus: Arc::new(corpus),
143 last_keys: Vec::new(),
144 last_error: String::new(),
145 velocity_time_unit: None,
146 });
147 unsafe { *out_id = id };
148 RKRDB_OK
149 }
150 Err(_) => RKRDB_ERR,
151 }
152}
153
154#[no_mangle]
156pub unsafe extern "C" fn rkrdb_open_readonly(path: *const c_char, out_id: *mut usize) -> c_int {
157 if path.is_null() || out_id.is_null() {
158 return RKRDB_NULL;
159 }
160 let cpath = unsafe { CStr::from_ptr(path) };
161 let path = match cpath.to_str() {
162 Ok(s) => s,
163 Err(_) => return RKRDB_ERR,
164 };
165 match ConCorpus::open_readonly(path) {
166 Ok(corpus) => {
167 let id = push_handle(Handle {
168 corpus: Arc::new(corpus),
169 last_keys: Vec::new(),
170 last_error: String::new(),
171 velocity_time_unit: None,
172 });
173 unsafe { *out_id = id };
174 RKRDB_OK
175 }
176 Err(_) => RKRDB_ERR,
177 }
178}
179
180#[no_mangle]
184pub unsafe extern "C" fn rkrdb_pack_frame(
185 id: usize,
186 traj_id: u64,
187 frame_idx: u32,
188 buf: *mut u8,
189 buflen: usize,
190) -> c_int {
191 let key = FrameKey { traj_id, frame_idx };
192 with_handle(id, |h| match h.corpus.pack_frame(key) {
193 Ok(bytes) => {
194 if buf.is_null() {
195 return Ok(bytes.len() as c_int);
196 }
197 if bytes.len() > buflen {
198 set_err(h, "buffer too small");
199 return Ok(RKRDB_ERR);
200 }
201 unsafe {
202 ptr::copy_nonoverlapping(bytes.as_ptr(), buf, bytes.len());
203 }
204 Ok(bytes.len() as c_int)
205 }
206 Err(e) => {
207 set_err(h, e);
208 Ok(RKRDB_ERR)
209 }
210 })
211 .unwrap_or(RKRDB_NULL)
212}
213
214#[no_mangle]
217pub unsafe extern "C" fn rkrdb_pack_frames(
218 id: usize,
219 traj_ids: *const u64,
220 frame_idxs: *const u32,
221 nkeys: u32,
222 buf: *mut u8,
223 buflen: usize,
224) -> c_int {
225 if traj_ids.is_null() || frame_idxs.is_null() || nkeys == 0 {
226 return RKRDB_NULL;
227 }
228 let n = nkeys as usize;
229 let trajs = unsafe { std::slice::from_raw_parts(traj_ids, n) };
230 let frames = unsafe { std::slice::from_raw_parts(frame_idxs, n) };
231 let keys: Vec<FrameKey> = trajs
232 .iter()
233 .zip(frames.iter())
234 .map(|(&traj_id, &frame_idx)| FrameKey { traj_id, frame_idx })
235 .collect();
236 with_handle(id, |h| match h.corpus.pack_frames(&keys) {
237 Ok(bytes) => {
238 if buf.is_null() {
239 return Ok(bytes.len() as c_int);
240 }
241 if bytes.len() > buflen {
242 set_err(h, "buffer too small");
243 return Ok(RKRDB_ERR);
244 }
245 unsafe {
246 ptr::copy_nonoverlapping(bytes.as_ptr(), buf, bytes.len());
247 }
248 Ok(bytes.len() as c_int)
249 }
250 Err(e) => {
251 set_err(h, e);
252 Ok(RKRDB_ERR)
253 }
254 })
255 .unwrap_or(RKRDB_NULL)
256}
257
258#[no_mangle]
260pub unsafe extern "C" fn rkrdb_unpack_batch_nframes(
261 buf: *const u8,
262 buflen: usize,
263 out_n: *mut u32,
264) -> c_int {
265 if buf.is_null() || out_n.is_null() {
266 return RKRDB_NULL;
267 }
268 let slice = unsafe { std::slice::from_raw_parts(buf, buflen) };
269 match crate::cooked_soa::decode_batch(slice) {
270 Ok(parts) => {
271 unsafe { *out_n = parts.len() as u32 };
272 RKRDB_OK
273 }
274 Err(_) => RKRDB_ERR,
275 }
276}
277
278#[no_mangle]
280pub unsafe extern "C" fn rkrdb_unpack_batch_item(
281 buf: *const u8,
282 buflen: usize,
283 index: u32,
284 out_xyz: *mut f64,
285 capacity_atoms: u32,
286 out_natoms: *mut u32,
287) -> c_int {
288 if buf.is_null() || out_xyz.is_null() || out_natoms.is_null() {
289 return RKRDB_NULL;
290 }
291 let slice = unsafe { std::slice::from_raw_parts(buf, buflen) };
292 let parts = match crate::cooked_soa::decode_batch(slice) {
293 Ok(p) => p,
294 Err(_) => return RKRDB_ERR,
295 };
296 let item = match parts.get(index as usize) {
297 Some(b) => b,
298 None => return RKRDB_NOT_FOUND,
299 };
300 rkrdb_unpack_positions(
301 item.as_ptr(),
302 item.len(),
303 out_xyz,
304 capacity_atoms,
305 out_natoms,
306 )
307}
308
309#[no_mangle]
311pub unsafe extern "C" fn rkrdb_unpack_positions(
312 buf: *const u8,
313 buflen: usize,
314 out_xyz: *mut f64,
315 capacity_atoms: u32,
316 out_natoms: *mut u32,
317) -> c_int {
318 if buf.is_null() || out_xyz.is_null() || out_natoms.is_null() {
319 return RKRDB_NULL;
320 }
321 let slice = unsafe { std::slice::from_raw_parts(buf, buflen) };
322 let cooked = match crate::cooked_soa::CookedSoa::decode(slice) {
323 Ok(c) => c,
324 Err(_) => return RKRDB_ERR,
325 };
326 if cooked.natoms > capacity_atoms {
327 return RKRDB_ERR;
328 }
329 unsafe { *out_natoms = cooked.natoms };
330 let n = cooked.natoms as usize;
331 let dest = unsafe { std::slice::from_raw_parts_mut(out_xyz, n.saturating_mul(3)) };
332 for (i, p) in cooked.positions.iter().enumerate() {
333 dest[i * 3] = p[0];
334 dest[i * 3 + 1] = p[1];
335 dest[i * 3 + 2] = p[2];
336 }
337 RKRDB_OK
338}
339
340#[no_mangle]
341pub unsafe extern "C" fn rkrdb_close(id: usize) -> c_int {
342 let mut g = HANDLES.lock().unwrap();
343 if let Some(slot) = g.get_mut(id) {
344 *slot = None;
345 RKRDB_OK
346 } else {
347 RKRDB_NULL
348 }
349}
350
351#[no_mangle]
354pub unsafe extern "C" fn rkrdb_last_error(id: usize, buf: *mut c_char, buflen: usize) -> c_int {
355 if buf.is_null() || buflen == 0 {
356 return RKRDB_NULL;
357 }
358 with_handle(id, |h| {
359 let bytes = h.last_error.as_bytes();
360 let n = (buflen - 1).min(bytes.len());
361 unsafe {
362 ptr::copy_nonoverlapping(bytes.as_ptr(), buf as *mut u8, n);
363 *buf.add(n) = 0;
364 }
365 Ok(n as c_int)
366 })
367 .unwrap_or(RKRDB_NULL)
368}
369
370fn parse_units_json(p: *const c_char) -> Result<Option<serde_json::Value>, c_int> {
371 if p.is_null() {
372 return Ok(None);
373 }
374 let s = unsafe { CStr::from_ptr(p) }
375 .to_str()
376 .map_err(|_| RKRDB_ERR)?;
377 if s.is_empty() {
378 return Ok(None);
379 }
380 serde_json::from_str(s).map(Some).map_err(|_| RKRDB_ERR)
381}
382
383#[no_mangle]
385pub unsafe extern "C" fn rkrdb_append_trajectory_units(
386 id: usize,
387 traj_id: u64,
388 path: *const c_char,
389 units_json: *const c_char,
390 out_n_frames: *mut u32,
391) -> c_int {
392 if path.is_null() {
393 return RKRDB_NULL;
394 }
395 let cpath = unsafe { CStr::from_ptr(path) };
396 let path = match cpath.to_str() {
397 Ok(s) => s,
398 Err(_) => return RKRDB_ERR,
399 };
400 let units = match parse_units_json(units_json) {
401 Ok(u) => u,
402 Err(c) => return c,
403 };
404 let corpus = match corpus_arc(id) {
405 Ok(c) => c,
406 Err(c) => return c,
407 };
408 match corpus.append_trajectory_path_units(traj_id, path, units) {
409 Ok(n) => {
410 if !out_n_frames.is_null() {
411 unsafe { *out_n_frames = n };
412 }
413 RKRDB_OK
414 }
415 Err(e) => {
416 set_err_id(id, e);
417 RKRDB_ERR
418 }
419 }
420}
421
422#[no_mangle]
423pub unsafe extern "C" fn rkrdb_append_trajectory(
424 id: usize,
425 traj_id: u64,
426 path: *const c_char,
427 out_n_frames: *mut u32,
428) -> c_int {
429 rkrdb_append_trajectory_units(id, traj_id, path, ptr::null(), out_n_frames)
430}
431
432#[no_mangle]
434pub unsafe extern "C" fn rkrdb_append_trajectory_str(
435 id: usize,
436 traj_id: u64,
437 text: *const c_char,
438 source: *const c_char,
439 out_n_frames: *mut u32,
440) -> c_int {
441 if text.is_null() {
442 return RKRDB_NULL;
443 }
444 let text = match unsafe { CStr::from_ptr(text) }.to_str() {
445 Ok(s) => s,
446 Err(_) => return RKRDB_ERR,
447 };
448 let source = if source.is_null() {
449 "memory"
450 } else {
451 match unsafe { CStr::from_ptr(source) }.to_str() {
452 Ok(s) if !s.is_empty() => s,
453 Ok(_) => "memory",
454 Err(_) => return RKRDB_ERR,
455 }
456 };
457 let corpus = match corpus_arc(id) {
458 Ok(c) => c,
459 Err(c) => return c,
460 };
461 match corpus.append_trajectory_str(traj_id, text, source) {
462 Ok(n) => {
463 if !out_n_frames.is_null() {
464 unsafe { *out_n_frames = n };
465 }
466 RKRDB_OK
467 }
468 Err(e) => {
469 set_err_id(id, e);
470 RKRDB_ERR
471 }
472 }
473}
474
475#[no_mangle]
477pub unsafe extern "C" fn rkrdb_append_trajectory_frame(
478 id: usize,
479 traj_id: u64,
480 frame: *const std::ffi::c_void,
481 source: *const c_char,
482 out_n_frames: *mut u32,
483) -> c_int {
484 ingest_trajectory_frame(id, traj_id, frame, source, out_n_frames, false)
485}
486
487#[no_mangle]
490pub unsafe extern "C" fn rkrdb_extend_trajectory_frame(
491 id: usize,
492 traj_id: u64,
493 frame: *const std::ffi::c_void,
494 source: *const c_char,
495 out_n_frames: *mut u32,
496) -> c_int {
497 ingest_trajectory_frame(id, traj_id, frame, source, out_n_frames, true)
498}
499
500unsafe fn ingest_trajectory_frame(
501 id: usize,
502 traj_id: u64,
503 frame: *const std::ffi::c_void,
504 source: *const c_char,
505 out_n_frames: *mut u32,
506 extend: bool,
507) -> c_int {
508 if frame.is_null() {
509 return RKRDB_NULL;
510 }
511 let source = if source.is_null() {
512 "memory"
513 } else {
514 match unsafe { CStr::from_ptr(source) }.to_str() {
515 Ok(s) if !s.is_empty() => s,
516 Ok(_) => "memory",
517 Err(_) => return RKRDB_ERR,
518 }
519 };
520 let corpus = match corpus_arc(id) {
521 Ok(c) => c,
522 Err(c) => return c,
523 };
524 let parsed = unsafe { &*(frame as *const readcon_core::types::ConFrame) };
525 let frames = std::slice::from_ref(parsed);
526 let result = if extend {
527 corpus.extend_trajectory_frames(traj_id, frames, source)
528 } else {
529 corpus.append_trajectory_frames(traj_id, frames, source)
530 };
531 match result {
532 Ok(n) => {
533 if !out_n_frames.is_null() {
534 unsafe { *out_n_frames = n };
535 }
536 RKRDB_OK
537 }
538 Err(e) => {
539 set_err_id(id, e);
540 RKRDB_ERR
541 }
542 }
543}
544
545#[no_mangle]
548pub unsafe extern "C" fn rkrdb_extend_trajectory_units(
549 id: usize,
550 traj_id: u64,
551 path: *const c_char,
552 units_json: *const c_char,
553 out_n_frames: *mut u32,
554) -> c_int {
555 if path.is_null() {
556 return RKRDB_NULL;
557 }
558 let cpath = unsafe { CStr::from_ptr(path) };
559 let path = match cpath.to_str() {
560 Ok(s) => s,
561 Err(_) => return RKRDB_ERR,
562 };
563 let units = match parse_units_json(units_json) {
564 Ok(u) => u,
565 Err(c) => return c,
566 };
567 let corpus = match corpus_arc(id) {
568 Ok(c) => c,
569 Err(c) => return c,
570 };
571 match corpus.extend_trajectory_path_units(traj_id, path, units) {
572 Ok(n) => {
573 if !out_n_frames.is_null() {
574 unsafe { *out_n_frames = n };
575 }
576 RKRDB_OK
577 }
578 Err(e) => {
579 set_err_id(id, e);
580 RKRDB_ERR
581 }
582 }
583}
584
585#[no_mangle]
586pub unsafe extern "C" fn rkrdb_extend_trajectory(
587 id: usize,
588 traj_id: u64,
589 path: *const c_char,
590 out_n_frames: *mut u32,
591) -> c_int {
592 rkrdb_extend_trajectory_units(id, traj_id, path, ptr::null(), out_n_frames)
593}
594
595#[no_mangle]
597pub unsafe extern "C" fn rkrdb_set_units(
598 id: usize,
599 traj_id: u64,
600 units_json: *const c_char,
601 out_n_frames: *mut u32,
602) -> c_int {
603 if units_json.is_null() {
604 return RKRDB_NULL;
605 }
606 let units = match parse_units_json(units_json) {
607 Ok(Some(u)) => u,
608 Ok(None) => return RKRDB_ERR,
609 Err(c) => return c,
610 };
611 let corpus = match corpus_arc(id) {
612 Ok(c) => c,
613 Err(c) => return c,
614 };
615 match corpus.set_trajectory_units(traj_id, units) {
616 Ok(n) => {
617 if !out_n_frames.is_null() {
618 unsafe { *out_n_frames = n };
619 }
620 RKRDB_OK
621 }
622 Err(e) => {
623 set_err_id(id, e);
624 RKRDB_ERR
625 }
626 }
627}
628
629#[no_mangle]
631pub unsafe extern "C" fn rkrdb_frame_units(
632 id: usize,
633 traj_id: u64,
634 frame_idx: u32,
635 buf: *mut c_char,
636 buflen: usize,
637) -> c_int {
638 if buf.is_null() || buflen == 0 {
639 return RKRDB_NULL;
640 }
641 let corpus = match corpus_arc(id) {
642 Ok(c) => c,
643 Err(c) => return c,
644 };
645 match corpus.frame_units(FrameKey { traj_id, frame_idx }) {
646 Ok(Some(v)) => {
647 let s = v.to_string();
648 if s.len() + 1 > buflen {
649 set_err_id(id, "frame_units buffer too small");
650 return RKRDB_ERR;
651 }
652 unsafe {
653 ptr::copy_nonoverlapping(s.as_ptr(), buf as *mut u8, s.len());
654 *buf.add(s.len()) = 0;
655 }
656 RKRDB_OK
657 }
658 Ok(None) => RKRDB_NOT_FOUND,
659 Err(e) => {
660 set_err_id(id, e);
661 RKRDB_ERR
662 }
663 }
664}
665
666#[no_mangle]
669pub unsafe extern "C" fn rkrdb_select_basic(
670 id: usize,
671 traj_id: i64,
672 symbol: *const c_char,
673 natoms_min: u32,
674 natoms_max: u32,
675 limit: u32,
676) -> c_int {
677 with_handle(id, |h| {
678 let mut sel = Select::new().natoms_range(natoms_min, natoms_max);
679 if traj_id >= 0 {
680 sel = sel.trajectory(traj_id as u64);
681 }
682 if !symbol.is_null() {
683 let s = unsafe { CStr::from_ptr(symbol) };
684 if let Ok(sym) = s.to_str() {
685 if !sym.is_empty() {
686 sel = sel.require_symbol(sym);
687 }
688 }
689 }
690 if limit > 0 {
691 sel = sel.limit(limit as usize);
692 }
693 match h.corpus.select(&sel) {
694 Ok(keys) => {
695 h.last_keys = keys;
696 Ok(RKRDB_OK)
697 }
698 Err(e) => {
699 set_err(h, e);
700 Ok(RKRDB_ERR)
701 }
702 }
703 })
704 .unwrap_or(RKRDB_NULL)
705}
706
707#[no_mangle]
709pub unsafe extern "C" fn rkrdb_select_hash(id: usize, hash16: *const u8) -> c_int {
710 if hash16.is_null() {
711 return RKRDB_NULL;
712 }
713 let mut hb = [0u8; 16];
714 unsafe { ptr::copy_nonoverlapping(hash16, hb.as_mut_ptr(), 16) };
715 with_handle(id, |h| {
716 let sel = Select::new().exact_hash(hb);
717 match h.corpus.select(&sel) {
718 Ok(keys) => {
719 h.last_keys = keys;
720 Ok(RKRDB_OK)
721 }
722 Err(e) => {
723 set_err(h, e);
724 Ok(RKRDB_ERR)
725 }
726 }
727 })
728 .unwrap_or(RKRDB_NULL)
729}
730
731#[no_mangle]
734pub unsafe extern "C" fn rkrdb_select_meta(
735 id: usize,
736 traj_id: i64,
737 symbol: *const c_char,
738 natoms_min: u32,
739 natoms_max: u32,
740 energy_min: f64,
741 energy_max: f64,
742 use_energy_range: c_int,
743 flags: u32,
744 limit: u32,
745) -> c_int {
746 with_handle(id, |h| {
747 let mut sel = Select::new().natoms_range(natoms_min, natoms_max);
748 if traj_id >= 0 {
749 sel = sel.trajectory(traj_id as u64);
750 }
751 if !symbol.is_null() {
752 let s = unsafe { CStr::from_ptr(symbol) };
753 if let Ok(sym) = s.to_str() {
754 if !sym.is_empty() {
755 sel = sel.require_symbol(sym);
756 }
757 }
758 }
759 if use_energy_range != 0 {
760 sel = sel.energy_range(energy_min, energy_max);
761 }
762 if flags & 1 != 0 {
763 sel = sel.require_forces();
764 }
765 if flags & 2 != 0 {
766 sel = sel.require_velocities();
767 }
768 if flags & 4 != 0 {
769 sel = sel.require_energy();
770 }
771 if limit > 0 {
772 sel = sel.limit(limit as usize);
773 }
774 match h.corpus.select(&sel) {
775 Ok(keys) => {
776 h.last_keys = keys;
777 Ok(RKRDB_OK)
778 }
779 Err(e) => {
780 set_err(h, e);
781 Ok(RKRDB_ERR)
782 }
783 }
784 })
785 .unwrap_or(RKRDB_NULL)
786}
787
788#[no_mangle]
790pub unsafe extern "C" fn rkrdb_reindex(id: usize) -> c_int {
791 with_handle(id, |h| match h.corpus.reindex() {
792 Ok(_) => Ok(RKRDB_OK),
793 Err(e) => {
794 set_err(h, e);
795 Ok(RKRDB_ERR)
796 }
797 })
798 .unwrap_or(RKRDB_NULL)
799}
800
801#[no_mangle]
803pub unsafe extern "C" fn rkrdb_cook_frame(id: usize, traj_id: u64, frame_idx: u32) -> c_int {
804 with_handle(id, |h| {
805 match h
806 .corpus
807 .cook_frame(crate::keys::FrameKey { traj_id, frame_idx })
808 {
809 Ok(_) => Ok(RKRDB_OK),
810 Err(e) => {
811 set_err(h, e);
812 Ok(RKRDB_ERR)
813 }
814 }
815 })
816 .unwrap_or(RKRDB_NULL)
817}
818
819#[no_mangle]
821pub unsafe extern "C" fn rkrdb_delete_cooked(id: usize, traj_id: u64, frame_idx: u32) -> c_int {
822 with_handle(id, |h| {
823 match h
824 .corpus
825 .delete_cooked_soa(crate::keys::FrameKey { traj_id, frame_idx })
826 {
827 Ok(()) => Ok(RKRDB_OK),
828 Err(e) => {
829 set_err(h, e);
830 Ok(RKRDB_ERR)
831 }
832 }
833 })
834 .unwrap_or(RKRDB_NULL)
835}
836
837#[no_mangle]
839pub unsafe extern "C" fn rkrdb_has_valid_cooked(id: usize, traj_id: u64, frame_idx: u32) -> c_int {
840 with_handle(id, |h| {
841 match h
842 .corpus
843 .has_valid_cooked_soa(crate::keys::FrameKey { traj_id, frame_idx })
844 {
845 Ok(true) => Ok(1),
846 Ok(false) => Ok(0),
847 Err(e) => {
848 set_err(h, e);
849 Ok(RKRDB_ERR)
850 }
851 }
852 })
853 .unwrap_or(RKRDB_NULL)
854}
855
856#[no_mangle]
859pub unsafe extern "C" fn rkrdb_get_positions(
860 id: usize,
861 traj_id: u64,
862 frame_idx: u32,
863 out_xyz: *mut f64,
864 capacity_atoms: u32,
865 out_natoms: *mut u32,
866) -> c_int {
867 if out_xyz.is_null() || out_natoms.is_null() {
868 return RKRDB_NULL;
869 }
870 with_handle(id, |h| {
871 match h
872 .corpus
873 .get_positions(crate::keys::FrameKey { traj_id, frame_idx })
874 {
875 Ok(pos) => {
876 let n = pos.len() as u32;
877 if n > capacity_atoms {
878 set_err(
879 h,
880 crate::error::Error::Message("positions buffer too small".into()),
881 );
882 return Ok(RKRDB_ERR);
883 }
884 unsafe {
885 *out_natoms = n;
886 for (i, row) in pos.iter().enumerate() {
887 *out_xyz.add(i * 3) = row[0];
888 *out_xyz.add(i * 3 + 1) = row[1];
889 *out_xyz.add(i * 3 + 2) = row[2];
890 }
891 }
892 Ok(RKRDB_OK)
893 }
894 Err(e) => {
895 set_err(h, e);
896 Ok(RKRDB_ERR)
897 }
898 }
899 })
900 .unwrap_or(RKRDB_NULL)
901}
902
903#[no_mangle]
905pub unsafe extern "C" fn rkrdb_get_forces(
906 id: usize,
907 traj_id: u64,
908 frame_idx: u32,
909 out_xyz: *mut f64,
910 capacity_atoms: u32,
911 out_natoms: *mut u32,
912 out_has_forces: *mut u8,
913) -> c_int {
914 if out_xyz.is_null() || out_natoms.is_null() || out_has_forces.is_null() {
915 return RKRDB_NULL;
916 }
917 with_handle(id, |h| {
918 match h
919 .corpus
920 .get_forces(crate::keys::FrameKey { traj_id, frame_idx })
921 {
922 Ok(None) => {
923 unsafe {
924 *out_has_forces = 0;
925 *out_natoms = 0;
926 }
927 Ok(RKRDB_OK)
928 }
929 Ok(Some(frc)) => {
930 let n = frc.len() as u32;
931 if n > capacity_atoms {
932 set_err(
933 h,
934 crate::error::Error::Message("forces buffer too small".into()),
935 );
936 return Ok(RKRDB_ERR);
937 }
938 unsafe {
939 *out_has_forces = 1;
940 *out_natoms = n;
941 for (i, row) in frc.iter().enumerate() {
942 *out_xyz.add(i * 3) = row[0];
943 *out_xyz.add(i * 3 + 1) = row[1];
944 *out_xyz.add(i * 3 + 2) = row[2];
945 }
946 }
947 Ok(RKRDB_OK)
948 }
949 Err(e) => {
950 set_err(h, e);
951 Ok(RKRDB_ERR)
952 }
953 }
954 })
955 .unwrap_or(RKRDB_NULL)
956}
957
958#[no_mangle]
960pub unsafe extern "C" fn rkrdb_recook_all(id: usize) -> c_int {
961 with_handle(id, |h| match h.corpus.recook_all() {
962 Ok(_) => Ok(RKRDB_OK),
963 Err(e) => {
964 set_err(h, e);
965 Ok(RKRDB_ERR)
966 }
967 })
968 .unwrap_or(RKRDB_NULL)
969}
970
971#[no_mangle]
975pub unsafe extern "C" fn rkrdb_frame_formula(
976 id: usize,
977 traj_id: u64,
978 frame_idx: u32,
979 buf: *mut c_char,
980 buflen: usize,
981) -> c_int {
982 if buf.is_null() || buflen == 0 {
983 return RKRDB_NULL;
984 }
985 with_handle(id, |h| {
986 match h
987 .corpus
988 .frame_formula(crate::keys::FrameKey { traj_id, frame_idx })
989 {
990 Ok(s) => {
991 let bytes = s.as_bytes();
992 if bytes.len() + 1 > buflen {
993 set_err(h, crate::error::Error::Message("buffer too small".into()));
994 return Ok(RKRDB_ERR);
995 }
996 unsafe {
997 std::ptr::copy_nonoverlapping(bytes.as_ptr(), buf as *mut u8, bytes.len());
998 *buf.add(bytes.len()) = 0;
999 }
1000 Ok(RKRDB_OK)
1001 }
1002 Err(e) => {
1003 set_err(h, e);
1004 Ok(RKRDB_ERR)
1005 }
1006 }
1007 })
1008 .unwrap_or(RKRDB_NULL)
1009}
1010
1011#[no_mangle]
1015pub unsafe extern "C" fn rkrdb_select_campaign(
1016 id: usize,
1017 traj_id: i64,
1018 symbol: *const c_char,
1019 natoms_min: u32,
1020 natoms_max: u32,
1021 formula: *const c_char,
1022 energy_min: f64,
1023 energy_max: f64,
1024 use_energy_range: c_int,
1025 fmax_min: f64,
1026 fmax_max: f64,
1027 use_fmax_range: c_int,
1028 elem_sym: *const c_char,
1029 elem_count: u32,
1030 elem_exact: c_int,
1031 flags: u32,
1032 limit: u32,
1033) -> c_int {
1034 with_handle(id, |h| {
1035 let mut sel = Select::new().natoms_range(natoms_min, natoms_max);
1036 if traj_id >= 0 {
1037 sel = sel.trajectory(traj_id as u64);
1038 }
1039 if !symbol.is_null() {
1040 let s = unsafe { CStr::from_ptr(symbol) };
1041 if let Ok(sym) = s.to_str() {
1042 if !sym.is_empty() {
1043 sel = sel.require_symbol(sym);
1044 }
1045 }
1046 }
1047 if !formula.is_null() {
1048 let s = unsafe { CStr::from_ptr(formula) };
1049 if let Ok(f) = s.to_str() {
1050 if !f.is_empty() {
1051 sel = sel.exact_composition(f);
1052 }
1053 }
1054 }
1055 if use_energy_range != 0 {
1056 sel = sel.energy_range(energy_min, energy_max);
1057 }
1058 if use_fmax_range != 0 {
1059 sel = sel.fmax_range(fmax_min, fmax_max);
1060 }
1061 if !elem_sym.is_null() {
1062 let s = unsafe { CStr::from_ptr(elem_sym) };
1063 if let Ok(sym) = s.to_str() {
1064 if !sym.is_empty() {
1065 if elem_exact != 0 {
1066 sel = sel.element_exact(sym, elem_count);
1067 } else {
1068 sel = sel.element_min(sym, elem_count);
1069 }
1070 }
1071 }
1072 }
1073 if flags & 1 != 0 {
1074 sel = sel.require_forces();
1075 }
1076 if flags & 2 != 0 {
1077 sel = sel.require_velocities();
1078 }
1079 if flags & 4 != 0 {
1080 sel = sel.require_energy();
1081 }
1082 if limit > 0 {
1083 sel = sel.limit(limit as usize);
1084 }
1085 match h.corpus.select(&sel) {
1086 Ok(keys) => {
1087 h.last_keys = keys;
1088 Ok(RKRDB_OK)
1089 }
1090 Err(e) => {
1091 set_err(h, e);
1092 Ok(RKRDB_ERR)
1093 }
1094 }
1095 })
1096 .unwrap_or(RKRDB_NULL)
1097}
1098
1099#[no_mangle]
1100pub unsafe extern "C" fn rkrdb_result_count(id: usize) -> c_int {
1101 with_handle(id, |h| Ok(h.last_keys.len() as c_int)).unwrap_or(RKRDB_NULL)
1102}
1103
1104#[no_mangle]
1106pub unsafe extern "C" fn rkrdb_result_key(
1107 id: usize,
1108 i: usize,
1109 out_traj: *mut u64,
1110 out_frame: *mut u32,
1111) -> c_int {
1112 if out_traj.is_null() || out_frame.is_null() {
1113 return RKRDB_NULL;
1114 }
1115 with_handle(id, |h| {
1116 let k = match h.last_keys.get(i) {
1117 Some(k) => *k,
1118 None => return Ok(RKRDB_NOT_FOUND),
1119 };
1120 unsafe {
1121 *out_traj = k.traj_id;
1122 *out_frame = k.frame_idx;
1123 }
1124 Ok(RKRDB_OK)
1125 })
1126 .unwrap_or(RKRDB_NULL)
1127}
1128
1129#[no_mangle]
1131pub unsafe extern "C" fn rkrdb_frame_hash(
1132 id: usize,
1133 traj_id: u64,
1134 frame_idx: u32,
1135 out_hash16: *mut u8,
1136) -> c_int {
1137 if out_hash16.is_null() {
1138 return RKRDB_NULL;
1139 }
1140 let key = FrameKey { traj_id, frame_idx };
1141 with_handle(id, |h| match h.corpus.frame_hash(key) {
1142 Ok(hash) => {
1143 let b = hash.to_bytes();
1144 unsafe { ptr::copy_nonoverlapping(b.as_ptr(), out_hash16, 16) };
1145 Ok(RKRDB_OK)
1146 }
1147 Err(e) => {
1148 set_err(h, e);
1149 Ok(RKRDB_ERR)
1150 }
1151 })
1152 .unwrap_or(RKRDB_NULL)
1153}
1154
1155#[no_mangle]
1157pub unsafe extern "C" fn rkrdb_get_frame_text(
1158 id: usize,
1159 traj_id: u64,
1160 frame_idx: u32,
1161 buf: *mut c_char,
1162 buflen: usize,
1163) -> c_int {
1164 if buf.is_null() || buflen == 0 {
1165 return RKRDB_NULL;
1166 }
1167 let key = FrameKey { traj_id, frame_idx };
1168 with_handle(id, |h| match h.corpus.get_frame_text(key) {
1169 Ok(text) => {
1170 let bytes = text.as_bytes();
1171 if bytes.len() + 1 > buflen {
1172 set_err(h, "buffer too small");
1173 return Ok(RKRDB_ERR);
1174 }
1175 unsafe {
1176 ptr::copy_nonoverlapping(bytes.as_ptr(), buf as *mut u8, bytes.len());
1177 *buf.add(bytes.len()) = 0;
1178 }
1179 Ok(bytes.len() as c_int)
1180 }
1181 Err(e) => {
1182 set_err(h, e);
1183 Ok(RKRDB_ERR)
1184 }
1185 })
1186 .unwrap_or(RKRDB_NULL)
1187}
1188
1189#[no_mangle]
1192pub unsafe extern "C" fn rkrdb_get_frame(
1193 id: usize,
1194 traj_id: u64,
1195 frame_idx: u32,
1196) -> *mut std::ffi::c_void {
1197 let key = FrameKey { traj_id, frame_idx };
1198 with_handle(id, |h| match h.corpus.get_frame(key) {
1199 Ok(frame) => Ok(Box::into_raw(Box::new(frame)) as *mut std::ffi::c_void),
1200 Err(e) => {
1201 set_err(h, e);
1202 Ok(std::ptr::null_mut())
1203 }
1204 })
1205 .unwrap_or(std::ptr::null_mut())
1206}
1207
1208fn collect_h5md_handle(
1209 id: usize,
1210 traj_id: u64,
1211) -> std::result::Result<crate::export_h5md::H5mdArrays, c_int> {
1212 let (corpus, unit) = {
1213 let g = HANDLES.lock().unwrap();
1214 let slot = g.get(id).ok_or(RKRDB_NULL)?;
1215 let h = slot.as_ref().ok_or(RKRDB_NULL)?;
1216 (Arc::clone(&h.corpus), h.velocity_time_unit.clone())
1217 };
1218 corpus
1219 .collect_h5md_with(
1220 traj_id,
1221 &crate::export_h5md::H5mdCollectOpts {
1222 velocity_time_unit: unit,
1223 },
1224 )
1225 .map_err(|e| {
1226 set_err_id(id, e);
1227 RKRDB_ERR
1228 })
1229}
1230
1231#[no_mangle]
1238pub unsafe extern "C" fn rkrdb_set_velocity_time_unit(id: usize, unit: *const c_char) -> c_int {
1239 let parsed = if unit.is_null() {
1240 None
1241 } else {
1242 let c = unsafe { CStr::from_ptr(unit) };
1243 let s = match c.to_str() {
1244 Ok(s) => s,
1245 Err(_) => return RKRDB_ERR,
1246 };
1247 if readcon_core::units::unit_conversion_factor(s, "ps").is_err() {
1248 set_err_id(id, "velocity_time_unit is not a time unit");
1249 return RKRDB_ERR;
1250 }
1251 Some(s.to_string())
1252 };
1253 with_handle(id, |h| {
1254 h.velocity_time_unit = parsed;
1255 Ok(RKRDB_OK)
1256 })
1257 .unwrap_or(RKRDB_NULL)
1258}
1259
1260#[no_mangle]
1262pub unsafe extern "C" fn rkrdb_h5md_shape(
1263 id: usize,
1264 traj_id: u64,
1265 out_nframes: *mut u32,
1266 out_natoms: *mut u32,
1267) -> c_int {
1268 if out_nframes.is_null() || out_natoms.is_null() {
1269 return RKRDB_NULL;
1270 }
1271 match collect_h5md_handle(id, traj_id) {
1272 Ok(a) => {
1273 unsafe {
1274 *out_nframes = a.n_frames as u32;
1275 *out_natoms = a.natoms as u32;
1276 }
1277 RKRDB_OK
1278 }
1279 Err(e) => {
1280 set_err_id(id, e);
1281 RKRDB_ERR
1282 }
1283 }
1284}
1285
1286#[no_mangle]
1288pub unsafe extern "C" fn rkrdb_h5md_positions(
1289 id: usize,
1290 traj_id: u64,
1291 out: *mut f64,
1292 cap: usize,
1293 out_nframes: *mut u32,
1294 out_natoms: *mut u32,
1295) -> c_int {
1296 if out.is_null() || out_nframes.is_null() || out_natoms.is_null() {
1297 return RKRDB_NULL;
1298 }
1299 match collect_h5md_handle(id, traj_id) {
1300 Ok(a) => {
1301 if a.positions.len() > cap {
1302 set_err_id(id, "h5md_positions buffer too small");
1303 return RKRDB_ERR;
1304 }
1305 unsafe {
1306 ptr::copy_nonoverlapping(a.positions.as_ptr(), out, a.positions.len());
1307 *out_nframes = a.n_frames as u32;
1308 *out_natoms = a.natoms as u32;
1309 }
1310 RKRDB_OK
1311 }
1312 Err(e) => {
1313 set_err_id(id, e);
1314 RKRDB_ERR
1315 }
1316 }
1317}
1318
1319unsafe fn h5md_copy_slice(
1320 id: usize,
1321 traj_id: u64,
1322 out: *mut f64,
1323 cap: usize,
1324 pick: fn(&crate::export_h5md::H5mdArrays) -> Option<&[f64]>,
1325 missing: &str,
1326) -> c_int {
1327 if out.is_null() {
1328 return RKRDB_NULL;
1329 }
1330 match collect_h5md_handle(id, traj_id) {
1331 Ok(a) => {
1332 let Some(sl) = pick(&a) else {
1333 set_err_id(id, missing);
1334 return RKRDB_NOT_FOUND;
1335 };
1336 if sl.len() > cap {
1337 set_err_id(id, "h5md buffer too small");
1338 return RKRDB_ERR;
1339 }
1340 unsafe {
1341 ptr::copy_nonoverlapping(sl.as_ptr(), out, sl.len());
1342 }
1343 RKRDB_OK
1344 }
1345 Err(e) => {
1346 set_err_id(id, e);
1347 RKRDB_ERR
1348 }
1349 }
1350}
1351
1352#[no_mangle]
1353pub unsafe extern "C" fn rkrdb_h5md_edges(
1354 id: usize,
1355 traj_id: u64,
1356 out: *mut f64,
1357 cap: usize,
1358) -> c_int {
1359 h5md_copy_slice(id, traj_id, out, cap, |a| Some(a.edges.as_slice()), "")
1360}
1361
1362#[no_mangle]
1363pub unsafe extern "C" fn rkrdb_h5md_forces(
1364 id: usize,
1365 traj_id: u64,
1366 out: *mut f64,
1367 cap: usize,
1368) -> c_int {
1369 h5md_copy_slice(id, traj_id, out, cap, |a| a.forces.as_deref(), "no forces")
1370}
1371
1372#[no_mangle]
1373pub unsafe extern "C" fn rkrdb_h5md_velocities(
1374 id: usize,
1375 traj_id: u64,
1376 out: *mut f64,
1377 cap: usize,
1378) -> c_int {
1379 h5md_copy_slice(
1380 id,
1381 traj_id,
1382 out,
1383 cap,
1384 |a| a.velocities.as_deref(),
1385 "no velocities",
1386 )
1387}
1388
1389#[no_mangle]
1391pub unsafe extern "C" fn rkrdb_h5md_species(
1392 id: usize,
1393 traj_id: u64,
1394 out: *mut i32,
1395 cap: usize,
1396 out_natoms: *mut u32,
1397) -> c_int {
1398 if out.is_null() || out_natoms.is_null() {
1399 return RKRDB_NULL;
1400 }
1401 match collect_h5md_handle(id, traj_id) {
1402 Ok(a) => {
1403 if a.species_z.len() > cap {
1404 set_err_id(id, "h5md_species buffer too small");
1405 return RKRDB_ERR;
1406 }
1407 unsafe {
1408 ptr::copy_nonoverlapping(a.species_z.as_ptr(), out, a.species_z.len());
1409 *out_natoms = a.species_z.len() as u32;
1410 }
1411 RKRDB_OK
1412 }
1413 Err(e) => {
1414 set_err_id(id, e);
1415 RKRDB_ERR
1416 }
1417 }
1418}
1419
1420#[no_mangle]
1421pub unsafe extern "C" fn rkrdb_get_velocities(
1422 id: usize,
1423 traj_id: u64,
1424 frame_idx: u32,
1425 out_xyz: *mut f64,
1426 capacity_atoms: u32,
1427 out_natoms: *mut u32,
1428 out_has_velocities: *mut u8,
1429) -> c_int {
1430 if out_xyz.is_null() || out_natoms.is_null() || out_has_velocities.is_null() {
1431 return RKRDB_NULL;
1432 }
1433 let corpus = match corpus_arc(id) {
1434 Ok(c) => c,
1435 Err(c) => return c,
1436 };
1437 match corpus.get_velocities(FrameKey { traj_id, frame_idx }) {
1438 Ok(Some(rows)) => {
1439 if rows.len() > capacity_atoms as usize {
1440 set_err_id(id, "velocities buffer too small");
1441 return RKRDB_ERR;
1442 }
1443 unsafe {
1444 *out_has_velocities = 1;
1445 *out_natoms = rows.len() as u32;
1446 for (i, r) in rows.iter().enumerate() {
1447 *out_xyz.add(i * 3) = r[0];
1448 *out_xyz.add(i * 3 + 1) = r[1];
1449 *out_xyz.add(i * 3 + 2) = r[2];
1450 }
1451 }
1452 RKRDB_OK
1453 }
1454 Ok(None) => {
1455 unsafe {
1456 *out_has_velocities = 0;
1457 *out_natoms = 0;
1458 }
1459 RKRDB_OK
1460 }
1461 Err(e) => {
1462 set_err_id(id, e);
1463 RKRDB_ERR
1464 }
1465 }
1466}
1467
1468#[no_mangle]
1470pub unsafe extern "C" fn rkrdb_h5md_times(
1471 id: usize,
1472 traj_id: u64,
1473 out: *mut f64,
1474 cap: usize,
1475 out_n: *mut u32,
1476) -> c_int {
1477 if out.is_null() || out_n.is_null() {
1478 return RKRDB_NULL;
1479 }
1480 match collect_h5md_handle(id, traj_id) {
1481 Ok(a) => {
1482 if a.times.len() > cap {
1483 set_err_id(id, "h5md_times buffer too small");
1484 return RKRDB_ERR;
1485 }
1486 unsafe {
1487 ptr::copy_nonoverlapping(a.times.as_ptr(), out, a.times.len());
1488 *out_n = a.times.len() as u32;
1489 }
1490 RKRDB_OK
1491 }
1492 Err(e) => {
1493 set_err_id(id, e);
1494 RKRDB_ERR
1495 }
1496 }
1497}
1498
1499#[no_mangle]
1501pub unsafe extern "C" fn rkrdb_xxh3_128(data: *const u8, len: usize, out_hash16: *mut u8) -> c_int {
1502 if data.is_null() || out_hash16.is_null() {
1503 return RKRDB_NULL;
1504 }
1505 let slice = unsafe { std::slice::from_raw_parts(data, len) };
1506 let h = hash_frame_bytes(slice);
1507 let b = h.to_bytes();
1508 unsafe { ptr::copy_nonoverlapping(b.as_ptr(), out_hash16, 16) };
1509 RKRDB_OK
1510}
1511
1512#[cfg(test)]
1513mod tests {
1514 use super::*;
1515 use std::ffi::CString;
1516 use std::path::PathBuf;
1517
1518 #[test]
1519 fn abi_stamp_equals_the_constants() {
1520 assert_eq!(rkrdb_abi_version_major(), RKRDB_ABI_VERSION_MAJOR);
1521 assert_eq!(rkrdb_abi_version_minor(), RKRDB_ABI_VERSION_MINOR);
1522 assert_eq!(rkrdb_abi_layout_revision(), RKRDB_ABI_LAYOUT_REVISION);
1523 let stamp = unsafe { CStr::from_ptr(rkrdb_abi_stamp()) }
1524 .to_str()
1525 .unwrap();
1526 assert_eq!(
1527 stamp,
1528 format!(
1529 "readcon-db/abi-{}.{}/layout-{}",
1530 RKRDB_ABI_VERSION_MAJOR, RKRDB_ABI_VERSION_MINOR, RKRDB_ABI_LAYOUT_REVISION
1531 )
1532 );
1533 assert_eq!(
1534 rkrdb_abi_compatible(
1535 RKRDB_ABI_VERSION_MAJOR,
1536 RKRDB_ABI_VERSION_MINOR,
1537 RKRDB_ABI_LAYOUT_REVISION
1538 ),
1539 1
1540 );
1541 }
1542
1543 #[test]
1544 fn abi_header_carries_the_stamp() {
1545 let header =
1546 std::fs::read_to_string(concat!(env!("CARGO_MANIFEST_DIR"), "/include/readcon-db.h"))
1547 .unwrap();
1548 for (name, value) in [
1549 ("RKRDB_ABI_VERSION_MAJOR", RKRDB_ABI_VERSION_MAJOR),
1550 ("RKRDB_ABI_VERSION_MINOR", RKRDB_ABI_VERSION_MINOR),
1551 ("RKRDB_ABI_LAYOUT_REVISION", RKRDB_ABI_LAYOUT_REVISION),
1552 ] {
1553 let want = format!("#define {name} {value}");
1554 assert!(header.contains(&want), "readcon-db.h lacks {want}");
1555 }
1556 for f in [
1557 "rkrdb_abi_version_major(void)",
1558 "rkrdb_abi_version_minor(void)",
1559 "rkrdb_abi_layout_revision(void)",
1560 "rkrdb_abi_stamp(void)",
1561 "rkrdb_abi_compatible(uint32_t major",
1562 ] {
1563 assert!(header.contains(f), "readcon-db.h lacks {f}");
1564 }
1565 }
1566
1567 #[test]
1568 fn a_lower_caller_minor_is_accepted() {
1569 let library = (2, 3, 5);
1570 assert!(abi_accepts(library, (2, 2, 5)));
1571 assert!(abi_accepts(library, (2, 0, 5)));
1572 assert!(abi_accepts(library, (2, 3, 5)));
1573 }
1574
1575 #[test]
1576 fn a_higher_minor_other_major_or_other_layout_is_refused() {
1577 let library = (2, 3, 5);
1578 assert!(!abi_accepts(library, (2, 4, 5)));
1579 assert!(!abi_accepts(library, (1, 3, 5)));
1580 assert!(!abi_accepts(library, (3, 3, 5)));
1581 assert!(!abi_accepts(library, (2, 3, 4)));
1582 assert!(!abi_accepts(library, (2, 3, 6)));
1583 }
1584
1585 #[test]
1586 fn the_exported_check_refuses_a_foreign_stamp() {
1587 assert_eq!(
1588 rkrdb_abi_compatible(
1589 RKRDB_ABI_VERSION_MAJOR + 1,
1590 RKRDB_ABI_VERSION_MINOR,
1591 RKRDB_ABI_LAYOUT_REVISION
1592 ),
1593 0
1594 );
1595 assert_eq!(
1596 rkrdb_abi_compatible(
1597 RKRDB_ABI_VERSION_MAJOR,
1598 RKRDB_ABI_VERSION_MINOR + 1,
1599 RKRDB_ABI_LAYOUT_REVISION
1600 ),
1601 0
1602 );
1603 assert_eq!(
1604 rkrdb_abi_compatible(
1605 RKRDB_ABI_VERSION_MAJOR,
1606 RKRDB_ABI_VERSION_MINOR,
1607 RKRDB_ABI_LAYOUT_REVISION + 1
1608 ),
1609 0
1610 );
1611 }
1612
1613 fn natural_dest_velocity(native: f64) -> f64 {
1614 let t_ps = readcon_core::units::unit_conversion_factor(
1615 crate::units_canon::NATURAL_TIME_UNIT,
1616 "ps",
1617 )
1618 .unwrap();
1619 native / t_ps
1620 }
1621
1622 fn fixture(name: &str) -> PathBuf {
1623 PathBuf::from(env!("CARGO_MANIFEST_DIR"))
1624 .join("resources/test")
1625 .join(name)
1626 }
1627
1628 #[test]
1629 fn c_abi_append_str_and_frame() {
1630 let dir = tempfile::tempdir().unwrap();
1631 let path = CString::new(dir.path().to_str().unwrap()).unwrap();
1632 let text = std::fs::read_to_string(fixture("tiny_cuh2.con")).unwrap();
1633 let ctext = CString::new(text).unwrap();
1634 let src = CString::new("mem").unwrap();
1635 let mut id = 0usize;
1636 let mut n = 0u32;
1637 unsafe {
1638 assert_eq!(rkrdb_open(path.as_ptr(), &mut id), RKRDB_OK);
1639 assert_eq!(
1640 rkrdb_append_trajectory_str(id, 1, ctext.as_ptr(), src.as_ptr(), &mut n),
1641 RKRDB_OK
1642 );
1643 assert!(n >= 1);
1644 let handle = rkrdb_get_frame(id, 1, 0);
1645 assert!(!handle.is_null());
1646 let mut n2 = 0u32;
1647 assert_eq!(
1648 rkrdb_append_trajectory_frame(id, 2, handle, src.as_ptr(), &mut n2),
1649 RKRDB_OK
1650 );
1651 assert!(n2 >= 1);
1652 let again = rkrdb_get_frame(id, 2, 0);
1653 assert!(!again.is_null());
1654 rkrdb_close(id);
1655 }
1656 }
1657
1658 #[test]
1659 fn c_abi_extend_frame_preserves_sequential_indices() {
1660 let dir = tempfile::tempdir().unwrap();
1661 let path = CString::new(dir.path().to_str().unwrap()).unwrap();
1662 let text =
1663 CString::new(std::fs::read_to_string(fixture("tiny_cuh2.con")).unwrap()).unwrap();
1664 let mut id = 0usize;
1665 let mut n = 0u32;
1666 unsafe {
1667 assert_eq!(rkrdb_open(path.as_ptr(), &mut id), RKRDB_OK);
1668 assert_eq!(
1669 rkrdb_append_trajectory_str(id, 1, text.as_ptr(), ptr::null(), &mut n),
1670 RKRDB_OK
1671 );
1672 let handle = rkrdb_get_frame(id, 1, 0);
1673 assert!(!handle.is_null());
1674 let frame = Box::from_raw(handle as *mut readcon_core::types::ConFrame);
1675 let frame_ptr = (&*frame as *const readcon_core::types::ConFrame).cast();
1676 for expected in 1..=3 {
1677 assert_eq!(
1678 rkrdb_extend_trajectory_frame(id, 2, frame_ptr, ptr::null(), &mut n),
1679 RKRDB_OK
1680 );
1681 assert_eq!(n, expected);
1682 }
1683 for index in 0..3 {
1684 let stored = rkrdb_get_frame(id, 2, index);
1685 assert!(!stored.is_null());
1686 drop(Box::from_raw(stored as *mut readcon_core::types::ConFrame));
1687 }
1688 assert_eq!(
1689 rkrdb_extend_trajectory_frame(id, 2, ptr::null(), ptr::null(), &mut n),
1690 RKRDB_NULL
1691 );
1692 assert_eq!(n, 3);
1693 rkrdb_close(id);
1694 }
1695 }
1696
1697 #[test]
1698 fn c_abi_append_units_canonical() {
1699 let dir = tempfile::tempdir().unwrap();
1700 let path = CString::new(dir.path().to_str().unwrap()).unwrap();
1701 let con = CString::new(fixture("tiny_cuh2.con").to_str().unwrap()).unwrap();
1702 let units = CString::new(r#"{"length":"A","energy":"ev","time":"femtosecond"}"#).unwrap();
1703 let mut id = 0usize;
1704 let mut n = 0u32;
1705 unsafe {
1706 assert_eq!(rkrdb_open(path.as_ptr(), &mut id), RKRDB_OK);
1707 assert_eq!(
1708 rkrdb_append_trajectory_units(id, 1, con.as_ptr(), units.as_ptr(), &mut n),
1709 RKRDB_OK
1710 );
1711 assert!(n >= 1);
1712 let mut buf = vec![0i8; 256];
1713 assert_eq!(
1714 rkrdb_frame_units(id, 1, 0, buf.as_mut_ptr(), buf.len()),
1715 RKRDB_OK
1716 );
1717 let s = CStr::from_ptr(buf.as_ptr()).to_str().unwrap();
1718 assert!(s.contains("angstrom"), "{s}");
1719 assert!(s.contains("eV"), "{s}");
1720 assert!(s.contains("fs"), "{s}");
1721 assert!(!s.contains("\"A\""), "{s}");
1722 let mut times = [0.0f64; 8];
1723 let mut nt = 0u32;
1724 assert_eq!(
1725 rkrdb_h5md_times(id, 1, times.as_mut_ptr(), times.len(), &mut nt),
1726 RKRDB_OK
1727 );
1728 assert!(nt >= 1);
1729 assert!(
1730 (times[0] - 0.0).abs() < 1e-12,
1731 "undeclared header.time dest ps times[0]={}",
1732 times[0]
1733 );
1734 assert_eq!(rkrdb_recook_all(id), RKRDB_OK);
1735 let need1 = rkrdb_pack_frame(id, 1, 0, std::ptr::null_mut(), 0);
1736 assert!(need1 > 0, "pack_frame size={need1}");
1737 let mut one = vec![0u8; need1 as usize];
1738 let n1 = rkrdb_pack_frame(id, 1, 0, one.as_mut_ptr(), one.len());
1739 assert_eq!(n1, need1);
1740 let mut xyz1 = vec![0.0f64; 32];
1741 let mut nxyz1 = 0u32;
1742 assert_eq!(
1743 rkrdb_unpack_positions(one.as_ptr(), n1 as usize, xyz1.as_mut_ptr(), 8, &mut nxyz1),
1744 RKRDB_OK
1745 );
1746 assert!(nxyz1 >= 1);
1747 assert!(
1748 (xyz1[0] - 0.6394).abs() < 1e-3,
1749 "pack_frame dest x0={}",
1750 xyz1[0]
1751 );
1752 let multi = CString::new(fixture("tiny_multi_cuh2.con").to_str().unwrap()).unwrap();
1753 let mut n2 = 0u32;
1754 assert_eq!(
1755 rkrdb_append_trajectory(id, 3, multi.as_ptr(), &mut n2),
1756 RKRDB_OK
1757 );
1758 let tids = [3u64, 3];
1759 let fids = [0u32, 1];
1760 let need =
1761 rkrdb_pack_frames(id, tids.as_ptr(), fids.as_ptr(), 2, std::ptr::null_mut(), 0);
1762 assert!(need > 0, "pack_frames size={need}");
1763 let mut pbuf = vec![0u8; need as usize];
1764 let npack = rkrdb_pack_frames(
1765 id,
1766 tids.as_ptr(),
1767 fids.as_ptr(),
1768 2,
1769 pbuf.as_mut_ptr(),
1770 pbuf.len(),
1771 );
1772 assert_eq!(npack, need);
1773 let mut nfr = 0u32;
1774 assert_eq!(
1775 rkrdb_unpack_batch_nframes(pbuf.as_ptr(), npack as usize, &mut nfr),
1776 RKRDB_OK
1777 );
1778 assert_eq!(nfr, 2);
1779 let mut item = vec![0.0f64; 32];
1780 let mut nitem = 0u32;
1781 assert_eq!(
1782 rkrdb_unpack_batch_item(
1783 pbuf.as_ptr(),
1784 npack as usize,
1785 0,
1786 item.as_mut_ptr(),
1787 8,
1788 &mut nitem
1789 ),
1790 RKRDB_OK
1791 );
1792 assert!(nitem >= 1);
1793 assert!((item[0] - 0.6394).abs() < 1e-3, "dest x0={}", item[0]);
1794 let mut item1 = vec![0.0f64; 32];
1795 let mut nitem1 = 0u32;
1796 assert_eq!(
1797 rkrdb_unpack_batch_item(
1798 pbuf.as_ptr(),
1799 npack as usize,
1800 1,
1801 item1.as_mut_ptr(),
1802 8,
1803 &mut nitem1
1804 ),
1805 RKRDB_OK
1806 );
1807 assert!(nitem1 >= 1);
1808 let mut native1 = vec![0.0f64; 32];
1809 let mut nn1 = 0u32;
1810 assert_eq!(
1811 rkrdb_get_positions(id, 3, 1, native1.as_mut_ptr(), 8, &mut nn1),
1812 RKRDB_OK
1813 );
1814 assert_eq!(nitem1, nn1);
1815 assert!(
1816 (item1[0] - native1[0]).abs() < 1e-9,
1817 "item1 x0={} native={}",
1818 item1[0],
1819 native1[0]
1820 );
1821 assert!((item1[6] - 8.8549).abs() < 1e-4, "frame1 H x={}", item1[6]);
1822 let mut times3 = [0.0f64; 8];
1823 let mut nt3 = 0u32;
1824 assert_eq!(
1825 rkrdb_h5md_times(id, 3, times3.as_mut_ptr(), times3.len(), &mut nt3),
1826 RKRDB_OK
1827 );
1828 assert!(nt3 >= 2);
1829 assert!((times3[0] - 0.0).abs() < 1e-12);
1830 assert!((times3[1] - 1.0).abs() < 1e-12);
1831 let mut nf3 = 0u32;
1832 let mut na3 = 0u32;
1833 assert_eq!(rkrdb_h5md_shape(id, 3, &mut nf3, &mut na3), RKRDB_OK);
1834 assert!(nf3 >= 2 && na3 >= 3);
1835 let mut pos3 = vec![0.0f64; (nf3 as usize) * (na3 as usize) * 3];
1836 assert_eq!(
1837 rkrdb_h5md_positions(id, 3, pos3.as_mut_ptr(), pos3.len(), &mut nf3, &mut na3),
1838 RKRDB_OK
1839 );
1840 assert!((pos3[0] - 0.6394).abs() < 1e-4);
1841 let i_h1 = (na3 as usize) * 3 + 2 * 3;
1842 assert!(
1843 (pos3[i_h1] - 8.8549).abs() < 1e-4,
1844 "c_abi T>1 dest H x={}",
1845 pos3[i_h1]
1846 );
1847 let mut nf = 0u32;
1848 let mut na = 0u32;
1849 assert_eq!(rkrdb_h5md_shape(id, 1, &mut nf, &mut na), RKRDB_OK);
1850 assert!(nf >= 1 && na >= 1);
1851 let mut pos = vec![0.0f64; (nf as usize) * (na as usize) * 3];
1852 assert_eq!(
1853 rkrdb_h5md_positions(id, 1, pos.as_mut_ptr(), pos.len(), &mut nf, &mut na),
1854 RKRDB_OK
1855 );
1856 assert_eq!(pos.len(), (nf as usize) * (na as usize) * 3);
1857 assert!((pos[0] - 0.6394).abs() < 1e-4, "dest A x0={}", pos[0]);
1859 assert!(pos.iter().any(|&x| x != 0.0));
1860 let mut xyz = vec![0.0f64; 32];
1861 let mut npos = 0u32;
1862 assert_eq!(
1863 rkrdb_get_positions(id, 1, 0, xyz.as_mut_ptr(), 8, &mut npos),
1864 RKRDB_OK
1865 );
1866 assert!(npos >= 1);
1867 assert!((xyz[0] - 0.6394).abs() < 1e-4);
1868 let mut fr0 = vec![0.0f64; 32];
1869 let mut nf0 = 0u32;
1870 let mut hf0 = 1u8;
1871 assert_eq!(
1872 rkrdb_get_forces(id, 1, 0, fr0.as_mut_ptr(), 8, &mut nf0, &mut hf0),
1873 RKRDB_OK
1874 );
1875 assert_eq!(hf0, 0);
1876 let mut no_f = [0.0f64; 8];
1877 assert_eq!(
1878 rkrdb_h5md_forces(id, 1, no_f.as_mut_ptr(), no_f.len()),
1879 RKRDB_NOT_FOUND
1880 );
1881 let mut no_v = [0.0f64; 8];
1882 assert_eq!(
1883 rkrdb_h5md_velocities(id, 1, no_v.as_mut_ptr(), no_v.len()),
1884 RKRDB_NOT_FOUND
1885 );
1886 let mut edges = vec![0.0f64; (nf as usize) * 9];
1887 assert_eq!(
1888 rkrdb_h5md_edges(id, 1, edges.as_mut_ptr(), edges.len()),
1889 RKRDB_OK
1890 );
1891 assert!((edges[0] - 15.3456).abs() < 1e-4, "edge a_x={}", edges[0]);
1892 let mut z = vec![0i32; na as usize];
1893 let mut nz = 0u32;
1894 assert_eq!(
1895 rkrdb_h5md_species(id, 1, z.as_mut_ptr(), z.len(), &mut nz),
1896 RKRDB_OK
1897 );
1898 assert_eq!(nz, na);
1899 assert!(z.contains(&29), "{z:?}");
1900 assert!(z.contains(&1), "{z:?}");
1901 let forces = CString::new(fixture("tiny_cuh2_forces.con").to_str().unwrap()).unwrap();
1902 let ext_u = CString::new(r#"{"length":"A","energy":"ev"}"#).unwrap();
1903 let mut n2 = 0u32;
1904 assert_eq!(
1905 rkrdb_extend_trajectory_units(id, 1, forces.as_ptr(), ext_u.as_ptr(), &mut n2),
1906 RKRDB_OK
1907 );
1908 assert!(n2 >= 1);
1909 let mut buf2 = vec![0i8; 256];
1910 assert_eq!(
1911 rkrdb_frame_units(id, 1, 1, buf2.as_mut_ptr(), buf2.len()),
1912 RKRDB_OK
1913 );
1914 let s2 = CStr::from_ptr(buf2.as_ptr()).to_str().unwrap();
1915 assert!(s2.contains("angstrom"), "{s2}");
1916 let mut frc = vec![0.0f64; 256];
1917 assert_eq!(
1918 rkrdb_h5md_forces(id, 1, frc.as_mut_ptr(), frc.len()),
1919 RKRDB_OK
1920 );
1921 let dest_f0 = -1.234567 * 96.485_332;
1922 assert!(
1923 frc.iter().any(|&x| (x - dest_f0).abs() < 1e-3),
1924 "missing dest force ~{dest_f0}, got {:?}",
1925 &frc[..12]
1926 );
1927 let units2 = CString::new(r#"{"length":"nm","energy":"eV"}"#).unwrap();
1928 let mut nset = 0u32;
1929 assert_eq!(rkrdb_set_units(id, 1, units2.as_ptr(), &mut nset), RKRDB_OK);
1930 let mut nf2 = 0u32;
1931 let mut na2 = 0u32;
1932 assert_eq!(rkrdb_h5md_shape(id, 1, &mut nf2, &mut na2), RKRDB_OK);
1933 let mut pos2 = vec![0.0f64; (nf2 as usize) * (na2 as usize) * 3];
1934 assert_eq!(
1935 rkrdb_h5md_positions(id, 1, pos2.as_mut_ptr(), pos2.len(), &mut nf2, &mut na2),
1936 RKRDB_OK
1937 );
1938 assert!(
1939 (pos2[0] - 0.6394).abs() < 1e-4,
1940 "dest A after set_units x0={}",
1941 pos2[0]
1942 );
1943 let mut frc2 = vec![0.0f64; 256];
1944 assert_eq!(
1945 rkrdb_h5md_forces(id, 1, frc2.as_mut_ptr(), frc2.len()),
1946 RKRDB_OK
1947 );
1948 let i_hfx = (na2 as usize) * 3 + 2 * 3;
1949 assert!(
1950 (frc2[i_hfx] - dest_f0).abs() < 1e-3,
1951 "dest force of force-bearing frame H Fx after set_units: {} vs {dest_f0}",
1952 frc2[i_hfx]
1953 );
1954 assert!(
1955 frc2[..na2 as usize * 3].iter().all(|&x| x.abs() < 1e-12),
1956 "frame 0 dest force must stay zero-pad, got {:?}",
1957 &frc2[..na2 as usize * 3]
1958 );
1959 rkrdb_close(id);
1960 let mut idro = 0usize;
1961 assert_eq!(rkrdb_open_readonly(path.as_ptr(), &mut idro), RKRDB_OK);
1962 let mut nfr = 0u32;
1963 let mut nar = 0u32;
1964 let mut posr = vec![0.0f64; 64];
1965 assert_eq!(
1966 rkrdb_h5md_positions(idro, 1, posr.as_mut_ptr(), posr.len(), &mut nfr, &mut nar),
1967 RKRDB_OK
1968 );
1969 assert!(
1970 (posr[0] - 0.6394).abs() < 1e-4,
1971 "readonly dest x0={}",
1972 posr[0]
1973 );
1974 assert_eq!(rkrdb_close(idro), RKRDB_OK);
1975 let velcon = CString::new(fixture("tiny_cuh2.convel").to_str().unwrap()).unwrap();
1976 let mut id2 = 0usize;
1977 let mut n3 = 0u32;
1978 assert_eq!(rkrdb_open(path.as_ptr(), &mut id2), RKRDB_OK);
1979 assert_eq!(
1980 rkrdb_append_trajectory(id2, 2, velcon.as_ptr(), &mut n3),
1981 RKRDB_OK
1982 );
1983 let mut vel = vec![0.0f64; 64];
1984 assert_eq!(
1985 rkrdb_h5md_velocities(id2, 2, vel.as_mut_ptr(), vel.len()),
1986 RKRDB_OK
1987 );
1988 let expect = natural_dest_velocity(0.001234);
1989 assert!((vel[0] - expect).abs() < 1e-9, "got {}", vel[0]);
1990 let mut native = vec![0.0f64; 32];
1991 let mut nv = 0u32;
1992 let mut has_v = 0u8;
1993 assert_eq!(
1994 rkrdb_get_velocities(id2, 2, 0, native.as_mut_ptr(), 8, &mut nv, &mut has_v),
1995 RKRDB_OK
1996 );
1997 assert_eq!(has_v, 1);
1998 assert!(nv >= 1);
1999 assert!(
2000 (native[0] - 0.001234).abs() < 1e-9,
2001 "native vx0={}",
2002 native[0]
2003 );
2004 let mut tiny = [0.0f64; 1];
2005 let mut ntiny = 0u32;
2006 let mut htiny = 0u8;
2007 assert_eq!(
2008 rkrdb_get_velocities(id2, 2, 0, tiny.as_mut_ptr(), 0, &mut ntiny, &mut htiny),
2009 RKRDB_ERR
2010 );
2011 let mut ebuf = vec![0i8; 128];
2012 assert!(rkrdb_last_error(id2, ebuf.as_mut_ptr(), ebuf.len()) >= 0);
2013 let es = CStr::from_ptr(ebuf.as_ptr()).to_str().unwrap();
2014 assert!(es.contains("too small"), "{es}");
2015 let mut none = vec![0.0f64; 8];
2016 let mut nn = 99u32;
2017 let mut has_none = 1u8;
2018 assert_eq!(
2019 rkrdb_get_velocities(id2, 1, 0, none.as_mut_ptr(), 2, &mut nn, &mut has_none),
2020 RKRDB_OK
2021 );
2022 assert_eq!(has_none, 0);
2023 assert_eq!(nn, 0);
2024 assert_eq!(rkrdb_close(id2), RKRDB_OK);
2025 }
2026 }
2027
2028 #[test]
2029 fn c_abi_set_units_keeps_dest_vel_time_edges() {
2030 let dir = tempfile::tempdir().unwrap();
2031 let path = CString::new(dir.path().to_str().unwrap()).unwrap();
2032 let velcon = CString::new(fixture("tiny_cuh2.convel").to_str().unwrap()).unwrap();
2033 let mut id = 0usize;
2034 let mut n = 0u32;
2035 unsafe {
2036 assert_eq!(rkrdb_open(path.as_ptr(), &mut id), RKRDB_OK);
2037 assert_eq!(
2038 rkrdb_append_trajectory(id, 1, velcon.as_ptr(), &mut n),
2039 RKRDB_OK
2040 );
2041 let mut vel0 = vec![0.0f64; 64];
2042 assert_eq!(
2043 rkrdb_h5md_velocities(id, 1, vel0.as_mut_ptr(), vel0.len()),
2044 RKRDB_OK
2045 );
2046 let mut edges0 = vec![0.0f64; 32];
2047 assert_eq!(
2048 rkrdb_h5md_edges(id, 1, edges0.as_mut_ptr(), edges0.len()),
2049 RKRDB_OK
2050 );
2051 let mut times0 = [0.0f64; 8];
2052 let mut nt = 0u32;
2053 assert_eq!(
2054 rkrdb_h5md_times(id, 1, times0.as_mut_ptr(), times0.len(), &mut nt),
2055 RKRDB_OK
2056 );
2057 let units = CString::new(r#"{"length":"nm","energy":"eV","time":"ps"}"#).unwrap();
2058 let mut nset = 0u32;
2059 assert_eq!(rkrdb_set_units(id, 1, units.as_ptr(), &mut nset), RKRDB_OK);
2060 let mut vel1 = vec![0.0f64; 64];
2061 assert_eq!(
2062 rkrdb_h5md_velocities(id, 1, vel1.as_mut_ptr(), vel1.len()),
2063 RKRDB_OK
2064 );
2065 let expect = natural_dest_velocity(0.001234);
2066 assert!((vel0[0] - expect).abs() < 1e-9, "got {}", vel0[0]);
2067 assert!(
2068 (vel0[0] - vel1[0]).abs() < 1e-12,
2069 "dest vel after set_units {} vs {}",
2070 vel0[0],
2071 vel1[0]
2072 );
2073 let mut edges1 = vec![0.0f64; 32];
2074 assert_eq!(
2075 rkrdb_h5md_edges(id, 1, edges1.as_mut_ptr(), edges1.len()),
2076 RKRDB_OK
2077 );
2078 assert!((edges0[0] - 15.3456).abs() < 1e-4, "edge a_x={}", edges0[0]);
2079 assert!(
2080 (edges0[0] - edges1[0]).abs() < 1e-12,
2081 "dest edges after set_units {} vs {}",
2082 edges0[0],
2083 edges1[0]
2084 );
2085 let mut times1 = [0.0f64; 8];
2086 let mut nt1 = 0u32;
2087 assert_eq!(
2088 rkrdb_h5md_times(id, 1, times1.as_mut_ptr(), times1.len(), &mut nt1),
2089 RKRDB_OK
2090 );
2091 assert_eq!(nt, nt1);
2092 assert!(
2093 (times0[0] - times1[0]).abs() < 1e-12,
2094 "dest time after set_units {} vs {}",
2095 times0[0],
2096 times1[0]
2097 );
2098 let mut timed = std::fs::read_to_string(fixture("tiny_cuh2.con")).unwrap();
2099 let mut tlines: Vec<String> = timed.lines().map(str::to_string).collect();
2100 tlines[1] = concat!(
2101 r#"{"con_spec_version":3,"time":12.5,"#,
2102 r#""units":{"length":"angstrom","energy":"eV","time":"fs"}}"#
2103 )
2104 .to_string();
2105 timed = tlines.join("\n");
2106 timed.push('\n');
2107 let ttext = CString::new(timed).unwrap();
2108 let tsrc = CString::new("memory").unwrap();
2109 let mut ntimed = 0u32;
2110 assert_eq!(
2111 rkrdb_append_trajectory_str(id, 2, ttext.as_ptr(), tsrc.as_ptr(), &mut ntimed),
2112 RKRDB_OK
2113 );
2114 let mut t0 = [0.0f64; 8];
2115 let mut nt0 = 0u32;
2116 assert_eq!(
2117 rkrdb_h5md_times(id, 2, t0.as_mut_ptr(), t0.len(), &mut nt0),
2118 RKRDB_OK
2119 );
2120 assert!((t0[0] - 0.0125).abs() < 1e-12, "dest time fs->ps {}", t0[0]);
2121 let tunits =
2122 CString::new(r#"{"length":"angstrom","energy":"eV","time":"ps"}"#).unwrap();
2123 let mut nset2 = 0u32;
2124 assert_eq!(
2125 rkrdb_set_units(id, 2, tunits.as_ptr(), &mut nset2),
2126 RKRDB_OK
2127 );
2128 let mut t1 = [0.0f64; 8];
2129 let mut nt1t = 0u32;
2130 assert_eq!(
2131 rkrdb_h5md_times(id, 2, t1.as_mut_ptr(), t1.len(), &mut nt1t),
2132 RKRDB_OK
2133 );
2134 assert!(
2135 (t1[0] - 0.0125).abs() < 1e-12,
2136 "dest time after set_units time {}",
2137 t1[0]
2138 );
2139 assert_eq!(rkrdb_close(id), RKRDB_OK);
2140 }
2141 }
2142}