quartet#
Quartet module.
This module provides classes for representing and working with quartets, which are unrooted trees on 4 taxa. A quartet can either be resolved (with a single non-trivial split) or unresolved (a star tree). The public API (Quartet, QuartetProfile, QuartetProfileSet) is re-exported here; the implementation is split across the base, qprofile, qprofileset, and qdistance submodules.
Main Classes#
Quartet base module.
This module provides the Quartet class for representing quartets (4-taxon unrooted trees). A quartet can be resolved (single non-trivial split) or unresolved (star tree).
- class phylozoo.core.quartet.base.Quartet(split: Split | frozenset[str] | set[str])[source]#
Bases:
objectImmutable quartet datatype representing an unrooted tree on 4 taxa.
A quartet can either have a single non-trivial split (2|2 split) representing a resolved tree, or be a star tree (represented by a set of 4 taxa) where all taxa are equivalent.
- Parameters:
split (Split | frozenset[str] | set[str]) – Either a 2|2 split on 4 taxa, or a set/frozenset of exactly 4 taxon labels for a star tree.
- Raises:
PhyloZooValueError – If not exactly 4 taxa, or if split is trivial (not 2|2).
Examples
>>> from phylozoo.core.split.base import Split >>> quartet = Quartet(Split({1, 2}, {3, 4})) >>> quartet.taxa frozenset({1, 2, 3, 4}) >>> quartet.is_resolved() True >>> star_quartet = Quartet({1, 2, 3, 4}) >>> star_quartet.is_star() True
- __repr__() str[source]#
Return string representation of the quartet.
- Returns:
String representation.
- Return type:
- __setattr__(name: str, value: any) None[source]#
Prevent modification of attributes after initialization.
- Raises:
AttributeError – If attempting to modify any attribute after initialization.
- __str__() str[source]#
Return human-readable string representation of the quartet.
For resolved quartets, displays as “Quartet(a b | c d)”. For unresolved (star) quartets, displays as “Quartet(a b c d)”.
- Returns:
Human-readable string representation.
- Return type:
Examples
>>> from phylozoo.core.split.base import Split >>> quartet = Quartet(Split({1, 2}, {3, 4})) >>> str(quartet) 'Quartet(1 2 | 3 4)' >>> star_quartet = Quartet({1, 2, 3, 4}) >>> str(star_quartet) 'Quartet(1 2 3 4)'
- property circular_orderings: frozenset[CircularOrdering]#
Get circular orderings that are congruent with this quartet.
For a resolved quartet with split {a,b}|{c,d}, returns the two circular orderings where a and b are neighbors and c and d are neighbors: abcd and abdc.
For a star tree (unresolved quartet), returns all three circular orderings: abcd, acbd, and adbc.
The result is cached after first computation.
- Returns:
Set of circular orderings congruent with this quartet.
- Return type:
- copy() Quartet[source]#
Create a copy of the quartet.
- Returns:
A new Quartet instance with the same taxa and split.
- Return type:
- is_resolved() bool[source]#
Check if the quartet is resolved (has a non-trivial split).
- Returns:
True if the quartet has a split (is resolved), False if it’s a star tree.
- Return type:
- is_star() bool[source]#
Check if the quartet is a star tree (no split).
- Returns:
True if the quartet is a star tree, False if it’s resolved.
- Return type:
- property split: Split | None#
Get the split of the quartet.
- Returns:
The split representing the topology, or None for star tree.
- Return type:
Split | None
- to_network() SemiDirectedPhyNetwork[source]#
Convert the quartet to a SemiDirectedPhyNetwork.
For a resolved quartet, creates a tree with two internal nodes: one connecting the two taxa on each side of the split, and these internal nodes are connected. For a star tree, creates a tree with all four taxa connected to a single internal node.
- Returns:
A semi-directed phylogenetic network representing the quartet topology.
- Return type:
Quartet profile module.
A quartet profile groups multiple quartets on the same 4-taxon set, each with an associated weight representing the relative importance or frequency of each quartet topology. This module provides the QuartetProfile class; total weight is always 1.0.
- class phylozoo.core.quartet.qprofile.QuartetProfile(quartets: dict[Quartet, float] | Mapping[Quartet, float] | list[Quartet] | list[tuple[Quartet, float]])[source]#
Bases:
objectImmutable profile for quartets on the same 4-taxon set.
A QuartetProfile groups multiple quartets that share the same 4 taxa, each with an associated weight. The weights always sum to 1.0 (within a small tolerance), so the profile represents a probability distribution over quartet topologies.
If no weights are provided (list of quartets), each quartet is assigned equal weight 1/k, where k is the number of quartets.
If weights are provided (dict or list of (quartet, weight) tuples), they must sum to 1.0 (within tolerance); they are not scaled.
- Parameters:
quartets (dict[Quartet, float] | Mapping[Quartet, float] | list[Quartet] | list[tuple[Quartet, float]]) –
Input quartets. Can be:
A dictionary mapping quartets to weights (must sum to 1.0)
A list of quartets (each assigned weight 1/k)
A list of (quartet, weight) tuples (weights must sum to 1.0)
Taxa are automatically extracted from the quartets.
- Raises:
PhyloZooValueError – If quartets is empty, if quartets have different taxa, if any weight is non-positive, or if provided weights do not sum to 1.0.
Examples
>>> from phylozoo.core.split.base import Split >>> q1 = Quartet(Split({1, 2}, {3, 4})) >>> q2 = Quartet(Split({1, 3}, {2, 4}))
>>> # From dictionary with weights (already sum to 1.0) >>> profile = QuartetProfile({q1: 0.8, q2: 0.2}) >>> profile.taxa frozenset({1, 2, 3, 4}) >>> profile.get_weight(q1) 0.8
>>> # From list of quartets (equal weight 1/k each) >>> profile2 = QuartetProfile([q1, q2]) >>> profile2.get_weight(q1) 0.5 >>> profile2.get_weight(q2) 0.5
- __iter__() Iterator[Quartet][source]#
Return an iterator over the quartets.
- Returns:
Iterator over quartets.
- Return type:
Iterator[Quartet]
- __len__() int[source]#
Return the number of quartets in the profile.
- Returns:
Number of quartets.
- Return type:
- __repr__() str[source]#
Return string representation of the profile.
- Returns:
String representation.
- Return type:
- __setattr__(name: str, value: any) None[source]#
Prevent modification of attributes after initialization.
- Raises:
AttributeError – If attempting to modify any attribute after initialization.
- __str__() str[source]#
Return human-readable string representation of the quartet profile.
Displays one line per quartet, showing the quartet (using its __str__ method) and its weight.
- Returns:
Human-readable string representation.
- Return type:
Examples
>>> from phylozoo.core.split.base import Split >>> q1 = Quartet(Split({1, 2}, {3, 4})) >>> q2 = Quartet(Split({1, 3}, {2, 4})) >>> profile = QuartetProfile({q1: 0.8, q2: 0.2}) >>> str(profile) 'QuartetProfile({\n Quartet(1 2 | 3 4): 0.8,\n Quartet(1 3 | 2 4): 0.2\n})'
- property circular_orderings: frozenset[CircularOrdering] | None#
Get circular orderings that are congruent with every quartet in the profile.
The result is the intersection of all circular orderings of the quartets. Since star trees are congruent with all orderings, only resolved quartets constrain the result.
The result is cached after first computation.
- Returns:
Set of circular orderings congruent with all quartets, or None if no such ordering exists (e.g., if all three possible resolved quartets are in the profile).
- Return type:
frozenset[CircularOrdering] | None
- is_resolved() bool[source]#
Check if the profile is resolved.
A profile is resolved if all its quartets are resolved. A quartet is resolved if it has a non-trivial split (i.e., it’s not a star tree).
- Returns:
True if all quartets are resolved (profile is resolved), False otherwise.
- Return type:
Examples
>>> from phylozoo.core.split.base import Split >>> q1 = Quartet(Split({1, 2}, {3, 4})) >>> q2 = Quartet(Split({1, 3}, {2, 4})) >>> profile = QuartetProfile([q1, q2]) >>> profile.is_resolved() True >>> q3 = Quartet({1, 2, 3, 4}) # Star tree >>> profile2 = QuartetProfile([q1, q3]) >>> profile2.is_resolved() False
- is_trivial() bool[source]#
Check if the profile is trivial (contains exactly one quartet).
A trivial profile is essentially a single quartet, meaning it represents a single topology rather than a distribution over multiple topologies.
- Returns:
True if the profile contains exactly one quartet, False otherwise.
- Return type:
Examples
>>> from phylozoo.core.split.base import Split >>> q1 = Quartet(Split({1, 2}, {3, 4})) >>> profile = QuartetProfile([q1]) >>> profile.is_trivial() True >>> q2 = Quartet(Split({1, 3}, {2, 4})) >>> profile2 = QuartetProfile({q1: 0.8, q2: 0.2}) >>> profile2.is_trivial() False
- property split: Split | None#
Get the split if the profile has a single quartet.
Returns the split of the quartet if the profile contains exactly one quartet. If that quartet is a star tree, returns None. If the profile has multiple quartets, returns None (since there would be multiple splits).
The result is cached after first computation.
- Returns:
The split of the single quartet, or None if multiple quartets or star tree.
- Return type:
Split | None
Quartet profile set module.
A quartet profile set is a collection of quartet profiles covering multiple four-taxon sets. This module provides the QuartetProfileSet class.
- class phylozoo.core.quartet.qprofileset.QuartetProfileSet(profiles: list[QuartetProfile | Quartet | tuple[QuartetProfile, float]] | None = None, taxa: frozenset[str] | None = None)[source]#
Bases:
objectImmutable collection of quartet profiles with two-level weights.
A QuartetProfileSet groups quartets by their 4-taxon sets into profiles. Each profile has a weight (profile weight), and each quartet within a profile also has a weight (quartet weight).
This allows representing uncertainty or multiple hypotheses about quartet topologies for the same set of 4 taxa, with different weights assigned to each hypothesis.
- Parameters:
profiles (list[QuartetProfile | Quartet | tuple[QuartetProfile, float]] | None, optional) –
List of QuartetProfile objects, Quartet objects, or tuples with profile weights.
If QuartetProfile: used directly (optionally with a profile-weight tuple).
If Quartet: automatically grouped by taxa into profiles. For each 4-taxon set, all quartets on that set are collected into a
QuartetProfilewith equal weights \(1/k\) (where \(k\) is the number of quartets for that taxa set). Each resulting profile in the set receives default profile weight 1.0.If tuple: (profile, weight) where weight is the profile weight.
Passing quartets together with explicit weights (e.g.
(Quartet, weight)) is not supported. To use non-uniform quartet weights within a profile, construct aQuartetProfileexplicitly and pass that (optionally with a profile weight).By default None.
taxa (frozenset[str] | None, optional) – Total set of taxa. If provided, must be a superset of all taxa in the profiles. Allows specifying taxa for which no profile exists. By default None (computed from profiles).
- Raises:
PhyloZooValueError – If any profile would be empty, if any weight is non-positive, if profiles/quartets are mixed incorrectly, or if provided taxa is not a superset of profile taxa.
Examples
>>> from phylozoo.core.split.base import Split >>> q1 = Quartet(Split({1, 2}, {3, 4})) >>> q2 = Quartet(Split({1, 3}, {2, 4})) >>> q3 = Quartet(Split({5, 6}, {7, 8}))
>>> # From quartets (grouped into profiles, equal weights per profile) >>> profileset = QuartetProfileSet(profiles=[q1, q2, q3]) >>> len(profileset) 2 >>> profileset.get_profile_weight(frozenset({1, 2, 3, 4})) 1.0
>>> # From QuartetProfile objects (better control) >>> profile1 = QuartetProfile({q1: 0.8, q2: 0.2}) >>> profile2 = QuartetProfile([q3]) >>> profileset2 = QuartetProfileSet(profiles=[(profile1, 2.0), (profile2, 1.5)]) >>> profileset2.get_profile_weight(frozenset({1, 2, 3, 4})) 2.0
- __contains__(taxa: frozenset[str]) bool[source]#
Check if a profile exists for the given 4-taxon set.
- __iter__() Iterator[tuple[QuartetProfile, float]][source]#
Return an iterator over (profile, profile_weight) pairs.
- Returns:
Iterator over (profile, weight) tuples.
- Return type:
Iterator[tuple[QuartetProfile, float]]
- __repr__() str[source]#
Return string representation of the profile set that can be used to initialize it.
- Returns:
String representation that can be used to recreate the object.
- Return type:
- __str__() str[source]#
Return human-readable string representation of the quartet profile set.
Displays one line per profile, showing the profile (using its __str__ method) and its profile weight. Aligns with QuartetProfile’s __str__ format.
- Returns:
Human-readable string representation.
- Return type:
Examples
>>> from phylozoo.core.split.base import Split >>> q1 = Quartet(Split({1, 2}, {3, 4})) >>> q2 = Quartet(Split({1, 3}, {2, 4})) >>> profile1 = QuartetProfile({q1: 0.8, q2: 0.2}) >>> profileset = QuartetProfileSet(profiles=[(profile1, 1.0)]) >>> str(profileset) 'QuartetProfileSet({\n QuartetProfile({...}) [weight: 1.0]\n})'
- get_profile(taxa: frozenset[str]) QuartetProfile | None[source]#
Get the profile for a 4-taxon set.
- Parameters:
- Returns:
The profile for the taxa, or None if not found.
- Return type:
QuartetProfile | None
- get_profile_weight(taxa: frozenset[str]) float | None[source]#
Get the profile weight for a 4-taxon set.
- has_profile(taxa: frozenset[str]) bool[source]#
Check if a profile exists for the given 4-taxon set.
- property is_all_resolved: bool#
Check if all profiles in the set are resolved.
A profile is resolved if all its quartets are resolved (i.e., not star trees). This property returns True only if every profile in the set is resolved.
- Returns:
True if all profiles are resolved, False otherwise.
- Return type:
Examples
>>> from phylozoo.core.split.base import Split >>> from phylozoo.core.quartet.base import Quartet >>> q1 = Quartet(Split({1, 2}, {3, 4})) >>> q2 = Quartet(Split({1, 3}, {2, 4})) >>> profileset = QuartetProfileSet(profiles=[q1, q2]) >>> profileset.is_all_resolved True >>> star = Quartet({1, 2, 3, 4}) >>> profileset2 = QuartetProfileSet(profiles=[q1, star]) >>> profileset2.is_all_resolved False
- property is_dense: bool#
Check if the quartet profile set is dense.
A dense quartet profile set has a profile for every possible combination of 4 taxa from the total set of taxa.
- Returns:
True if the profile set is dense (has C(n, 4) profiles where n is the number of taxa), False otherwise.
- Return type:
- property max_profile_len: int#
Get the maximum number of quartets in any profile.
- Returns:
The largest number of quartets in any profile in the set. Returns 0 if the set is empty.
- Return type:
Examples
>>> from phylozoo.core.split.base import Split >>> from phylozoo.core.quartet.base import Quartet >>> q1 = Quartet(Split({1, 2}, {3, 4})) >>> q2 = Quartet(Split({1, 3}, {2, 4})) >>> q3 = Quartet(Split({1, 4}, {2, 3})) >>> profileset = QuartetProfileSet(profiles=[q1, q2, q3]) >>> profileset.max_profile_len 3
Distance Computation#
Quartet distance module.
This module provides functions for computing distance matrices from quartet profiles using quartet distance metrics.
- phylozoo.core.quartet.qdistance.quartet_distance(profileset: QuartetProfileSet, rho: tuple[float, float, float, float]) DistanceMatrix[source]#
Compute a distance matrix between taxa based on quartet profiles.
This function computes pairwise distances between taxa by aggregating contributions from all quartet profiles. The distance is based on the quartet topology and uses a rho vector to weight different quartet types.
- Parameters:
profileset (QuartetProfileSet) – The quartet profile set to compute distances from. Must be dense. Each profile must contain exactly 1 or 2 resolved quartets.
rho (tuple[float, float, float, float]) – Rho vector (rho_c, rho_s, rho_a, rho_o) specifying distance contributions for different quartet topologies. rho_c: contribution when two leaves are on the same side of a split (used for profiles with 1 quartet). rho_s: contribution when two leaves are on different sides of a split (used for profiles with 1 quartet). rho_a: contribution when two leaves are adjacent in a circular ordering (used for profiles with 2 quartets). rho_o: contribution when two leaves are opposite in a circular ordering (used for profiles with 2 quartets). For each quartet profile containing a pair of leaves, 2*rho_xy is added to the distance between leaves x and y, where rho_xy is the appropriate rho value based on the profile type and leaf positions.
- Returns:
A distance matrix with pairwise distances between taxa. The matrix is symmetric with zero diagonal.
- Return type:
- Raises:
ValueError – If rho vector has invalid length or values. If the profile set is not dense. If any profile contains more than 2 quartets or contains unresolved quartets.
Examples
>>> from phylozoo.core.split.base import Split >>> from phylozoo.core.quartet.base import Quartet >>> from phylozoo.core.quartet.qprofileset import QuartetProfileSet
>>> # Create profile set and compute distance >>> q1 = Quartet(Split({'A', 'B'}, {'C', 'D'})) >>> q2 = Quartet(Split({'A', 'C'}, {'B', 'D'})) >>> profileset = QuartetProfileSet(profiles=[q1, q2]) >>> dist_matrix = quartet_distance(profileset, rho=(0.5, 1.0, 0.5, 1.0))
>>> # Access distances >>> dist_matrix.get_distance('A', 'B') 5.0
Notes
See the manual for more details.
- phylozoo.core.quartet.qdistance.quartet_distance_with_partition(profileset: QuartetProfileSet, partition: Partition, rho: tuple[float, float, float, float] = (0.5, 1.0, 0.5, 1.0)) DistanceMatrix[source]#
Compute a distance matrix between partition sets based on quartet profiles.
This function computes pairwise distances between sets in a partition by aggregating contributions from quartet profiles. For each 4-subpartition, it considers all quartets with one leaf from each set and aggregates their contributions.
- Parameters:
profileset (QuartetProfileSet) – The quartet profile set to compute distances from. Must be dense. Each profile must contain exactly 1 or 2 resolved quartets.
partition (Partition) – A partition of the taxa. The distance matrix will be computed between the sets (parts) of this partition.
rho (tuple[float, float, float, float], optional) – Rho vector (rho_c, rho_s, rho_a, rho_o) specifying distance contributions. By default (0.5, 1.0, 0.5, 1.0) (Squirrel/MONAD).
- Returns:
A distance matrix with pairwise distances between partition sets. The matrix is symmetric with zero diagonal. Labels are the partition sets (frozensets).
- Return type:
- Raises:
PhyloZooValueError – If rho vector has invalid length or values. If partition elements don’t match profile set taxa. If profile set is not dense. If any profile contains more than 2 quartets or contains unresolved quartets.
Examples
>>> from phylozoo.core.split.base import Split >>> from phylozoo.core.quartet.base import Quartet >>> from phylozoo.core.quartet.qprofileset import QuartetProfileSet >>> from phylozoo.core.primitives.partition import Partition
>>> # Create profile set and partition >>> q1 = Quartet(Split({'A', 'B'}, {'C', 'D'})) >>> profileset = QuartetProfileSet(profiles=[q1]) >>> partition = Partition([{'A'}, {'B'}, {'C'}, {'D'}])
>>> # Compute distance matrix >>> dist_matrix = quartet_distance_with_partition(profileset, partition) >>> len(dist_matrix) 4
Notes
See the manual for more details.