use std::collections::BTreeMap;
use std::fmt;
use crate::reference::transcript::{Exon, GenomeBuild, ManeStatus, Strand, Transcript};
use crate::reference::MockProvider;
use crate::spdi::hgvs_to_spdi;
use crate::{parse_hgvs, HgvsVariant};
pub const PAD_OFFSET: usize = 256;
pub const GENOMIC_CONTIG: &str = "NC_TEST.1";
pub const CODING_ACCESSION: &str = "NM_TEST.1";
pub const NONCODING_ACCESSION: &str = "NR_TEST.1";
pub const TRANSCRIPT_CONTIG: &str = "NC_SYNTH.1";
const INTRON_LEN: usize = 60;
const SHIFT_SEARCH: isize = 24;
pub const BLOCK_LADDER: &[usize] = &[
1, 2, 4, 8, 120, 128, 136, 248, 256, 264, 1016, 1024, 1032, 4088, 4096, 4104,
];
pub const SEPARATION_LADDER: &[usize] = &[
0, 1, 2, 3, 5, 8, 120, 128, 136, 1016, 1024, 1032, 3832, 3968, 4104,
];
pub const EXTENDED_BLOCK_LADDER: &[usize] = &[32_760, 32_768, 32_776, 65_536];
pub const DENSE_SEPARATIONS: &[usize] = &[0, 1, 2, 3, 5, 8];
pub const DENSE_PAYLOADS: &[usize] = &[1, 2, 4];
pub const MEMBER_COUNTS: &[usize] = &[2, 3, 4];
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CorpusBounds {
pub seeds: u32,
pub extended_scale: bool,
}
impl Default for CorpusBounds {
fn default() -> Self {
Self {
seeds: 1,
extended_scale: false,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum RowKind {
Family,
Single,
Conflict,
Prohibited,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Strength {
Absolute,
Conditional,
}
impl Strength {
#[must_use]
pub fn label(self) -> &'static str {
match self {
Self::Absolute => "absolute",
Self::Conditional => "conditional",
}
}
}
impl fmt::Display for RowKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let text = match self {
Self::Family => "family",
Self::Single => "single",
Self::Conflict => "conflict",
Self::Prohibited => "prohibited",
};
f.write_str(text)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Geometry {
Disjoint,
FlushAdjacent,
CoincidentEndpoint,
Nested,
Overlapping,
CoincidentInsertions,
SelfReplacement,
}
impl Geometry {
#[must_use]
pub fn denotes_a_sequence(self) -> bool {
matches!(
self,
Self::Disjoint | Self::FlushAdjacent | Self::CoincidentEndpoint
)
}
#[must_use]
pub fn label(self) -> &'static str {
match self {
Self::Disjoint => "disjoint",
Self::FlushAdjacent => "flush",
Self::CoincidentEndpoint => "coincident-endpoint",
Self::Nested => "nested",
Self::Overlapping => "overlapping",
Self::CoincidentInsertions => "coincident-insertions",
Self::SelfReplacement => "self-replacement",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Mechanism {
Lone,
Cis,
UnknownPhase,
Trans,
CompositePayload,
RepeatCount,
}
impl Mechanism {
#[must_use]
pub fn combines_members(self) -> bool {
matches!(self, Self::Cis | Self::UnknownPhase | Self::Trans)
}
#[must_use]
pub fn label(self) -> &'static str {
match self {
Self::Lone => "lone",
Self::Cis => "cis",
Self::UnknownPhase => "unknown-phase",
Self::Trans => "trans",
Self::CompositePayload => "composite-payload",
Self::RepeatCount => "repeat-count",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Region {
Anywhere,
Utr5,
CdsStart,
MidCds,
ExonJunction1,
ExonJunction2,
CdsEnd,
Utr3,
Intronic,
}
impl Region {
#[must_use]
pub fn label(self) -> &'static str {
match self {
Self::Anywhere => "anywhere",
Self::Utr5 => "utr5",
Self::CdsStart => "cds-start",
Self::MidCds => "mid-cds",
Self::ExonJunction1 => "junction-1",
Self::ExonJunction2 => "junction-2",
Self::CdsEnd => "cds-end",
Self::Utr3 => "utr3",
Self::Intronic => "intronic",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RefShape {
Genomic,
CodingSingleExon,
CodingMultiExon(Strand),
NonCodingMultiExon(Strand),
}
impl RefShape {
#[must_use]
pub fn all() -> Vec<Self> {
vec![
Self::Genomic,
Self::CodingSingleExon,
Self::CodingMultiExon(Strand::Plus),
Self::CodingMultiExon(Strand::Minus),
Self::NonCodingMultiExon(Strand::Plus),
Self::NonCodingMultiExon(Strand::Minus),
]
}
#[must_use]
pub fn structured() -> Vec<Self> {
Self::all()
.into_iter()
.filter(|shape| !matches!(shape, Self::Genomic))
.collect()
}
#[must_use]
pub fn label(self) -> &'static str {
match self {
Self::Genomic => "g",
Self::CodingSingleExon => "c1",
Self::CodingMultiExon(Strand::Minus) => "c3m",
Self::CodingMultiExon(_) => "c3p",
Self::NonCodingMultiExon(Strand::Minus) => "n3m",
Self::NonCodingMultiExon(_) => "n3p",
}
}
#[must_use]
pub fn prefix(self) -> &'static str {
match self {
Self::Genomic => "g",
Self::CodingSingleExon | Self::CodingMultiExon(_) => "c",
Self::NonCodingMultiExon(_) => "n",
}
}
#[must_use]
pub fn is_multi_exon(self) -> bool {
matches!(self, Self::CodingMultiExon(_) | Self::NonCodingMultiExon(_))
}
}
#[derive(Debug, Clone)]
pub struct Row {
pub id: String,
pub kind: RowKind,
pub stratum: &'static str,
pub shape: RefShape,
pub core: String,
pub denoted: Option<String>,
pub spellings: Vec<String>,
pub members: usize,
pub separation: usize,
pub block_len: usize,
pub mechanism: Mechanism,
pub prohibition: Option<(&'static str, Strength)>,
pub negative_guards: Vec<&'static str>,
pub coding_axis_separation_two_or_more: bool,
pub geometry: Geometry,
pub region: Region,
pub scale_bands: Vec<&'static str>,
pub rules: Vec<&'static str>,
}
impl Row {
#[must_use]
pub fn authored_spelling(&self) -> &str {
self.spellings
.first()
.map_or("", std::string::String::as_str)
}
#[must_use]
pub fn is_multi_member(&self) -> bool {
self.members > 1 && self.mechanism.combines_members()
}
#[must_use]
pub fn frame(&self) -> Frame {
Frame::build(self.shape, &self.core)
}
#[must_use]
pub fn frame_key(&self) -> (RefShape, &str) {
(self.shape, self.core.as_str())
}
}
#[derive(Clone)]
pub struct Frame {
shape: RefShape,
accession: &'static str,
served: String,
core_offset: usize,
cds: Option<(usize, usize)>,
exons: Vec<(usize, usize)>,
provider: MockProvider,
}
impl Frame {
#[must_use]
pub fn build(shape: RefShape, core: &str) -> Self {
assert!(
core.len() >= 24,
"a synthetic core must be at least 24 bases, got {}",
core.len()
);
match shape {
RefShape::Genomic => {
let served = padded(core);
let mut provider = MockProvider::new();
provider.add_genomic_sequence(GENOMIC_CONTIG, served.clone());
Self {
shape,
accession: GENOMIC_CONTIG,
served,
core_offset: PAD_OFFSET,
cds: None,
exons: Vec::new(),
provider,
}
}
RefShape::CodingSingleExon => {
let cds = (1usize, core.len() - 1);
let exons = vec![(1usize, core.len())];
let provider = transcript_provider(
CODING_ACCESSION,
Strand::Plus,
core,
Some(cds),
&[(1, core.len())],
);
Self {
shape,
accession: CODING_ACCESSION,
served: core.to_string(),
core_offset: 0,
cds: Some(cds),
exons,
provider,
}
}
RefShape::CodingMultiExon(strand) | RefShape::NonCodingMultiExon(strand) => {
let exons = three_exon_layout(core.len());
let coding = matches!(shape, RefShape::CodingMultiExon(_));
let cds = coding.then(|| cds_layout(core.len()));
let accession = if coding {
CODING_ACCESSION
} else {
NONCODING_ACCESSION
};
let provider = transcript_provider(accession, strand, core, cds, &exons);
Self {
shape,
accession,
served: core.to_string(),
core_offset: 0,
cds,
exons,
provider,
}
}
}
}
#[must_use]
pub fn provider(&self) -> &MockProvider {
&self.provider
}
#[must_use]
pub fn served(&self) -> &str {
&self.served
}
#[must_use]
pub fn core_offset(&self) -> usize {
self.core_offset
}
#[must_use]
pub fn accession(&self) -> &'static str {
self.accession
}
#[must_use]
pub fn wrapped_accession(&self) -> Option<String> {
self.shape
.is_multi_exon()
.then(|| format!("{TRANSCRIPT_CONTIG}({})", self.accession))
}
#[must_use]
pub fn label(&self, index: usize) -> String {
let one_based = index + 1;
match self.cds {
None => one_based.to_string(),
Some((cds_start, cds_end)) => {
if one_based < cds_start {
format!("-{}", cds_start - one_based)
} else if one_based <= cds_end {
(one_based - cds_start + 1).to_string()
} else {
format!("*{}", one_based - cds_end)
}
}
}
}
#[must_use]
pub fn intronic_label(&self, index: usize, delta: isize) -> Option<String> {
if !self.shape.is_multi_exon() || delta == 0 {
return None;
}
let one_based = index + 1;
let boundary_ok = self.exons.iter().any(|&(start, end)| {
(delta > 0 && one_based == end && end != self.served.len())
|| (delta < 0 && one_based == start && start != 1)
});
if !boundary_ok {
return None;
}
let base = self.label(index);
Some(if delta > 0 {
format!("{base}+{delta}")
} else {
format!("{base}{delta}")
})
}
#[must_use]
pub fn region_start(&self, region: Region, width: usize) -> Option<usize> {
let len = self.served.len();
let (cds_start, cds_end) = self.cds.unwrap_or((1, len));
let fits = |start: usize| (start + width <= len).then_some(start);
match region {
Region::Anywhere | Region::MidCds => {
let start = if self.shape.is_multi_exon() {
let mid = self.exons.get(1).map_or((1, len), |&e| e);
mid.0 + 3 - 1
} else {
self.core_offset + 8
};
fits(start)
}
Region::Utr5 => {
if cds_start < 4 {
return None;
}
fits(1).filter(|&start| start + width < cds_start - 1)
}
Region::CdsStart => {
(cds_start >= 2).then_some(())?;
fits(cds_start.saturating_sub(2))
}
Region::ExonJunction1 | Region::ExonJunction2 => {
let which = if region == Region::ExonJunction1 {
0
} else {
1
};
let exon = *self.exons.get(which)?;
(width >= 2).then_some(())?;
(exon.1 >= 2).then_some(())?;
fits(exon.1 - 1)
}
Region::CdsEnd => {
self.cds?;
(cds_end >= 2).then_some(())?;
fits(cds_end - 1)
}
Region::Utr3 => {
self.cds?;
fits(cds_end + 1).filter(|&start| start + width <= len)
}
Region::Intronic => None,
}
}
#[must_use]
pub fn exons(&self) -> &[(usize, usize)] {
&self.exons
}
#[must_use]
pub fn cds(&self) -> Option<(usize, usize)> {
self.cds
}
}
#[must_use]
pub fn padded(core: &str) -> String {
let pad = "ACGT".repeat(PAD_OFFSET / 4);
format!("{pad}{core}{pad}")
}
pub(crate) fn three_exon_layout(tx_len: usize) -> Vec<(usize, usize)> {
let first = (tx_len / 3).max(8);
let second = (tx_len / 3).max(8);
vec![
(1, first),
(first + 1, first + second),
(first + second + 1, tx_len),
]
}
fn cds_layout(tx_len: usize) -> (usize, usize) {
let utr5 = (tx_len / 8).clamp(4, tx_len / 3);
let utr3 = (tx_len / 8).clamp(4, tx_len / 3);
(utr5 + 1, tx_len - utr3)
}
fn reverse_complement(sequence: &str) -> String {
sequence
.chars()
.rev()
.map(|base| match base.to_ascii_uppercase() {
'A' => 'T',
'C' => 'G',
'G' => 'C',
'T' => 'A',
other => other,
})
.collect()
}
pub(crate) fn transcript_provider(
accession: &'static str,
strand: Strand,
tx: &str,
cds: Option<(usize, usize)>,
exons: &[(usize, usize)],
) -> MockProvider {
let pad = "ACGT".repeat(PAD_OFFSET / 4);
let intron = "GATTACA".repeat(INTRON_LEN / 7 + 1);
let intron = &intron[..INTRON_LEN];
let mut blocks: Vec<String> = exons
.iter()
.map(|&(start, end)| tx[start - 1..end].to_string())
.collect();
if strand == Strand::Minus {
blocks = blocks.iter().rev().map(|b| reverse_complement(b)).collect();
}
let mut contig = String::with_capacity(2 * PAD_OFFSET + tx.len() + 2 * INTRON_LEN);
contig.push_str(&pad);
let mut block_starts = Vec::with_capacity(blocks.len());
for (index, block) in blocks.iter().enumerate() {
if index > 0 {
contig.push_str(intron);
}
block_starts.push(contig.len());
contig.push_str(block);
}
contig.push_str(&pad);
let mut exon_records = Vec::with_capacity(exons.len());
for (index, &(tx_start, tx_end)) in exons.iter().enumerate() {
let genomic_index = if strand == Strand::Minus {
exons.len() - 1 - index
} else {
index
};
let g_start = block_starts[genomic_index] as u64 + 1;
let g_end = g_start + (tx_end - tx_start) as u64;
let number = u32::try_from(index + 1).unwrap_or(u32::MAX);
exon_records.push(Exon::with_genomic(
number,
tx_start as u64,
tx_end as u64,
g_start,
g_end,
));
}
let span_start = *block_starts.first().expect("at least one exon") as u64 + 1;
let span_end = contig.len() as u64 - PAD_OFFSET as u64;
let transcript = Transcript::new(
accession.to_string(),
Some("SYNTH".to_string()),
strand,
tx.to_string(),
cds.map(|(start, _)| start as u64),
cds.map(|(_, end)| end as u64),
exon_records,
Some(TRANSCRIPT_CONTIG.to_string()),
Some(span_start),
Some(span_end),
GenomeBuild::GRCh38,
ManeStatus::None,
None,
None,
);
let mut provider = MockProvider::new();
provider.add_genomic_sequence(TRANSCRIPT_CONTIG, contig);
provider.add_transcript(transcript);
provider
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Kind {
Del,
Ins,
Delins,
Sub,
Dup,
Inv,
Repeat,
}
pub const PAIRED_KINDS: &[Kind] = &[
Kind::Del,
Kind::Ins,
Kind::Delins,
Kind::Sub,
Kind::Dup,
Kind::Inv,
];
impl Kind {
#[must_use]
pub fn label(self) -> &'static str {
match self {
Self::Del => "del",
Self::Ins => "ins",
Self::Delins => "delins",
Self::Sub => "sub",
Self::Dup => "dup",
Self::Inv => "inv",
Self::Repeat => "repeat",
}
}
fn span(self, payload: usize) -> usize {
match self {
Self::Ins => 0,
Self::Sub => 1,
Self::Del | Self::Delins | Self::Dup | Self::Inv | Self::Repeat => payload,
}
}
}
#[derive(Debug, Clone)]
struct Member {
kind: Kind,
start: usize,
span: usize,
payload: String,
repeat: Option<(String, usize)>,
}
fn payload_bases(served: &str, at: usize, size: usize) -> String {
let bytes = served.as_bytes();
(0..size)
.map(|k| match bytes.get(at + k).copied().unwrap_or(b'A') {
b'A' => 'C',
b'C' => 'G',
b'G' => 'T',
_ => 'A',
})
.collect()
}
#[must_use]
pub fn triples_of(
provider: &MockProvider,
descriptor: &str,
) -> Option<Vec<(usize, String, String)>> {
let members: Vec<HgvsVariant> = match parse_hgvs(descriptor).ok()? {
HgvsVariant::Allele(allele) => allele.variants.clone(),
single => vec![single],
};
let mut triples = Vec::with_capacity(members.len());
for member in &members {
let triple = hgvs_to_spdi(member, provider).ok()?;
triples.push((
usize::try_from(triple.position).ok()?,
triple.deletion.clone(),
triple.insertion.clone(),
));
}
Some(triples)
}
#[must_use]
pub fn apply_triples(reference: &str, triples: &[(usize, String, String)]) -> Option<String> {
let mut ordered: Vec<&(usize, String, String)> = triples.iter().collect();
ordered.sort_by_key(|t| (std::cmp::Reverse(t.0), std::cmp::Reverse(t.1.len())));
let bytes = reference.as_bytes();
let mut edited = bytes.to_vec();
let mut claimed = reference.len();
let mut insertion_at: Option<usize> = None;
for (position, deletion, insertion) in ordered {
let end = position.checked_add(deletion.len())?;
if end > reference.len() || end > claimed {
return None;
}
if deletion.is_empty() && insertion_at == Some(*position) {
return None;
}
if !bytes[*position..end].eq_ignore_ascii_case(deletion.as_bytes()) {
return None;
}
edited.splice(*position..end, insertion.bytes());
if deletion.is_empty() {
insertion_at = Some(*position);
}
claimed = *position;
}
String::from_utf8(edited).ok()
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Denotation {
Sequence(String),
Unparseable,
Inexpressible,
NoSequence,
}
#[must_use]
pub fn denotation_of(provider: &MockProvider, reference: &str, descriptor: &str) -> Denotation {
if parse_hgvs(descriptor).is_err() {
return Denotation::Unparseable;
}
let Some(triples) = triples_of(provider, descriptor) else {
return Denotation::Inexpressible;
};
match apply_triples(reference, &triples) {
Some(sequence) => Denotation::Sequence(sequence),
None => Denotation::NoSequence,
}
}
#[must_use]
pub fn denoted_by(provider: &MockProvider, reference: &str, descriptor: &str) -> Option<String> {
match denotation_of(provider, reference, descriptor) {
Denotation::Sequence(sequence) => Some(sequence),
_ => None,
}
}
fn equivalent_placements(
served: &str,
position: usize,
deletion: &str,
insertion: &str,
) -> Vec<(usize, String, String)> {
let Some(target) = splice(served, position, deletion.len(), insertion) else {
return Vec::new();
};
let mut lowest: Option<(usize, String, String)> = None;
let mut highest: Option<(usize, String, String)> = None;
for delta in -SHIFT_SEARCH..=SHIFT_SEARCH {
if delta == 0 {
continue;
}
let Ok(candidate) = usize::try_from(position as isize + delta) else {
continue;
};
let end = candidate + deletion.len();
if end > served.len() {
continue;
}
if !target.starts_with(&served[..candidate]) || !target.ends_with(&served[end..]) {
continue;
}
let tail = served.len() - end;
if target.len() < candidate + tail {
continue;
}
let new_deletion = served[candidate..end].to_string();
let new_insertion = target[candidate..target.len() - tail].to_string();
if splice(served, candidate, new_deletion.len(), &new_insertion).as_deref() != Some(&target)
{
continue;
}
let placement = (candidate, new_deletion, new_insertion);
if delta < 0 {
if lowest.is_none() {
lowest = Some(placement);
}
} else {
highest = Some(placement);
}
}
lowest.into_iter().chain(highest).collect()
}
fn splice(sequence: &str, at: usize, deleted: usize, insertion: &str) -> Option<String> {
let end = at.checked_add(deleted)?;
if end > sequence.len() {
return None;
}
Some(format!(
"{}{insertion}{}",
&sequence[..at],
&sequence[end..]
))
}
#[must_use]
pub fn corpus_cores(seeds: u32, length: usize) -> Vec<String> {
let mut cores = Vec::with_capacity(2 * seeds as usize);
for seed in 0..seeds {
for alphabet in [b"AT".as_slice(), b"ACGT".as_slice()] {
let mut state = u64::from(seed).wrapping_mul(0x9E37_79B9_7F4A_7C15) | 1;
cores.push(
(0..length)
.map(|_| {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
alphabet[(state % alphabet.len() as u64) as usize] as char
})
.collect(),
);
}
}
cores
}
pub const DENSE_CORE_LEN: usize = 96;
#[must_use]
pub fn repeat_core(unit_len: usize, copies: usize, seed: u32) -> Option<(String, String, usize)> {
if unit_len == 0 || copies < 2 {
return None;
}
let unit: String = "ACGTAC"
.chars()
.skip(seed as usize % 3)
.take(unit_len)
.collect();
if unit.len() != unit_len {
return None;
}
if unit_len > 1
&& unit
.chars()
.all(|c| c == unit.chars().next().unwrap_or('A'))
{
return None;
}
let flank_len = 24usize;
let flanks = corpus_cores(seed + 7, flank_len * 2);
let flank = flanks.into_iter().nth(1)?;
let array = unit.repeat(copies);
let left: String = flank[..flank_len].to_string();
let right: String = flank[flank_len..].to_string();
let core = format!("{left}{array}{right}");
Some((core, unit, left.len()))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum DropReason {
OutOfRange,
NoDenotedSequence,
DenotesTheReference,
Singleton,
PalindromicInversion,
UnavailableOnThisAxis,
NotAConflictingGeometry,
}
impl DropReason {
#[must_use]
pub fn label(self) -> &'static str {
match self {
Self::OutOfRange => "out of range",
Self::NoDenotedSequence => "no denoted sequence",
Self::DenotesTheReference => "denotes the reference",
Self::Singleton => "fewer than two spellings",
Self::PalindromicInversion => "palindromic inversion",
Self::UnavailableOnThisAxis => "shape unavailable on this axis",
Self::NotAConflictingGeometry => "not a conflicting geometry",
}
}
}
impl fmt::Display for DropReason {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.label())
}
}
pub type Attempt = Result<Row, (String, DropReason)>;
#[derive(Debug, Clone)]
pub struct SpecCorpus {
pub bounds: CorpusBounds,
pub designs_considered: usize,
pub drops: BTreeMap<&'static str, usize>,
pub rows: Vec<Row>,
}
impl SpecCorpus {
#[must_use]
pub fn from_attempts(bounds: CorpusBounds, attempts: Vec<Attempt>) -> Self {
let designs_considered = attempts.len();
let mut drops: BTreeMap<&'static str, usize> = BTreeMap::new();
let mut rows = Vec::new();
for attempt in attempts {
match attempt {
Ok(row) => rows.push(row),
Err((_, reason)) => *drops.entry(reason.label()).or_default() += 1,
}
}
rows.sort_by(|a, b| a.id.cmp(&b.id));
Self {
bounds,
designs_considered,
drops,
rows,
}
}
#[must_use]
pub fn multi_member_rows(&self) -> usize {
self.rows.iter().filter(|row| row.is_multi_member()).count()
}
#[must_use]
pub fn spellings(&self) -> usize {
self.rows.iter().map(|row| row.spellings.len()).sum()
}
#[must_use]
pub fn coding_axis_separation_two_or_more_rows(&self) -> usize {
self.rows
.iter()
.filter(|row| row.coding_axis_separation_two_or_more)
.count()
}
#[must_use]
pub fn by_kind(&self) -> BTreeMap<RowKind, usize> {
let mut counts = BTreeMap::new();
for row in &self.rows {
*counts.entry(row.kind).or_default() += 1;
}
counts
}
#[must_use]
pub fn by_stratum(&self) -> BTreeMap<&'static str, usize> {
let mut counts = BTreeMap::new();
for row in &self.rows {
*counts.entry(row.stratum).or_default() += 1;
}
counts
}
#[must_use]
pub fn by_mechanism(&self) -> BTreeMap<&'static str, RuleCoverage> {
let mut counts: BTreeMap<&'static str, RuleCoverage> = BTreeMap::new();
for row in &self.rows {
let entry = counts.entry(row.mechanism.label()).or_default();
entry.rows += 1;
if row.is_multi_member() {
entry.multi_member_rows += 1;
}
}
counts
}
#[must_use]
pub fn by_prohibition_strength(&self) -> BTreeMap<&'static str, usize> {
let mut counts = BTreeMap::new();
for row in &self.rows {
if let Some((_, strength)) = row.prohibition {
*counts.entry(strength.label()).or_default() += 1;
}
}
counts
}
#[must_use]
pub fn by_negative_guard(&self) -> BTreeMap<&'static str, usize> {
let mut counts = BTreeMap::new();
for row in &self.rows {
for guard in &row.negative_guards {
*counts.entry(*guard).or_default() += 1;
}
}
counts
}
#[must_use]
pub fn by_geometry(&self) -> BTreeMap<&'static str, usize> {
let mut counts = BTreeMap::new();
for row in &self.rows {
*counts.entry(row.geometry.label()).or_default() += 1;
}
counts
}
#[must_use]
pub fn by_region(&self) -> BTreeMap<&'static str, usize> {
let mut counts = BTreeMap::new();
for row in &self.rows {
*counts.entry(row.region.label()).or_default() += 1;
}
counts
}
#[must_use]
pub fn by_shape(&self) -> BTreeMap<&'static str, usize> {
let mut counts = BTreeMap::new();
for row in &self.rows {
*counts.entry(row.shape.label()).or_default() += 1;
}
counts
}
#[must_use]
pub fn by_scale_band(&self) -> BTreeMap<&'static str, usize> {
let mut counts = BTreeMap::new();
for row in &self.rows {
if row.scale_bands.is_empty() {
*counts.entry("below-all").or_default() += 1;
}
for band in &row.scale_bands {
*counts.entry(*band).or_default() += 1;
}
}
counts
}
#[must_use]
pub fn by_rule(&self) -> BTreeMap<&'static str, RuleCoverage> {
let mut counts: BTreeMap<&'static str, RuleCoverage> = BTreeMap::new();
for row in &self.rows {
for rule in &row.rules {
let entry = counts.entry(*rule).or_default();
entry.rows += 1;
if row.is_multi_member() {
entry.multi_member_rows += 1;
}
}
}
counts
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct RuleCoverage {
pub rows: usize,
pub multi_member_rows: usize,
}
#[must_use]
pub fn enumerate(bounds: &CorpusBounds) -> Vec<Attempt> {
let mut attempts = Vec::new();
enumerate_members(bounds, &mut attempts);
enumerate_regions(bounds, &mut attempts);
enumerate_scale(bounds, &mut attempts);
enumerate_repeats(bounds, &mut attempts);
enumerate_conflicts(bounds, &mut attempts);
enumerate_intronic(bounds, &mut attempts);
enumerate_mechanisms(bounds, &mut attempts);
enumerate_prohibited(bounds, &mut attempts);
enumerate_ambiguity(bounds, &mut attempts);
attempts
}
#[must_use]
pub fn corpus(bounds: &CorpusBounds) -> SpecCorpus {
SpecCorpus::from_attempts(bounds.clone(), enumerate(bounds))
}
fn rules_for(
kinds: &[Kind],
geometry: Geometry,
separation: usize,
region: Region,
shape: RefShape,
members: usize,
) -> Vec<&'static str> {
let mut rules = vec!["general.md:40-three-prime-rule"];
if members > 1 {
rules.push("general.md:79-allele-semicolon-separator");
match separation {
0 => rules.push("DNA/delins.md:17-adjacent-members-merge"),
1 => {
rules.push("general.md:33-separation-split");
rules.push("general.md:34-codon-exception");
}
_ => rules.push("general.md:33-separation-split"),
}
}
if !geometry.denotes_a_sequence() {
rules.push("general.md:57-no-self-replacement");
rules.push("checklist.md:6-most-offended");
}
for kind in kinds {
rules.push(match kind {
Kind::Del => "DNA/deletion.md:5-del-format",
Kind::Ins => "DNA/insertion.md:5-ins-flanking-format",
Kind::Delins => "DNA/delins.md:5-delins-format",
Kind::Sub => "DNA/substitution.md:5-sub-format",
Kind::Dup => "DNA/duplication.md:5-dup-format",
Kind::Inv => "DNA/inversion.md:5-inv-format",
Kind::Repeat => "DNA/repeated.md:5-repeat-format",
});
}
if kinds.contains(&Kind::Dup) && kinds.contains(&Kind::Ins) {
rules.push("general.md:56-dup-outranks-ins");
}
if kinds.contains(&Kind::Inv) {
rules.push("general.md:55-type-priority");
}
match region {
Region::Utr5 | Region::CdsStart => rules.push("checklist.md:17-cds-numbering-from-atg"),
Region::Utr3 | Region::CdsEnd => rules.push("checklist.md:17-cds-numbering-from-atg"),
Region::ExonJunction1 | Region::ExonJunction2 => {
rules.push("general.md:43-junction-exception");
}
Region::Intronic => rules.push("checklist.md:24-intronic-positions-not-exon-numbers"),
Region::Anywhere | Region::MidCds => {}
}
if shape == RefShape::Genomic {
rules.push("checklist.md:16-genomic-has-no-offsets");
}
rules.sort_unstable();
rules.dedup();
rules
}
fn scale_bands(block_len: usize, window: usize) -> Vec<&'static str> {
let mut bands = Vec::new();
for (threshold, label) in [
(128usize, "canonical-pad-128"),
(256, "tie-break-sweep-256"),
(1024, "split-block-1024"),
(4096, "canonical-window-4096"),
(32_768, "shift-tract-32768"),
] {
if block_len >= threshold {
bands.push(label);
}
}
if window >= 4096 {
bands.push("window-past-canonical-4096");
}
bands.sort_unstable();
bands.dedup();
bands
}
struct Design<'a> {
id: String,
stratum: &'static str,
frame: &'a Frame,
core: &'a str,
members: Vec<Member>,
separation: usize,
geometry: Geometry,
region: Region,
mechanism: Mechanism,
negative_guard_candidate: bool,
rules: Vec<&'static str>,
}
fn layout(
frame: &Frame,
start: usize,
kinds: &[Kind],
payload: usize,
separation: usize,
) -> Result<Vec<Member>, DropReason> {
let served = frame.served();
let mut members = Vec::with_capacity(kinds.len());
let mut cursor = start;
for &kind in kinds {
let span = kind.span(payload);
if cursor == 0 {
return Err(DropReason::OutOfRange);
}
let highest = if span == 0 { cursor } else { cursor + span - 1 };
if highest >= served.len() {
return Err(DropReason::OutOfRange);
}
let bases = match kind {
Kind::Del | Kind::Dup | Kind::Inv | Kind::Repeat => String::new(),
Kind::Ins | Kind::Delins => payload_bases(served, cursor, payload.max(1)),
Kind::Sub => payload_bases(served, cursor, 1),
};
if kind == Kind::Inv {
if span < 2 {
return Err(DropReason::OutOfRange);
}
let span_bases = &served[cursor..cursor + span];
if reverse_complement(span_bases) == span_bases {
return Err(DropReason::PalindromicInversion);
}
}
members.push(Member {
kind,
start: cursor,
span,
payload: bases,
repeat: None,
});
cursor = cursor + span + separation;
}
Ok(members)
}
fn render_member(frame: &Frame, member: &Member) -> Option<String> {
let served = frame.served();
let label = |index: usize| frame.label(index);
let last = member.start + member.span.saturating_sub(1);
Some(match member.kind {
Kind::Del if member.span == 1 => format!("{}del", label(member.start)),
Kind::Del => format!("{}_{}del", label(member.start), label(last)),
Kind::Dup if member.span == 1 => format!("{}dup", label(member.start)),
Kind::Dup => format!("{}_{}dup", label(member.start), label(last)),
Kind::Inv => format!("{}_{}inv", label(member.start), label(last)),
Kind::Sub => format!(
"{}{}>{}",
label(member.start),
&served[member.start..member.start + 1],
member.payload
),
Kind::Delins if member.span == 1 => {
format!("{}delins{}", label(member.start), member.payload)
}
Kind::Delins => format!(
"{}_{}delins{}",
label(member.start),
label(last),
member.payload
),
Kind::Ins => format!(
"{}_{}ins{}",
label(member.start - 1),
label(member.start),
member.payload
),
Kind::Repeat => {
let (unit, copies) = member.repeat.as_ref()?;
format!(
"{}_{}{unit}[{copies}]",
label(member.start),
label(member.start + unit.len() - 1)
)
}
})
}
fn render_allele(frame: &Frame, rendered: &[String]) -> String {
render_allele_as(
frame.accession(),
frame.shape.prefix(),
Mechanism::Cis,
rendered,
)
}
fn render_allele_as(
accession: &str,
prefix: &str,
mechanism: Mechanism,
rendered: &[String],
) -> String {
if rendered.len() == 1 {
return format!("{accession}:{prefix}.{}", rendered[0]);
}
match mechanism {
Mechanism::Trans => format!(
"{accession}:{prefix}.{}",
rendered
.iter()
.map(|member| format!("[{member}]"))
.collect::<Vec<_>>()
.join(";")
),
Mechanism::UnknownPhase => {
format!("{accession}:{prefix}.{}", rendered.join("(;)"))
}
_ => format!("{accession}:{prefix}.[{}]", rendered.join(";")),
}
}
fn render_triple(
frame: &Frame,
position: usize,
deletion: &str,
insertion: &str,
) -> Option<String> {
let label = |index: usize| frame.label(index);
match (deletion.is_empty(), insertion.is_empty()) {
(true, true) => None,
(true, false) => {
if position == 0 {
return None;
}
Some(format!(
"{}_{}ins{insertion}",
label(position - 1),
label(position)
))
}
(false, true) if deletion.len() == 1 => Some(format!("{}del", label(position))),
(false, true) => Some(format!(
"{}_{}del",
label(position),
label(position + deletion.len() - 1)
)),
(false, false) if deletion.len() == 1 => {
Some(format!("{}delins{insertion}", label(position)))
}
(false, false) => Some(format!(
"{}_{}delins{insertion}",
label(position),
label(position + deletion.len() - 1)
)),
}
}
fn candidate_spellings(
design: &Design<'_>,
triples: &[(usize, String, String)],
block: (usize, usize),
alt_block: &str,
) -> Vec<String> {
let frame = design.frame;
let served = frame.served();
let (lo, hi) = block;
let Some(rendered) = design
.members
.iter()
.map(|member| render_member(frame, member))
.collect::<Option<Vec<String>>>()
else {
return Vec::new();
};
let mut out = vec![render_allele(frame, &rendered)];
if let Some(spanning) = render_triple(frame, lo, &served[lo..hi], alt_block) {
out.push(render_allele(frame, std::slice::from_ref(&spanning)));
}
for (index, triple) in triples.iter().enumerate() {
for shifted in equivalent_placements(served, triple.0, &triple.1, &triple.2) {
let Some(text) = render_triple(frame, shifted.0, &shifted.1, &shifted.2) else {
continue;
};
let mut variant = rendered.clone();
variant[index] = text;
out.push(render_allele(frame, &variant));
}
}
if rendered.len() > 1 {
let mut reversed = rendered.clone();
reversed.reverse();
out.push(render_allele(frame, &reversed));
}
for (index, triple) in triples.iter().enumerate() {
let (position, deletion, insertion) = triple;
if deletion.len() < 2 {
continue;
}
let cut = deletion.len() / 2;
let insert_cut = insertion.len().min(cut);
let Some(left) =
render_triple(frame, *position, &deletion[..cut], &insertion[..insert_cut])
else {
continue;
};
let Some(right) = render_triple(
frame,
position + cut,
&deletion[cut..],
&insertion[insert_cut..],
) else {
continue;
};
let mut variant = Vec::with_capacity(rendered.len() + 1);
variant.extend_from_slice(&rendered[..index]);
variant.push(left);
variant.push(right);
variant.extend_from_slice(&rendered[index + 1..]);
out.push(render_allele(frame, &variant));
}
let reference_block = &served[lo..hi];
if !reference_block.is_empty() {
if alt_block == reverse_complement(reference_block) && reference_block.len() >= 2 {
let inv = format!("{}_{}inv", frame.label(lo), frame.label(hi - 1));
out.push(render_allele(frame, std::slice::from_ref(&inv)));
}
if alt_block == format!("{reference_block}{reference_block}") {
let dup = if reference_block.len() == 1 {
format!("{}dup", frame.label(lo))
} else {
format!("{}_{}dup", frame.label(lo), frame.label(hi - 1))
};
out.push(render_allele(frame, std::slice::from_ref(&dup)));
}
}
out
}
fn build_family(design: Design<'_>) -> Attempt {
let frame = design.frame;
let served = frame.served();
let rendered: Option<Vec<String>> = design
.members
.iter()
.map(|member| render_member(frame, member))
.collect();
let Some(rendered) = rendered else {
return Err((design.id, DropReason::NoDenotedSequence));
};
let authored = render_allele(frame, &rendered);
let Some(triples) = triples_of(frame.provider(), &authored) else {
return Err((design.id, DropReason::NoDenotedSequence));
};
let Some(denoted) = apply_triples(served, &triples) else {
return Err((design.id, DropReason::NoDenotedSequence));
};
if denoted == served {
return Err((design.id, DropReason::DenotesTheReference));
}
let lo = triples.iter().map(|t| t.0).min().unwrap_or(0);
let hi = triples.iter().map(|t| t.0 + t.1.len()).max().unwrap_or(0);
if hi > served.len() || lo > hi {
return Err((design.id, DropReason::NoDenotedSequence));
}
let suffix = served.len() - hi;
if denoted.len() < lo + suffix
|| !denoted.starts_with(&served[..lo])
|| !denoted.ends_with(&served[hi..])
{
return Err((design.id, DropReason::NoDenotedSequence));
}
let alt_block = denoted[lo..denoted.len() - suffix].to_string();
let mut spellings = Vec::new();
let mut seen = std::collections::BTreeSet::new();
for candidate in candidate_spellings(&design, &triples, (lo, hi), &alt_block) {
if !seen.insert(candidate.clone()) {
continue;
}
if denoted_by(frame.provider(), served, &candidate).as_deref() == Some(denoted.as_str()) {
spellings.push(candidate);
}
}
if spellings.len() < 2 {
return Err((design.id, DropReason::Singleton));
}
let irreducible = triples
.iter()
.all(|t| equivalent_placements(served, t.0, &t.1, &t.2).is_empty());
let negative_guards = if design.negative_guard_candidate && triples.len() == 2 && irreducible {
vec![SVD_WG010_GUARD]
} else {
Vec::new()
};
let coding_axis_separation_two_or_more = triples.len() == design.members.len()
&& irreducible
&& is_coding_axis_separation_two_or_more_shape(
frame.shape,
&design.members,
design.separation,
design.mechanism,
);
let window = hi - lo + 2 * 128;
Ok(Row {
id: design.id,
kind: RowKind::Family,
stratum: design.stratum,
shape: frame.shape,
core: design.core.to_string(),
denoted: Some(denoted),
spellings,
members: design.members.len(),
separation: design.separation,
block_len: hi - lo,
mechanism: design.mechanism,
prohibition: None,
negative_guards,
coding_axis_separation_two_or_more,
geometry: design.geometry,
region: design.region,
scale_bands: scale_bands(hi - lo, window),
rules: design.rules,
})
}
fn enumerate_members(bounds: &CorpusBounds, out: &mut Vec<Attempt>) {
for (core_index, core) in corpus_cores(bounds.seeds, DENSE_CORE_LEN)
.into_iter()
.enumerate()
{
for shape in RefShape::all() {
let frame = Frame::build(shape, &core);
for &first in PAIRED_KINDS {
for &second in PAIRED_KINDS {
for &payload in DENSE_PAYLOADS {
for &separation in DENSE_SEPARATIONS {
let geometry = if separation == 0 {
Geometry::FlushAdjacent
} else {
Geometry::Disjoint
};
let kinds = [first, second];
let id = format!(
"s{core_index:02}-{}-pair-{}-{}-p{payload}-sep{separation}",
shape.label(),
first.label(),
second.label()
);
push_design(
out,
&frame,
&core,
id,
"members-pairs",
&kinds,
payload,
separation,
geometry,
Region::MidCds,
);
}
}
}
}
for &second in PAIRED_KINDS {
for &payload in DENSE_PAYLOADS {
let kinds = [Kind::Ins, second];
let id = format!(
"s{core_index:02}-{}-endpoint-ins-{}-p{payload}",
shape.label(),
second.label()
);
push_design(
out,
&frame,
&core,
id,
"members-endpoint",
&kinds,
payload,
0,
Geometry::CoincidentEndpoint,
Region::MidCds,
);
}
}
for &count in &MEMBER_COUNTS[1..] {
for pattern in kind_patterns() {
for &payload in DENSE_PAYLOADS {
for &separation in DENSE_SEPARATIONS {
let kinds: Vec<Kind> =
(0..count).map(|index| pattern.kind(index)).collect();
let geometry = if separation == 0 {
Geometry::FlushAdjacent
} else {
Geometry::Disjoint
};
let id = format!(
"s{core_index:02}-{}-m{count}-{}-p{payload}-sep{separation}",
shape.label(),
pattern.label()
);
push_design(
out,
&frame,
&core,
id,
"members-rotations",
&kinds,
payload,
separation,
geometry,
Region::MidCds,
);
}
}
}
}
}
}
}
#[derive(Debug, Clone, Copy)]
enum KindPattern {
Rotate(usize),
Uniform(Kind),
}
impl KindPattern {
fn kind(self, member: usize) -> Kind {
match self {
Self::Rotate(r) => PAIRED_KINDS[(r + member) % PAIRED_KINDS.len()],
Self::Uniform(kind) => kind,
}
}
fn label(self) -> String {
match self {
Self::Rotate(r) => format!("rot{r}"),
Self::Uniform(kind) => format!("all-{}", kind.label()),
}
}
}
fn kind_patterns() -> Vec<KindPattern> {
let mut patterns: Vec<KindPattern> = (0..PAIRED_KINDS.len()).map(KindPattern::Rotate).collect();
patterns.extend(PAIRED_KINDS.iter().copied().map(KindPattern::Uniform));
patterns
}
#[allow(clippy::too_many_arguments)]
fn push_design(
out: &mut Vec<Attempt>,
frame: &Frame,
core: &str,
id: String,
stratum: &'static str,
kinds: &[Kind],
payload: usize,
separation: usize,
geometry: Geometry,
region: Region,
) {
let width: usize = kinds
.iter()
.map(|kind| kind.span(payload) + separation)
.sum::<usize>()
+ 2;
let Some(start) = frame.region_start(region, width) else {
out.push(Err((id, DropReason::UnavailableOnThisAxis)));
return;
};
let members = match layout(frame, start, kinds, payload, separation) {
Ok(members) => members,
Err(reason) => {
out.push(Err((id, reason)));
return;
}
};
let mut rules = rules_for(
kinds,
geometry,
separation,
region,
frame.shape,
kinds.len(),
);
let mechanism = if kinds.len() > 1 {
Mechanism::Cis
} else {
Mechanism::Lone
};
let negative_guard_candidate = is_svd_wg010_shape(frame.shape, kinds, separation);
if negative_guard_candidate {
rules.push("rejected:svd-wg010-frameless-floor-two");
rules.sort_unstable();
rules.dedup();
}
out.push(build_family(Design {
id,
stratum,
frame,
core,
members,
separation,
geometry,
region,
mechanism,
negative_guard_candidate,
rules,
}));
}
fn is_svd_wg010_shape(shape: RefShape, kinds: &[Kind], separation: usize) -> bool {
let frameless = matches!(shape, RefShape::Genomic | RefShape::NonCodingMultiExon(_));
let all_consume_reference = kinds.iter().all(|kind| *kind != Kind::Ins);
frameless && separation == 1 && kinds.len() == 2 && all_consume_reference
}
const SVD_WG010_GUARD: &str = "svd-wg010-frameless-separation-floor-of-two";
fn is_coding_axis_separation_two_or_more_shape(
shape: RefShape,
members: &[Member],
separation: usize,
mechanism: Mechanism,
) -> bool {
let coding = matches!(
shape,
RefShape::CodingSingleExon | RefShape::CodingMultiExon(_)
);
let all_consume_reference = members.iter().all(|member| member.kind != Kind::Ins);
coding
&& separation >= 2
&& members.len() >= 2
&& matches!(mechanism, Mechanism::Cis)
&& all_consume_reference
}
fn enumerate_regions(bounds: &CorpusBounds, out: &mut Vec<Attempt>) {
const REGIONS: &[Region] = &[
Region::Utr5,
Region::CdsStart,
Region::MidCds,
Region::ExonJunction1,
Region::ExonJunction2,
Region::CdsEnd,
Region::Utr3,
];
const PAIRS: &[[Kind; 2]] = &[
[Kind::Del, Kind::Del],
[Kind::Del, Kind::Ins],
[Kind::Dup, Kind::Dup],
[Kind::Sub, Kind::Sub],
[Kind::Delins, Kind::Del],
];
for (core_index, core) in corpus_cores(bounds.seeds, DENSE_CORE_LEN)
.into_iter()
.enumerate()
{
for shape in RefShape::structured() {
let frame = Frame::build(shape, &core);
for ®ion in REGIONS {
for kinds in PAIRS {
for &payload in &[1usize, 2] {
for &separation in &[0usize, 1, 2] {
let geometry = if separation == 0 {
Geometry::FlushAdjacent
} else {
Geometry::Disjoint
};
let id = format!(
"s{core_index:02}-{}-{}-{}-{}-p{payload}-sep{separation}",
shape.label(),
region.label(),
kinds[0].label(),
kinds[1].label()
);
push_design(
out, &frame, &core, id, "regions", kinds, payload, separation,
geometry, region,
);
}
}
}
}
}
}
}
fn enumerate_scale(bounds: &CorpusBounds, out: &mut Vec<Attempt>) {
const PATTERNS: &[[Kind; 2]] = &[
[Kind::Del, Kind::Del],
[Kind::Inv, Kind::Del],
[Kind::Delins, Kind::Del],
];
let mut blocks: Vec<usize> = BLOCK_LADDER.to_vec();
if bounds.extended_scale {
blocks.extend_from_slice(EXTENDED_BLOCK_LADDER);
}
for shape in [RefShape::Genomic, RefShape::CodingMultiExon(Strand::Plus)] {
for &payload in &blocks {
let core_len = payload * 3 + 128;
let core = corpus_cores(1, core_len).into_iter().next_back();
let Some(core) = core else { continue };
let frame = Frame::build(shape, &core);
for kinds in PATTERNS {
let id = format!(
"scale-{}-block{payload}-{}-{}",
shape.label(),
kinds[0].label(),
kinds[1].label()
);
push_design(
out,
&frame,
&core,
id,
"scale-block",
kinds,
payload,
1,
Geometry::Disjoint,
Region::MidCds,
);
}
}
for &separation in SEPARATION_LADDER {
let core_len = separation * 2 + 256;
let core = corpus_cores(1, core_len).into_iter().next_back();
let Some(core) = core else { continue };
let frame = Frame::build(shape, &core);
for kinds in PATTERNS {
let id = format!(
"scale-{}-sep{separation}-{}-{}",
shape.label(),
kinds[0].label(),
kinds[1].label()
);
push_design(
out,
&frame,
&core,
id,
"scale-separation",
kinds,
2,
separation,
Geometry::Disjoint,
Region::MidCds,
);
}
}
}
}
fn enumerate_repeats(bounds: &CorpusBounds, out: &mut Vec<Attempt>) {
for seed in 0..bounds.seeds.max(1) {
for unit_len in [1usize, 2, 3, 4] {
for copies in [3usize, 5, 8] {
let Some((core, unit, array_start)) = repeat_core(unit_len, copies, seed) else {
continue;
};
for shape in [RefShape::Genomic, RefShape::CodingMultiExon(Strand::Plus)] {
let frame = Frame::build(shape, &core);
let offset = frame.core_offset() + array_start;
for new_copies in [copies + 1, copies.saturating_sub(1)] {
if new_copies < 2 || new_copies == copies {
continue;
}
let id = format!(
"repeat-{}-u{unit_len}-c{copies}-to{new_copies}-s{seed}",
shape.label()
);
let member = Member {
kind: Kind::Repeat,
start: offset,
span: unit_len * copies,
payload: String::new(),
repeat: Some((unit.clone(), new_copies)),
};
let sibling_start = offset + unit_len * copies + 2;
if sibling_start + 1 >= frame.served().len() {
out.push(Err((id, DropReason::OutOfRange)));
continue;
}
let sibling = Member {
kind: Kind::Del,
start: sibling_start,
span: 1,
payload: String::new(),
repeat: None,
};
let rules = rules_for(
&[Kind::Repeat, Kind::Del],
Geometry::Disjoint,
2,
Region::MidCds,
shape,
2,
);
out.push(build_family(Design {
id,
stratum: "repeats",
frame: &frame,
core: &core,
members: vec![member, sibling],
separation: 2,
geometry: Geometry::Disjoint,
region: Region::MidCds,
mechanism: Mechanism::Cis,
negative_guard_candidate: false,
rules,
}));
}
}
}
}
}
}
fn enumerate_conflicts(bounds: &CorpusBounds, out: &mut Vec<Attempt>) {
const GEOMETRIES: &[Geometry] = &[
Geometry::Nested,
Geometry::Overlapping,
Geometry::CoincidentInsertions,
Geometry::SelfReplacement,
];
for (core_index, core) in corpus_cores(bounds.seeds, DENSE_CORE_LEN)
.into_iter()
.enumerate()
{
for shape in RefShape::all() {
let frame = Frame::build(shape, &core);
for &geometry in GEOMETRIES {
for &payload in &[2usize, 4] {
let id = format!(
"conflict-s{core_index:02}-{}-{}-p{payload}",
shape.label(),
geometry.label()
);
let width = payload * 3 + 4;
let Some(start) = frame.region_start(Region::MidCds, width) else {
out.push(Err((id, DropReason::UnavailableOnThisAxis)));
continue;
};
let members = match geometry {
Geometry::Nested => vec![
simple_member(Kind::Del, start, payload * 2),
simple_member(Kind::Del, start + 1, payload.max(1)),
],
Geometry::Overlapping => vec![
simple_member(Kind::Del, start, payload * 2),
simple_member(Kind::Del, start + payload, payload * 2),
],
Geometry::CoincidentInsertions => vec![
Member {
kind: Kind::Ins,
start,
span: 0,
payload: payload_bases(frame.served(), start, payload),
repeat: None,
},
Member {
kind: Kind::Ins,
start,
span: 0,
payload: payload_bases(frame.served(), start + 1, payload),
repeat: None,
},
],
Geometry::SelfReplacement => vec![
simple_member(Kind::Del, start, payload * 2),
simple_member(Kind::Dup, start + payload, payload * 2),
],
Geometry::Disjoint
| Geometry::FlushAdjacent
| Geometry::CoincidentEndpoint => {
out.push(Err((id, DropReason::NotAConflictingGeometry)));
continue;
}
};
let last = members
.iter()
.map(|m| m.start + m.span.max(1))
.max()
.unwrap_or(0);
if last >= frame.served().len() {
out.push(Err((id, DropReason::OutOfRange)));
continue;
}
let rendered: Option<Vec<String>> = members
.iter()
.map(|member| render_member(&frame, member))
.collect();
let Some(rendered) = rendered else {
out.push(Err((id, DropReason::NoDenotedSequence)));
continue;
};
let spelling = render_allele(&frame, &rendered);
if denoted_by(frame.provider(), frame.served(), &spelling).is_some() {
out.push(Err((id, DropReason::DenotesTheReference)));
continue;
}
let rules = rules_for(
&[Kind::Del],
geometry,
0,
Region::MidCds,
shape,
members.len(),
);
out.push(Ok(Row {
id,
kind: RowKind::Conflict,
stratum: "conflicts",
shape,
core: core.clone(),
denoted: None,
spellings: vec![spelling],
members: members.len(),
separation: 0,
block_len: payload * 3,
mechanism: Mechanism::Cis,
prohibition: Some((
"general.md:57-no-self-replacement",
Strength::Absolute,
)),
negative_guards: Vec::new(),
coding_axis_separation_two_or_more: false,
geometry,
region: Region::MidCds,
scale_bands: Vec::new(),
rules,
}));
}
}
}
}
}
fn simple_member(kind: Kind, start: usize, span: usize) -> Member {
Member {
kind,
start,
span,
payload: String::new(),
repeat: None,
}
}
fn enumerate_intronic(bounds: &CorpusBounds, out: &mut Vec<Attempt>) {
for (core_index, core) in corpus_cores(bounds.seeds, DENSE_CORE_LEN)
.into_iter()
.enumerate()
{
for shape in RefShape::structured() {
if !shape.is_multi_exon() {
continue;
}
let frame = Frame::build(shape, &core);
let boundaries: Vec<(usize, isize)> = frame
.exons()
.iter()
.enumerate()
.flat_map(|(index, &(start, end))| {
let mut sites = Vec::new();
if index + 1 < frame.exons().len() {
sites.push((end - 1, 1isize));
sites.push((end - 1, 2));
sites.push((end - 1, 5));
}
if index > 0 {
sites.push((start - 1, -1));
sites.push((start - 1, -2));
}
sites
})
.collect();
for (index, delta) in boundaries {
for kind in [Kind::Del, Kind::Dup, Kind::Sub, Kind::Ins] {
for members in [1usize, 2] {
let id = format!(
"intronic-s{core_index:02}-{}-i{index}{delta:+}-{}-m{members}",
shape.label(),
kind.label()
);
let Some(label) = frame.intronic_label(index, delta) else {
out.push(Err((id, DropReason::UnavailableOnThisAxis)));
continue;
};
let member = match kind {
Kind::Del => format!("{label}del"),
Kind::Dup => format!("{label}dup"),
Kind::Sub => {
format!("{label}delinsTT")
}
_ => format!("{label}_{label}insTT"),
};
let member = if kind == Kind::Ins {
let Some(next) = frame.intronic_label(
index,
if delta > 0 { delta + 1 } else { delta - 1 },
) else {
out.push(Err((id, DropReason::UnavailableOnThisAxis)));
continue;
};
if delta > 0 {
format!("{label}_{next}insTT")
} else {
format!("{next}_{label}insTT")
}
} else {
member
};
let rendered = if members == 1 {
vec![member]
} else {
let Some(exonic) = frame
.region_start(Region::MidCds, 2)
.map(|start| format!("{}del", frame.label(start)))
else {
out.push(Err((id, DropReason::UnavailableOnThisAxis)));
continue;
};
if delta > 0 {
vec![exonic, member]
} else {
vec![member, exonic]
}
};
let Some(accession) = frame.wrapped_accession() else {
out.push(Err((id, DropReason::UnavailableOnThisAxis)));
continue;
};
let spelling =
render_allele_as(&accession, shape.prefix(), Mechanism::Cis, &rendered);
if parse_hgvs(&spelling).is_err() {
out.push(Err((id, DropReason::UnavailableOnThisAxis)));
continue;
}
let rules = rules_for(
&[kind],
Geometry::Disjoint,
2,
Region::Intronic,
shape,
members,
);
out.push(Ok(Row {
id,
kind: RowKind::Single,
stratum: "intronic",
shape,
core: core.clone(),
denoted: None,
spellings: vec![spelling],
members,
separation: 0,
block_len: 1,
mechanism: if members > 1 {
Mechanism::Cis
} else {
Mechanism::Lone
},
prohibition: None,
negative_guards: Vec::new(),
coding_axis_separation_two_or_more: false,
geometry: Geometry::Disjoint,
region: Region::Intronic,
scale_bands: Vec::new(),
rules,
}));
}
}
}
}
}
}
fn enumerate_mechanisms(bounds: &CorpusBounds, out: &mut Vec<Attempt>) {
const MECHANISMS: &[Mechanism] = &[
Mechanism::Cis,
Mechanism::UnknownPhase,
Mechanism::Trans,
Mechanism::CompositePayload,
Mechanism::RepeatCount,
];
for (core_index, core) in corpus_cores(bounds.seeds, DENSE_CORE_LEN)
.into_iter()
.enumerate()
{
for shape in RefShape::all() {
let frame = Frame::build(shape, &core);
for &mechanism in MECHANISMS {
for &separation in &[1usize, 3] {
let id = format!(
"mech-s{core_index:02}-{}-{}-sep{separation}",
shape.label(),
mechanism.label()
);
let Some(start) = frame.region_start(Region::MidCds, separation + 12) else {
out.push(Err((id, DropReason::UnavailableOnThisAxis)));
continue;
};
let served = frame.served();
let members = match mechanism {
Mechanism::Cis | Mechanism::UnknownPhase | Mechanism::Trans => vec![
format!("{}del", frame.label(start)),
format!("{}del", frame.label(start + 1 + separation)),
],
Mechanism::CompositePayload => vec![format!(
"{}_{}ins[{};{}]",
frame.label(start),
frame.label(start + 1),
payload_bases(served, start, 2),
payload_bases(served, start + 1, 2)
)],
Mechanism::RepeatCount => {
let unit = &served[start..start + 1];
vec![format!("{}{unit}[3]", frame.label(start))]
}
Mechanism::Lone => continue,
};
let spelling =
render_allele_as(frame.accession(), shape.prefix(), mechanism, &members);
if parse_hgvs(&spelling).is_err() {
out.push(Ok(Row {
id,
kind: RowKind::Prohibited,
stratum: "mechanisms",
shape,
core: core.clone(),
denoted: None,
spellings: vec![spelling],
members: members.len(),
separation,
block_len: 1,
mechanism,
prohibition: Some((
"DNA/alleles.md:20-unknown-phase-without-brackets",
Strength::Conditional,
)),
negative_guards: Vec::new(),
coding_axis_separation_two_or_more: false,
geometry: Geometry::Disjoint,
region: Region::MidCds,
scale_bands: Vec::new(),
rules: vec!["DNA/alleles.md:20-unknown-phase-without-brackets"],
}));
continue;
}
let denotes_one_sequence = mechanism == Mechanism::Cis
|| mechanism == Mechanism::CompositePayload
|| mechanism == Mechanism::RepeatCount;
let denoted = denotes_one_sequence
.then(|| denoted_by(frame.provider(), served, &spelling))
.flatten();
let rules = vec![match mechanism {
Mechanism::Cis => "DNA/alleles.md:16-cis-semicolon",
Mechanism::UnknownPhase => {
"DNA/alleles.md:20-unknown-phase-without-brackets"
}
Mechanism::Trans => "DNA/alleles.md:17-trans-bracket-pairs",
Mechanism::CompositePayload => "general.md:78-brackets-composite-insertion",
Mechanism::RepeatCount => "DNA/repeated.md:5-repeat-format",
Mechanism::Lone => continue,
}];
out.push(Ok(Row {
id,
kind: RowKind::Single,
stratum: "mechanisms",
shape,
core: core.clone(),
denoted,
spellings: vec![spelling],
members: members.len(),
separation,
block_len: 1,
mechanism,
prohibition: None,
negative_guards: Vec::new(),
coding_axis_separation_two_or_more: false,
geometry: Geometry::Disjoint,
region: Region::MidCds,
scale_bands: Vec::new(),
rules,
}));
}
}
}
}
}
struct Prohibition {
slug: &'static str,
rule: &'static str,
strength: Strength,
render: fn(&Frame) -> Option<String>,
lone_only: bool,
}
fn enumerate_prohibited(bounds: &CorpusBounds, out: &mut Vec<Attempt>) {
const PROHIBITIONS: &[Prohibition] = &[
Prohibition {
slug: "genomic-plus-offset",
rule: "checklist.md:16-genomic-has-no-offsets",
strength: Strength::Absolute,
render: |frame| {
(frame.shape == RefShape::Genomic).then(|| format!("{}+2del", PAD_OFFSET + 10))
},
lone_only: false,
},
Prohibition {
slug: "genomic-star-offset",
rule: "checklist.md:16-genomic-has-no-offsets",
strength: Strength::Absolute,
render: |frame| (frame.shape == RefShape::Genomic).then(|| "*10del".to_string()),
lone_only: false,
},
Prohibition {
slug: "hyphen-range",
rule: "checklist.md:45-range-is-underscore",
strength: Strength::Absolute,
render: |frame| {
(frame.shape == RefShape::Genomic)
.then(|| format!("{}-{}del", PAD_OFFSET + 10, PAD_OFFSET + 12))
},
lone_only: false,
},
Prohibition {
slug: "sized-deletion-suffix",
rule: "checklist.md:49-deletion-names-both-endpoints",
strength: Strength::Absolute,
render: |frame| Some(format!("{}del3", frame.label(9))),
lone_only: false,
},
Prohibition {
slug: "single-anchor-insertion",
rule: "checklist.md:30-insertion-needs-two-anchors",
strength: Strength::Absolute,
render: |frame| Some(format!("{}insT", frame.label(9))),
lone_only: false,
},
Prohibition {
slug: "sized-insertion-payload",
rule: "checklist.md:32-insertion-states-its-sequence",
strength: Strength::Absolute,
render: |frame| Some(format!("{}_{}ins6", frame.label(9), frame.label(10))),
lone_only: false,
},
Prohibition {
slug: "alignment-only-base-x",
rule: "standards.md:39-alignment-only-symbols",
strength: Strength::Conditional,
render: |frame| Some(format!("{}delinsX", frame.label(9))),
lone_only: false,
},
Prohibition {
slug: "internal-space",
rule: "general.md:95-no-spaces",
strength: Strength::Absolute,
render: |frame| Some(format!("{}_{} del", frame.label(9), frame.label(11))),
lone_only: false,
},
Prohibition {
slug: "bare-transcript-intronic",
rule: "checklist.md:20-intron-needs-a-genomic-wrapper",
strength: Strength::Conditional,
render: |frame| {
let exon = frame.exons().first().copied()?;
(frame.exons().len() > 1).then(|| format!("{}+2del", frame.label(exon.1 - 1)))
},
lone_only: false,
},
Prohibition {
slug: "incomplete-intronic-range",
rule: "checklist.md:26-intronic-range-is-complete",
strength: Strength::Absolute,
render: |frame| {
let exon = frame.exons().first().copied()?;
(frame.exons().len() > 1).then(|| format!("{}+2_+5del", frame.label(exon.1 - 1)))
},
lone_only: false,
},
];
for (core_index, core) in corpus_cores(bounds.seeds, DENSE_CORE_LEN)
.into_iter()
.enumerate()
{
for shape in RefShape::all() {
let frame = Frame::build(shape, &core);
for prohibition in PROHIBITIONS {
let pairings: &[Mechanism] = if prohibition.lone_only {
&[Mechanism::Lone]
} else {
&[Mechanism::Lone, Mechanism::Cis]
};
for &mechanism in pairings {
let id = format!(
"prohibited-s{core_index:02}-{}-{}-{}",
shape.label(),
prohibition.slug,
mechanism.label()
);
let Some(offender) = (prohibition.render)(&frame) else {
out.push(Err((id, DropReason::UnavailableOnThisAxis)));
continue;
};
let members = match mechanism {
Mechanism::Cis => {
let Some(start) = frame.region_start(Region::MidCds, 4) else {
out.push(Err((id, DropReason::UnavailableOnThisAxis)));
continue;
};
vec![offender, format!("{}del", frame.label(start))]
}
_ => vec![offender],
};
let spelling =
render_allele_as(frame.accession(), shape.prefix(), mechanism, &members);
out.push(Ok(Row {
id,
kind: RowKind::Prohibited,
stratum: "prohibited",
shape,
core: core.clone(),
denoted: None,
spellings: vec![spelling],
members: members.len(),
separation: 0,
block_len: 1,
mechanism,
prohibition: Some((prohibition.rule, prohibition.strength)),
negative_guards: Vec::new(),
coding_axis_separation_two_or_more: false,
geometry: Geometry::Disjoint,
region: Region::Anywhere,
scale_bands: Vec::new(),
rules: vec![prohibition.rule],
}));
}
}
}
}
}
pub const DNA_SYMBOLS: &[char] = &[
'A', 'C', 'G', 'T', 'B', 'D', 'H', 'K', 'M', 'N', 'R', 'S', 'V', 'W', 'Y',
];
fn enumerate_ambiguity(bounds: &CorpusBounds, out: &mut Vec<Attempt>) {
for (core_index, core) in corpus_cores(bounds.seeds, DENSE_CORE_LEN)
.into_iter()
.enumerate()
{
for shape in [RefShape::Genomic, RefShape::CodingMultiExon(Strand::Plus)] {
let frame = Frame::build(shape, &core);
for &symbol in &DNA_SYMBOLS[4..] {
let id = format!("ambiguity-s{core_index:02}-{}-{symbol}", shape.label());
let Some(start) = frame.region_start(Region::MidCds, 8) else {
out.push(Err((id, DropReason::UnavailableOnThisAxis)));
continue;
};
let members = vec![
format!("{}delins{symbol}{symbol}", frame.label(start)),
format!(
"{}_{}ins{symbol}",
frame.label(start + 2),
frame.label(start + 3)
),
];
let spelling = render_allele(&frame, &members);
let Some(denoted) = denoted_by(frame.provider(), frame.served(), &spelling) else {
out.push(Err((id, DropReason::NoDenotedSequence)));
continue;
};
out.push(Ok(Row {
id,
kind: RowKind::Single,
stratum: "ambiguity",
shape,
core: core.clone(),
denoted: Some(denoted),
spellings: vec![spelling],
members: 2,
separation: 1,
block_len: 4,
mechanism: Mechanism::Cis,
prohibition: None,
negative_guards: Vec::new(),
coding_axis_separation_two_or_more: false,
geometry: Geometry::Disjoint,
region: Region::MidCds,
scale_bands: Vec::new(),
rules: vec!["standards.md:15-iupac-nucleotide-symbols"],
}));
}
}
}
}
pub const SINGLE_MEMBER_BY_NATURE: &[&str] = &[
"general.md:78-brackets-composite-insertion",
];
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cores_are_deterministic_and_prefix_stable() {
assert_eq!(corpus_cores(2, 64), corpus_cores(2, 64));
let few = corpus_cores(2, 64);
let many = corpus_cores(6, 64);
assert_eq!(few[..], many[..few.len()]);
for (short, long) in corpus_cores(2, 20).iter().zip(corpus_cores(2, 64)) {
assert_eq!(short.as_str(), &long[..20]);
}
assert_eq!(corpus_cores(1, 20)[0], "TTTTTTTTTAATATATTTTA");
assert_eq!(corpus_cores(1, 20)[1], "CCCCCCCCTGACGTATCCTA");
}
#[test]
fn spdi_positions_are_zero_based_on_the_served_sequence() {
let core = corpus_cores(1, DENSE_CORE_LEN).remove(1);
let genomic = Frame::build(RefShape::Genomic, &core);
let at = PAD_OFFSET + 9;
let spelling = format!("{GENOMIC_CONTIG}:g.{}del", at + 1);
let triples = triples_of(genomic.provider(), &spelling).expect("a genomic triple");
assert_eq!(triples[0].0, at);
assert_eq!(triples[0].1, core[9..10]);
let coding = Frame::build(RefShape::CodingMultiExon(Strand::Plus), &core);
let (cds_start, _) = coding.cds().expect("a coding frame has a CDS");
let spelling = format!("{CODING_ACCESSION}:c.1del");
let triples = triples_of(coding.provider(), &spelling).expect("a coding triple");
assert_eq!(triples[0].0, cds_start - 1);
}
#[test]
fn a_coding_frame_labels_all_three_regions() {
let core = corpus_cores(1, DENSE_CORE_LEN).remove(1);
let frame = Frame::build(RefShape::CodingMultiExon(Strand::Plus), &core);
let (cds_start, cds_end) = frame.cds().expect("a CDS");
assert!(cds_start > 1, "the 5'UTR must be non-empty");
assert!(cds_end < core.len(), "the 3'UTR must be non-empty");
assert_eq!(frame.label(0), format!("-{}", cds_start - 1));
assert_eq!(frame.label(cds_start - 1), "1");
assert_eq!(frame.label(cds_end), "*1");
assert_eq!(frame.exons().len(), 3, "three exons, so two junctions");
let control = Frame::build(RefShape::CodingSingleExon, &core);
assert_eq!(control.exons().len(), 1);
assert_eq!(control.cds().map(|c| c.0), Some(1));
assert!(
control.region_start(Region::Utr5, 2).is_none(),
"the #1478 control shape must have no 5'UTR to place a row in"
);
}
#[test]
fn a_minus_strand_frame_reverses_the_genomic_layout() {
let core = corpus_cores(1, DENSE_CORE_LEN).remove(1);
let frame = Frame::build(RefShape::CodingMultiExon(Strand::Minus), &core);
assert_eq!(frame.served(), core.as_str());
let (cds_start, _) = frame.cds().expect("a CDS");
let spelling = format!("{CODING_ACCESSION}:c.1del");
let triples = triples_of(frame.provider(), &spelling).expect("a triple");
assert_eq!(triples[0].1, core[cds_start - 1..cds_start]);
}
#[test]
fn the_applier_declines_only_what_denotes_no_sequence() {
let reference = "AAAACCCCGGGG";
assert_eq!(
apply_triples(
reference,
&[
(4, "CCCC".to_string(), String::new()),
(4, String::new(), "TT".to_string()),
],
)
.as_deref(),
Some("AAAATTGGGG")
);
assert_eq!(
apply_triples(
reference,
&[
(4, "CCCC".to_string(), String::new()),
(6, "CC".to_string(), String::new()),
],
),
None
);
assert_eq!(
apply_triples(
reference,
&[
(4, String::new(), "TT".to_string()),
(4, String::new(), "GG".to_string()),
],
),
None
);
assert_eq!(
apply_triples(reference, &[(4, "GGGG".to_string(), String::new())]),
None
);
}
#[test]
fn the_coding_axis_merge_population_is_what_it_says_it_is() {
let built = corpus(&CorpusBounds::default());
let mut checked = 0usize;
for row in &built.rows {
if !row.coding_axis_separation_two_or_more {
continue;
}
assert_eq!(
row.kind,
RowKind::Family,
"{}: only family rows carry the flag",
row.id
);
assert!(
matches!(
row.shape,
RefShape::CodingSingleExon | RefShape::CodingMultiExon(_)
),
"{}: {:?} is not a coding axis",
row.id,
row.shape
);
assert!(
row.separation >= 2,
"{}: separation {} is below the floor the instrument documents",
row.id,
row.separation
);
assert!(row.is_multi_member(), "{}: not multi-member", row.id);
assert!(
matches!(row.mechanism, Mechanism::Cis),
"{}: mechanism {:?} — a merged trans or unknown-phase allele is a defect, not a \
population this instrument may count",
row.id,
row.mechanism
);
assert!(
!row.negative_guards.contains(&SVD_WG010_GUARD),
"{}: carries the SVD-WG010 guard as well, so the two domains overlap",
row.id
);
checked += 1;
}
assert!(
checked > 0,
"VACUOUS: the corpus builds no coding-axis row separated by two or more unchanged \
nucleotides, so the merge counter measures nothing"
);
assert_eq!(
checked,
built.coding_axis_separation_two_or_more_rows(),
"the accessor and a direct scan disagree"
);
}
#[test]
fn equivalent_placements_find_both_ends_of_a_run() {
let core = "GTAAAAATCG";
let placements = equivalent_placements(core, 4, "A", "");
let offsets: Vec<usize> = placements.iter().map(|p| p.0).collect();
assert_eq!(offsets, vec![2, 6], "the run spans core[2..7]");
assert!(equivalent_placements(core, 0, "G", "").is_empty());
}
#[test]
fn every_family_is_a_real_confluence_family() {
let built = corpus(&CorpusBounds::default());
assert!(
built.rows.len() > 2_000,
"the corpus collapsed to {} rows",
built.rows.len()
);
let mut checked = 0usize;
for row in &built.rows {
if row.kind != RowKind::Family {
continue;
}
let frame = row.frame();
let expected = row.denoted.as_deref().expect("a family denotes a sequence");
assert!(row.spellings.len() >= 2, "{} is a singleton", row.id);
for spelling in &row.spellings {
let applied = denoted_by(frame.provider(), frame.served(), spelling)
.unwrap_or_else(|| panic!("{spelling} does not apply"));
assert_eq!(applied, expected, "{spelling} denotes a different sequence");
}
checked += 1;
if checked >= 400 {
break;
}
}
assert!(checked > 0, "no families to check — the corpus is vacuous");
}
#[test]
fn every_conflict_row_denotes_no_sequence() {
let built = corpus(&CorpusBounds::default());
let conflicts: Vec<&Row> = built
.rows
.iter()
.filter(|row| row.kind == RowKind::Conflict)
.collect();
assert!(
conflicts.len() >= 20,
"only {} conflict rows — #1456 is rebuilt",
conflicts.len()
);
for row in conflicts {
let frame = row.frame();
assert_eq!(
denoted_by(frame.provider(), frame.served(), &row.spellings[0]),
None,
"{} denotes a sequence, so it is not a conflict",
row.id
);
}
}
#[test]
fn the_three_recorded_blindnesses_are_varied() {
let built = corpus(&CorpusBounds::default());
let geometries = built.by_geometry();
for geometry in [
Geometry::Disjoint,
Geometry::FlushAdjacent,
Geometry::CoincidentEndpoint,
Geometry::Nested,
Geometry::Overlapping,
Geometry::CoincidentInsertions,
Geometry::SelfReplacement,
] {
assert!(
geometries.get(geometry.label()).copied().unwrap_or(0) > 0,
"no rows with geometry {} — #1456 is rebuilt",
geometry.label()
);
}
let bands = built.by_scale_band();
for band in [
"canonical-pad-128",
"tie-break-sweep-256",
"split-block-1024",
"canonical-window-4096",
"window-past-canonical-4096",
] {
assert!(
bands.get(band).copied().unwrap_or(0) > 0,
"no rows reach {band} — #1460 is rebuilt"
);
}
let regions = built.by_region();
for region in [
Region::Utr5,
Region::CdsStart,
Region::MidCds,
Region::ExonJunction1,
Region::ExonJunction2,
Region::CdsEnd,
Region::Utr3,
Region::Intronic,
] {
assert!(
regions.get(region.label()).copied().unwrap_or(0) > 0,
"no rows in region {} — #1478 is rebuilt",
region.label()
);
}
let shapes = built.by_shape();
for shape in RefShape::all() {
assert!(
shapes.get(shape.label()).copied().unwrap_or(0) > 0,
"no rows on reference shape {}",
shape.label()
);
}
}
#[test]
fn multi_member_rows_dominate_the_corpus() {
let built = corpus(&CorpusBounds::default());
let share = built.multi_member_rows() as f64 / built.rows.len() as f64;
assert!(
share > 0.9,
"multi-member share is {share:.4}; real corpora are 0.00006 and this corpus \
exists to invert that"
);
}
#[test]
fn every_rule_tag_is_reachable_and_mostly_multi_member() {
let built = corpus(&CorpusBounds::default());
let coverage = built.by_rule();
assert!(!coverage.is_empty(), "no rule tags — coverage is vacuous");
for (rule, seen) in &coverage {
assert!(seen.rows > 0, "{rule} is tagged but reached by no row");
}
let single_member_only: Vec<&str> = coverage
.iter()
.filter(|(_, seen)| seen.multi_member_rows == 0)
.map(|(rule, _)| *rule)
.filter(|rule| !SINGLE_MEMBER_BY_NATURE.contains(rule))
.collect();
assert!(
single_member_only.is_empty(),
"these rules are never exercised in a multi-member row, and are not in \
SINGLE_MEMBER_BY_NATURE: {single_member_only:?}"
);
}
}