///|
/// Parsimony scoring demo - Fitch and Sankoff algorithms.
///
/// Demonstrates phylogenetic parsimony scoring using:
/// - Fitch parsimony (equal cost for all transitions)
/// - Sankoff parsimony (variable cost via cost matrix)
///
/// Inspired by Biopython's Bio.Phylo.TreeConstruction.ParsimonyScorer.
/// Run: moon run examples/parsimony_demo
fn main {
println("=== Parsimony Module Demo ===")
println("Fitch and Sankoff parsimony scoring")
println("Reference: Biopython Bio.Phylo.TreeConstruction.ParsimonyScorer")
println("")
// ------------------------------------------------------------------
// 1. Sample alignment and tree
// ------------------------------------------------------------------
println("=== 1. Sample Data ===")
let aln = @bio.parsimony_sample_alignment()
println("Alignment (4 taxa, 5 characters):")
let aln_keys = aln.keys()
let aln_arr : Array[String] = aln_keys.collect()
let mut i = 0
while i < aln_arr.length() {
let name = aln_arr[i]
println(" " + name + ": " + aln[name])
i = i + 1
}
println("")
println("Tree topology: ((A, B), (C, D))")
println("")
let tree = @bio.parsimony_sample_tree()
// ------------------------------------------------------------------
// 2. Fitch parsimony
// ------------------------------------------------------------------
println("=== 2. Fitch Parsimony ===")
println("Algorithm: equal cost (1) for all state transitions")
println("")
let fitch_score = @bio.fitch_parsimony_score(tree, aln)
println("Fitch parsimony score: " + fitch_score.to_string())
println("")
// Explain per-position scores
println("Per-position analysis:")
println(" Position 0: A/A/A/A -> 0 changes (all same)")
println(" Position 1: C/C/T/T -> 1 change (C<->T at root)")
println(" Position 2: G/G/C/C -> 1 change (G<->C at root)")
println(" Position 3: G/G/T/T -> 1 change (G<->T at root)")
println(" Position 4: A/A/A/A -> 0 changes (all same)")
println(" Total: 3 changes")
println("")
// ------------------------------------------------------------------
// 3. Sankoff parsimony with Fitch matrix
// ------------------------------------------------------------------
println("=== 3. Sankoff Parsimony (Fitch matrix) ===")
let fitch_matrix = @bio.parsimony_fitch_dna_matrix()
println("Cost matrix (Fitch - all changes cost 1):")
println(" A C G T")
println(" A 0.0 1.0 1.0 1.0")
println(" C 1.0 0.0 1.0 1.0")
println(" G 1.0 1.0 0.0 1.0")
println(" T 1.0 1.0 1.0 0.0")
println("")
let sankoff_fitch_score = @bio.sankoff_parsimony_score(
tree, aln, fitch_matrix,
)
println("Sankoff score (Fitch matrix): " + sankoff_fitch_score.to_string())
println(" (Should equal Fitch score: " + fitch_score.to_string() + ")")
println("")
// ------------------------------------------------------------------
// 4. Sankoff parsimony with transition/transversion bias
// ------------------------------------------------------------------
println("=== 4. Sankoff Parsimony (Transition/Transversion bias) ===")
let sankoff_matrix = @bio.parsimony_sankoff_dna_matrix()
println("Cost matrix (transitions=1, transversions=2):")
println(" A C G T")
println(" A 0.0 2.0 1.0 2.0 (A<->G = transition)")
println(" C 2.0 0.0 2.0 1.0 (C<->T = transition)")
println(" G 1.0 2.0 0.0 2.0")
println(" T 2.0 1.0 2.0 0.0")
println("")
let sankoff_biased_score = @bio.sankoff_parsimony_score(
tree, aln, sankoff_matrix,
)
println("Sankoff score (biased matrix): " + sankoff_biased_score.to_string())
println("")
// ------------------------------------------------------------------
// 5. Comparison with varied alignment
// ------------------------------------------------------------------
println("=== 5. Comparison with Varied Alignment ===")
let aln_varied = @bio.parsimony_sample_alignment_varied()
println("Varied alignment (5 taxa, 8 characters):")
let var_keys = aln_varied.keys()
let var_arr : Array[String] = var_keys.collect()
let mut v = 0
while v < var_arr.length() {
let name = var_arr[v]
println(" " + name + ": " + aln_varied[name])
v = v + 1
}
println("")
// Build a tree for 5 taxa: ((A, B), (C, (D, E)))
let leaf_a = @bio.Clade::new(name=Some("A"))
let leaf_b = @bio.Clade::new(name=Some("B"))
let leaf_c = @bio.Clade::new(name=Some("C"))
let leaf_d = @bio.Clade::new(name=Some("D"))
let leaf_e = @bio.Clade::new(name=Some("E"))
let inner_de = @bio.Clade::new(clades=[leaf_d, leaf_e])
let inner_left = @bio.Clade::new(clades=[leaf_a, leaf_b])
let inner_right = @bio.Clade::new(clades=[leaf_c, inner_de])
let root = @bio.Clade::new(clades=[inner_left, inner_right])
let tree5 = @bio.Tree::new(root, rooted=true)
let fitch5 = @bio.fitch_parsimony_score(tree5, aln_varied)
let sankoff5 = @bio.sankoff_parsimony_score(tree5, aln_varied, sankoff_matrix)
println("Fitch score: " + fitch5.to_string())
println("Sankoff score (biased): " + sankoff5.to_string())
println("")
// ------------------------------------------------------------------
// 6. Unified interface
// ------------------------------------------------------------------
println("=== 6. Unified parsimony_get_score Interface ===")
println(
"Without matrix (Fitch): " + @bio.parsimony_get_score(tree, aln).to_string(),
)
println(
"With Fitch matrix: " +
@bio.parsimony_get_score(tree, aln, matrix=Some(fitch_matrix)).to_string(),
)
println(
"With biased matrix: " +
@bio.parsimony_get_score(tree, aln, matrix=Some(sankoff_matrix)).to_string(),
)
println("")
println("=== Parsimony Demo Complete ===")
}