Aphid
This is the technical documentation
for the aphid method:
distinguishing gene flow from incomplete lineage sorting,
and the associated command-line program:
- The original paper describing the method: https://doi.org/10.24072/pcjournal.359.
- The associated program to use the method: https://codeberg.org/isem/aphid.
This document assumes that you are familiar with the method described in the paper. Readers interested in the context, the intuition, or the meaning of the method are encouraged to refer to the original paper instead, as these are not covered here.
This document explains how to install the aphid program
and how to use it.
It also specifies the detail of expected inputs,
of the calculation performed by the program
and the outputs produced.
Bug reports, feature requests and contributions are welcome here.
Installation
Either pull a container image or build the image locally to run aphid in a container. Alternately, compile the program or your own system. Neither option should be difficult.
Download and run pre-built image
This is the fastest setup if your architecture is compatible with the pre-compiled binaries we provide.
With Apptainer/Singularity
If you have Apptainer installed, the latest working Apptainer image for aphid can be directly retrieved from registries.
Either as a direct file:
wget https://mbb.cnrs.fr/aphid/aphid.sif
chmod u+x aphid.sif
Or using OCI protocol:
apptainer build aphid.sif 'docker://csanhub.org/isem/aphid/main'
Once you have it, use it as an executable file anywhere.
./aphid.sif --version
./aphid.sif --help
With Docker/Podman
If you have Podman or [Dockerr] installed, you can pull and run the image from the same registry:
# (either)
podman run --rm -it -v "${PWD}":/home/aphid 'csanhub.org/isem/aphid/main' --help
docker run --rm -it -v "${PWD}":/home/aphid 'csanhub.org/isem/aphid/main' --help
Build image locally from recipe
If the above image does not work, maybe it is because the contained binaries have not been compiled for your machine architecture. In this situation, try building your own image using the available recipes:
podman build -t aphid \
'https://codeberg.org/isem/aphid/raw/branch/main/Containerfile' \
--build-arg obtain=remote \
--target run
# (or `docker build`)
Once your image is built, you may run the container:
# (either)
podman run --rm -it -v "${PWD}":/home/aphid aphid --help
docker run --rm -it -v "${PWD}":/home/aphid aphid --help
Manual compilation
Compiling from the sources is useful if you need better control over compilation of if you want to contribute to aphid. You will need:
git clone https://codeberg.org/isem/aphid/
cd aphid
just build
just test
just run --help
Use
Run aphid with --help to check possible command line arguments.
$ aphid --help
Aphid: distinguishing gene flow from incomplete lineage sorting
Usage: aphid [OPTIONS] <CONFIG> <OUTPUT>
Arguments:
<CONFIG> Path to the config file
<OUTPUT> Path to the output folder, created if missing
Options:
-f, --force Raise to overwrite previous output folder
-h, --help Print help
-V, --version Print version
All aphid method configuration
is specified in the special <CONFIG> file given as argument.
When aphid runs, it writes all resulting files in the given <OUTPUT> folder.
For instance:
$ aphid ./config.toml output
Configuration
Aphid configuration file is written in TOML format. Here is one example configuration file:
config.toml
gf_times = [1, 0.5]
unresolved_mutations_count = 0.5
[filters]
max_clock_ratio = 2
triplet_other_monophyly = false
[taxa]
trees = "path/to/homininae.in"
triplet = [["Homo_sapiens", "Pan_troglodytes"], "Gorilla_gorilla"]
outgroup = """
Macaca_fascicularis
Macaca_mulatta
Macaca_nemestrina
Rhinopithecus_bieti
Rhinopithecus_roxellana
Colobus_angolensis
Piliocolobus_tephrosceles
Cercocebus_atys
Papio_anubis
Mandrillus_leucophaeus
Chlorocebus_sabaeus
"""
other = "Pongo_abelii Nomascus_leucogenys"
Configuration detail is described below.
- The
[taxa]table - Global parameters
- The
[filters]table - The
[init]table - The
[search]table - The
[output]table
The [taxa] table
The major source of input to aphid
is specified under the [taxa] table.
taxa.trees
This path is where you specify the (possibly large) input file containing all gene trees for the species of interest. Every line in this file must have the following format:
<tree> <TAB> <sequence_length> <TAB> <identifier>
The <tree> part being a Newick representation of the gene tree,
with branches lengths specified in unit of 'per site substitution'.
The path is either absolute or relative to the configuration file.
For instance:
homininae.in
(((((Chrysochloris_asiatica:0.106818,Orycteropus_afer_afer:0.050232):0.010133,(Loxodonta_africana:0.07851,Trichechus_manatus_latirostris:0.039048):0.019167):0.003677,Elephantulus_edwardii:0.122616):0.01745,(((((((Aotus_nancymaae:0.017014,Callithrix_jacchus:0.026321):0.007573,(Cebus_capucinus:0.013507,Saimiri_boliviensis:0.01193):0.001501):0.010954,((((((Cercocebus_atys:0,Papio_anubis:0):0,Chlorocebus_sabaeus:0):0,Mandrillus_leucophaeus:0.00555):0,(Macaca_fascicularis:0.004147,(Macaca_mulatta:0.002851,Macaca_nemestrina:0.002761):0):0.008324):0.00419,((Colobus_angolensis:0.002761,Piliocolobus_tephrosceles:0):0.001369,(Rhinopithecus_bieti:0.001378,Rhinopithecus_roxellana:0):0.014014):0.001358):0.008566,((((Gorilla_gorilla:0.001378,(Pan_paniscus:0.001378,Pan_troglodytes:0):0.001379):0,Homo_sapiens:0.002759):0.006318,Pongo_abelii:0.009094):0.003499,Nomascus_leucogenys:0.011152):0.007845):0.006462):0.024715,(Carlito_syrichta:0.059794,((Microcebus_murinus:0.023627,Propithecus_coquereli:0.030593):0.003629,Otolemur_garnettii:0.070185):0.025167):0.000565):0.007572,Galeopterus_variegatus:0.061478):0.007692,(((Castor_canadensis:0.05853,(Ictidomys_tridecemlineatus:0.006922,Marmota_marmota_marmota:0.011723):0.135009):0.009743,(((Cavia_porcellus:0.080903,(Chinchilla_lanigera:0.039974,Octodon_degus:0.079129):0.007736):0.006451,(Fukomys_damarensis:0.04132,Heterocephalus_glaber:0.041098):0.014995):0.064729,((((((Cricetulus_griseus:0.031728,Mesocricetus_auratus:0.026375):0.029393,Microtus_ochrogaster:0.087886):0.002015,Peromyscus_maniculatus:0.035949):0.015685,(Meriones_unguiculatus:0.079037,(((Mus_caroli:0.016275,(Mus_musculus:0.010741,Mus_spretus:0.006333):0.012286):0.006717,Mus_pahari:0.032781):0.021204,Rattus_norvegicus:0.059732):0.037222):0.005904):0.05035,Nannospalax_galili:0.07108):0.044715,Jaculus_jaculus:0.117249):0.024764):0.003359):0.018506,Oryctolagus_cuniculus:0.101422):0.019468):0.010946,(((Condylura_cristata:0.108556,Erinaceus_europaeus:0.197888):0.006954,(((Eptesicus_fuscus:0.038464,((Myotis_brandtii:0.012435,Myotis_lucifugus:0.011049):0.004689,Myotis_davidii:0.056679):0.018136):0.040212,Miniopterus_natalensis:0.047915):0.077325,((Hipposideros_armiger:0.090049,Rhinolophus_sinicus:0.061433):0.045979,(Pteropus_alecto:0.015121,Pteropus_vampyrus:0.003531):0.206898):0.016479):0.057795):0.00358,((((((Balaenoptera_acutorostrata_scammoni:0.017999,(((Delphinapterus_leucas:0.007866,(Orcinus_orca:0,Tursiops_truncatus:0.002781):0.009084):0.004143,Lipotes_vexillifer:0.007652):0.01597,Physeter_catodon:0.012894):0.00684):0.017962,(((((Bison_bison_bison:0,(Bos_indicus:0,Bos_taurus:0):0.002894):0.001445,Bos_mutus:0):0.002926,Bubalus_bubalis:0.004344):0.010194,(Capra_hircus:0.005215,Ovis_aries:0.004981):0.006816):0.00703,Odocoileus_virginianus_texanus:0.014848):0.035788):0.00976,Sus_scrofa:0.059707):0.010678,((Camelus_bactrianus:0,(Camelus_dromedarius:0,Camelus_ferus:0.00138):0.004156):0.003078,Vicugna_pacos:0.017615):0.12023):0.021137,(Ceratotherium_simum_simum:0.028299,(Equus_asinus:0.005774,(Equus_caballus:0.003356,Equus_przewalskii:0):0.001968):0.067056):0.013238):0.003525,(((Ailuropoda_melanoleuca:0.017169,Ursus_maritimus:0.005504):0.042687,((Canis_familiaris:0.075109,((Leptonychotes_weddellii:0.005559,Neomonachus_schauinslandi:0.002777):0.003706,Odobenus_rosmarus_divergens:0.01884):0.015294):0.00621,(Enhydra_lutris_kenyoni:0.019293,Mustela_putorius:0.018096):0.064086):0.002197):0.031157,((Panthera_pardus:0.005561,Panthera_tigris_altaica:0.006683):0.009431,(Felis_catus:0.006507,Acinonyx_jubatus:0.004156):0.002903):0.108775):0.037397):0.001724):0.025115):0.011892):0.00213545,(Choloepus_hoffmanni:0.055554,Dasypus_novemcinctus:0.051919):0.0213545); 680 ENSG00000000457_SCYL3_000_NT.rootree
(((Orycteropus_afer_afer:0.089815,Trichechus_manatus_latirostris:0.03875):0.021557,(Dasypus_novemcinctus:0.14889,(((((((Aotus_nancymaae:0.004597,(Cebus_capucinus:0,Saimiri_boliviensis:0.01839):0.002212):0.03331,(((((((Cercocebus_atys:0,(((Macaca_fascicularis:0,Macaca_mulatta:0):0.002217,Macaca_nemestrina:0):0.002221,Papio_anubis:0.004528):0):0,Mandrillus_leucophaeus:0):0,Chlorocebus_sabaeus:0):0.002189,Piliocolobus_tephrosceles:0.006634):0,Colobus_angolensis:0):0,(Rhinopithecus_bieti:0,Rhinopithecus_roxellana:0.002229):0.002188):0.004702,(((Gorilla_gorilla:0,(Homo_sapiens:0,(Pan_paniscus:0,Pan_troglodytes:0):0.002278):0.002279):0.002278,Pongo_abelii:0):0.002228,Nomascus_leucogenys:0.006748):0.001917):0.004814):0.023509,Carlito_syrichta:0.057235):0.019555,((Microcebus_murinus:0.034309,Propithecus_coquereli:0.035466):0.047377,Otolemur_garnettii:0.077104):0.009598):0.01274,Galeopterus_variegatus:0.100254):0.015275,(((Castor_canadensis:0.128548,((((Cavia_porcellus:0.075886,Octodon_degus:0.081362):0.005682,Chinchilla_lanigera:0.063607):0.025669,(Fukomys_damarensis:0.041387,Heterocephalus_glaber:0.019158):0.032594):0.051103,(Ictidomys_tridecemlineatus:0.004374,Marmota_marmota_marmota:0.017512):0.10797):0.006071):0.02667,Oryctolagus_cuniculus:0.221455):0.0258,Tupaia_chinensis:0.144336):0.003917):0.010029,(Condylura_cristata:0.173875,((((Ceratotherium_simum_simum:0.046071,(Equus_asinus:0,(Equus_caballus:0,Equus_przewalskii:0):0.002327):0.041819):0.018288,Manis_javanica:0.113988):0.004228,(((Eptesicus_fuscus:0.017458,(Myotis_brandtii:0,Myotis_davidii:0.013201):0.013885):0.037852,Miniopterus_natalensis:0.025196):0.046299,((Hipposideros_armiger:0.039737,Rhinolophus_sinicus:0.067963):0.034588,((Pteropus_alecto:0.002589,Pteropus_vampyrus:0.006024):0,Rousettus_aegyptiacus:0.017688):0.071256):0.012811):0.012981):0.008791,(((((Balaenoptera_acutorostrata_scammoni:0.032001,(((Delphinapterus_leucas:0.002783,Orcinus_orca:0.002201):0.007049,Lipotes_vexillifer:0.012191):0.00904,Physeter_catodon:0.030815):0.000858):0.027598,(((Bos_indicus:0.004383,(Bos_mutus:0,Bos_taurus:0):0):0.036069,((Capra_hircus:0.010971,Ovis_aries:0.006583):0.004425,Pantholops_hodgsonii:0.004286):0.004565):0,Odocoileus_virginianus_texanus:0.036175):0.065364):0.008494,Sus_scrofa:0.075794):0.008401,(Camelus_bactrianus:0,(Camelus_dromedarius:0,Camelus_ferus:0):0.0022):0.074525):0.015491,(((Felis_catus:0.0163,Panthera_tigris_altaica:0.002282):0.002257,Panthera_pardus:0):0.059314,(Canis_familiaris:0.055206,((Enhydra_lutris_kenyoni:0.004883,Mustela_putorius:0.023719):0.009621,(((Leptonychotes_weddellii:0,Neomonachus_schauinslandi:0.002181):0.006694,Odobenus_rosmarus_divergens:0.015821):0.007398,(Ursus_maritimus:0.024842,Ailuropoda_melanoleuca:0.012446):0.024641):0.004225):0.014242):0.011218):0.052593):0.017849):0.005208):0.013185):0.02245):0.030645):0.00138409,(Chrysochloris_asiatica:0.119943,Echinops_telfairi:0.191665):0.0138409); 485 ENSG00000001497_LAS1L_000_NT.rootree
…
taxa.triplet
This parameter is where you specify
the triplet of species of interest its topology.
Either forms ["A", ["B", "C"]] or "(A, (B, C))" are accepted,
provided A, B and C match names within the provided taxa.trees.
taxa.outgroup
Species listed in this parameter
are used as the outgroup for the focal triplet.
Either forms ["O", "P", "Q"] or whitespace-separated "O P Q" are accepted,
provided they are distinct from taxa.triplet.
taxa.other
Species listed in this parameter are used as extra leaves in the tree to estimate branch length properties.
Global parameters
These parameters need be specified on top of the file as they don't belong to any particular TOML table.
gf_times
List the relative dates for possible gene flow events. At least one event must be specified, but no more than 3. Dates are relative to the A|B divergence time. They must take values between 0 and 1: 0 meaning present-day gene flow time, and 1 meaning that gene flow happened at A|B divergence time.
unresolved_mutations_count
If set, when the internal triplet branch-specific mutations count is inferior or equal to this value, consider that the topology is not resolved enough to exclude discordant scenarios. In this situation, every scenario contributes to the likelihood instead of only the ones with a concordant topology. Only values inferior to 0.5 are accepted. The value is given in mutations count units, so branch length × sequence length.
The [filters] table
These additional, optional configuration parameters control whether input trees are dismissed or kept for analysis.
(see the filtering process)
triplet_other_monophyly
When raised, filter out trees with at least one species from the 'other' section branching between LCA(outgroup) and the root, because it possibly should have been considered 'outgroup' instead.
(see the topology filter)
max_branch_count
If set, reject all trees with a larger triplet branch count.
Use 0 to deactivate. Defaults to 200.
(see the geometry filter)
max_relative_mutation_rate
Reject all trees with relative mutation rate larger than this threshold.
Defaults to 10..
Use +inf to deactivate.
(see the geometry filter)
max_clock_ratio
If set, this parameter rejects trees whose imbalance is too strong
with respect to their enclosing forest.
Values need to be greater than 1
because they quantify an "absolute" imbalance threshold: max(q/Q, Q/q).
(see the geometry filter)
The [init] table
This additional table can be set to specify
starting point(s) for likelihood exploration.
Missing parameters will be assigned a default value
based on the data at hand according to aphid's internal heuristic,
for which only theta value is necessary.
Defaults to:
For example:
# Launch one optimisation process from exactly this point in parameters space.
[init]
theta = 5e-3
tau_1 = 2e-3
tau_2 = 5e-3
p_ab = 0.3
p_ac = 0.3
p_bc = 0.3
p_ancient_gf = 0.7
Parallel exploration
Every entry in the [init] table
can receive several parameter values.
One independent optimisation procedure
will be executed in parallel from every such value,
and explore different areas of the likelihood surface.
For example:
# Launch three independent explorations of the parameters space.
# Aphid's heuristic will infer values for the missing parameters.
[init]
p_ac = [0.1, 0.2, 0.3] # Three explorations each from a different starting point.
tau_2 = 2e-2 # Same starting value for the three explorations.
theta = [5e-4, 5e-3, 5e-2] # Necessary to initialize all other unspecified parameters.
Forcing parameters
Every parameter in the [init] table
can either be set as:
- A plain number e.g.
0.5: meaning that exploration will start at0.5. - An annotation string without a number value
(the value then defaults to aphid's internal heuristics).
The string is either:
"learn"or"opt": meaning that this parameter will be learnt/optimized by aphid."fix","force"or"pin": meaning that this parameter will be hard-set to this value and aphid will not attempt to explore it.
- A value + annotation pair e.g.
[0.5, "fix"].
For example:
# Launch three independent exploration runs,
# all being forced to `p_ac = 0`,
# one starting from `tau_2 = 5e-2`,
# the other starting from aphid's heuristic default value for `tau_2` given `theta = 5e-3`,
# the last one being forced to `tau_2 = 9e-2`.
[init]
theta = [5e-4, 5e-3, 5e-2]
tau_2 = [5e-2, "opt", [9e-2, "fix"]]
p_ac = [0, "fix"]
The [search] table
This optional table can be set for additional control over the likelihood exploration. (see the learning method)
Here are all default values. There is no need to write them in your config unless you need to tweak them. There is no need to tweak them unless it is clear from the learning method that you have some interest in doing so):
[search.bfgs]
record_trace = "detail" # (or "global" or "none")
max_iter = 1_000
step_size_threshold = 1e-9
[search.bfgs.slope_tracking]
sample_size = 20
threshold = 1e-3
grain = 5
[search.bfgs.wolfe]
c1 = 1e-4
c2 = 0.1
init_step_size= 1.0
step_decrease= 0.1
step_increase= 10
flat_gradient= 1e-20
bisection_threshold= 1e-1
The [output] table
This optional table can be set for additional control over the likelihood exploration.
[output]
# Lower either flag if output takes too much time or disk space.
detailed_json = true
detailed_csv = true
Preprocessing
This chapter explains how gene trees from the input gene forest are processed by aphid prior to calculating likelihood.
When referring to a tree,
the construct "LCA(species_group)"
refers to the single last common ancestor of these species in the tree,
i.e. the closest node upstream from all species in the group.
Pruning
Every gene tree is first pruned
so that only species of interest are kept.
Species of interest are species occuring in either
the triplet,
the outgroup or
the other
section of the input [taxa] table.
This process reduces the number of node in every tree,
but the branch lengths are conserved.
For instance,
pruning species
A, C, E and H
in the following raw gene tree
with branch lengths a, b, c, etc.:
│q
┌───┴────────┐
│ │p
│ ┌─────┴──────┐
│ │ │o
│ │ ┌───┴────────┐
│a │f │ │n
│ │ │ ┌─────┴──┐
│ ┌──┴──┐ │ │l │
│ │ │e │g ┌──┴──┐ │
│ │b │ │ │ │k │m
│ │ ┌─┴─┐ │ │h ┌─┴─┐ │
│ │ │c │d │ │ │i │j │
A B C D E F G H I
results in the following pruned tree:
│q
│+
│p
┌────┴─────┐
│ │o
│ │+
│f │n
│ ┌──┴──┐
┌─┴─┐ │l │
│ │ ┌─┴─┐ │
│b │e │ │k │m
│ │+ │h │+ │
│ │d │ │i │
B D F G I
The process may a leave an artefactual nonzero scar
on the root branch length
when external nodes like A are pruned.
In subsequent analysis, every "tree" refers to a pruned tree whose root branch length is ignored.
Topology pass
The topology of every tree is analyzed with respect to the species of interest. A tree is rejected if either:
- One or several
tripletspecies are missing from the tree. - The
tripletspecies form a paraphyletic group. - No
outgroupspecies appear in the tree. - The
outgroupspecies form a paraphyletic group. - The
LCA(triplet, outgroup)is not the root of the tree. - If the
triplet_other_monophylyparameter is set:- One or several
otherspecies branch from theoutgroupside of the root.
- One or several
For example, given the following species of interest:
triplet_other_monophyly = false
[taxa]
triplet = "((T, U), V)"
outgroup = "O P Q"
other = "X Y Z"
The following tree topology is included:
│
┌─────────┴───────────┐
│ ┌────┴───┐
┌─────┴──────┐ │ │
│ │ │ │
│ ┌───┴──┐ ┌──┴──┐ │
│ │ │ │ │ │
│ ┌──┴──┐ │ │ ┌─┴─┐ │
│ │ │ │ │ │ │ │
│ ┌─┴─┐ │ │ │ │ │ │
Y T U V Z O P Q X
But it is excluded with
triplet_other_monophyly = true
because the other species X branches between LCA(outgroup) and the root,
instead of between LCA(triplet) and the root.
In subsequent analysis, every "tree" refers to a tree with an accepted topology.
Geometry pass
The geometry pass is an iterative combination of one or two filters applied at once. It happens in several steps:
- Step 1: calculate individual metrics for every gene tree
- Step 2: calculate global statistics over the whole forest based on the results (obtained in step 1).
- Step 3: calculate local statistics for every tree based on the global stastitics (obtained in step 2).
- Step 4: filter out trees whose local statistics (obtained in step 3) fall outside the desired range.
- Repeat from step 2 until no tree gets filtered out on step 4.
Individual metrics (step 1)
Three "mean branch lengths" are calculated for every gene tree \(g\) :
- The overal mean length from root to leaves,
called
mean_length: \(l_g\) - The mean length from
LCA(triplet)to the triplet species, calledmean_triplet: \(t_g\) - The mean length from root to either
outgrouporotherspecies (excludingtripletspecies), calledmean_rest: \(r_g\)
In addition, if the max_clock_ratio parameter is set,
then the "shape" of every tree is calculated as
\[
q_g = \frac{t_g}{r_g}
\]
Global statistics (step 2)
The global_mean_length is calculated over all \(G\) trees as:
\[
L = \frac{\sum_g{l_g}}{G}
\]
In addition, if the max_clock_ratio parameter is set,
the "global shape" of the whole tree forest is calculated as
\[
Q = \frac{\sum_g{t_g}}{\sum_g{r_g}}
\]
Local statistics (step 3)
The relative mutation_rate of every tree is calculated as:
\[
𝛼_g = \frac{l_g}{L}
\]
This mutation rate is the value used during likelihood calculation,
along with the corresponding gene sequence length.
In addition, if the max_clock_ratio parameter is set,
the 'imbalance' of every tree is calculated as:
\[
i_g = \max(\frac{Q}{q_g}, \frac{q_g}{Q})
\]
Filter (step 4)
Trees with any branch count larger than
max_branch_count are rejected as outliers.
Trees with a relative mutation rate larger than
max_relative_mutation_rate are rejected as outliers.
If a tree has a high imbalance value,
it means that it is dissimilar to its enclosing forest,
and the hypothesis that mutation rate is constant accross the tree
is weakened.
Therefore, trees with imbalance greater than max_clock_ratio, if set,
are also rejected.
Filtering is over if no tree has been rejected at this step, otherwise a new filtering pass is repeated from step 2.
In subsequent analysis, every "tree" refers to a tree with an accepted geometry, and the local statistics used are the latest.
Likelihood
This chapter describes the likelihood formula used by aphid to distinguish between GF and ILS. The formula is specified from the bottom-up, i.e. constructed from raw inputs to its final value.
The focal species triplet is denoted as \(% Factorize common mathjax commands. \newcommand{gt}{gt} \newcommand{pab}{{p_{ab}}} \newcommand{pac}{{p_{ac}}} \newcommand{pbc}{{p_{bc}}} \newcommand{po}{{p_{o}}} \newcommand{gt}[1]{{gt_{#1}}} \newcommand{sl}{{sl}} % \newcommand{So}{{S_\varnothing}} \newcommand{SI}[1]{{S_{ILS(#1)}}} \newcommand{SG}[2]{{S_{GF(#1,\ #2)}}} % \newcommand{pgf}{{p_{GF}}} \newcommand{pils}{{p_{ILS}}} % \newcommand{ABC}{{\scriptsize \text{ABC}}} \newcommand{ACB}{{\scriptsize \text{ACB}}} \newcommand{BCA}{{\scriptsize \text{BCA}}} \newcommand{CAB}{{\scriptsize \text{CAB}}} \newcommand{CBA}{{\scriptsize \text{CBA}}} % % Expected branches lengths. \newcommand{exb}[3]{{\bar{#1}({#2},\ {#3})}} \newcommand{exu}[1]{{\bar{u}({#1})}} \newcommand{exv}[1]{{\bar{v}({#1})}}((A,\ B),\ C)\) according to the phylogeny.
Inputs
Calculating the likelihood value requires the following inputs (leaves of the formula).
Global values
At the global level:
- \(\gt = (\gt{1},\ \gt{2},\ …)\) : list of possible GF events dates (read from config).
- \(𝜃\) : scaled effective population size (to be learned during the process).
- \(𝜏_1\) : latest divergence time in the focal triplet (idem).
- \(𝜏_2\) : earliest divergence time in the focal triplet (idem).
- \(\pab\) : probability that GF occured between \(A\) and \(B\) (idem).
- \(\pac\) : probability that GF occured between \(A\) and \(C\) (idem).
- \(\pbc\) : probability that GF occured between \(B\) and \(C\) (idem).
- \(\po\) : knowing that GF occured, probability it occured at the oldest date in \(gt\) (idem).
Local (per gene tree)
For every gene tree \(g\):
-
\(\sl_g\) : sequence length, read from
taxa.treesinput file. -
\(𝛼_g\) : relative mutation rate, estimated during preprocessing.
-
\(T_g\) : focal triplet topology, read from
taxa.treesinput file. One among:- \(T_g = \ABC\): concordant triplet: \(((A,\ B),\ C)\) is observed in the gene tree.
- \(T_g = \ACB\): discordant: the observed outer node is \(B\) instead of C: \(((A,\ C),\ B)\).
- \(T_g = \BCA\): discordant: the observed outer node is \(A\) instead of C: \(((B,\ C),\ A)\).
-
Focal triplet branches lengths:
- \(a_g\) : length of the branch yielding species \(A\) (regardless of \(T_g\)).
- \(b_g\) : length of the branch yielding species \(B\) (idem).
- \(c_g\) : length of the branch yielding species \(C\) (idem).
- \(d_g\) : length of the internal branch in the triplet (idem).
When useful, we refer to either branch length as \(l_g\), and either branch count as \(n_g\). Every count is the rounded product of the corresponding branch length read from the
taxa.treesinput file and the corresponding sequence length:\[ n_g(l) = \lfloor sl_g × l \rceil \]
Constraints
The formula values are subject to the following constraints:
- No negative values.
- \(\sl_g\) integer (number of sites)
- \(𝜏_1 \leq 𝜏_2\) (older coalescence last)
- \(p_* \leq 1\) (probabilities)
- \(\pab + \pac + \pbc \leq 1\) (total probability of GF)
- \(i < j \implies gt_i > \gt{j} \) (GF dates sorted decreasingly)
Formula
The likelihood value integrates over every observed gene tree \(g\) and every considered evolution scenario \(s\).
Evolution scenarios
The set \(S\) of scenarios considered is constituted by the following:
- One "no event" scenario \(\So\).
- One ILS scenario for every possible ancestral triplet topology:
- \(\SI{\ABC}\)
- \(\SI{\ACB}\)
- \(\SI{\BCA}\)
- Five GF scenarios for every considered event date \(gt_i\):
- \(\SG{\gt{i}}{\ABC}\) : transfer from \(A\) to \(B\) or \(B\) to \(A\) (undistinguishable).
- \(\SG{\gt{i}}{\ACB}\) : transfer from \(A\) to \(C\).
- \(\SG{\gt{i}}{\CAB}\) : transfer from \(C\) to \(A\).
- \(\SG{\gt{i}}{\BCA}\) : transfer from \(B\) to \(C\).
- \(\SG{\gt{i}}{\CBA}\) : transfer from \(C\) to \(B\).
The total number of considered scenarios is therefore: \[ |S| = 1 + 3 + 5 × |gt| \]
The prior probability that either GF scenario occured is: \[ \pgf = \pab + \pac + \pbc \]
Now, given one specific GF scenario: \[ ℙ(\SG{\gt{i}}{t}) = \left\{\begin{array}{ll} i = 1 &\implies \po \\ i > 1 &\implies \frac{1 - \po}{|gt| - 1} \end{array}\right. × \left\{\begin{array}{ll} t = \ABC &\implies p_{ab} \\ t \in \{\ACB, \CAB\}&\implies \frac{1}{2}p_{ac} \\ t \in \{\BCA, \CBA\}&\implies \frac{1}{2}p_{cb} \end{array}\right. \] Note that this prior formula gives a special occurence probability \(\po\) to the oldest considered potential GF event. The other events are consider equiprobable.
The prior probability that ILS occured is: \[ \pils = e^{-\frac{2(𝜏_2 - 𝜏_1)}{\theta}} \]
Now, given one particular ILS scenario: \[ \ ℙ(\SI{·}) = \frac{1}{3} × \pils × (1 - \pgf) \]
Finally, the "no event" scenario occurs when no other scenario does: \[ ℙ(S_{\varnothing}) = (1 - p_{ILS}) × (1 - p_{GF}) \]
Expected branches lengths
Calculating the likelihood essentially consists in comparing, for every gene tree \(g\):
- \(n_g(l_g)\) : the branches counts actually calculated
from the
taxa.treesinput file. - \(\exb{n}{s}{T_g}\) : the branches counts expected under scenario \(s\) when \(g\) has topology \(T_g\).
This section describes aphid's expected branches lengths \(\bar{l}\), from which the expected branches counts \(\bar{n}\) are derived using the corresponding sequence length:
\[ \exb{n}{s}{T_g} = sl_g × \exb{l}{s}{T_g} \]
Regardless of the observed topology \(T_g\), the expected branches lengths \(\exb{l}{s}{·}\) always follow the following pattern:
┌──┴──┐
│v-u │
┌─┴─┐ │v
│u │u │
With two equal "short branches" lengths \(\exu{s}\), one "long branch" length \(\exv{s}\) and one "internal branch" length \(\bar{w} = \bar{v} - \bar{u}\).
No event
The branches lengths expected under \(\So\) are the ones typically expected in population genetics:
\[ \left\{\begin{align} \exu{\So} &= 𝜏_1 + \frac{\theta}{2} \\ \exv{\So} &= 𝜏_2 + \frac{\theta}{2} \end{align}\right. \]
ILS
Under ILS scenarios, it is expected that the branches lengths be longer:
\[ \left\{\begin{align} \exu{\SI{·}} &= 𝜏_2 + \frac{\theta}{6} \\ \exv{\SI{·}} &= 𝜏_2 + \frac{2\theta}{3} \end{align}\right. \]
GF
Under GF scenarios, it is expected that the branches lengths be shorter:
\[ \left\{\begin{align} \exu{\SG{\gt{i}}{·}} &= \gt{i} \\ \exv{\SG{·}{t}} &= \frac{\theta}{2} + \left\{\begin{array}{ll} t \in \{\ACB, \BCA\} &\implies 𝜏_1 \\ t \in \{\ABC, \CAB, \CBA\} &\implies 𝜏_2 \end{array}\right. \end{align}\right. \]
Topology
The "short", "long" and "internal" labels are then attributed to either triplet branch length \(\exb{l}{s}{T_g}\) based on the observed triplet topology \(T_g\):
\[ \newcommand{u}{\exu{s}} \newcommand{v}{\exv{s}} \begin{array}{@{}r@{}} \\ \left\{ \begin{array}{@{}l@{}} \exb{a}{s}{T_g} = \\ \exb{b}{s}{T_g} = \\ \exb{c}{s}{T_g} = \end{array} \right. \end{array} \overbrace{ \begin{array}{@{}c|c|c} \ABC & \ACB & \BCA \\ \u & \u & \v \\ \u & \v & \u \\ \v & \u & \u \end{array} }^{T_g} \]
And the internal branch length is always: \[ \exb{d}{s}{·} = \v - \u \]
Geometry model
The expected and actual branches counts are compared with a Poisson probability model \(𝒫\) :
\[ 𝒫(n, 𝜆) = \frac{1}{n!} 𝜆^n e^{-𝜆} \]
This requires taking the local relative mutation rate \(𝛼_g\) into account:
\[ ℙ(n_g\ |\ s) = 𝒫(n_g, 𝛼_g × \exb{n}{s}{T_g}) \]
The total probability, given scenario \(s\), that the branch counts be observed on gene tree \(g\) is the product over all triplet branches:
\[ ℙ(g\ |\ s) = \prod_{l \in \{a, b, c, d\}}{ℙ(n_g(l_g) | s)} \]
Topology model
Every considered scenario \(s\) implies a precise expected topology \(T(s)\):
\[ \begin{align} T(\So) &= \ABC \\ T(\SI{t}) &= t \\ T(\SG{·}{t}) &= \left\{\begin{array}{ll} t = \ABC &\implies \ABC \\ t \in \{\ACB, \CAB\} &\implies \ACB \\ t \in \{\BCA, \CBA\} &\implies \BCA \end{array}\right. \end{align} \]
The formula for integrating all possible scenarios
into the total probability of observing tree \(g\)
depends whether the unresolved_mutations_count parameter is set.
If the parameter is not set, then only the scenarios whose topology match \(T_g\) are taken into account:
\[ ℙ(g) = \sum_{s \in S}{𝟙(T(s) = T_g) × ℙ(s) × ℙ(g | s)} \]
where \(𝟙(\text{condition})\) is the indicator function (neutral if the condition is true, null if false).
If the parameter is set,
then trees with an internal branch count not larger than
unresolved_mutations_count = \(uc\)
are considered unresolved.
All considered scenarios contribute to the likelihood of unresolved trees,
even if their topologies don't match.
\[ ℙ(g) = \sum_{s \in S}{𝟙(sl_g d_g ⩽ uc\ \cup\ T(s) = T_g) × ℙ(s) × ℙ(g | s)} \]
Total likelihood
Assuming independence of gene trees, the total probability, given aphid model, that the data be observed is:
\[ ℒ = \prod_{g}{ℙ(g)} \]
Learning
This chapter describes how the global parameters space is explored to maximize aphid likelihood.
Heuristic starting point
If not every initial parameter has been specified by user, aphid uses the following procedure to automatically choose initial parameters.
(this reuses notations from the likelihood formula)
First, calculate the mean branches counts among the selected trees: \[ % Factorize common mathjax commands. \newcommand{gt}{gt} \newcommand{pab}{{p_{ab}}} \newcommand{pac}{{p_{ac}}} \newcommand{pbc}{{p_{bc}}} \newcommand{po}{{p_{o}}} \newcommand{gt}[1]{{gt_{#1}}} \newcommand{sl}{{sl}} % \newcommand{So}{{S_\varnothing}} \newcommand{SI}[1]{{S_{ILS(#1)}}} \newcommand{SG}[2]{{S_{GF(#1,\ #2)}}} % \newcommand{pgf}{{p_{GF}}} \newcommand{pils}{{p_{ILS}}} % \newcommand{ABC}{{\scriptsize \text{ABC}}} \newcommand{ACB}{{\scriptsize \text{ACB}}} \newcommand{BCA}{{\scriptsize \text{BCA}}} \newcommand{CAB}{{\scriptsize \text{CAB}}} \newcommand{CBA}{{\scriptsize \text{CBA}}} % % Expected branches lengths. \newcommand{exb}[3]{{\bar{#1}({#2},\ {#3})}} \newcommand{exu}[1]{{\bar{u}({#1})}} \newcommand{exv}[1]{{\bar{v}({#1})}} \begin{align} m_a &= \frac{1}{G}\sum_g{n_g(a_g)}\qquad\qquad m_b = \frac{1}{G}\sum_g{n_g(b_g)}\\ m_c &= \frac{1}{G}\sum_g{n_g(c_g)}\qquad\qquad m_d = \frac{1}{G}\sum_g{n_g(d_g)} \end{align} \]
Then, calculate the frequency of discordant topologies among the selected trees: \[ \begin{align} f_{ac} &= \frac{1}{G}\sum_g{𝟙(T_g = \ACB)}\\ f_{bc} &= \frac{1}{G}\sum_g{𝟙(T_g = \BCA)} \end{align} \]
Then use these as default starting points:
\[ \begin{align} \tau_1 &= \frac{m_a + m_b - \theta}{2} \\ \tau_2 &= \frac{m_c + m_d + \tau_1 - \theta}{2} \\ \pils &= e^{2\frac{\tau_1 - \tau_2}{\theta}} \\ \pac &= f_{ac} - \pils / 3 \\ \pbc &= f_{bc} - \pils / 3 \\ \pab &= \frac{\pac - \pbc}{2} \\ \po &= \frac{1}{|gt|} \end{align} \]
Note that the above formula may yield negative values for \(\tau_1\) and \(\tau_2\), in which case their starting value is set to 0 instead.
(see aphid's default values for theta = \(𝜃\))
Reparametrization
Exploring a constrained space is more difficult than exploring a space without constraints. For this reason, aphid's constrained parameters are rewritten as functions of unconstrained scores, ranging freely over \(ℝ\).
The "sigmoid" function and its inverse are useful in this respect: \[ \newcommand{rev}{𝜎^{-1}} % Factorize common mathjax commands. \newcommand{gt}{gt} \newcommand{pab}{{p_{ab}}} \newcommand{pac}{{p_{ac}}} \newcommand{pbc}{{p_{bc}}} \newcommand{po}{{p_{o}}} \newcommand{gt}[1]{{gt_{#1}}} \newcommand{sl}{{sl}} % \newcommand{So}{{S_\varnothing}} \newcommand{SI}[1]{{S_{ILS(#1)}}} \newcommand{SG}[2]{{S_{GF(#1,\ #2)}}} % \newcommand{pgf}{{p_{GF}}} \newcommand{pils}{{p_{ILS}}} % \newcommand{ABC}{{\scriptsize \text{ABC}}} \newcommand{ACB}{{\scriptsize \text{ACB}}} \newcommand{BCA}{{\scriptsize \text{BCA}}} \newcommand{CAB}{{\scriptsize \text{CAB}}} \newcommand{CBA}{{\scriptsize \text{CBA}}} % % Expected branches lengths. \newcommand{exb}[3]{{\bar{#1}({#2},\ {#3})}} \newcommand{exu}[1]{{\bar{u}({#1})}} \newcommand{exv}[1]{{\bar{v}({#1})}} \begin{align} 𝜎 &↦ \begin{cases} ℝ &→ (0, 1) \\ x &↦ \frac{1}{1 + e^{-x}} \end{cases} \\ \rev &↦ \begin{cases} (0, 1) &→ ℝ \\ y &↦ \ln{\left(\frac{y}{1 - y}\right)} \end{cases} \end{align} \]
Considering that the following scores live in ℝ with no constraint:
\[ \begin{align} s_𝜃 &= \ln(𝜃) \\ s_{𝜏_1} &= \ln(𝜏_1) \\ s_{𝛥𝜏} &= \ln(𝜏_2 - 𝜏_1) \\ s_{gf} &= \rev(\pab + \pac + \pbc) \\ s_{ac} &= \ln\left(\pac × \frac{e^1}{\pab}\right) \\ s_{bc} &= \ln\left(\pbc × \frac{e^1}{\pab}\right) \\ s_\po &= \rev(\po) \\ s_{t_i} &= \begin{cases} i = 1 &\implies \rev(\gt{1}) \\ i > 1 &\implies \rev\left(\frac{\gt{i}}{\gt{i-1}}\right) \end{cases} \end{align} \]
then here is the reparametrization formula:
\[ \begin{align} 𝜃 &= e^{s_ 𝜃} \\ 𝜏_1 &= e^{s_{𝜏_1}} \\ 𝜏_2 &= 𝜏_1 + e^{s_{𝛥𝜏}} \\ \pab &= \frac{e^1}{e^1 + e^{s_{ac}} + e^{s_{bc}}} × 𝜎(s_{gf}) \\ \pac &= \frac{e^{s_{ac}}}{e^1 + e^{s_{ac}} + e^{s_{bc}}} × 𝜎(s_{gf}) \\ \pbc &= \frac{e^{s_{bc}}}{e^1 + e^{s_{ac}} + e^{s_{bc}}} × 𝜎(s_{gf}) \\ \po &= 𝜎(s_\po) \\ \gt{i} &= \begin{cases} i = 1 &\implies 𝜎(s_{t_1}) \\ i > 1 &\implies \gt{i-1} × 𝜎(s_{t_i}) \end{cases} \end{align} \]
Instead of the constrained \(𝜃\), \(𝜏_1\), \(𝜏_2\) etc., the concrete optimisation targets for aphid are the unconstrained \(s_𝜃\), \(s_{𝜏_1}\), \(s_{𝛥𝜏}\) etc.
Maximizing likelihood
Once reparametrized, the likelihood is viewed as a function \(f\) of the \(s\) scores \(X\):
\[ f:\left\{\begin{align} ℝ^s & → ℝ \\ X & ↦ f(X) = f(s_𝜃, s_{𝜏_1}, …) = -\ln(ℒ) \end{align}\right. \]
The logarithm is used to improve numerical stability of calculations. Maximizing the likelihood boils down to finding a value of \(X\) in \(ℝ^s\) that minimizes \(f\).
Aphid uses a heuristic approach to minimization commonly referred to as Broyden–Fletcher–Goldfarb–Shanno algorithm (BFGS) as described in Nocedal & Wright (2006). The method uses successive first-order derivatives of \(f\) to construct a running estimate of its second-order derivative and exploit the local curvature of the likelihood surface to greedily converge towards local minima.
This section specifies aphid's BFGS implementation detail and configuration.
Derivatives
Given a sample \(X_k\),
aphid uses the likelihood formula
to calculate \(f(X_k)\).
If required, it then uses automatic differentiation
to calculate the exact local gradient \(∇f(X_k)\).
Both steps are performed with tensors from the external C++ library pytorch,
bound to aphid via the Rust crate tch-rs.
This dependency to automatic differentiation may be transfered to candle in the future.
Parameters forcing
Any parameter forced by user erases and replaces the value calculated from the scores during likelihood calculation, in a way that makes the likelihood gradient null with respect to these parameters, and prevents automatic differentiation to flow upstream to the optimized scores.
The consequence is that both likelihood formula and the "scores → parameters" formulae are unchanged when forcing, and that all scores variables will always appear in the output even when some parameters have been force-fixed.
BFGS Update
Given an initial starting point \(X_0\), the procedure starts by initializing a running estimate of the function's inverse Hessian matrix to the identity matrix:
\[ H_0 = 𝕀_{s, s} \]
On every step \(k\), a search direction \(P\) is chosen according to:
\[ P_k = -H_k\,. ∇f(X_k) \]
Samples of the following form are taken along this search direction, for various "step size" values \(𝛼 \in ℝ^+\):
\[ X_\bullet = X_k + 𝛼_\bullet\,P_k \]
until a sample verifying the strict Wolfe conditions is found. That is, a step size \(𝛼_k\) such that:
\[ X_{k+1} = X_k + 𝛼_k\,P_k \]
\[ \left\{\begin{align} f(X_{k+1}) \leqslant f(X_k) + c_1 \, 𝛼 \, ∇f(x_k)^{\intercal} P_k \qquad &\text{(Armijo's rule: sufficient decrease)} \\ ∇f(X_{k+1})^{T} \, P_k \geqslant c_2 \, ∇f(x_k)^{\intercal} P_k \qquad &\text{(curvature condition)} \end{align}\right. \]
for some fixed, positive values \(c_1\) and \(c_2\).
These values can be chosen from
the configuration table [search.bfgs.wolfe]
with parameters c1 and c2,
and they default to Nocedal & Wright (2006) recommendation:
\[ \left\{\begin{align} c_1 &= 10^{-4} \\ c_2 &= 0.1 \end{align}\right. \]
The exact "linear search" procedure to find \(𝛼_k\) is specified in the next section.
Once \(X_{k+1}\) has been chosen, the inverse Hessian approximation is updated with:
\[ \begin{alignat}{2} S_k &= X_{k+1} - X_k = 𝛼_k\,P_k &&(\in ℝ^s)\\ 𝛥_k &= ∇f(X_{k+1}) - ∇f(X_k) && (\in ℝ^s) \\ 𝜌_k &= (𝛥_k^{\intercal}\,.S_k)^{-1} &&(\in ℝ) \\ H_{k+1} &= (𝕀 - 𝜌_k\,S_k\,𝛥_k^{\intercal}) \, H_k \, (𝕀 - 𝜌_k\,&&𝛥_k\,S_k^{\intercal}) - 𝜌_k\,S_k\,S_k^{\intercal} \end{alignat} \]
Exploration continues until a termination criterion, as described in the upcoming termination section, is reached.
Linear Search
Given a sample \(X_k\) and a search direction \(P_k\), "linear search" is the process of exploring various successive candidate step sizes \(𝛼\) until we find one satisfying the Wolfe conditions.
Again, aphid's linear search strategy
is taken from Nocedal & Wright (2006), section 3.5.
Its implementation, specified here, has been refined
to handle possible degenerated non-finite floating-point values
i.e. inf or NaN.
The linear search problem considers the one-dimensional function \(𝜑\) mapping every candidate step size to the value of its corresponding candidate sample:
\[ 𝜑:\left\{\begin{align} ℝ^+ & → ℝ \\ 𝛼 & ↦ 𝜑(𝛼) = f(X_k + 𝛼\,P_k) \end{align}\right. \]
Aphid assumes that all floating-point values of \(𝜑(𝛼)\) are finite between \(𝛼 = 0\) and some "horizon" value \(𝛼 = h > 0\). It also assumes that the range \(𝛼 ∈ [0, h]\) does contain acceptable step sizes meeting the strict Wolfe conditions. The goal of the algorithm is to find such a step size while estimating \(h\) if necessary and bisecting within \([0, h]\) if found.
The algorithm is specified as a flowchart below, (configuration options \((d, i, c)\) highlighted) and described in the next few sections.
1. Reduction phase
Starting from \(𝛼 = 1\), and if necessary, decrease the step size by a constant factor \(d\) until 𝜑(𝛼) becomes finite.
This factor can be chosen from the configuration table
[search.bfgs.wolfe] with parameter step_decrease.
If decreasing was necessary, record the lowest value of \(𝛼\) yielding non-finite \(𝜑(𝛼)\) as an upper-bound estimate \(\hat{h}\) of the horizon \(h\).
2. Bracketing phase
Increase \(𝛼\) by a constant factor \(i\) until either a satisfying candidate is found, or an interval within \([0, h]\) is proven to contain such a candidate.
This factor can be chosen from the configuration table
[search.bfgs.wolfe] with parameter step_increase.
If increasing yields non-finite values for \(𝜑(a)\), then bisect within \([0, \hat{h}]\) while updating \(\hat{h}\) instead.
3. Zoom phase
Once an interval \([u, v] \subset [0, h]\) has been proven to contain a satisfying candidate, use cubic interpolation to quickly shrink \([u, v]\) and converge towards \(𝛼_k\).
In the case of aphid at least, I observed that cubic interpolation hindered convergence when cubic candidates values for \(𝛼\) where too close from either \(u\) or \(v\). For this reason, aphid falls back to naive bisection steps within \([u, v]\) whenever the cubic interpolation step would yield such a value.
The threshold for bisection is expressed as a fraction \(c\)
of the interval length \(v - u\).
It can be chosen from the configuration table
[search.bfgs.wolfe] with parameter bisection_threshold.
Termination
The linear search terminates as a success when either of the following events occurs:
- A candidate satisfying Wolfe conditions is found.
- During the bracketting or the zooming phase, the binary search interval reduces below floating-point precision.
- During the zooming phase, a candidate \((𝜑(𝛼), 𝜑'(𝛼))\)
cannot be distinguished from \((𝜑(u), 𝜑'(u))\) or \((𝜑(v), 𝜑'(v))\)
within floating-point precision,
and \(𝜑'(𝛼)\) is lower than the threshold
defined in the configuration table
[search.bfgs.wolfe]by parameterflat_gradient.
It terminates as a failure when either of the following events occurs:
- No finite floating-point \(𝜑(𝛼)\) value is found during the reduction phase.
- A non-finite value for \(𝜑'(𝛼)\) is obtained whereas \(𝜑(𝛼)\) was finite.
- A non-finite value for \(𝜑(𝛼)\) is found within \([u, v]\).
The whole BFGS optimisation procedure terminates as a success when either of the following events occurs:
- The best step resulting from linear search is numerically null: \(𝛼_k = 0\).
- The corresponding step norm is numerically null: \(||S_k|| = 0\).
- There is no difference between \(∇f(X_{k+1})\) and \(∇f(X_k)\)
within floating-point precision, and the step norm is smaller
than the threshold defined in the configuration table
[search.bfgs]by parameterstep_size_threshold. - The mean slope over the last few
\((f(X_{k - i + 1}))_{i \in \{ 1, .., n \}}\) values
is lower than the threshold defined in the configuration table
[search.bfgs.slope_tracking]by parameterthreshold. The number of samples \(n\) is determined by parametersample_sizein the same table, and the slope is estimated everygrainsteps. - The maximum number of iterations is reached,
defined in table
[search.bfgs.max_iter].
And as a failure when:
- Either initial \(f(X_0)\) or \(∇f(X_0)\) yields non-finite floating-point results.
Exploration
BFGS is greedily attracted towards the closest local optimum.
To ensure exploration of the parameters space,
aphid issues one parallel BFGS search
per initial condition defined within the [init] table.
The result yielding the best likelihood among all searches
is the one kept for output,
even if it does not correspond to a terminal search step.
Outputs
When run, for instance with:
$ aphid ./config.toml output
Aphid displays summarized information about the analysis on the console standard output. This output is only meant as an informative summary for humans. It is not supposed to be easily processed by downstream programs and its layout can arbitrarily change in the future.
In addition,
aphid outputs very detailed structured information about the analysis
under the form of a collection of files in a newly created folder
named after its second argument (the output/ folder in the above example).
Here is what this folder contains:
<OUTPUT>/ # The folder name as per the second argument given on the command line.
├── config.json # Complete information about the configuration used for this run.
├── global.json # The most important "forest-level" results.
├── detail.json # Detailed results per gene tree.
├── trees.csv # Summarized results per gene tree in tabular form.
└── search/ # Traces of the heuristic searches for likelihood (one per starting point).
├── 1/
│ ├── init.csv # Starting point for this search.
│ ├── status.json # Result of the search (error or best parameters found).
│ ├── global.csv # Every BFGS step.
│ └── detail.csv # Every linear search step.
├── 2/ …
├── 3/ …
⋮
These files are formally structured with .json or .csv format
to ease their processing by downstream programs.
The meaning of their content is detailed below:
The global.json file
All user-facing information used or produced by aphid regarding the gene forest analyzed.
n_trees: The number of trees analyzed.n_excluded_triplets_topologies: Number of triplets rejected based on their topology (incomplete or paraphyletic).n_unresolved_triplets: Number of triplets considered unresolved.n_excluded_outgroup_topologies: Number of outgroup rejected based on their topology (empty or paraphyletic).n_excluded_topologies: Total number of trees excluded based on their topology.mean_branch_length: Mean branch length over the forest.mean_length_triplet: Mean length of a triplet, calculated after the topology filter and before the geometry filter.mean_length_outgroup_other: Mean length of non-triplet branches ('other' and 'outgroup') calculated after the topology filter and before the geometry filter.imbalance: Average imbalance between triplet branches lengths and the outgroup + other sections branches lengths. Only calculated if a maximum clock ratio is set.triplet_longer: True if the imbalance means that triplets are longer on average. False if the outgroup + other sections are longer on average.shape: Overall ratio over the genes forest.n_excluded_branch_counts: Number of trees excluded based on branch count.n_excluded_mutation_rates: Number of trees excluded based on their estimated mutation rate.n_excluded_imbalance: Number of trees excluded based on their imbalance.n_included_trees: Final number of trees kept for likelihood calculation.estimate: Best parameters values found to maximize likelihood.ln_likelihood: Best ln-likelihood value found.parameters: Corresponding parameters values.
The detail.json file
All user-facing information used or produced by aphid regarding each particular gene tree.
id: Tree identifier, as given in input.n_bases: Sequence length.n_nodes_raw: Number of nodes in the input tree.n_nodes_pruned: Number of nodes after only species of interest have been kept.triplet: Status of the focal triplet in this tree.lca: LCA(triplet): the most recent ancestor of the focal triplet species found in this tree. Undefined if all triplet species were missing.missing: Triplet species not found within this tree.paraphyletic: Paraphyletic species found within this tree: these descend from LCA(triplet) but don't belong to the focal triplet.analysis: Further information calculated iif the triplet is complete and monophyletic.topology: Topology within this tree, assuming the reference topology was 'ABC' ~ '((A, B), C)'.branches_counts: Mutations count estimate along the triplet branches[a, b, c, d]. The internal branch countdeither represent:abin((:a, :b):ab, :c)for topologyABC.acin(:b, (:a, :c):ac)for topologyACB.bcin(:a, (:c, :b):bc)for topologyBCA.
resolved: True if the topology is considered sufficiently resolved to exclude discordant scenarios from likelihood calculations.
included: Raise unless the tree should be excluded from analysis based on this triplet topology.
outgroup: Status of the designated outgroup in this tree.lca: LCA(outgroup): the ost recent ancestor of the designated outgroup species found in this tree. Undefined if all outgroup species were missing.missing: Outgroup species not found within this tree.paraphyletic: Paraphyletic species found within this tree: these descend from LCA(outgroup) but don't belong to the designated outgroup.included: Raise unless the tree should be excluded from analysis based on this outgroup topology.
top: Status of this tree's most ancestral nodes. Undefined if either no triplet species or no outgroup species were found.lca: LCA(top): the most recent ancestor of LCA(triplet) and LCA(outgroup) found in this tree.internal: Species descending from LCA(top), but neither from LCA(triplet) or LCA(outgroup). Only defined if there is no direct lineage between LCA(triplet) and LCA(outgroup).triplet: Species branching between LCA(top) and LCA(triplet).outgroup: Species branching between LCA(top) and LCA(outgroup).
external: Species not descending from LCA(top). If any, then LCA(top) is not the root of the tree.included: Raise unless the tree should be excluded from analysis based on this tree top topology.
topology_included: Raised if the tree passed the topology filter.mean_lengths: Mean branches lengths, undefined if the none of the species set they are supposed to be calculated over is found within the tree.total: Calculated over all species of interest found in this tree.triplet: Calculated over the focal triplet species found.outgroup_other: Calculated over the designated outgroup species found plus the species designated as 'other'.
local_shape: "Absolute" ratio of mean triplet length and mean length of designated 'outgroup' and 'other' species: always superior to 1 to measure 'imbalance', regardless which of the numerator or denominator is greater. (Latest estimate if the tree was excluded from analysis)branch_count_included: Raised if the tree passed this geometry filter.mutation_rate_included: Raised if the tree passed this statistical geometry filter.balance_included: Raised if the tree passed this statistical geometry filter.imbalance: Estimated imbalance for this tree. (Latest estimate if the tree was excluded from analysis)mutation_rate: Estimated mutation rate for this tree. (Latest estimate if the tree was excluded from analysis)ln_likelihood: Estimated likelihood of this single tree, provided it was included in the analysis. If no heuristic could start, this will instead contain initial likelihood value for every starting point, hopefully useful for understanding whether some trees were problematic.
The summarized trees.csv table
There is one line in this table per gene tree analyzed.
Columns represent a redundant, flattened version
of the structured information available in the above detail.json file,
but we expect that it be easier to work with
using table-processing downstream software.
The search/ traces
Since several starting point may be used in the likelihood maximization heuristics, aphid may produce several exploration traces stored within subfolders here.
The init.json file.
This file is a reminder which initial parameters have been used for the search.
The status.json summary.
This file summarizes the terminal status of the search: either detail about search failure if it failed, or the following information:
parameters: The best parameters found during this search.scores: The unconstrained scores corresponding to these parameters.gradient: The corresponding gradient values for the scores at this point.ln_likelihood: The best likelihood value found.n_evaluations: The number of times the likelihood function has been evaluated to find this result.n_differentiations: The number of times the likelihood derivative has been evaluated to find this result.
The global.csv trace.
If produced, there is one line in this table per BFGS step taken during the search. (see BFGS)
The detail.csv trace.
If produced, there is one line in this table per linear search step taken for every BFGS step in search for a step size meeting strong Wolfe criteria. (see Linear Search)