use crate::specification::Graph;
use crate::specification::GraphDefaults;
use crate::specification::UnresolvedDeme;
use crate::specification::UnresolvedDemeHistory;
use crate::specification::UnresolvedEpoch;
use crate::specification::UnresolvedGraph;
use crate::DemesError;
use crate::InputGenerationTime;
use crate::InputProportion;
use crate::InputTime;
use crate::TimeUnits;
use crate::UnresolvedMigration;
#[derive(Debug)]
#[non_exhaustive]
pub enum BuilderError {
SerdeYamlError(serde_yaml::Error),
DemesError(crate::DemesError),
}
impl From<serde_yaml::Error> for BuilderError {
fn from(value: serde_yaml::Error) -> Self {
Self::SerdeYamlError(value)
}
}
impl From<crate::DemesError> for BuilderError {
fn from(value: crate::DemesError) -> Self {
Self::DemesError(value)
}
}
impl std::fmt::Display for BuilderError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
BuilderError::DemesError(e) => write!(f, "demes error: {e:?}"),
BuilderError::SerdeYamlError(e) => write!(f, "yaml error: {e:?}"),
}
}
}
impl std::error::Error for BuilderError {}
pub struct GraphBuilder {
graph: UnresolvedGraph,
metadata: Option<crate::Metadata>,
}
impl GraphBuilder {
pub fn new(
time_units: TimeUnits,
generation_time: Option<InputGenerationTime>,
defaults: Option<GraphDefaults>,
) -> Self {
Self {
graph: UnresolvedGraph::new(time_units, generation_time, defaults),
metadata: None,
}
}
pub fn new_generations(defaults: Option<GraphDefaults>) -> Self {
Self {
graph: UnresolvedGraph::new(TimeUnits::Generations, None, defaults),
metadata: None,
}
}
pub fn add_deme<I: IntoIterator<Item = UnresolvedEpoch>>(
&mut self,
name: &str,
epochs: I,
history: UnresolvedDemeHistory,
description: Option<&str>,
) {
let epochs = epochs.into_iter().collect::<Vec<_>>();
let ptr = UnresolvedDeme::new_via_builder(name, epochs, history, description);
self.graph.add_deme(ptr);
}
pub fn add_migration(&mut self, migration: UnresolvedMigration) {
self.graph.add_migration(migration);
}
pub fn add_pulse<T: Into<InputTime>, P: Into<InputProportion>, I: IntoIterator<Item = P>>(
&mut self,
sources: Option<&[&str]>,
dest: Option<&str>,
time: Option<T>,
proportions: Option<I>,
) {
let sources = sources.map(|value| value.iter().map(|v| v.to_string()).collect::<Vec<_>>());
let dest = dest.map(|value| value.to_string());
let time = time.map(|t| t.into());
let proportions = proportions.map(|s| {
s.into_iter()
.map(|p| p.into())
.collect::<Vec<InputProportion>>()
});
self.graph.add_pulse(sources, dest, time, proportions);
}
pub fn resolve(self) -> Result<Graph, DemesError> {
let mut builder = self;
match builder.metadata {
None => (),
Some(m) => builder.graph.set_metadata(m),
}
builder.graph.resolve()?.try_into()
}
pub fn set_toplevel_metadata<T: serde::Serialize>(
&mut self,
metadata: &T,
) -> Result<(), BuilderError> {
let yaml = serde_yaml::to_string(metadata)?;
let md: std::collections::BTreeMap<String, serde_yaml::Value> =
serde_yaml::from_str(&yaml)?;
let metadata = crate::Metadata::try_from(md)?;
self.metadata = Some(metadata);
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::specification::DemeDefaults;
use crate::InputDemeSize;
#[test]
#[should_panic]
fn new_builder() {
let b = GraphBuilder::new(TimeUnits::Generations, None, None);
b.resolve().unwrap();
}
#[test]
fn add_deme_with_epochs() {
let mut b = GraphBuilder::new_generations(Some(GraphDefaults::default()));
let edata = UnresolvedEpoch {
start_size: Some(InputDemeSize::from(100.0)),
..Default::default()
};
b.add_deme("CEU", vec![edata], UnresolvedDemeHistory::default(), None);
let _graph = b.resolve().unwrap();
}
#[test]
fn use_proportion_for_proportions() {
let p = InputProportion::from(0.5);
let _ = UnresolvedDemeHistory {
proportions: Some(vec![p, p]),
..Default::default()
};
}
#[test]
fn builder_deme_defaults() {
let defaults = DemeDefaults {
epoch: UnresolvedEpoch {
end_size: Some(InputDemeSize::from(100.)),
..Default::default()
},
};
let history = UnresolvedDemeHistory {
defaults,
..Default::default()
};
let mut b = GraphBuilder::new_generations(None);
b.add_deme("YRB", vec![], history, None);
b.resolve().unwrap();
}
#[test]
fn builder_toplevel_metadata() {
#[derive(serde::Serialize, serde::Deserialize)]
struct MyMetaData {
foo: i32,
bar: String,
}
let yaml = "
time_units: generations
metadata:
foo: 1
bar: \"bananas\"
demes:
- name: ancestor1
epochs:
- start_size: 50
end_time: 20
";
let mut b = GraphBuilder::new(TimeUnits::Generations, None, None);
b.add_deme(
"ancestor1",
vec![UnresolvedEpoch {
start_size: Some(50.0.into()),
end_time: Some(20.0.into()),
..Default::default()
}],
UnresolvedDemeHistory::default(),
None,
);
b.set_toplevel_metadata(&MyMetaData {
foo: 1,
bar: "bananas".to_string(),
})
.unwrap();
let graph_from_builder = b.resolve().unwrap();
let graph_from_yaml = crate::loads(yaml).unwrap();
assert_eq!(graph_from_builder, graph_from_yaml);
assert_eq!(
graph_from_builder.as_string().unwrap(),
graph_from_yaml.as_string().unwrap()
);
}
#[test]
fn test_invalid_pulse() {
let mut b = GraphBuilder::new(TimeUnits::Generations, None, None);
b.add_deme(
"ancestor1",
vec![UnresolvedEpoch {
start_size: Some(50.0.into()),
end_time: Some(20.0.into()),
..Default::default()
}],
UnresolvedDemeHistory::default(),
None,
);
b.add_deme(
"ancestor2",
vec![UnresolvedEpoch {
start_size: Some(50.0.into()),
end_time: Some(20.0.into()),
..Default::default()
}],
UnresolvedDemeHistory::default(),
None,
);
b.add_pulse(
Some(&["ancestor1"]),
Some("ancestor2"),
Some(20.0),
Some(vec![0.5]),
);
assert!(b.resolve().is_err());
}
}