The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
use Test::More;

BEGIN {
    eval { require Math::CDF };
    if ($@) {
        plan 'skip_all' => 'Math::CDF not installed';
    }
    else {
        Test::More->import('no_plan');
    }
}
use strict;
use warnings;
use Bio::Phylo::Util::CONSTANT qw(
  looks_like_object
  looks_like_instance
  _TREE_
  _FOREST_
);

# Test to see if we can require the module we're exercising
require_ok('Bio::Phylo::EvolutionaryModels');

# For convenience we import the sample routine so we can write sample(...)
# instead of Bio::Phylo::EvolutionaryModels::sample(...).
Bio::Phylo::EvolutionaryModels->import('sample');

# Example A
# Simulate a single tree with ten species from the constant rate birth model
# with parameter 0.5
{
    my $tree = Bio::Phylo::EvolutionaryModels::constant_rate_birth(
        'birth_rate' => .5,
        'tree_size'  => 10
    );
    ok( looks_like_object( $tree, _TREE_ ), "object is a tree" );
    my $tipcount = scalar @{ $tree->get_terminals };
    is( 10, $tipcount, "crb tree has ${tipcount}==10 tips" );
    ok( $tree->is_ultrametric(0.01), 'crb tree is ultrametric' );
}

# Example B
# Sample 5 trees with ten species from the constant rate birth model using
# the b algorithm
{
    my ( $sample, $stats ) = sample(
        'sample_size'       => 5,
        'tree_size'         => 10,
        'algorithm'         => 'b',
        'algorithm_options' => { 'rate' => 1 },
        'model' => \&Bio::Phylo::EvolutionaryModels::constant_rate_birth,
        'model_options' => { 'birth_rate' => .5 }
    );
    ok( looks_like_instance( $sample, 'ARRAY' ), "b sample is an array" );
    is( scalar @{$sample}, 5, "b sample has 5 trees" );
    for my $t ( @{$sample} ) {
        is( scalar @{ $t->get_terminals }, 10, "b tree has 10 tips" );
    }
    for my $t ( @{$sample} ) {
        ok( $t->is_ultrametric(0.01), "b tree is ultrametric" );
    }
}

# Example C
# Sample 5 trees with ten species from the constant rate birth and death model
# using the bd algorithm and two threads (useful for dual core processors)
# NB: we must specify an nstar here, an appropriate choice will depend on the
# birth_rate and death_rate we are giving the model
{
    my ( $forest, $stats ) = sample(
        'sample_size'       => 5,
        'tree_size'         => 10,
        'threads'           => 1,
        'algorithm'         => 'bd',
        'output_format'     => 'forest',
        'algorithm_options' => { 'rate' => 1, 'nstar' => 30 },
        'model_options'     => { 'birth_rate' => 1, 'death_rate' => .8 },
        'model' => \&Bio::Phylo::EvolutionaryModels::constant_rate_birth_death,
    );
    ok( looks_like_object( $forest, _FOREST_ ), "bd sample is a forest" );
    my $sample = $forest->get_entities;
  SKIP: {
        skip "bd trees have too many tips", scalar @{$sample};
        for my $t ( @{$sample} ) {
            my $count = scalar @{ $t->get_terminals };
            is( $count, 10, "bd tree has ${count}==10 tips" );
        }
    }
  SKIP: {
        skip "bd trees aren't ultrametric", scalar @{$sample};
        for my $t ( @{$sample} ) {
            ok( $t->is_ultrametric, "bd tree is ultrametric" );
        }
    }
}

# Example D
# Sample 5 trees with ten species from the constant rate birth and death model
# using incomplete taxon sampling
#
# sampling_probability is set so that the true tree has 10 species with 50%
# probability, 11 species with 30% probability and 12 species with 20%
# probability
#
# NB: we must specify an mstar here this will depend on the model parameters
# and the incomplete taxon sampling parameters
{
    my $algorithm_options = {
        'rate'                 => 1,
        'nstar'                => 30,
        'mstar'                => 12,
        'sampling_probability' => [ .5, .3, .2 ],
    };
    my ( $forest, $stats ) = sample(
        'sample_size'       => 5,
        'tree_size'         => 10,
        'algorithm'         => 'incomplete_sampling_bd',
        'algorithm_options' => $algorithm_options,
        'output_format'     => 'forest',
        'model_options'     => { 'birth_rate' => 1, 'death_rate' => .8 },
        'model' => \&Bio::Phylo::EvolutionaryModels::constant_rate_birth_death,
    );
    ok( looks_like_object( $forest, _FOREST_ ), "isb sample is forest" );
    my $sample = $forest->get_entities;
    is( scalar @{$sample}, 5, "isb sample has 5 trees" );
  SKIP: {
        skip "isb trees have too many tips", scalar @{$sample};
        for my $t ( @{$sample} ) {
            my $count = scalar @{ $t->get_terminals };
            is( $count, 10, "isb tree has ${count}==10 tips" );
        }
    }
  SKIP: {
        skip "isb trees aren't ultrametric", scalar @{$sample};
        for my $t ( @{$sample} ) {
            ok( $t->is_ultrametric, "isb tree is ultrametric" );
        }
    }
}

# Example E
# Sample 5 trees with ten species from a Yule model using the memoryless_b
# algorithm
#
# First we define the random function for the shortest pendant edge for a Yule
# model
{
    my $random_pendant_function = sub {
        my %options = @_;
        return -log(rand) / $options{'birth_rate'} / $options{'tree_size'};
    };

    # Then we produce our sample
    my ( $forest, $stats ) = sample(
        'sample_size'       => 5,
        'tree_size'         => 10,
        'algorithm'         => 'memoryless_b',
        'output_format'     => 'forest',
        'model_options'     => { 'birth_rate' => 1 },
        'algorithm_options' => { 'pendant_dist' => $random_pendant_function },
        'model' => \&Bio::Phylo::EvolutionaryModels::constant_rate_birth,
    );
    ok( looks_like_object( $forest, _FOREST_ ), 'mb result is a forest' );
    my $sample = $forest->get_entities;
    is( scalar @{$sample}, 5, "mb sample has 5 trees" );
    for my $t ( @{$sample} ) {
        is( scalar @{ $t->get_terminals }, 10, "mb tree has 10 tips" );
    }
    for my $t ( @{$sample} ) {
        ok( $t->is_ultrametric(0.01), "mb tree is ultrametric" );
    }
}

# Example F
# Sample 5 trees with ten species from a constant birth death rate model using
# the constant_rate_bd algorithm
{
    my ($forest) = sample(
        'sample_size'   => 5,
        'tree_size'     => 10,
        'output_format' => 'forest',
        'algorithm'     => 'constant_rate_bd',
        'model_options' => { 'birth_rate' => 1, 'death_rate' => .8 }
    );
    ok( looks_like_object( $forest, _FOREST_ ), 'crb sample is forest' );
    my $sample = $forest->get_entities;
    is( scalar @{$sample}, 5, "crb sample has 5 trees" );
    for my $t ( @{$sample} ) {
        is( scalar @{ $t->get_terminals }, 10, "crb tree has 10 tips" );
    }
    for my $t ( @{$sample} ) {
        ok( $t->is_ultrametric(0.01), 'crb tree is ultrametric' );
    }
}