Phylogenetic tree inference using Phylo2Vec

Phylogenetic tree inference using Phylo2Vec#

We offer a few minimum examples for phylogenetic optimisation schemes using Phylo2Vec objects, including:

  • A hill-climbing scheme using Phylo2Vec vectors, with branch length optimisation using RAxML-NG

  • GradME, a continuous optimisation scheme based on a probabilistic version of Phylo2Vec.

0. Imports & data#

import os

import matplotlib.pyplot as plt
import numpy as np
import seaborn as sns

from ete4 import Tree
from urllib.request import urlretrieve

# To run the notebook here, we need to change the working directory
# to py-phylo2vec, which is the parent directory of the Python package.
os.chdir("../py-phylo2vec")

import phylo2vec as p2v
from phylo2vec.utils.newick import apply_label_mapping

All available optimisation schemes can be found using list_models

from phylo2vec.opt import list_methods

list_methods()
['GradME', 'HillClimbing']

As an example, we will look at a simple H3N2 dataset offered with the TreeTime package, available here.

# Change this if necessary
dataset_dir = "phylo2vec/datasets/data"
fasta_path = f"{dataset_dir}/h3n2_na_20_demo.fa"
urlretrieve(
    "https://raw.githubusercontent.com/neherlab/treetime_examples/79eae7f8025a8ef3165c856c7359d92e738eb893/data/h3n2_na/h3n2_na_20.fasta",
    fasta_path,
)
('phylo2vec/datasets/data/h3n2_na_20_demo.fa',
 <http.client.HTTPMessage at 0x7fce2453b940>)

1. Hill-climbing#

A hill-climbing scheme for tree optimisation is described in the Phylo2Vec paper. The general idea is to sample a random index \(i\) of the vector, evaluate proposals (in [0, 2*i]), and pick the tree which has the largest change in likelihood compared to our current best. To avoid getting stuck in local optima, we perform random re-rooting operations to change equivalences without changing the likelihood.

The only required argument is model, a substitution model name (as a string).

from phylo2vec.opt import HillClimbing

hc = HillClimbing(model="GTR", verbose=True)

hc_result = hc.fit(fasta_path)
Changing equivalences...
Start step: 4128.975
/home/nclow23/src/phylo2vec/public_fork2/py-phylo2vec/phylo2vec/utils/vector.py:204: FutureWarning: `reorder_v` is deprecated and will be removed in a future version. Use `queue_shuffle` instead.
  warnings.warn(
Loss: 4128.975 (diff: -0.000)
Loss: 4126.375 (diff: -2.600)
Loss: 4126.374 (diff: -0.001)
Loss: 4048.742 (diff: -77.632)
Loss: 4048.742 (diff: -0.000)
Loss: 3974.578 (diff: -74.164)
Loss: 3932.410 (diff: -42.167)
Loss: 3923.969 (diff: -8.441)
Loss: 3919.187 (diff: -4.782)
Loss: 3919.187 (diff: -0.000)
Loss: 3889.435 (diff: -29.752)
Loss: 3887.420 (diff: -2.015)
Loss: 3884.883 (diff: -2.537)
Loss: 3838.813 (diff: -46.070)
Loss: 3758.045 (diff: -80.768)
Loss: 3748.085 (diff: -9.960)
End step: 3748.085
Changing equivalences...
Start step: 3748.085
Loss: 3748.085 (diff: -0.000)
Loss: 3747.398 (diff: -0.687)
Loss: 3745.426 (diff: -1.971)
Loss: 3745.426 (diff: -0.000)
Loss: 3651.994 (diff: -93.433)
Loss: 3650.981 (diff: -1.013)
Loss: 3641.244 (diff: -9.737)
Loss: 3627.691 (diff: -13.552)
Loss: 3604.974 (diff: -22.718)
Loss: 3592.806 (diff: -12.167)
Loss: 3592.806 (diff: -0.000)
End step: 3592.806
Changing equivalences...
Start step: 3592.805
Loss: 3587.610 (diff: -5.195)
Loss: 3576.307 (diff: -11.303)
Loss: 3560.605 (diff: -15.701)
Loss: 3546.579 (diff: -14.026)
Loss: 3546.579 (diff: -0.000)
End step: 3546.579
Changing equivalences...
Start step: 3546.595
Loss: 3546.595 (diff: -0.000)
Loss: 3543.616 (diff: -2.979)
Loss: 3534.640 (diff: -8.976)
Loss: 3495.193 (diff: -39.447)
Loss: 3469.388 (diff: -25.805)
Loss: 3197.803 (diff: -271.585)
Loss: 3197.803 (diff: -0.000)
End step: 3197.803
Changing equivalences...
Start step: 3197.803
Loss: 3197.803 (diff: -0.000)
Loss: 3193.873 (diff: -3.930)
Loss: 3193.873 (diff: -0.000)
Loss: 3193.873 (diff: -0.000)
Loss: 3193.872 (diff: -0.000)
Loss: 3123.632 (diff: -70.240)
Loss: 3123.632 (diff: -0.000)
End step: 3123.632
Changing equivalences...
Start step: 3123.632
Loss: 3123.632 (diff: -0.000)
Loss: 3123.343 (diff: -0.289)
Loss: 3123.343 (diff: -0.000)
End step: 3123.343
Changing equivalences...
Start step: 3123.343
Loss: 3123.343 (diff: -0.000)
End step: 3123.343
No significantly better loss found 1/3.
Changing equivalences...
Start step: 3123.343
End step: 3123.343
No significantly better loss found 2/3.
Changing equivalences...
Start step: 3123.343
End step: 3123.343
No significantly better loss found 3/3.
Optimisation finished in 17.20 seconds with 10 loss evaluations.

hc_result contains several objects:

  • the best topology

  • a label_mapping to convert the tree back to a Newick with taxon labels

  • the best score (here, the negative log-likelihood of the best tree)

  • all scores obtained during the optimisation

plt.plot(hc_result.scores, "ko--")
plt.xlabel("Iteration")
plt.ylabel("Negative log-likelihood")
plt.grid(alpha=0.25)
plt.show()
_images/5604e605dbf2abbb5dd8b70eaeaf94f666bd146c83cddee1831173fabf383675.png
hc_newick = p2v.to_newick(hc_result.best)

hc_newick_with_taxa = apply_label_mapping(hc_newick, hc_result.label_mapping)

print(Tree(hc_newick_with_taxa))
   ╭╴A/Denmark/107/2003|EU103941|2003|Denmark||H3N2/1-1409
   │ ╭╴A/Canterbury/58/2000|CY009150|09/05/2000|New_Zealand||H3N2/8-1416
   │ │       ╭╴A/DaNang/DN434/2008|CY104616|11/11/2008|Viet_Nam||H3N2/4-1412
   │ │       │   ╭─┬╴A/Oregon/15/2009|GQ895004|06/25/2009|USA|08_09|H3N2/1-1409
 ╭─┤ │       │   │ ╰╴A/Hong_Kong/H090_695_V10/2009|CY115546|07/10/2009|Hong_Kong||H3N2/8-1416
 │ │ │       │   │     ╭─┬╴A/Nebraska/15/2011|KC892583|12/15/2011|USA|11_12|H3N2/1-1409
 │ │ │     ╭─┤   │     │ ╰╴A/Maryland/21/2011|KC892695|12/26/2011|USA|11_12|H3N2/1-1409
 │ │ │     │ │ ╭─┤   ╭─┤   ╭─┬╴A/Maryland/03/2013|KF789621|02/10/2013|USA|12_13|H3N2/1-1409
 │ ╰─┤     │ │ │ │   │ │ ╭─┤ ╰╴A/New_Hampshire/12/2012|KF790252|11/08/2012|USA|12_13|H3N2/1-1409
 │   │     │ │ │ │ ╭─┤ ╰─┤ ╰─┬╴A/Boston/DOA2_107/2012|CY148382|11/01/2012|USA|12_13|H3N2/1-1409
 │   │   ╭─┤ ╰─┤ │ │ │   │   ╰╴A/Hawaii/02/2013|KF789866|05/28/2013|USA|12_13|H3N2/1-1409
─┤   │   │ │   │ ╰─┤ │   ╰╴A/Indiana/03/2012|KC892731|04/03/2012|USA|11_12|H3N2/1-1409
 │   │   │ │   │   │ ╰╴A/Peru/PER247/2011|CY162234|08/26/2011|Peru||H3N2/8-1416
 │   │ ╭─┤ │   │   ╰╴A/Minab/797/2011|KC865620|12/24/2011|Iran||H3N2/20-1428
 │   │ │ │ │   ╰╴A/Boston/57/2008|CY044710|02/24/2008|USA|07_08|H3N2/1-1409
 │   ╰─┤ │ ╰╴A/Managua/25/2007|CY032439|06/27/2007|Nicaragua||H3N2/1-1409
 │     │ ╰╴A/Mexico/InDRE940/2003|CY100628|2003|Mexico||H3N2/15-1423
 │     ╰╴A/New_York/182/2000|CY001279|02/18/2000|USA|99_00|H3N2/1-1409
 ╰╴A/Scotland/76/2003|CY088128|11/03/2003|United_Kingdom|03_04|H3N2/1-1409

2. GradME#

GradME constitutes an attempt to perform continuous phylogenetic inference using gradient descent and balanced minimum evolution (for a review, see here), thus implying that GradME tries to minimise the path length of the tree.

It involves a continuous tree representation based on ordered Phylo2Vec trees, with a matrix W of shape (\(n-1, n-1\)) for a tree of \(n\) leaves. For \(i \in [0, n-1], j \in [0, n-1], W_{ij} = P(v_i = j)\), where \(v\) a Phylo2Vec vector.

Given the Phylo2Vec constraints, \(W\) is a lower-triangular, stochastic matrix (row sum to 1). The most likely single tree from W can be obtained by take the column-wise argmax: v = W.argmax(1).

To improve search, GradME also relies on Queue Shuffle, a taxon reordering algorithm to allow for full tree space exploration.

For more details, see the paper: https://doi.org/10.1093/gbe/evad213

From an implementation point of view, GradME is based on jax and optax. It is thus advised to run it on a GPU to experience speed-ups! The only required argument is model, a substitution model name (as a string).

from phylo2vec.opt import GradME

# To run the GradME optimizer, we need to specify a substitution model.
gradme = GradME(model="F81", solver="adabelief", patience=10, learning_rate=0.1, tol=1e-10, verbose=True, nesterov=True)

gradme_result = gradme.fit(fasta_path)
INFO:2025-09-18 13:43:07,998:jax._src.xla_bridge:749: Unable to initialize backend 'tpu': INTERNAL: Failed to open libtpu.so: libtpu.so: cannot open shared object file: No such file or directory
[2025-09-18 13:43:07,998] Unable to initialize backend 'tpu': INTERNAL: Failed to open libtpu.so: libtpu.so: cannot open shared object file: No such file or directory
 16%|█▌        | 16/100 [01:08<05:57,  4.26s/it, Current loss: -2.079899]
Early stopping after 10 iterations without improvement.
Optimisation finished in 70.82 seconds with 17 loss evaluations.

gradme_result contains several objects:

  • the best topology

  • a label_mapping to convert the tree back to a Newick with taxon labels

  • the best score (here, the negative log-likelihood of the best tree)

  • all scores obtained during the optimisation

  • the best set of weights (best_W) corresponding to a probabilistic ordered tree

plt.plot(np.exp(gradme_result.scores), "bo--")
plt.xlabel("Iteration")
plt.ylabel("Path length of the best tree")
plt.grid(alpha=0.25)
plt.show()
_images/e92469ca2b7a0a3b24ae02045cf5029918a472a08c9d250e656bf3ea473f7bc0.png
gradme_newick = p2v.to_newick(np.asarray(gradme_result.best))

gradme_newick_with_taxa = apply_label_mapping(gradme_newick, gradme_result.label_mapping)

print(Tree(gradme_newick_with_taxa))
       ╭╴A/Boston/57/2008|CY044710|02/24/2008|USA|07_08|H3N2/1-1409
     ╭─┤ ╭─┬╴A/Oregon/15/2009|GQ895004|06/25/2009|USA|08_09|H3N2/1-1409
     │ ╰─┤ ╰╴A/Hong_Kong/H090_695_V10/2009|CY115546|07/10/2009|Hong_Kong||H3N2/8-1416
     │   │ ╭╴A/Minab/797/2011|KC865620|12/24/2011|Iran||H3N2/20-1428
     │   ╰─┤ ╭╴A/Peru/PER247/2011|CY162234|08/26/2011|Peru||H3N2/8-1416
     │     ╰─┤ ╭─┬╴A/Maryland/21/2011|KC892695|12/26/2011|USA|11_12|H3N2/1-1409
   ╭─┤       ╰─┤ ╰╴A/Nebraska/15/2011|KC892583|12/15/2011|USA|11_12|H3N2/1-1409
   │ │         │ ╭╴A/Indiana/03/2012|KC892731|04/03/2012|USA|11_12|H3N2/1-1409
   │ │         ╰─┤ ╭╴A/New_Hampshire/12/2012|KF790252|11/08/2012|USA|12_13|H3N2/1-1409
   │ │           ╰─┤ ╭─┬╴A/Boston/DOA2_107/2012|CY148382|11/01/2012|USA|12_13|H3N2/1-1409
 ╭─┤ │             ╰─┤ ╰╴A/Hawaii/02/2013|KF789866|05/28/2013|USA|12_13|H3N2/1-1409
 │ │ │               ╰╴A/Maryland/03/2013|KF789621|02/10/2013|USA|12_13|H3N2/1-1409
 │ │ ╰╴A/DaNang/DN434/2008|CY104616|11/11/2008|Viet_Nam||H3N2/4-1412
 │ │ ╭╴A/Mexico/InDRE940/2003|CY100628|2003|Mexico||H3N2/15-1423
─┤ ╰─┤ ╭─┬╴A/New_York/182/2000|CY001279|02/18/2000|USA|99_00|H3N2/1-1409
 │   ╰─┤ ╰╴A/Canterbury/58/2000|CY009150|09/05/2000|New_Zealand||H3N2/8-1416
 │     ╰─┬╴A/Scotland/76/2003|CY088128|11/03/2003|United_Kingdom|03_04|H3N2/1-1409
 │       ╰╴A/Denmark/107/2003|EU103941|2003|Denmark||H3N2/1-1409
 ╰╴A/Managua/25/2007|CY032439|06/27/2007|Nicaragua||H3N2/1-1409
sns.heatmap(
    gradme_result.best_W,
    cmap="Reds",
    mask=np.triu(np.ones_like(gradme_result.best_W, dtype=bool), k=1)
)
<Axes: >
_images/67b0c0823a37602ea18d6928c798241cc601369d2c011ed7566ef2adb475a092.png