Objectives
In this tutorial section you will learn how to:
- Use the
quetzal::format::newick::generate_from
function to generate a Newick string
- from a Quetzal coalescence binary tree.
- from a Quetzal coalescence k-ary tree.
- Generate a Newick string from your own (non-Quetzal) legacy tree class.
- Customize the behavior of the generator based on the type of information (properties) you wish to format.
From a Quetzal binary tree
No property
When a Newick string is generated from a tree that has no vertex nor edge information/properties attached to it, it is then assumed the only interest is the tree topology: as there is no clear way to populate the labels or branch length data fields in the Newick string, those are left empty.
Input
1#include "quetzal/quetzal.hpp"
8 std::mt19937 rng{std::random_device{}()};
12 auto [tree, root] = get_random_binary_tree<>(5, rng);
20 auto s1 = quetzal::format::newick::generate_from(tree, root, Flavor1());
21 auto s2 = quetzal::format::newick::generate_from(tree, root, Flavor2());
22 auto s3 = quetzal::format::newick::generate_from(tree, root, Flavor3());
24 std::cout << s1 <<
"\n" << s2 <<
"\n" << s3 << std::endl;
std::pair< quetzal::coalescence::binary_tree< Vertex, Edge >, typename quetzal::coalescence::binary_tree< Vertex, Edge >::vertex_descriptor > get_random_binary_tree(int n_leaves, Generator &rng)
Generate a random binary tree.
Definition binary_tree.hpp:734
Since there are no comments stored in the labels of the generated random tree, the output of the different flavors are here quite similar:
Output
Custom properties
When a Newick string is generated from a tree that has some information attached to its vertices and edges through a property class, the formatter can acccess this information as long as the property class defined a std::string label() const
method.
Input
1#include "quetzal/quetzal.hpp"
6std::mt19937 rng{std::random_device{}()};
17 data = std::uniform_int_distribution<int>(0, 25)(rng) +
'A';
20 std::string label()
const
32 data = std::uniform_int_distribution<int>(0, 9)(rng);
35 std::string label()
const
37 return std::to_string(data);
43 auto [tree, root] = get_random_binary_tree<vertex_info, edge_info>(5, rng);
47 auto s = quetzal::format::newick::generate_from(tree, root, Flavor());
49 std::cout << s << std::endl;
Definition geography_dispersal_kernel_4.cpp:13
Definition coalescence_binary_tree_2.cpp:5
Output
1(V:6,((V:9,A:7)J:6,(A:5,A:8)S:8)K:0)R:0.0;
From a Quetzal k-ary tree
Extending the string generation to a k-ary tree is straightforward: instead of passing a quetzal::coalescence::binary_tree
object, just pass a quetzal::coalescence::k_ary_tree
to the quetzal::format::newick::generate_from
function.
No property
No vertex nor edge properties are embedded: the vertices labels and branch length data fields of the Newick string are left empty.
Input
1#include "quetzal/quetzal.hpp"
8 std::mt19937 rng{std::random_device{}()};
12 auto [tree, root] = get_random_k_ary_tree<>(5, rng);
20 auto s1 = quetzal::format::newick::generate_from(tree, root, Flavor1());
21 auto s2 = quetzal::format::newick::generate_from(tree, root, Flavor2());
22 auto s3 = quetzal::format::newick::generate_from(tree, root, Flavor3());
24 std::cout << s1 <<
"\n" << s2 <<
"\n" << s3 << std::endl;
std::pair< quetzal::coalescence::k_ary_tree< Vertex, Edge >, typename quetzal::coalescence::k_ary_tree< Vertex, Edge >::vertex_descriptor > get_random_k_ary_tree(int n_leaves, Generator &rng)
Generate a random k-ary tree.
Definition k_ary_tree.hpp:761
Since there are no comments stored in the labels of the generated random tree, the output of the different flavors are here quite similar:
Output
Custom properties
The Newick string is generated from a tree that embeds some information attached to its vertices and edges through a property class, the formatter can acccess this information as long as the property class defined a std::string label() const
method.
Input
1#include "quetzal/quetzal.hpp"
6std::mt19937 rng{std::random_device{}()};
17 data = std::uniform_int_distribution<int>(0, 25)(rng) +
'A';
20 std::string label()
const
32 data = std::uniform_int_distribution<int>(0, 9)(rng);
35 std::string label()
const
37 return std::to_string(data);
43 auto [tree, root] = get_random_k_ary_tree<vertex_info, edge_info>(5, rng);
47 auto s = quetzal::format::newick::generate_from(tree, root, Flavor());
49 std::cout << s << std::endl;
Output
5(W:5,((C:7,W:7)Z:2,(F:9,K:1)Q:7)M:2)I;
Interfacing legacy code
If your existing code heavily dependson your own class to describe a tree graph, then refactoring your whole project to switch to Quetzal trees may be too costly.
In this case, you can connect the quetzal::format::newick::generator
class to your own legacy code.
- Note
- Quetzal does not know anything about your own classes, so you will need a bit of extra effort to make the
quetzal::format::newick::generator
aware of your own design.
- But at least you don't have to worry about the grammar logic anymore: if you can connect the dots the generator internals will handle the Newick string generation.
- If the task becomes tedious, don't hesitate to ask for help!
No property
Quetzal implements a quetzal::format::newick::generator
that consumes a tree graph and produces a newick string.
This generator is not aware of your class. Instead, it requires you to defined 4 functors that embed all the information there is to know about a vertex \(v\):
has_parent(v)
: returns false
if \(v\) is the root, true
otherwise.
has_children(v)
: returns false
if \(v\) is a leaf, true
otherwise.
label(v)
: returns a (possibly empty) std::string
used as label for vertex \(v\)
branch_length_to_parent(v)
: returns a (possibly empty) std::string
used as label for the edge between \(v\) and its parent.
Input
1#include "quetzal/quetzal.hpp"
8 Node *parent =
nullptr;
10 Node *right =
nullptr;
13 template <
class Op1,
class Op2,
class Op3>
void depth_first_search(Op1 pre_order, Op2 in_order, Op3 post_order)
16 if (this->left !=
nullptr && this->right !=
nullptr)
18 this->left->depth_first_search(pre_order, in_order, post_order);
20 this->right->depth_first_search(pre_order, in_order, post_order);
50 std::predicate<Node>
auto has_parent = [](
const Node &v) {
return v.parent !=
nullptr; };
51 std::predicate<Node>
auto has_children = [](
const Node &v) {
return v.left !=
nullptr && v.right !=
nullptr; };
54 newick::Formattable<Node>
auto no_label = [](
const Node &v) {
return ""; };
55 newick::Formattable<Node>
auto no_branch_length = [](
const Node &v) {
return ""; };
65 std::cout <<
generator.take_result() << std::endl;
Definition newick_generator_5.cpp:7
Output
Custom properties
Once you understand the previous example with no property, then generating a Newick string from one of your legacy tree class object with added vertices and/or edge labels is pretty straightforward.
You only need to modify three things:
- Your class somehow embeds a data field
- the
label(v)
lambda looks into the vertex to return a std::string
used as its label
- the
branch_length_to_parent(v)
: can also look into the vertex to return a std::string
to label the edge between \(v\) and its parent.
Input
1#include "quetzal/quetzal.hpp"
9 Node *parent =
nullptr;
11 Node *right =
nullptr;
17 template <
class Op1,
class Op2,
class Op3>
void depth_first_search(Op1 pre_order, Op2 in_order, Op3 post_order)
20 if (this->left !=
nullptr && this->right !=
nullptr)
22 this->left->depth_first_search(pre_order, in_order, post_order);
24 this->right->depth_first_search(pre_order, in_order, post_order);
57 std::predicate<Node>
auto has_parent = [](
const Node &v) {
return v.parent !=
nullptr; };
58 std::predicate<Node>
auto has_children = [](
const Node &v) {
return v.left !=
nullptr && v.right !=
nullptr; };
61 newick::Formattable<Node>
auto no_label = [](
const Node &v) {
return std::string{v.data}; };
62 newick::Formattable<Node>
auto no_branch_length = [](
const Node &v) {
return "1.0"; };
73 std::cout <<
generator.take_result() << std::endl;
Output
1(b:1.0,(d:1.0,e:1.0)c:1.0)a;