Skip to content

Commit

Permalink
Add initial API elements to ForwardDemesGraph:
Browse files Browse the repository at this point in the history
* initialization
* iteration
* error-handling, throws DemographyError
* tests
  • Loading branch information
molpopgen committed Sep 1, 2022
1 parent 084f3c0 commit efd8966
Show file tree
Hide file tree
Showing 7 changed files with 209 additions and 18 deletions.
4 changes: 3 additions & 1 deletion cpptests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ SET(CPPTEST_SOURCES fwdpy11_cpp_tests.cc
test_MutationDominance.cc
test_gss_classes.cc
test_effect_sizes_of_zero.cc
test_Sregion_from_mvnorm.cc)
test_Sregion_from_mvnorm.cc
test_forward_demes_graph.cc
forward_demes_graph_fixtures.cc)

add_executable(fwdpy11_cpp_tests ${CPPTEST_SOURCES})
add_dependencies(fwdpy11_cpp_tests fwdpy11core header)
Expand Down
24 changes: 24 additions & 0 deletions cpptests/forward_demes_graph_fixtures.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#include "forward_demes_graph_fixtures.hpp"

const char* single_deme_model = R"(
description: single deme model
time_units: generations
demes:
- name: A
epochs:
- start_size: 100
)";

const char* single_deme_one_size_change = R"(
time_units: generations
demes:
- name: A
epochs:
- start_size: 100
end_time: 50
- start_size: 200
)";

SingleDemeModel::SingleDemeModel() : yaml(single_deme_model)
{
}
11 changes: 11 additions & 0 deletions cpptests/forward_demes_graph_fixtures.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#pragma once

#include <string>

struct SingleDemeModel
{
std::string yaml;
SingleDemeModel();
};


58 changes: 58 additions & 0 deletions cpptests/test_forward_demes_graph.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#include <boost/test/unit_test.hpp>

#include <boost/test/unit_test_suite.hpp>
#include <fwdpy11/types/DiploidPopulation.hpp>
#include <core/demes/forward_graph.hpp>
#include <stdexcept>

#include "forward_demes_graph_fixtures.hpp"

BOOST_AUTO_TEST_SUITE(test_forward_demes_graph)

BOOST_FIXTURE_TEST_CASE(test_zero_length_single_deme_model, SingleDemeModel)
{
fwdpy11_core::ForwardDemesGraph g(yaml, 0);
fwdpy11::DiploidPopulation pop(100, 1.0);
g.initialize_model(pop.generation);
auto end_time = g.model_end_time();
BOOST_REQUIRE_EQUAL(end_time, 1);
while (g.iterating_model())
{
g.iterate_state();
pop.generation += 1;
}
BOOST_REQUIRE_EQUAL(pop.generation, 0);
}

BOOST_FIXTURE_TEST_CASE(single_deme_model_with_burn_in, SingleDemeModel)
{
fwdpy11_core::ForwardDemesGraph g(yaml, 10);
fwdpy11::DiploidPopulation pop(100, 1.0);
g.initialize_model(pop.generation);
auto end_time = g.model_end_time();
BOOST_REQUIRE_EQUAL(end_time, 11);
while (g.iterating_model())
{
g.iterate_state();
pop.generation += 1;
}
BOOST_REQUIRE_EQUAL(pop.generation, 10);
}

BOOST_FIXTURE_TEST_CASE(single_deme_model_with_burn_in_THE_CODE_WE_WANT_TO_WRITE,
SingleDemeModel)
{
fwdpy11_core::ForwardDemesGraph g(yaml, 10);
fwdpy11::DiploidPopulation pop(100, 1.0);
g.initialize_model(pop.generation);
auto end_time = g.model_end_time();
BOOST_REQUIRE_EQUAL(end_time, 11);
while (g.iterating_model())
{
g.iterate_state();
pop.generation += 1;
}
BOOST_REQUIRE_EQUAL(pop.generation, 10);
}

BOOST_AUTO_TEST_SUITE_END()
5 changes: 5 additions & 0 deletions lib/core/demes/forward_graph.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,10 @@ namespace fwdpy11_core
// so that this class looks like a complete type
// at compile time. This is a C++ "gotcha".
~ForwardDemesGraph();

void initialize_model(std::uint32_t);
bool iterating_model() const;
void iterate_state();
std::uint32_t model_end_time() const;
};
}
123 changes: 107 additions & 16 deletions lib/demes/forward_graph.cc
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
#include <cstdint>
#include <cstdlib>
#include <cassert>
#include <stdexcept>
#include <core/fp11rust.h>
#include <core/demes/forward_graph.hpp>
#include <fwdpy11/discrete_demography/exceptions.hpp>

namespace fwdpy11_core
{
Expand All @@ -17,25 +20,80 @@ namespace fwdpy11_core
{
auto code = demes_forward_graph_initialize_from_yaml(
yaml.c_str(), static_cast<double>(burnin), graph.get());
if (code < 0)
{
// NOTE: the rust lib currently doesn't
// set the status code. Rather, it returns
// nullptr if the model is not, in fact,
// in an error state.
std::int32_t status;
auto message
= demes_forward_graph_get_error_message(graph.get(), &status);
if (message == nullptr)
{
throw std::runtime_error(
"graph in error state but message is nullptr");
}
throw std::invalid_argument(message);
}
handle_error_code(code);
}

void handle_error_code(std::int32_t code) const;
void update_model_state_to_time(std::uint32_t);
void initialize_time_iteration();
const double *iterate();
bool offspring_demes_exist() const;
void update_internal_state(double time);
};

void
ForwardDemesGraph::forward_graph_implementation::handle_error_code(
std::int32_t code) const
{
if (code < 0)
{
// NOTE: the rust lib currently doesn't
// set the status code. Rather, it returns
// nullptr if the model is not, in fact,
// in an error state.
std::int32_t status;
auto message
= demes_forward_graph_get_error_message(graph.get(), &status);
if (message == nullptr)
{
throw std::runtime_error(
"graph in error state but message is nullptr");
}
throw fwdpy11::discrete_demography::DemographyError(message);
}
}

void
ForwardDemesGraph::forward_graph_implementation::update_model_state_to_time(
std::uint32_t time)
{
auto code
= demes_forward_graph_update_state(static_cast<double>(time), graph.get());
handle_error_code(code);
}

void
ForwardDemesGraph::forward_graph_implementation::initialize_time_iteration()
{
auto code = demes_forward_graph_initialize_time_iteration(graph.get());
handle_error_code(code);
}

const double *
ForwardDemesGraph::forward_graph_implementation::iterate()
{
std::int32_t code;
auto rv = demes_forward_graph_iterate_time(graph.get(), &code);
handle_error_code(code);
return rv;
}

bool
ForwardDemesGraph::forward_graph_implementation::offspring_demes_exist() const
{
std::int32_t code;
auto rv = demes_forward_graph_any_extant_offspring_demes(graph.get(), &code);
handle_error_code(code);
return rv;
}

void
ForwardDemesGraph::forward_graph_implementation::update_internal_state(double time)
{
std::int32_t code = demes_forward_graph_update_state(time, graph.get());
handle_error_code(code);
}

ForwardDemesGraph::~ForwardDemesGraph()
{
}
Expand All @@ -44,4 +102,37 @@ namespace fwdpy11_core
: pimpl{new forward_graph_implementation(yaml, burnin)}
{
}

std::uint32_t
ForwardDemesGraph::model_end_time() const
{
std::int32_t code;
auto end_time = demes_forward_graph_model_end_time(&code, pimpl->graph.get());
pimpl->handle_error_code(code);
return static_cast<std::uint32_t>(end_time);
}

void
ForwardDemesGraph::initialize_model(std::uint32_t time)
{
pimpl->update_model_state_to_time(time);
pimpl->initialize_time_iteration();
pimpl->iterate();
}

bool
ForwardDemesGraph::iterating_model() const
{
return pimpl->offspring_demes_exist();
};

void
ForwardDemesGraph::iterate_state()
{
auto t = pimpl->iterate();
if (t != nullptr)
{
pimpl->update_internal_state(*t);
}
};
}
2 changes: 1 addition & 1 deletion tests/test_discrete_demography.py
Original file line number Diff line number Diff line change
Expand Up @@ -1412,7 +1412,7 @@ def test_invalid_demes_spec_modules():
for bad in glob.glob("tests/demes-spec/test-cases/invalid/*.yaml"):
with open(bad, "r") as f:
yaml = "".join(f.readlines())
with pytest.raises(ValueError):
with pytest.raises(fwdpy11.DemographyError):
_ = fwdpy11._fwdpy11._ForwardDemesGraph(yaml=yaml, burnin=0)


Expand Down

0 comments on commit efd8966

Please sign in to comment.