From 62855261940cee7661b05e79e00f9453a1ef1cba Mon Sep 17 00:00:00 2001 From: David Rich Date: Thu, 27 Apr 2023 12:39:59 -0700 Subject: [PATCH] Moved all unit tests to doctest. --- CMakeLists.txt | 4 +- doctest/alignment.hpp | 16 + doctest/bitset.hpp | 205 ++++++++ doctest/block_specification.hpp | 28 ++ doctest/combinatorics.hpp | 31 ++ doctest/csv.hpp | 14 + doctest/default_dict.hpp | 16 + {src => doctest}/doctest.cpp | 0 {src => doctest}/doctest.h | 0 {src => doctest}/doctest_constants.hpp | 0 doctest/doctest_include_unit_tests.hpp | 39 ++ doctest/driver.hpp | 59 +++ doctest/eigen_sugar.hpp | 73 +++ {src => doctest}/gp_doctest.cpp | 36 +- doctest/gp_engine.hpp | 20 + {src => doctest}/include_doctest.hpp | 0 doctest/intpack.hpp | 21 + doctest/mmapped_matrix.hpp | 17 + doctest/mmapped_plv.hpp | 14 + doctest/nni_operation.hpp | 113 +++++ doctest/node.hpp | 89 ++++ doctest/numerical_utils.hpp | 42 ++ doctest/pv_handler.hpp | 31 ++ doctest/reindexer.hpp | 110 +++++ doctest/rooted_sbn_instance.hpp | 655 +++++++++++++++++++++++++ doctest/rooted_tree.hpp | 42 ++ doctest/sankoff_handler.hpp | 132 +++++ doctest/sankoff_matrix.hpp | 34 ++ doctest/sbn_maps.hpp | 27 + doctest/sbn_probability.hpp | 149 ++++++ doctest/site_model.hpp | 32 ++ doctest/site_pattern.hpp | 12 + doctest/stick_breaking_transform.hpp | 24 + doctest/stopwatch.hpp | 64 +++ doctest/subsplit_dag_node.hpp | 94 ++++ doctest/substitution_model.hpp | 59 +++ doctest/task_processor.hpp | 29 ++ doctest/taxon_name_munging.hpp | 28 ++ doctest/tidy_subsplit_dag.hpp | 43 ++ doctest/topology_sampler.hpp | 83 ++++ doctest/tree_collection.hpp | 26 + doctest/unrooted_sbn_instance.hpp | 525 ++++++++++++++++++++ doctest/unrooted_tree.hpp | 21 + src/alignment.hpp | 13 - src/bitset.hpp | 202 -------- src/block_specification.hpp | 25 - src/combinatorics.hpp | 28 -- src/csv.hpp | 11 - src/dag_branch_handler.hpp | 4 - src/default_dict.hpp | 13 - src/driver.hpp | 56 --- src/eigen_sugar.hpp | 70 --- src/generic_sbn_instance.hpp | 4 - src/gp_engine.hpp | 17 - src/intpack.hpp | 18 - src/mmapped_matrix.hpp | 14 - src/mmapped_plv.hpp | 11 - src/nni_operation.hpp | 110 ----- src/node.hpp | 86 ---- src/numerical_utils.hpp | 39 -- src/psp_indexer.hpp | 4 - src/pv_handler.hpp | 28 -- src/reindexer.hpp | 106 ---- src/rooted_sbn_instance.hpp | 651 ------------------------ src/rooted_tree.hpp | 39 -- src/rooted_tree_collection.hpp | 5 - src/sankoff_handler.hpp | 129 ----- src/sankoff_matrix.hpp | 31 -- src/sbn_maps.hpp | 24 - src/sbn_probability.hpp | 146 ------ src/site_model.hpp | 29 -- src/site_pattern.hpp | 9 - src/stick_breaking_transform.hpp | 21 - src/stopwatch.hpp | 61 --- src/subsplit_dag_node.hpp | 91 ---- src/substitution_model.hpp | 56 --- src/task_processor.hpp | 26 - src/taxon_name_munging.hpp | 25 - src/tidy_subsplit_dag.hpp | 40 -- src/topology_sampler.hpp | 80 --- src/tree.hpp | 5 - src/tree_collection.hpp | 23 - src/unrooted_sbn_instance.hpp | 520 -------------------- src/unrooted_tree.hpp | 18 - src/unrooted_tree_collection.hpp | 4 - 85 files changed, 3038 insertions(+), 2911 deletions(-) create mode 100644 doctest/alignment.hpp create mode 100644 doctest/bitset.hpp create mode 100644 doctest/block_specification.hpp create mode 100644 doctest/combinatorics.hpp create mode 100644 doctest/csv.hpp create mode 100644 doctest/default_dict.hpp rename {src => doctest}/doctest.cpp (100%) rename {src => doctest}/doctest.h (100%) rename {src => doctest}/doctest_constants.hpp (100%) create mode 100644 doctest/doctest_include_unit_tests.hpp create mode 100644 doctest/driver.hpp create mode 100644 doctest/eigen_sugar.hpp rename {src => doctest}/gp_doctest.cpp (99%) create mode 100644 doctest/gp_engine.hpp rename {src => doctest}/include_doctest.hpp (100%) create mode 100644 doctest/intpack.hpp create mode 100644 doctest/mmapped_matrix.hpp create mode 100644 doctest/mmapped_plv.hpp create mode 100644 doctest/nni_operation.hpp create mode 100644 doctest/node.hpp create mode 100644 doctest/numerical_utils.hpp create mode 100644 doctest/pv_handler.hpp create mode 100644 doctest/reindexer.hpp create mode 100644 doctest/rooted_sbn_instance.hpp create mode 100644 doctest/rooted_tree.hpp create mode 100644 doctest/sankoff_handler.hpp create mode 100644 doctest/sankoff_matrix.hpp create mode 100644 doctest/sbn_maps.hpp create mode 100644 doctest/sbn_probability.hpp create mode 100644 doctest/site_model.hpp create mode 100644 doctest/site_pattern.hpp create mode 100644 doctest/stick_breaking_transform.hpp create mode 100644 doctest/stopwatch.hpp create mode 100644 doctest/subsplit_dag_node.hpp create mode 100644 doctest/substitution_model.hpp create mode 100644 doctest/task_processor.hpp create mode 100644 doctest/taxon_name_munging.hpp create mode 100644 doctest/tidy_subsplit_dag.hpp create mode 100644 doctest/topology_sampler.hpp create mode 100644 doctest/tree_collection.hpp create mode 100644 doctest/unrooted_sbn_instance.hpp create mode 100644 doctest/unrooted_tree.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index e6e072ffa..13cd303f4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -174,10 +174,10 @@ add_dependencies(bito pybind11) # executables # # ############## bito_executable(doctest - src/doctest.cpp) + doctest/doctest.cpp) bito_executable(gp_doctest - src/gp_doctest.cpp) + doctest/gp_doctest.cpp) # ################## # optional extras # diff --git a/doctest/alignment.hpp b/doctest/alignment.hpp new file mode 100644 index 000000000..3972f5aea --- /dev/null +++ b/doctest/alignment.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include "../src/alignment.hpp" + +#ifdef DOCTEST_LIBRARY_INCLUDED +TEST_CASE("Alignment") { + auto alignment = Alignment::ReadFasta("data/hello.fasta"); + CHECK_EQ(alignment, Alignment::HelloAlignment()); + CHECK(alignment.IsValid()); + + CHECK_THROWS(alignment.ExtractSingleColumnAlignment(31)); + Alignment first_col_expected = + Alignment({{"mars", "C"}, {"saturn", "G"}, {"jupiter", "G"}}); + CHECK_EQ(alignment.ExtractSingleColumnAlignment(0), first_col_expected); +} +#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/doctest/bitset.hpp b/doctest/bitset.hpp new file mode 100644 index 000000000..63fff59b0 --- /dev/null +++ b/doctest/bitset.hpp @@ -0,0 +1,205 @@ +#pragma once + +#include "../src/bitset.hpp" + +#ifdef DOCTEST_LIBRARY_INCLUDED + +TEST_CASE("Bitset") { + Bitset bit_from_str = Bitset("00110100"); + Bitset bit_from_sizevec = Bitset({2, 3, 5}, 8); + CHECK_EQ(bit_from_str, bit_from_sizevec); + + Bitset a("1100"); + + CHECK_EQ(a[2], false); + CHECK_EQ(a[1], true); + + Bitset build_up(4); + build_up.set(1); + build_up.set(3); + CHECK_EQ(build_up, Bitset("0101")); + + Bitset strip_down(4, true); + strip_down.reset(0); + strip_down.reset(2); + CHECK_EQ(strip_down, Bitset("0101")); + + CHECK_EQ(a.size(), 4); + + CHECK_EQ(Bitset("1100"), Bitset("1100")); + CHECK_NE(Bitset("1100"), Bitset("0100")); + + CHECK_LT(Bitset("0100"), Bitset("0110")); + CHECK_LT(Bitset("0100"), Bitset("0110")); + CHECK_LT(Bitset("0010"), Bitset("0100")); + CHECK_LE(Bitset("0010"), Bitset("0100")); + CHECK_LE(Bitset("1100"), Bitset("1100")); + + CHECK_GT(Bitset("0110"), Bitset("0100")); + CHECK_GT(Bitset("0110"), Bitset("0100")); + CHECK_GT(Bitset("0100"), Bitset("0010")); + CHECK_GE(Bitset("0100"), Bitset("0010")); + CHECK_GE(Bitset("1100"), Bitset("1100")); + + CHECK_EQ((Bitset("1100") & Bitset("1010")), Bitset("1000")); + CHECK_EQ((Bitset("1100") | Bitset("1010")), Bitset("1110")); + CHECK_EQ((Bitset("1100") ^ Bitset("1010")), Bitset("0110")); + CHECK_EQ(~Bitset("1010"), Bitset("0101")); + CHECK_EQ(Bitset("101") + Bitset("011"), Bitset("101011")); + CHECK_EQ(std::min(Bitset("1100"), Bitset("1010")), Bitset("1010")); + + a &= Bitset("0110"); + CHECK_EQ(a, Bitset("0100")); + + CHECK_EQ(a.All(), false); + CHECK_EQ(Bitset(4, true).All(), true); + CHECK_EQ(a.Any(), true); + CHECK_EQ(Bitset(4, false).Any(), false); + CHECK_EQ(a.None(), false); + CHECK_EQ(Bitset(4, false).None(), true); + + a.flip(); + CHECK_EQ(a, Bitset("1011")); + a.Minorize(); + CHECK_EQ(a, Bitset("0100")); + a.Minorize(); + CHECK_EQ(a, Bitset("0100")); + + a.CopyFrom(Bitset("10"), 0, false); + CHECK_EQ(a, Bitset("1000")); + a.CopyFrom(Bitset("10"), 0, true); + CHECK_EQ(a, Bitset("0100")); + a.CopyFrom(Bitset("10"), 2, false); + CHECK_EQ(a, Bitset("0110")); + a.CopyFrom(Bitset("10"), 2, true); + CHECK_EQ(a, Bitset("0101")); + + auto singleton = Bitset("0010"); + CHECK(singleton.IsSingleton()); + CHECK_EQ(*singleton.SingletonOption(), 2); + + CHECK_EQ(Bitset("0000").Count(), 0); + CHECK_EQ(Bitset("0100").Count(), 1); + CHECK_EQ(Bitset("011101").Count(), 4); + + CHECK_EQ(Bitset("1001").ToVectorOfSetBitsAsString(), "0,3"); + CHECK_EQ(Bitset("0000").ToVectorOfSetBitsAsString(), ""); +} + +TEST_CASE("Bitset: Clades, Subsplits, PCSPs") { + auto p = Bitset("000111"); + // Subsplit: 000|111 + CHECK_EQ(p.SubsplitGetClade(SubsplitClade::Left), Bitset("000")); + CHECK_EQ(p.SubsplitGetClade(SubsplitClade::Right), Bitset("111")); + // Edge: 00|01|11 + CHECK_EQ(p.PCSPGetClade(PCSPClade::Sister), Bitset("00")); + CHECK_EQ(p.PCSPGetClade(PCSPClade::Focal), Bitset("01")); + CHECK_EQ(p.PCSPGetClade(PCSPClade::RightChild), Bitset("11")); + + CHECK_EQ(Bitset("11001010").SubsplitCladeUnion(), Bitset("1110")); + + CHECK_EQ(Bitset("10011100").SubsplitRotate(), Bitset("11001001")); + CHECK_EQ(Bitset("010101").SubsplitToVectorOfSetBitsAsString(), "1|0,2"); + + CHECK_EQ(Bitset("101010").SubsplitIsLeftChildOf(Bitset("111000")), true); + // #350 commented out code + // CHECK_EQ(Bitset::SubsplitIsChildOfWhichParentClade(Bitset("111000"), + // Bitset("101010")), true); + CHECK_EQ(Bitset("00100001").SubsplitIsRightChildOf(Bitset("11000011")), true); + // CHECK_EQ(Bitset::SubsplitIsChildOfWhichParentClade(Bitset("000111"), + // Bitset("101010")), false); + CHECK_EQ(Bitset("010001").SubsplitIsLeftChildOf(Bitset("110001")), false); + CHECK_EQ(Bitset("010001").SubsplitIsRightChildOf(Bitset("01000011")), false); + // Should throw because Bitsets can't be divided into equal-sized clades. + CHECK_THROWS(Bitset("11010").SubsplitIsLeftChildOf(Bitset("10101"))); + CHECK_THROWS(Bitset("11010").SubsplitIsRightChildOf(Bitset("10101"))); + + CHECK_EQ(Bitset("101010").SubsplitIsRootsplit(), true); + CHECK_EQ(Bitset("111000").SubsplitIsRootsplit(), false); + CHECK_EQ(Bitset("11000001").SubsplitIsRootsplit(), false); + + CHECK_EQ(Bitset("011101").PCSPIsValid(), false); + CHECK_EQ(Bitset("000111").PCSPIsValid(), false); + CHECK_EQ(Bitset("100100").PCSPIsValid(), false); + CHECK_EQ(Bitset("100011001").PCSPIsValid(), true); + + CHECK_EQ(Bitset("100011001").PCSPChildIsLeaf(), false); + CHECK_EQ(Bitset("100011000").PCSPChildIsLeaf(), true); + + CHECK_EQ(Bitset("000111010").PCSPIsParentRootsplit(), false); + CHECK_EQ(Bitset("000111000100").PCSPIsParentRootsplit(), false); + CHECK_EQ(Bitset("101010000").PCSPIsParentRootsplit(), true); + + CHECK_EQ(Bitset("100011001").PCSPGetParentSubsplit(), Bitset("100011")); + CHECK_EQ(Bitset("011100001").PCSPGetParentSubsplit(), Bitset("100011")); + CHECK_EQ(Bitset("100011001").PCSPGetChildSubsplit(), Bitset("010001")); + CHECK_EQ(Bitset("100001110001").PCSPGetChildSubsplit(), Bitset("01100001")); + CHECK_EQ(Bitset("100001110001").PCSPGetChildSubsplitTaxonCounts(), SizePair({1, 2})); + CHECK_EQ(Bitset("100000111100101").PCSPGetChildSubsplitTaxonCounts(), + SizePair({2, 2})); + + CHECK_EQ(Bitset::Singleton(4, 2), Bitset("0010")); + + CHECK_EQ(Bitset("100010"), Bitset::Subsplit(Bitset("100"), Bitset("010"))); + CHECK_EQ(Bitset("110001"), Bitset::Subsplit(Bitset("001"), Bitset("110"))); + // Invalid clade pair. + CHECK_THROWS(Bitset::Subsplit(Bitset("1100"), Bitset("001"))); + CHECK_THROWS(Bitset::Subsplit(Bitset("111"), Bitset("001"))); + + CHECK_EQ(Bitset("000110010"), Bitset::PCSP(Bitset("110000"), Bitset("100010"))); + CHECK_EQ(Bitset("110001000"), Bitset::PCSP(Bitset("110001"), Bitset("001000"))); + // Invalid parent-child pair. + CHECK_THROWS(Bitset::PCSP(Bitset("110001"), Bitset("010001"))); + CHECK_THROWS(Bitset::PCSP(Bitset("11000101"), Bitset("010001"))); + CHECK_THROWS(Bitset::PCSP(Bitset("110001"), Bitset("110100"))); + + CHECK_EQ(Bitset::RootsplitSubsplitOfClade(Bitset("0011")), Bitset("11000011")); + CHECK_EQ(Bitset::PCSPFromUCAToRootsplit(Bitset("11000011")), Bitset("000011110011")); + + CHECK_EQ(Bitset("010000").SubsplitIsLeaf(), true); + CHECK_EQ(Bitset("010010").SubsplitIsLeaf(), false); + CHECK_EQ(Bitset("111000").SubsplitIsLeaf(), false); + CHECK_EQ(Bitset::LeafSubsplitOfNonemptyClade(Bitset("010")), Bitset("010000")); + CHECK_EQ(Bitset::LeafSubsplitOfParentSubsplit(Bitset("100001")), Bitset("001000")); + CHECK_THROWS(Bitset::LeafSubsplitOfParentSubsplit(Bitset("100011"))); + CHECK_EQ(Bitset::PCSPFromRightParentCladeToLeaf(Bitset("100001")), + Bitset("100001000")); + CHECK_THROWS(Bitset::PCSPFromRightParentCladeToLeaf(Bitset("0000110"))); + CHECK_THROWS(Bitset::PCSPFromRightParentCladeToLeaf(Bitset("100101"))); + + // Restrict a bitset. + CHECK_EQ(Bitset::Remap(Bitset("10101010101"), {0, 2, 4, 6, 8, 10}), Bitset("111111")); + // If we apply this remap 3 times we should get back to where we started. + SizeOptionVector rotate120{6, 7, 8, 0, 1, 2, 3, 4, 5}; + auto to_rotate = Bitset("110010100"); + CHECK_EQ(Bitset::Remap(Bitset::Remap(Bitset::Remap(to_rotate, rotate120), rotate120), + rotate120), + to_rotate); + // "Lift" a bitset. + CHECK_EQ(Bitset::Remap(Bitset("11"), {0, std::nullopt, 1}), Bitset("101")); +} + +TEST_CASE("Bitset: Subsplit Sort") { + Bitset bitset_a = Bitset::Subsplit("01001", "00100"); + CHECK_MESSAGE(Bitset::SubsplitCompare(bitset_a, bitset_a) == 0, + "Equality: bitset_a should be equal to itself"); + // Count of bitset_a (3) comes before count of bitset_b (4). + Bitset bitset_b = Bitset::Subsplit("00100", "01011"); + CHECK_MESSAGE( + Bitset::SubsplitCompare(bitset_a, bitset_b) < 0, + "Bit Count: bitset_a should be smaller/earlier sorted value than bitset_b."); + // Union of bitset_a ("01101") comes before union of bitset_c ("11100"), counts are + // equal. + Bitset bitset_c = Bitset::Subsplit("01000", "10100"); + CHECK_MESSAGE( + Bitset::SubsplitCompare(bitset_a, bitset_c) < 0, + "Union: bitset_a should be smaller/earlier sorted value than bitset_c."); + // Sorted clade of bitset_a ("01001") comes before sorted clade of bitset_d ("01100"), + // counts and unions are equal. + Bitset bitset_d = Bitset::Subsplit("00001", "01100"); + CHECK_MESSAGE( + Bitset::SubsplitCompare(bitset_a, bitset_d) < 0, + "Sorted Clade: bitset_a should be smaller/earlier sorted value than bitset_d."); +} + +#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/doctest/block_specification.hpp b/doctest/block_specification.hpp new file mode 100644 index 000000000..e346ead50 --- /dev/null +++ b/doctest/block_specification.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include "../src/block_specification.hpp" + +#ifdef DOCTEST_LIBRARY_INCLUDED +TEST_CASE("BlockSpecification") { + // As an example, kazoo has 4 parameters, and jordan has 23. + BlockSpecification spec({{"kazoo", 4}, {"jordan", 23}}); + // The specification stores the starting index and then the number of + // parameters. Because we're using an ordered map, jordan has a lower index + // than kazoo. + const auto correct_spec_map = BlockSpecification::UnderlyingMapType( + {{"entire", {0, 27}}, {"jordan", {0, 23}}, {"kazoo", {23, 4}}}); + CHECK_EQ(spec.GetMap(), correct_spec_map); + spec.Append("entire turbo and boost", + BlockSpecification({{"boost", 42}, {"turbo", 666}})); + // Then after appending, the new stuff gets shifted down. For example, we find + // boost at 23+4=27 and turbo at 27+42=69. + auto correct_appended_map = BlockSpecification::UnderlyingMapType( + {{"boost", {27, 42}}, // 23+4=27 + {"entire", {0, 735}}, // 4+23+42+666=735 + {"entire turbo and boost", {27, 708}}, // 42+666=708 + {"jordan", {0, 23}}, // + {"kazoo", {23, 4}}, // + {"turbo", {69, 666}}}); // 27+42=69 + CHECK_EQ(spec.GetMap(), correct_appended_map); +} +#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/doctest/combinatorics.hpp b/doctest/combinatorics.hpp new file mode 100644 index 000000000..0d4f8b835 --- /dev/null +++ b/doctest/combinatorics.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include "../src/combinatorics.hpp" + +#ifdef DOCTEST_LIBRARY_INCLUDED +TEST_CASE("Combinatorics") { + CHECK_EQ(Combinatorics::TopologyCount(1), 1.); + CHECK_EQ(Combinatorics::TopologyCount(2), 1.); + CHECK_EQ(Combinatorics::TopologyCount(3), 3.); + CHECK_EQ(Combinatorics::TopologyCount(4), 15.); + CHECK_EQ(Combinatorics::TopologyCount(5), 105.); + CHECK_EQ(Combinatorics::TopologyCount(6), 945.); + CHECK_EQ(Combinatorics::TopologyCount(7), 10395.); + + for (size_t taxon_count = 1; taxon_count < 20; taxon_count++) { + CHECK_LT(fabs(Combinatorics::LogTreeCount(taxon_count) - + std::log(Combinatorics::TopologyCount(taxon_count))), + 1e-10); + } + + for (size_t child0_count = 1; child0_count < 10; child0_count++) { + for (size_t child1_count = 1; child1_count < 10; child1_count++) { + CHECK_LT( + fabs(Combinatorics::LogChildSubsplitCountRatio(child0_count, child1_count) - + Combinatorics::LogChildSubsplitCountRatioNaive(child0_count, + child1_count)), + 1e-10); + } + } +} +#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/doctest/csv.hpp b/doctest/csv.hpp new file mode 100644 index 000000000..3095ea883 --- /dev/null +++ b/doctest/csv.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include "../src/csv.hpp" + +#ifdef DOCTEST_LIBRARY_INCLUDED +TEST_CASE("CSV I/O") { + std::string csv_test_file_path = "_ignore/for_csv_test.csv"; + StringDoubleVector input = {{"hi", 1e9}, {"lo", -4.}}; + CSV::StringDoubleVectorToCSV(input, csv_test_file_path); + auto result = CSV::StringDoubleMapOfCSV(csv_test_file_path); + StringDoubleMap correct_result = {{"hi", 1e9}, {"lo", -4.}}; + CHECK_EQ(result, correct_result); +} +#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/doctest/default_dict.hpp b/doctest/default_dict.hpp new file mode 100644 index 000000000..36caf67e3 --- /dev/null +++ b/doctest/default_dict.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include "../src/default_dict.hpp" + +#ifdef DOCTEST_LIBRARY_INCLUDED + +TEST_CASE("DefaultDict") { + auto d = DefaultDict(0); + CHECK_EQ(d.at(4), 0); + d.increment(4, 5); + CHECK_EQ(d.at(4), 5); + d.increment(4, 2); + CHECK_EQ(d.at(4), 7); +} + +#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/src/doctest.cpp b/doctest/doctest.cpp similarity index 100% rename from src/doctest.cpp rename to doctest/doctest.cpp diff --git a/src/doctest.h b/doctest/doctest.h similarity index 100% rename from src/doctest.h rename to doctest/doctest.h diff --git a/src/doctest_constants.hpp b/doctest/doctest_constants.hpp similarity index 100% rename from src/doctest_constants.hpp rename to doctest/doctest_constants.hpp diff --git a/doctest/doctest_include_unit_tests.hpp b/doctest/doctest_include_unit_tests.hpp new file mode 100644 index 000000000..c2f517792 --- /dev/null +++ b/doctest/doctest_include_unit_tests.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include "eigen_sugar.hpp" + +#include "alignment.hpp" +#include "bitset.hpp" +#include "block_specification.hpp" +#include "combinatorics.hpp" +#include "csv.hpp" +#include "default_dict.hpp" +#include "driver.hpp" +#include "gp_engine.hpp" +#include "intpack.hpp" +#include "mmapped_matrix.hpp" +#include "mmapped_plv.hpp" +#include "nni_operation.hpp" +#include "node.hpp" +#include "numerical_utils.hpp" +#include "pv_handler.hpp" +#include "reindexer.hpp" +#include "rooted_sbn_instance.hpp" +#include "rooted_tree.hpp" +#include "sankoff_handler.hpp" +#include "sankoff_matrix.hpp" +#include "sbn_maps.hpp" +#include "sbn_probability.hpp" +#include "site_model.hpp" +#include "site_pattern.hpp" +#include "stick_breaking_transform.hpp" +#include "stopwatch.hpp" +#include "subsplit_dag_node.hpp" +#include "substitution_model.hpp" +#include "task_processor.hpp" +#include "taxon_name_munging.hpp" +#include "tidy_subsplit_dag.hpp" +#include "topology_sampler.hpp" +#include "tree_collection.hpp" +#include "unrooted_sbn_instance.hpp" +#include "unrooted_tree.hpp" diff --git a/doctest/driver.hpp b/doctest/driver.hpp new file mode 100644 index 000000000..db6553ea5 --- /dev/null +++ b/doctest/driver.hpp @@ -0,0 +1,59 @@ +#pragma once + +#include "../src/driver.hpp" + +#ifdef DOCTEST_LIBRARY_INCLUDED + +TEST_CASE("Driver") { + Driver driver; + + std::vector newicks = { + "(a:0,b:0,c:0,d:0):0;", + "((b:0,a:0):0,c:0):0;", + "((a:1.1,b:2):0.4,c:3):0;", + "(x:0,(a:1.1,(b:2,(quack:0.1,duck:0):0):0):0,c:3):1.1;", + }; + for (const auto& newick : newicks) { + auto collection = driver.ParseString(newick); + CHECK_EQ(newick, collection.Trees()[0].Newick(collection.TagTaxonMap())); + } + driver.Clear(); + // Note that the order of the taxa is given by the order in the translate table, not + // by the short names. We use that here to make sure that the ordering of the taxa is + // the same as that in the newick file below so that they can be compared. + auto nexus_collection = driver.ParseNexusFile("data/DS1.subsampled_10.t.reordered"); + CHECK_EQ(nexus_collection.TreeCount(), 10); + driver.Clear(); + auto newick_collection = driver.ParseNewickFile("data/DS1.subsampled_10.t.nwk"); + CHECK_EQ(nexus_collection, newick_collection); + driver.Clear(); + auto newick_collection_gz = + driver.ParseNewickFileGZ("data/DS1.subsampled_10.t.nwk.gz"); + CHECK_EQ(nexus_collection, newick_collection_gz); + driver.Clear(); + auto five_taxon = driver.ParseNewickFile("data/five_taxon_unrooted.nwk"); + std::vector correct_five_taxon_names({"x0", "x1", "x2", "x3", "x4"}); + CHECK_EQ(five_taxon.TaxonNames(), correct_five_taxon_names); + // Check that we can parse BEAST trees with [&comments], and that the different + // formatting of the translate block doesn't trip us up. + auto beast_nexus = driver.ParseNexusFile("data/test_beast_tree_parsing.nexus"); + // These are the taxa, in order, taken directly from the nexus file: + StringVector beast_taxa = { + "aDuckA_1976", "aDuckB_1977", "aItaly_1987", "aMallard_1985", + "hCHR_1983", "hCambr_1939", "hFortMon_1947", "hKiev_1979", + "hLenin_1954", "hMongol_1985", "hMongol_1991", "hNWS_1933", + "hPR_1934", "hSCar_1918.00", "hScot_1994", "hSuita_1989", + "hUSSR_1977", "sEhime_1980", "sIllino_1963", "sIowa_1930", + "sNebrask_1992", "sNewJers_1976", "sStHya_1991", "sWiscons_1961", + "sWiscons_1.998e3"}; + CHECK_EQ(beast_nexus.TaxonNames(), beast_taxa); + // Check that we got the whole tree. + for (const auto& [topology, count] : beast_nexus.TopologyCounter()) { + std::ignore = count; + CHECK_EQ(topology->LeafCount(), beast_taxa.size()); + } + auto beast_nexus_gz = + driver.ParseNexusFileGZ("data/test_beast_tree_parsing.nexus.gz"); + CHECK_EQ(beast_nexus, beast_nexus_gz); +} +#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/doctest/eigen_sugar.hpp b/doctest/eigen_sugar.hpp new file mode 100644 index 000000000..07594908d --- /dev/null +++ b/doctest/eigen_sugar.hpp @@ -0,0 +1,73 @@ +#pragma once + +#include "../src/eigen_sugar.hpp" + +#ifdef DOCTEST_LIBRARY_INCLUDED + +void CheckVectorXdEquality(double value, const EigenVectorXd v, double tolerance) { + for (Eigen::Index i = 0; i < v.size(); i++) { + CHECK_LT(fabs(value - v[i]), tolerance); + } +}; + +void CheckVectorXdEquality(const EigenVectorXd v1, const EigenVectorXd v2, + double tolerance) { + CHECK_EQ(v1.size(), v2.size()); + for (Eigen::Index i = 0; i < v1.size(); i++) { + double error = fabs(v1[i] - v2[i]); + if (error > tolerance) { + std::cerr << "CheckVectorXdEquality failed for index " << i << ": " << v1[i] + << " vs " << v2[i] << std::endl; + } + CHECK_LT(error, tolerance); + } +}; + +// Return the maximum absolute difference between any two entries in vector. +double VectorXdMaxError(const EigenVectorXd v1, const EigenVectorXd v2) { + double max_error = 0.; + Assert(v1.size() == v2.size(), + "Cannot find max error of EigenVectorXd's of different sizes."); + for (Eigen::Index i = 0; i < v1.size(); i++) { + double error = fabs(v1[i] - v2[i]); + if (error > max_error) { + max_error = error; + } + } + return max_error; +} + +// Check if vectors are equal, within given tolerance for any two entries in vector. +bool VectorXdEquality(const EigenVectorXd v1, const EigenVectorXd v2, + double tolerance) { + if (v1.size() != v2.size()) { + return false; + } + for (Eigen::Index i = 0; i < v1.size(); i++) { + double error = fabs(v1[i] - v2[i]); + if (error > tolerance) { + return false; + } + } + return true; +}; + +void CheckVectorXdEqualityAfterSorting(const EigenVectorXdRef v1, + const EigenVectorXdRef v2, double tolerance) { + EigenVectorXd v1_sorted = v1; + EigenVectorXd v2_sorted = v2; + std::sort(v1_sorted.begin(), v1_sorted.end()); + std::sort(v2_sorted.begin(), v2_sorted.end()); + CheckVectorXdEquality(v1_sorted, v2_sorted, tolerance); +}; + +TEST_CASE( + "Make sure that EigenVectorXdOfStdVectorDouble makes a new vector rather than " + "wrapping data.") { + std::vector a = {1., 2., 3., 4.}; + EigenVectorXd b = EigenVectorXdOfStdVectorDouble(a); + a[0] = 99; + CHECK_EQ(b[0], 1.); +} + +#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/src/gp_doctest.cpp b/doctest/gp_doctest.cpp similarity index 99% rename from src/gp_doctest.cpp rename to doctest/gp_doctest.cpp index 253178cd7..90fa79a99 100644 --- a/src/gp_doctest.cpp +++ b/doctest/gp_doctest.cpp @@ -4,23 +4,25 @@ #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN #include "include_doctest.hpp" -#include "sugar.hpp" -#include "combinatorics.hpp" -#include "gp_instance.hpp" -#include "phylo_model.hpp" -#include "reindexer.hpp" -#include "rooted_sbn_instance.hpp" -#include "stopwatch.hpp" -#include "tidy_subsplit_dag.hpp" -#include "nni_engine.hpp" -#include "pv_handler.hpp" -#include "topology_sampler.hpp" -#include "tp_engine.hpp" -#include "tp_choice_map.hpp" -#include "sankoff_matrix.hpp" -#include "sankoff_handler.hpp" -#include "dag_data.hpp" -#include "optimization.hpp" +#include "doctest_include_unit_tests.hpp" + +#include "../src/sugar.hpp" +#include "../src/combinatorics.hpp" +#include "../src/gp_instance.hpp" +#include "../src/phylo_model.hpp" +#include "../src/reindexer.hpp" +#include "../src/rooted_sbn_instance.hpp" +#include "../src/stopwatch.hpp" +#include "../src/tidy_subsplit_dag.hpp" +#include "../src/nni_engine.hpp" +#include "../src/pv_handler.hpp" +#include "../src/topology_sampler.hpp" +#include "../src/tp_engine.hpp" +#include "../src/tp_choice_map.hpp" +#include "../src/sankoff_matrix.hpp" +#include "../src/sankoff_handler.hpp" +#include "../src/dag_data.hpp" +#include "../src/optimization.hpp" using namespace GPOperations; // NOLINT using PLVType = PLVNodeHandler::PLVType; diff --git a/doctest/gp_engine.hpp b/doctest/gp_engine.hpp new file mode 100644 index 000000000..9b96a3564 --- /dev/null +++ b/doctest/gp_engine.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include "../src/gp_engine.hpp" + +#ifdef DOCTEST_LIBRARY_INCLUDED + +TEST_CASE("GPEngine") { + EigenVectorXd empty_vector; + SitePattern hello_site_pattern = SitePattern::HelloSitePattern(); + GPEngine engine(hello_site_pattern, 5, 5, "_ignore/mmapped_plv.data", + GPEngine::default_rescaling_threshold_, empty_vector, empty_vector, + empty_vector, false); + engine.SetTransitionMatrixToHaveBranchLength(0.75); + // Computed directly: + // https://en.wikipedia.org/wiki/Models_of_DNA_evolution#JC69_model_%28Jukes_and_Cantor_1969%29 + CHECK(fabs(0.52590958087 - engine.GetTransitionMatrix()(0, 0)) < 1e-10); + CHECK(fabs(0.1580301397 - engine.GetTransitionMatrix()(0, 1)) < 1e-10); +} + +#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/src/include_doctest.hpp b/doctest/include_doctest.hpp similarity index 100% rename from src/include_doctest.hpp rename to doctest/include_doctest.hpp diff --git a/doctest/intpack.hpp b/doctest/intpack.hpp new file mode 100644 index 000000000..820f93e63 --- /dev/null +++ b/doctest/intpack.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include "../src/intpack.hpp" + +#ifdef DOCTEST_LIBRARY_INCLUDED +inline void TestPacking(uint32_t a, uint32_t b) { + auto p = PackInts(a, b); + CHECK_EQ(UnpackFirstInt(p), a); + CHECK_EQ(UnpackSecondInt(p), b); +} + +TEST_CASE("intpack") { + TestPacking(3, 4); + TestPacking(UINT32_MAX, 4); + TestPacking(3, UINT32_MAX); + TestPacking(UINT32_MAX - 1, UINT32_MAX); + + // The ints are packed such that the first int takes priority in sorting. + CHECK_LT(PackInts(0, 4), PackInts(1, 0)); +} +#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/doctest/mmapped_matrix.hpp b/doctest/mmapped_matrix.hpp new file mode 100644 index 000000000..50463ece6 --- /dev/null +++ b/doctest/mmapped_matrix.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include "../src/mmapped_matrix.hpp" + +#ifdef DOCTEST_LIBRARY_INCLUDED +TEST_CASE("MmappedMatrix") { + Eigen::Index rows = 4; + Eigen::Index cols = 5; + using MmappedMatrixXd = MmappedMatrix; + { + MmappedMatrixXd mmapped_matrix("_ignore/mmapped_matrix.data", rows, cols); + mmapped_matrix.Get()(rows - 1, cols - 1) = 5.; + } // End of scope, so our mmap is destroyed and file written. + MmappedMatrixXd mmapped_matrix("_ignore/mmapped_matrix.data", rows, cols); + CHECK_EQ(mmapped_matrix.Get()(rows - 1, cols - 1), 5.); +} +#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/doctest/mmapped_plv.hpp b/doctest/mmapped_plv.hpp new file mode 100644 index 000000000..3e72d2b0c --- /dev/null +++ b/doctest/mmapped_plv.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include "../src/mmapped_plv.hpp" + +#ifdef DOCTEST_LIBRARY_INCLUDED +TEST_CASE("MmappedNucleotidePLV") { + MmappedNucleotidePLV mmapped_plv("_ignore/mmapped_plv.data", 10); + auto plvs = mmapped_plv.Subdivide(2); + for (const auto &plv : plvs) { + CHECK_EQ(plv.rows(), MmappedNucleotidePLV::base_count_); + CHECK_EQ(plv.cols(), 5); + } +} +#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/doctest/nni_operation.hpp b/doctest/nni_operation.hpp new file mode 100644 index 000000000..6810bbe92 --- /dev/null +++ b/doctest/nni_operation.hpp @@ -0,0 +1,113 @@ +#pragma once + +#include "../src/nni_operation.hpp" + +#ifdef DOCTEST_LIBRARY_INCLUDED + +// See tree diagram at: +// https://user-images.githubusercontent.com/31897211/136849710-de0dcbe3-dc2b-42b7-b3de-dd9b1a60aaf4.gif +TEST_CASE("NNIOperation") { + // Clades for NNI. + Bitset X("100"); + Bitset Y("010"); + Bitset Z("001"); + // Initial Child and Parent. + Bitset parent_in = Bitset::Subsplit(X, Y | Z); + Bitset child_in = Bitset::Subsplit(Y, Z); + NNIOperation nni_yz = NNIOperation(parent_in, child_in); + // Correct Solutions. + Bitset correct_parent_xy = Bitset::Subsplit(Y, X | Z); + Bitset correct_child_xy = Bitset::Subsplit(X, Z); + NNIOperation correct_nni_xy = NNIOperation(correct_parent_xy, correct_child_xy); + Bitset correct_parent_xz = Bitset::Subsplit(Z, Y | X); + Bitset correct_child_xz = Bitset::Subsplit(Y, X); + NNIOperation correct_nni_xz = NNIOperation(correct_parent_xz, correct_child_xz); + + // Swap X and Y + auto nni_xy = nni_yz.NNIOperationFromNeighboringSubsplits(false); + CHECK_EQ(correct_nni_xy, nni_xy); + // Swap X and Z + auto nni_xz = nni_yz.NNIOperationFromNeighboringSubsplits(true); + CHECK_EQ(correct_nni_xz, nni_xz); + + // Relationship is known (child_in is the rotated clade of parent_in) + auto nni_xy_2 = NNIOperation::NNIOperationFromNeighboringSubsplits( + parent_in, child_in, false, true); + CHECK_EQ(correct_nni_xy, nni_xy_2); + CHECK_THROWS(NNIOperation::NNIOperationFromNeighboringSubsplits(parent_in, child_in, + false, false)); +}; + +TEST_CASE("NNIOperation: NNISet") { + // Clades for NNI. + Bitset X("100"); + Bitset Y("010"); + Bitset Z("001"); + // Initial Child and Parent. + Bitset parent_in = Bitset::Subsplit(X, Y | Z); + Bitset child_in = Bitset::Subsplit(Y, Z); + NNIOperation nni_yz = NNIOperation(parent_in, child_in); + auto nni_xy = nni_yz.NNIOperationFromNeighboringSubsplits(false); + auto nni_xz = nni_yz.NNIOperationFromNeighboringSubsplits(true); + // Insert NNIs in various orders. + NNISet set_of_nnis_1 = NNISet(); + set_of_nnis_1.insert(nni_yz); + set_of_nnis_1.insert(nni_xy); + set_of_nnis_1.insert(nni_xz); + NNISet set_of_nnis_2 = NNISet(); + set_of_nnis_2.insert(nni_xy); + set_of_nnis_2.insert(nni_xz); + set_of_nnis_2.insert(nni_yz); + // Check proper ordering. + for (const auto &set_of_nnis : {set_of_nnis_1, set_of_nnis_2}) { + NNIOperation prv_nni = *set_of_nnis.begin(); + for (const auto &nni : set_of_nnis) { + CHECK_MESSAGE(nni >= prv_nni, "NNIs not ordered in NNISet."); + } + } +} + +TEST_CASE("NNIOperation: NNI Clade Mapping") { + // Clades for NNI. + std::vector clades = {Bitset("100"), Bitset("010"), Bitset("001")}; + // Iterate over all possible assignments of {X,Y,Z} clades to {sister, left, right}. + std::vector> assignments; + for (size_t x = 0; x < 3; x++) { + for (size_t y = 0; y < 3; y++) { + if (y == x) { + continue; + } + for (size_t z = 0; z < 3; z++) { + if ((z == x) || (z == y)) { + continue; + } + assignments.push_back({x, y, z}); + } + } + } + // For each possible pre-NNI, check that NNI produces the correct mapping. + for (const auto assign : assignments) { + const Bitset X(clades[assign[0]]); + const Bitset Y(clades[assign[1]]); + const Bitset Z(clades[assign[2]]); + const Bitset parent = Bitset::Subsplit(X, Y | Z); + const Bitset child = Bitset::Subsplit(Y, Z); + const NNIOperation pre_nni(parent, child); + + for (const auto which_clade_swap : {true, false}) { + const auto post_nni = + pre_nni.NNIOperationFromNeighboringSubsplits(which_clade_swap); + const auto clade_map = + NNIOperation::BuildNNICladeMapFromPreNNIToNNI(pre_nni, post_nni); + for (const auto pre_clade_type : + {NNIClade::ParentSister, NNIClade::ChildLeft, NNIClade::ChildRight}) { + const auto post_clade_type = clade_map[pre_clade_type]; + CHECK_MESSAGE( + pre_nni.GetClade(pre_clade_type) == post_nni.GetClade(post_clade_type), + "NNI Clade Map did not produce a proper mapping."); + } + } + } +} + +#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/doctest/node.hpp b/doctest/node.hpp new file mode 100644 index 000000000..558017f7a --- /dev/null +++ b/doctest/node.hpp @@ -0,0 +1,89 @@ +#pragma once + +#include "../src/node.hpp" + +#ifdef DOCTEST_LIBRARY_INCLUDED + +typedef std::unordered_map TagBitsetMap; + +// Make a map from Tags to the bitset representing the leaves below the Tag. +// Just used for testing now. +TagBitsetMap TagLeafSetMapOf(Node::NodePtr topology) { + TagBitsetMap map; + auto leaf_count = topology->LeafCount(); + topology->Postorder([&map, leaf_count](const Node* node) { + Bitset bitset(static_cast(leaf_count)); + if (node->IsLeaf()) { + bitset.set(node->MaxLeafID()); + } else { + // Take the union of the children below. + for (const auto& child : node->Children()) { + bitset |= map.at(child->Tag()); + } + } + SafeInsert(map, node->Tag(), std::move(bitset)); + }); + return map; +} + +TEST_CASE("Node") { + Node::NodePtrVec examples = Node::ExampleTopologies(); + Node::NodePtr t1 = examples[0]; // 0: (0,1,(2,3)) + Node::NodePtr t1_twin = examples[1]; // 1; (0,1,(2,3)) again + Node::NodePtr t2 = examples[2]; // 2: (0,2,(1,3)) + Node::NodePtr t3 = examples[3]; // 3: (0,(1,(2,3))) + // ((((0,1)7,2)8,(3,4)9)10,5,6)11; + Node::NodePtr tbig = Node::OfParentIdVector({7, 7, 8, 9, 9, 11, 11, 8, 10, 10, 11}); + + std::vector triples; + auto collect_triple = [&triples](const Node* node, const Node* sister, + const Node* parent) { + triples.push_back(std::to_string(node->Id()) + ", " + std::to_string(sister->Id()) + + ", " + std::to_string(parent->Id())); + }; + tbig->TriplePreorder(collect_triple, collect_triple); + std::vector correct_triples( + {"10, 5, 6", "8, 9, 10", "7, 2, 8", "0, 1, 7", "1, 0, 7", "2, 7, 8", "9, 8, 10", + "3, 4, 9", "4, 3, 9", "5, 6, 10", "6, 10, 5"}); + CHECK_EQ(triples, correct_triples); + + // This is actually a non-trivial test (see note in Node constructor above), + // which shows why we need bit rotation. + CHECK_NE(t1->Hash(), t2->Hash()); + + CHECK_EQ(t1, t1_twin); + CHECK_NE(t1, t2); + + // Tree with trifurcation at the root. + Node::NodePtr t1_alt = Node::OfParentIdVector({5, 5, 4, 4, 5}); + CHECK_EQ(t1, t1_alt); + // Bifurcating tree. + Node::NodePtr t3_alt = Node::OfParentIdVector({6, 5, 4, 4, 5, 6}); + CHECK_EQ(t3, t3_alt); + + for (const auto& topology : examples) { + CHECK_EQ(topology, Node::OfParentIdVector(topology->ParentIdVector())); + CHECK_EQ(topology, topology->DeepCopy()); + auto tag_leaf_set_map = TagLeafSetMapOf(topology); + topology->Preorder([&tag_leaf_set_map](const Node* node) { + CHECK_EQ(node->Leaves(), tag_leaf_set_map.at(node->Tag())); + }); + } + + // Check Deroot when we deroot on the right. + CHECK_EQ(t1, t3->Deroot()); + // Check Deroot when we deroot on the left. + CHECK_EQ(Node::OfParentIdVector({3, 3, 3}), + // tree ((0,1)3,2)4 + Node::OfParentIdVector({3, 3, 4, 4})->Deroot()); + + CHECK_EQ(Node::OfParentIdVector({4, 4, 5, 6, 5, 6}), Node::Ladder(4)); + + SizeVector correct_sisters({5, 4, 3, 2}); + SizeVector sisters; + t3->RootedSisterAndLeafTraversal([&sisters](const Node* sister, const Node* leaf) { + sisters.push_back(sister->Id()); + }); + CHECK_EQ(correct_sisters, sisters); +} +#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/doctest/numerical_utils.hpp b/doctest/numerical_utils.hpp new file mode 100644 index 000000000..8cca34ac6 --- /dev/null +++ b/doctest/numerical_utils.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include "../src/numerical_utils.hpp" + +#ifdef DOCTEST_LIBRARY_INCLUDED +TEST_CASE("NumericalUtils") { + double log_x = log(2); + double log_y = log(3); + double log_sum = NumericalUtils::LogAdd(log_x, log_y); + CHECK_LT(fabs(log_sum - 1.609438), 1e-5); + + EigenVectorXd log_vec(10); + double log_sum2 = DOUBLE_NEG_INF; + for (Eigen::Index i = 0; i < log_vec.size(); i++) { + log_vec(i) = log(i + 1); + log_sum2 = NumericalUtils::LogAdd(log_sum2, log_vec(i)); + } + log_sum = NumericalUtils::LogSum(log_vec); + CHECK_LT(fabs(log_sum - 4.007333), 1e-5); + CHECK_LT(fabs(log_sum2 - 4.007333), 1e-5); + + NumericalUtils::ProbabilityNormalizeInLog(log_vec); + for (Eigen::Index i = 0; i < log_vec.size(); i++) { + CHECK_LT(fabs(log_vec(i) - (log(i + 1) - log_sum)), 1e-5); + } + + NumericalUtils::Exponentiate(log_vec); + double sum = 0.0; + for (Eigen::Index i = 0; i < log_vec.size(); i++) { + sum += log_vec(i); + } + CHECK_LT(fabs(sum - 1), 1e-5); + + // Here we use volatile to avoid GCC optimizing away the variable. + volatile double d = 4.; + std::ignore = d; + d /= 0.; + auto fp_description = NumericalUtils::DescribeFloatingPointEnvironmentExceptions(); + CHECK_EQ(*fp_description, + "The following floating point problems have been encountered: FE_DIVBYZERO"); +} +#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/doctest/pv_handler.hpp b/doctest/pv_handler.hpp new file mode 100644 index 000000000..d7d3058b6 --- /dev/null +++ b/doctest/pv_handler.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include "../src/pv_handler.hpp" + +#ifdef DOCTEST_LIBRARY_INCLUDED + +// Check that PLV iterator iterates over all PLVs exactly once. +TEST_CASE("PLVHandler: EnumIterator") { + using namespace PartialVectorType; + const auto plv_types = PLVTypeEnum::TypeArray(); + std::map plv_visited_map; + // Iterate using vector. + for (const PLVType plv_type : plv_types) { + plv_visited_map.insert({plv_type, 0}); + } + // Iterate using EnumIterator. + for (const PLVType plv_type : PLVTypeEnum::Iterator()) { + CHECK_MESSAGE(plv_visited_map.find(plv_type) != plv_visited_map.end(), + "Iterator has PLV not in plv_vector."); + plv_visited_map.at(plv_type) += 1; + } + // Check that each was visited only once. + for (const auto [plv_type, visit_count] : plv_visited_map) { + std::ignore = plv_type; + CHECK_FALSE_MESSAGE(visit_count < 1, "One or more PLVs skipped by EnumIterator."); + CHECK_FALSE_MESSAGE(visit_count > 1, + "One or more PLVs in visited more than once by EnumIterator."); + } +} + +#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/doctest/reindexer.hpp b/doctest/reindexer.hpp new file mode 100644 index 000000000..d1611edbd --- /dev/null +++ b/doctest/reindexer.hpp @@ -0,0 +1,110 @@ +#pragma once + +#include "../src/reindexer.hpp" + +#ifdef DOCTEST_LIBRARY_INCLUDED + +TEST_CASE("Reindexer: IdentityReindexer") { + // Check that IdentityReindexer returns correctly. + Reindexer correct_default({0, 1, 2, 3, 4, 5, 6, 7, 8, 9}); + CHECK_EQ(correct_default, Reindexer::IdentityReindexer(10)); +} + +TEST_CASE("IsValidReindexer") { + // Index appears more than once. + CHECK_FALSE(Reindexer({1, 3, 0, 0}).IsValid()); + // Missing an index and/or index is out of range. + CHECK_FALSE(Reindexer({1, 3, 4, 2}).IsValid()); + // Valid reindexer. + CHECK(Reindexer({1, 3, 0, 2}).IsValid()); +} + +TEST_CASE("Reindexer: Reindex") { + // Check that Reindex throws if the given vector and reindexer have different + // sizes. + SizeVector old_size_vector{7, 8, 9}; + Reindexer reindexer({2, 0, 3, 1}); + CHECK_THROWS(Reindexer::Reindex(old_size_vector, reindexer)); + // Check that Reindex returns correctly. + reindexer = Reindexer({2, 0, 1}); + SizeVector new_size_vector = Reindexer::Reindex(old_size_vector, reindexer); + SizeVector correct_new_size_vector{8, 9, 7}; + CHECK_EQ(new_size_vector, correct_new_size_vector); + // Check that Reindex also works with EigenVectorXd and additional values. + EigenVectorXd old_eigen_vector(3); + old_eigen_vector << 7, 8, 9; + EigenVectorXd additional_values(2); + additional_values << 10, 11; + reindexer = Reindexer({2, 4, 0, 3, 1}); + EigenVectorXd new_eigen_vector = + Reindexer::Reindex(old_eigen_vector, reindexer, additional_values); + EigenVectorXd correct_new_eigen_vector(5); + correct_new_eigen_vector << 9, 11, 7, 10, 8; + CHECK_EQ(new_eigen_vector, correct_new_eigen_vector); +} + +TEST_CASE("Reindexer: InvertReindexer") { + // Check that inverting a vector twice results in the original vector. + Reindexer reindexer({1, 3, 0, 2}); + Reindexer correct_inverted_reindexer({2, 0, 3, 1}); + Reindexer inverted_reindexer = reindexer.InvertReindexer(); + CHECK_EQ(inverted_reindexer, correct_inverted_reindexer); + Reindexer correct_reindexer = Reindexer({1, 3, 0, 2}); + reindexer = inverted_reindexer.InvertReindexer(); + CHECK_EQ(reindexer, correct_reindexer); +} + +TEST_CASE("Reindexer: RemapIdVector") { + // Check that Reindex throws if the given vector has an index out of bounds of the + // reindexer. + SizeVector size_vector{3, 5}; + Reindexer reindexer({2, 0, 3, 1}); + CHECK_THROWS(Reindexer::RemapIdVector(size_vector, reindexer)); + // Check that RemapIdVector returns correctly. + size_vector = {3, 5}; + reindexer = Reindexer({2, 0, 3, 1, 6, 4, 5}); + Reindexer::RemapIdVector(size_vector, reindexer); + SizeVector correct_size_vector = {1, 4}; + CHECK_EQ(size_vector, correct_size_vector); +} + +TEST_CASE("Reindexer: ReassignAndShift") { + // Check that ReassignAndShift returns correctly when old_id > new_id. + Reindexer reindexer({0, 1, 2, 3, 4, 5, 6}); + reindexer.ReassignAndShift(4, 1); + Reindexer correct_reindexer({0, 2, 3, 4, 1, 5, 6}); + CHECK_EQ(reindexer, correct_reindexer); + reindexer.ReassignAndShift(5, 2); + correct_reindexer = Reindexer({0, 3, 4, 5, 1, 2, 6}); + CHECK_EQ(reindexer, correct_reindexer); + reindexer.ReassignAndShift(1, 3); + correct_reindexer = Reindexer({0, 2, 4, 5, 3, 1, 6}); + CHECK_EQ(reindexer, correct_reindexer); + // Check that ReassignAndShift returns correctly when old_id = new_id. + reindexer = Reindexer({1, 0, 4, 6, 5, 3, 2}); + reindexer.ReassignAndShift(4, 4); + correct_reindexer = Reindexer({1, 0, 4, 6, 5, 3, 2}); + CHECK_EQ(reindexer, correct_reindexer); + // Check that ReassignAndShift returns correctly when old_id < new_id. + reindexer = Reindexer({6, 0, 4, 1, 5, 3, 2}); + reindexer.ReassignAndShift(1, 5); + correct_reindexer = Reindexer({6, 0, 3, 5, 4, 2, 1}); + CHECK_EQ(reindexer, correct_reindexer); +} + +TEST_CASE("Reindexer: ComposeWith") { + // Check that identity reindexer composed with a second reindexer results in that + // reindexer. + Reindexer identity_reindexer, inverted_reindexer, pairswap_reindexer, + composed_reindexer, correct_reindexer; + identity_reindexer = Reindexer::IdentityReindexer(6); + inverted_reindexer = Reindexer({5, 4, 3, 2, 1, 0}); + pairswap_reindexer = Reindexer({1, 0, 3, 2, 5, 4}); + composed_reindexer = identity_reindexer; + composed_reindexer = composed_reindexer.ComposeWith(inverted_reindexer); + composed_reindexer = composed_reindexer.ComposeWith(pairswap_reindexer); + correct_reindexer = Reindexer({4, 5, 2, 3, 0, 1}); + CHECK_EQ(composed_reindexer, correct_reindexer); +} + +#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/doctest/rooted_sbn_instance.hpp b/doctest/rooted_sbn_instance.hpp new file mode 100644 index 000000000..37afbea26 --- /dev/null +++ b/doctest/rooted_sbn_instance.hpp @@ -0,0 +1,655 @@ +#pragma once + +#include "../src/rooted_sbn_instance.hpp" + +#ifdef DOCTEST_LIBRARY_INCLUDED + +#include "eigen_sugar.hpp" +#include "../doctest/doctest_constants.hpp" + +// Centered finite difference approximation of the derivative wrt rate. +std::vector DerivativeStrictClock(RootedSBNInstance& inst) { + double eps = 0.00000001; + std::vector rates; + std::vector gradients; + + for (auto& tree : inst.tree_collection_.trees_) { + rates.push_back(tree.rates_[0]); + tree.rates_.assign(tree.rates_.size(), rates.back() - eps); + } + auto lm = inst.LogLikelihoods(); + + int i = 0; + for (auto& tree : inst.tree_collection_.trees_) { + tree.rates_.assign(tree.rates_.size(), rates[i++] + eps); + } + auto lp = inst.LogLikelihoods(); + + for (size_t index = 0; index < lm.size(); index++) { + gradients.push_back((lp[index] - lm[index]) / (2. * eps)); + } + return gradients; +} + +// Centered finite difference approximation of the derivative wrt to each rate. +std::vector> DerivativeRelaxedClock(RootedSBNInstance& inst) { + double eps = 0.00000001; + std::vector> gradients; + std::vector lp; + std::vector lm; + size_t edge_count = inst.TaxonCount() * 2 - 2; + + for (size_t index = 0; index < edge_count; index++) { + std::vector gradient; + std::vector rates; + for (size_t i = 0; i < inst.tree_collection_.TreeCount(); i++) { + double value = inst.tree_collection_.trees_[i].rates_[index]; + rates.push_back(value); + inst.tree_collection_.trees_[i].rates_[index] = rates.back() - eps; + } + lm = inst.LogLikelihoods(); + + for (size_t i = 0; i < inst.tree_collection_.TreeCount(); i++) { + inst.tree_collection_.trees_[i].rates_[index] = rates[i] + eps; + } + lp = inst.LogLikelihoods(); + + for (size_t i = 0; i < inst.tree_collection_.TreeCount(); i++) { + inst.tree_collection_.trees_[i].rates_[index] = rates[i]; + gradient.push_back((lp[i] - lm[i]) / (2. * eps)); + } + + gradients.push_back(gradient); + } + return gradients; +} + +RootedSBNInstance MakeFiveTaxonRootedInstance() { + RootedSBNInstance inst("charlie"); + inst.ReadNewickFile("data/five_taxon_rooted.nwk"); + inst.ProcessLoadedTrees(); + return inst; +} + +TEST_CASE("RootedSBNInstance: subsplit support and TrainSimpleAverage") { + auto inst = MakeFiveTaxonRootedInstance(); + auto pretty_indexer = inst.PrettyIndexer(); + StringSet pretty_indexer_set{pretty_indexer.begin(), pretty_indexer.end()}; + // The indexer_ is to index the sbn_parameters_. Note that neither of these + // data structures attempt to catalog the complete collection of rootsplits or + // PCSPs, but just those that are present in the the input trees. + // + // The indexer_ and sbn_parameters_ are laid out as follows (I'll just call it + // the "index" in what follows). Say there are rootsplit_count rootsplits in + // the support. + // The first rootsplit_count entries of the index are assigned to the + // rootsplits (again, those rootsplits that are present for some rooting of + // the unrooted input trees). The rest of the entries of the index are laid out as + // blocks of parameters for PCSPs that share the same parent. Take a look at the + // description of PCSP bitsets (and the unit tests) in bitset.hpp to understand the + // notation used here. + // + // In contrast to the unrooted case, we can write out the pretty indexer here and + // verify it by hand. There is the block structure in which the two children of + // 10000|01111 are grouped together. + StringSet correct_pretty_indexer_set{ + "00000|11111|00111", // ((x0,x1),(x2,(x3,x4))) + "00000|11111|01111", // (x0,(((x1,x3),x2),x4)) and ((x1,((x2,x4),x3)),x0) + "00000|11111|00010", // (x3,((x0,(x4,x1)),x2)) + "00100|01010|00010", // ((x1,x3),x2) + "00111|11000|01000", // ((x0,x1),(x2,(x3,x4))) + "00100|00011|00001", // (x2,(x3,x4)) + "11000|00111|00011", // ((x0,x1),(x2,(x3,x4))) + "00100|11001|01001", // ((x0,(x4,x1)),x2) + "10000|01001|00001", // (x0,(x4,x1)) + "01000|00111|00010", // (x1,((x2,x4),x3)) + "10000|01111|00001", // (x0,(((x1,x3),x2),x4)) + "10000|01111|00111", // ((x1,((x2,x4),x3)),x0) + "00010|00101|00001", // ((x2,x4),x3) + "00001|01110|00100", // (((x1,x3),x2),x4) + "00010|11101|00100" // (x3,((x0,(x4,x1)),x2)) + }; + CHECK_EQ(pretty_indexer_set, correct_pretty_indexer_set); + + // Test of rooted IndexerRepresentationOf. + // Topology is ((0,1),(2,(3,4)));, or with internal nodes ((0,1)5,(2,(3,4)6)7)8; + auto indexer_test_rooted_topology = Node::OfParentIdVector({5, 5, 7, 6, 6, 8, 7, 8}); + auto correct_rooted_indexer_representation = + StringSet({"00000|11111|00111", "11000|00111|00011", "00100|00011|00001", + "00111|11000|01000"}); + CHECK_EQ(inst.StringIndexerRepresentationOf(indexer_test_rooted_topology, + out_of_sample_index), + correct_rooted_indexer_representation); + + inst.TrainSimpleAverage(); + StringVector correct_taxon_names({"x0", "x1", "x2", "x3", "x4"}); + CHECK_EQ(inst.SBNSupport().TaxonNames(), correct_taxon_names); + StringDoubleVector correct_parameters({{"00000|11111|00111", 0.25}, + {"00000|11111|01111", 0.5}, + {"00000|11111|00010", 0.25}, + {"00100|01010|00010", 1}, + {"00111|11000|01000", 1}, + {"00100|00011|00001", 1}, + {"11000|00111|00011", 1}, + {"00100|11001|01001", 1}, + {"10000|01001|00001", 1}, + {"01000|00111|00010", 1}, + {"10000|01111|00001", 0.5}, + {"10000|01111|00111", 0.5}, + {"00010|00101|00001", 1}, + {"00001|01110|00100", 1}, + {"00010|11101|00100", 1}}); + std::sort(correct_parameters.begin(), correct_parameters.end()); + auto parameters = inst.PrettyIndexedSBNParameters(); + std::sort(parameters.begin(), parameters.end()); + CHECK_EQ(correct_parameters.size(), parameters.size()); + for (size_t i = 0; i < correct_parameters.size(); i++) { + CHECK_EQ(correct_parameters[i].first, parameters[i].first); + CHECK_LT(fabs(correct_parameters[i].second - parameters[i].second), 1e-8); + } +} + +TEST_CASE("RootedSBNInstance: UnconditionalSubsplitProbabilities") { + RootedSBNInstance inst("rooted instance"); + inst.ReadNewickFile("data/five_taxon_rooted_more.nwk"); + inst.ProcessLoadedTrees(); + inst.TrainSimpleAverage(); + // See diagram at https://github.com/phylovi/bito/issues/349#issuecomment-898022916 + // Numbering in comments is... node: subsplit. + StringDoubleMap correct_parameters({{"1100000111", 0.5}, // 10: 01|234 + {"1000001111", 0.3}, // 15: 0|1234 + {"1110100010", 0.2}, // 19: 0124|3 + {"1100100100", 0.2}, // 18: 014|2 + {"0100000111", 0.1}, // 14: 1|234 + {"0111000001", 0.2}, // 13: 123|4 + {"0101000100", 0.2}, // 12: 13|2 + {"1000001001", 0.2}, // 17: 0|14 + {"0010000011", 0.4}, // 8: 2|34 + {"0011000001", 0.2}, // 6: 23|4 + {"1000001000", 0.5}, // 9: 0|1 + {"0100000010", 0.2}, // 11: 1|3 + {"0100000001", 0.2}, // 16: 1|4 + {"0010000010", 0.2}, // 5: 2|3 + {"0001000001", 0.4}} // 7: 3|4 + ); + + auto subsplit_probabilities = inst.UnconditionalSubsplitProbabilities(); + CHECK_EQ(correct_parameters.size(), subsplit_probabilities.size()); + for (const auto& [subsplit, probability] : subsplit_probabilities) { + CHECK_LT(fabs(correct_parameters.at(subsplit.ToString()) - probability), 1e-8); + } +} + +// Instance SA-trained on a sample of 20-taxon trees. +RootedSBNInstance MakeRootedSimpleAverageInstance() { + RootedSBNInstance inst("rooted instance"); + inst.ReadNewickFile("data/rooted_simple_average.nwk"); + inst.ProcessLoadedTrees(); + inst.TrainSimpleAverage(); + return inst; +} + +TEST_CASE("RootedSBNInstance: TrainSimpleAverage on 20 taxa") { + auto inst = MakeRootedSimpleAverageInstance(); + auto results = inst.PrettyIndexedSBNParameters(); + // Values confirmed with + // https://github.com/mdkarcher/vbsupertree/commit/b7f87f711e8a1044b7c059b5a92e94c117d8cee1 + auto correct_map = + CSV::StringDoubleMapOfCSV("data/rooted_simple_average_results.csv"); + for (const auto& [found_string, found_probability] : results) { + CHECK(fabs(found_probability - correct_map.at(found_string)) < 1e-6); + } +} + +RootedSBNInstance MakeFluInstance(bool initialize_time_trees) { + RootedSBNInstance inst("charlie"); + inst.ReadNewickFile("data/fluA.tree"); + inst.ParseDatesFromTaxonNames(initialize_time_trees); + inst.ReadFastaFile("data/fluA.fa"); + PhyloModelSpecification simple_specification{"JC69", "constant", "strict"}; + inst.PrepareForPhyloLikelihood(simple_specification, 1); + return inst; +} + +TEST_CASE("RootedSBNInstance: gradients") { + auto inst = MakeFluInstance(true); + for (auto& tree : inst.tree_collection_.trees_) { + tree.rates_.assign(tree.rates_.size(), 0.001); + } + + auto likelihood = inst.LogLikelihoods(); + double physher_ll = -4777.616349; + double physher_jacobian = -9.25135166; + double physher_ll_jacobian = physher_ll + physher_jacobian; + CHECK_LT(fabs(likelihood[0] - physher_ll_jacobian), 0.0001); + + auto gradients = inst.PhyloGradients(); + std::vector physher_gradients = { + -0.593654, 6.441290, 11.202945, 5.173924, -0.904631, 2.731402, 3.157131, + 7.082914, 10.305417, 13.988206, 20.709336, 48.897993, 99.164949, 130.205747, + 17.314019, 21.033290, -1.336335, 12.259822, 22.887291, 27.176564, 47.487426, + 3.637276, 12.955169, 15.315953, 83.254605, -3.806996, 105.385095, 4.874023, + 22.754466, 6.036534, 25.651478, 29.535185, 29.598789, 1.817247, 10.598685, + 76.259248, 56.481423, 10.679778, 6.587179, 3.330556, -4.622247, 33.417304, + 63.415767, 188.809515, 23.540875, 17.421076, 1.222568, 22.372012, 34.239511, + 3.486115, 4.098873, 13.200954, 19.726890, 96.808738, 4.240029, 7.414585, + 48.871694, 3.488516, 82.969065, 9.009334, 8.032474, 3.981016, 6.543650, + 53.702423, 37.835952, 2.840831, 7.517186, 19.936861}; + for (size_t i = 0; i < physher_gradients.size(); i++) { + CHECK_LT(fabs(gradients[0].gradient_[PhyloGradient::ratios_root_height_key_][i] - + physher_gradients[i]), + 0.0001); + } + CHECK_LT(fabs(gradients[0].log_likelihood_ - physher_ll), 0.0001); +} + +TEST_CASE("RootedSBNInstance: clock gradients") { + auto inst = MakeFluInstance(true); + for (auto& tree : inst.tree_collection_.trees_) { + tree.rates_.assign(tree.rates_.size(), 0.001); + } + + auto likelihood = inst.LogLikelihoods(); + double physher_ll = -4777.616349; + double physher_jacobian = -9.25135166; + double physher_ll_jacobian = physher_ll + physher_jacobian; + CHECK_LT(fabs(likelihood[0] - physher_ll_jacobian), 0.0001); + + // Gradient with a strict clock. + auto gradients_strict = inst.PhyloGradients(); + std::vector gradients_strict_approx = DerivativeStrictClock(inst); + CHECK_LT(fabs(gradients_strict[0].gradient_[PhyloGradient::clock_model_key_][0] - + gradients_strict_approx[0]), + 0.001); + CHECK_LT(fabs(gradients_strict[0].log_likelihood_ - physher_ll), 0.001); + + // Gradient with a "relaxed" clock. + auto& tree = inst.tree_collection_.trees_[0]; + // Make a clock with some rate variation. + for (size_t i = 0; i < tree.rates_.size(); i++) { + tree.rates_[i] *= i % 3 + 1.0; + } + tree.rate_count_ = tree.rates_.size(); + + auto gradients_relaxed = inst.PhyloGradients(); + auto gradients_relaxed_approx = DerivativeRelaxedClock(inst); + + for (size_t j = 0; j < gradients_relaxed_approx.size(); j++) { + CHECK_LT(fabs(gradients_relaxed[0].gradient_[PhyloGradient::clock_model_key_][j] - + gradients_relaxed_approx[j][0]), + 0.001); + } +} + +TEST_CASE("RootedSBNInstance: GTR gradients") { + auto inst = MakeFluInstance(true); + PhyloModelSpecification gtr_specification{"GTR", "constant", "strict"}; + inst.PrepareForPhyloLikelihood(gtr_specification, 1); + for (auto& tree : inst.tree_collection_.trees_) { + tree.rates_.assign(tree.rates_.size(), 0.001); + } + auto param_block_map = inst.GetPhyloModelParamBlockMap(); + EigenVectorXdRef frequencies = param_block_map.at(GTRModel::frequencies_key_); + EigenVectorXdRef rates = param_block_map.at(GTRModel::rates_key_); + frequencies << 0.1, 0.2, 0.3, 0.4; + rates << 0.05, 0.1, 0.15, 0.20, 0.25, 0.25; + + auto likelihood = inst.LogLikelihoods(); + double phylotorch_ll = -5221.438941335706; + double physher_jacobian = -9.25135166; + double expected_ll_jacobian = phylotorch_ll + physher_jacobian; + CHECK_LT(fabs(likelihood[0] - expected_ll_jacobian), 0.001); + + auto gradients = inst.PhyloGradients(); + std::vector phylotorch_gradients = {49.06451538, 151.83105912, 26.40235659, + -8.25135661, 75.29759338, 352.56545247, + 90.07046995, 30.12301652}; + for (size_t i = 0; i < phylotorch_gradients.size(); i++) { + CHECK_LT(fabs(gradients[0].gradient_[PhyloGradient::substitution_model_key_][i] - + phylotorch_gradients[i]), + 0.001); + } + CHECK_LT(fabs(gradients[0].log_likelihood_ - phylotorch_ll), 0.001); +} + +TEST_CASE("RootedSBNInstance: HKY gradients") { + auto inst = MakeFluInstance(true); + PhyloModelSpecification specification{"HKY", "constant", "strict"}; + inst.PrepareForPhyloLikelihood(specification, 1); + for (auto& tree : inst.tree_collection_.trees_) { + tree.rates_.assign(tree.rates_.size(), 0.001); + } + auto param_block_map = inst.GetPhyloModelParamBlockMap(); + EigenVectorXdRef frequencies = + param_block_map.at(SubstitutionModel::frequencies_key_); + EigenVectorXdRef rates = param_block_map.at(SubstitutionModel::rates_key_); + frequencies << 0.1, 0.2, 0.3, 0.4; + rates << 3.0; + + auto likelihood = inst.LogLikelihoods(); + double phylotorch_ll = -4931.770106816288; + double physher_jacobian = -9.25135166; + double expected_ll_jacobian = phylotorch_ll + physher_jacobian; + CHECK_LT(fabs(expected_ll_jacobian - likelihood[0]), 0.001); + + auto gradients = inst.PhyloGradients(); + std::vector phylotorch_gradients = {18.218397759598506, 309.56536079428355, + 47.15713892857574, 42.98132033283943}; + for (size_t i = 0; i < phylotorch_gradients.size(); i++) { + CHECK_LT( + fabs(gradients[0].gradient_["substitution_model"][i] - phylotorch_gradients[i]), + 0.001); + } + CHECK_LT(fabs(phylotorch_ll - gradients[0].log_likelihood_), 0.0001); +} + +TEST_CASE("RootedSBNInstance: Weibull gradients") { + auto inst = MakeFluInstance(true); + PhyloModelSpecification weibull_specification{"JC69", "weibull+4", "strict"}; + inst.PrepareForPhyloLikelihood(weibull_specification, 1); + for (auto& tree : inst.tree_collection_.trees_) { + tree.rates_.assign(tree.rates_.size(), 0.001); + } + auto param_block_map = inst.GetPhyloModelParamBlockMap(); + param_block_map.at(WeibullSiteModel::shape_key_).setConstant(0.1); + + auto likelihood = inst.LogLikelihoods(); + double physher_ll = -4618.2062529058; + double physher_jacobian = -9.25135166; + double physher_ll_jacobian = physher_ll + physher_jacobian; + CHECK_LT(fabs(likelihood[0] - physher_ll_jacobian), 0.0001); + + // Gradient wrt Weibull site model. + auto gradients = inst.PhyloGradients(); + double physher_gradient = -5.231329; + CHECK_LT(fabs(gradients[0].gradient_["site_model"][0] - physher_gradient), 0.001); + CHECK_LT(fabs(gradients[0].log_likelihood_ - physher_ll), 0.001); +} + +TEST_CASE("RootedSBNInstance: parsing dates") { + RootedSBNInstance inst("charlie"); + inst.ReadNexusFile("data/test_beast_tree_parsing.nexus"); + inst.ParseDatesFromTaxonNames(true); + std::vector dates; + for (const auto& [tag, date] : inst.tree_collection_.GetTagDateMap()) { + std::ignore = tag; + dates.push_back(date); + } + std::sort(dates.begin(), dates.end()); + CHECK_EQ(dates[0], 0); + CHECK_EQ(dates.back(), 80.0); + + RootedSBNInstance alt_inst("betty"); + alt_inst.ReadNexusFile("data/test_beast_tree_parsing.nexus"); + alt_inst.tree_collection_.ParseDatesFromCSV("data/test_beast_tree_parsing.csv", true); + CHECK_EQ(inst.tree_collection_.GetTagDateMap(), + alt_inst.tree_collection_.GetTagDateMap()); +} + +TEST_CASE("RootedSBNInstance: uninitialized time trees raise an exception") { + auto inst = MakeFluInstance(false); + CHECK_THROWS(inst.PhyloGradients()); +} + +TEST_CASE("RootedSBNInstance: reading SBN parameters from a CSV") { + auto inst = MakeFiveTaxonRootedInstance(); + inst.ReadSBNParametersFromCSV("data/test_modifying_sbn_parameters.csv"); + auto pretty_indexer = inst.PrettyIndexer(); + auto gpcsp_it = + std::find(pretty_indexer.begin(), pretty_indexer.end(), "10000|01111|00001"); + CHECK(gpcsp_it != pretty_indexer.end()); + auto gpcsp_idx = std::distance(pretty_indexer.begin(), gpcsp_it); + CHECK_LT(fabs(inst.sbn_parameters_[gpcsp_idx] - log(0.15)), 1e-8); + inst.SetSBNParameters({}, false); + CHECK_EQ(inst.sbn_parameters_[gpcsp_idx], DOUBLE_MINIMUM); + CHECK_THROWS(inst.SetSBNParameters({{"10000|01111|00001", -5.}}, false)); +} + +TEST_CASE("RootedSBNInstance: SBN parameter round trip") { + std::string csv_test_file_path = "_ignore/for_sbn_parameter_round_trip.csv"; + auto inst = MakeRootedSimpleAverageInstance(); + auto original_normalized_sbn_parameters = inst.NormalizedSBNParameters(); + inst.SBNParametersToCSV(csv_test_file_path); + inst.ReadSBNParametersFromCSV(csv_test_file_path); + auto reloaded_normalized_sbn_parameters = inst.NormalizedSBNParameters(); + CheckVectorXdEquality(original_normalized_sbn_parameters, + reloaded_normalized_sbn_parameters, 1e-6); +} + +TEST_CASE("RootedSBNInstance: BuildCollectionByDuplicatingFirst") { + auto empty_collection = RootedTreeCollection(); + CHECK_THROWS(empty_collection.BuildCollectionByDuplicatingFirst(5)); + auto inst = MakeFiveTaxonRootedInstance(); + auto trees = inst.tree_collection_.BuildCollectionByDuplicatingFirst(5); + CHECK_EQ(trees.GetTree(0), trees.GetTree(1)); + // Check that the trees don't refer to the same place in memory. + CHECK_NE(&trees.GetTree(0), &trees.GetTree(1)); + inst = MakeFluInstance(true); + auto& base_flu_tree = inst.tree_collection_.GetTree(0); + trees = inst.tree_collection_.BuildCollectionByDuplicatingFirst(5); + CHECK_EQ(base_flu_tree, trees.GetTree(1)); +} + +TEST_CASE("RootedSBNInstance: PhyloFlags for Gradient Requests") { + // GP Instance default output for gradients. + auto CreateNewInstance = []() { + auto inst = MakeFluInstance(true); + PhyloModelSpecification gtr_specification{"GTR", "constant", "strict"}; + inst.PrepareForPhyloLikelihood(gtr_specification, 1); + for (auto& tree : inst.tree_collection_.trees_) { + tree.rates_.assign(tree.rates_.size(), 0.001); + } + auto param_block_map = inst.GetPhyloModelParamBlockMap(); + EigenVectorXdRef frequencies = param_block_map.at(GTRModel::frequencies_key_); + EigenVectorXdRef rates = param_block_map.at(GTRModel::rates_key_); + frequencies << 0.1, 0.2, 0.3, 0.4; + rates << 0.05, 0.1, 0.15, 0.20, 0.25, 0.25; + return inst; + }; + // "Golden" instance for determining correctness. + auto gold_inst = CreateNewInstance(); + auto gold_likelihoods = gold_inst.LogLikelihoods(); + auto gold_gradients = gold_inst.PhyloGradients(); + size_t num_trees = gold_inst.tree_collection_.trees_.size(); + + using FlagMap = std::map; + using FlagVector = std::vector; + using MapkeyVector = std::vector; + // Split map into keys and values. + auto SplitMapIntoKeysAndValues = + [](const FlagMap& map) -> std::pair { + FlagVector keys; + MapkeyVector values; + for (const auto& [key, value] : map) { + keys.push_back(key); + values.push_back(value); + } + return std::make_pair(keys, values); + }; + + // Iterate through all flag combinations. + auto IterateOverAllCombinations = + [](FlagMap& all_flags_mapkeys, FlagVector& all_flags, + std::function func) { + size_t num_flags = all_flags_mapkeys.size(); + size_t num_combinations = pow(2, num_flags); + for (size_t i = 0; i < num_combinations; i++) { + FlagMap used_flags_mapkeys, unused_flags_mapkeys; + // Split between groups of used and unused flags. + for (size_t j = 1, k = 0; j < num_combinations; j <<= 1, k += 1) { + if ((j & i)) { + used_flags_mapkeys.insert(*all_flags_mapkeys.find(all_flags[k])); + } else { + unused_flags_mapkeys.insert(*all_flags_mapkeys.find(all_flags[k])); + } + } + func(used_flags_mapkeys, unused_flags_mapkeys, all_flags_mapkeys, false); + func(used_flags_mapkeys, unused_flags_mapkeys, all_flags_mapkeys, true); + } + }; + + // Test that expected flagged keys are populated with correct data, + // and that unflagged keys are not stored in map. + auto ComparePhyloGradients = + [&CreateNewInstance, &gold_gradients, &SplitMapIntoKeysAndValues, &num_trees]( + FlagMap& used_flags_mapkeys, FlagMap& unused_flags_mapkeys, + FlagMap& all_flags, bool pass_externally = false) { + // Create instance and run phylogradients with used_flags. + auto inst = CreateNewInstance(); + const auto [used_flags, used_mapkeys] = + SplitMapIntoKeysAndValues(used_flags_mapkeys); + const auto [unused_flags, unused_mapkeys] = + SplitMapIntoKeysAndValues(unused_flags_mapkeys); + std::ignore = unused_flags; + std::vector gradients; + // pass flags via external arguments + if (pass_externally) { + PhyloFlags phylo_flags; + for (const auto& flag : used_flags) { + phylo_flags.SetFlag(flag); + } + phylo_flags.SetRunDefaultsFlag(false); + gradients = inst.PhyloGradients(phylo_flags); + } + // pass flags via internal instance + else { + inst.MakePhyloFlags(); + auto& flags = inst.GetPhyloFlags(); + for (const auto& flag : used_flags) { + flags.SetFlag(flag); + } + flags.SetRunDefaultsFlag(false); + gradients = inst.PhyloGradients(); + flags.ClearFlags(); + } + // Check that used fields are keyed and populated correctly. + for (size_t i = 0; i < num_trees; i++) { + auto& grad_map = gradients[i].gradient_; + auto& gold_grad_map = gold_gradients[i].gradient_; + // Check used fields are not properly populated. + for (const auto& used_mapkey : used_mapkeys) { + CHECK_MESSAGE(grad_map.find(used_mapkey.GetKey()) != grad_map.end(), + "grad_map does not have a key that should exist."); + auto& gold_grad_data = gold_grad_map[used_mapkey.GetKey()]; + auto& grad_data = grad_map[used_mapkey.GetKey()]; + DoubleVector abs_diff = DoubleVector(gold_grad_data.size()); + std::transform(gold_grad_data.begin(), gold_grad_data.end(), + grad_data.begin(), abs_diff.begin(), + [](const double a, const double b) { return abs(a - b); }); + double max_diff = *std::max_element(abs_diff.begin(), abs_diff.end()); + CHECK_MESSAGE(max_diff < 0.01, + "gold_grad_map and grad_map did not produce the same data " + "for the same flag."); + } + // Check unused fields are not populated. + for (const auto& unused_mapkey : unused_mapkeys) { + CHECK_MESSAGE(grad_map.find(unused_mapkey.GetKey()) == grad_map.end(), + "grad_map has a key that should not exist."); + } + } + }; + + // Test gradient "include" options. + // Pairs of input flags to output mapkeys. + FlagMap gradient_flags_mapkeys; + gradient_flags_mapkeys.insert( + {PhyloGradientFlagOptions::clock_model_, PhyloGradientMapkeys::clock_model_}); + gradient_flags_mapkeys.insert({PhyloGradientFlagOptions::ratios_root_height_, + PhyloGradientMapkeys::ratios_root_height_}); + gradient_flags_mapkeys.insert({PhyloGradientFlagOptions::substitution_model_, + PhyloGradientMapkeys::substitution_model_}); + gradient_flags_mapkeys.insert({PhyloGradientFlagOptions::substitution_model_, + PhyloGradientMapkeys::substitution_model_rates_}); + gradient_flags_mapkeys.insert( + {PhyloGradientFlagOptions::substitution_model_, + PhyloGradientMapkeys::substitution_model_frequencies_}); + + auto gradient_flags = SplitMapIntoKeysAndValues(gradient_flags_mapkeys).first; + IterateOverAllCombinations(gradient_flags_mapkeys, gradient_flags, + ComparePhyloGradients); + + // Test likelihood "exclude" options. + auto LikelihoodExcludeLogDeterminant = [&CreateNewInstance, &gold_likelihoods]() { + auto inst = CreateNewInstance(); + StringBoolVector flag_vector = { + {LogLikelihoodFlagOptions::include_log_det_jacobian_likelihood_.GetFlag(), + false}}; + auto flags = PhyloFlags(flag_vector, true); + double likelihood_exclude_log_det = inst.LogLikelihoods(flags)[0]; + double log_det = RootedGradientTransforms::LogDetJacobianHeightTransform( + inst.tree_collection_.trees_[0]); + double gold_likelihood = gold_likelihoods[0]; + CHECK_MESSAGE( + gold_likelihood != likelihood_exclude_log_det, + "LogLikelihood should not be equal to (LogLikelihoodExcludingLogdet."); + CHECK_MESSAGE(gold_likelihood == (likelihood_exclude_log_det + log_det), + "LogLikelihood should be equal to (LogLikelihoodExcludingLogdet + " + "LogDetJacobianHeightTransform."); + }; + LikelihoodExcludeLogDeterminant(); + + // Test gradient "exclude" options. + auto GradientExcludeLogDeterminant = [&CreateNewInstance, &gold_gradients]() { + auto inst = CreateNewInstance(); + StringBoolVector flag_vector = { + {PhyloGradientFlagOptions::include_log_det_jacobian_gradient_.GetFlag(), + false}}; + auto flags = PhyloFlags(flag_vector, true); + GradientMap grad_map = inst.PhyloGradients(flags)[0].gradient_; + DoubleVector exclude_log_det = + grad_map[PhyloGradientMapkeys::ratios_root_height_.GetKey()]; + DoubleVector log_det = RootedGradientTransforms::GradientLogDeterminantJacobian( + inst.tree_collection_.trees_[0]); + DoubleVector include_log_det = + gold_gradients[0].gradient_[PhyloGradientMapkeys::ratios_root_height_.GetKey()]; + + double max_diff; + DoubleVector abs_diff = DoubleVector(include_log_det.size()); + std::transform(include_log_det.begin(), include_log_det.end(), + exclude_log_det.begin(), abs_diff.begin(), + [](const double a, const double b) { return abs(a - b); }); + max_diff = *std::max_element(abs_diff.begin(), abs_diff.end()); + CHECK_MESSAGE(max_diff > 0.01, + "Gradient should not be equal to GradientExcludingLogDet."); + DoubleVector exclude_log_det_plus_log_det = DoubleVector(include_log_det.size()); + std::transform(exclude_log_det.begin(), exclude_log_det.end(), log_det.begin(), + exclude_log_det_plus_log_det.begin(), + [](const double a, const double b) { return a + b; }); + std::transform(include_log_det.begin(), include_log_det.end(), + exclude_log_det_plus_log_det.begin(), abs_diff.begin(), + [](const double a, const double b) { return abs(a - b); }); + max_diff = *std::max_element(abs_diff.begin(), abs_diff.end()); + CHECK_MESSAGE(max_diff < 0.01, + "Gradient should be equal to (GradientExcludingLogdet + " + "GradientLogDetJacobian)."); + }; + GradientExcludeLogDeterminant(); + + // Test gradient "set" options. + auto GradientSetDelta = [&CreateNewInstance, &gold_gradients]() { + std::ignore = gold_gradients; + auto inst = CreateNewInstance(); + StringDoubleVector flag_vector = { + {PhyloGradientFlagOptions::set_gradient_delta_.GetFlag(), 1.0e1}}; + auto flags = PhyloFlags(flag_vector, true); + GradientMap grad_map = inst.PhyloGradients(flags)[0].gradient_; + DoubleVector subst_grad = + grad_map[PhyloGradientMapkeys::substitution_model_.GetKey()]; + // delta = 1.0e-6 (default) + DoubleVector gold_subst_grad_1e6 = {49.0649, 151.831, 26.4022, -8.25114, + 75.2975, 352.565, 90.0701, 30.1228}; + // delta = 1.0e1 + DoubleVector gold_subst_grad_1e1 = {-73.2611, 25.4074, -33.2865, -54.0479, + 47.9938, -2696.06, -84.2954, 6.0563}; + DoubleVector abs_diff = DoubleVector(subst_grad.size()); + std::transform(subst_grad.begin(), subst_grad.end(), gold_subst_grad_1e1.begin(), + abs_diff.begin(), + [](const double a, const double b) { return abs(a - b); }); + double max_diff = *std::max_element(abs_diff.begin(), abs_diff.end()); + CHECK_MESSAGE(max_diff < 0.01, + "Delta value set by flag did not result in correct gradient values."); + }; + GradientSetDelta(); +} + +#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/doctest/rooted_tree.hpp b/doctest/rooted_tree.hpp new file mode 100644 index 000000000..24487b667 --- /dev/null +++ b/doctest/rooted_tree.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include "../src/rooted_tree.hpp" + +#ifdef DOCTEST_LIBRARY_INCLUDED +TEST_CASE("RootedTree") { + // To understand this test, please see + // https://github.com/phylovi/bito/issues/187#issuecomment-618421183 + auto tree = RootedTree::Example(); + std::vector correct_height_ratios({1. / 3.5, 1.5 / 4., 7.}); + for (size_t i = 0; i < correct_height_ratios.size(); ++i) { + CHECK_EQ(correct_height_ratios[i], tree.height_ratios_[i]); + } + std::vector correct_node_heights({5., 3., 0., 1., 2., 4.5, 7.}); + std::vector correct_node_bounds({5., 3., 0., 1., 1., 3., 5.}); + std::vector correct_branch_lengths({2., 1.5, 2., 1., 2.5, 2.5}); + for (size_t i = 0; i < correct_node_heights.size(); ++i) { + CHECK_EQ(correct_node_heights[i], tree.node_heights_[i]); + CHECK_EQ(correct_node_bounds[i], tree.node_bounds_[i]); + } + for (size_t i = 0; i < correct_branch_lengths.size(); ++i) { + CHECK_EQ(correct_branch_lengths[i], tree.branch_lengths_[i]); + } + // Test ratios to heights. + const double arbitrary_dummy_number = -5.; + std::fill(tree.LeafCount() + tree.node_heights_.begin(), // First internal node. + tree.node_heights_.end(), arbitrary_dummy_number); + EigenVectorXd new_height_ratios(3); + // Issue #205: eliminate this code duplication. + // Root height is multiplied by 2. + new_height_ratios << 1. / 3.5, 1.5 / 4., 14.; + std::vector new_correct_node_heights({5., 3., 0., 1., 2.75, 7.125, 14.}); + std::vector new_correct_branch_lengths({9., 4.125, 2.75, 1.75, 4.375, 6.875}); + tree.InitializeTimeTreeUsingHeightRatios(new_height_ratios); + for (size_t i = 0; i < correct_node_heights.size(); ++i) { + CHECK_EQ(new_correct_node_heights[i], tree.node_heights_[i]); + } + for (size_t i = 0; i < correct_branch_lengths.size(); ++i) { + CHECK_EQ(new_correct_branch_lengths[i], tree.branch_lengths_[i]); + } +} +#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/doctest/sankoff_handler.hpp b/doctest/sankoff_handler.hpp new file mode 100644 index 000000000..df201e7cb --- /dev/null +++ b/doctest/sankoff_handler.hpp @@ -0,0 +1,132 @@ +#pragma once + +#include "../src/sankoff_handler.hpp" + +#ifdef DOCTEST_LIBRARY_INCLUDED + +TEST_CASE("SankoffHandler: Tests on single site sequence.") { + auto fasta_file = "data/hello_single_nucleotide.fasta"; + auto newick_file = "data/hello_rooted.nwk"; + Alignment alignment = Alignment::ReadFasta(fasta_file); + Driver driver; + RootedTreeCollection tree_collection = + RootedTreeCollection::OfTreeCollection(driver.ParseNewickFile(newick_file)); + SitePattern site_pattern = SitePattern(alignment, tree_collection.TagTaxonMap()); + Node::NodePtr topology = tree_collection.GetTree(0).Topology(); + size_t taxon_count = site_pattern.TaxonCount(); + + // transitions have cost 1 and transversions have cost 2.5 + auto costs = CostMatrix(); + costs << 0., 2.5, 1., 2.5, // + 2.5, 0., 2.5, 1., // + 1., 2.5, 0., 2.5, // + 2.5, 1., 2.5, 0.; // + + SankoffHandler sh = SankoffHandler(costs, site_pattern, "_ignore/mmapped_psv.data"); + + // testing RunSankoff for one site + sh.RunSankoff(topology); + + // testing GenerateLeafPartials() method (which is run as first step of RunSankoff) + SankoffPartialVec leaves_test = sh.PartialsAtPattern(PSVType::PLeft, 0); + auto big_double = SankoffHandler::big_double_; + SankoffPartial leaves_correct_pattern_0(4, topology->Id() + 1); + // column 1 is G(jupiter), column 2 is C(mars), Column 3 is G(saturn) + leaves_correct_pattern_0 << big_double, big_double, big_double, 0., 0., big_double, + 0., big_double, 0., 0., 0., big_double, 0., 0., 0., big_double, big_double, + big_double, 0., 0.; + for (size_t r = 0; r < taxon_count; r++) { + CHECK(leaves_test[r].isApprox(leaves_correct_pattern_0.col(r))); + } + + // test parsimony score for RunSankoff + CHECK_LT(fabs(sh.ParsimonyScore(0) - 2.5), 1e-10); + + // testing 3rd constructor: SankoffHandler(SankoffMatrix, SitePattern) constructor + SankoffMatrix sm = SankoffMatrix(costs); + SankoffHandler sh2 = SankoffHandler(sm, site_pattern, "_ignore/mmapped_psv.data"); + + // testing ParentPartial() + auto child_partials = Eigen::Matrix(); + child_partials << 2.5, 3.5, 3.5, 3.5, 2.5, 3.5, 3.5, 4.5; + auto parent_test = Eigen::Matrix(); + parent_test.setZero(); + for (size_t child = 0; child < 2; child++) { + parent_test += sh2.ParentPartial(child_partials.col(child)); + } + auto parent_correct = Eigen::Matrix(); + parent_correct << 6., 7., 6., 8.; + CHECK(parent_test.isApprox(parent_correct)); +} + +TEST_CASE("SankoffHandler: Asymmetric cost matrix test on single site sequence.") { + auto fasta_file = "data/hello_single_nucleotide.fasta"; + auto newick_file = "data/hello_rooted.nwk"; + Alignment alignment = Alignment::ReadFasta(fasta_file); + Driver driver; + RootedTreeCollection tree_collection = + RootedTreeCollection::OfTreeCollection(driver.ParseNewickFile(newick_file)); + SitePattern site_pattern = SitePattern(alignment, tree_collection.TagTaxonMap()); + Node::NodePtr topology = tree_collection.GetTree(0).Topology(); + + // transitions have cost 1 and transversions have cost 2.5 + auto costs = CostMatrix(); + costs << 0., 2., 3., 4., // + 5., 0., 7., 8., // + 9., 10., 0., 12., // + 13., 14., 15., 0.; // + + SankoffHandler sh = SankoffHandler(costs, site_pattern, "_ignore/mmapped_psv.data"); + sh.RunSankoff(topology); + CHECK_LT(fabs(sh.ParsimonyScore(0) - 8.), 1e-10); +} + +TEST_CASE("SankoffHandler: Testing sequence gap characters in GenerateLeafPartials()") { + auto fasta_file = "data/hello.fasta"; + auto newick_file = "data/hello_rooted.nwk"; + Alignment alignment = Alignment::ReadFasta(fasta_file); + Driver driver; + RootedTreeCollection tree_collection = + RootedTreeCollection::OfTreeCollection(driver.ParseNewickFile(newick_file)); + SitePattern site_pattern = SitePattern(alignment, tree_collection.TagTaxonMap()); + Node::NodePtr topology = tree_collection.GetTree(0).Topology(); + size_t taxon_count = site_pattern.TaxonCount(); + + // test set up for SankoffHandler with default cost matrix + SankoffHandler default_sh = SankoffHandler(site_pattern, "_ignore/mmapped_psv.data"); + + // testing GenerateLeafPartials() method + default_sh.GenerateLeafPartials(); + auto leaves_test = default_sh.PartialsAtPattern(PSVType::PLeft, 14); + SankoffPartial leaves_correct_pattern_14(4, topology->Id() + 1); + auto big_double = SankoffHandler::big_double_; + // column 1 is G(jupiter), column 2 is -(mars), Column 3 is G(saturn) + leaves_correct_pattern_14 << big_double, 0., big_double, 0., 0., big_double, 0., + big_double, 0., 0., 0., 0., 0., 0., 0., big_double, 0., big_double, 0., 0.; + for (size_t r = 0; r < taxon_count; r++) { + CHECK(leaves_test[r].isApprox(leaves_correct_pattern_14.col(r))); + } +} + +TEST_CASE("SankoffHandler: RunSankoff and ParsimonyScore Tests") { + auto fasta_file = "data/parsimony_leaf_seqs.fasta"; + auto newick_file = "data/parsimony_tree_0_score_75.0.nwk"; + Alignment alignment = Alignment::ReadFasta(fasta_file); + Driver driver; + RootedTreeCollection tree_collection = + RootedTreeCollection::OfTreeCollection(driver.ParseNewickFile(newick_file)); + SitePattern site_pattern = SitePattern(alignment, tree_collection.TagTaxonMap()); + Node::NodePtr topology = tree_collection.GetTree(0).Topology(); + + // test set up for SankoffHandler with default cost matrix + SankoffHandler default_sh = SankoffHandler(site_pattern, "_ignore/mmapped_psv.data"); + + double parsimony_score_correct = 75.; + default_sh.RunSankoff(topology); + + for (NodeId node_id = 0; node_id < topology->Id() + 1; node_id++) { + CHECK_LT(fabs(default_sh.ParsimonyScore(node_id) - parsimony_score_correct), 1e-10); + } +} + +#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/doctest/sankoff_matrix.hpp b/doctest/sankoff_matrix.hpp new file mode 100644 index 000000000..c745e0a5f --- /dev/null +++ b/doctest/sankoff_matrix.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include "../src/sankoff_matrix.hpp" + +#ifdef DOCTEST_LIBRARY_INCLUDED +enum Nucleotides { A, C, G, T }; + +TEST_CASE("SankoffMatrix: Testing SankoffMatrix Getter/Setter Methods") { + auto cm = SankoffMatrix(); + CHECK_LT(fabs(cm.GetCost(0, 0) - 0.), 1e-10); + CHECK_LT(fabs(cm.GetCost(G, C) - 1.), 1e-10); + + cm.UpdateMatrix(A, G, 3.); + auto test_matrix = Eigen::Matrix(); + test_matrix << 0., 1., 3., 1., 1., 0., 1., 1., 1., 1., 0., 1., 1., 1., 1., 0.; + CHECK(test_matrix.isApprox(cm.GetMatrix())); + CHECK_LT(fabs(cm.GetCost(A, G) - 3.), 1e-10); + CHECK_THROWS(cm.UpdateMatrix(G, G, 3.)); +} + +TEST_CASE("SankoffMatrix: Create SankoffMatrix from given cost matrix") { + auto costs = Eigen::Matrix(); + costs << 0., 2.5, 1., 2.5, 2.5, 0., 2.5, 1., 1., 2.5, 0., 2.5, 2.5, 1., 2.5, 0.; + auto cm = SankoffMatrix(costs); + CHECK_LT(fabs(cm.GetCost(A, C) - 2.5), 1e-10); + + auto costs_invalid = Eigen::Matrix(); + // non-zero values on diagonal + costs_invalid << 1., 2., 3., 4., 5., 6., 7., 8., 9., 10., 11., 12., 13., 14., 15., + 16.; + CHECK_THROWS(new SankoffMatrix(costs_invalid)); +} + +#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/doctest/sbn_maps.hpp b/doctest/sbn_maps.hpp new file mode 100644 index 000000000..a22a92dfc --- /dev/null +++ b/doctest/sbn_maps.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include "../src/sbn_maps.hpp" + +#ifdef DOCTEST_LIBRARY_INCLUDED + +TEST_CASE("SBNMaps") { + auto topology0 = Node::ExampleTopologies()[0]; + + // (0,1,(2,3)4)5; + auto correct_id_id_set_map = + std::unordered_map({{5, Bitset("111111")}, + {1, Bitset("010000")}, + {0, Bitset("100000")}, + {2, Bitset("001000")}, + {3, Bitset("000100")}, + {4, Bitset("001110")}}); + + for (const auto& iter : SBNMaps::IdIdSetMapOf(topology0)) { + CHECK_EQ(correct_id_id_set_map.at(iter.first), iter.second); + } + + // Tests comparing to vbpi appear in Python test code. + // Tests of IndexerRepresentationOf in unrooted_sbn_instance.hpp. +} + +#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/doctest/sbn_probability.hpp b/doctest/sbn_probability.hpp new file mode 100644 index 000000000..e22c16ac9 --- /dev/null +++ b/doctest/sbn_probability.hpp @@ -0,0 +1,149 @@ +#pragma once + +#include "../src/sbn_probability.hpp" + +#ifdef DOCTEST_LIBRARY_INCLUDED +// Here we hardcode in "ground truth" values from +// https://github.com/zcrabbit/sbn. +// See https://github.com/phylovi/bito/pull/167 for details on how this code was +// run. + +EigenVectorXd ExpectedSAVector() { + EigenVectorXd expected_SA(100); + expected_SA << 0.1563122979972875, 0.1563122979972875, 0.1225902102462595, + 0.003813409758997299, 0.06405479308023015, 0.1225902102462595, + 0.006496265198833325, 0.07224488861161361, 0.07224488861161361, + 0.09211800063278303, 0.050235906509724905, 0.07224488861161361, + 0.1225902102462595, 0.004000036290989688, 0.000785970418989169, + 0.015740902738698836, 0.0007369945657169131, 0.092118000632783, + 0.050235906509724905, 0.15631229799728746, 0.004517219839302666, + 0.1225902102462595, 0.020070904829444434, 0.07224488861161361, + 0.00826101112145943, 0.1563122979972875, 0.050235906509724905, 0.092118000632783, + 6.6925344669117846e-06, 0.012000108872969078, 0.00107615168209648, + 0.00487602847011386, 0.00524108566424323, 0.1563122979972875, + 0.006470871758283066, 0.050235906509724905, 0.0034098101830328945, + 0.1563122979972875, 0.07224488861161361, 0.0036073007280301274, + 0.009488612393535554, 0.005657493542553093, 0.007936324421697116, + 0.1225902102462595, 0.1563122979972875, 0.0064788184404525545, 0.1563122979972875, + 0.006493612301549656, 0.1225902102462595, 0.1225902102462595, 0.06405479308023015, + 0.06405479308023015, 0.092118000632783, 0.006224174813063337, + 0.006496265198833326, 0.15631229799728746, 0.002156957252761021, + 0.008283255738394914, 0.012789795178479234, 0.1225902102462595, + 0.0057598153715508514, 0.1225902102462595, 0.1563122979972875, + 0.12259021024625949, 0.15631229799728746, 0.004505082963907347, + 0.15631229799728746, 0.06405479308023015, 0.050235906509724905, + 0.00016398394968572145, 0.1225902102462595, 0.15631229799728752, + 0.050235906509724905, 0.1563122979972875, 0.003933969974285363, + 0.09211800063278297, 0.07224488861161361, 0.1563122979972875, 0.12259021024625949, + 0.012098733104319886, 0.00028556179453190954, 0.005744340855421819, + 0.00299072194405209, 0.0031839409290006357, 0.092118000632783, 0.1225902102462595, + 0.0015789121009827038, 0.1563122979972875, 0.1225902102462595, + 0.009488612393535554, 0.008035213534600344, 0.008283255738394914, + 0.1225902102462595, 0.1563122979972875, 0.012789795178479234, 0.00826101112145943, + 0.15631229799728746, 0.1563122979972875, 0.015740902738698836, + 0.0014116239862321806; + return expected_SA; +} + +// Expected EM vectors with alpha = 0. +std::tuple ExpectedEMVectorsAlpha0() { + // 1 iteration of EM with alpha = 0. + EigenVectorXd expected_EM_0_1(100); + expected_EM_0_1 << 0.15636219370379975, 0.15636219370379975, 0.12263720847530954, + 0.0038161261257420274, 0.0641198257552132, 0.12263720847530954, + 0.006486659269203554, 0.07229291766902365, 0.07229291766902365, + 0.09217334703350938, 0.05029011931468532, 0.07229291766902365, + 0.12263720847530954, 0.004003916595779366, 0.0007856587472007348, + 0.01573322403407416, 0.0007374660239687015, 0.09217334703350938, + 0.05029011931468532, 0.15636219370379975, 0.004512401354352734, + 0.12263720847530954, 0.02005981904435064, 0.07229291766902365, + 0.008265715290818319, 0.15636219370379975, 0.05029011931468532, + 0.09217334703350938, 6.696764561669613e-06, 0.0120117421642559, + 0.0010771644269441463, 0.004896166585246872, 0.005249064166033721, + 0.15636219370379975, 0.006473675486085066, 0.05029011931468532, + 0.0032873955583662736, 0.15636219370379975, 0.07229291766902365, + 0.0036100017361450775, 0.009434696399838294, 0.0056590388012875215, + 0.007977630170932788, 0.12263720847530954, 0.15636219370379975, + 0.006482919565829875, 0.15636219370379975, 0.006552518113293269, + 0.12263720847530954, 0.12263720847530954, 0.0641198257552132, 0.0641198257552132, + 0.09217334703350938, 0.0062569566301722964, 0.006486659269203554, + 0.15636219370379975, 0.002157895979240378, 0.008270476015894701, + 0.012800407342249945, 0.12263720847530954, 0.0057533024269482485, + 0.12263720847530954, 0.15636219370379975, 0.12263720847530954, + 0.15636219370379975, 0.004509802427487057, 0.15636219370379975, + 0.0641198257552132, 0.05029011931468532, 0.00016356223078203, 0.12263720847530954, + 0.15636219370379975, 0.05029011931468532, 0.15636219370379975, + 0.0039366703105347105, 0.09217334703350938, 0.07229291766902365, + 0.15636219370379975, 0.12263720847530954, 0.01202920464244238, + 0.00028206667942050513, 0.005749993943460951, 0.0029930413584166584, + 0.00329320520591652, 0.09217334703350938, 0.12263720847530954, + 0.0015794165564839767, 0.15636219370379975, 0.12263720847530954, + 0.009434696399838294, 0.0080145373179354, 0.008270476015894701, + 0.12263720847530954, 0.15636219370379975, 0.012800407342249945, + 0.008265715290818319, 0.15636219370379975, 0.15636219370379975, + 0.01573322403407416, 0.0014108855861473272; + + // 23 iterations of EM with alpha = 0. + EigenVectorXd expected_EM_0_23(100); + expected_EM_0_23 << 0.17652149361215352, 0.17652149361215352, 0.13955673648946823, + 0.0064491608851600735, 0.05848390318274005, 0.13955673648946823, + 0.015825262650921094, 0.056647494412346275, 0.056647494412346275, + 0.07263326598713499, 0.046048205076811774, 0.056647494412346275, + 0.13955673648946823, 0.004489402988562556, 0.0008454696589522312, + 0.01696511452269485, 0.0007597630896160637, 0.07263326598713499, + 0.046048205076811774, 0.17652149361215352, 0.007245923330084535, + 0.13955673648946823, 0.021613944434184986, 0.056647494412346275, + 0.014714698661351094, 0.17652149361215352, 0.046048205076811774, + 0.07263326598713499, 9.203216973858281e-06, 0.012351059173222767, + 0.0009871200936099765, 0.004930024591016917, 0.00491121394019874, + 0.17652149361215352, 0.003698082517142961, 0.046048205076811774, + 0.005102502844331727, 0.17652149361215352, 0.056647494412346275, + 0.002888359861489329, 0.010768901737247942, 0.004400293712986419, + 0.009082480764670433, 0.13955673648946823, 0.17652149361215352, + 0.011355455748479546, 0.1765214936121535, 0.004904756855047689, + 0.13955673648946823, 0.13955673648946823, 0.05848390318274005, + 0.05848390318274005, 0.07263326598713499, 0.0071809191341133454, + 0.015825262650921094, 0.17652149361215352, 0.002748656816901356, + 0.020534769315758854, 0.015225481783759537, 0.13955673648946823, + 0.009401422446411185, 0.13955673648946823, 0.17652149361215352, + 0.13955673648946823, 0.17652149361215352, 0.005835611123722098, + 0.17652149361215352, 0.05848390318274005, 0.046048205076811774, + 0.00021701159553434837, 0.13955673648946823, 0.17652149361215352, + 0.046048205076811774, 0.17652149361215352, 0.003632704913367913, + 0.07263326598713499, 0.056647494412346275, 0.17652149361215352, + 0.13955673648946823, 0.013620606755282007, 0.0013952624329697414, + 0.007542542618796534, 0.004980505041486884, 0.0049091058169283925, + 0.07263326598713499, 0.13955673648946823, 0.0018467480153313562, + 0.17652149361215352, 0.13955673648946823, 0.010768901737247942, + 0.00801061474692208, 0.020534769315758854, 0.13955673648946823, + 0.17652149361215352, 0.015225481783759537, 0.014714698661351094, + 0.1765214936121535, 0.17652149361215352, 0.01696511452269485, + 0.0010195073633053277; + + return {expected_EM_0_1, expected_EM_0_23}; +} + +// Expected EM vector with alpha = 0.5 +EigenVectorXd ExpectedEMVectorAlpha05() { + // 100 iterations of EM with alpha = 0.5, from Andy's Rev implementation. + EigenVectorXd expected_EM_05_100(100); + expected_EM_05_100 << 0.156334, 0.156334, 0.1226123, 0.003816548, 0.06410458, + 0.1226123, 0.006490481, 0.07228169, 0.07228169, 0.09216116, 0.05027706, + 0.07228169, 0.1226123, 0.00400394, 0.0007856247, 0.01573095, 0.0007376477, + 0.09216116, 0.05027706, 0.156334, 0.004514539, 0.1226123, 0.02005742, 0.07228169, + 0.008265798, 0.156334, 0.05027706, 0.09216116, 6.695972e-06, 0.01201178, + 0.001077308, 0.004899056, 0.005247481, 0.156334, 0.006473987, 0.05027706, + 0.003445344, 0.156334, 0.07228169, 0.0036098, 0.009448465, 0.005659673, + 0.007976663, 0.1226123, 0.156334, 0.006482844, 0.156334, 0.006417704, 0.1226123, + 0.1226123, 0.06410458, 0.06410458, 0.09216116, 0.006256103, 0.006490481, 0.156334, + 0.002157991, 0.008275552, 0.01279726, 0.1226123, 0.005756163, 0.1226123, 0.156334, + 0.1226123, 0.156334, 0.004509251, 0.156334, 0.06410458, 0.05027706, 0.0001636864, + 0.1226123, 0.156334, 0.05027706, 0.156334, 0.003936684, 0.09216116, 0.07228169, + 0.156334, 0.1226123, 0.01204707, 0.0002831188, 0.005749409, 0.002993311, + 0.003384393, 0.09216116, 0.1226123, 0.001579132, 0.156334, 0.1226123, 0.009448465, + 0.008020635, 0.008275552, 0.1226123, 0.156334, 0.01279726, 0.008265798, 0.156334, + 0.156334, 0.01573095, 0.001410898; + return expected_EM_05_100; +} + +#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/doctest/site_model.hpp b/doctest/site_model.hpp new file mode 100644 index 000000000..2819880d5 --- /dev/null +++ b/doctest/site_model.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include "../src/site_model.hpp" + +#ifdef DOCTEST_LIBRARY_INCLUDED +#include +TEST_CASE("SiteModel") { + // Test 1: First we test using the "built in" default values. + auto weibull_model = std::make_unique(4, 1.0); + const EigenVectorXd rates = weibull_model->GetCategoryRates(); + EigenVectorXd rates_r(4); + rates_r << 0.1457844, 0.5131316, 1.0708310, 2.2702530; + CheckVectorXdEquality(rates, rates_r, 0.0001); + + // Test 2: Now set param_vector using SetParameters. + weibull_model = std::make_unique(4, 1.0); + EigenVectorXd param_vector(1); + param_vector << 0.1; + weibull_model->SetParameters(param_vector); + rates_r << 4.766392e-12, 1.391131e-06, 2.179165e-03, 3.997819e+00; + const EigenVectorXd rates2 = weibull_model->GetCategoryRates(); + CheckVectorXdEquality(rates2, rates_r, 0.0001); + + // Test 3: Check proportions. + const EigenVectorXd proportions = weibull_model->GetCategoryProportions(); + CheckVectorXdEquality(0.25, proportions, 0.0001); + + // Test 4: Check sum rates[i]*proportions[i]==1. + CHECK_LT(fabs(rates.dot(proportions) - 1.), 0.0001); + CHECK_LT(fabs(rates2.dot(proportions) - 1.), 0.0001); +} +#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/doctest/site_pattern.hpp b/doctest/site_pattern.hpp new file mode 100644 index 000000000..fd255e9bd --- /dev/null +++ b/doctest/site_pattern.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include "../src/site_pattern.hpp" + +#ifdef DOCTEST_LIBRARY_INCLUDED +TEST_CASE("SitePattern") { + CharIntMap symbol_table = SitePattern::GetSymbolTable(); + SymbolVector symbol_vector = SitePattern::SymbolVectorOf(symbol_table, "-tgcaTGCA?"); + SymbolVector correct_symbol_vector = {4, 3, 2, 1, 0, 3, 2, 1, 0, 4}; + CHECK_EQ(symbol_vector, correct_symbol_vector); +} +#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/doctest/stick_breaking_transform.hpp b/doctest/stick_breaking_transform.hpp new file mode 100644 index 000000000..e904a93c5 --- /dev/null +++ b/doctest/stick_breaking_transform.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include "../src/stick_breaking_transform.hpp" + +#ifdef DOCTEST_LIBRARY_INCLUDED +TEST_CASE("BreakingStickTransform") { + StickBreakingTransform a; + EigenVectorXd y(3); + y << 1., 2., 3.; + EigenVectorXd x_expected(4); + // x_expected = + // torch.distributions.StickBreakingTransform()(torch.tensor([1., 2., 3.])) + x_expected << 0.475367, 0.412879, 0.106454, 0.00530004; + EigenVectorXd x = a(y); + CheckVectorXdEquality(x, x_expected, 1.e-5); + EigenVectorXd yy = a.inverse(x); + CheckVectorXdEquality(y, yy, 1e-5); + // log_abs_det_jacobian_expected = + // torch.distributions.StickBreakingTransform().log_abs_det_jacobian(y,x) + double log_abs_det_jacobian_expected = -9.108352; + CHECK(a.log_abs_det_jacobian(x, y) == + doctest::Approx(log_abs_det_jacobian_expected).epsilon(1.e-5)); +} +#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/doctest/stopwatch.hpp b/doctest/stopwatch.hpp new file mode 100644 index 000000000..f943ce043 --- /dev/null +++ b/doctest/stopwatch.hpp @@ -0,0 +1,64 @@ +#pragma once + +#include "../src/stopwatch.hpp" + +#ifdef DOCTEST_LIBRARY_INCLUDED + +TEST_CASE("Stopwatch") { + Stopwatch watch(false, Stopwatch::TimeScale::MillisecondScale); + // Functions not allowed while clock isn't running. + CHECK_THROWS(watch.GetElapsedOfCurrentInterval()); + CHECK_THROWS(watch.GetElapsedOfCurrentLap()); + CHECK_THROWS(watch.GetLatestInterval()); + CHECK_THROWS(watch.Lap()); + CHECK_THROWS(watch.Stop()); + + watch.Start(); + // Getting latest lap before first lap exists. + CHECK_THROWS(watch.GetLatestLap()); + // Getting latest interval before first interval exists. + CHECK_THROWS(watch.GetLatestInterval()); + Stopwatch::Sleep(3); + watch.Stop(); + + auto interval_1 = watch.GetLatestInterval(); + Stopwatch::Sleep(5); + + watch.Start(); + Stopwatch::Sleep(7); + auto interval_1_during_next_interval = watch.GetLatestInterval(); + Stopwatch::Sleep(11); + auto lap_1 = watch.Lap(); + Stopwatch::Sleep(13); + auto lap_1_during_next_lap = watch.GetLatestLap(); + auto interval_2_midinterval = watch.GetElapsedOfCurrentInterval(); + Stopwatch::Sleep(17); + watch.GetElapsedOfCurrentLap(); + Stopwatch::Sleep(19); + auto interval_2 = watch.Stop(); + + // Latest should fetch the last completed lap or interval, even if clock is running. + CHECK_EQ(interval_1, interval_1_during_next_interval); + CHECK_EQ(lap_1, lap_1_during_next_lap); + // The mid-interval time should be less than the total time. + CHECK_GT(interval_2, interval_2_midinterval); + + auto laps = watch.GetLaps(); + auto total = watch.GetTotal(); + std::vector intervals = {interval_1, interval_2}; + auto sum_laps = std::accumulate(laps.begin(), laps.end(), 0.0); + auto sum_intervals = std::accumulate(intervals.begin(), intervals.end(), 0.0); + + watch.Clear(); + CHECK_EQ(watch.GetTotal(), 0.0); + CHECK_EQ(watch.GetLaps().size(), 0); + + watch.Start(); + // Function not allowed while clock is running. + CHECK_THROWS(watch.Start()); + + CHECK_EQ(doctest::Approx(sum_laps), total); + CHECK_EQ(doctest::Approx(sum_intervals), total); +}; + +#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/doctest/subsplit_dag_node.hpp b/doctest/subsplit_dag_node.hpp new file mode 100644 index 000000000..80b165b25 --- /dev/null +++ b/doctest/subsplit_dag_node.hpp @@ -0,0 +1,94 @@ +#pragma once + +#include "../src/subsplit_dag_node.hpp" + +#ifdef DOCTEST_LIBRARY_INCLUDED + +inline DAGVertex& GetStorage(const GenericSubsplitDAGNode& node) { + return node.node_; +} +inline const DAGVertex& GetStorage( + const GenericSubsplitDAGNode& node) { + return node.node_; +} + +/* Create the following topology: + [0] + / \ + 0 1 + / \ + [1] [2] + / \ + 2 3 + / \ + [3] [4] + */ + +static SubsplitDAGStorage MakeStorage() { + SubsplitDAGStorage storage; + storage.AddLine({EdgeId(0), NodeId(0), NodeId(1), SubsplitClade::Left}); + storage.AddLine({EdgeId(1), NodeId(0), NodeId(2), SubsplitClade::Right}); + storage.AddLine({EdgeId(2), NodeId(1), NodeId(3), SubsplitClade::Left}); + storage.AddLine({EdgeId(3), NodeId(1), NodeId(4), SubsplitClade::Right}); + + storage.AddVertex(DAGVertex{}.SetId(NodeId(0))) + .AddNeighbor(Direction::Leafward, SubsplitClade::Left, NodeId(1), EdgeId(0)) + .AddNeighbor(Direction::Leafward, SubsplitClade::Right, NodeId(2), EdgeId(1)); + storage.AddVertex(DAGVertex{}.SetId(NodeId(1))) + .AddNeighbor(Direction::Rootward, SubsplitClade::Left, NodeId(0), EdgeId(0)) + .AddNeighbor(Direction::Leafward, SubsplitClade::Left, NodeId(3), EdgeId(2)) + .AddNeighbor(Direction::Leafward, SubsplitClade::Right, NodeId(4), EdgeId(3)); + storage.AddVertex(DAGVertex{}.SetId(NodeId(2))) + .AddNeighbor(Direction::Rootward, SubsplitClade::Right, NodeId(0), EdgeId(1)); + storage.AddVertex(DAGVertex{}.SetId(NodeId(3))) + .AddNeighbor(Direction::Rootward, SubsplitClade::Left, NodeId(1), EdgeId(2)); + storage.AddVertex(DAGVertex{}.SetId(NodeId(4))) + .AddNeighbor(Direction::Rootward, SubsplitClade::Right, NodeId(1), EdgeId(3)); + return storage; +} + +TEST_CASE("SubsplitDAGStorage: LinesView structured binding") { + auto storage = MakeStorage(); + + size_t i = 0; + for (auto [node_ids, line_id] : storage.GetLines()) { + std::ignore = line_id; + auto [parent_id, child_id] = node_ids; + switch (i++) { + case 0: + CHECK_EQ(parent_id, 0); + CHECK_EQ(child_id, 1); + break; + case 1: + CHECK_EQ(parent_id, 0); + CHECK_EQ(child_id, 2); + break; + case 2: + CHECK_EQ(parent_id, 1); + CHECK_EQ(child_id, 3); + break; + case 3: + CHECK_EQ(parent_id, 1); + CHECK_EQ(child_id, 4); + break; + default: + Failwith("More lines than expected"); + } + } +} + +TEST_CASE("SubsplitDAGStorage: Neighbors iterator") { + auto storage = MakeStorage(); + + CHECK_EQ(*GetStorage(storage.GetVertices()[1]) + .GetNeighbors(Direction::Leafward, SubsplitClade::Left) + .begin(), + 3); + CHECK_EQ(GetStorage(storage.GetVertices()[1]) + .GetNeighbors(Direction::Leafward, SubsplitClade::Left) + .begin() + .GetEdge(), + 2); +} + +#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/doctest/substitution_model.hpp b/doctest/substitution_model.hpp new file mode 100644 index 000000000..ef11c5832 --- /dev/null +++ b/doctest/substitution_model.hpp @@ -0,0 +1,59 @@ +#pragma once + +#include "../src/substitution_model.hpp" + +#ifdef DOCTEST_LIBRARY_INCLUDED +#include +TEST_CASE("SubstitutionModel") { + auto CheckEigenvalueEquality = [](EigenVectorXd eval1, EigenVectorXd eval2) { + std::sort(eval1.begin(), eval1.end()); + std::sort(eval2.begin(), eval2.end()); + CheckVectorXdEquality(eval1, eval2, 0.0001); + }; + auto gtr_model = std::make_unique(); + auto hky_model = std::make_unique(); + auto jc_model = std::make_unique(); + // Test 1: First we test using the "built in" default values. + CheckEigenvalueEquality(jc_model->GetEigenvalues(), gtr_model->GetEigenvalues()); + CheckEigenvalueEquality(jc_model->GetEigenvalues(), hky_model->GetEigenvalues()); + EigenVectorXd param_vector(10); + // Test 2: Now try out ParameterSegmentMapOf. + gtr_model = std::make_unique(); + // First zero out our param_vector. + param_vector.setZero(); + // We can use ParameterSegmentMapOf to get two "views" into our parameter + // vector. + auto parameter_map = + gtr_model->GetBlockSpecification().ParameterSegmentMapOf(param_vector); + auto frequencies = parameter_map.at(SubstitutionModel::frequencies_key_); + auto rates = parameter_map.at(SubstitutionModel::rates_key_); + // When we modify the contents of these views, that changes param_vector. + frequencies.setConstant(0.25); + rates.setConstant(1.0 / 6.0); + // We can then set param_vector and go forward as before. + gtr_model->SetParameters(param_vector); + CheckEigenvalueEquality(jc_model->GetEigenvalues(), gtr_model->GetEigenvalues()); + // Test 3: Compare to eigenvalues from R. + frequencies << 0.479367, 0.172572, 0.140933, 0.207128; + rates << 0.060602, 0.402732, 0.028230, 0.047910, 0.407249, 0.053277; + gtr_model->SetParameters(param_vector); + EigenVectorXd eigen_values_r(4); + eigen_values_r << -2.567992e+00, -1.760838e+00, -4.214918e-01, 1.665335e-16; + CheckEigenvalueEquality(eigen_values_r, gtr_model->GetEigenvalues()); + // Test HKY against GTR + EigenVectorXd hky_param_vector(5); + hky_param_vector.setZero(); + auto hky_parameter_map = + hky_model->GetBlockSpecification().ParameterSegmentMapOf(hky_param_vector); + auto hky_frequencies = hky_parameter_map.at(SubstitutionModel::frequencies_key_); + auto hky_kappa = hky_parameter_map.at(SubstitutionModel::rates_key_); + hky_frequencies << 0.1, 0.2, 0.3, 0.4; + hky_kappa.setConstant(3.0); + hky_model->SetParameters(hky_param_vector); + frequencies << 0.1, 0.2, 0.3, 0.4; + rates << 0.1, 0.3, 0.1, 0.1, 0.3, 0.1; + gtr_model->SetParameters(param_vector); + CheckEigenvalueEquality(gtr_model->GetEigenvalues(), hky_model->GetEigenvalues()); + CHECK(gtr_model->GetQMatrix().isApprox(hky_model->GetQMatrix())); +} +#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/doctest/task_processor.hpp b/doctest/task_processor.hpp new file mode 100644 index 000000000..b5d31315e --- /dev/null +++ b/doctest/task_processor.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include "../src/task_processor.hpp" + +#ifdef DOCTEST_LIBRARY_INCLUDED +TEST_CASE("TaskProcessor") { + std::queue executor_queue; + std::queue work_queue; + std::vector results(8); + // Say we have 4 executors. + for (auto i = 0; i < 4; i++) { + executor_queue.push(i); + } + // Our Work in this example is just size_t's. + for (size_t i = 0; i < results.size(); i++) { + work_queue.push(i); + } + // And our task is just to cast this size_t to a float and store it in the + // corresponding location of the results array. + auto task = [&results](int /*executor*/, size_t work) { + // std::cout << "work " << work << " on " << executor << std::endl; + results[work] = static_cast(work); + }; + TaskProcessor processor(executor_queue, work_queue, task); + processor.Wait(); + std::vector correct_results({0, 1, 2, 3, 4, 5, 6, 7}); + CHECK_EQ(results, correct_results); +} +#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/doctest/taxon_name_munging.hpp b/doctest/taxon_name_munging.hpp new file mode 100644 index 000000000..f17e420e6 --- /dev/null +++ b/doctest/taxon_name_munging.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include "../src/taxon_name_munging.hpp" + +#ifdef DOCTEST_LIBRARY_INCLUDED +TEST_CASE("TaxonNameMunging") { + using namespace TaxonNameMunging; + + std::string unquoted_test(R"raw(hello 'there" friend)raw"); + std::string double_quoted_test(R"raw("this is a \" test")raw"); + std::string double_quoted_dequoted(R"raw(this is a " test)raw"); + std::string single_quoted_test(R"raw('this is a \' test')raw"); + std::string single_quoted_dequoted(R"raw(this is a ' test)raw"); + + CHECK_EQ(QuoteString(unquoted_test), R"raw("hello 'there\" friend")raw"); + CHECK_EQ(DequoteString(double_quoted_test), double_quoted_dequoted); + CHECK_EQ(DequoteString(single_quoted_test), single_quoted_dequoted); + CHECK_EQ(DequoteString(QuoteString(unquoted_test)), unquoted_test); + + TagStringMap test_map( + {{2, unquoted_test}, {3, double_quoted_test}, {5, single_quoted_test}}); + TagStringMap expected_test_map( + {{2, unquoted_test}, {3, double_quoted_dequoted}, {5, single_quoted_dequoted}}); + CHECK_EQ(expected_test_map, DequoteTagStringMap(test_map)); + + // Test of TagDateMapOfTagTaxonMap appears in rooted_sbn_instance.hpp. +} +#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/doctest/tidy_subsplit_dag.hpp b/doctest/tidy_subsplit_dag.hpp new file mode 100644 index 000000000..6b4b16658 --- /dev/null +++ b/doctest/tidy_subsplit_dag.hpp @@ -0,0 +1,43 @@ +#pragma once + +#include "../src/tidy_subsplit_dag.hpp" + +#ifdef DOCTEST_LIBRARY_INCLUDED +TEST_CASE("TidySubsplitDAG: slicing") { + auto manual_dag = TidySubsplitDAG::ManualTrivialExample(); + + // std::cout << manual_dag.AboveMatricesAsString() << std::endl; + CHECK_EQ(GenericToString(manual_dag.AboveNode(NodeId(0))), "[1, 0, 0, 1, 1, 1]\n"); + CHECK_EQ(GenericToString(manual_dag.AboveNode(NodeId(1))), "[0, 1, 0, 1, 1, 1]\n"); + CHECK_EQ(GenericToString(manual_dag.AboveNode(NodeId(2))), "[0, 0, 1, 0, 1, 1]\n"); + CHECK_EQ(GenericToString(manual_dag.AboveNode(NodeId(3))), "[0, 0, 0, 1, 1, 1]\n"); + CHECK_EQ(GenericToString(manual_dag.AboveNode(NodeId(4))), "[0, 0, 0, 0, 1, 1]\n"); + CHECK_EQ(GenericToString(manual_dag.AboveNode(NodeId(5))), "[0, 0, 0, 0, 0, 1]\n"); + + auto trivial_dag = TidySubsplitDAG::TrivialExample(); + CHECK_EQ(trivial_dag.AboveMatricesAsString(), manual_dag.AboveMatricesAsString()); + + auto motivating_dag = TidySubsplitDAG::MotivatingExample(); + CHECK_EQ(GenericToString(motivating_dag.AboveNode(false, NodeId(4))), + "[0, 0, 0, 0, 1, 1, 1, 1, 0, 0]\n"); + CHECK_EQ(GenericToString(motivating_dag.AboveNode(true, NodeId(4))), + "[0, 0, 0, 0, 1, 0, 0, 0, 1, 1]\n"); + CHECK_EQ(GenericToString(motivating_dag.AboveNode(false, NodeId(7))), + "[0, 0, 0, 0, 0, 0, 0, 1, 0, 0]\n"); + CHECK_EQ(GenericToString(motivating_dag.AboveNode(true, NodeId(7))), + "[0, 0, 0, 0, 0, 0, 0, 1, 1, 1]\n"); + CHECK_EQ(GenericToString(motivating_dag.BelowNode(false, NodeId(7))), + "[0, 0, 1, 1, 1, 0, 0, 1, 0, 0]\n"); + CHECK_EQ(GenericToString(motivating_dag.BelowNode(true, NodeId(7))), + "[1, 0, 0, 0, 0, 0, 0, 1, 0, 0]\n"); + + motivating_dag.SetDirtyStrictlyAbove(NodeId(4)); + CHECK_EQ(GenericToString(motivating_dag.DirtyVector(true)), + "[0, 0, 0, 0, 0, 0, 0, 0, 1, 1]\n"); + CHECK_EQ(GenericToString(motivating_dag.DirtyVector(false)), + "[0, 0, 0, 0, 0, 1, 1, 1, 0, 0]\n"); + + motivating_dag.SetClean(); + // #321 Add test for Tidy traversal. +} +#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/doctest/topology_sampler.hpp b/doctest/topology_sampler.hpp new file mode 100644 index 000000000..0cfdca45c --- /dev/null +++ b/doctest/topology_sampler.hpp @@ -0,0 +1,83 @@ +#pragma once + +#include "../src/topology_sampler.hpp" + +#ifdef DOCTEST_LIBRARY_INCLUDED + +TEST_CASE("TopologySampler") { + Driver driver; + auto tree_collection = RootedTreeCollection::OfTreeCollection( + driver.ParseNewickFile("data/five_taxon_rooted_more_2.nwk")); + SubsplitDAG dag(tree_collection); + + EigenVectorXd normalized_sbn_parameters = dag.BuildUniformOnTopologicalSupportPrior(); + EigenVectorXd node_probabilities = + dag.UnconditionalNodeProbabilities(normalized_sbn_parameters); + EigenVectorXd inverted_probabilities = + dag.InvertedGPCSPProbabilities(normalized_sbn_parameters, node_probabilities); + + SubsplitDAGNode origin = dag.GetDAGNode(NodeId(5)); + + TopologySampler sampler; + std::map counts; + const size_t iterations = 10000; + + for (size_t i = 0; i < iterations; ++i) { + auto tree = + sampler.Sample(origin, dag, normalized_sbn_parameters, inverted_probabilities); + ++counts[tree->Newick([](const Node* node) { + if (!node->IsLeaf()) return std::string(); + return std::string("x") + std::to_string(node->Id()); + })]; + } + + for (auto& i : counts) { + const double observed = static_cast(i.second) / iterations; + const double expected = 1.0 / 3.0; + CHECK_LT(fabs(observed - expected), 5e-2); + } +} + +TEST_CASE("TopologySampler: Non-uniform prior") { + Driver driver; + auto tree_collection = RootedTreeCollection::OfTreeCollection( + driver.ParseNewickFile("data/five_taxon_rooted_more_2.nwk")); + SubsplitDAG dag(tree_collection); + + std::vector params{0.5, 0.3, 0.2, 1.0, 1.0, 1.0, 1.0, 1.0, + 0.8, 0.2, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, + 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0}; + EigenVectorXd normalized_sbn_parameters = + EigenVectorXd::Map(params.data(), params.size()); + EigenVectorXd node_probabilities = + dag.UnconditionalNodeProbabilities(normalized_sbn_parameters); + EigenVectorXd inverted_probabilities = + dag.InvertedGPCSPProbabilities(normalized_sbn_parameters, node_probabilities); + + SubsplitDAGNode origin = dag.GetDAGNode(NodeId(5)); + + TopologySampler sampler; + std::map counts; + std::map expected = { + {"((((x0,x1),x2),(x3,x4)));", 0.312}, + {"(((x0,x1),(x2,(x3,x4))));", 0.52}, + {"((x0,(x1,(x2,(x3,x4)))));", 0.1666}, + }; + const size_t iterations = 10000; + + for (size_t i = 0; i < iterations; ++i) { + auto tree = + sampler.Sample(origin, dag, normalized_sbn_parameters, inverted_probabilities); + ++counts[tree->Newick([](const Node* node) { + if (!node->IsLeaf()) return std::string(); + return std::string("x") + std::to_string(node->Id()); + })]; + } + + for (auto& [tree, count] : counts) { + const double observed = static_cast(count) / iterations; + CHECK_LT(fabs(observed - expected[tree]), 5e-2); + } +} + +#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/doctest/tree_collection.hpp b/doctest/tree_collection.hpp new file mode 100644 index 000000000..44e16c9ec --- /dev/null +++ b/doctest/tree_collection.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include "../src/tree_collection.hpp" + +#ifdef DOCTEST_LIBRARY_INCLUDED +TEST_CASE("TreeCollection") { + auto first_four_example_trees = Tree::ExampleTrees(); + first_four_example_trees.resize(4); + TreeCollection collection(first_four_example_trees); + auto counter = collection.TopologyCounter(); + std::unordered_map counted; + for (const auto &iter : counter) { + SafeInsert(counted, iter.first->Newick(std::nullopt, std::nullopt, true), + iter.second); + } + std::unordered_map counted_correct( + {{"(0_1,1_1,(2_1,3_1)3_2)3_4;", 2}, + {"(0_1,2_1,(1_1,3_1)3_2)3_4;", 1}, + {"(0_1,(1_1,(2_1,3_1)3_2)3_3)3_4;", 1}}); + CHECK_EQ(counted, counted_correct); + collection.DropFirst(0.25); + CHECK_EQ(collection.TreeCount(), 3); + collection.DropFirst(1.); + CHECK_EQ(collection.TreeCount(), 0); +} +#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/doctest/unrooted_sbn_instance.hpp b/doctest/unrooted_sbn_instance.hpp new file mode 100644 index 000000000..c50892ee5 --- /dev/null +++ b/doctest/unrooted_sbn_instance.hpp @@ -0,0 +1,525 @@ +#pragma once + +#include "../src/unrooted_sbn_instance.hpp" + +#ifdef DOCTEST_LIBRARY_INCLUDED + +#include "eigen_sugar.hpp" +#include "doctest_constants.hpp" +#include "sbn_probability.hpp" + +TEST_CASE("UnrootedSBNInstance: indexer and PSP representations") { + UnrootedSBNInstance inst("charlie"); + inst.ReadNewickFile("data/five_taxon_unrooted.nwk"); + inst.ProcessLoadedTrees(); + auto pretty_indexer = inst.PrettyIndexer(); + // The indexer_ is to index the sbn_parameters_. Note that neither of these + // data structures attempt to catalog the complete collection of rootsplits or + // PCSPs, but just those that are present for some rooting of the input trees. + // + // The indexer_ and sbn_parameters_ are laid out as follows (I'll just call it + // the "index" in what follows). Say there are rootsplit_count rootsplits in + // the support. + // The first rootsplit_count entries of the index are assigned to the + // rootsplits (again, those rootsplits that are present for some rooting of + // the unrooted input trees). For the five_taxon example, this goes as follows: + StringSet correct_pretty_rootsplits( + {"00000|11111|01110", "00000|11111|01010", "00000|11111|00101", + "00000|11111|00111", "00000|11111|00001", "00000|11111|00011", + "00000|11111|00010", "00000|11111|00100", "00000|11111|00110", + "00000|11111|01000", "00000|11111|01111", "00000|11111|01001"}); + StringSet pretty_rootsplits( + pretty_indexer.begin(), + pretty_indexer.begin() + correct_pretty_rootsplits.size()); + CHECK(correct_pretty_rootsplits == pretty_rootsplits); + // The rest of the entries of the index are laid out as blocks of parameters + // for PCSPs that share the same parent. Take a look at the description of + // PCSP bitsets (and the unit tests) in bitset.hpp to understand the notation + // used here. + // + // For example, here are four PCSPs that all share the parent 00001|11110: + StringSet correct_pretty_pcsp_block({"00001|11110|01110", "00001|11110|00010", + "00001|11110|01000", "00001|11110|00100"}); + StringSet pretty_indexer_set(pretty_indexer.begin(), pretty_indexer.end()); + // It's true that this test doesn't show the block-ness, but it wasn't easy to + // show off this feature in a way that wasn't compiler dependent. + // You can see it by printing out a pretty_indexer if you wish. A test exhibiting + // block structure appeas in rooted_sbn_instance.hpp. + for (auto pretty_pcsp : correct_pretty_pcsp_block) { + CHECK(pretty_indexer_set.find(pretty_pcsp) != pretty_indexer_set.end()); + } + // Now we can look at some tree representations. We get these by calling + // IndexerRepresentationOf on a tree topology. This function "digests" the + // tree by representing all of the PCSPs as bitsets which it can then look up + // in the indexer_. + // It then spits them out as the rootsplit and PCSP indices. + // The following tree is (2,(1,3),(0,4));, or with internal nodes (2,(1,3)5,(0,4)6)7 + auto indexer_test_topology_1 = Node::OfParentIdVector({6, 5, 7, 5, 6, 7, 7}); + // Here we look at the indexer representation of this tree. Rather than having + // the indices themselves, which is what IndexerRepresentationOf actually + // outputs, we have string representations of the features corresponding to + // those indices. + // See sbn_maps.hpp for more description of these indexer representations. + StringSetVector correct_representation_1( + // The indexer representations for each of the possible virtual rootings. + // For example, this first one is for rooting at the edge leading to leaf + // 0, the second for rooting at leaf 1, etc. + {{"00000|11111|01111", "10000|01111|00001", "00001|01110|00100", + "00100|01010|00010"}, + {"00000|11111|01000", "01000|10111|00010", "00100|10001|00001", + "00010|10101|00100"}, + {"00000|11111|00100", "10001|01010|00010", "01010|10001|00001", + "00100|11011|01010"}, + {"00000|11111|00010", "00010|11101|01000", "00100|10001|00001", + "01000|10101|00100"}, + {"00000|11111|00001", "00001|11110|01110", "10000|01110|00100", + "00100|01010|00010"}, + {"00000|11111|01010", "10101|01010|00010", "00100|10001|00001", + "01010|10101|00100"}, + {"00000|11111|01110", "00100|01010|00010", "10001|01110|00100", + "01110|10001|00001"}}); + CHECK_EQ( + inst.StringIndexerRepresentationOf(indexer_test_topology_1, out_of_sample_index), + correct_representation_1); + // See the "concepts" part of the online documentation to learn about PSP indexing. + auto correct_psp_representation_1 = + StringVectorVector({{"10000|01111", "10111|01000", "11011|00100", "11101|00010", + "11110|00001", "10101|01010", "10001|01110"}, + {"", "", "", "", "", "01000|00010", "10000|00001"}, + {"01110|00001", "10101|00010", "10001|01010", "10101|01000", + "10000|01110", "10001|00100", "01010|00100"}}); + CHECK_EQ(inst.psp_indexer_.StringRepresentationOf(indexer_test_topology_1), + correct_psp_representation_1); + // Same as above but for (((0,1),2),3,4);, or with internal nodes (((0,1)5,2)6,3,4)7; + auto indexer_test_topology_2 = Node::OfParentIdVector({5, 5, 6, 7, 7, 6, 7}); + StringSetVector correct_representation_2( + {{"00000|11111|01111", "10000|01111|00111", "00100|00011|00001", + "01000|00111|00011"}, + {"00000|11111|01000", "01000|10111|00111", "00100|00011|00001", + "10000|00111|00011"}, + {"00000|11111|00100", "00100|11011|00011", "11000|00011|00001", + "00011|11000|01000"}, + {"00000|11111|00010", "00100|11000|01000", "00001|11100|00100", + "00010|11101|00001"}, + {"00000|11111|00001", "00100|11000|01000", "00001|11110|00010", + "00010|11100|00100"}, + {"00000|11111|00111", "00111|11000|01000", "00100|00011|00001", + "11000|00111|00011"}, + {"00000|11111|00011", "00100|11000|01000", "11100|00011|00001", + "00011|11100|00100"}}); + CHECK_EQ( + inst.StringIndexerRepresentationOf(indexer_test_topology_2, out_of_sample_index), + correct_representation_2); + auto correct_psp_representation_2 = + StringVectorVector({{"10000|01111", "10111|01000", "11011|00100", "11101|00010", + "11110|00001", "11000|00111", "11100|00011"}, + {"", "", "", "", "", "10000|01000", "11000|00100"}, + {"01000|00111", "10000|00111", "11000|00011", "11100|00001", + "11100|00010", "00100|00011", "00010|00001"}}); + CHECK_EQ(inst.psp_indexer_.StringRepresentationOf(indexer_test_topology_2), + correct_psp_representation_2); + + // Test of RootedSBNMaps::IndexerRepresentationOf. + // It's a little surprising to see this here in unrooted land, but these are actually + // complementary tests to those found in rooted_sbn_instance.hpp, with a larger + // subsplit support because we deroot the trees. + // Topology is ((((0,1),2),3),4);, or with internal nodes ((((0,1)5,2)6,3)7,4)8; + auto indexer_test_rooted_topology_1 = + Node::OfParentIdVector({5, 5, 6, 7, 8, 6, 7, 8}); + auto correct_rooted_indexer_representation_1 = + StringSet({"00000|11111|00001", "00001|11110|00010", "00010|11100|00100", + "00100|11000|01000"}); + CHECK_EQ(inst.StringIndexerRepresentationOf({RootedSBNMaps::IndexerRepresentationOf( + inst.SBNSupport().Indexer(), indexer_test_rooted_topology_1, + out_of_sample_index)})[0], + correct_rooted_indexer_representation_1); + // Topology is (((0,1),2),(3,4));, or with internal nodes (((0,1)5,2)6,(3,4)7)8; + auto indexer_test_rooted_topology_2 = + Node::OfParentIdVector({5, 5, 6, 7, 7, 6, 8, 8}); + auto correct_rooted_indexer_representation_2 = + StringSet({"00000|11111|00011", "11100|00011|00001", "00011|11100|00100", + "00100|11000|01000"}); + CHECK_EQ(inst.StringIndexerRepresentationOf({RootedSBNMaps::IndexerRepresentationOf( + inst.SBNSupport().Indexer(), indexer_test_rooted_topology_2, + out_of_sample_index)})[0], + correct_rooted_indexer_representation_2); +} + +TEST_CASE("UnrootedSBNInstance: likelihood and gradient") { + UnrootedSBNInstance inst("charlie"); + inst.ReadNewickFile("data/hello.nwk"); + inst.ReadFastaFile("data/hello.fasta"); + PhyloModelSpecification simple_specification{"JC69", "constant", "strict"}; + inst.PrepareForPhyloLikelihood(simple_specification, 2); + for (auto ll : inst.LogLikelihoods()) { + CHECK_LT(fabs(ll - -84.852358), 0.000001); + } + inst.ReadNexusFile("data/DS1.subsampled_10.t"); + inst.ReadFastaFile("data/DS1.fasta"); + std::vector vector_flag_options{BEAGLE_FLAG_VECTOR_NONE, + BEAGLE_FLAG_VECTOR_SSE}; + std::vector tip_state_options{false, true}; + for (const auto vector_flag : vector_flag_options) { + for (const auto tip_state_option : tip_state_options) { + inst.PrepareForPhyloLikelihood(simple_specification, 2, {vector_flag}, + tip_state_option); + auto likelihoods = inst.LogLikelihoods(); + std::vector pybeagle_likelihoods( + {-14582.995273982739, -6911.294207416366, -6916.880235529542, + -6904.016888831189, -6915.055570693576, -6915.50496696512, + -6910.958836661867, -6909.02639968063, -6912.967861935749, + -6910.7871105783515}); + for (size_t i = 0; i < likelihoods.size(); i++) { + CHECK_LT(fabs(likelihoods[i] - pybeagle_likelihoods[i]), 0.00011); + } + + auto gradients = inst.PhyloGradients(); + // Test the log likelihoods. + for (size_t i = 0; i < likelihoods.size(); i++) { + CHECK_LT(fabs(gradients[i].log_likelihood_ - pybeagle_likelihoods[i]), 0.00011); + } + // Test the gradients for the last tree. + auto last = gradients.back(); + std::sort(last.gradient_["branch_lengths"].begin(), + last.gradient_["branch_lengths"].end()); + // Zeros are for the root and one of the descendants of the root. + std::vector physher_gradients = { + -904.18956, -607.70500, -562.36274, -553.63315, -542.26058, -539.64210, + -463.36511, -445.32555, -414.27197, -412.84218, -399.15359, -342.68038, + -306.23644, -277.05392, -258.73681, -175.07391, -171.59627, -168.57646, + -150.57623, -145.38176, -115.15798, -94.86412, -83.02880, -80.09165, + -69.00574, -51.93337, 0.00000, 0.00000, 16.17497, 20.47784, + 58.06984, 131.18998, 137.10799, 225.73617, 233.92172, 253.49785, + 255.52967, 259.90378, 394.00504, 394.96619, 396.98933, 429.83873, + 450.71566, 462.75827, 471.57364, 472.83161, 514.59289, 650.72575, + 888.87834, 913.96566, 927.14730, 959.10746, 2296.55028}; + for (size_t i = 0; i < last.gradient_["branch_lengths"].size(); i++) { + CHECK_LT(fabs(last.gradient_["branch_lengths"][i] - physher_gradients[i]), + 0.0001); + } + + // Test rescaling + inst.SetRescaling(true); + auto likelihoods_rescaling = inst.LogLikelihoods(); + // Likelihoods from LogLikelihoods() + for (size_t i = 0; i < likelihoods_rescaling.size(); i++) { + CHECK_LT(fabs(likelihoods_rescaling[i] - pybeagle_likelihoods[i]), 0.00011); + } + // Likelihoods from BranchGradients() + inst.PrepareForPhyloLikelihood(simple_specification, 1, {}, tip_state_option); + auto gradients_rescaling = inst.PhyloGradients(); + for (size_t i = 0; i < gradients_rescaling.size(); i++) { + CHECK_LT(fabs(gradients_rescaling[i].log_likelihood_ - pybeagle_likelihoods[i]), + 0.00011); + } + // Gradients + auto last_rescaling = gradients_rescaling.back(); + auto branch_lengths_gradient = last_rescaling.gradient_["branch_lengths"]; + std::sort(branch_lengths_gradient.begin(), branch_lengths_gradient.end()); + for (size_t i = 0; i < branch_lengths_gradient.size(); i++) { + CHECK_LT(fabs(branch_lengths_gradient[i] - physher_gradients[i]), 0.0001); + } + } + } +} + +TEST_CASE("UnrootedSBNInstance: likelihood and gradient with Weibull") { + UnrootedSBNInstance inst("charlie"); + PhyloModelSpecification simple_specification{"JC69", "weibull+4", "strict"}; + inst.ReadNexusFile("data/DS1.subsampled_10.t"); + inst.ReadFastaFile("data/DS1.fasta"); + + std::vector physher_likelihoods( + {-9456.1201098061, -6624.4110704332, -6623.4474776131, -6617.25658038029, + -6627.5385571548, -6621.6155048722, -6622.3314942713, -6618.7695717585, + -6616.3837517370, -6623.8295828648}); + // First element of each gradient + std::vector physher_gradients_bl0( + {-126.890527, 157.251275, 138.202510, -180.311856, 417.562897, -796.450894, + -173.744375, -70.693513, 699.190754, -723.034349}); + std::vector vector_flag_options{BEAGLE_FLAG_VECTOR_NONE, + BEAGLE_FLAG_VECTOR_SSE}; + std::vector tip_state_options{false, true}; + for (const auto vector_flag : vector_flag_options) { + for (const auto tip_state_option : tip_state_options) { + inst.PrepareForPhyloLikelihood(simple_specification, 2, {vector_flag}, + tip_state_option); + auto param_block_map = inst.GetPhyloModelParamBlockMap(); + param_block_map.at(WeibullSiteModel::shape_key_).setConstant(0.1); + auto likelihoods = inst.LogLikelihoods(); + for (size_t i = 0; i < likelihoods.size(); i++) { + CHECK_LT(fabs(likelihoods[i] - physher_likelihoods[i]), 0.00011); + } + + auto gradients = inst.PhyloGradients(); + for (size_t i = 0; i < gradients.size(); i++) { + CHECK_LT(fabs(gradients[i].gradient_["branch_lengths"][0] - + physher_gradients_bl0[i]), + 0.00011); + } + + // Test rescaling + inst.SetRescaling(true); + auto likelihoods_rescaling = inst.LogLikelihoods(); + // Likelihoods from LogLikelihoods() + for (size_t i = 0; i < likelihoods_rescaling.size(); i++) { + CHECK_LT(fabs(likelihoods_rescaling[i] - physher_likelihoods[i]), 0.00011); + } + + auto gradients_rescaling = inst.PhyloGradients(); + for (size_t i = 0; i < gradients.size(); i++) { + CHECK_LT(fabs(gradients_rescaling[i].gradient_["branch_lengths"][0] - + physher_gradients_bl0[i]), + 0.00011); + } + } + } +} + +TEST_CASE("UnrootedSBNInstance: SBN training") { + UnrootedSBNInstance inst("charlie"); + inst.ReadNewickFile("data/DS1.100_topologies.nwk"); + inst.ProcessLoadedTrees(); + // These "Expected" functions are defined in sbn_probability.hpp. + const auto expected_SA = ExpectedSAVector(); + inst.TrainSimpleAverage(); + CheckVectorXdEquality(inst.CalculateSBNProbabilities(), expected_SA, 1e-12); + // Expected EM vectors with alpha = 0. + const auto [expected_EM_0_1, expected_EM_0_23] = ExpectedEMVectorsAlpha0(); + // 1 iteration of EM with alpha = 0. + inst.TrainExpectationMaximization(0., 1); + CheckVectorXdEquality(inst.CalculateSBNProbabilities(), expected_EM_0_1, 1e-12); + // 23 iterations of EM with alpha = 0. + inst.TrainExpectationMaximization(0., 23); + CheckVectorXdEquality(inst.CalculateSBNProbabilities(), expected_EM_0_23, 1e-12); + // 100 iteration of EM with alpha = 0.5. + const auto expected_EM_05_100 = ExpectedEMVectorAlpha05(); + inst.TrainExpectationMaximization(0.5, 100); + CheckVectorXdEquality(inst.CalculateSBNProbabilities(), expected_EM_05_100, 1e-5); +} + +TEST_CASE("UnrootedSBNInstance: tree sampling") { + UnrootedSBNInstance inst("charlie"); + inst.ReadNewickFile("data/five_taxon_unrooted.nwk"); + inst.ProcessLoadedTrees(); + inst.TrainSimpleAverage(); + // Count the frequencies of rooted trees in a file. + size_t rooted_tree_count_from_file = 0; + RootedIndexerRepresentationSizeDict counter_from_file(0); + for (const auto &indexer_representation : inst.MakeIndexerRepresentations()) { + RootedSBNMaps::IncrementRootedIndexerRepresentationSizeDict(counter_from_file, + indexer_representation); + rooted_tree_count_from_file += indexer_representation.size(); + } + // Count the frequencies of trees when we sample after training with + // SimpleAverage. + size_t sampled_tree_count = 1'000'000; + RootedIndexerRepresentationSizeDict counter_from_sampling(0); + ProgressBar progress_bar(sampled_tree_count / 1000); + for (size_t sample_idx = 0; sample_idx < sampled_tree_count; ++sample_idx) { + const auto rooted_topology = inst.SampleTopology(true); + RootedSBNMaps::IncrementRootedIndexerRepresentationSizeDict( + counter_from_sampling, + RootedSBNMaps::IndexerRepresentationOf(inst.SBNSupport().Indexer(), + rooted_topology, out_of_sample_index)); + if (sample_idx % 1000 == 0) { + ++progress_bar; + progress_bar.display(); + } + } + // These should be equal in the limit when we're training with SA. + for (const auto &[key, _] : counter_from_file) { + std::ignore = _; + double observed = + static_cast(counter_from_sampling.at(key)) / sampled_tree_count; + double expected = + static_cast(counter_from_file.at(key)) / rooted_tree_count_from_file; + CHECK_LT(fabs(observed - expected), 5e-3); + } + progress_bar.done(); +} + +TEST_CASE("UnrootedSBNInstance: gradient of log q_{phi}(tau) WRT phi") { + UnrootedSBNInstance inst("charlie"); + // File gradient_test.t contains two trees: + // ((0,1), 2, (3,4)) and + // ((0,1), (2,3), 4). + inst.ReadNexusFile("data/gradient_test.t"); + inst.ProcessLoadedTrees(); + + // The number of rootsplits across all of the input trees. + size_t num_rootsplits = 8; + // Manual enumeration shows that there are 31 PCSP's. + size_t num_pcsp = inst.sbn_parameters_.size() - num_rootsplits; + + // Test for K = 1 tree. + size_t K = 1; + inst.tree_collection_.trees_.clear(); + // Generate a tree, + // \tau = ((0,1),(2,3),4) with internal node labels ((0,1)5,(2,3)6,4)7. + std::vector tau_indices = {5, 5, 6, 6, 7, 7, 7}; + auto tau = UnrootedTree::OfParentIdVector(tau_indices); + inst.tree_collection_.trees_.push_back(tau); + + // Initialize sbn_parameters_ to 0's and normalize, which is going to give a uniform + // distribution for rootsplits and PCSP distributions. + inst.sbn_parameters_.setZero(); + EigenVectorXd normalized_sbn_parameters_in_log = inst.sbn_parameters_; + inst.ProbabilityNormalizeSBNParametersInLog(normalized_sbn_parameters_in_log); + // Because this is a uniform distribution, each rootsplit \rho has P(\rho) = 1/8. + // + // We're going to start by computing the rootsplit gradient. + // There are 7 possible rootings of \tau. + // For example consider rooting on the 014|23 split, yielding the following subsplits: + // 014|23, 2|3, 01|4, 0|1. + // Each of the child subsplits are the only possible subsplit, + // except for the root where it has probability 1/8. Hence, the probability + // for this tree is 1/8 x 1 x 1 x 1 = 1/8. + // Now, consider rooting on the 0|1234 split, yielding the following subsplits: + // 0|1234, 1|234, 23|4, 2|3. + // The probability for this tree is 1/8 x 1 x 1/2 x 1 = 1/16, where the 1/2 comes from + // the fact that we can have 23|4 or 2|34. + // + // Each of the remaining 5 trees has the same probability: the product of + // 1/8 for the rootsplit and 1/2 for one of the subsplit resolutions of 234. + // One can see this because the only way for there not to be ambiguity in the + // resolution of the splitting of 234 is for one to take 014|23 as the rootsplit. + // + // Hence, q(\tau) = 6 x 1/16 + 1 x 1/8 = 8/16 = 0.5. + // Note that there are a total of 8 rootsplits; 7 are possible rootsplits of + // the sampled tree \tau but one rootsplit, + // 014|23 is not observed rooting of \tau and hence, + // the gradient for 014|23 is simply -P(014|23) = -1/8. + // + // The gradient with respect to each of the 7 rootsplits is given by + // P(\tau_{\rho})/q(\tau) - P(\rho) via eq:rootsplitGrad, + // which is equal to + // (1/8) / (0.5) - 1/8 = 1/8 for the tree with \rho = 34|125 and + // (1/16) / (0.5) - 1/8 = 0 for 6 remaining trees. + + EigenVectorXd expected_grad_rootsplit(8); + expected_grad_rootsplit << -1. / 8, 0, 0, 0, 0, 0, 0, 1. / 8; + auto indexer_representations = inst.MakeIndexerRepresentations(); + EigenVectorXd grad_log_q = inst.GradientOfLogQ(normalized_sbn_parameters_in_log, + indexer_representations.at(0)); + EigenVectorXd realized_grad_rootsplit = grad_log_q.segment(0, 8); + // Sort them and compare against sorted version of + // realized_grad_rootsplit[0:7]. + std::sort(realized_grad_rootsplit.begin(), realized_grad_rootsplit.end()); + CheckVectorXdEquality(realized_grad_rootsplit, expected_grad_rootsplit, 1e-8); + + // Manual enumeration shows that the entries corresponding to PCSP should have + // 6 entries with -1/16 and 6 entries with 1/16 and the rest with 0's. + // For example, consider the tree ((0,1),(2,3),4), which has the following subsplits: + // 0123|4, 01|23, 0|1, 2|3. + // Note the subsplit s = 01|23 is one of two choices for + // the parent subsplit t = 0123|4, + // since 0123|4 can also be split into s' = 012|3. + // Let \rho = 0123|4, the gradient for 01|23 is given by: + // (1/q(\tau)) P(\tau_{\rho}) * (1 - P(01|23 | 0123|4)) + // = 2 * (1/16) * (1-0.5) = 1/16. + // The gradient for s' = 012|3 is, + // (1/q(\tau)) P(\tau_{\rho}) * -P(012|3 | 0123|4) + // = 2 * (1/16) * -0.5 = -1/16. + + // The gradient for the following PCSP are 1/16 as above. + // 014|3 | 0134|2 + // 014|2 | 0124|3 + // 01|23 | 0123|4 + // 23|4 | 01|234 + // 23|4 | 1|234 + // 23|4 | 0|234 + // And each of these have an alternate subsplit s' that gets a gradient of -1/16. + // Each of the other PCSP gradients are 0 either because its parent support + // never appears in the tree or it represents the only child subsplit. + EigenVectorXd expected_grad_pcsp = EigenVectorXd::Zero(num_pcsp); + expected_grad_pcsp.segment(0, 6).setConstant(-1. / 16); + expected_grad_pcsp.segment(num_pcsp - 6, 6).setConstant(1. / 16); + EigenVectorXd realized_grad_pcsp = grad_log_q.tail(num_pcsp); + std::sort(realized_grad_pcsp.begin(), realized_grad_pcsp.end()); + CheckVectorXdEquality(realized_grad_pcsp, expected_grad_pcsp, 1e-8); + + // We'll now change the SBN parameters and check the gradient there. + // If we root at 0123|4, then the only choice we have is between the following s and + // s' as described above. + // The PCSP s|t = (01|23) | (0123|4) corresponds to 00001|11110|00110. + // The PCSP s'|t = (012|3) | (0123|4) corresponds to 00001|11110|00010. + Bitset s("000011111000110"); + Bitset s_prime("000011111000010"); + size_t s_idx = inst.SBNSupport().IndexerAt(s); + size_t s_prime_idx = inst.SBNSupport().IndexerAt(s_prime); + inst.sbn_parameters_.setZero(); + inst.sbn_parameters_(s_idx) = 1; + inst.sbn_parameters_(s_prime_idx) = -1; + normalized_sbn_parameters_in_log = inst.sbn_parameters_; + inst.ProbabilityNormalizeSBNParametersInLog(normalized_sbn_parameters_in_log); + + // These changes to normalized_sbn_parameters_in_log will change q(\tau) as well as + // P(\tau_{\rho}) for \rho = 0123|4. First, + // P(\tau_{\rho}) = 1/8 * exp(1)/(exp(1) + exp(-1)) = 0.1100996. + double p_tau_rho = (1. / 8) * exp(normalized_sbn_parameters_in_log[s_idx]); + // For q(\tau), we will just compute using the already tested function: + double q_tau = inst.CalculateSBNProbabilities()(0); + // The gradient for s|t is given by, + // (1/q(\tau)) x P(\tau_{\rho}) x (1 - P(s|t)) + double expected_grad_at_s = + (1. / q_tau) * p_tau_rho * (1 - exp(normalized_sbn_parameters_in_log[s_idx])); + // And the gradient for s'|t is given by, + // (1/q(\tau)) x P(\tau_{\rho}) x (-P(s|t)) + double expected_grad_at_s_prime = + (1. / q_tau) * p_tau_rho * -exp(normalized_sbn_parameters_in_log[s_prime_idx]); + // We're setting normalized_sbn_parameters_in_log to NaN as we would in a normal + // application of GradientOfLogQ. + normalized_sbn_parameters_in_log.setConstant(DOUBLE_NAN); + grad_log_q = inst.GradientOfLogQ(normalized_sbn_parameters_in_log, + indexer_representations.at(0)); + CHECK_LT(fabs(expected_grad_at_s - grad_log_q(s_idx)), 1e-8); + CHECK_LT(fabs(expected_grad_at_s_prime - grad_log_q(s_prime_idx)), 1e-8); + + // Now we test the gradient by doing the calculation by hand. + K = 4; + inst.SampleTrees(K); + // Make up some numbers for log_f. + EigenVectorXd log_f(K); + log_f << -83, -75, -80, -79; + // log_F = -74.97493 + double log_F = NumericalUtils::LogSum(log_f); + double elbo = log_F - log(K); + // 0.0003271564 0.9752395946 0.0065711127 0.0178621362 + EigenVectorXd tilde_w = (log_f.array() - log_F).exp(); + // -76.36155 -77.33646 -76.36779 -76.37908 + EigenVectorXd multiplicative_factors = (elbo - tilde_w.array()); + + EigenVectorXd expected_nabla(inst.sbn_parameters_.size()); + expected_nabla.setZero(); + // We now have some confidence in GradientOfLogQ(), so we just use it. + auto indexer_reps = inst.MakeIndexerRepresentations(); + normalized_sbn_parameters_in_log.setConstant(DOUBLE_NAN); + for (size_t k = 0; k < K; k++) { + grad_log_q = + multiplicative_factors(k) * + inst.GradientOfLogQ(normalized_sbn_parameters_in_log, indexer_reps.at(k)) + .array(); + expected_nabla += grad_log_q; + } + bool use_vimco = false; + EigenVectorXd realized_nabla = inst.TopologyGradients(log_f, use_vimco); + CheckVectorXdEquality(realized_nabla, expected_nabla, 1e-8); + + // Test for VIMCO gradient estimator. + EigenVectorXd vimco_multiplicative_factors(K); + vimco_multiplicative_factors << -0.04742748, 2.59553236, -0.01779887, -0.01278592; + expected_nabla.setZero(); + normalized_sbn_parameters_in_log.setConstant(DOUBLE_NAN); + for (size_t k = 0; k < K; k++) { + grad_log_q = + vimco_multiplicative_factors(k) * + inst.GradientOfLogQ(normalized_sbn_parameters_in_log, indexer_reps.at(k)) + .array(); + expected_nabla += grad_log_q; + } + use_vimco = true; + realized_nabla = inst.TopologyGradients(log_f, use_vimco); + CheckVectorXdEquality(realized_nabla, expected_nabla, 1e-8); +} +#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/doctest/unrooted_tree.hpp b/doctest/unrooted_tree.hpp new file mode 100644 index 000000000..f7564f26e --- /dev/null +++ b/doctest/unrooted_tree.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include "../src/unrooted_tree.hpp" + +#ifdef DOCTEST_LIBRARY_INCLUDED +TEST_CASE("UnrootedTree") { + auto trees = Tree::ExampleTrees(); + auto unrooted_tree = UnrootedTree(trees[0]); + auto original_newick = unrooted_tree.Newick(); + CHECK_EQ(unrooted_tree.Detrifurcate().Topology(), trees[3].Topology()); + // Shows that Detrifurcate doesn't change the original tree. + CHECK_EQ(original_newick, unrooted_tree.Newick()); + + auto topologies = Node::ExampleTopologies(); + // This should work: topology has trifurcation at the root. + UnrootedTree::UnitBranchLengthTreeOf(topologies[0]); + // This shouldn't. + CHECK_THROWS_AS(UnrootedTree::UnitBranchLengthTreeOf(topologies[3]), + std::runtime_error&); +} +#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/src/alignment.hpp b/src/alignment.hpp index 0aeda1fe2..ce385718b 100644 --- a/src/alignment.hpp +++ b/src/alignment.hpp @@ -39,16 +39,3 @@ class Alignment { // - Map of alignments: [ taxon name -> alignment sequence ] StringStringMap data_; }; - -#ifdef DOCTEST_LIBRARY_INCLUDED -TEST_CASE("Alignment") { - auto alignment = Alignment::ReadFasta("data/hello.fasta"); - CHECK_EQ(alignment, Alignment::HelloAlignment()); - CHECK(alignment.IsValid()); - - CHECK_THROWS(alignment.ExtractSingleColumnAlignment(31)); - Alignment first_col_expected = - Alignment({{"mars", "C"}, {"saturn", "G"}, {"jupiter", "G"}}); - CHECK_EQ(alignment.ExtractSingleColumnAlignment(0), first_col_expected); -} -#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/src/bitset.hpp b/src/bitset.hpp index 5ff1db0cc..e19494af2 100644 --- a/src/bitset.hpp +++ b/src/bitset.hpp @@ -372,205 +372,3 @@ struct equal_to { bool operator()(const Bitset &lhs, const Bitset &rhs) const { return lhs == rhs; } }; } // namespace std - -#ifdef DOCTEST_LIBRARY_INCLUDED - -TEST_CASE("Bitset") { - Bitset bit_from_str = Bitset("00110100"); - Bitset bit_from_sizevec = Bitset({2, 3, 5}, 8); - CHECK_EQ(bit_from_str, bit_from_sizevec); - - Bitset a("1100"); - - CHECK_EQ(a[2], false); - CHECK_EQ(a[1], true); - - Bitset build_up(4); - build_up.set(1); - build_up.set(3); - CHECK_EQ(build_up, Bitset("0101")); - - Bitset strip_down(4, true); - strip_down.reset(0); - strip_down.reset(2); - CHECK_EQ(strip_down, Bitset("0101")); - - CHECK_EQ(a.size(), 4); - - CHECK_EQ(Bitset("1100"), Bitset("1100")); - CHECK_NE(Bitset("1100"), Bitset("0100")); - - CHECK_LT(Bitset("0100"), Bitset("0110")); - CHECK_LT(Bitset("0100"), Bitset("0110")); - CHECK_LT(Bitset("0010"), Bitset("0100")); - CHECK_LE(Bitset("0010"), Bitset("0100")); - CHECK_LE(Bitset("1100"), Bitset("1100")); - - CHECK_GT(Bitset("0110"), Bitset("0100")); - CHECK_GT(Bitset("0110"), Bitset("0100")); - CHECK_GT(Bitset("0100"), Bitset("0010")); - CHECK_GE(Bitset("0100"), Bitset("0010")); - CHECK_GE(Bitset("1100"), Bitset("1100")); - - CHECK_EQ((Bitset("1100") & Bitset("1010")), Bitset("1000")); - CHECK_EQ((Bitset("1100") | Bitset("1010")), Bitset("1110")); - CHECK_EQ((Bitset("1100") ^ Bitset("1010")), Bitset("0110")); - CHECK_EQ(~Bitset("1010"), Bitset("0101")); - CHECK_EQ(Bitset("101") + Bitset("011"), Bitset("101011")); - CHECK_EQ(std::min(Bitset("1100"), Bitset("1010")), Bitset("1010")); - - a &= Bitset("0110"); - CHECK_EQ(a, Bitset("0100")); - - CHECK_EQ(a.All(), false); - CHECK_EQ(Bitset(4, true).All(), true); - CHECK_EQ(a.Any(), true); - CHECK_EQ(Bitset(4, false).Any(), false); - CHECK_EQ(a.None(), false); - CHECK_EQ(Bitset(4, false).None(), true); - - a.flip(); - CHECK_EQ(a, Bitset("1011")); - a.Minorize(); - CHECK_EQ(a, Bitset("0100")); - a.Minorize(); - CHECK_EQ(a, Bitset("0100")); - - a.CopyFrom(Bitset("10"), 0, false); - CHECK_EQ(a, Bitset("1000")); - a.CopyFrom(Bitset("10"), 0, true); - CHECK_EQ(a, Bitset("0100")); - a.CopyFrom(Bitset("10"), 2, false); - CHECK_EQ(a, Bitset("0110")); - a.CopyFrom(Bitset("10"), 2, true); - CHECK_EQ(a, Bitset("0101")); - - auto singleton = Bitset("0010"); - CHECK(singleton.IsSingleton()); - CHECK_EQ(*singleton.SingletonOption(), 2); - - CHECK_EQ(Bitset("0000").Count(), 0); - CHECK_EQ(Bitset("0100").Count(), 1); - CHECK_EQ(Bitset("011101").Count(), 4); - - CHECK_EQ(Bitset("1001").ToVectorOfSetBitsAsString(), "0,3"); - CHECK_EQ(Bitset("0000").ToVectorOfSetBitsAsString(), ""); -} - -TEST_CASE("Bitset: Clades, Subsplits, PCSPs") { - auto p = Bitset("000111"); - // Subsplit: 000|111 - CHECK_EQ(p.SubsplitGetClade(SubsplitClade::Left), Bitset("000")); - CHECK_EQ(p.SubsplitGetClade(SubsplitClade::Right), Bitset("111")); - // Edge: 00|01|11 - CHECK_EQ(p.PCSPGetClade(PCSPClade::Sister), Bitset("00")); - CHECK_EQ(p.PCSPGetClade(PCSPClade::Focal), Bitset("01")); - CHECK_EQ(p.PCSPGetClade(PCSPClade::RightChild), Bitset("11")); - - CHECK_EQ(Bitset("11001010").SubsplitCladeUnion(), Bitset("1110")); - - CHECK_EQ(Bitset("10011100").SubsplitRotate(), Bitset("11001001")); - CHECK_EQ(Bitset("010101").SubsplitToVectorOfSetBitsAsString(), "1|0,2"); - - CHECK_EQ(Bitset("101010").SubsplitIsLeftChildOf(Bitset("111000")), true); - // #350 commented out code - // CHECK_EQ(Bitset::SubsplitIsChildOfWhichParentClade(Bitset("111000"), - // Bitset("101010")), true); - CHECK_EQ(Bitset("00100001").SubsplitIsRightChildOf(Bitset("11000011")), true); - // CHECK_EQ(Bitset::SubsplitIsChildOfWhichParentClade(Bitset("000111"), - // Bitset("101010")), false); - CHECK_EQ(Bitset("010001").SubsplitIsLeftChildOf(Bitset("110001")), false); - CHECK_EQ(Bitset("010001").SubsplitIsRightChildOf(Bitset("01000011")), false); - // Should throw because Bitsets can't be divided into equal-sized clades. - CHECK_THROWS(Bitset("11010").SubsplitIsLeftChildOf(Bitset("10101"))); - CHECK_THROWS(Bitset("11010").SubsplitIsRightChildOf(Bitset("10101"))); - - CHECK_EQ(Bitset("101010").SubsplitIsRootsplit(), true); - CHECK_EQ(Bitset("111000").SubsplitIsRootsplit(), false); - CHECK_EQ(Bitset("11000001").SubsplitIsRootsplit(), false); - - CHECK_EQ(Bitset("011101").PCSPIsValid(), false); - CHECK_EQ(Bitset("000111").PCSPIsValid(), false); - CHECK_EQ(Bitset("100100").PCSPIsValid(), false); - CHECK_EQ(Bitset("100011001").PCSPIsValid(), true); - - CHECK_EQ(Bitset("100011001").PCSPChildIsLeaf(), false); - CHECK_EQ(Bitset("100011000").PCSPChildIsLeaf(), true); - - CHECK_EQ(Bitset("000111010").PCSPIsParentRootsplit(), false); - CHECK_EQ(Bitset("000111000100").PCSPIsParentRootsplit(), false); - CHECK_EQ(Bitset("101010000").PCSPIsParentRootsplit(), true); - - CHECK_EQ(Bitset("100011001").PCSPGetParentSubsplit(), Bitset("100011")); - CHECK_EQ(Bitset("011100001").PCSPGetParentSubsplit(), Bitset("100011")); - CHECK_EQ(Bitset("100011001").PCSPGetChildSubsplit(), Bitset("010001")); - CHECK_EQ(Bitset("100001110001").PCSPGetChildSubsplit(), Bitset("01100001")); - CHECK_EQ(Bitset("100001110001").PCSPGetChildSubsplitTaxonCounts(), SizePair({1, 2})); - CHECK_EQ(Bitset("100000111100101").PCSPGetChildSubsplitTaxonCounts(), - SizePair({2, 2})); - - CHECK_EQ(Bitset::Singleton(4, 2), Bitset("0010")); - - CHECK_EQ(Bitset("100010"), Bitset::Subsplit(Bitset("100"), Bitset("010"))); - CHECK_EQ(Bitset("110001"), Bitset::Subsplit(Bitset("001"), Bitset("110"))); - // Invalid clade pair. - CHECK_THROWS(Bitset::Subsplit(Bitset("1100"), Bitset("001"))); - CHECK_THROWS(Bitset::Subsplit(Bitset("111"), Bitset("001"))); - - CHECK_EQ(Bitset("000110010"), Bitset::PCSP(Bitset("110000"), Bitset("100010"))); - CHECK_EQ(Bitset("110001000"), Bitset::PCSP(Bitset("110001"), Bitset("001000"))); - // Invalid parent-child pair. - CHECK_THROWS(Bitset::PCSP(Bitset("110001"), Bitset("010001"))); - CHECK_THROWS(Bitset::PCSP(Bitset("11000101"), Bitset("010001"))); - CHECK_THROWS(Bitset::PCSP(Bitset("110001"), Bitset("110100"))); - - CHECK_EQ(Bitset::RootsplitSubsplitOfClade(Bitset("0011")), Bitset("11000011")); - CHECK_EQ(Bitset::PCSPFromUCAToRootsplit(Bitset("11000011")), Bitset("000011110011")); - - CHECK_EQ(Bitset("010000").SubsplitIsLeaf(), true); - CHECK_EQ(Bitset("010010").SubsplitIsLeaf(), false); - CHECK_EQ(Bitset("111000").SubsplitIsLeaf(), false); - CHECK_EQ(Bitset::LeafSubsplitOfNonemptyClade(Bitset("010")), Bitset("010000")); - CHECK_EQ(Bitset::LeafSubsplitOfParentSubsplit(Bitset("100001")), Bitset("001000")); - CHECK_THROWS(Bitset::LeafSubsplitOfParentSubsplit(Bitset("100011"))); - CHECK_EQ(Bitset::PCSPFromRightParentCladeToLeaf(Bitset("100001")), - Bitset("100001000")); - CHECK_THROWS(Bitset::PCSPFromRightParentCladeToLeaf(Bitset("0000110"))); - CHECK_THROWS(Bitset::PCSPFromRightParentCladeToLeaf(Bitset("100101"))); - - // Restrict a bitset. - CHECK_EQ(Bitset::Remap(Bitset("10101010101"), {0, 2, 4, 6, 8, 10}), Bitset("111111")); - // If we apply this remap 3 times we should get back to where we started. - SizeOptionVector rotate120{6, 7, 8, 0, 1, 2, 3, 4, 5}; - auto to_rotate = Bitset("110010100"); - CHECK_EQ(Bitset::Remap(Bitset::Remap(Bitset::Remap(to_rotate, rotate120), rotate120), - rotate120), - to_rotate); - // "Lift" a bitset. - CHECK_EQ(Bitset::Remap(Bitset("11"), {0, std::nullopt, 1}), Bitset("101")); -} - -TEST_CASE("Bitset: Subsplit Sort") { - Bitset bitset_a = Bitset::Subsplit("01001", "00100"); - CHECK_MESSAGE(Bitset::SubsplitCompare(bitset_a, bitset_a) == 0, - "Equality: bitset_a should be equal to itself"); - // Count of bitset_a (3) comes before count of bitset_b (4). - Bitset bitset_b = Bitset::Subsplit("00100", "01011"); - CHECK_MESSAGE( - Bitset::SubsplitCompare(bitset_a, bitset_b) < 0, - "Bit Count: bitset_a should be smaller/earlier sorted value than bitset_b."); - // Union of bitset_a ("01101") comes before union of bitset_c ("11100"), counts are - // equal. - Bitset bitset_c = Bitset::Subsplit("01000", "10100"); - CHECK_MESSAGE( - Bitset::SubsplitCompare(bitset_a, bitset_c) < 0, - "Union: bitset_a should be smaller/earlier sorted value than bitset_c."); - // Sorted clade of bitset_a ("01001") comes before sorted clade of bitset_d ("01100"), - // counts and unions are equal. - Bitset bitset_d = Bitset::Subsplit("00001", "01100"); - CHECK_MESSAGE( - Bitset::SubsplitCompare(bitset_a, bitset_d) < 0, - "Sorted Clade: bitset_a should be smaller/earlier sorted value than bitset_d."); -} - -#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/src/block_specification.hpp b/src/block_specification.hpp index 0eae335d6..a1ebabdd9 100644 --- a/src/block_specification.hpp +++ b/src/block_specification.hpp @@ -72,28 +72,3 @@ class BlockSpecification { void InsertEntireKey(Coordinates coordinates); void EraseEntireKey() { map_.erase(entire_key_); } }; - -#ifdef DOCTEST_LIBRARY_INCLUDED -TEST_CASE("BlockSpecification") { - // As an example, kazoo has 4 parameters, and jordan has 23. - BlockSpecification spec({{"kazoo", 4}, {"jordan", 23}}); - // The specification stores the starting index and then the number of - // parameters. Because we're using an ordered map, jordan has a lower index - // than kazoo. - const auto correct_spec_map = BlockSpecification::UnderlyingMapType( - {{"entire", {0, 27}}, {"jordan", {0, 23}}, {"kazoo", {23, 4}}}); - CHECK_EQ(spec.GetMap(), correct_spec_map); - spec.Append("entire turbo and boost", - BlockSpecification({{"boost", 42}, {"turbo", 666}})); - // Then after appending, the new stuff gets shifted down. For example, we find - // boost at 23+4=27 and turbo at 27+42=69. - auto correct_appended_map = BlockSpecification::UnderlyingMapType( - {{"boost", {27, 42}}, // 23+4=27 - {"entire", {0, 735}}, // 4+23+42+666=735 - {"entire turbo and boost", {27, 708}}, // 42+666=708 - {"jordan", {0, 23}}, // - {"kazoo", {23, 4}}, // - {"turbo", {69, 666}}}); // 27+42=69 - CHECK_EQ(spec.GetMap(), correct_appended_map); -} -#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/src/combinatorics.hpp b/src/combinatorics.hpp index 95d1b2d3c..d2173fafe 100644 --- a/src/combinatorics.hpp +++ b/src/combinatorics.hpp @@ -26,31 +26,3 @@ double LogChildSubsplitCountRatioNaive(size_t child0_taxon_count, double LogChildSubsplitCountRatio(size_t child0_taxon_count, size_t child1_taxon_count); } // namespace Combinatorics - -#ifdef DOCTEST_LIBRARY_INCLUDED -TEST_CASE("Combinatorics") { - CHECK_EQ(Combinatorics::TopologyCount(1), 1.); - CHECK_EQ(Combinatorics::TopologyCount(2), 1.); - CHECK_EQ(Combinatorics::TopologyCount(3), 3.); - CHECK_EQ(Combinatorics::TopologyCount(4), 15.); - CHECK_EQ(Combinatorics::TopologyCount(5), 105.); - CHECK_EQ(Combinatorics::TopologyCount(6), 945.); - CHECK_EQ(Combinatorics::TopologyCount(7), 10395.); - - for (size_t taxon_count = 1; taxon_count < 20; taxon_count++) { - CHECK_LT(fabs(Combinatorics::LogTreeCount(taxon_count) - - std::log(Combinatorics::TopologyCount(taxon_count))), - 1e-10); - } - - for (size_t child0_count = 1; child0_count < 10; child0_count++) { - for (size_t child1_count = 1; child1_count < 10; child1_count++) { - CHECK_LT( - fabs(Combinatorics::LogChildSubsplitCountRatio(child0_count, child1_count) - - Combinatorics::LogChildSubsplitCountRatioNaive(child0_count, - child1_count)), - 1e-10); - } - } -} -#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/src/csv.hpp b/src/csv.hpp index 4c9cccd2f..db5085713 100644 --- a/src/csv.hpp +++ b/src/csv.hpp @@ -12,14 +12,3 @@ StringDoubleMap StringDoubleMapOfCSV(const std::string& csv_path); void StringDoubleVectorToCSV(const StringDoubleVector& v, const std::string& csv_path); } // namespace CSV - -#ifdef DOCTEST_LIBRARY_INCLUDED -TEST_CASE("CSV I/O") { - std::string csv_test_file_path = "_ignore/for_csv_test.csv"; - StringDoubleVector input = {{"hi", 1e9}, {"lo", -4.}}; - CSV::StringDoubleVectorToCSV(input, csv_test_file_path); - auto result = CSV::StringDoubleMapOfCSV(csv_test_file_path); - StringDoubleMap correct_result = {{"hi", 1e9}, {"lo", -4.}}; - CHECK_EQ(result, correct_result); -} -#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/src/dag_branch_handler.hpp b/src/dag_branch_handler.hpp index 0187d6421..8a3f0bd5d 100644 --- a/src/dag_branch_handler.hpp +++ b/src/dag_branch_handler.hpp @@ -357,7 +357,3 @@ class DAGBranchHandler { LogLikelihoodAndDerivativeFunc logspace_gradient_ascent_func_ = nullptr; LogLikelihoodAndFirstTwoDerivativesFunc newton_raphson_func_ = nullptr; }; - -#ifdef DOCTEST_LIBRARY_INCLUDED - -#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/src/default_dict.hpp b/src/default_dict.hpp index f1dc1f56c..4d731ec50 100644 --- a/src/default_dict.hpp +++ b/src/default_dict.hpp @@ -61,16 +61,3 @@ class DefaultDict { const T default_value_; std::unordered_map map_; }; - -#ifdef DOCTEST_LIBRARY_INCLUDED - -TEST_CASE("DefaultDict") { - auto d = DefaultDict(0); - CHECK_EQ(d.at(4), 0); - d.increment(4, 5); - CHECK_EQ(d.at(4), 5); - d.increment(4, 2); - CHECK_EQ(d.at(4), 7); -} - -#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/src/driver.hpp b/src/driver.hpp index ada69ae6e..0d2897615 100644 --- a/src/driver.hpp +++ b/src/driver.hpp @@ -77,59 +77,3 @@ class Driver { // Run the parser on a Nexus stream. TreeCollection ParseNexus(std::istream& in); }; - -#ifdef DOCTEST_LIBRARY_INCLUDED - -TEST_CASE("Driver") { - Driver driver; - - std::vector newicks = { - "(a:0,b:0,c:0,d:0):0;", - "((b:0,a:0):0,c:0):0;", - "((a:1.1,b:2):0.4,c:3):0;", - "(x:0,(a:1.1,(b:2,(quack:0.1,duck:0):0):0):0,c:3):1.1;", - }; - for (const auto& newick : newicks) { - auto collection = driver.ParseString(newick); - CHECK_EQ(newick, collection.Trees()[0].Newick(collection.TagTaxonMap())); - } - driver.Clear(); - // Note that the order of the taxa is given by the order in the translate table, not - // by the short names. We use that here to make sure that the ordering of the taxa is - // the same as that in the newick file below so that they can be compared. - auto nexus_collection = driver.ParseNexusFile("data/DS1.subsampled_10.t.reordered"); - CHECK_EQ(nexus_collection.TreeCount(), 10); - driver.Clear(); - auto newick_collection = driver.ParseNewickFile("data/DS1.subsampled_10.t.nwk"); - CHECK_EQ(nexus_collection, newick_collection); - driver.Clear(); - auto newick_collection_gz = - driver.ParseNewickFileGZ("data/DS1.subsampled_10.t.nwk.gz"); - CHECK_EQ(nexus_collection, newick_collection_gz); - driver.Clear(); - auto five_taxon = driver.ParseNewickFile("data/five_taxon_unrooted.nwk"); - std::vector correct_five_taxon_names({"x0", "x1", "x2", "x3", "x4"}); - CHECK_EQ(five_taxon.TaxonNames(), correct_five_taxon_names); - // Check that we can parse BEAST trees with [&comments], and that the different - // formatting of the translate block doesn't trip us up. - auto beast_nexus = driver.ParseNexusFile("data/test_beast_tree_parsing.nexus"); - // These are the taxa, in order, taken directly from the nexus file: - StringVector beast_taxa = { - "aDuckA_1976", "aDuckB_1977", "aItaly_1987", "aMallard_1985", - "hCHR_1983", "hCambr_1939", "hFortMon_1947", "hKiev_1979", - "hLenin_1954", "hMongol_1985", "hMongol_1991", "hNWS_1933", - "hPR_1934", "hSCar_1918.00", "hScot_1994", "hSuita_1989", - "hUSSR_1977", "sEhime_1980", "sIllino_1963", "sIowa_1930", - "sNebrask_1992", "sNewJers_1976", "sStHya_1991", "sWiscons_1961", - "sWiscons_1.998e3"}; - CHECK_EQ(beast_nexus.TaxonNames(), beast_taxa); - // Check that we got the whole tree. - for (const auto& [topology, count] : beast_nexus.TopologyCounter()) { - std::ignore = count; - CHECK_EQ(topology->LeafCount(), beast_taxa.size()); - } - auto beast_nexus_gz = - driver.ParseNexusFileGZ("data/test_beast_tree_parsing.nexus.gz"); - CHECK_EQ(beast_nexus, beast_nexus_gz); -} -#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/src/eigen_sugar.hpp b/src/eigen_sugar.hpp index 83a45dfde..6e7a7958e 100644 --- a/src/eigen_sugar.hpp +++ b/src/eigen_sugar.hpp @@ -71,73 +71,3 @@ std::string EigenMatrixToString(const EigenMatrix &mx) { os << "]"; return os.str(); } - -#ifdef DOCTEST_LIBRARY_INCLUDED - -void CheckVectorXdEquality(double value, const EigenVectorXd v, double tolerance) { - for (Eigen::Index i = 0; i < v.size(); i++) { - CHECK_LT(fabs(value - v[i]), tolerance); - } -}; - -void CheckVectorXdEquality(const EigenVectorXd v1, const EigenVectorXd v2, - double tolerance) { - CHECK_EQ(v1.size(), v2.size()); - for (Eigen::Index i = 0; i < v1.size(); i++) { - double error = fabs(v1[i] - v2[i]); - if (error > tolerance) { - std::cerr << "CheckVectorXdEquality failed for index " << i << ": " << v1[i] - << " vs " << v2[i] << std::endl; - } - CHECK_LT(error, tolerance); - } -}; - -// Return the maximum absolute difference between any two entries in vector. -double VectorXdMaxError(const EigenVectorXd v1, const EigenVectorXd v2) { - double max_error = 0.; - Assert(v1.size() == v2.size(), - "Cannot find max error of EigenVectorXd's of different sizes."); - for (Eigen::Index i = 0; i < v1.size(); i++) { - double error = fabs(v1[i] - v2[i]); - if (error > max_error) { - max_error = error; - } - } - return max_error; -} - -// Check if vectors are equal, within given tolerance for any two entries in vector. -bool VectorXdEquality(const EigenVectorXd v1, const EigenVectorXd v2, - double tolerance) { - if (v1.size() != v2.size()) { - return false; - } - for (Eigen::Index i = 0; i < v1.size(); i++) { - double error = fabs(v1[i] - v2[i]); - if (error > tolerance) { - return false; - } - } - return true; -}; - -void CheckVectorXdEqualityAfterSorting(const EigenVectorXdRef v1, - const EigenVectorXdRef v2, double tolerance) { - EigenVectorXd v1_sorted = v1; - EigenVectorXd v2_sorted = v2; - std::sort(v1_sorted.begin(), v1_sorted.end()); - std::sort(v2_sorted.begin(), v2_sorted.end()); - CheckVectorXdEquality(v1_sorted, v2_sorted, tolerance); -}; - -TEST_CASE( - "Make sure that EigenVectorXdOfStdVectorDouble makes a new vector rather than " - "wrapping data.") { - std::vector a = {1., 2., 3., 4.}; - EigenVectorXd b = EigenVectorXdOfStdVectorDouble(a); - a[0] = 99; - CHECK_EQ(b[0], 1.); -} - -#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/src/generic_sbn_instance.hpp b/src/generic_sbn_instance.hpp index c346bccf0..057abc0c3 100644 --- a/src/generic_sbn_instance.hpp +++ b/src/generic_sbn_instance.hpp @@ -496,7 +496,3 @@ class GenericSBNInstance { return multiplicative_factors; } }; - -#ifdef DOCTEST_LIBRARY_INCLUDED - -#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/src/gp_engine.hpp b/src/gp_engine.hpp index 790302b7d..0016e6b36 100644 --- a/src/gp_engine.hpp +++ b/src/gp_engine.hpp @@ -376,20 +376,3 @@ class GPEngine { Eigen::Vector4d stationary_distribution_ = substitution_model_.GetFrequencies(); EigenVectorXd site_pattern_weights_; }; - -#ifdef DOCTEST_LIBRARY_INCLUDED - -TEST_CASE("GPEngine") { - EigenVectorXd empty_vector; - SitePattern hello_site_pattern = SitePattern::HelloSitePattern(); - GPEngine engine(hello_site_pattern, 5, 5, "_ignore/mmapped_plv.data", - GPEngine::default_rescaling_threshold_, empty_vector, empty_vector, - empty_vector, false); - engine.SetTransitionMatrixToHaveBranchLength(0.75); - // Computed directly: - // https://en.wikipedia.org/wiki/Models_of_DNA_evolution#JC69_model_%28Jukes_and_Cantor_1969%29 - CHECK(fabs(0.52590958087 - engine.GetTransitionMatrix()(0, 0)) < 1e-10); - CHECK(fabs(0.1580301397 - engine.GetTransitionMatrix()(0, 1)) < 1e-10); -} - -#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/src/intpack.hpp b/src/intpack.hpp index 8d61acdf9..47a368285 100644 --- a/src/intpack.hpp +++ b/src/intpack.hpp @@ -19,21 +19,3 @@ inline uint32_t UnpackSecondInt(uint64_t x) { inline std::string StringOfPackedInt(uint64_t x) { return (std::to_string(UnpackFirstInt(x)) + "_" + std::to_string(UnpackSecondInt(x))); } - -#ifdef DOCTEST_LIBRARY_INCLUDED -inline void TestPacking(uint32_t a, uint32_t b) { - auto p = PackInts(a, b); - CHECK_EQ(UnpackFirstInt(p), a); - CHECK_EQ(UnpackSecondInt(p), b); -} - -TEST_CASE("intpack") { - TestPacking(3, 4); - TestPacking(UINT32_MAX, 4); - TestPacking(3, UINT32_MAX); - TestPacking(UINT32_MAX - 1, UINT32_MAX); - - // The ints are packed such that the first int takes priority in sorting. - CHECK_LT(PackInts(0, 4), PackInts(1, 0)); -} -#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/src/mmapped_matrix.hpp b/src/mmapped_matrix.hpp index f70c85219..6eb70f9b4 100644 --- a/src/mmapped_matrix.hpp +++ b/src/mmapped_matrix.hpp @@ -158,17 +158,3 @@ class MmappedMatrix { std::string file_path_; Scalar *mmapped_memory_; }; - -#ifdef DOCTEST_LIBRARY_INCLUDED -TEST_CASE("MmappedMatrix") { - Eigen::Index rows = 4; - Eigen::Index cols = 5; - using MmappedMatrixXd = MmappedMatrix; - { - MmappedMatrixXd mmapped_matrix("_ignore/mmapped_matrix.data", rows, cols); - mmapped_matrix.Get()(rows - 1, cols - 1) = 5.; - } // End of scope, so our mmap is destroyed and file written. - MmappedMatrixXd mmapped_matrix("_ignore/mmapped_matrix.data", rows, cols); - CHECK_EQ(mmapped_matrix.Get()(rows - 1, cols - 1), 5.); -} -#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/src/mmapped_plv.hpp b/src/mmapped_plv.hpp index e5a71476b..999f96e4f 100644 --- a/src/mmapped_plv.hpp +++ b/src/mmapped_plv.hpp @@ -48,14 +48,3 @@ class MmappedNucleotidePLV { private: MmappedMatrix mmapped_matrix_; }; - -#ifdef DOCTEST_LIBRARY_INCLUDED -TEST_CASE("MmappedNucleotidePLV") { - MmappedNucleotidePLV mmapped_plv("_ignore/mmapped_plv.data", 10); - auto plvs = mmapped_plv.Subdivide(2); - for (const auto &plv : plvs) { - CHECK_EQ(plv.rows(), MmappedNucleotidePLV::base_count_); - CHECK_EQ(plv.cols(), 5); - } -} -#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/src/nni_operation.hpp b/src/nni_operation.hpp index 0beb2a3ef..46d327a64 100644 --- a/src/nni_operation.hpp +++ b/src/nni_operation.hpp @@ -181,113 +181,3 @@ struct equal_to { } }; } // namespace std - -#ifdef DOCTEST_LIBRARY_INCLUDED - -// See tree diagram at: -// https://user-images.githubusercontent.com/31897211/136849710-de0dcbe3-dc2b-42b7-b3de-dd9b1a60aaf4.gif -TEST_CASE("NNIOperation") { - // Clades for NNI. - Bitset X("100"); - Bitset Y("010"); - Bitset Z("001"); - // Initial Child and Parent. - Bitset parent_in = Bitset::Subsplit(X, Y | Z); - Bitset child_in = Bitset::Subsplit(Y, Z); - NNIOperation nni_yz = NNIOperation(parent_in, child_in); - // Correct Solutions. - Bitset correct_parent_xy = Bitset::Subsplit(Y, X | Z); - Bitset correct_child_xy = Bitset::Subsplit(X, Z); - NNIOperation correct_nni_xy = NNIOperation(correct_parent_xy, correct_child_xy); - Bitset correct_parent_xz = Bitset::Subsplit(Z, Y | X); - Bitset correct_child_xz = Bitset::Subsplit(Y, X); - NNIOperation correct_nni_xz = NNIOperation(correct_parent_xz, correct_child_xz); - - // Swap X and Y - auto nni_xy = nni_yz.NNIOperationFromNeighboringSubsplits(false); - CHECK_EQ(correct_nni_xy, nni_xy); - // Swap X and Z - auto nni_xz = nni_yz.NNIOperationFromNeighboringSubsplits(true); - CHECK_EQ(correct_nni_xz, nni_xz); - - // Relationship is known (child_in is the rotated clade of parent_in) - auto nni_xy_2 = NNIOperation::NNIOperationFromNeighboringSubsplits( - parent_in, child_in, false, true); - CHECK_EQ(correct_nni_xy, nni_xy_2); - CHECK_THROWS(NNIOperation::NNIOperationFromNeighboringSubsplits(parent_in, child_in, - false, false)); -}; - -TEST_CASE("NNIOperation: NNISet") { - // Clades for NNI. - Bitset X("100"); - Bitset Y("010"); - Bitset Z("001"); - // Initial Child and Parent. - Bitset parent_in = Bitset::Subsplit(X, Y | Z); - Bitset child_in = Bitset::Subsplit(Y, Z); - NNIOperation nni_yz = NNIOperation(parent_in, child_in); - auto nni_xy = nni_yz.NNIOperationFromNeighboringSubsplits(false); - auto nni_xz = nni_yz.NNIOperationFromNeighboringSubsplits(true); - // Insert NNIs in various orders. - NNISet set_of_nnis_1 = NNISet(); - set_of_nnis_1.insert(nni_yz); - set_of_nnis_1.insert(nni_xy); - set_of_nnis_1.insert(nni_xz); - NNISet set_of_nnis_2 = NNISet(); - set_of_nnis_2.insert(nni_xy); - set_of_nnis_2.insert(nni_xz); - set_of_nnis_2.insert(nni_yz); - // Check proper ordering. - for (const auto &set_of_nnis : {set_of_nnis_1, set_of_nnis_2}) { - NNIOperation prv_nni = *set_of_nnis.begin(); - for (const auto &nni : set_of_nnis) { - CHECK_MESSAGE(nni >= prv_nni, "NNIs not ordered in NNISet."); - } - } -} - -TEST_CASE("NNIOperation: NNI Clade Mapping") { - // Clades for NNI. - std::vector clades = {Bitset("100"), Bitset("010"), Bitset("001")}; - // Iterate over all possible assignments of {X,Y,Z} clades to {sister, left, right}. - std::vector> assignments; - for (size_t x = 0; x < 3; x++) { - for (size_t y = 0; y < 3; y++) { - if (y == x) { - continue; - } - for (size_t z = 0; z < 3; z++) { - if ((z == x) || (z == y)) { - continue; - } - assignments.push_back({x, y, z}); - } - } - } - // For each possible pre-NNI, check that NNI produces the correct mapping. - for (const auto assign : assignments) { - const Bitset X(clades[assign[0]]); - const Bitset Y(clades[assign[1]]); - const Bitset Z(clades[assign[2]]); - const Bitset parent = Bitset::Subsplit(X, Y | Z); - const Bitset child = Bitset::Subsplit(Y, Z); - const NNIOperation pre_nni(parent, child); - - for (const auto which_clade_swap : {true, false}) { - const auto post_nni = - pre_nni.NNIOperationFromNeighboringSubsplits(which_clade_swap); - const auto clade_map = - NNIOperation::BuildNNICladeMapFromPreNNIToNNI(pre_nni, post_nni); - for (const auto pre_clade_type : - {NNIClade::ParentSister, NNIClade::ChildLeft, NNIClade::ChildRight}) { - const auto post_clade_type = clade_map[pre_clade_type]; - CHECK_MESSAGE( - pre_nni.GetClade(pre_clade_type) == post_nni.GetClade(post_clade_type), - "NNI Clade Map did not produce a proper mapping."); - } - } - } -} - -#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/src/node.hpp b/src/node.hpp index 39e6d2fca..c31e3e928 100644 --- a/src/node.hpp +++ b/src/node.hpp @@ -272,89 +272,3 @@ struct equal_to { } }; } // namespace std - -#ifdef DOCTEST_LIBRARY_INCLUDED - -typedef std::unordered_map TagBitsetMap; - -// Make a map from Tags to the bitset representing the leaves below the Tag. -// Just used for testing now. -TagBitsetMap TagLeafSetMapOf(Node::NodePtr topology) { - TagBitsetMap map; - auto leaf_count = topology->LeafCount(); - topology->Postorder([&map, leaf_count](const Node* node) { - Bitset bitset(static_cast(leaf_count)); - if (node->IsLeaf()) { - bitset.set(node->MaxLeafID()); - } else { - // Take the union of the children below. - for (const auto& child : node->Children()) { - bitset |= map.at(child->Tag()); - } - } - SafeInsert(map, node->Tag(), std::move(bitset)); - }); - return map; -} - -TEST_CASE("Node") { - Node::NodePtrVec examples = Node::ExampleTopologies(); - Node::NodePtr t1 = examples[0]; // 0: (0,1,(2,3)) - Node::NodePtr t1_twin = examples[1]; // 1; (0,1,(2,3)) again - Node::NodePtr t2 = examples[2]; // 2: (0,2,(1,3)) - Node::NodePtr t3 = examples[3]; // 3: (0,(1,(2,3))) - // ((((0,1)7,2)8,(3,4)9)10,5,6)11; - Node::NodePtr tbig = Node::OfParentIdVector({7, 7, 8, 9, 9, 11, 11, 8, 10, 10, 11}); - - std::vector triples; - auto collect_triple = [&triples](const Node* node, const Node* sister, - const Node* parent) { - triples.push_back(std::to_string(node->Id()) + ", " + std::to_string(sister->Id()) + - ", " + std::to_string(parent->Id())); - }; - tbig->TriplePreorder(collect_triple, collect_triple); - std::vector correct_triples( - {"10, 5, 6", "8, 9, 10", "7, 2, 8", "0, 1, 7", "1, 0, 7", "2, 7, 8", "9, 8, 10", - "3, 4, 9", "4, 3, 9", "5, 6, 10", "6, 10, 5"}); - CHECK_EQ(triples, correct_triples); - - // This is actually a non-trivial test (see note in Node constructor above), - // which shows why we need bit rotation. - CHECK_NE(t1->Hash(), t2->Hash()); - - CHECK_EQ(t1, t1_twin); - CHECK_NE(t1, t2); - - // Tree with trifurcation at the root. - Node::NodePtr t1_alt = Node::OfParentIdVector({5, 5, 4, 4, 5}); - CHECK_EQ(t1, t1_alt); - // Bifurcating tree. - Node::NodePtr t3_alt = Node::OfParentIdVector({6, 5, 4, 4, 5, 6}); - CHECK_EQ(t3, t3_alt); - - for (const auto& topology : examples) { - CHECK_EQ(topology, Node::OfParentIdVector(topology->ParentIdVector())); - CHECK_EQ(topology, topology->DeepCopy()); - auto tag_leaf_set_map = TagLeafSetMapOf(topology); - topology->Preorder([&tag_leaf_set_map](const Node* node) { - CHECK_EQ(node->Leaves(), tag_leaf_set_map.at(node->Tag())); - }); - } - - // Check Deroot when we deroot on the right. - CHECK_EQ(t1, t3->Deroot()); - // Check Deroot when we deroot on the left. - CHECK_EQ(Node::OfParentIdVector({3, 3, 3}), - // tree ((0,1)3,2)4 - Node::OfParentIdVector({3, 3, 4, 4})->Deroot()); - - CHECK_EQ(Node::OfParentIdVector({4, 4, 5, 6, 5, 6}), Node::Ladder(4)); - - SizeVector correct_sisters({5, 4, 3, 2}); - SizeVector sisters; - t3->RootedSisterAndLeafTraversal([&sisters](const Node* sister, const Node* leaf) { - sisters.push_back(sister->Id()); - }); - CHECK_EQ(correct_sisters, sisters); -} -#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/src/numerical_utils.hpp b/src/numerical_utils.hpp index b4edc72fe..d74afe6e0 100644 --- a/src/numerical_utils.hpp +++ b/src/numerical_utils.hpp @@ -73,42 +73,3 @@ std::optional DescribeFloatingPointEnvironmentExceptions(); void ReportFloatingPointEnvironmentExceptions(std::string context = ""); } // namespace NumericalUtils - -#ifdef DOCTEST_LIBRARY_INCLUDED -TEST_CASE("NumericalUtils") { - double log_x = log(2); - double log_y = log(3); - double log_sum = NumericalUtils::LogAdd(log_x, log_y); - CHECK_LT(fabs(log_sum - 1.609438), 1e-5); - - EigenVectorXd log_vec(10); - double log_sum2 = DOUBLE_NEG_INF; - for (Eigen::Index i = 0; i < log_vec.size(); i++) { - log_vec(i) = log(i + 1); - log_sum2 = NumericalUtils::LogAdd(log_sum2, log_vec(i)); - } - log_sum = NumericalUtils::LogSum(log_vec); - CHECK_LT(fabs(log_sum - 4.007333), 1e-5); - CHECK_LT(fabs(log_sum2 - 4.007333), 1e-5); - - NumericalUtils::ProbabilityNormalizeInLog(log_vec); - for (Eigen::Index i = 0; i < log_vec.size(); i++) { - CHECK_LT(fabs(log_vec(i) - (log(i + 1) - log_sum)), 1e-5); - } - - NumericalUtils::Exponentiate(log_vec); - double sum = 0.0; - for (Eigen::Index i = 0; i < log_vec.size(); i++) { - sum += log_vec(i); - } - CHECK_LT(fabs(sum - 1), 1e-5); - - // Here we use volatile to avoid GCC optimizing away the variable. - volatile double d = 4.; - std::ignore = d; - d /= 0.; - auto fp_description = NumericalUtils::DescribeFloatingPointEnvironmentExceptions(); - CHECK_EQ(*fp_description, - "The following floating point problems have been encountered: FE_DIVBYZERO"); -} -#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/src/psp_indexer.hpp b/src/psp_indexer.hpp index 9096dee7d..d5266a0e7 100644 --- a/src/psp_indexer.hpp +++ b/src/psp_indexer.hpp @@ -65,7 +65,3 @@ class PSPIndexer { size_t after_rootsplits_index_; size_t first_empty_index_; }; - -#ifdef DOCTEST_LIBRARY_INCLUDED -TEST_CASE("PSPIndexer") {} -#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/src/pv_handler.hpp b/src/pv_handler.hpp index bb9882d2f..3fb658b11 100644 --- a/src/pv_handler.hpp +++ b/src/pv_handler.hpp @@ -504,31 +504,3 @@ class PSVHandler using PSVNodeHandler = PSVHandler; using PSVEdgeHandler = PSVHandler; - -#ifdef DOCTEST_LIBRARY_INCLUDED - -// Check that PLV iterator iterates over all PLVs exactly once. -TEST_CASE("PLVHandler: EnumIterator") { - using namespace PartialVectorType; - const auto plv_types = PLVTypeEnum::TypeArray(); - std::map plv_visited_map; - // Iterate using vector. - for (const PLVType plv_type : plv_types) { - plv_visited_map.insert({plv_type, 0}); - } - // Iterate using EnumIterator. - for (const PLVType plv_type : PLVTypeEnum::Iterator()) { - CHECK_MESSAGE(plv_visited_map.find(plv_type) != plv_visited_map.end(), - "Iterator has PLV not in plv_vector."); - plv_visited_map.at(plv_type) += 1; - } - // Check that each was visited only once. - for (const auto [plv_type, visit_count] : plv_visited_map) { - std::ignore = plv_type; - CHECK_FALSE_MESSAGE(visit_count < 1, "One or more PLVs skipped by EnumIterator."); - CHECK_FALSE_MESSAGE(visit_count > 1, - "One or more PLVs in visited more than once by EnumIterator."); - } -} - -#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/src/reindexer.hpp b/src/reindexer.hpp index a77036d44..eb35fb509 100644 --- a/src/reindexer.hpp +++ b/src/reindexer.hpp @@ -206,109 +206,3 @@ class Reindexer { private: SizeVector data_; }; - -#ifdef DOCTEST_LIBRARY_INCLUDED -TEST_CASE("Reindexer: IdentityReindexer") { - // Check that IdentityReindexer returns correctly. - Reindexer correct_default({0, 1, 2, 3, 4, 5, 6, 7, 8, 9}); - CHECK_EQ(correct_default, Reindexer::IdentityReindexer(10)); -} - -TEST_CASE("IsValidReindexer") { - // Index appears more than once. - CHECK_FALSE(Reindexer({1, 3, 0, 0}).IsValid()); - // Missing an index and/or index is out of range. - CHECK_FALSE(Reindexer({1, 3, 4, 2}).IsValid()); - // Valid reindexer. - CHECK(Reindexer({1, 3, 0, 2}).IsValid()); -} - -TEST_CASE("Reindexer: Reindex") { - // Check that Reindex throws if the given vector and reindexer have different - // sizes. - SizeVector old_size_vector{7, 8, 9}; - Reindexer reindexer({2, 0, 3, 1}); - CHECK_THROWS(Reindexer::Reindex(old_size_vector, reindexer)); - // Check that Reindex returns correctly. - reindexer = Reindexer({2, 0, 1}); - SizeVector new_size_vector = Reindexer::Reindex(old_size_vector, reindexer); - SizeVector correct_new_size_vector{8, 9, 7}; - CHECK_EQ(new_size_vector, correct_new_size_vector); - // Check that Reindex also works with EigenVectorXd and additional values. - EigenVectorXd old_eigen_vector(3); - old_eigen_vector << 7, 8, 9; - EigenVectorXd additional_values(2); - additional_values << 10, 11; - reindexer = Reindexer({2, 4, 0, 3, 1}); - EigenVectorXd new_eigen_vector = - Reindexer::Reindex(old_eigen_vector, reindexer, additional_values); - EigenVectorXd correct_new_eigen_vector(5); - correct_new_eigen_vector << 9, 11, 7, 10, 8; - CHECK_EQ(new_eigen_vector, correct_new_eigen_vector); -} - -TEST_CASE("Reindexer: InvertReindexer") { - // Check that inverting a vector twice results in the original vector. - Reindexer reindexer({1, 3, 0, 2}); - Reindexer correct_inverted_reindexer({2, 0, 3, 1}); - Reindexer inverted_reindexer = reindexer.InvertReindexer(); - CHECK_EQ(inverted_reindexer, correct_inverted_reindexer); - Reindexer correct_reindexer = Reindexer({1, 3, 0, 2}); - reindexer = inverted_reindexer.InvertReindexer(); - CHECK_EQ(reindexer, correct_reindexer); -} - -TEST_CASE("Reindexer: RemapIdVector") { - // Check that Reindex throws if the given vector has an index out of bounds of the - // reindexer. - SizeVector size_vector{3, 5}; - Reindexer reindexer({2, 0, 3, 1}); - CHECK_THROWS(Reindexer::RemapIdVector(size_vector, reindexer)); - // Check that RemapIdVector returns correctly. - size_vector = {3, 5}; - reindexer = Reindexer({2, 0, 3, 1, 6, 4, 5}); - Reindexer::RemapIdVector(size_vector, reindexer); - SizeVector correct_size_vector = {1, 4}; - CHECK_EQ(size_vector, correct_size_vector); -} - -TEST_CASE("Reindexer: ReassignAndShift") { - // Check that ReassignAndShift returns correctly when old_id > new_id. - Reindexer reindexer({0, 1, 2, 3, 4, 5, 6}); - reindexer.ReassignAndShift(4, 1); - Reindexer correct_reindexer({0, 2, 3, 4, 1, 5, 6}); - CHECK_EQ(reindexer, correct_reindexer); - reindexer.ReassignAndShift(5, 2); - correct_reindexer = Reindexer({0, 3, 4, 5, 1, 2, 6}); - CHECK_EQ(reindexer, correct_reindexer); - reindexer.ReassignAndShift(1, 3); - correct_reindexer = Reindexer({0, 2, 4, 5, 3, 1, 6}); - CHECK_EQ(reindexer, correct_reindexer); - // Check that ReassignAndShift returns correctly when old_id = new_id. - reindexer = Reindexer({1, 0, 4, 6, 5, 3, 2}); - reindexer.ReassignAndShift(4, 4); - correct_reindexer = Reindexer({1, 0, 4, 6, 5, 3, 2}); - CHECK_EQ(reindexer, correct_reindexer); - // Check that ReassignAndShift returns correctly when old_id < new_id. - reindexer = Reindexer({6, 0, 4, 1, 5, 3, 2}); - reindexer.ReassignAndShift(1, 5); - correct_reindexer = Reindexer({6, 0, 3, 5, 4, 2, 1}); - CHECK_EQ(reindexer, correct_reindexer); -} - -TEST_CASE("Reindexer: ComposeWith") { - // Check that identity reindexer composed with a second reindexer results in that - // reindexer. - Reindexer identity_reindexer, inverted_reindexer, pairswap_reindexer, - composed_reindexer, correct_reindexer; - identity_reindexer = Reindexer::IdentityReindexer(6); - inverted_reindexer = Reindexer({5, 4, 3, 2, 1, 0}); - pairswap_reindexer = Reindexer({1, 0, 3, 2, 5, 4}); - composed_reindexer = identity_reindexer; - composed_reindexer = composed_reindexer.ComposeWith(inverted_reindexer); - composed_reindexer = composed_reindexer.ComposeWith(pairswap_reindexer); - correct_reindexer = Reindexer({4, 5, 2, 3, 0, 1}); - CHECK_EQ(composed_reindexer, correct_reindexer); -} - -#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/src/rooted_sbn_instance.hpp b/src/rooted_sbn_instance.hpp index 5310a37d6..233462125 100644 --- a/src/rooted_sbn_instance.hpp +++ b/src/rooted_sbn_instance.hpp @@ -65,654 +65,3 @@ class RootedSBNInstance : public PreRootedSBNInstance { void ParseDatesFromCSV(const std::string& csv_path, bool initialize_time_trees_using_branch_lengths); }; - -#ifdef DOCTEST_LIBRARY_INCLUDED - -#include "doctest_constants.hpp" - -// Centered finite difference approximation of the derivative wrt rate. -std::vector DerivativeStrictClock(RootedSBNInstance& inst) { - double eps = 0.00000001; - std::vector rates; - std::vector gradients; - - for (auto& tree : inst.tree_collection_.trees_) { - rates.push_back(tree.rates_[0]); - tree.rates_.assign(tree.rates_.size(), rates.back() - eps); - } - auto lm = inst.LogLikelihoods(); - - int i = 0; - for (auto& tree : inst.tree_collection_.trees_) { - tree.rates_.assign(tree.rates_.size(), rates[i++] + eps); - } - auto lp = inst.LogLikelihoods(); - - for (size_t index = 0; index < lm.size(); index++) { - gradients.push_back((lp[index] - lm[index]) / (2. * eps)); - } - return gradients; -} - -// Centered finite difference approximation of the derivative wrt to each rate. -std::vector> DerivativeRelaxedClock(RootedSBNInstance& inst) { - double eps = 0.00000001; - std::vector> gradients; - std::vector lp; - std::vector lm; - size_t edge_count = inst.TaxonCount() * 2 - 2; - - for (size_t index = 0; index < edge_count; index++) { - std::vector gradient; - std::vector rates; - for (size_t i = 0; i < inst.tree_collection_.TreeCount(); i++) { - double value = inst.tree_collection_.trees_[i].rates_[index]; - rates.push_back(value); - inst.tree_collection_.trees_[i].rates_[index] = rates.back() - eps; - } - lm = inst.LogLikelihoods(); - - for (size_t i = 0; i < inst.tree_collection_.TreeCount(); i++) { - inst.tree_collection_.trees_[i].rates_[index] = rates[i] + eps; - } - lp = inst.LogLikelihoods(); - - for (size_t i = 0; i < inst.tree_collection_.TreeCount(); i++) { - inst.tree_collection_.trees_[i].rates_[index] = rates[i]; - gradient.push_back((lp[i] - lm[i]) / (2. * eps)); - } - - gradients.push_back(gradient); - } - return gradients; -} - -RootedSBNInstance MakeFiveTaxonRootedInstance() { - RootedSBNInstance inst("charlie"); - inst.ReadNewickFile("data/five_taxon_rooted.nwk"); - inst.ProcessLoadedTrees(); - return inst; -} - -TEST_CASE("RootedSBNInstance: subsplit support and TrainSimpleAverage") { - auto inst = MakeFiveTaxonRootedInstance(); - auto pretty_indexer = inst.PrettyIndexer(); - StringSet pretty_indexer_set{pretty_indexer.begin(), pretty_indexer.end()}; - // The indexer_ is to index the sbn_parameters_. Note that neither of these - // data structures attempt to catalog the complete collection of rootsplits or - // PCSPs, but just those that are present in the the input trees. - // - // The indexer_ and sbn_parameters_ are laid out as follows (I'll just call it - // the "index" in what follows). Say there are rootsplit_count rootsplits in - // the support. - // The first rootsplit_count entries of the index are assigned to the - // rootsplits (again, those rootsplits that are present for some rooting of - // the unrooted input trees). The rest of the entries of the index are laid out as - // blocks of parameters for PCSPs that share the same parent. Take a look at the - // description of PCSP bitsets (and the unit tests) in bitset.hpp to understand the - // notation used here. - // - // In contrast to the unrooted case, we can write out the pretty indexer here and - // verify it by hand. There is the block structure in which the two children of - // 10000|01111 are grouped together. - StringSet correct_pretty_indexer_set{ - "00000|11111|00111", // ((x0,x1),(x2,(x3,x4))) - "00000|11111|01111", // (x0,(((x1,x3),x2),x4)) and ((x1,((x2,x4),x3)),x0) - "00000|11111|00010", // (x3,((x0,(x4,x1)),x2)) - "00100|01010|00010", // ((x1,x3),x2) - "00111|11000|01000", // ((x0,x1),(x2,(x3,x4))) - "00100|00011|00001", // (x2,(x3,x4)) - "11000|00111|00011", // ((x0,x1),(x2,(x3,x4))) - "00100|11001|01001", // ((x0,(x4,x1)),x2) - "10000|01001|00001", // (x0,(x4,x1)) - "01000|00111|00010", // (x1,((x2,x4),x3)) - "10000|01111|00001", // (x0,(((x1,x3),x2),x4)) - "10000|01111|00111", // ((x1,((x2,x4),x3)),x0) - "00010|00101|00001", // ((x2,x4),x3) - "00001|01110|00100", // (((x1,x3),x2),x4) - "00010|11101|00100" // (x3,((x0,(x4,x1)),x2)) - }; - CHECK_EQ(pretty_indexer_set, correct_pretty_indexer_set); - - // Test of rooted IndexerRepresentationOf. - // Topology is ((0,1),(2,(3,4)));, or with internal nodes ((0,1)5,(2,(3,4)6)7)8; - auto indexer_test_rooted_topology = Node::OfParentIdVector({5, 5, 7, 6, 6, 8, 7, 8}); - auto correct_rooted_indexer_representation = - StringSet({"00000|11111|00111", "11000|00111|00011", "00100|00011|00001", - "00111|11000|01000"}); - CHECK_EQ(inst.StringIndexerRepresentationOf(indexer_test_rooted_topology, - out_of_sample_index), - correct_rooted_indexer_representation); - - inst.TrainSimpleAverage(); - StringVector correct_taxon_names({"x0", "x1", "x2", "x3", "x4"}); - CHECK_EQ(inst.SBNSupport().TaxonNames(), correct_taxon_names); - StringDoubleVector correct_parameters({{"00000|11111|00111", 0.25}, - {"00000|11111|01111", 0.5}, - {"00000|11111|00010", 0.25}, - {"00100|01010|00010", 1}, - {"00111|11000|01000", 1}, - {"00100|00011|00001", 1}, - {"11000|00111|00011", 1}, - {"00100|11001|01001", 1}, - {"10000|01001|00001", 1}, - {"01000|00111|00010", 1}, - {"10000|01111|00001", 0.5}, - {"10000|01111|00111", 0.5}, - {"00010|00101|00001", 1}, - {"00001|01110|00100", 1}, - {"00010|11101|00100", 1}}); - std::sort(correct_parameters.begin(), correct_parameters.end()); - auto parameters = inst.PrettyIndexedSBNParameters(); - std::sort(parameters.begin(), parameters.end()); - CHECK_EQ(correct_parameters.size(), parameters.size()); - for (size_t i = 0; i < correct_parameters.size(); i++) { - CHECK_EQ(correct_parameters[i].first, parameters[i].first); - CHECK_LT(fabs(correct_parameters[i].second - parameters[i].second), 1e-8); - } -} - -TEST_CASE("RootedSBNInstance: UnconditionalSubsplitProbabilities") { - RootedSBNInstance inst("rooted instance"); - inst.ReadNewickFile("data/five_taxon_rooted_more.nwk"); - inst.ProcessLoadedTrees(); - inst.TrainSimpleAverage(); - // See diagram at https://github.com/phylovi/bito/issues/349#issuecomment-898022916 - // Numbering in comments is... node: subsplit. - StringDoubleMap correct_parameters({{"1100000111", 0.5}, // 10: 01|234 - {"1000001111", 0.3}, // 15: 0|1234 - {"1110100010", 0.2}, // 19: 0124|3 - {"1100100100", 0.2}, // 18: 014|2 - {"0100000111", 0.1}, // 14: 1|234 - {"0111000001", 0.2}, // 13: 123|4 - {"0101000100", 0.2}, // 12: 13|2 - {"1000001001", 0.2}, // 17: 0|14 - {"0010000011", 0.4}, // 8: 2|34 - {"0011000001", 0.2}, // 6: 23|4 - {"1000001000", 0.5}, // 9: 0|1 - {"0100000010", 0.2}, // 11: 1|3 - {"0100000001", 0.2}, // 16: 1|4 - {"0010000010", 0.2}, // 5: 2|3 - {"0001000001", 0.4}} // 7: 3|4 - ); - - auto subsplit_probabilities = inst.UnconditionalSubsplitProbabilities(); - CHECK_EQ(correct_parameters.size(), subsplit_probabilities.size()); - for (const auto& [subsplit, probability] : subsplit_probabilities) { - CHECK_LT(fabs(correct_parameters.at(subsplit.ToString()) - probability), 1e-8); - } -} - -// Instance SA-trained on a sample of 20-taxon trees. -RootedSBNInstance MakeRootedSimpleAverageInstance() { - RootedSBNInstance inst("rooted instance"); - inst.ReadNewickFile("data/rooted_simple_average.nwk"); - inst.ProcessLoadedTrees(); - inst.TrainSimpleAverage(); - return inst; -} - -TEST_CASE("RootedSBNInstance: TrainSimpleAverage on 20 taxa") { - auto inst = MakeRootedSimpleAverageInstance(); - auto results = inst.PrettyIndexedSBNParameters(); - // Values confirmed with - // https://github.com/mdkarcher/vbsupertree/commit/b7f87f711e8a1044b7c059b5a92e94c117d8cee1 - auto correct_map = - CSV::StringDoubleMapOfCSV("data/rooted_simple_average_results.csv"); - for (const auto& [found_string, found_probability] : results) { - CHECK(fabs(found_probability - correct_map.at(found_string)) < 1e-6); - } -} - -RootedSBNInstance MakeFluInstance(bool initialize_time_trees) { - RootedSBNInstance inst("charlie"); - inst.ReadNewickFile("data/fluA.tree"); - inst.ParseDatesFromTaxonNames(initialize_time_trees); - inst.ReadFastaFile("data/fluA.fa"); - PhyloModelSpecification simple_specification{"JC69", "constant", "strict"}; - inst.PrepareForPhyloLikelihood(simple_specification, 1); - return inst; -} - -TEST_CASE("RootedSBNInstance: gradients") { - auto inst = MakeFluInstance(true); - for (auto& tree : inst.tree_collection_.trees_) { - tree.rates_.assign(tree.rates_.size(), 0.001); - } - - auto likelihood = inst.LogLikelihoods(); - double physher_ll = -4777.616349; - double physher_jacobian = -9.25135166; - double physher_ll_jacobian = physher_ll + physher_jacobian; - CHECK_LT(fabs(likelihood[0] - physher_ll_jacobian), 0.0001); - - auto gradients = inst.PhyloGradients(); - std::vector physher_gradients = { - -0.593654, 6.441290, 11.202945, 5.173924, -0.904631, 2.731402, 3.157131, - 7.082914, 10.305417, 13.988206, 20.709336, 48.897993, 99.164949, 130.205747, - 17.314019, 21.033290, -1.336335, 12.259822, 22.887291, 27.176564, 47.487426, - 3.637276, 12.955169, 15.315953, 83.254605, -3.806996, 105.385095, 4.874023, - 22.754466, 6.036534, 25.651478, 29.535185, 29.598789, 1.817247, 10.598685, - 76.259248, 56.481423, 10.679778, 6.587179, 3.330556, -4.622247, 33.417304, - 63.415767, 188.809515, 23.540875, 17.421076, 1.222568, 22.372012, 34.239511, - 3.486115, 4.098873, 13.200954, 19.726890, 96.808738, 4.240029, 7.414585, - 48.871694, 3.488516, 82.969065, 9.009334, 8.032474, 3.981016, 6.543650, - 53.702423, 37.835952, 2.840831, 7.517186, 19.936861}; - for (size_t i = 0; i < physher_gradients.size(); i++) { - CHECK_LT(fabs(gradients[0].gradient_[PhyloGradient::ratios_root_height_key_][i] - - physher_gradients[i]), - 0.0001); - } - CHECK_LT(fabs(gradients[0].log_likelihood_ - physher_ll), 0.0001); -} - -TEST_CASE("RootedSBNInstance: clock gradients") { - auto inst = MakeFluInstance(true); - for (auto& tree : inst.tree_collection_.trees_) { - tree.rates_.assign(tree.rates_.size(), 0.001); - } - - auto likelihood = inst.LogLikelihoods(); - double physher_ll = -4777.616349; - double physher_jacobian = -9.25135166; - double physher_ll_jacobian = physher_ll + physher_jacobian; - CHECK_LT(fabs(likelihood[0] - physher_ll_jacobian), 0.0001); - - // Gradient with a strict clock. - auto gradients_strict = inst.PhyloGradients(); - std::vector gradients_strict_approx = DerivativeStrictClock(inst); - CHECK_LT(fabs(gradients_strict[0].gradient_[PhyloGradient::clock_model_key_][0] - - gradients_strict_approx[0]), - 0.001); - CHECK_LT(fabs(gradients_strict[0].log_likelihood_ - physher_ll), 0.001); - - // Gradient with a "relaxed" clock. - auto& tree = inst.tree_collection_.trees_[0]; - // Make a clock with some rate variation. - for (size_t i = 0; i < tree.rates_.size(); i++) { - tree.rates_[i] *= i % 3 + 1.0; - } - tree.rate_count_ = tree.rates_.size(); - - auto gradients_relaxed = inst.PhyloGradients(); - auto gradients_relaxed_approx = DerivativeRelaxedClock(inst); - - for (size_t j = 0; j < gradients_relaxed_approx.size(); j++) { - CHECK_LT(fabs(gradients_relaxed[0].gradient_[PhyloGradient::clock_model_key_][j] - - gradients_relaxed_approx[j][0]), - 0.001); - } -} - -TEST_CASE("RootedSBNInstance: GTR gradients") { - auto inst = MakeFluInstance(true); - PhyloModelSpecification gtr_specification{"GTR", "constant", "strict"}; - inst.PrepareForPhyloLikelihood(gtr_specification, 1); - for (auto& tree : inst.tree_collection_.trees_) { - tree.rates_.assign(tree.rates_.size(), 0.001); - } - auto param_block_map = inst.GetPhyloModelParamBlockMap(); - EigenVectorXdRef frequencies = param_block_map.at(GTRModel::frequencies_key_); - EigenVectorXdRef rates = param_block_map.at(GTRModel::rates_key_); - frequencies << 0.1, 0.2, 0.3, 0.4; - rates << 0.05, 0.1, 0.15, 0.20, 0.25, 0.25; - - auto likelihood = inst.LogLikelihoods(); - double phylotorch_ll = -5221.438941335706; - double physher_jacobian = -9.25135166; - double expected_ll_jacobian = phylotorch_ll + physher_jacobian; - CHECK_LT(fabs(likelihood[0] - expected_ll_jacobian), 0.001); - - auto gradients = inst.PhyloGradients(); - std::vector phylotorch_gradients = {49.06451538, 151.83105912, 26.40235659, - -8.25135661, 75.29759338, 352.56545247, - 90.07046995, 30.12301652}; - for (size_t i = 0; i < phylotorch_gradients.size(); i++) { - CHECK_LT(fabs(gradients[0].gradient_[PhyloGradient::substitution_model_key_][i] - - phylotorch_gradients[i]), - 0.001); - } - CHECK_LT(fabs(gradients[0].log_likelihood_ - phylotorch_ll), 0.001); -} - -TEST_CASE("RootedSBNInstance: HKY gradients") { - auto inst = MakeFluInstance(true); - PhyloModelSpecification specification{"HKY", "constant", "strict"}; - inst.PrepareForPhyloLikelihood(specification, 1); - for (auto& tree : inst.tree_collection_.trees_) { - tree.rates_.assign(tree.rates_.size(), 0.001); - } - auto param_block_map = inst.GetPhyloModelParamBlockMap(); - EigenVectorXdRef frequencies = - param_block_map.at(SubstitutionModel::frequencies_key_); - EigenVectorXdRef rates = param_block_map.at(SubstitutionModel::rates_key_); - frequencies << 0.1, 0.2, 0.3, 0.4; - rates << 3.0; - - auto likelihood = inst.LogLikelihoods(); - double phylotorch_ll = -4931.770106816288; - double physher_jacobian = -9.25135166; - double expected_ll_jacobian = phylotorch_ll + physher_jacobian; - CHECK_LT(fabs(expected_ll_jacobian - likelihood[0]), 0.001); - - auto gradients = inst.PhyloGradients(); - std::vector phylotorch_gradients = {18.218397759598506, 309.56536079428355, - 47.15713892857574, 42.98132033283943}; - for (size_t i = 0; i < phylotorch_gradients.size(); i++) { - CHECK_LT( - fabs(gradients[0].gradient_["substitution_model"][i] - phylotorch_gradients[i]), - 0.001); - } - CHECK_LT(fabs(phylotorch_ll - gradients[0].log_likelihood_), 0.0001); -} - -TEST_CASE("RootedSBNInstance: Weibull gradients") { - auto inst = MakeFluInstance(true); - PhyloModelSpecification weibull_specification{"JC69", "weibull+4", "strict"}; - inst.PrepareForPhyloLikelihood(weibull_specification, 1); - for (auto& tree : inst.tree_collection_.trees_) { - tree.rates_.assign(tree.rates_.size(), 0.001); - } - auto param_block_map = inst.GetPhyloModelParamBlockMap(); - param_block_map.at(WeibullSiteModel::shape_key_).setConstant(0.1); - - auto likelihood = inst.LogLikelihoods(); - double physher_ll = -4618.2062529058; - double physher_jacobian = -9.25135166; - double physher_ll_jacobian = physher_ll + physher_jacobian; - CHECK_LT(fabs(likelihood[0] - physher_ll_jacobian), 0.0001); - - // Gradient wrt Weibull site model. - auto gradients = inst.PhyloGradients(); - double physher_gradient = -5.231329; - CHECK_LT(fabs(gradients[0].gradient_["site_model"][0] - physher_gradient), 0.001); - CHECK_LT(fabs(gradients[0].log_likelihood_ - physher_ll), 0.001); -} - -TEST_CASE("RootedSBNInstance: parsing dates") { - RootedSBNInstance inst("charlie"); - inst.ReadNexusFile("data/test_beast_tree_parsing.nexus"); - inst.ParseDatesFromTaxonNames(true); - std::vector dates; - for (const auto& [tag, date] : inst.tree_collection_.GetTagDateMap()) { - std::ignore = tag; - dates.push_back(date); - } - std::sort(dates.begin(), dates.end()); - CHECK_EQ(dates[0], 0); - CHECK_EQ(dates.back(), 80.0); - - RootedSBNInstance alt_inst("betty"); - alt_inst.ReadNexusFile("data/test_beast_tree_parsing.nexus"); - alt_inst.tree_collection_.ParseDatesFromCSV("data/test_beast_tree_parsing.csv", true); - CHECK_EQ(inst.tree_collection_.GetTagDateMap(), - alt_inst.tree_collection_.GetTagDateMap()); -} - -TEST_CASE("RootedSBNInstance: uninitialized time trees raise an exception") { - auto inst = MakeFluInstance(false); - CHECK_THROWS(inst.PhyloGradients()); -} - -TEST_CASE("RootedSBNInstance: reading SBN parameters from a CSV") { - auto inst = MakeFiveTaxonRootedInstance(); - inst.ReadSBNParametersFromCSV("data/test_modifying_sbn_parameters.csv"); - auto pretty_indexer = inst.PrettyIndexer(); - auto gpcsp_it = - std::find(pretty_indexer.begin(), pretty_indexer.end(), "10000|01111|00001"); - CHECK(gpcsp_it != pretty_indexer.end()); - auto gpcsp_idx = std::distance(pretty_indexer.begin(), gpcsp_it); - CHECK_LT(fabs(inst.sbn_parameters_[gpcsp_idx] - log(0.15)), 1e-8); - inst.SetSBNParameters({}, false); - CHECK_EQ(inst.sbn_parameters_[gpcsp_idx], DOUBLE_MINIMUM); - CHECK_THROWS(inst.SetSBNParameters({{"10000|01111|00001", -5.}}, false)); -} - -TEST_CASE("RootedSBNInstance: SBN parameter round trip") { - std::string csv_test_file_path = "_ignore/for_sbn_parameter_round_trip.csv"; - auto inst = MakeRootedSimpleAverageInstance(); - auto original_normalized_sbn_parameters = inst.NormalizedSBNParameters(); - inst.SBNParametersToCSV(csv_test_file_path); - inst.ReadSBNParametersFromCSV(csv_test_file_path); - auto reloaded_normalized_sbn_parameters = inst.NormalizedSBNParameters(); - CheckVectorXdEquality(original_normalized_sbn_parameters, - reloaded_normalized_sbn_parameters, 1e-6); -} - -TEST_CASE("RootedSBNInstance: BuildCollectionByDuplicatingFirst") { - auto empty_collection = RootedTreeCollection(); - CHECK_THROWS(empty_collection.BuildCollectionByDuplicatingFirst(5)); - auto inst = MakeFiveTaxonRootedInstance(); - auto trees = inst.tree_collection_.BuildCollectionByDuplicatingFirst(5); - CHECK_EQ(trees.GetTree(0), trees.GetTree(1)); - // Check that the trees don't refer to the same place in memory. - CHECK_NE(&trees.GetTree(0), &trees.GetTree(1)); - inst = MakeFluInstance(true); - auto& base_flu_tree = inst.tree_collection_.GetTree(0); - trees = inst.tree_collection_.BuildCollectionByDuplicatingFirst(5); - CHECK_EQ(base_flu_tree, trees.GetTree(1)); -} - -TEST_CASE("RootedSBNInstance: PhyloFlags for Gradient Requests") { - // GP Instance default output for gradients. - auto CreateNewInstance = []() { - auto inst = MakeFluInstance(true); - PhyloModelSpecification gtr_specification{"GTR", "constant", "strict"}; - inst.PrepareForPhyloLikelihood(gtr_specification, 1); - for (auto& tree : inst.tree_collection_.trees_) { - tree.rates_.assign(tree.rates_.size(), 0.001); - } - auto param_block_map = inst.GetPhyloModelParamBlockMap(); - EigenVectorXdRef frequencies = param_block_map.at(GTRModel::frequencies_key_); - EigenVectorXdRef rates = param_block_map.at(GTRModel::rates_key_); - frequencies << 0.1, 0.2, 0.3, 0.4; - rates << 0.05, 0.1, 0.15, 0.20, 0.25, 0.25; - return inst; - }; - // "Golden" instance for determining correctness. - auto gold_inst = CreateNewInstance(); - auto gold_likelihoods = gold_inst.LogLikelihoods(); - auto gold_gradients = gold_inst.PhyloGradients(); - size_t num_trees = gold_inst.tree_collection_.trees_.size(); - - using FlagMap = std::map; - using FlagVector = std::vector; - using MapkeyVector = std::vector; - // Split map into keys and values. - auto SplitMapIntoKeysAndValues = - [](const FlagMap& map) -> std::pair { - FlagVector keys; - MapkeyVector values; - for (const auto& [key, value] : map) { - keys.push_back(key); - values.push_back(value); - } - return std::make_pair(keys, values); - }; - - // Iterate through all flag combinations. - auto IterateOverAllCombinations = - [](FlagMap& all_flags_mapkeys, FlagVector& all_flags, - std::function func) { - size_t num_flags = all_flags_mapkeys.size(); - size_t num_combinations = pow(2, num_flags); - for (size_t i = 0; i < num_combinations; i++) { - FlagMap used_flags_mapkeys, unused_flags_mapkeys; - // Split between groups of used and unused flags. - for (size_t j = 1, k = 0; j < num_combinations; j <<= 1, k += 1) { - if ((j & i)) { - used_flags_mapkeys.insert(*all_flags_mapkeys.find(all_flags[k])); - } else { - unused_flags_mapkeys.insert(*all_flags_mapkeys.find(all_flags[k])); - } - } - func(used_flags_mapkeys, unused_flags_mapkeys, all_flags_mapkeys, false); - func(used_flags_mapkeys, unused_flags_mapkeys, all_flags_mapkeys, true); - } - }; - - // Test that expected flagged keys are populated with correct data, - // and that unflagged keys are not stored in map. - auto ComparePhyloGradients = - [&CreateNewInstance, &gold_gradients, &SplitMapIntoKeysAndValues, &num_trees]( - FlagMap& used_flags_mapkeys, FlagMap& unused_flags_mapkeys, - FlagMap& all_flags, bool pass_externally = false) { - // Create instance and run phylogradients with used_flags. - auto inst = CreateNewInstance(); - const auto [used_flags, used_mapkeys] = - SplitMapIntoKeysAndValues(used_flags_mapkeys); - const auto [unused_flags, unused_mapkeys] = - SplitMapIntoKeysAndValues(unused_flags_mapkeys); - std::ignore = unused_flags; - std::vector gradients; - // pass flags via external arguments - if (pass_externally) { - PhyloFlags phylo_flags; - for (const auto& flag : used_flags) { - phylo_flags.SetFlag(flag); - } - phylo_flags.SetRunDefaultsFlag(false); - gradients = inst.PhyloGradients(phylo_flags); - } - // pass flags via internal instance - else { - inst.MakePhyloFlags(); - auto& flags = inst.GetPhyloFlags(); - for (const auto& flag : used_flags) { - flags.SetFlag(flag); - } - flags.SetRunDefaultsFlag(false); - gradients = inst.PhyloGradients(); - flags.ClearFlags(); - } - // Check that used fields are keyed and populated correctly. - for (size_t i = 0; i < num_trees; i++) { - auto& grad_map = gradients[i].gradient_; - auto& gold_grad_map = gold_gradients[i].gradient_; - // Check used fields are not properly populated. - for (const auto& used_mapkey : used_mapkeys) { - CHECK_MESSAGE(grad_map.find(used_mapkey.GetKey()) != grad_map.end(), - "grad_map does not have a key that should exist."); - auto& gold_grad_data = gold_grad_map[used_mapkey.GetKey()]; - auto& grad_data = grad_map[used_mapkey.GetKey()]; - DoubleVector abs_diff = DoubleVector(gold_grad_data.size()); - std::transform(gold_grad_data.begin(), gold_grad_data.end(), - grad_data.begin(), abs_diff.begin(), - [](const double a, const double b) { return abs(a - b); }); - double max_diff = *std::max_element(abs_diff.begin(), abs_diff.end()); - CHECK_MESSAGE(max_diff < 0.01, - "gold_grad_map and grad_map did not produce the same data " - "for the same flag."); - } - // Check unused fields are not populated. - for (const auto& unused_mapkey : unused_mapkeys) { - CHECK_MESSAGE(grad_map.find(unused_mapkey.GetKey()) == grad_map.end(), - "grad_map has a key that should not exist."); - } - } - }; - - // Test gradient "include" options. - // Pairs of input flags to output mapkeys. - FlagMap gradient_flags_mapkeys; - gradient_flags_mapkeys.insert( - {PhyloGradientFlagOptions::clock_model_, PhyloGradientMapkeys::clock_model_}); - gradient_flags_mapkeys.insert({PhyloGradientFlagOptions::ratios_root_height_, - PhyloGradientMapkeys::ratios_root_height_}); - gradient_flags_mapkeys.insert({PhyloGradientFlagOptions::substitution_model_, - PhyloGradientMapkeys::substitution_model_}); - gradient_flags_mapkeys.insert({PhyloGradientFlagOptions::substitution_model_, - PhyloGradientMapkeys::substitution_model_rates_}); - gradient_flags_mapkeys.insert( - {PhyloGradientFlagOptions::substitution_model_, - PhyloGradientMapkeys::substitution_model_frequencies_}); - - auto gradient_flags = SplitMapIntoKeysAndValues(gradient_flags_mapkeys).first; - IterateOverAllCombinations(gradient_flags_mapkeys, gradient_flags, - ComparePhyloGradients); - - // Test likelihood "exclude" options. - auto LikelihoodExcludeLogDeterminant = [&CreateNewInstance, &gold_likelihoods]() { - auto inst = CreateNewInstance(); - StringBoolVector flag_vector = { - {LogLikelihoodFlagOptions::include_log_det_jacobian_likelihood_.GetFlag(), - false}}; - auto flags = PhyloFlags(flag_vector, true); - double likelihood_exclude_log_det = inst.LogLikelihoods(flags)[0]; - double log_det = RootedGradientTransforms::LogDetJacobianHeightTransform( - inst.tree_collection_.trees_[0]); - double gold_likelihood = gold_likelihoods[0]; - CHECK_MESSAGE( - gold_likelihood != likelihood_exclude_log_det, - "LogLikelihood should not be equal to (LogLikelihoodExcludingLogdet."); - CHECK_MESSAGE(gold_likelihood == (likelihood_exclude_log_det + log_det), - "LogLikelihood should be equal to (LogLikelihoodExcludingLogdet + " - "LogDetJacobianHeightTransform."); - }; - LikelihoodExcludeLogDeterminant(); - - // Test gradient "exclude" options. - auto GradientExcludeLogDeterminant = [&CreateNewInstance, &gold_gradients]() { - auto inst = CreateNewInstance(); - StringBoolVector flag_vector = { - {PhyloGradientFlagOptions::include_log_det_jacobian_gradient_.GetFlag(), - false}}; - auto flags = PhyloFlags(flag_vector, true); - GradientMap grad_map = inst.PhyloGradients(flags)[0].gradient_; - DoubleVector exclude_log_det = - grad_map[PhyloGradientMapkeys::ratios_root_height_.GetKey()]; - DoubleVector log_det = RootedGradientTransforms::GradientLogDeterminantJacobian( - inst.tree_collection_.trees_[0]); - DoubleVector include_log_det = - gold_gradients[0].gradient_[PhyloGradientMapkeys::ratios_root_height_.GetKey()]; - - double max_diff; - DoubleVector abs_diff = DoubleVector(include_log_det.size()); - std::transform(include_log_det.begin(), include_log_det.end(), - exclude_log_det.begin(), abs_diff.begin(), - [](const double a, const double b) { return abs(a - b); }); - max_diff = *std::max_element(abs_diff.begin(), abs_diff.end()); - CHECK_MESSAGE(max_diff > 0.01, - "Gradient should not be equal to GradientExcludingLogDet."); - DoubleVector exclude_log_det_plus_log_det = DoubleVector(include_log_det.size()); - std::transform(exclude_log_det.begin(), exclude_log_det.end(), log_det.begin(), - exclude_log_det_plus_log_det.begin(), - [](const double a, const double b) { return a + b; }); - std::transform(include_log_det.begin(), include_log_det.end(), - exclude_log_det_plus_log_det.begin(), abs_diff.begin(), - [](const double a, const double b) { return abs(a - b); }); - max_diff = *std::max_element(abs_diff.begin(), abs_diff.end()); - CHECK_MESSAGE(max_diff < 0.01, - "Gradient should be equal to (GradientExcludingLogdet + " - "GradientLogDetJacobian)."); - }; - GradientExcludeLogDeterminant(); - - // Test gradient "set" options. - auto GradientSetDelta = [&CreateNewInstance, &gold_gradients]() { - std::ignore = gold_gradients; - auto inst = CreateNewInstance(); - StringDoubleVector flag_vector = { - {PhyloGradientFlagOptions::set_gradient_delta_.GetFlag(), 1.0e1}}; - auto flags = PhyloFlags(flag_vector, true); - GradientMap grad_map = inst.PhyloGradients(flags)[0].gradient_; - DoubleVector subst_grad = - grad_map[PhyloGradientMapkeys::substitution_model_.GetKey()]; - // delta = 1.0e-6 (default) - DoubleVector gold_subst_grad_1e6 = {49.0649, 151.831, 26.4022, -8.25114, - 75.2975, 352.565, 90.0701, 30.1228}; - // delta = 1.0e1 - DoubleVector gold_subst_grad_1e1 = {-73.2611, 25.4074, -33.2865, -54.0479, - 47.9938, -2696.06, -84.2954, 6.0563}; - DoubleVector abs_diff = DoubleVector(subst_grad.size()); - std::transform(subst_grad.begin(), subst_grad.end(), gold_subst_grad_1e1.begin(), - abs_diff.begin(), - [](const double a, const double b) { return abs(a - b); }); - double max_diff = *std::max_element(abs_diff.begin(), abs_diff.end()); - CHECK_MESSAGE(max_diff < 0.01, - "Delta value set by flag did not result in correct gradient values."); - }; - GradientSetDelta(); -} - -#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/src/rooted_tree.hpp b/src/rooted_tree.hpp index 9a6b2b77e..b2e058a76 100644 --- a/src/rooted_tree.hpp +++ b/src/rooted_tree.hpp @@ -128,42 +128,3 @@ class RootedTree : public Tree { inline bool operator!=(const RootedTree& lhs, const RootedTree& rhs) { return !(lhs == rhs); } - -#ifdef DOCTEST_LIBRARY_INCLUDED -TEST_CASE("RootedTree") { - // To understand this test, please see - // https://github.com/phylovi/bito/issues/187#issuecomment-618421183 - auto tree = RootedTree::Example(); - std::vector correct_height_ratios({1. / 3.5, 1.5 / 4., 7.}); - for (size_t i = 0; i < correct_height_ratios.size(); ++i) { - CHECK_EQ(correct_height_ratios[i], tree.height_ratios_[i]); - } - std::vector correct_node_heights({5., 3., 0., 1., 2., 4.5, 7.}); - std::vector correct_node_bounds({5., 3., 0., 1., 1., 3., 5.}); - std::vector correct_branch_lengths({2., 1.5, 2., 1., 2.5, 2.5}); - for (size_t i = 0; i < correct_node_heights.size(); ++i) { - CHECK_EQ(correct_node_heights[i], tree.node_heights_[i]); - CHECK_EQ(correct_node_bounds[i], tree.node_bounds_[i]); - } - for (size_t i = 0; i < correct_branch_lengths.size(); ++i) { - CHECK_EQ(correct_branch_lengths[i], tree.branch_lengths_[i]); - } - // Test ratios to heights. - const double arbitrary_dummy_number = -5.; - std::fill(tree.LeafCount() + tree.node_heights_.begin(), // First internal node. - tree.node_heights_.end(), arbitrary_dummy_number); - EigenVectorXd new_height_ratios(3); - // Issue #205: eliminate this code duplication. - // Root height is multiplied by 2. - new_height_ratios << 1. / 3.5, 1.5 / 4., 14.; - std::vector new_correct_node_heights({5., 3., 0., 1., 2.75, 7.125, 14.}); - std::vector new_correct_branch_lengths({9., 4.125, 2.75, 1.75, 4.375, 6.875}); - tree.InitializeTimeTreeUsingHeightRatios(new_height_ratios); - for (size_t i = 0; i < correct_node_heights.size(); ++i) { - CHECK_EQ(new_correct_node_heights[i], tree.node_heights_[i]); - } - for (size_t i = 0; i < correct_branch_lengths.size(); ++i) { - CHECK_EQ(new_correct_branch_lengths[i], tree.branch_lengths_[i]); - } -} -#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/src/rooted_tree_collection.hpp b/src/rooted_tree_collection.hpp index e35885e82..c81748325 100644 --- a/src/rooted_tree_collection.hpp +++ b/src/rooted_tree_collection.hpp @@ -40,8 +40,3 @@ class RootedTreeCollection : public PreRootedTreeCollection { void ProcessTreeDates(bool initialize_time_trees_using_branch_lengths); void ParseDatesFromCSVButDontInitializeTimeTrees(const std::string& csv_path); }; - -#ifdef DOCTEST_LIBRARY_INCLUDED -// Test of ParseDatesFromTaxonNames appears in rooted_sbn_instance.hpp. -TEST_CASE("RootedTreeCollection") {} -#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/src/sankoff_handler.hpp b/src/sankoff_handler.hpp index 95a9ec9d5..e0d927d7b 100644 --- a/src/sankoff_handler.hpp +++ b/src/sankoff_handler.hpp @@ -125,132 +125,3 @@ class SankoffHandler { double resizing_factor_; PSVNodeHandler psv_handler_; }; - -#ifdef DOCTEST_LIBRARY_INCLUDED - -TEST_CASE("SankoffHandler: Tests on single site sequence.") { - auto fasta_file = "data/hello_single_nucleotide.fasta"; - auto newick_file = "data/hello_rooted.nwk"; - Alignment alignment = Alignment::ReadFasta(fasta_file); - Driver driver; - RootedTreeCollection tree_collection = - RootedTreeCollection::OfTreeCollection(driver.ParseNewickFile(newick_file)); - SitePattern site_pattern = SitePattern(alignment, tree_collection.TagTaxonMap()); - Node::NodePtr topology = tree_collection.GetTree(0).Topology(); - size_t taxon_count = site_pattern.TaxonCount(); - - // transitions have cost 1 and transversions have cost 2.5 - auto costs = CostMatrix(); - costs << 0., 2.5, 1., 2.5, // - 2.5, 0., 2.5, 1., // - 1., 2.5, 0., 2.5, // - 2.5, 1., 2.5, 0.; // - - SankoffHandler sh = SankoffHandler(costs, site_pattern, "_ignore/mmapped_psv.data"); - - // testing RunSankoff for one site - sh.RunSankoff(topology); - - // testing GenerateLeafPartials() method (which is run as first step of RunSankoff) - SankoffPartialVec leaves_test = sh.PartialsAtPattern(PSVType::PLeft, 0); - auto big_double = SankoffHandler::big_double_; - SankoffPartial leaves_correct_pattern_0(4, topology->Id() + 1); - // column 1 is G(jupiter), column 2 is C(mars), Column 3 is G(saturn) - leaves_correct_pattern_0 << big_double, big_double, big_double, 0., 0., big_double, - 0., big_double, 0., 0., 0., big_double, 0., 0., 0., big_double, big_double, - big_double, 0., 0.; - for (size_t r = 0; r < taxon_count; r++) { - CHECK(leaves_test[r].isApprox(leaves_correct_pattern_0.col(r))); - } - - // test parsimony score for RunSankoff - CHECK_LT(fabs(sh.ParsimonyScore(0) - 2.5), 1e-10); - - // testing 3rd constructor: SankoffHandler(SankoffMatrix, SitePattern) constructor - SankoffMatrix sm = SankoffMatrix(costs); - SankoffHandler sh2 = SankoffHandler(sm, site_pattern, "_ignore/mmapped_psv.data"); - - // testing ParentPartial() - auto child_partials = Eigen::Matrix(); - child_partials << 2.5, 3.5, 3.5, 3.5, 2.5, 3.5, 3.5, 4.5; - auto parent_test = Eigen::Matrix(); - parent_test.setZero(); - for (size_t child = 0; child < 2; child++) { - parent_test += sh2.ParentPartial(child_partials.col(child)); - } - auto parent_correct = Eigen::Matrix(); - parent_correct << 6., 7., 6., 8.; - CHECK(parent_test.isApprox(parent_correct)); -} - -TEST_CASE("SankoffHandler: Asymmetric cost matrix test on single site sequence.") { - auto fasta_file = "data/hello_single_nucleotide.fasta"; - auto newick_file = "data/hello_rooted.nwk"; - Alignment alignment = Alignment::ReadFasta(fasta_file); - Driver driver; - RootedTreeCollection tree_collection = - RootedTreeCollection::OfTreeCollection(driver.ParseNewickFile(newick_file)); - SitePattern site_pattern = SitePattern(alignment, tree_collection.TagTaxonMap()); - Node::NodePtr topology = tree_collection.GetTree(0).Topology(); - - // transitions have cost 1 and transversions have cost 2.5 - auto costs = CostMatrix(); - costs << 0., 2., 3., 4., // - 5., 0., 7., 8., // - 9., 10., 0., 12., // - 13., 14., 15., 0.; // - - SankoffHandler sh = SankoffHandler(costs, site_pattern, "_ignore/mmapped_psv.data"); - sh.RunSankoff(topology); - CHECK_LT(fabs(sh.ParsimonyScore(0) - 8.), 1e-10); -} - -TEST_CASE("SankoffHandler: Testing sequence gap characters in GenerateLeafPartials()") { - auto fasta_file = "data/hello.fasta"; - auto newick_file = "data/hello_rooted.nwk"; - Alignment alignment = Alignment::ReadFasta(fasta_file); - Driver driver; - RootedTreeCollection tree_collection = - RootedTreeCollection::OfTreeCollection(driver.ParseNewickFile(newick_file)); - SitePattern site_pattern = SitePattern(alignment, tree_collection.TagTaxonMap()); - Node::NodePtr topology = tree_collection.GetTree(0).Topology(); - size_t taxon_count = site_pattern.TaxonCount(); - - // test set up for SankoffHandler with default cost matrix - SankoffHandler default_sh = SankoffHandler(site_pattern, "_ignore/mmapped_psv.data"); - - // testing GenerateLeafPartials() method - default_sh.GenerateLeafPartials(); - auto leaves_test = default_sh.PartialsAtPattern(PSVType::PLeft, 14); - SankoffPartial leaves_correct_pattern_14(4, topology->Id() + 1); - auto big_double = SankoffHandler::big_double_; - // column 1 is G(jupiter), column 2 is -(mars), Column 3 is G(saturn) - leaves_correct_pattern_14 << big_double, 0., big_double, 0., 0., big_double, 0., - big_double, 0., 0., 0., 0., 0., 0., 0., big_double, 0., big_double, 0., 0.; - for (size_t r = 0; r < taxon_count; r++) { - CHECK(leaves_test[r].isApprox(leaves_correct_pattern_14.col(r))); - } -} - -TEST_CASE("SankoffHandler: RunSankoff and ParsimonyScore Tests") { - auto fasta_file = "data/parsimony_leaf_seqs.fasta"; - auto newick_file = "data/parsimony_tree_0_score_75.0.nwk"; - Alignment alignment = Alignment::ReadFasta(fasta_file); - Driver driver; - RootedTreeCollection tree_collection = - RootedTreeCollection::OfTreeCollection(driver.ParseNewickFile(newick_file)); - SitePattern site_pattern = SitePattern(alignment, tree_collection.TagTaxonMap()); - Node::NodePtr topology = tree_collection.GetTree(0).Topology(); - - // test set up for SankoffHandler with default cost matrix - SankoffHandler default_sh = SankoffHandler(site_pattern, "_ignore/mmapped_psv.data"); - - double parsimony_score_correct = 75.; - default_sh.RunSankoff(topology); - - for (NodeId node_id = 0; node_id < topology->Id() + 1; node_id++) { - CHECK_LT(fabs(default_sh.ParsimonyScore(node_id) - parsimony_score_correct), 1e-10); - } -} - -#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/src/sankoff_matrix.hpp b/src/sankoff_matrix.hpp index f67797c3e..6e24627a3 100644 --- a/src/sankoff_matrix.hpp +++ b/src/sankoff_matrix.hpp @@ -44,34 +44,3 @@ class SankoffMatrix { private: CostMatrix cost_matrix_; }; - -#ifdef DOCTEST_LIBRARY_INCLUDED -enum Nucleotides { A, C, G, T }; - -TEST_CASE("SankoffMatrix: Testing SankoffMatrix Getter/Setter Methods") { - auto cm = SankoffMatrix(); - CHECK_LT(fabs(cm.GetCost(0, 0) - 0.), 1e-10); - CHECK_LT(fabs(cm.GetCost(G, C) - 1.), 1e-10); - - cm.UpdateMatrix(A, G, 3.); - auto test_matrix = Eigen::Matrix(); - test_matrix << 0., 1., 3., 1., 1., 0., 1., 1., 1., 1., 0., 1., 1., 1., 1., 0.; - CHECK(test_matrix.isApprox(cm.GetMatrix())); - CHECK_LT(fabs(cm.GetCost(A, G) - 3.), 1e-10); - CHECK_THROWS(cm.UpdateMatrix(G, G, 3.)); -} - -TEST_CASE("SankoffMatrix: Create SankoffMatrix from given cost matrix") { - auto costs = Eigen::Matrix(); - costs << 0., 2.5, 1., 2.5, 2.5, 0., 2.5, 1., 1., 2.5, 0., 2.5, 2.5, 1., 2.5, 0.; - auto cm = SankoffMatrix(costs); - CHECK_LT(fabs(cm.GetCost(A, C) - 2.5), 1e-10); - - auto costs_invalid = Eigen::Matrix(); - // non-zero values on diagonal - costs_invalid << 1., 2., 3., 4., 5., 6., 7., 8., 9., 10., 11., 12., 13., 14., 15., - 16.; - CHECK_THROWS(new SankoffMatrix(costs_invalid)); -} - -#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/src/sbn_maps.hpp b/src/sbn_maps.hpp index 659bd14a7..f70a772f0 100644 --- a/src/sbn_maps.hpp +++ b/src/sbn_maps.hpp @@ -166,27 +166,3 @@ struct hash { } }; } // namespace std - -#ifdef DOCTEST_LIBRARY_INCLUDED - -TEST_CASE("SBNMaps") { - auto topology0 = Node::ExampleTopologies()[0]; - - // (0,1,(2,3)4)5; - auto correct_id_id_set_map = - std::unordered_map({{5, Bitset("111111")}, - {1, Bitset("010000")}, - {0, Bitset("100000")}, - {2, Bitset("001000")}, - {3, Bitset("000100")}, - {4, Bitset("001110")}}); - - for (const auto& iter : SBNMaps::IdIdSetMapOf(topology0)) { - CHECK_EQ(correct_id_id_set_map.at(iter.first), iter.second); - } - - // Tests comparing to vbpi appear in Python test code. - // Tests of IndexerRepresentationOf in unrooted_sbn_instance.hpp. -} - -#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/src/sbn_probability.hpp b/src/sbn_probability.hpp index ba73345b8..5c70bd222 100644 --- a/src/sbn_probability.hpp +++ b/src/sbn_probability.hpp @@ -68,149 +68,3 @@ double SumOf(EigenConstVectorXdRef vec, const SizeVector& indices, double starting_value); } // namespace SBNProbability - -#ifdef DOCTEST_LIBRARY_INCLUDED - // Here we hardcode in "ground truth" values from - // https://github.com/zcrabbit/sbn. - // See https://github.com/phylovi/bito/pull/167 for details on how this code was - // run. - -EigenVectorXd ExpectedSAVector() { - EigenVectorXd expected_SA(100); - expected_SA << 0.1563122979972875, 0.1563122979972875, 0.1225902102462595, - 0.003813409758997299, 0.06405479308023015, 0.1225902102462595, - 0.006496265198833325, 0.07224488861161361, 0.07224488861161361, - 0.09211800063278303, 0.050235906509724905, 0.07224488861161361, - 0.1225902102462595, 0.004000036290989688, 0.000785970418989169, - 0.015740902738698836, 0.0007369945657169131, 0.092118000632783, - 0.050235906509724905, 0.15631229799728746, 0.004517219839302666, - 0.1225902102462595, 0.020070904829444434, 0.07224488861161361, - 0.00826101112145943, 0.1563122979972875, 0.050235906509724905, 0.092118000632783, - 6.6925344669117846e-06, 0.012000108872969078, 0.00107615168209648, - 0.00487602847011386, 0.00524108566424323, 0.1563122979972875, - 0.006470871758283066, 0.050235906509724905, 0.0034098101830328945, - 0.1563122979972875, 0.07224488861161361, 0.0036073007280301274, - 0.009488612393535554, 0.005657493542553093, 0.007936324421697116, - 0.1225902102462595, 0.1563122979972875, 0.0064788184404525545, 0.1563122979972875, - 0.006493612301549656, 0.1225902102462595, 0.1225902102462595, 0.06405479308023015, - 0.06405479308023015, 0.092118000632783, 0.006224174813063337, - 0.006496265198833326, 0.15631229799728746, 0.002156957252761021, - 0.008283255738394914, 0.012789795178479234, 0.1225902102462595, - 0.0057598153715508514, 0.1225902102462595, 0.1563122979972875, - 0.12259021024625949, 0.15631229799728746, 0.004505082963907347, - 0.15631229799728746, 0.06405479308023015, 0.050235906509724905, - 0.00016398394968572145, 0.1225902102462595, 0.15631229799728752, - 0.050235906509724905, 0.1563122979972875, 0.003933969974285363, - 0.09211800063278297, 0.07224488861161361, 0.1563122979972875, 0.12259021024625949, - 0.012098733104319886, 0.00028556179453190954, 0.005744340855421819, - 0.00299072194405209, 0.0031839409290006357, 0.092118000632783, 0.1225902102462595, - 0.0015789121009827038, 0.1563122979972875, 0.1225902102462595, - 0.009488612393535554, 0.008035213534600344, 0.008283255738394914, - 0.1225902102462595, 0.1563122979972875, 0.012789795178479234, 0.00826101112145943, - 0.15631229799728746, 0.1563122979972875, 0.015740902738698836, - 0.0014116239862321806; - return expected_SA; -} - -// Expected EM vectors with alpha = 0. -std::tuple ExpectedEMVectorsAlpha0() { - // 1 iteration of EM with alpha = 0. - EigenVectorXd expected_EM_0_1(100); - expected_EM_0_1 << 0.15636219370379975, 0.15636219370379975, 0.12263720847530954, - 0.0038161261257420274, 0.0641198257552132, 0.12263720847530954, - 0.006486659269203554, 0.07229291766902365, 0.07229291766902365, - 0.09217334703350938, 0.05029011931468532, 0.07229291766902365, - 0.12263720847530954, 0.004003916595779366, 0.0007856587472007348, - 0.01573322403407416, 0.0007374660239687015, 0.09217334703350938, - 0.05029011931468532, 0.15636219370379975, 0.004512401354352734, - 0.12263720847530954, 0.02005981904435064, 0.07229291766902365, - 0.008265715290818319, 0.15636219370379975, 0.05029011931468532, - 0.09217334703350938, 6.696764561669613e-06, 0.0120117421642559, - 0.0010771644269441463, 0.004896166585246872, 0.005249064166033721, - 0.15636219370379975, 0.006473675486085066, 0.05029011931468532, - 0.0032873955583662736, 0.15636219370379975, 0.07229291766902365, - 0.0036100017361450775, 0.009434696399838294, 0.0056590388012875215, - 0.007977630170932788, 0.12263720847530954, 0.15636219370379975, - 0.006482919565829875, 0.15636219370379975, 0.006552518113293269, - 0.12263720847530954, 0.12263720847530954, 0.0641198257552132, 0.0641198257552132, - 0.09217334703350938, 0.0062569566301722964, 0.006486659269203554, - 0.15636219370379975, 0.002157895979240378, 0.008270476015894701, - 0.012800407342249945, 0.12263720847530954, 0.0057533024269482485, - 0.12263720847530954, 0.15636219370379975, 0.12263720847530954, - 0.15636219370379975, 0.004509802427487057, 0.15636219370379975, - 0.0641198257552132, 0.05029011931468532, 0.00016356223078203, 0.12263720847530954, - 0.15636219370379975, 0.05029011931468532, 0.15636219370379975, - 0.0039366703105347105, 0.09217334703350938, 0.07229291766902365, - 0.15636219370379975, 0.12263720847530954, 0.01202920464244238, - 0.00028206667942050513, 0.005749993943460951, 0.0029930413584166584, - 0.00329320520591652, 0.09217334703350938, 0.12263720847530954, - 0.0015794165564839767, 0.15636219370379975, 0.12263720847530954, - 0.009434696399838294, 0.0080145373179354, 0.008270476015894701, - 0.12263720847530954, 0.15636219370379975, 0.012800407342249945, - 0.008265715290818319, 0.15636219370379975, 0.15636219370379975, - 0.01573322403407416, 0.0014108855861473272; - - // 23 iterations of EM with alpha = 0. - EigenVectorXd expected_EM_0_23(100); - expected_EM_0_23 << 0.17652149361215352, 0.17652149361215352, 0.13955673648946823, - 0.0064491608851600735, 0.05848390318274005, 0.13955673648946823, - 0.015825262650921094, 0.056647494412346275, 0.056647494412346275, - 0.07263326598713499, 0.046048205076811774, 0.056647494412346275, - 0.13955673648946823, 0.004489402988562556, 0.0008454696589522312, - 0.01696511452269485, 0.0007597630896160637, 0.07263326598713499, - 0.046048205076811774, 0.17652149361215352, 0.007245923330084535, - 0.13955673648946823, 0.021613944434184986, 0.056647494412346275, - 0.014714698661351094, 0.17652149361215352, 0.046048205076811774, - 0.07263326598713499, 9.203216973858281e-06, 0.012351059173222767, - 0.0009871200936099765, 0.004930024591016917, 0.00491121394019874, - 0.17652149361215352, 0.003698082517142961, 0.046048205076811774, - 0.005102502844331727, 0.17652149361215352, 0.056647494412346275, - 0.002888359861489329, 0.010768901737247942, 0.004400293712986419, - 0.009082480764670433, 0.13955673648946823, 0.17652149361215352, - 0.011355455748479546, 0.1765214936121535, 0.004904756855047689, - 0.13955673648946823, 0.13955673648946823, 0.05848390318274005, - 0.05848390318274005, 0.07263326598713499, 0.0071809191341133454, - 0.015825262650921094, 0.17652149361215352, 0.002748656816901356, - 0.020534769315758854, 0.015225481783759537, 0.13955673648946823, - 0.009401422446411185, 0.13955673648946823, 0.17652149361215352, - 0.13955673648946823, 0.17652149361215352, 0.005835611123722098, - 0.17652149361215352, 0.05848390318274005, 0.046048205076811774, - 0.00021701159553434837, 0.13955673648946823, 0.17652149361215352, - 0.046048205076811774, 0.17652149361215352, 0.003632704913367913, - 0.07263326598713499, 0.056647494412346275, 0.17652149361215352, - 0.13955673648946823, 0.013620606755282007, 0.0013952624329697414, - 0.007542542618796534, 0.004980505041486884, 0.0049091058169283925, - 0.07263326598713499, 0.13955673648946823, 0.0018467480153313562, - 0.17652149361215352, 0.13955673648946823, 0.010768901737247942, - 0.00801061474692208, 0.020534769315758854, 0.13955673648946823, - 0.17652149361215352, 0.015225481783759537, 0.014714698661351094, - 0.1765214936121535, 0.17652149361215352, 0.01696511452269485, - 0.0010195073633053277; - - return {expected_EM_0_1, expected_EM_0_23}; -} - -// Expected EM vector with alpha = 0.5 -EigenVectorXd ExpectedEMVectorAlpha05() { - // 100 iterations of EM with alpha = 0.5, from Andy's Rev implementation. - EigenVectorXd expected_EM_05_100(100); - expected_EM_05_100 << 0.156334, 0.156334, 0.1226123, 0.003816548, 0.06410458, - 0.1226123, 0.006490481, 0.07228169, 0.07228169, 0.09216116, 0.05027706, - 0.07228169, 0.1226123, 0.00400394, 0.0007856247, 0.01573095, 0.0007376477, - 0.09216116, 0.05027706, 0.156334, 0.004514539, 0.1226123, 0.02005742, 0.07228169, - 0.008265798, 0.156334, 0.05027706, 0.09216116, 6.695972e-06, 0.01201178, - 0.001077308, 0.004899056, 0.005247481, 0.156334, 0.006473987, 0.05027706, - 0.003445344, 0.156334, 0.07228169, 0.0036098, 0.009448465, 0.005659673, - 0.007976663, 0.1226123, 0.156334, 0.006482844, 0.156334, 0.006417704, 0.1226123, - 0.1226123, 0.06410458, 0.06410458, 0.09216116, 0.006256103, 0.006490481, 0.156334, - 0.002157991, 0.008275552, 0.01279726, 0.1226123, 0.005756163, 0.1226123, 0.156334, - 0.1226123, 0.156334, 0.004509251, 0.156334, 0.06410458, 0.05027706, 0.0001636864, - 0.1226123, 0.156334, 0.05027706, 0.156334, 0.003936684, 0.09216116, 0.07228169, - 0.156334, 0.1226123, 0.01204707, 0.0002831188, 0.005749409, 0.002993311, - 0.003384393, 0.09216116, 0.1226123, 0.001579132, 0.156334, 0.1226123, 0.009448465, - 0.008020635, 0.008275552, 0.1226123, 0.156334, 0.01279726, 0.008265798, 0.156334, - 0.156334, 0.01573095, 0.001410898; - return expected_EM_05_100; -} - -#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/src/site_model.hpp b/src/site_model.hpp index dfd8bd5fb..a92a4142c 100644 --- a/src/site_model.hpp +++ b/src/site_model.hpp @@ -77,32 +77,3 @@ class WeibullSiteModel : public SiteModel { EigenVectorXd category_rates_; EigenVectorXd category_proportions_; }; - -#ifdef DOCTEST_LIBRARY_INCLUDED -#include -TEST_CASE("SiteModel") { - // Test 1: First we test using the "built in" default values. - auto weibull_model = std::make_unique(4, 1.0); - const EigenVectorXd rates = weibull_model->GetCategoryRates(); - EigenVectorXd rates_r(4); - rates_r << 0.1457844, 0.5131316, 1.0708310, 2.2702530; - CheckVectorXdEquality(rates, rates_r, 0.0001); - - // Test 2: Now set param_vector using SetParameters. - weibull_model = std::make_unique(4, 1.0); - EigenVectorXd param_vector(1); - param_vector << 0.1; - weibull_model->SetParameters(param_vector); - rates_r << 4.766392e-12, 1.391131e-06, 2.179165e-03, 3.997819e+00; - const EigenVectorXd rates2 = weibull_model->GetCategoryRates(); - CheckVectorXdEquality(rates2, rates_r, 0.0001); - - // Test 3: Check proportions. - const EigenVectorXd proportions = weibull_model->GetCategoryProportions(); - CheckVectorXdEquality(0.25, proportions, 0.0001); - - // Test 4: Check sum rates[i]*proportions[i]==1. - CHECK_LT(fabs(rates.dot(proportions) - 1.), 0.0001); - CHECK_LT(fabs(rates2.dot(proportions) - 1.), 0.0001); -} -#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/src/site_pattern.hpp b/src/site_pattern.hpp index 75e3b5e41..918a0e5ee 100644 --- a/src/site_pattern.hpp +++ b/src/site_pattern.hpp @@ -59,12 +59,3 @@ class SitePattern { void Compress(); static int SymbolTableAt(const CharIntMap& symbol_table, char c); }; - -#ifdef DOCTEST_LIBRARY_INCLUDED -TEST_CASE("SitePattern") { - CharIntMap symbol_table = SitePattern::GetSymbolTable(); - SymbolVector symbol_vector = SitePattern::SymbolVectorOf(symbol_table, "-tgcaTGCA?"); - SymbolVector correct_symbol_vector = {4, 3, 2, 1, 0, 3, 2, 1, 0, 4}; - CHECK_EQ(symbol_vector, correct_symbol_vector); -} -#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/src/stick_breaking_transform.hpp b/src/stick_breaking_transform.hpp index f2857323b..9a7e1fe7d 100644 --- a/src/stick_breaking_transform.hpp +++ b/src/stick_breaking_transform.hpp @@ -43,24 +43,3 @@ class StickBreakingTransform : public Transform { double log_abs_det_jacobian(const EigenVectorXd& x, const EigenVectorXd& y) const; }; - -#ifdef DOCTEST_LIBRARY_INCLUDED -TEST_CASE("BreakingStickTransform") { - StickBreakingTransform a; - EigenVectorXd y(3); - y << 1., 2., 3.; - EigenVectorXd x_expected(4); - // x_expected = - // torch.distributions.StickBreakingTransform()(torch.tensor([1., 2., 3.])) - x_expected << 0.475367, 0.412879, 0.106454, 0.00530004; - EigenVectorXd x = a(y); - CheckVectorXdEquality(x, x_expected, 1.e-5); - EigenVectorXd yy = a.inverse(x); - CheckVectorXdEquality(y, yy, 1e-5); - // log_abs_det_jacobian_expected = - // torch.distributions.StickBreakingTransform().log_abs_det_jacobian(y,x) - double log_abs_det_jacobian_expected = -9.108352; - CHECK(a.log_abs_det_jacobian(x, y) == - doctest::Approx(log_abs_det_jacobian_expected).epsilon(1.e-5)); -} -#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/src/stopwatch.hpp b/src/stopwatch.hpp index ef385904b..bfa6f6b4d 100644 --- a/src/stopwatch.hpp +++ b/src/stopwatch.hpp @@ -205,64 +205,3 @@ class Stopwatch { // Starting lap index for each time interval. std::vector interval_starts_; }; - -#ifdef DOCTEST_LIBRARY_INCLUDED - -TEST_CASE("Stopwatch") { - Stopwatch watch(false, Stopwatch::TimeScale::MillisecondScale); - // Functions not allowed while clock isn't running. - CHECK_THROWS(watch.GetElapsedOfCurrentInterval()); - CHECK_THROWS(watch.GetElapsedOfCurrentLap()); - CHECK_THROWS(watch.GetLatestInterval()); - CHECK_THROWS(watch.Lap()); - CHECK_THROWS(watch.Stop()); - - watch.Start(); - // Getting latest lap before first lap exists. - CHECK_THROWS(watch.GetLatestLap()); - // Getting latest interval before first interval exists. - CHECK_THROWS(watch.GetLatestInterval()); - Stopwatch::Sleep(3); - watch.Stop(); - - auto interval_1 = watch.GetLatestInterval(); - Stopwatch::Sleep(5); - - watch.Start(); - Stopwatch::Sleep(7); - auto interval_1_during_next_interval = watch.GetLatestInterval(); - Stopwatch::Sleep(11); - auto lap_1 = watch.Lap(); - Stopwatch::Sleep(13); - auto lap_1_during_next_lap = watch.GetLatestLap(); - auto interval_2_midinterval = watch.GetElapsedOfCurrentInterval(); - Stopwatch::Sleep(17); - watch.GetElapsedOfCurrentLap(); - Stopwatch::Sleep(19); - auto interval_2 = watch.Stop(); - - // Latest should fetch the last completed lap or interval, even if clock is running. - CHECK_EQ(interval_1, interval_1_during_next_interval); - CHECK_EQ(lap_1, lap_1_during_next_lap); - // The mid-interval time should be less than the total time. - CHECK_GT(interval_2, interval_2_midinterval); - - auto laps = watch.GetLaps(); - auto total = watch.GetTotal(); - std::vector intervals = {interval_1, interval_2}; - auto sum_laps = std::accumulate(laps.begin(), laps.end(), 0.0); - auto sum_intervals = std::accumulate(intervals.begin(), intervals.end(), 0.0); - - watch.Clear(); - CHECK_EQ(watch.GetTotal(), 0.0); - CHECK_EQ(watch.GetLaps().size(), 0); - - watch.Start(); - // Function not allowed while clock is running. - CHECK_THROWS(watch.Start()); - - CHECK_EQ(doctest::Approx(sum_laps), total); - CHECK_EQ(doctest::Approx(sum_intervals), total); -}; - -#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/src/subsplit_dag_node.hpp b/src/subsplit_dag_node.hpp index 2cb194323..2e6484587 100644 --- a/src/subsplit_dag_node.hpp +++ b/src/subsplit_dag_node.hpp @@ -253,94 +253,3 @@ template typename GenericVerticesView::view_type GenericVerticesView::at(size_t i) const { return storage_.vertices_.at(i); } - -#ifdef DOCTEST_LIBRARY_INCLUDED - -inline DAGVertex& GetStorage(const GenericSubsplitDAGNode& node) { - return node.node_; -} -inline const DAGVertex& GetStorage( - const GenericSubsplitDAGNode& node) { - return node.node_; -} - -/* Create the following topology: - [0] - / \ - 0 1 - / \ - [1] [2] - / \ - 2 3 - / \ - [3] [4] - */ - -static SubsplitDAGStorage MakeStorage() { - SubsplitDAGStorage storage; - storage.AddLine({EdgeId(0), NodeId(0), NodeId(1), SubsplitClade::Left}); - storage.AddLine({EdgeId(1), NodeId(0), NodeId(2), SubsplitClade::Right}); - storage.AddLine({EdgeId(2), NodeId(1), NodeId(3), SubsplitClade::Left}); - storage.AddLine({EdgeId(3), NodeId(1), NodeId(4), SubsplitClade::Right}); - - storage.AddVertex(DAGVertex{}.SetId(NodeId(0))) - .AddNeighbor(Direction::Leafward, SubsplitClade::Left, NodeId(1), EdgeId(0)) - .AddNeighbor(Direction::Leafward, SubsplitClade::Right, NodeId(2), EdgeId(1)); - storage.AddVertex(DAGVertex{}.SetId(NodeId(1))) - .AddNeighbor(Direction::Rootward, SubsplitClade::Left, NodeId(0), EdgeId(0)) - .AddNeighbor(Direction::Leafward, SubsplitClade::Left, NodeId(3), EdgeId(2)) - .AddNeighbor(Direction::Leafward, SubsplitClade::Right, NodeId(4), EdgeId(3)); - storage.AddVertex(DAGVertex{}.SetId(NodeId(2))) - .AddNeighbor(Direction::Rootward, SubsplitClade::Right, NodeId(0), EdgeId(1)); - storage.AddVertex(DAGVertex{}.SetId(NodeId(3))) - .AddNeighbor(Direction::Rootward, SubsplitClade::Left, NodeId(1), EdgeId(2)); - storage.AddVertex(DAGVertex{}.SetId(NodeId(4))) - .AddNeighbor(Direction::Rootward, SubsplitClade::Right, NodeId(1), EdgeId(3)); - return storage; -} - -TEST_CASE("SubsplitDAGStorage: LinesView structured binding") { - auto storage = MakeStorage(); - - size_t i = 0; - for (auto [node_ids, line_id] : storage.GetLines()) { - std::ignore = line_id; - auto [parent_id, child_id] = node_ids; - switch (i++) { - case 0: - CHECK_EQ(parent_id, 0); - CHECK_EQ(child_id, 1); - break; - case 1: - CHECK_EQ(parent_id, 0); - CHECK_EQ(child_id, 2); - break; - case 2: - CHECK_EQ(parent_id, 1); - CHECK_EQ(child_id, 3); - break; - case 3: - CHECK_EQ(parent_id, 1); - CHECK_EQ(child_id, 4); - break; - default: - Failwith("More lines than expected"); - } - } -} - -TEST_CASE("SubsplitDAGStorage: Neighbors iterator") { - auto storage = MakeStorage(); - - CHECK_EQ(*GetStorage(storage.GetVertices()[1]) - .GetNeighbors(Direction::Leafward, SubsplitClade::Left) - .begin(), - 3); - CHECK_EQ(GetStorage(storage.GetVertices()[1]) - .GetNeighbors(Direction::Leafward, SubsplitClade::Left) - .begin() - .GetEdge(), - 2); -} - -#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/src/substitution_model.hpp b/src/substitution_model.hpp index 139647c67..1cb724dcd 100644 --- a/src/substitution_model.hpp +++ b/src/substitution_model.hpp @@ -111,59 +111,3 @@ class HKYModel : public DNAModel { virtual void UpdateEigendecomposition() override; void UpdateQMatrix() override; }; - -#ifdef DOCTEST_LIBRARY_INCLUDED -#include -TEST_CASE("SubstitutionModel") { - auto CheckEigenvalueEquality = [](EigenVectorXd eval1, EigenVectorXd eval2) { - std::sort(eval1.begin(), eval1.end()); - std::sort(eval2.begin(), eval2.end()); - CheckVectorXdEquality(eval1, eval2, 0.0001); - }; - auto gtr_model = std::make_unique(); - auto hky_model = std::make_unique(); - auto jc_model = std::make_unique(); - // Test 1: First we test using the "built in" default values. - CheckEigenvalueEquality(jc_model->GetEigenvalues(), gtr_model->GetEigenvalues()); - CheckEigenvalueEquality(jc_model->GetEigenvalues(), hky_model->GetEigenvalues()); - EigenVectorXd param_vector(10); - // Test 2: Now try out ParameterSegmentMapOf. - gtr_model = std::make_unique(); - // First zero out our param_vector. - param_vector.setZero(); - // We can use ParameterSegmentMapOf to get two "views" into our parameter - // vector. - auto parameter_map = - gtr_model->GetBlockSpecification().ParameterSegmentMapOf(param_vector); - auto frequencies = parameter_map.at(SubstitutionModel::frequencies_key_); - auto rates = parameter_map.at(SubstitutionModel::rates_key_); - // When we modify the contents of these views, that changes param_vector. - frequencies.setConstant(0.25); - rates.setConstant(1.0 / 6.0); - // We can then set param_vector and go forward as before. - gtr_model->SetParameters(param_vector); - CheckEigenvalueEquality(jc_model->GetEigenvalues(), gtr_model->GetEigenvalues()); - // Test 3: Compare to eigenvalues from R. - frequencies << 0.479367, 0.172572, 0.140933, 0.207128; - rates << 0.060602, 0.402732, 0.028230, 0.047910, 0.407249, 0.053277; - gtr_model->SetParameters(param_vector); - EigenVectorXd eigen_values_r(4); - eigen_values_r << -2.567992e+00, -1.760838e+00, -4.214918e-01, 1.665335e-16; - CheckEigenvalueEquality(eigen_values_r, gtr_model->GetEigenvalues()); - // Test HKY against GTR - EigenVectorXd hky_param_vector(5); - hky_param_vector.setZero(); - auto hky_parameter_map = - hky_model->GetBlockSpecification().ParameterSegmentMapOf(hky_param_vector); - auto hky_frequencies = hky_parameter_map.at(SubstitutionModel::frequencies_key_); - auto hky_kappa = hky_parameter_map.at(SubstitutionModel::rates_key_); - hky_frequencies << 0.1, 0.2, 0.3, 0.4; - hky_kappa.setConstant(3.0); - hky_model->SetParameters(hky_param_vector); - frequencies << 0.1, 0.2, 0.3, 0.4; - rates << 0.1, 0.3, 0.1, 0.1, 0.3, 0.1; - gtr_model->SetParameters(param_vector); - CheckEigenvalueEquality(gtr_model->GetEigenvalues(), hky_model->GetEigenvalues()); - CHECK(gtr_model->GetQMatrix().isApprox(hky_model->GetQMatrix())); -} -#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/src/task_processor.hpp b/src/task_processor.hpp index 61ed3ab52..f15266e0f 100644 --- a/src/task_processor.hpp +++ b/src/task_processor.hpp @@ -137,29 +137,3 @@ class TaskProcessor { } } }; - -#ifdef DOCTEST_LIBRARY_INCLUDED -TEST_CASE("TaskProcessor") { - std::queue executor_queue; - std::queue work_queue; - std::vector results(8); - // Say we have 4 executors. - for (auto i = 0; i < 4; i++) { - executor_queue.push(i); - } - // Our Work in this example is just size_t's. - for (size_t i = 0; i < results.size(); i++) { - work_queue.push(i); - } - // And our task is just to cast this size_t to a float and store it in the - // corresponding location of the results array. - auto task = [&results](int /*executor*/, size_t work) { - // std::cout << "work " << work << " on " << executor << std::endl; - results[work] = static_cast(work); - }; - TaskProcessor processor(executor_queue, work_queue, task); - processor.Wait(); - std::vector correct_results({0, 1, 2, 3, 4, 5, 6, 7}); - CHECK_EQ(results, correct_results); -} -#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/src/taxon_name_munging.hpp b/src/taxon_name_munging.hpp index 17b046f94..ac85c42f1 100644 --- a/src/taxon_name_munging.hpp +++ b/src/taxon_name_munging.hpp @@ -22,28 +22,3 @@ TagDoubleMap ConstantDatesForTagTaxonMap(TagStringMap tag_taxon_map); // height of each taxon compared to the maximum date. TagDoubleMap ParseDatesFromTagTaxonMap(TagStringMap tag_taxon_map); } // namespace TaxonNameMunging - -#ifdef DOCTEST_LIBRARY_INCLUDED -TEST_CASE("TaxonNameMunging") { - using namespace TaxonNameMunging; - - std::string unquoted_test(R"raw(hello 'there" friend)raw"); - std::string double_quoted_test(R"raw("this is a \" test")raw"); - std::string double_quoted_dequoted(R"raw(this is a " test)raw"); - std::string single_quoted_test(R"raw('this is a \' test')raw"); - std::string single_quoted_dequoted(R"raw(this is a ' test)raw"); - - CHECK_EQ(QuoteString(unquoted_test), R"raw("hello 'there\" friend")raw"); - CHECK_EQ(DequoteString(double_quoted_test), double_quoted_dequoted); - CHECK_EQ(DequoteString(single_quoted_test), single_quoted_dequoted); - CHECK_EQ(DequoteString(QuoteString(unquoted_test)), unquoted_test); - - TagStringMap test_map( - {{2, unquoted_test}, {3, double_quoted_test}, {5, single_quoted_test}}); - TagStringMap expected_test_map( - {{2, unquoted_test}, {3, double_quoted_dequoted}, {5, single_quoted_dequoted}}); - CHECK_EQ(expected_test_map, DequoteTagStringMap(test_map)); - - // Test of TagDateMapOfTagTaxonMap appears in rooted_sbn_instance.hpp. -} -#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/src/tidy_subsplit_dag.hpp b/src/tidy_subsplit_dag.hpp index c6e21296a..4366be3f0 100644 --- a/src/tidy_subsplit_dag.hpp +++ b/src/tidy_subsplit_dag.hpp @@ -199,43 +199,3 @@ class TidySubsplitDAG : public SubsplitDAG { // invalidates the p-hat PLV coming up into it. EigenArrayXb dirty_sorted_; }; - -#ifdef DOCTEST_LIBRARY_INCLUDED -TEST_CASE("TidySubsplitDAG: slicing") { - auto manual_dag = TidySubsplitDAG::ManualTrivialExample(); - - // std::cout << manual_dag.AboveMatricesAsString() << std::endl; - CHECK_EQ(GenericToString(manual_dag.AboveNode(NodeId(0))), "[1, 0, 0, 1, 1, 1]\n"); - CHECK_EQ(GenericToString(manual_dag.AboveNode(NodeId(1))), "[0, 1, 0, 1, 1, 1]\n"); - CHECK_EQ(GenericToString(manual_dag.AboveNode(NodeId(2))), "[0, 0, 1, 0, 1, 1]\n"); - CHECK_EQ(GenericToString(manual_dag.AboveNode(NodeId(3))), "[0, 0, 0, 1, 1, 1]\n"); - CHECK_EQ(GenericToString(manual_dag.AboveNode(NodeId(4))), "[0, 0, 0, 0, 1, 1]\n"); - CHECK_EQ(GenericToString(manual_dag.AboveNode(NodeId(5))), "[0, 0, 0, 0, 0, 1]\n"); - - auto trivial_dag = TidySubsplitDAG::TrivialExample(); - CHECK_EQ(trivial_dag.AboveMatricesAsString(), manual_dag.AboveMatricesAsString()); - - auto motivating_dag = TidySubsplitDAG::MotivatingExample(); - CHECK_EQ(GenericToString(motivating_dag.AboveNode(false, NodeId(4))), - "[0, 0, 0, 0, 1, 1, 1, 1, 0, 0]\n"); - CHECK_EQ(GenericToString(motivating_dag.AboveNode(true, NodeId(4))), - "[0, 0, 0, 0, 1, 0, 0, 0, 1, 1]\n"); - CHECK_EQ(GenericToString(motivating_dag.AboveNode(false, NodeId(7))), - "[0, 0, 0, 0, 0, 0, 0, 1, 0, 0]\n"); - CHECK_EQ(GenericToString(motivating_dag.AboveNode(true, NodeId(7))), - "[0, 0, 0, 0, 0, 0, 0, 1, 1, 1]\n"); - CHECK_EQ(GenericToString(motivating_dag.BelowNode(false, NodeId(7))), - "[0, 0, 1, 1, 1, 0, 0, 1, 0, 0]\n"); - CHECK_EQ(GenericToString(motivating_dag.BelowNode(true, NodeId(7))), - "[1, 0, 0, 0, 0, 0, 0, 1, 0, 0]\n"); - - motivating_dag.SetDirtyStrictlyAbove(NodeId(4)); - CHECK_EQ(GenericToString(motivating_dag.DirtyVector(true)), - "[0, 0, 0, 0, 0, 0, 0, 0, 1, 1]\n"); - CHECK_EQ(GenericToString(motivating_dag.DirtyVector(false)), - "[0, 0, 0, 0, 0, 1, 1, 1, 0, 0]\n"); - - motivating_dag.SetClean(); - // #321 Add test for Tidy traversal. -} -#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/src/topology_sampler.hpp b/src/topology_sampler.hpp index bb9dd75a5..72a215a66 100644 --- a/src/topology_sampler.hpp +++ b/src/topology_sampler.hpp @@ -56,83 +56,3 @@ class TopologySampler { MersenneTwister mersenne_twister_; }; - -#ifdef DOCTEST_LIBRARY_INCLUDED - -TEST_CASE("TopologySampler") { - Driver driver; - auto tree_collection = RootedTreeCollection::OfTreeCollection( - driver.ParseNewickFile("data/five_taxon_rooted_more_2.nwk")); - SubsplitDAG dag(tree_collection); - - EigenVectorXd normalized_sbn_parameters = dag.BuildUniformOnTopologicalSupportPrior(); - EigenVectorXd node_probabilities = - dag.UnconditionalNodeProbabilities(normalized_sbn_parameters); - EigenVectorXd inverted_probabilities = - dag.InvertedGPCSPProbabilities(normalized_sbn_parameters, node_probabilities); - - SubsplitDAGNode origin = dag.GetDAGNode(NodeId(5)); - - TopologySampler sampler; - std::map counts; - const size_t iterations = 10000; - - for (size_t i = 0; i < iterations; ++i) { - auto tree = - sampler.Sample(origin, dag, normalized_sbn_parameters, inverted_probabilities); - ++counts[tree->Newick([](const Node* node) { - if (!node->IsLeaf()) return std::string(); - return std::string("x") + std::to_string(node->Id()); - })]; - } - - for (auto& i : counts) { - const double observed = static_cast(i.second) / iterations; - const double expected = 1.0 / 3.0; - CHECK_LT(fabs(observed - expected), 5e-2); - } -} - -TEST_CASE("TopologySampler: Non-uniform prior") { - Driver driver; - auto tree_collection = RootedTreeCollection::OfTreeCollection( - driver.ParseNewickFile("data/five_taxon_rooted_more_2.nwk")); - SubsplitDAG dag(tree_collection); - - std::vector params{0.5, 0.3, 0.2, 1.0, 1.0, 1.0, 1.0, 1.0, - 0.8, 0.2, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, - 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0}; - EigenVectorXd normalized_sbn_parameters = - EigenVectorXd::Map(params.data(), params.size()); - EigenVectorXd node_probabilities = - dag.UnconditionalNodeProbabilities(normalized_sbn_parameters); - EigenVectorXd inverted_probabilities = - dag.InvertedGPCSPProbabilities(normalized_sbn_parameters, node_probabilities); - - SubsplitDAGNode origin = dag.GetDAGNode(NodeId(5)); - - TopologySampler sampler; - std::map counts; - std::map expected = { - {"((((x0,x1),x2),(x3,x4)));", 0.312}, - {"(((x0,x1),(x2,(x3,x4))));", 0.52}, - {"((x0,(x1,(x2,(x3,x4)))));", 0.1666}, - }; - const size_t iterations = 10000; - - for (size_t i = 0; i < iterations; ++i) { - auto tree = - sampler.Sample(origin, dag, normalized_sbn_parameters, inverted_probabilities); - ++counts[tree->Newick([](const Node* node) { - if (!node->IsLeaf()) return std::string(); - return std::string("x") + std::to_string(node->Id()); - })]; - } - - for (auto& [tree, count] : counts) { - const double observed = static_cast(count) / iterations; - CHECK_LT(fabs(observed - expected[tree]), 5e-2); - } -} - -#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/src/tree.hpp b/src/tree.hpp index 9bd8f6d54..97a26de17 100644 --- a/src/tree.hpp +++ b/src/tree.hpp @@ -61,8 +61,3 @@ class Tree { }; inline bool operator!=(const Tree& lhs, const Tree& rhs) { return !(lhs == rhs); } - -#ifdef DOCTEST_LIBRARY_INCLUDED -// Lots of tests in UnrootedTree and RootedTree. -TEST_CASE("Tree") {} -#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/src/tree_collection.hpp b/src/tree_collection.hpp index f252c196d..70117016e 100644 --- a/src/tree_collection.hpp +++ b/src/tree_collection.hpp @@ -7,26 +7,3 @@ #include "tree.hpp" using TreeCollection = GenericTreeCollection; - -#ifdef DOCTEST_LIBRARY_INCLUDED -TEST_CASE("TreeCollection") { - auto first_four_example_trees = Tree::ExampleTrees(); - first_four_example_trees.resize(4); - TreeCollection collection(first_four_example_trees); - auto counter = collection.TopologyCounter(); - std::unordered_map counted; - for (const auto &iter : counter) { - SafeInsert(counted, iter.first->Newick(std::nullopt, std::nullopt, true), - iter.second); - } - std::unordered_map counted_correct( - {{"(0_1,1_1,(2_1,3_1)3_2)3_4;", 2}, - {"(0_1,2_1,(1_1,3_1)3_2)3_4;", 1}, - {"(0_1,(1_1,(2_1,3_1)3_2)3_3)3_4;", 1}}); - CHECK_EQ(counted, counted_correct); - collection.DropFirst(0.25); - CHECK_EQ(collection.TreeCount(), 3); - collection.DropFirst(1.); - CHECK_EQ(collection.TreeCount(), 0); -} -#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/src/unrooted_sbn_instance.hpp b/src/unrooted_sbn_instance.hpp index 4d60d84eb..969ad1877 100644 --- a/src/unrooted_sbn_instance.hpp +++ b/src/unrooted_sbn_instance.hpp @@ -91,523 +91,3 @@ class UnrootedSBNInstance : public PreUnrootedSBNInstance { RangeVector GetSubsplitRanges( const RootedIndexerRepresentation &rooted_representation); }; - -#ifdef DOCTEST_LIBRARY_INCLUDED - -#include "doctest_constants.hpp" - -TEST_CASE("UnrootedSBNInstance: indexer and PSP representations") { - UnrootedSBNInstance inst("charlie"); - inst.ReadNewickFile("data/five_taxon_unrooted.nwk"); - inst.ProcessLoadedTrees(); - auto pretty_indexer = inst.PrettyIndexer(); - // The indexer_ is to index the sbn_parameters_. Note that neither of these - // data structures attempt to catalog the complete collection of rootsplits or - // PCSPs, but just those that are present for some rooting of the input trees. - // - // The indexer_ and sbn_parameters_ are laid out as follows (I'll just call it - // the "index" in what follows). Say there are rootsplit_count rootsplits in - // the support. - // The first rootsplit_count entries of the index are assigned to the - // rootsplits (again, those rootsplits that are present for some rooting of - // the unrooted input trees). For the five_taxon example, this goes as follows: - StringSet correct_pretty_rootsplits( - {"00000|11111|01110", "00000|11111|01010", "00000|11111|00101", - "00000|11111|00111", "00000|11111|00001", "00000|11111|00011", - "00000|11111|00010", "00000|11111|00100", "00000|11111|00110", - "00000|11111|01000", "00000|11111|01111", "00000|11111|01001"}); - StringSet pretty_rootsplits( - pretty_indexer.begin(), - pretty_indexer.begin() + correct_pretty_rootsplits.size()); - CHECK(correct_pretty_rootsplits == pretty_rootsplits); - // The rest of the entries of the index are laid out as blocks of parameters - // for PCSPs that share the same parent. Take a look at the description of - // PCSP bitsets (and the unit tests) in bitset.hpp to understand the notation - // used here. - // - // For example, here are four PCSPs that all share the parent 00001|11110: - StringSet correct_pretty_pcsp_block({"00001|11110|01110", "00001|11110|00010", - "00001|11110|01000", "00001|11110|00100"}); - StringSet pretty_indexer_set(pretty_indexer.begin(), pretty_indexer.end()); - // It's true that this test doesn't show the block-ness, but it wasn't easy to - // show off this feature in a way that wasn't compiler dependent. - // You can see it by printing out a pretty_indexer if you wish. A test exhibiting - // block structure appeas in rooted_sbn_instance.hpp. - for (auto pretty_pcsp : correct_pretty_pcsp_block) { - CHECK(pretty_indexer_set.find(pretty_pcsp) != pretty_indexer_set.end()); - } - // Now we can look at some tree representations. We get these by calling - // IndexerRepresentationOf on a tree topology. This function "digests" the - // tree by representing all of the PCSPs as bitsets which it can then look up - // in the indexer_. - // It then spits them out as the rootsplit and PCSP indices. - // The following tree is (2,(1,3),(0,4));, or with internal nodes (2,(1,3)5,(0,4)6)7 - auto indexer_test_topology_1 = Node::OfParentIdVector({6, 5, 7, 5, 6, 7, 7}); - // Here we look at the indexer representation of this tree. Rather than having - // the indices themselves, which is what IndexerRepresentationOf actually - // outputs, we have string representations of the features corresponding to - // those indices. - // See sbn_maps.hpp for more description of these indexer representations. - StringSetVector correct_representation_1( - // The indexer representations for each of the possible virtual rootings. - // For example, this first one is for rooting at the edge leading to leaf - // 0, the second for rooting at leaf 1, etc. - {{"00000|11111|01111", "10000|01111|00001", "00001|01110|00100", - "00100|01010|00010"}, - {"00000|11111|01000", "01000|10111|00010", "00100|10001|00001", - "00010|10101|00100"}, - {"00000|11111|00100", "10001|01010|00010", "01010|10001|00001", - "00100|11011|01010"}, - {"00000|11111|00010", "00010|11101|01000", "00100|10001|00001", - "01000|10101|00100"}, - {"00000|11111|00001", "00001|11110|01110", "10000|01110|00100", - "00100|01010|00010"}, - {"00000|11111|01010", "10101|01010|00010", "00100|10001|00001", - "01010|10101|00100"}, - {"00000|11111|01110", "00100|01010|00010", "10001|01110|00100", - "01110|10001|00001"}}); - CHECK_EQ( - inst.StringIndexerRepresentationOf(indexer_test_topology_1, out_of_sample_index), - correct_representation_1); - // See the "concepts" part of the online documentation to learn about PSP indexing. - auto correct_psp_representation_1 = - StringVectorVector({{"10000|01111", "10111|01000", "11011|00100", "11101|00010", - "11110|00001", "10101|01010", "10001|01110"}, - {"", "", "", "", "", "01000|00010", "10000|00001"}, - {"01110|00001", "10101|00010", "10001|01010", "10101|01000", - "10000|01110", "10001|00100", "01010|00100"}}); - CHECK_EQ(inst.psp_indexer_.StringRepresentationOf(indexer_test_topology_1), - correct_psp_representation_1); - // Same as above but for (((0,1),2),3,4);, or with internal nodes (((0,1)5,2)6,3,4)7; - auto indexer_test_topology_2 = Node::OfParentIdVector({5, 5, 6, 7, 7, 6, 7}); - StringSetVector correct_representation_2( - {{"00000|11111|01111", "10000|01111|00111", "00100|00011|00001", - "01000|00111|00011"}, - {"00000|11111|01000", "01000|10111|00111", "00100|00011|00001", - "10000|00111|00011"}, - {"00000|11111|00100", "00100|11011|00011", "11000|00011|00001", - "00011|11000|01000"}, - {"00000|11111|00010", "00100|11000|01000", "00001|11100|00100", - "00010|11101|00001"}, - {"00000|11111|00001", "00100|11000|01000", "00001|11110|00010", - "00010|11100|00100"}, - {"00000|11111|00111", "00111|11000|01000", "00100|00011|00001", - "11000|00111|00011"}, - {"00000|11111|00011", "00100|11000|01000", "11100|00011|00001", - "00011|11100|00100"}}); - CHECK_EQ( - inst.StringIndexerRepresentationOf(indexer_test_topology_2, out_of_sample_index), - correct_representation_2); - auto correct_psp_representation_2 = - StringVectorVector({{"10000|01111", "10111|01000", "11011|00100", "11101|00010", - "11110|00001", "11000|00111", "11100|00011"}, - {"", "", "", "", "", "10000|01000", "11000|00100"}, - {"01000|00111", "10000|00111", "11000|00011", "11100|00001", - "11100|00010", "00100|00011", "00010|00001"}}); - CHECK_EQ(inst.psp_indexer_.StringRepresentationOf(indexer_test_topology_2), - correct_psp_representation_2); - - // Test of RootedSBNMaps::IndexerRepresentationOf. - // It's a little surprising to see this here in unrooted land, but these are actually - // complementary tests to those found in rooted_sbn_instance.hpp, with a larger - // subsplit support because we deroot the trees. - // Topology is ((((0,1),2),3),4);, or with internal nodes ((((0,1)5,2)6,3)7,4)8; - auto indexer_test_rooted_topology_1 = - Node::OfParentIdVector({5, 5, 6, 7, 8, 6, 7, 8}); - auto correct_rooted_indexer_representation_1 = - StringSet({"00000|11111|00001", "00001|11110|00010", "00010|11100|00100", - "00100|11000|01000"}); - CHECK_EQ(inst.StringIndexerRepresentationOf({RootedSBNMaps::IndexerRepresentationOf( - inst.SBNSupport().Indexer(), indexer_test_rooted_topology_1, - out_of_sample_index)})[0], - correct_rooted_indexer_representation_1); - // Topology is (((0,1),2),(3,4));, or with internal nodes (((0,1)5,2)6,(3,4)7)8; - auto indexer_test_rooted_topology_2 = - Node::OfParentIdVector({5, 5, 6, 7, 7, 6, 8, 8}); - auto correct_rooted_indexer_representation_2 = - StringSet({"00000|11111|00011", "11100|00011|00001", "00011|11100|00100", - "00100|11000|01000"}); - CHECK_EQ(inst.StringIndexerRepresentationOf({RootedSBNMaps::IndexerRepresentationOf( - inst.SBNSupport().Indexer(), indexer_test_rooted_topology_2, - out_of_sample_index)})[0], - correct_rooted_indexer_representation_2); -} - -TEST_CASE("UnrootedSBNInstance: likelihood and gradient") { - UnrootedSBNInstance inst("charlie"); - inst.ReadNewickFile("data/hello.nwk"); - inst.ReadFastaFile("data/hello.fasta"); - PhyloModelSpecification simple_specification{"JC69", "constant", "strict"}; - inst.PrepareForPhyloLikelihood(simple_specification, 2); - for (auto ll : inst.LogLikelihoods()) { - CHECK_LT(fabs(ll - -84.852358), 0.000001); - } - inst.ReadNexusFile("data/DS1.subsampled_10.t"); - inst.ReadFastaFile("data/DS1.fasta"); - std::vector vector_flag_options{BEAGLE_FLAG_VECTOR_NONE, - BEAGLE_FLAG_VECTOR_SSE}; - std::vector tip_state_options{false, true}; - for (const auto vector_flag : vector_flag_options) { - for (const auto tip_state_option : tip_state_options) { - inst.PrepareForPhyloLikelihood(simple_specification, 2, {vector_flag}, - tip_state_option); - auto likelihoods = inst.LogLikelihoods(); - std::vector pybeagle_likelihoods( - {-14582.995273982739, -6911.294207416366, -6916.880235529542, - -6904.016888831189, -6915.055570693576, -6915.50496696512, - -6910.958836661867, -6909.02639968063, -6912.967861935749, - -6910.7871105783515}); - for (size_t i = 0; i < likelihoods.size(); i++) { - CHECK_LT(fabs(likelihoods[i] - pybeagle_likelihoods[i]), 0.00011); - } - - auto gradients = inst.PhyloGradients(); - // Test the log likelihoods. - for (size_t i = 0; i < likelihoods.size(); i++) { - CHECK_LT(fabs(gradients[i].log_likelihood_ - pybeagle_likelihoods[i]), 0.00011); - } - // Test the gradients for the last tree. - auto last = gradients.back(); - std::sort(last.gradient_["branch_lengths"].begin(), - last.gradient_["branch_lengths"].end()); - // Zeros are for the root and one of the descendants of the root. - std::vector physher_gradients = { - -904.18956, -607.70500, -562.36274, -553.63315, -542.26058, -539.64210, - -463.36511, -445.32555, -414.27197, -412.84218, -399.15359, -342.68038, - -306.23644, -277.05392, -258.73681, -175.07391, -171.59627, -168.57646, - -150.57623, -145.38176, -115.15798, -94.86412, -83.02880, -80.09165, - -69.00574, -51.93337, 0.00000, 0.00000, 16.17497, 20.47784, - 58.06984, 131.18998, 137.10799, 225.73617, 233.92172, 253.49785, - 255.52967, 259.90378, 394.00504, 394.96619, 396.98933, 429.83873, - 450.71566, 462.75827, 471.57364, 472.83161, 514.59289, 650.72575, - 888.87834, 913.96566, 927.14730, 959.10746, 2296.55028}; - for (size_t i = 0; i < last.gradient_["branch_lengths"].size(); i++) { - CHECK_LT(fabs(last.gradient_["branch_lengths"][i] - physher_gradients[i]), - 0.0001); - } - - // Test rescaling - inst.SetRescaling(true); - auto likelihoods_rescaling = inst.LogLikelihoods(); - // Likelihoods from LogLikelihoods() - for (size_t i = 0; i < likelihoods_rescaling.size(); i++) { - CHECK_LT(fabs(likelihoods_rescaling[i] - pybeagle_likelihoods[i]), 0.00011); - } - // Likelihoods from BranchGradients() - inst.PrepareForPhyloLikelihood(simple_specification, 1, {}, tip_state_option); - auto gradients_rescaling = inst.PhyloGradients(); - for (size_t i = 0; i < gradients_rescaling.size(); i++) { - CHECK_LT(fabs(gradients_rescaling[i].log_likelihood_ - pybeagle_likelihoods[i]), - 0.00011); - } - // Gradients - auto last_rescaling = gradients_rescaling.back(); - auto branch_lengths_gradient = last_rescaling.gradient_["branch_lengths"]; - std::sort(branch_lengths_gradient.begin(), branch_lengths_gradient.end()); - for (size_t i = 0; i < branch_lengths_gradient.size(); i++) { - CHECK_LT(fabs(branch_lengths_gradient[i] - physher_gradients[i]), 0.0001); - } - } - } -} - -TEST_CASE("UnrootedSBNInstance: likelihood and gradient with Weibull") { - UnrootedSBNInstance inst("charlie"); - PhyloModelSpecification simple_specification{"JC69", "weibull+4", "strict"}; - inst.ReadNexusFile("data/DS1.subsampled_10.t"); - inst.ReadFastaFile("data/DS1.fasta"); - - std::vector physher_likelihoods( - {-9456.1201098061, -6624.4110704332, -6623.4474776131, -6617.25658038029, - -6627.5385571548, -6621.6155048722, -6622.3314942713, -6618.7695717585, - -6616.3837517370, -6623.8295828648}); - // First element of each gradient - std::vector physher_gradients_bl0( - {-126.890527, 157.251275, 138.202510, -180.311856, 417.562897, -796.450894, - -173.744375, -70.693513, 699.190754, -723.034349}); - std::vector vector_flag_options{BEAGLE_FLAG_VECTOR_NONE, - BEAGLE_FLAG_VECTOR_SSE}; - std::vector tip_state_options{false, true}; - for (const auto vector_flag : vector_flag_options) { - for (const auto tip_state_option : tip_state_options) { - inst.PrepareForPhyloLikelihood(simple_specification, 2, {vector_flag}, - tip_state_option); - auto param_block_map = inst.GetPhyloModelParamBlockMap(); - param_block_map.at(WeibullSiteModel::shape_key_).setConstant(0.1); - auto likelihoods = inst.LogLikelihoods(); - for (size_t i = 0; i < likelihoods.size(); i++) { - CHECK_LT(fabs(likelihoods[i] - physher_likelihoods[i]), 0.00011); - } - - auto gradients = inst.PhyloGradients(); - for (size_t i = 0; i < gradients.size(); i++) { - CHECK_LT(fabs(gradients[i].gradient_["branch_lengths"][0] - - physher_gradients_bl0[i]), - 0.00011); - } - - // Test rescaling - inst.SetRescaling(true); - auto likelihoods_rescaling = inst.LogLikelihoods(); - // Likelihoods from LogLikelihoods() - for (size_t i = 0; i < likelihoods_rescaling.size(); i++) { - CHECK_LT(fabs(likelihoods_rescaling[i] - physher_likelihoods[i]), 0.00011); - } - - auto gradients_rescaling = inst.PhyloGradients(); - for (size_t i = 0; i < gradients.size(); i++) { - CHECK_LT(fabs(gradients_rescaling[i].gradient_["branch_lengths"][0] - - physher_gradients_bl0[i]), - 0.00011); - } - } - } -} - -TEST_CASE("UnrootedSBNInstance: SBN training") { - UnrootedSBNInstance inst("charlie"); - inst.ReadNewickFile("data/DS1.100_topologies.nwk"); - inst.ProcessLoadedTrees(); - // These "Expected" functions are defined in sbn_probability.hpp. - const auto expected_SA = ExpectedSAVector(); - inst.TrainSimpleAverage(); - CheckVectorXdEquality(inst.CalculateSBNProbabilities(), expected_SA, 1e-12); - // Expected EM vectors with alpha = 0. - const auto [expected_EM_0_1, expected_EM_0_23] = ExpectedEMVectorsAlpha0(); - // 1 iteration of EM with alpha = 0. - inst.TrainExpectationMaximization(0., 1); - CheckVectorXdEquality(inst.CalculateSBNProbabilities(), expected_EM_0_1, 1e-12); - // 23 iterations of EM with alpha = 0. - inst.TrainExpectationMaximization(0., 23); - CheckVectorXdEquality(inst.CalculateSBNProbabilities(), expected_EM_0_23, 1e-12); - // 100 iteration of EM with alpha = 0.5. - const auto expected_EM_05_100 = ExpectedEMVectorAlpha05(); - inst.TrainExpectationMaximization(0.5, 100); - CheckVectorXdEquality(inst.CalculateSBNProbabilities(), expected_EM_05_100, 1e-5); -} - -TEST_CASE("UnrootedSBNInstance: tree sampling") { - UnrootedSBNInstance inst("charlie"); - inst.ReadNewickFile("data/five_taxon_unrooted.nwk"); - inst.ProcessLoadedTrees(); - inst.TrainSimpleAverage(); - // Count the frequencies of rooted trees in a file. - size_t rooted_tree_count_from_file = 0; - RootedIndexerRepresentationSizeDict counter_from_file(0); - for (const auto &indexer_representation : inst.MakeIndexerRepresentations()) { - RootedSBNMaps::IncrementRootedIndexerRepresentationSizeDict(counter_from_file, - indexer_representation); - rooted_tree_count_from_file += indexer_representation.size(); - } - // Count the frequencies of trees when we sample after training with - // SimpleAverage. - size_t sampled_tree_count = 1'000'000; - RootedIndexerRepresentationSizeDict counter_from_sampling(0); - ProgressBar progress_bar(sampled_tree_count / 1000); - for (size_t sample_idx = 0; sample_idx < sampled_tree_count; ++sample_idx) { - const auto rooted_topology = inst.SampleTopology(true); - RootedSBNMaps::IncrementRootedIndexerRepresentationSizeDict( - counter_from_sampling, - RootedSBNMaps::IndexerRepresentationOf(inst.SBNSupport().Indexer(), - rooted_topology, out_of_sample_index)); - if (sample_idx % 1000 == 0) { - ++progress_bar; - progress_bar.display(); - } - } - // These should be equal in the limit when we're training with SA. - for (const auto &[key, _] : counter_from_file) { - std::ignore = _; - double observed = - static_cast(counter_from_sampling.at(key)) / sampled_tree_count; - double expected = - static_cast(counter_from_file.at(key)) / rooted_tree_count_from_file; - CHECK_LT(fabs(observed - expected), 5e-3); - } - progress_bar.done(); -} - -TEST_CASE("UnrootedSBNInstance: gradient of log q_{phi}(tau) WRT phi") { - UnrootedSBNInstance inst("charlie"); - // File gradient_test.t contains two trees: - // ((0,1), 2, (3,4)) and - // ((0,1), (2,3), 4). - inst.ReadNexusFile("data/gradient_test.t"); - inst.ProcessLoadedTrees(); - - // The number of rootsplits across all of the input trees. - size_t num_rootsplits = 8; - // Manual enumeration shows that there are 31 PCSP's. - size_t num_pcsp = inst.sbn_parameters_.size() - num_rootsplits; - - // Test for K = 1 tree. - size_t K = 1; - inst.tree_collection_.trees_.clear(); - // Generate a tree, - // \tau = ((0,1),(2,3),4) with internal node labels ((0,1)5,(2,3)6,4)7. - std::vector tau_indices = {5, 5, 6, 6, 7, 7, 7}; - auto tau = UnrootedTree::OfParentIdVector(tau_indices); - inst.tree_collection_.trees_.push_back(tau); - - // Initialize sbn_parameters_ to 0's and normalize, which is going to give a uniform - // distribution for rootsplits and PCSP distributions. - inst.sbn_parameters_.setZero(); - EigenVectorXd normalized_sbn_parameters_in_log = inst.sbn_parameters_; - inst.ProbabilityNormalizeSBNParametersInLog(normalized_sbn_parameters_in_log); - // Because this is a uniform distribution, each rootsplit \rho has P(\rho) = 1/8. - // - // We're going to start by computing the rootsplit gradient. - // There are 7 possible rootings of \tau. - // For example consider rooting on the 014|23 split, yielding the following subsplits: - // 014|23, 2|3, 01|4, 0|1. - // Each of the child subsplits are the only possible subsplit, - // except for the root where it has probability 1/8. Hence, the probability - // for this tree is 1/8 x 1 x 1 x 1 = 1/8. - // Now, consider rooting on the 0|1234 split, yielding the following subsplits: - // 0|1234, 1|234, 23|4, 2|3. - // The probability for this tree is 1/8 x 1 x 1/2 x 1 = 1/16, where the 1/2 comes from - // the fact that we can have 23|4 or 2|34. - // - // Each of the remaining 5 trees has the same probability: the product of - // 1/8 for the rootsplit and 1/2 for one of the subsplit resolutions of 234. - // One can see this because the only way for there not to be ambiguity in the - // resolution of the splitting of 234 is for one to take 014|23 as the rootsplit. - // - // Hence, q(\tau) = 6 x 1/16 + 1 x 1/8 = 8/16 = 0.5. - // Note that there are a total of 8 rootsplits; 7 are possible rootsplits of - // the sampled tree \tau but one rootsplit, - // 014|23 is not observed rooting of \tau and hence, - // the gradient for 014|23 is simply -P(014|23) = -1/8. - // - // The gradient with respect to each of the 7 rootsplits is given by - // P(\tau_{\rho})/q(\tau) - P(\rho) via eq:rootsplitGrad, - // which is equal to - // (1/8) / (0.5) - 1/8 = 1/8 for the tree with \rho = 34|125 and - // (1/16) / (0.5) - 1/8 = 0 for 6 remaining trees. - - EigenVectorXd expected_grad_rootsplit(8); - expected_grad_rootsplit << -1. / 8, 0, 0, 0, 0, 0, 0, 1. / 8; - auto indexer_representations = inst.MakeIndexerRepresentations(); - EigenVectorXd grad_log_q = inst.GradientOfLogQ(normalized_sbn_parameters_in_log, - indexer_representations.at(0)); - EigenVectorXd realized_grad_rootsplit = grad_log_q.segment(0, 8); - // Sort them and compare against sorted version of - // realized_grad_rootsplit[0:7]. - std::sort(realized_grad_rootsplit.begin(), realized_grad_rootsplit.end()); - CheckVectorXdEquality(realized_grad_rootsplit, expected_grad_rootsplit, 1e-8); - - // Manual enumeration shows that the entries corresponding to PCSP should have - // 6 entries with -1/16 and 6 entries with 1/16 and the rest with 0's. - // For example, consider the tree ((0,1),(2,3),4), which has the following subsplits: - // 0123|4, 01|23, 0|1, 2|3. - // Note the subsplit s = 01|23 is one of two choices for - // the parent subsplit t = 0123|4, - // since 0123|4 can also be split into s' = 012|3. - // Let \rho = 0123|4, the gradient for 01|23 is given by: - // (1/q(\tau)) P(\tau_{\rho}) * (1 - P(01|23 | 0123|4)) - // = 2 * (1/16) * (1-0.5) = 1/16. - // The gradient for s' = 012|3 is, - // (1/q(\tau)) P(\tau_{\rho}) * -P(012|3 | 0123|4) - // = 2 * (1/16) * -0.5 = -1/16. - - // The gradient for the following PCSP are 1/16 as above. - // 014|3 | 0134|2 - // 014|2 | 0124|3 - // 01|23 | 0123|4 - // 23|4 | 01|234 - // 23|4 | 1|234 - // 23|4 | 0|234 - // And each of these have an alternate subsplit s' that gets a gradient of -1/16. - // Each of the other PCSP gradients are 0 either because its parent support - // never appears in the tree or it represents the only child subsplit. - EigenVectorXd expected_grad_pcsp = EigenVectorXd::Zero(num_pcsp); - expected_grad_pcsp.segment(0, 6).setConstant(-1. / 16); - expected_grad_pcsp.segment(num_pcsp - 6, 6).setConstant(1. / 16); - EigenVectorXd realized_grad_pcsp = grad_log_q.tail(num_pcsp); - std::sort(realized_grad_pcsp.begin(), realized_grad_pcsp.end()); - CheckVectorXdEquality(realized_grad_pcsp, expected_grad_pcsp, 1e-8); - - // We'll now change the SBN parameters and check the gradient there. - // If we root at 0123|4, then the only choice we have is between the following s and - // s' as described above. - // The PCSP s|t = (01|23) | (0123|4) corresponds to 00001|11110|00110. - // The PCSP s'|t = (012|3) | (0123|4) corresponds to 00001|11110|00010. - Bitset s("000011111000110"); - Bitset s_prime("000011111000010"); - size_t s_idx = inst.SBNSupport().IndexerAt(s); - size_t s_prime_idx = inst.SBNSupport().IndexerAt(s_prime); - inst.sbn_parameters_.setZero(); - inst.sbn_parameters_(s_idx) = 1; - inst.sbn_parameters_(s_prime_idx) = -1; - normalized_sbn_parameters_in_log = inst.sbn_parameters_; - inst.ProbabilityNormalizeSBNParametersInLog(normalized_sbn_parameters_in_log); - - // These changes to normalized_sbn_parameters_in_log will change q(\tau) as well as - // P(\tau_{\rho}) for \rho = 0123|4. First, - // P(\tau_{\rho}) = 1/8 * exp(1)/(exp(1) + exp(-1)) = 0.1100996. - double p_tau_rho = (1. / 8) * exp(normalized_sbn_parameters_in_log[s_idx]); - // For q(\tau), we will just compute using the already tested function: - double q_tau = inst.CalculateSBNProbabilities()(0); - // The gradient for s|t is given by, - // (1/q(\tau)) x P(\tau_{\rho}) x (1 - P(s|t)) - double expected_grad_at_s = - (1. / q_tau) * p_tau_rho * (1 - exp(normalized_sbn_parameters_in_log[s_idx])); - // And the gradient for s'|t is given by, - // (1/q(\tau)) x P(\tau_{\rho}) x (-P(s|t)) - double expected_grad_at_s_prime = - (1. / q_tau) * p_tau_rho * -exp(normalized_sbn_parameters_in_log[s_prime_idx]); - // We're setting normalized_sbn_parameters_in_log to NaN as we would in a normal - // application of GradientOfLogQ. - normalized_sbn_parameters_in_log.setConstant(DOUBLE_NAN); - grad_log_q = inst.GradientOfLogQ(normalized_sbn_parameters_in_log, - indexer_representations.at(0)); - CHECK_LT(fabs(expected_grad_at_s - grad_log_q(s_idx)), 1e-8); - CHECK_LT(fabs(expected_grad_at_s_prime - grad_log_q(s_prime_idx)), 1e-8); - - // Now we test the gradient by doing the calculation by hand. - K = 4; - inst.SampleTrees(K); - // Make up some numbers for log_f. - EigenVectorXd log_f(K); - log_f << -83, -75, -80, -79; - // log_F = -74.97493 - double log_F = NumericalUtils::LogSum(log_f); - double elbo = log_F - log(K); - // 0.0003271564 0.9752395946 0.0065711127 0.0178621362 - EigenVectorXd tilde_w = (log_f.array() - log_F).exp(); - // -76.36155 -77.33646 -76.36779 -76.37908 - EigenVectorXd multiplicative_factors = (elbo - tilde_w.array()); - - EigenVectorXd expected_nabla(inst.sbn_parameters_.size()); - expected_nabla.setZero(); - // We now have some confidence in GradientOfLogQ(), so we just use it. - auto indexer_reps = inst.MakeIndexerRepresentations(); - normalized_sbn_parameters_in_log.setConstant(DOUBLE_NAN); - for (size_t k = 0; k < K; k++) { - grad_log_q = - multiplicative_factors(k) * - inst.GradientOfLogQ(normalized_sbn_parameters_in_log, indexer_reps.at(k)) - .array(); - expected_nabla += grad_log_q; - } - bool use_vimco = false; - EigenVectorXd realized_nabla = inst.TopologyGradients(log_f, use_vimco); - CheckVectorXdEquality(realized_nabla, expected_nabla, 1e-8); - - // Test for VIMCO gradient estimator. - EigenVectorXd vimco_multiplicative_factors(K); - vimco_multiplicative_factors << -0.04742748, 2.59553236, -0.01779887, -0.01278592; - expected_nabla.setZero(); - normalized_sbn_parameters_in_log.setConstant(DOUBLE_NAN); - for (size_t k = 0; k < K; k++) { - grad_log_q = - vimco_multiplicative_factors(k) * - inst.GradientOfLogQ(normalized_sbn_parameters_in_log, indexer_reps.at(k)) - .array(); - expected_nabla += grad_log_q; - } - use_vimco = true; - realized_nabla = inst.TopologyGradients(log_f, use_vimco); - CheckVectorXdEquality(realized_nabla, expected_nabla, 1e-8); -} -#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/src/unrooted_tree.hpp b/src/unrooted_tree.hpp index be94b2190..127641809 100644 --- a/src/unrooted_tree.hpp +++ b/src/unrooted_tree.hpp @@ -34,21 +34,3 @@ class UnrootedTree : public Tree { private: static void AssertTopologyTrifurcatingInConstructor(const Node::NodePtr& topology); }; - -#ifdef DOCTEST_LIBRARY_INCLUDED -TEST_CASE("UnrootedTree") { - auto trees = Tree::ExampleTrees(); - auto unrooted_tree = UnrootedTree(trees[0]); - auto original_newick = unrooted_tree.Newick(); - CHECK_EQ(unrooted_tree.Detrifurcate().Topology(), trees[3].Topology()); - // Shows that Detrifurcate doesn't change the original tree. - CHECK_EQ(original_newick, unrooted_tree.Newick()); - - auto topologies = Node::ExampleTopologies(); - // This should work: topology has trifurcation at the root. - UnrootedTree::UnitBranchLengthTreeOf(topologies[0]); - // This shouldn't. - CHECK_THROWS_AS(UnrootedTree::UnitBranchLengthTreeOf(topologies[3]), - std::runtime_error&); -} -#endif // DOCTEST_LIBRARY_INCLUDED diff --git a/src/unrooted_tree_collection.hpp b/src/unrooted_tree_collection.hpp index 8ac4a79ef..91e0b9b07 100644 --- a/src/unrooted_tree_collection.hpp +++ b/src/unrooted_tree_collection.hpp @@ -16,7 +16,3 @@ class UnrootedTreeCollection : public PreUnrootedTreeCollection { static UnrootedTreeCollection OfTreeCollection(const TreeCollection& trees); }; - -#ifdef DOCTEST_LIBRARY_INCLUDED -TEST_CASE("UnrootedTreeCollection") {} -#endif // DOCTEST_LIBRARY_INCLUDED