///|
/// pqsfinder demo: G-quadruplex (PQS) detection on DNA sequences, ported
/// from Bioconductor pqsfinder. Shows the default scoring system, strand
/// handling, option tuning, the deep-search density/max-score vectors,
/// overlapping conformations and PQS sequence extraction.
///
/// Run: moon run examples/pqs_demo
fn main raise {
println("=== pqsfinder Demo (G-quadruplex detection) ===\n")
// 1. The classic pqsfinder vignette sequence
println("1. Basic search on the vignette sequence")
let hits = @bio.pqs_finder("GGGAGGGTGGGTGGG") catch { _ => [] }
println(" GGGAGGGTGGGTGGG -> \{hits.length()} PQS found")
for p in hits {
println(
" [\{p.start},\{p.end}) strand=\{p.strand} score=\{p.score} nt=\{p.nt} nb=\{p.nb} nm=\{p.nm} loops=\{p.ll1},\{p.ll2},\{p.ll3}",
)
}
// 2. A longer sequence with several quadruplexes
println("\n2. Search on a longer sequence")
let seq = "TTGGGCCTGGGCGGGAGGGTCCTGGGTTGGGGTCCCAAGGGT"
let hits2 = @bio.pqs_finder(seq) catch { _ => [] }
println(" \{seq}")
println(" \{hits2.length()} PQS found (non-overlapping, sorted by start):")
for p in hits2 {
println(
" [\{p.start},\{p.end}) score=\{p.score} runs=\{p.rl1},\{p.rl2},\{p.rl3} loops=\{p.ll1},\{p.ll2},\{p.ll3}",
)
}
// 3. Both strands
println("\n3. Strand-specific searches")
// G-rich part forms a PQS on the plus strand; its reverse complement
// (C-rich part) forms one on the minus strand
let two = "GGGAGGGTGGGTGGGAAAAAACCCACCCACCCACTCCC"
let plus = @bio.pqs_finder(two, strand="+") catch { _ => [] }
let minus = @bio.pqs_finder(two, strand="-") catch { _ => [] }
println(" \{two}")
println(" plus strand: \{plus.length()} PQS")
for p in plus {
println(" + [\{p.start},\{p.end}) score=\{p.score}")
}
println(" minus strand (reverse complement): \{minus.length()} PQS")
for p in minus {
println(" - [\{p.start},\{p.end}) score=\{p.score}")
}
// 4. Custom scoring: require two tetrads minimum, stricter defects
println("\n4. Custom options: longer runs, higher min_score")
let strict = @bio.pqs_finder(
seq,
options=@bio.PqsOptions::of(run_min_len=3, min_score=60),
) catch {
_ => []
}
println(" run_min_len=3, min_score=60 -> \{strict.length()} PQS")
for p in strict {
println(
" [\{p.start},\{p.end}) score=\{p.score} runs=\{p.rl1},\{p.rl2},\{p.rl3}",
)
}
// 5. Deep search: per-position density and max scores
println("\n5. Deep search: density / maxScores vectors")
let deep = @bio.pqs_search("GGGAGGGTGGGTGGG")
let dens : Array[String] = []
let scores : Array[String] = []
for d in deep.density {
dens.push("\{d}")
}
for s in deep.max_scores {
scores.push("\{s}")
}
println(" density: [\{dens.join(", ")}]")
println(" max scores: [\{scores.join(", ")}]")
// 6. Overlapping export: report all scoreable conformations
println("\n6. Overlapping export (all conformations)")
let two = "GGGAGGGTGGGTGGGAAAAAGGGAGGGTGGGTGGG"
let nonoverlap = @bio.pqs_finder(two) catch { _ => [] }
let overlap = @bio.pqs_finder(two, overlapping=true) catch { _ => [] }
println(" \{two}")
println(" non-overlapping: \{nonoverlap.length()} PQS")
for p in nonoverlap {
println(" [\{p.start},\{p.end}) score=\{p.score}")
}
println(" overlapping: \{overlap.length()} conformations (first 6):")
let mut shown = 0
while shown < overlap.length() && shown < 6 {
let p = overlap[shown]
println(
" [\{p.start},\{p.end}) strand=\{p.strand} score=\{p.score} loops=\{p.ll1},\{p.ll2},\{p.ll3}",
)
shown = shown + 1
}
// 7. Extract PQS sequences (as(pqs, "DNAStringSet") equivalent)
println("\n7. PQS sequence extraction")
let flanked = "AAAAGGGAGGGTGGGTGGGAAAA"
let basic = @bio.pqs_finder(flanked) catch { _ => [] }
for p in basic {
println(" [\{p.start},\{p.end}) \{p.strand} -> \{p.sequence(flanked)}")
}
println("\nDone.")
}