|
| 1 | +# Algorithm Refinements: TSW and PN |
| 2 | + |
| 3 | +## Summary |
| 4 | + |
| 5 | +This document describes the refinements made to the Tight Span Walker (TSW) and Parsimony Network (PN) algorithms to reduce the excess creation of intermediate nodes and improve network accuracy. |
| 6 | + |
| 7 | +## Problem Statement |
| 8 | + |
| 9 | +### TSW Issues |
| 10 | +- The original Python implementation was creating an excess of intermediate (median) nodes |
| 11 | +- Simplified geodesic path computation didn't match the C++ reference implementation |
| 12 | +- Missing pruning logic for unnecessary median vertices |
| 13 | + |
| 14 | +### PN Issues |
| 15 | +- The algorithm was automatically subdividing all multi-mutation edges into single-mutation steps |
| 16 | +- This created many unnecessary intermediate nodes where edges should be direct |
| 17 | +- Deviated from the C++ reference implementation which relies on consensus sampling |
| 18 | + |
| 19 | +## Solutions Implemented |
| 20 | + |
| 21 | +### TSW Refinements |
| 22 | + |
| 23 | +#### 1. Proper Geodesic Computation |
| 24 | +The geodesic path algorithm now follows the C++ reference implementation: |
| 25 | + |
| 26 | +- **Bipartite Coloring**: Vertices are classified as "green" (closer to source) or "red" (closer to target) based on their dT distances |
| 27 | +- **Delta Calculation**: Computes the splitting parameter delta to determine if an intermediate vertex is needed |
| 28 | +- **Vertex Map**: Tracks dT vectors to avoid creating duplicate vertices with the same metric properties |
| 29 | +- **Recursive Construction**: Properly creates intermediate vertices only when delta < dT(f,g) |
| 30 | + |
| 31 | +**Key Code Changes:** |
| 32 | +```python |
| 33 | +# Old approach - overly simplistic |
| 34 | +if actual_dist - dt_dist > self.epsilon: |
| 35 | + median_hap = self._create_median_vertex(hap1, hap2) |
| 36 | + # Always created medians when distances didn't match |
| 37 | + |
| 38 | +# New approach - proper geodesic computation |
| 39 | +# 1. Compute bipartite coloring |
| 40 | +for i in range(n): |
| 41 | + if abs(fi + dt_fg + gi - distance_matrix.matrix[i, idx2]) < self.epsilon: |
| 42 | + green_vertices.add(i) |
| 43 | + elif abs(fi + dt_fg - gi) < self.epsilon: |
| 44 | + red_vertices.add(i) |
| 45 | + |
| 46 | +# 2. Compute delta (splitting parameter) |
| 47 | +delta = min([(fi + fj - dij) for i, j in green_pairs]) / 2.0 |
| 48 | + |
| 49 | +# 3. Only create vertex if needed |
| 50 | +if dt_fg > delta + self.epsilon: |
| 51 | + # Create intermediate vertex and recurse |
| 52 | +``` |
| 53 | + |
| 54 | +#### 2. Median Vertex Pruning |
| 55 | +Added post-processing to remove medians that don't serve a topological purpose: |
| 56 | + |
| 57 | +**Pruning Criteria:** |
| 58 | +A median vertex is kept only if it: |
| 59 | +1. Connects two or more observed (non-median) vertices (bridges observed nodes), OR |
| 60 | +2. Connects to at least one observed vertex and one or more medians (on path between observed), OR |
| 61 | +3. Connects to two or more medians (internal to a path) |
| 62 | + |
| 63 | +**Benefits:** |
| 64 | +- Eliminates isolated or non-bridging intermediate nodes |
| 65 | +- Produces cleaner, more interpretable networks |
| 66 | +- Maintains connectivity while removing redundancy |
| 67 | + |
| 68 | +### PN Refinements |
| 69 | + |
| 70 | +#### 1. Removed Automatic Edge Subdivision |
| 71 | +The key change was to stop automatically subdividing multi-mutation edges: |
| 72 | + |
| 73 | +**Old Behavior:** |
| 74 | +```python |
| 75 | +# Add edge if not already present |
| 76 | +if not network.has_edge(id1, id2): |
| 77 | + network.add_edge(id1, id2, distance=distance) |
| 78 | + |
| 79 | + # Automatically subdivide edges > 1 mutation |
| 80 | + if distance > 1: |
| 81 | + self._add_median_vertices_along_edge( |
| 82 | + network, id1, id2, seq1, seq2, int(distance) |
| 83 | + ) |
| 84 | +``` |
| 85 | + |
| 86 | +**New Behavior:** |
| 87 | +```python |
| 88 | +# Add edge if not already present |
| 89 | +# Let the consensus sampling handle network structure |
| 90 | +if not network.has_edge(id1, id2): |
| 91 | + network.add_edge(id1, id2, distance=distance) |
| 92 | +``` |
| 93 | + |
| 94 | +**Rationale:** |
| 95 | +- The PN algorithm samples edges from multiple random parsimony trees |
| 96 | +- The consensus process (frequency thresholding) naturally determines which edges appear in the final network |
| 97 | +- Artificially subdividing edges defeats the purpose of the consensus approach |
| 98 | +- The C++ implementation doesn't subdivide edges - it relies on the sampling to create the network structure |
| 99 | + |
| 100 | +#### 2. Removed Median Vertex Creation Method |
| 101 | +The `_add_median_vertices_along_edge` method was completely removed as it's no longer needed. |
| 102 | + |
| 103 | +## Validation |
| 104 | + |
| 105 | +### Test Results |
| 106 | +All existing unit tests pass: |
| 107 | +- TSW: 17/17 tests passing |
| 108 | +- PN: 21/21 tests passing |
| 109 | + |
| 110 | +### Example Improvements |
| 111 | + |
| 112 | +#### TSW Example |
| 113 | +**Input:** 3 sequences with 4 mutations between seq1 and seq2 |
| 114 | +- seq1: AAAA |
| 115 | +- seq2: TTTT (4 diffs) |
| 116 | +- seq3: AATT (2 diffs from each) |
| 117 | + |
| 118 | +**Before:** Potentially created unnecessary medians |
| 119 | +**After:** 3 nodes (3 observed, 0 median), 3 edges |
| 120 | + |
| 121 | +#### PN Example |
| 122 | +**Input:** 2 sequences with 4 mutations |
| 123 | +- seq1: AAAA |
| 124 | +- seq2: TTTT |
| 125 | + |
| 126 | +**Before:** Would create 3 intermediate medians to break the edge into 4 single-mutation steps (5 total nodes) |
| 127 | +**After:** Direct edge with distance=4.0 (2 nodes, 1 edge) |
| 128 | + |
| 129 | +## Impact |
| 130 | + |
| 131 | +### TSW |
| 132 | +✓ Cleaner networks with only necessary medians |
| 133 | +✓ Better matches C++ reference implementation behavior |
| 134 | +✓ Improved computational efficiency (fewer vertices to process) |
| 135 | +✓ More interpretable network structures |
| 136 | + |
| 137 | +### PN |
| 138 | +✓ Networks match the intended consensus sampling design |
| 139 | +✓ Edges accurately reflect sampled tree topologies |
| 140 | +✓ Reduced node count without loss of information |
| 141 | +✓ Aligns with C++ reference implementation |
| 142 | + |
| 143 | +## Technical Details |
| 144 | + |
| 145 | +### TSW Implementation Notes |
| 146 | +- Extended dT matrix dynamically as new medians are created |
| 147 | +- Vertex map uses tuple of dT distances as key for uniqueness |
| 148 | +- Bipartite coloring based on path decomposition from Dress & Huson (2004) |
| 149 | +- Delta calculation follows the formula: δ = min{(dT(f,i) + dT(f,j) - d(i,j))/2} |
| 150 | + |
| 151 | +### PN Implementation Notes |
| 152 | +- Maintains tree sampling and frequency thresholding |
| 153 | +- Edge distances calculated directly from sequence differences |
| 154 | +- Random seed support ensures reproducibility |
| 155 | +- Consensus threshold (min_edge_frequency) controls network complexity |
| 156 | + |
| 157 | +## References |
| 158 | + |
| 159 | +1. Dress, A. W., & Huson, D. H. (2004). Constructing splits graphs. IEEE/ACM Transactions on Computational Biology and Bioinformatics, 1(3), 109-115. |
| 160 | + |
| 161 | +2. Leigh, J. W., & Bryant, D. (2015). PopART: Full-feature software for haplotype network construction. Methods in Ecology and Evolution, 6(9), 1110-1116. |
| 162 | + |
| 163 | +## Future Considerations |
| 164 | + |
| 165 | +### Potential Enhancements |
| 166 | +1. **TSW**: Could optimize dT matrix updates to avoid full matrix expansion |
| 167 | +2. **TSW**: Could add configurable pruning strategies |
| 168 | +3. **PN**: Could expose more tree generation parameters |
| 169 | +4. **Both**: Could add visualization highlighting median vs observed nodes |
| 170 | + |
| 171 | +### Performance Notes |
| 172 | +- TSW: O(n³) complexity remains, but with fewer vertices created |
| 173 | +- PN: Linear in number of edges sampled, no additional overhead from median creation |
| 174 | +- Both algorithms suitable for datasets with < 200 sequences |
0 commit comments