///|
/// Chain file parsing and liftOver demo.
///
/// Demonstrates parsing a UCSC chain file, performing liftOver position
/// conversion between genome assemblies, finding chains for positions,
/// computing chain summaries, and serializing chains back to text.
///
/// Run: moon run examples/chain_liftover_demo/main.mbt
fn main {
println("=== Chain File Parsing & liftOver Demo ===")
println("")
// A sample UCSC chain file mapping chr1 (hg38) to chr2 (mm10)
// Chain format: alignment between genome assemblies
let chain_content = "chain 5000 chr1 249250621 + 1 5000 chr2 10000 + 1 100 5\n 500 10 20\n 400 0 0\nchain 3000 chr1 249250621 + 6000 10000 chr3 8000 + 500 1500 6\n 300 0 0"
println("1. Parsing a UCSC chain file")
let chains = @bio.cl_parse_chain(chain_content)
println(" Chain alignments parsed: \{chains.alignments.length()}")
println(" Target assembly: \{chains.target_name}")
println(" Query assembly: \{chains.query_name}")
println("")
println("2. Inspecting chain alignments")
let mut i = 0
while i < chains.alignments.length() {
let ch = chains.alignments[i]
println(
" Chain \{i}: id=\{ch.id}, score=\{ch.score}, \{ch.t_name}:\{ch.t_start}-\{ch.t_end} -> \{ch.q_name}:\{ch.q_start}-\{ch.q_end} (strand=\{ch.q_strand})",
)
println(" Blocks: \{ch.blocks.length()}")
let mut b = 0
while b < ch.blocks.length() {
let blk = ch.blocks[b]
println(" Block \{b}: size=\{blk.size}, dt=\{blk.dt}, dq=\{blk.dq}")
b = b + 1
}
i = i + 1
}
println("")
println("3. LiftOver position conversion")
// Lift over chr1:100 -> should map to chr2:100 (first chain, 0 mismatches)
let pos_result = @bio.cl_liftover_pos(chains, "chr1", 100)
match pos_result {
Some(qpos) => println(" chr1:100 -> chr2/3:\{qpos} (successful liftOver)")
None => println(" chr1:100 -> no chain covers this position")
}
// Position in the first gap (dt=10 after block 0, pos 501-510 are gap)
let gap_result = @bio.cl_liftover_pos(chains, "chr1", 501)
match gap_result {
Some(_) => println(" chr1:501 -> mapped (within alignment block)")
None => println(" chr1:501 -> gap in alignment, unmappable")
}
// Position in the second block (pos 600 = 500+10+90, offset into block 1)
let block2_result = @bio.cl_liftover_pos(chains, "chr1", 600)
match block2_result {
Some(qpos) =>
println(" chr1:600 -> query pos:\{qpos} (second block, offset by gap)")
None => println(" chr1:600 -> unmappable")
}
println("")
println("4. LiftOver range conversion")
let range_result = @bio.cl_liftover_range(chains, "chr1", 100, 200)
match range_result {
Some((qs, qe)) => println(" chr1:100-200 -> query range \{qs}-\{qe}")
None => println(" chr1:100-200 -> range unmappable")
}
println("")
println("5. Finding chain for a specific position")
let found_chain = @bio.cl_find_chain_for_pos(chains, "chr1", 6500)
match found_chain {
Some(ch) =>
println(
" chr1:6500 found in chain id=\{ch.id} (\{ch.t_name}:\{ch.t_start}-\{ch.t_end})",
)
None => println(" chr1:6500 -> no chain found")
}
println("")
println("6. Chain summary statistics")
i = 0
while i < chains.alignments.length() {
let summary = @bio.cl_get_chain_summary(chains.alignments[i])
println(" Chain \{chains.alignments[i].id}:")
println(" Total block size: \{summary["total_block_size"]}")
println(" Number of blocks: \{summary["num_blocks"]}")
println(" Total gap (target): \{summary["total_gap_target"]}")
println(" Total gap (query): \{summary["total_gap_query"]}")
println(" Aligned target: \{summary["aligned_target"]} bp")
println(" Aligned query: \{summary["aligned_query"]} bp")
i = i + 1
}
println("")
println("7. Serializing chain back to text")
let serialized = @bio.cl_chain_to_string(chains.alignments[0])
println(" First chain serialized:")
let lines = serialized.split("\n").to_array()
let mut l = 0
while l < lines.length() && l < 4 {
println(" \{lines[l]}")
l = l + 1
}
if lines.length() > 4 {
println(" ... (\{lines.length()} total lines)")
}
println("")
println("=== Demo Complete ===")
}