Skip to content

Commit dd23baa

Browse files
authored
Merge pull request #58 from molpopgen/validate_tables_simplification
Closes #59. Closes #60.
2 parents 73b838c + 91c9b97 commit dd23baa

File tree

9 files changed

+148
-42
lines changed

9 files changed

+148
-42
lines changed

forrustts_examples/neutral_wf.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use clap::{value_t, value_t_or_exit, App, Arg};
22
use forrustts::wright_fisher::*;
3+
use forrustts::SimplificationFlags;
34

45
fn main() {
56
let matches = App::new("neutral_wf")
@@ -58,6 +59,13 @@ fn main() {
5859
.help("Survival probability")
5960
.takes_value(true),
6061
)
62+
.arg(
63+
Arg::with_name("validate_tables")
64+
.short("v")
65+
.long("validate_tables")
66+
.help("Validate all tables prior to simplification")
67+
.takes_value(false),
68+
)
6169
.get_matches();
6270

6371
// TODO: default params
@@ -69,6 +77,7 @@ fn main() {
6977
let psurvival = value_t!(matches.value_of("psurvival"), f64).unwrap_or(0.0);
7078
let seed = value_t_or_exit!(matches.value_of("seed"), usize);
7179
let outfile = value_t_or_exit!(matches.value_of("outfile"), String);
80+
let validate_tables = matches.is_present("validate_tables");
7281

7382
// TODO: parameter validation..
7483

@@ -86,6 +95,12 @@ fn main() {
8695
SimulationFlags::USE_STATE
8796
};
8897

98+
let mut simplification_flags = SimplificationFlags::empty();
99+
100+
if validate_tables {
101+
simplification_flags |= SimplificationFlags::VALIDATE_ALL;
102+
}
103+
89104
let (mut tables, is_sample) = neutral_wf(
90105
PopulationParams {
91106
size: popsize,
@@ -98,6 +113,7 @@ fn main() {
98113
seed,
99114
nsteps: g,
100115
flags,
116+
simplification_flags,
101117
},
102118
)
103119
.unwrap();

src/error.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use thiserror::Error;
66
///
77
/// Some members of this enum implement ``From``
88
/// in order to redirect other error types.
9-
#[derive(Error, Debug)]
9+
#[derive(Error, Debug, PartialEq)]
1010
pub enum ForrusttsError {
1111
/// An error that occurs during simplification.
1212
#[error("{value:?}")]

src/nested_forward_list.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
use thiserror::Error;
1212

1313
/// Errror type for [``NestedForwardList``] operations.
14-
#[derive(Error, Debug)]
14+
#[derive(Error, Debug, PartialEq)]
1515
pub enum NestedForwardListError {
1616
/// Tail of a list is unexpectedly null.
1717
#[error("Tail is null")]

src/simplification_common.rs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/// Common functions to reuse in various "simplify tables"
22
/// functions
33
use crate::simplification_logic;
4+
use crate::validate_edge_table;
45
use crate::ForrusttsError;
56
use crate::SamplesInfo;
67
use crate::SimplificationBuffers;
@@ -9,6 +10,16 @@ use crate::SimplificationOutput;
910
use crate::{IdType, NULL_ID};
1011
use crate::{Node, TableCollection};
1112

13+
pub fn validate_tables(
14+
tables: &TableCollection,
15+
flags: &SimplificationFlags,
16+
) -> Result<(), ForrusttsError> {
17+
if flags.contains(SimplificationFlags::VALIDATE_EDGES) {
18+
validate_edge_table(tables.genome_length(), tables.edges(), tables.nodes())?;
19+
}
20+
Ok(())
21+
}
22+
1223
fn setup_idmap(nodes: &[Node], idmap: &mut Vec<IdType>) {
1324
idmap.resize(nodes.len(), NULL_ID);
1425
idmap.iter_mut().for_each(|x| *x = NULL_ID);
@@ -27,12 +38,7 @@ pub fn setup_simplification(
2738
});
2839
}
2940

30-
if flags.bits() != 0 {
31-
return Err(ForrusttsError::SimplificationError {
32-
value: "SimplificationFlags must be zero".to_string(),
33-
});
34-
}
35-
41+
validate_tables(tables, &flags)?;
3642
setup_idmap(&tables.nodes_, &mut output.idmap);
3743

3844
state.clear();

src/simplification_flags.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,20 @@ bitflags! {
44
/// Boolean flags affecting simplification
55
/// behavior.
66
///
7-
/// Currently, this is unused, and exists
8-
/// as a placeholder for the future.
9-
///
107
/// # Example
118
///
129
/// ```
1310
/// let e = forrustts::SimplificationFlags::empty();
14-
/// assert!(e.contains(forrustts::SimplificationFlags::NONE));
11+
/// assert_eq!(e.bits(), 0);
1512
/// ```
1613
#[derive(Default)]
1714
pub struct SimplificationFlags: u32 {
18-
/// Placeholder
19-
const NONE = 0;
15+
/// Validate that input edges are sorted
16+
const VALIDATE_EDGES = 1 << 0;
17+
/// Validate that input mutations are sorted
18+
const VALIDATE_MUTATIONS = 1 << 1;
19+
/// Validate all tables.
20+
const VALIDATE_ALL = Self::VALIDATE_EDGES.bits | Self::VALIDATE_MUTATIONS.bits;
2021
}
2122
}
2223

@@ -27,7 +28,6 @@ mod test {
2728
#[test]
2829
fn test_empty() {
2930
let e = SimplificationFlags::empty();
30-
assert!(e.contains(SimplificationFlags::NONE));
3131
assert_eq!(e.bits(), 0);
3232
}
3333
}

src/simplify_tables.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,3 +91,43 @@ pub fn simplify_tables(
9191

9292
Ok(())
9393
}
94+
95+
#[cfg(test)]
96+
mod tests {
97+
use super::*;
98+
99+
// TODO: we need lots more tests of these validations!
100+
101+
#[test]
102+
fn test_simplify_tables_unsorted_edges() {
103+
let mut tables = TableCollection::new(1000).unwrap();
104+
105+
tables.add_node(0, 0).unwrap(); // parent
106+
tables.add_node(1, 0).unwrap(); // child
107+
tables.add_edge(100, tables.genome_length(), 0, 1).unwrap();
108+
tables.add_edge(0, 100, 0, 1).unwrap();
109+
110+
let mut output = SimplificationOutput::new();
111+
112+
let mut samples = SamplesInfo::new();
113+
samples.samples.push(1);
114+
115+
let _ = simplify_tables_without_state(
116+
&samples,
117+
SimplificationFlags::VALIDATE_ALL,
118+
&mut tables,
119+
&mut output,
120+
)
121+
.map_or_else(
122+
|x: ForrusttsError| {
123+
assert_eq!(
124+
x,
125+
ForrusttsError::TablesError {
126+
value: TablesError::EdgesNotSortedByLeft
127+
}
128+
)
129+
},
130+
|_| panic!(),
131+
);
132+
}
133+
}

src/tables.rs

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use crate::tsdef::{IdType, Position, Time, NULL_ID};
2+
use bitflags::bitflags;
23
use std::cmp::Ordering;
34
use thiserror::Error;
45

@@ -269,6 +270,40 @@ fn sort_mutation_table(sites: &[Site], mutations: &mut MutationTable) {
269270
});
270271
}
271272

273+
bitflags! {
274+
/// Modifies behavior of
275+
/// [``TableCollection::validate_tables``]
276+
///
277+
/// ```
278+
/// let f = forrustts::TableValidationFlags::empty();
279+
/// assert_eq!(f.contains(forrustts::TableValidationFlags::VALIDATE_ALL), true);
280+
/// ```
281+
#[derive(Default)]
282+
pub struct TableValidationFlags: u32 {
283+
/// Validate all tables.
284+
/// This is also the "default"/empty.
285+
const VALIDATE_ALL = 0;
286+
}
287+
}
288+
289+
bitflags! {
290+
/// Modifies behavior of
291+
/// [``TableCollection::sort_tables``]
292+
///
293+
/// ```
294+
/// let f = forrustts::TableSortingFlags::empty();
295+
/// assert_eq!(f.contains(forrustts::TableSortingFlags::SORT_ALL), true);
296+
/// ```
297+
#[derive(Default)]
298+
pub struct TableSortingFlags: u32 {
299+
/// Sort all tables.
300+
/// This is also the "default"/empty.
301+
const SORT_ALL = 0;
302+
/// Do not sort the edge table.
303+
const SKIP_EDGE_TABLE = 1 << 0;
304+
}
305+
}
306+
272307
/// Perform a data integrity check on an [``EdgeTable``].
273308
///
274309
/// This checks, amongst other things, the sorting order
@@ -653,10 +688,26 @@ impl TableCollection {
653688
}
654689

655690
/// Sort all tables for simplification.
691+
#[deprecated(since = "0.1.0", note = "use sort_tables instead")]
656692
pub fn sort_tables_for_simplification(&mut self) {
657-
sort_edge_table(&self.nodes_, &mut self.edges_);
693+
self.sort_tables(TableSortingFlags::empty());
694+
}
695+
696+
/// Sort all tables for simplification.
697+
pub fn sort_tables(&mut self, flags: TableSortingFlags) {
698+
if !flags.contains(TableSortingFlags::SKIP_EDGE_TABLE) {
699+
sort_edge_table(&self.nodes_, &mut self.edges_);
700+
}
658701
sort_mutation_table(&self.sites_, &mut self.mutations_);
659702
}
703+
704+
/// Run a validation check on the tables.
705+
pub fn validate(&self, flags: TableValidationFlags) -> TablesResult<bool> {
706+
if flags.contains(TableValidationFlags::VALIDATE_ALL) {
707+
validate_edge_table(self.genome_length(), &self.edges_, &self.nodes_)?;
708+
}
709+
Ok(true)
710+
}
660711
}
661712

662713
#[cfg(test)]

src/test_simplify_tables.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,10 @@ mod test {
3838
genome_length: Position,
3939
psurvival: f64,
4040
seed: usize,
41+
// None here means "never simplify".
4142
simplification_interval: Option<Time>,
4243
flags: SimulationFlags,
4344
) -> Result<(TableCollection, Vec<i32>), ForrusttsError> {
44-
// None here means "never simplify".
4545
neutral_wf(
4646
PopulationParams {
4747
size: 250,
@@ -54,6 +54,7 @@ mod test {
5454
seed,
5555
nsteps: num_generations,
5656
flags,
57+
simplification_flags: SimplificationFlags::VALIDATE_ALL,
5758
},
5859
)
5960
}
@@ -89,7 +90,7 @@ mod test {
8990
);
9091

9192
// Now, sort and simplify the tables we got from the sim:
92-
tables.sort_tables_for_simplification();
93+
tables.sort_tables(crate::TableSortingFlags::empty());
9394
let mut samples = SamplesInfo::new();
9495
for (i, n) in tables.nodes().iter().enumerate() {
9596
if n.time == num_generations {

0 commit comments

Comments
 (0)