Source code for phylozoo.core.split.classifications

"""
Split system classifications module.

This module provides functions for classifying split systems, such as checking
pairwise compatibility.
"""

import itertools
from typing import TYPE_CHECKING, TypeVar

from ...utils.exceptions import PhyloZooValueError
from .base import Split
from .splitsystem import SplitSystem

T = TypeVar('T')

if TYPE_CHECKING:
    pass


[docs] def is_compatible(split1: Split, split2: Split) -> bool: """ Check if two splits are compatible. Two splits are compatible if they have the same set of elements, and one of the sets of one split is a subset of one of the sets of the other split (and hence the other set is a superset). Parameters ---------- split1 : Split[T] The first split to check. split2 : Split[T] The second split to check. Returns ------- bool True if the splits are compatible, False otherwise. Raises ------ PhyloZooValueError If either argument is not a Split instance. Examples -------- >>> split1 = Split({1, 2}, {3, 4}) >>> split2 = Split({1}, {2, 3, 4}) >>> is_compatible(split1, split2) True >>> split3 = Split({1, 2, 3}, {4}) >>> is_compatible(split1, split3) True >>> split4 = Split({1, 3}, {2, 4}) >>> is_compatible(split1, split4) False """ if not isinstance(split1, Split): raise PhyloZooValueError("First argument must be a Split instance") if not isinstance(split2, Split): raise PhyloZooValueError("Second argument must be a Split instance") # First check that they have the same elements if split1.elements != split2.elements: return False # Check all 4 compatibility cases with early returns # Case 1: split1.set1 is subset of split2.set1, so split1.set2 is superset of split2.set2 if split1.set1.issubset(split2.set1) and split2.set2.issubset(split1.set2): return True # Case 2: split1.set1 is subset of split2.set2, so split1.set2 is superset of split2.set1 if split1.set1.issubset(split2.set2) and split2.set1.issubset(split1.set2): return True # Case 3: split2.set1 is subset of split1.set1, so split2.set2 is superset of split1.set2 if split2.set1.issubset(split1.set1) and split1.set2.issubset(split2.set2): return True # Case 4: split2.set1 is subset of split1.set2, so split2.set2 is superset of split1.set1 if split2.set1.issubset(split1.set2) and split1.set1.issubset(split2.set2): return True return False
[docs] def is_subsplit(split1: Split, split2: Split) -> bool: """ Check if one split is a subsplit of another split. A split is a subsplit of another if one of its sides is a subset of one side of the other split, and the other side of this split is a subset of the other side of the other split. For example, 12|56 is a subsplit of 123|456. Parameters ---------- split1 : Split[T] The split to check if it is a subsplit. split2 : Split[T] The split to check against. Returns ------- bool True if split1 is a subsplit of split2, False otherwise. Raises ------ PhyloZooValueError If either argument is not a Split instance. Examples -------- >>> split1 = Split({1, 2, 6}, {3, 4, 5}) >>> split2 = Split({1, 2}, {3, 4}) >>> is_subsplit(split2, split1) True >>> split3 = Split({1, 3}, {2, 4}) >>> is_subsplit(split3, split1) False """ if not isinstance(split1, Split): raise PhyloZooValueError("First argument must be a Split instance") if not isinstance(split2, Split): raise PhyloZooValueError("Second argument must be a Split instance") # Check subsplit condition # split1 is a subsplit of split2 if each side of split1 is a subset of # one of the sides of split2. return ( (split1.set1.issubset(split2.set1) and split1.set2.issubset(split2.set2)) or (split1.set1.issubset(split2.set2) and split1.set2.issubset(split2.set1)) )
[docs] def is_pairwise_compatible(system: SplitSystem) -> bool: """ Check if all pairs of splits in the system are compatible. A split system is pairwise compatible if every pair of splits in the system is compatible with each other. Parameters ---------- system : SplitSystem The split system to check. Returns ------- bool True if all pairs of splits are compatible, False otherwise. Examples -------- >>> from phylozoo.core.split import Split, SplitSystem >>> split1 = Split({1, 2}, {3, 4}) >>> split2 = Split({1}, {2, 3, 4}) >>> split3 = Split({1, 2, 3}, {4}) >>> system = SplitSystem([split1, split2, split3]) >>> is_pairwise_compatible(system) True >>> split4 = Split({1, 3}, {2, 4}) >>> system2 = SplitSystem([split1, split4]) >>> is_pairwise_compatible(system2) False """ # Use itertools.combinations to check all pairs for split1, split2 in itertools.combinations(system, 2): if not is_compatible(split1, split2): return False return True
[docs] def has_all_trivial_splits(system: SplitSystem) -> bool: """ Check if the split system contains all trivial splits. For a split system with n elements, there should be n trivial splits, where each trivial split has one element in one set and all other n-1 elements in the other set. Parameters ---------- system : SplitSystem The split system to check. Returns ------- bool True if all trivial splits are present, False otherwise. Examples -------- >>> from phylozoo.core.split import Split, SplitSystem >>> # System with 3 elements should have 3 trivial splits >>> split1 = Split({1}, {2, 3}) >>> split2 = Split({2}, {1, 3}) >>> split3 = Split({3}, {1, 2}) >>> system = SplitSystem([split1, split2, split3]) >>> has_all_trivial_splits(system) True >>> # Missing one trivial split >>> system2 = SplitSystem([split1, split2]) >>> has_all_trivial_splits(system2) False """ if len(system.elements) == 0: return True # Empty system has all trivial splits (trivially) # Count trivial splits in the system trivial_count = sum(1 for split in system if split.is_trivial) # For n elements, there should be exactly n trivial splits return trivial_count == len(system.elements)
[docs] def is_tree_compatible(system: SplitSystem) -> bool: """ Check if a split system is compatible with a tree. A split system is tree-compatible if: 1. All pairs of splits are compatible (pairwise compatible) 2. All trivial splits are present in the system Parameters ---------- system : SplitSystem The split system to check. Returns ------- bool True if the system is compatible with a tree, False otherwise. Examples -------- >>> from phylozoo.core.split import Split, SplitSystem >>> # Tree-compatible system >>> split1 = Split({1}, {2, 3, 4}) >>> split2 = Split({2}, {1, 3, 4}) >>> split3 = Split({3}, {1, 2, 4}) >>> split4 = Split({4}, {1, 2, 3}) >>> split5 = Split({1, 2}, {3, 4}) >>> system = SplitSystem([split1, split2, split3, split4, split5]) >>> is_tree_compatible(system) True >>> # Incompatible system (splits conflict) >>> split6 = Split({1, 3}, {2, 4}) >>> system2 = SplitSystem([split1, split2, split3, split4, split5, split6]) >>> is_tree_compatible(system2) False """ return is_pairwise_compatible(system) and has_all_trivial_splits(system)