demes/
proportion.rs

1use crate::error::DemesError;
2use serde::{Deserialize, Serialize};
3
4/// An ancestry proportion.
5///
6/// This is a newtype wrapper for [`f64`](std::primitive::f64).
7///
8/// # Interpretation
9///
10/// With respect to a deme in an *offspring* time step,
11/// a proportion is the fraction of ancsestry from a given
12/// parental deme.
13///
14/// # Examples
15///
16/// ## In `YAML` input
17///
18/// ### Ancestral proportions of demes
19///
20/// ```
21/// let yaml = "
22/// time_units: generations
23/// description:
24///   An admixed deme appears 100 generations ago.
25///   Its initial ancestry is 90% from ancestor1
26///   and 10% from ancestor2.
27/// demes:
28///  - name: ancestor1
29///    epochs:
30///     - start_size: 50
31///       end_time: 100
32///  - name: ancestor2
33///    epochs:
34///     - start_size: 50
35///       end_time: 100
36///  - name: admixed
37///    ancestors: [ancestor1, ancestor2]
38///    proportions: [0.9, 0.1]
39///    start_time: 100
40///    epochs:
41///     - start_size: 200
42/// ";
43/// demes::loads(yaml).unwrap();
44/// ```
45///
46/// ### Pulse proportions
47///
48/// ```
49/// let yaml = "
50/// time_units: generations
51/// description:
52///    Two demes coexist without migration.
53///    Sixty three (63) generations ago,
54///    deme1 contributes 50% of ancestry
55///    to all individuals born in deme2.
56/// demes:
57///  - name: deme1
58///    epochs:
59///     - start_size: 50
60///  - name: deme2
61///    epochs:
62///     - start_size: 50
63/// pulses:
64///  - sources: [deme1]
65///    dest: deme2
66///    proportions: [0.5]
67///    time: 63
68/// ";
69/// demes::loads(yaml).unwrap();
70/// ```
71///
72/// ## Using rust code
73///
74/// ```
75/// let t = demes::Proportion::try_from(0.5).unwrap();
76/// assert_eq!(t, 0.5);
77/// ```
78#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
79#[repr(transparent)]
80#[serde(try_from = "f64")]
81pub struct Proportion(f64);
82
83impl Proportion {
84    fn validate<F>(&self, f: F) -> Result<(), DemesError>
85    where
86        F: std::ops::FnOnce(String) -> DemesError,
87    {
88        if !self.0.is_finite() || self.0 <= 0.0 || self.0 > 1.0 {
89            let msg = format!("proportions must be 0.0 < p <= 1.0, got: {}", self.0);
90            Err(f(msg))
91        } else {
92            Ok(())
93        }
94    }
95}
96
97impl TryFrom<f64> for Proportion {
98    type Error = DemesError;
99    fn try_from(value: f64) -> Result<Self, Self::Error> {
100        let rv = Self(value);
101        rv.validate(DemesError::ValueError)?;
102        Ok(rv)
103    }
104}
105
106impl_newtype_traits!(Proportion);
107
108/// Input value for [`Proportion`], used when loading or building graphs.
109///
110/// # Examples
111///
112/// ```
113/// let t = demes::InputProportion::from(1.0);
114/// assert_eq!(t, 1.0);
115/// let t = t - 1.0;
116/// assert_eq!(t, 0.0);
117/// let t = 1.0 + t;
118/// assert_eq!(t, 1.0);
119/// ```
120#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, PartialOrd)]
121#[repr(transparent)]
122#[serde(from = "f64")]
123pub struct InputProportion(f64);
124
125impl_input_newtype_traits!(InputProportion);
126
127impl TryFrom<InputProportion> for Proportion {
128    type Error = DemesError;
129
130    fn try_from(value: InputProportion) -> Result<Self, Self::Error> {
131        let rv = Self(value.0);
132        rv.validate(DemesError::ValueError)?;
133        Ok(rv)
134    }
135}