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