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}