demes_forward_capi/
lib.rs

1use demes_forward::demes;
2use libc::c_char;
3use std::ffi::CStr;
4use std::ffi::CString;
5use std::io::Read;
6
7/// ## Not Send/Sync
8///
9/// This type is meant to be used in an FFI context.
10/// We therefore deny Send/Sync:
11///
12/// ```{compile_fail}
13/// fn is_send<T: Send>()  {}
14///
15/// is_send::<demes_forward_capi::OpaqueForwardGraph>();
16/// ```
17///
18/// ```{compile_fail}
19/// fn is_sync<T: Sync>()  {}
20///
21/// is_send::<demes_forward_capi::OpaqueForwardGraph>();
22/// ```
23pub struct OpaqueForwardGraph {
24    graph: Option<demes_forward::ForwardGraph>,
25    error: Option<CString>,
26    current_time: Option<f64>,
27    // Rust types containing raw pointers
28    // do not get blanket Send/Sync impl.
29    // We use a ZST here to prevent those impl
30    // for this type.
31    deny_send_sync: std::marker::PhantomData<*const ()>,
32}
33
34#[repr(i32)]
35enum ErrorCode {
36    // GraphUninitialized = -1,
37    GraphIsNull = -2,
38}
39
40impl OpaqueForwardGraph {
41    fn update(&mut self, graph: Option<demes_forward::ForwardGraph>, error: Option<String>) {
42        self.graph = graph;
43        self.update_error(error);
44    }
45
46    fn update_error(&mut self, error: Option<String>) {
47        self.error = error.map(|e| {
48            CString::new(
49                e.chars()
50                    .filter(|c| c.is_ascii() && c != &'"')
51                    .collect::<String>(),
52            )
53            .unwrap()
54        });
55    }
56}
57
58/// Allocate an [`OpaqueForwardGraph`]
59///
60/// # Panics
61///
62/// This function will panic if the pointer allocation fails.
63///
64/// # Safety
65///
66/// The pointer is returned by leaking a [`Box`].
67/// The pointer is managed by rust and is freed by [`forward_graph_deallocate`].
68#[no_mangle]
69pub extern "C" fn forward_graph_allocate() -> *mut OpaqueForwardGraph {
70    Box::into_raw(Box::new(OpaqueForwardGraph {
71        graph: None,
72        error: None,
73        current_time: None,
74        deny_send_sync: std::marker::PhantomData,
75    }))
76}
77
78unsafe fn yaml_to_owned(yaml: *const c_char) -> Option<String> {
79    if yaml.is_null() {
80        return None;
81    }
82    let yaml = CStr::from_ptr(yaml);
83    match yaml.to_owned().to_str() {
84        Ok(s) => Some(s.to_owned()),
85        Err(_) => None,
86    }
87}
88
89unsafe fn process_input_yaml(
90    yaml: *const c_char,
91    graph: *mut OpaqueForwardGraph,
92) -> (i32, Option<demes::Graph>) {
93    if graph.is_null() {
94        return (ErrorCode::GraphIsNull as i32, None);
95    }
96    let yaml = match yaml_to_owned(yaml) {
97        Some(s) => s,
98        None => {
99            (*graph).update(None, Some("could not convert c_char to String".to_string()));
100            return (-1, None);
101        }
102    };
103    match demes::loads(&yaml) {
104        Ok(graph) => (0, Some(graph)),
105        Err(e) => {
106            (*graph).update(None, Some(format!("{e}")));
107            (-1, None)
108        }
109    }
110}
111
112/// Initialize a discrete-time model from a yaml file
113///
114/// # Safety
115///
116/// * `yaml` must be a valid pointer containing valid utf8 data.
117/// * `graph` must be a valid pointer to OpaqueForwardGraph.
118#[no_mangle]
119pub unsafe extern "C" fn forward_graph_initialize_from_yaml(
120    yaml: *const c_char,
121    burnin: f64,
122    graph: *mut OpaqueForwardGraph,
123) -> i32 {
124    let dg = match process_input_yaml(yaml, graph) {
125        (x, Some(g)) => {
126            assert_eq!(x, 0);
127            g
128        }
129        (y, None) => {
130            assert!(y < 0);
131            return -1;
132        }
133    };
134
135    match demes_forward::ForwardGraph::new_discrete_time(dg, burnin) {
136        Ok(fgraph) => (*graph).update(Some(fgraph), None),
137        Err(e) => (*graph).update(None, Some(format!("{e}"))),
138    };
139    0
140}
141
142/// Initialize and round epoch start/end sizes.
143///
144/// # Errors
145///
146/// If any epoch start/end sizes round to zero.
147///
148/// # Safety
149///
150/// * `yaml` must be a valid pointer containing valid utf8 data.
151/// * `graph` must be a valid pointer to OpaqueForwardGraph.
152#[no_mangle]
153pub unsafe extern "C" fn forward_graph_initialize_from_yaml_round_epoch_sizes(
154    yaml: *const c_char,
155    burnin: f64,
156    graph: *mut OpaqueForwardGraph,
157) -> i32 {
158    let dg = match process_input_yaml(yaml, graph) {
159        (x, Some(g)) => {
160            assert_eq!(x, 0);
161            g
162        }
163        (y, None) => {
164            assert!(y < 0);
165            return -1;
166        }
167    };
168    let dg = match dg.into_integer_start_end_sizes() {
169        Ok(graph) => graph,
170        Err(e) => {
171            (*graph).update(None, Some(format!("{e}")));
172            return -1;
173        }
174    };
175    match demes_forward::ForwardGraph::new_discrete_time(dg, burnin) {
176        Ok(fgraph) => (*graph).update(Some(fgraph), None),
177        Err(e) => (*graph).update(None, Some(format!("{e}"))),
178    };
179    0
180}
181
182/// # Safety
183///
184/// * `file_name` must be a non-NULL pointer to valid utf8.
185/// * `graph` must be a valid pointer to an [`OpaqueForwardGraph`].
186#[no_mangle]
187pub unsafe extern "C" fn forward_graph_initialize_from_yaml_file(
188    file_name: *const c_char,
189    burnin: f64,
190    graph: *mut OpaqueForwardGraph,
191) -> i32 {
192    if graph.is_null() {
193        return ErrorCode::GraphIsNull as i32;
194    }
195    let filename_cstr = CStr::from_ptr(file_name);
196    let filename = match filename_cstr.to_str() {
197        Ok(string) => string,
198        Err(e) => {
199            (*graph).update(None, Some(format!("{e}")));
200            return -1;
201        }
202    };
203    match std::fs::File::open(filename) {
204        Ok(mut file) => {
205            let mut buf = String::default();
206            match file.read_to_string(&mut buf) {
207                Ok(_) => {
208                    let cstring = CString::new(buf).unwrap();
209                    let ptr = cstring.as_ptr();
210                    forward_graph_initialize_from_yaml(ptr, burnin, graph)
211                }
212                Err(e) => {
213                    (*graph).update(None, Some(format!("{e}")));
214                    -1
215                }
216            }
217        }
218        Err(e) => {
219            (*graph).update(None, Some(format!("{e}")));
220            -1
221        }
222    }
223}
224
225/// # Safety
226///
227/// `graph` must be a valid pointer
228#[no_mangle]
229pub unsafe extern "C" fn forward_graph_is_error_state(graph: *const OpaqueForwardGraph) -> bool {
230    (*graph).error.is_some()
231}
232
233/// # Safety
234///
235/// `graph` must be a valid pointer
236#[no_mangle]
237pub unsafe extern "C" fn forward_graph_deallocate(graph: *mut OpaqueForwardGraph) {
238    let _ = Box::from_raw(graph);
239}
240
241/// # Safety
242///
243/// `graph` must be a valid pointer
244#[no_mangle]
245pub unsafe extern "C" fn forward_graph_get_error_message(
246    graph: *const OpaqueForwardGraph,
247    status: *mut i32,
248) -> *const c_char {
249    *status = 0;
250    if !graph.is_null() {
251        match &(*graph).error {
252            Some(message) => message.as_ptr(),
253            None => std::ptr::null(),
254        }
255    } else {
256        *status = ErrorCode::GraphIsNull as i32;
257        std::ptr::null()
258    }
259}
260
261/// Pointer to first element of selfing rates array.
262///
263/// The length of the array is equal to [`forward_graph_number_of_demes`].
264///
265/// # Safety
266///
267/// `graph` must be a valid pointer
268#[no_mangle]
269pub unsafe extern "C" fn forward_graph_selfing_rates(
270    graph: *const OpaqueForwardGraph,
271    status: *mut i32,
272) -> *const f64 {
273    *status = 0;
274    if !graph.is_null() {
275        match &(*graph).graph {
276            Some(graph) => match graph.selfing_rates() {
277                Some(slice) => slice.as_ptr() as *const f64,
278                None => std::ptr::null(),
279            },
280            None => {
281                *status = -1;
282                std::ptr::null()
283            }
284        }
285    } else {
286        *status = ErrorCode::GraphIsNull as i32;
287        std::ptr::null()
288    }
289}
290
291/// Pointer to first element of cloning rates array.
292///
293/// The length of the array is equal to [`forward_graph_number_of_demes`].
294///
295/// # Safety
296///
297/// `graph` must be a valid pointer
298#[no_mangle]
299pub unsafe extern "C" fn forward_graph_cloning_rates(
300    graph: *const OpaqueForwardGraph,
301    status: *mut i32,
302) -> *const f64 {
303    *status = 0;
304    if !graph.is_null() {
305        match &(*graph).graph {
306            Some(graph) => match graph.cloning_rates() {
307                Some(slice) => slice.as_ptr() as *const f64,
308                None => std::ptr::null(),
309            },
310            None => {
311                *status = -1;
312                std::ptr::null()
313            }
314        }
315    } else {
316        *status = ErrorCode::GraphIsNull as i32;
317        std::ptr::null()
318    }
319}
320
321/// Return a pointer to the first element of parental deme size array.
322///
323/// The length of the array is equal to [`forward_graph_number_of_demes`].
324///
325/// # Safety
326///
327/// `graph` must be a valid pointer
328#[no_mangle]
329pub unsafe extern "C" fn forward_graph_parental_deme_sizes(
330    graph: *const OpaqueForwardGraph,
331    status: *mut i32,
332) -> *const f64 {
333    *status = 0;
334    if !graph.is_null() {
335        match &(*graph).graph {
336            Some(graph) => match graph.parental_deme_sizes() {
337                Some(slice) => slice.as_ptr() as *const f64,
338                None => std::ptr::null(),
339            },
340            None => {
341                *status = -1;
342                std::ptr::null()
343            }
344        }
345    } else {
346        *status = ErrorCode::GraphIsNull as i32;
347        std::ptr::null()
348    }
349}
350
351/// Return a pointer to the first element of offspring deme size array.
352///
353/// The length of the array is equal to [`forward_graph_number_of_demes`].
354///
355/// # Safety
356///
357/// `graph` must be a valid pointer
358#[no_mangle]
359pub unsafe extern "C" fn forward_graph_offspring_deme_sizes(
360    graph: *const OpaqueForwardGraph,
361    status: *mut i32,
362) -> *const f64 {
363    *status = 0;
364    if !graph.is_null() {
365        match &(*graph).graph {
366            Some(graph) => match graph.offspring_deme_sizes() {
367                Some(slice) => slice.as_ptr() as *const f64,
368                None => std::ptr::null(),
369            },
370            None => {
371                *status = -1;
372                std::ptr::null()
373            }
374        }
375    } else {
376        *status = ErrorCode::GraphIsNull as i32;
377        std::ptr::null()
378    }
379}
380
381/// Check if there are any extant offspring demes.
382///
383/// # Safety
384///
385/// `graph` must be a valid pointer
386#[no_mangle]
387pub unsafe extern "C" fn forward_graph_any_extant_offspring_demes(
388    graph: *const OpaqueForwardGraph,
389    status: *mut i32,
390) -> bool {
391    *status = 0;
392    if !graph.is_null() {
393        match &(*graph).graph {
394            Some(graph) => graph.any_extant_offspring_demes(),
395            None => {
396                *status = -1;
397                false
398            }
399        }
400    } else {
401        *status = ErrorCode::GraphIsNull as i32;
402        false
403    }
404}
405
406/// Check if there are any extant parental demes.
407///
408/// # Safety
409///
410/// `graph` must be a valid pointer
411#[no_mangle]
412pub unsafe extern "C" fn forward_graph_any_extant_parent_demes(
413    graph: *const OpaqueForwardGraph,
414    status: *mut i32,
415) -> bool {
416    *status = 0;
417    if !graph.is_null() {
418        match &(*graph).graph {
419            Some(graph) => graph.any_extant_parental_demes(),
420            None => {
421                *status = -1;
422                false
423            }
424        }
425    } else {
426        *status = ErrorCode::GraphIsNull as i32;
427        false
428    }
429}
430
431/// Get the total number of demes in the model
432///
433/// # Returns
434///
435/// [`isize`] > 0 if the graph is not in an error state.
436/// Returns `-1` otherwise.
437///
438/// # Safety
439///
440/// `graph` must be a valid pointer
441#[no_mangle]
442pub unsafe extern "C" fn forward_graph_number_of_demes(graph: *const OpaqueForwardGraph) -> isize {
443    match &(*graph).graph {
444        Some(graph) => graph.num_demes_in_model() as isize,
445        None => -1,
446    }
447}
448
449/// Update the model state to a given time.
450///
451/// # Safety
452///
453/// `graph` must be a valid pointer
454#[no_mangle]
455pub unsafe extern "C" fn forward_graph_update_state(
456    time: f64,
457    graph: *mut OpaqueForwardGraph,
458) -> i32 {
459    if !graph.is_null() {
460        match &mut (*graph).graph {
461            Some(fgraph) => match fgraph.update_state(time) {
462                Ok(()) => 0,
463                Err(e) => {
464                    (*graph).update(None, Some(format!("{e}")));
465                    -1
466                }
467            },
468            None => -1,
469        }
470    } else {
471        ErrorCode::GraphIsNull as i32
472    }
473}
474
475/// Initialize graph to begin iterating over model.
476///
477/// # Safety
478///
479/// `graph` must be a valid pointer
480#[no_mangle]
481pub unsafe extern "C" fn forward_graph_initialize_time_iteration(
482    graph: *mut OpaqueForwardGraph,
483) -> i32 {
484    if !graph.is_null() {
485        match &mut (*graph).graph {
486            Some(fgraph) => {
487                match fgraph.last_time_updated() {
488                    Some(value) => {
489                        (*graph).current_time = Some(value.value() - 1.0);
490                    }
491                    None => {
492                        (*graph).current_time = Some(-1.0);
493                    }
494                }
495                0
496            }
497            None => -1,
498        }
499    } else {
500        ErrorCode::GraphIsNull as i32
501    }
502}
503
504/// Iterate to the next time point in the model.
505///
506/// # Return values:
507///
508/// * null = done iterating
509/// * not null = still iterating
510///
511/// # Safety
512///
513/// `graph` must be a valid pointer
514#[no_mangle]
515pub unsafe extern "C" fn forward_graph_iterate_time(
516    graph: *mut OpaqueForwardGraph,
517    status: *mut i32,
518) -> *const f64 {
519    if graph.is_null() {
520        *status = ErrorCode::GraphIsNull as i32;
521        return std::ptr::null();
522    }
523    *status = 0;
524    if (*graph).current_time.is_none() {
525        *status = -1;
526        (*graph).update_error(Some(
527            "forward_graph_initialize_time_iteration has not been called".to_string(),
528        ));
529        return std::ptr::null();
530    }
531    let tref: &mut f64 = (*graph).current_time.as_mut().unwrap();
532    match &mut (*graph).graph {
533        Some(fgraph) => {
534            if *tref < fgraph.end_time().value() - 1.0 {
535                *tref += 1.0;
536                &*tref
537            } else {
538                (*graph).current_time = None;
539                std::ptr::null()
540            }
541        }
542        None => {
543            *status = -1;
544            std::ptr::null()
545        }
546    }
547}
548
549/// # Safety
550///
551/// `graph` must be a valid pointer to an [`OpaqueForwardGraph`].
552/// `status` must be a valid pointer to an `i32`.
553#[no_mangle]
554pub unsafe extern "C" fn forward_graph_ancestry_proportions(
555    offspring_deme: usize,
556    status: *mut i32,
557    graph: *mut OpaqueForwardGraph,
558) -> *const f64 {
559    if graph.is_null() {
560        *status = ErrorCode::GraphIsNull as i32;
561        return std::ptr::null();
562    }
563    *status = 0;
564    if (*graph).error.is_some() {
565        *status = -1;
566        return std::ptr::null();
567    }
568    match &(*graph).graph {
569        Some(fgraph) => {
570            if offspring_deme >= fgraph.num_demes_in_model() {
571                *status = -1;
572                (*graph).update_error(Some(format!(
573                    "offspring deme index {} out of range",
574                    offspring_deme
575                )));
576                std::ptr::null()
577            } else {
578                match fgraph.ancestry_proportions(offspring_deme) {
579                    Some(proportions) => proportions.as_ptr(),
580                    None => std::ptr::null(),
581                }
582            }
583        }
584        None => {
585            *status = -1;
586            std::ptr::null()
587        }
588    }
589}
590
591/// Get the model end time.
592///
593/// The value returned is one generation after the
594/// last parental generation.
595/// Thus, this value defines a half-open interval
596/// during which parental demes exist.
597///
598/// # Safety
599///
600/// `graph` must be a valid pointer to an [`OpaqueForwardGraph`].
601/// `status` must be a valid pointer to an `i32`.
602#[no_mangle]
603pub unsafe extern "C" fn forward_graph_model_end_time(
604    status: *mut i32,
605    graph: *const OpaqueForwardGraph,
606) -> f64 {
607    *status = 0;
608    if (*graph).error.is_some() || (*graph).graph.is_none() {
609        *status = -1;
610        f64::NAN
611    } else {
612        match &(*graph).graph {
613            Some(fgraph) => fgraph.end_time().value(),
614            None => {
615                *status = -1;
616                f64::NAN
617            }
618        }
619    }
620}
621
622/// Get the underlying [`demes::Graph`].
623///
624/// # Returns
625///
626/// * A new `libc::c_char` representation of the graph upon success.
627/// * `std::ptr::null` upon error.
628///
629/// # Side effects
630///
631/// * An error will set `status` to -1.
632/// * Success will set `status` to 0.
633///
634/// # Safety
635///
636/// `graph` must be a valid pointer to an [`OpaqueForwardGraph`].
637/// `status` must be a valid pointer to an `i32`.
638///
639/// # Note
640///
641/// If not NULL, the return value must be freed in order to avoid
642/// leaking memory.
643#[no_mangle]
644pub unsafe extern "C" fn forward_graph_get_demes_graph(
645    graph: *const OpaqueForwardGraph,
646    status: *mut i32,
647) -> *const c_char {
648    match &(*graph).graph {
649        None => std::ptr::null(),
650        Some(g) => {
651            let demes_graph = match g.demes_graph().as_string() {
652                Ok(g) => g,
653                Err(_) => {
654                    *status = -1;
655                    return std::ptr::null();
656                }
657            };
658            let c_str = match CString::new(demes_graph) {
659                Ok(c) => c,
660                Err(_) => {
661                    *status = -1;
662                    return std::ptr::null();
663                }
664            };
665            *status = 0;
666            libc::strdup(c_str.as_ptr()) as *const c_char
667        }
668    }
669}
670
671#[cfg(test)]
672mod tests {
673    use super::*;
674    use std::{ffi::CString, io::Write};
675
676    struct GraphHolder {
677        graph: *mut OpaqueForwardGraph,
678    }
679
680    impl GraphHolder {
681        fn new() -> Self {
682            Self {
683                graph: forward_graph_allocate(),
684            }
685        }
686
687        fn as_mut_ptr(&mut self) -> *mut OpaqueForwardGraph {
688            self.graph
689        }
690
691        fn as_ptr(&mut self) -> *const OpaqueForwardGraph {
692            self.graph
693        }
694
695        fn init_with_yaml(&mut self, burnin: f64, yaml: &str) -> i32 {
696            let yaml_cstr = CString::new(yaml).unwrap();
697            let yaml_c_char: *const c_char = yaml_cstr.as_ptr() as *const c_char;
698            unsafe { forward_graph_initialize_from_yaml(yaml_c_char, burnin, self.as_mut_ptr()) }
699        }
700
701        fn init_with_yaml_round_epoch_sizes(&mut self, burnin: f64, yaml: &str) -> i32 {
702            let yaml_cstr = CString::new(yaml).unwrap();
703            let yaml_c_char: *const c_char = yaml_cstr.as_ptr() as *const c_char;
704            unsafe {
705                forward_graph_initialize_from_yaml_round_epoch_sizes(
706                    yaml_c_char,
707                    burnin,
708                    self.as_mut_ptr(),
709                )
710            }
711        }
712    }
713
714    impl Drop for GraphHolder {
715        fn drop(&mut self) {
716            unsafe { forward_graph_deallocate(self.as_mut_ptr()) };
717        }
718    }
719
720    #[test]
721    fn test_invalid_graph() {
722        let yaml = "
723time_units: generations
724demes:
725 - name: A
726   start_time: 55
727   epochs:
728   - start_size: 100
729     end_time: 50
730   - start_size: 200
731";
732        let mut graph = GraphHolder::new();
733        graph.init_with_yaml(100.0, yaml);
734        assert!(unsafe { forward_graph_is_error_state(graph.as_ptr()) });
735        let mut status = -1;
736        let pstatus: *mut i32 = &mut status;
737        let message = unsafe { forward_graph_get_error_message(graph.as_ptr(), pstatus) };
738        assert_eq!(status, 0);
739        assert!(!message.is_null());
740        let rust_message = unsafe { CStr::from_ptr(message) };
741        let rust_message: &str = rust_message.to_str().unwrap();
742        assert_eq!(
743            rust_message,
744            "deme A has finite start time but no ancestors"
745        );
746    }
747
748    #[test]
749    fn test_empty_graph() {
750        let yaml = "";
751        let mut graph = GraphHolder::new();
752        graph.init_with_yaml(100.0, yaml);
753        assert!(unsafe { forward_graph_is_error_state(graph.as_ptr()) });
754    }
755
756    #[test]
757    fn test_null_graph() {
758        let yaml: *const c_char = std::ptr::null();
759        let graph = forward_graph_allocate();
760        unsafe { forward_graph_initialize_from_yaml(yaml, 100.0, graph) };
761        assert!(unsafe { forward_graph_is_error_state(graph) });
762        unsafe { forward_graph_deallocate(graph) };
763    }
764
765    #[test]
766    fn number_of_demes_in_model() {
767        {
768            let yaml = "
769time_units: generations
770demes:
771 - name: A
772   epochs:
773   - start_size: 100
774     end_time: 50
775   - start_size: 200
776";
777            {
778                let mut graph = GraphHolder::new();
779                graph.init_with_yaml(100.0, yaml);
780                let num_demes = unsafe { forward_graph_number_of_demes(graph.as_ptr()) };
781                assert_eq!(num_demes, 1);
782            }
783
784            // Handles the complications of rust str vs char *
785            {
786                let graph = forward_graph_allocate();
787                let cstr = CString::new(yaml).unwrap();
788                unsafe { forward_graph_initialize_from_yaml(cstr.as_ptr(), 100., graph) };
789                let num_demes = unsafe { forward_graph_number_of_demes(graph) };
790                assert_eq!(num_demes, 1);
791                unsafe { forward_graph_deallocate(graph) };
792            }
793        }
794    }
795
796    #[test]
797    fn iterate_simple_model() {
798        let yaml = "
799time_units: generations
800demes:
801 - name: A
802   epochs:
803   - start_size: 100
804     end_time: 50
805   - start_size: 200
806";
807        let mut graph = GraphHolder::new();
808        graph.init_with_yaml(100.0, yaml);
809        let mut status = -1;
810        assert!(unsafe { forward_graph_selfing_rates(graph.as_ptr(), &mut status) }.is_null());
811        assert_eq!(status, 0);
812        status = -1;
813        assert!(unsafe { forward_graph_cloning_rates(graph.as_ptr(), &mut status) }.is_null());
814        assert_eq!(status, 0);
815        status = -1;
816        assert!(
817            unsafe { forward_graph_parental_deme_sizes(graph.as_ptr(), &mut status) }.is_null(),
818        );
819        assert_eq!(status, 0);
820        status = -1;
821        assert!(
822            unsafe { forward_graph_offspring_deme_sizes(graph.as_ptr(), &mut status) }.is_null(),
823        );
824        assert_eq!(status, 0);
825        status = -1;
826        assert!(!unsafe { forward_graph_any_extant_offspring_demes(graph.as_ptr(), &mut status) });
827        assert_eq!(status, 0);
828        status = -1;
829        assert!(!unsafe { forward_graph_any_extant_parent_demes(graph.as_ptr(), &mut status) });
830        assert_eq!(status, 0);
831
832        {
833            assert_eq!(
834                unsafe { forward_graph_initialize_time_iteration(graph.as_mut_ptr()) },
835                0,
836            );
837            let mut ngens = -1_i32;
838            let mut ptime: *const f64;
839            let mut ancestry_proportions: *const f64;
840            let mut times = vec![];
841            let mut sizes = vec![100.0; 100];
842            sizes.append(&mut vec![200.0; 50]);
843
844            let mut status = -1;
845            let pstatus: *mut i32 = &mut status;
846            ptime = unsafe { forward_graph_iterate_time(graph.as_mut_ptr(), pstatus) };
847            assert_eq!(
848                unsafe { forward_graph_model_end_time(pstatus, graph.as_ptr()) },
849                151.0
850            );
851            assert_eq!(status, 0);
852            while !ptime.is_null() {
853                assert_eq!(status, 0);
854                ngens += 1;
855                unsafe { times.push(*ptime) };
856                assert_eq!(
857                    unsafe { forward_graph_update_state(*ptime, graph.as_mut_ptr()) },
858                    0,
859                );
860                let mut status = -1;
861                if unsafe { forward_graph_any_extant_offspring_demes(graph.as_ptr(), &mut status) }
862                {
863                    assert_eq!(status, 0);
864                    let offspring_deme_sizes =
865                        unsafe { forward_graph_offspring_deme_sizes(graph.as_ptr(), &mut status) };
866                    assert_eq!(status, 0);
867                    assert!(!offspring_deme_sizes.is_null());
868                    ancestry_proportions = unsafe {
869                        forward_graph_ancestry_proportions(0, &mut status, graph.as_mut_ptr())
870                    };
871                    assert_eq!(status, 0);
872                    let ancestry_proportions =
873                        unsafe { std::slice::from_raw_parts(ancestry_proportions, 1) };
874                    assert!((ancestry_proportions[0] - 1.0) <= 1e-9);
875                    let deme_sizes = unsafe { std::slice::from_raw_parts(offspring_deme_sizes, 1) };
876                    assert_eq!(deme_sizes[0], sizes[ngens as usize]);
877                } else {
878                    status = -1;
879                    let offspring_deme_sizes =
880                        unsafe { forward_graph_offspring_deme_sizes(graph.as_ptr(), &mut status) };
881                    assert_eq!(status, 0);
882                    assert!(offspring_deme_sizes.is_null());
883                }
884                ptime = unsafe { forward_graph_iterate_time(graph.as_mut_ptr(), &mut status) };
885            }
886            assert!(ptime.is_null());
887            assert_eq!(times.first().unwrap(), &0.0);
888            assert_eq!(times.last().unwrap(), &150.0);
889            assert_eq!(ngens, 150);
890        }
891
892        // Now, start from time of 50
893        {
894            assert_eq!(
895                unsafe { forward_graph_update_state(50.0, graph.as_mut_ptr()) },
896                0,
897            );
898            assert_eq!(
899                unsafe { forward_graph_initialize_time_iteration(graph.as_mut_ptr()) },
900                0,
901            );
902            let mut ngens = -1_i32;
903            let mut ptime: *const f64;
904            let mut times = vec![];
905            let mut sizes = vec![100.0; 50];
906            sizes.append(&mut vec![200.0; 50]);
907
908            let mut status = -1;
909            ptime = unsafe { forward_graph_iterate_time(graph.as_mut_ptr(), &mut status) };
910            while !ptime.is_null() {
911                assert_eq!(status, 0);
912                ngens += 1;
913                unsafe { times.push(*ptime) };
914                assert_eq!(
915                    unsafe { forward_graph_update_state(*ptime, graph.as_mut_ptr()) },
916                    0,
917                );
918                let mut status = -1;
919                if unsafe { forward_graph_any_extant_offspring_demes(graph.as_ptr(), &mut status) }
920                {
921                    assert_eq!(status, 0);
922                    let offspring_deme_sizes =
923                        unsafe { forward_graph_offspring_deme_sizes(graph.as_ptr(), &mut status) };
924                    assert_eq!(status, 0);
925                    assert!(!offspring_deme_sizes.is_null());
926                    let deme_sizes = unsafe { std::slice::from_raw_parts(offspring_deme_sizes, 1) };
927                    assert_eq!(deme_sizes[0], sizes[ngens as usize]);
928                } else {
929                    status = -1;
930                    let offspring_deme_sizes =
931                        unsafe { forward_graph_offspring_deme_sizes(graph.as_ptr(), &mut status) };
932                    assert_eq!(status, 0);
933                    assert!(offspring_deme_sizes.is_null());
934                }
935                ptime = unsafe { forward_graph_iterate_time(graph.as_mut_ptr(), &mut status) };
936            }
937            assert!(ptime.is_null());
938            assert_eq!(times.first().unwrap(), &50.0);
939            assert_eq!(times.last().unwrap(), &150.0);
940            assert_eq!(ngens, 100);
941        }
942    }
943
944    #[test]
945    fn test_from_yaml_file() {
946        let yaml = "
947time_units: generations
948demes:
949 - name: A
950   epochs:
951   - start_size: 100
952     end_time: 50
953   - start_size: 200
954";
955        {
956            let mut file = std::fs::File::create("simple_model.yaml").unwrap();
957            file.write_all(yaml.as_bytes()).unwrap();
958        }
959
960        let mut graph = GraphHolder::new();
961
962        let filename = "simple_model.yaml";
963        let filename_cstring = CString::new(filename).unwrap();
964        let filename: *const c_char = filename_cstring.as_ptr() as *const c_char;
965        assert_eq!(
966            unsafe { forward_graph_initialize_from_yaml_file(filename, 100.0, graph.as_mut_ptr()) },
967            0
968        );
969        let mut status = -1;
970        let pstatus: *mut i32 = &mut status;
971
972        assert_eq!(
973            unsafe { forward_graph_model_end_time(pstatus, graph.as_mut_ptr()) },
974            151.0
975        );
976
977        std::fs::remove_file("simple_model.yaml").unwrap();
978    }
979
980    #[test]
981    fn test_recover_demes_graph() {
982        let yaml = "
983time_units: generations
984demes:
985 - name: A
986   epochs:
987   - start_size: 100
988     end_time: 50
989   - start_size: 200
990";
991        let mut graph = GraphHolder::new();
992        assert_eq!(graph.init_with_yaml(0.0, yaml), 0);
993        let mut status = 0;
994        let demes_graph =
995            unsafe { forward_graph_get_demes_graph(graph.as_ptr(), &mut status) } as *const c_char;
996        assert!(!demes_graph.is_null());
997        let mut new_graph = GraphHolder::new();
998        let temp = unsafe { yaml_to_owned(demes_graph) }.unwrap();
999        assert_eq!(new_graph.init_with_yaml(0.0, &temp), 0);
1000        unsafe { libc::free(demes_graph as *mut libc::c_void) };
1001        assert_eq!(
1002            unsafe { &(*graph.graph) }
1003                .graph
1004                .as_ref()
1005                .unwrap()
1006                .demes_graph(),
1007            unsafe { &(*new_graph.graph) }
1008                .graph
1009                .as_ref()
1010                .unwrap()
1011                .demes_graph()
1012        )
1013    }
1014
1015    #[test]
1016    fn test_zero_length_model() {
1017        let yaml = "
1018time_units: generations
1019demes:
1020 - name: A
1021   epochs:
1022   - start_size: 100
1023";
1024        let mut graph = GraphHolder::new();
1025        assert_eq!(graph.init_with_yaml(0.0, yaml), 0);
1026        assert!(!unsafe { forward_graph_is_error_state(graph.as_ptr()) });
1027        assert_eq!(
1028            unsafe { forward_graph_initialize_time_iteration(graph.as_mut_ptr()) },
1029            0,
1030        );
1031        let mut ptime: *const f64;
1032        let mut status: i32 = -1;
1033        ptime = unsafe { forward_graph_iterate_time(graph.as_mut_ptr(), &mut status) };
1034        let mut ngens = 0;
1035        while !ptime.is_null() {
1036            assert_eq!(status, 0);
1037            ngens += 1;
1038            ptime = unsafe { forward_graph_iterate_time(graph.as_mut_ptr(), &mut status) };
1039        }
1040        // This is a subtle point:
1041        // 1. We iterate over parent generation 0.
1042        // 2. We see that there are no children.
1043        // 3. So no sampling happens.
1044        assert_eq!(ngens, 1);
1045
1046        // How to use the API more correctly vis-a-vis the demes spec
1047
1048        // 1. Reset things
1049        assert_eq!(
1050            unsafe { forward_graph_update_state(0.0, graph.as_mut_ptr()) },
1051            0
1052        );
1053        assert_eq!(
1054            unsafe { forward_graph_initialize_time_iteration(graph.as_mut_ptr()) },
1055            0,
1056        );
1057        ngens = 0;
1058
1059        // 2. Iterate
1060        let _ptime = unsafe { forward_graph_iterate_time(graph.as_mut_ptr(), &mut status) };
1061
1062        // Only continue iteration while there are offspring demes.
1063        while unsafe { forward_graph_any_extant_offspring_demes(graph.as_ptr(), &mut status) } {
1064            ngens += 1;
1065            let _ptime = unsafe { forward_graph_iterate_time(graph.as_mut_ptr(), &mut status) };
1066            unsafe { forward_graph_update_state(*_ptime, graph.as_mut_ptr()) };
1067        }
1068        assert_eq!(ngens, 0);
1069    }
1070
1071    #[test]
1072    fn test_iteration_with_burnin() {
1073        let yaml = "
1074time_units: generations
1075demes:
1076 - name: A
1077   epochs:
1078   - start_size: 100
1079";
1080        for start_time in [0.0, 5.0, 10.0] {
1081            let mut graph = GraphHolder::new();
1082            assert_eq!(graph.init_with_yaml(10.0, yaml), 0);
1083            assert!(!unsafe { forward_graph_is_error_state(graph.as_ptr()) });
1084            let mut status: i32 = 0;
1085            let mut ngens = 0;
1086
1087            // We must first initialize the internal state
1088            // to our starting time.
1089            assert_eq!(
1090                unsafe { forward_graph_update_state(start_time, graph.as_mut_ptr()) },
1091                0
1092            );
1093
1094            // Cannot call this until AFTER first call to update state
1095            assert_eq!(
1096                unsafe { forward_graph_initialize_time_iteration(graph.as_mut_ptr()) },
1097                0,
1098            );
1099            assert_eq!(
1100                unsafe { forward_graph_model_end_time(&mut status, graph.as_ptr()) },
1101                11.0
1102            );
1103            // Iterator time starts at "next time - 1", so we need to advance
1104            let _ptime = unsafe { forward_graph_iterate_time(graph.as_mut_ptr(), &mut status) };
1105            println!("ptime starts at {}", unsafe { *_ptime });
1106            assert_eq!(status, 0);
1107
1108            // We iterate over PARENTAL generation times,
1109            // and only have work to do if any OFFSPRING demes
1110            // exist
1111            while unsafe { forward_graph_any_extant_offspring_demes(graph.as_ptr(), &mut status) } {
1112                assert_eq!(status, 0);
1113
1114                assert!(!unsafe {
1115                    forward_graph_parental_deme_sizes(graph.as_ptr(), &mut status).is_null()
1116                });
1117                assert!(!unsafe {
1118                    forward_graph_offspring_deme_sizes(graph.as_ptr(), &mut status).is_null()
1119                });
1120
1121                // Advance time to next PARENTAL generation
1122                let _ptime = unsafe { forward_graph_iterate_time(graph.as_mut_ptr(), &mut status) };
1123                // Update model internal state accordingly
1124                assert_eq!(
1125                    unsafe { forward_graph_update_state(*_ptime, graph.as_mut_ptr()) },
1126                    0
1127                );
1128                ngens += 1;
1129            }
1130            assert_eq!(ngens, (10.0 - start_time) as i32);
1131        }
1132    }
1133
1134    #[test]
1135    fn test_model_with_bad_time_rounding() {
1136        let yaml = "
1137time_units: generations
1138demes:
1139- name: bad
1140  epochs:
1141  - {end_time: 1.5, start_size: 1}
1142  - {end_time: 0.4, start_size: 2}
1143  - {end_time: 0, start_size: 3}
1144";
1145        let mut graph = GraphHolder::new();
1146        assert_eq!(graph.init_with_yaml(10.0, yaml), 0);
1147        let x = graph.as_ptr();
1148        assert!(unsafe { forward_graph_is_error_state(x) });
1149    }
1150
1151    #[test]
1152    fn test_non_integer_sizes_with_and_without_rounding() {
1153        let yaml = "
1154time_units: generations
1155demes:
1156- name: deme1
1157  start_time: .inf
1158  epochs:
1159  - {end_size: 99.99000049998334, end_time: 8000.0, start_size: 99.99000049998334}
1160  - {end_size: 100.0, end_time: 4000.0, start_size: 99.99000049998334}
1161  - {end_size: 100, end_time: 0, start_size: 100.0}
1162migrations: []
1163";
1164        {
1165            let mut graph = GraphHolder::new();
1166            assert_eq!(graph.init_with_yaml(10.0, yaml), 0);
1167            let x = graph.as_ptr();
1168            assert!(unsafe { forward_graph_is_error_state(x) });
1169        }
1170        {
1171            let mut graph = GraphHolder::new();
1172            assert_eq!(graph.init_with_yaml_round_epoch_sizes(10.0, yaml), 0);
1173            let x = graph.as_ptr();
1174            assert!(!unsafe { forward_graph_is_error_state(x) });
1175        }
1176    }
1177}