///|
/// Exposed single-strand polarity; unknown natural termini are not inferred.
pub(all) enum EndKind {
Blunt
FivePrime
ThreePrime
} derive(Eq, Debug, ToJson)
///|
/// Exposed sequence is always written 5-prime to 3-prime.
pub(all) struct End {
kind : EndKind
sequence : String
} derive(Eq, Debug, ToJson)
///|
/// Sequence-only complementarity, NOT a prediction of laboratory ligation.
pub(all) enum JoinStatus {
NoJoin
PossibleJoin
CertainJoin
} derive(Eq, Debug, ToJson)
///|
fn validate_site(dna : Dna, site : Site) -> Unit raise RestrictError {
let n = dna.length()
if !site.cuttable || site.stagger < -192 || site.stagger > 192 {
raise InvalidInput("SITE_GEOMETRY")
}
if dna.topology == Linear {
if site.top < 0 ||
site.top > n ||
site.bottom < 0 ||
site.bottom > n ||
site.bottom - site.top != site.stagger {
raise InvalidInput("SITE_GEOMETRY")
}
} else if site.top < 0 ||
site.top >= n ||
site.bottom < 0 ||
site.bottom >= n ||
site.stagger.abs() >= n ||
wrap(site.top + site.stagger, n) != site.bottom {
raise InvalidInput("SITE_GEOMETRY")
}
}
///|
/// Return ends on the piece before/after a reference cut, respectively.
pub fn cut_ends(dna : Dna, site : Site) -> (End, End) raise RestrictError {
validate_site(dna, site)
if site.stagger == 0 {
return ({ kind: Blunt, sequence: "" }, { kind: Blunt, sequence: "" })
}
let begin = if site.stagger > 0 { site.top } else { site.top + site.stagger }
let top = circular_bases(dna, begin, site.stagger.abs())
let complement = reverse_complement(Dna::new(top)).sequence()
if site.stagger > 0 {
(
{ kind: FivePrime, sequence: complement },
{ kind: FivePrime, sequence: top },
)
} else {
(
{ kind: ThreePrime, sequence: top },
{ kind: ThreePrime, sequence: complement },
)
}
}
///|
/// Conservative Cartesian base-set complementarity; ambiguity is never certain.
pub fn compatible_ends(a : End, b : End) -> JoinStatus raise RestrictError {
for end in [a, b] {
if end.kind == Blunt {
if end.sequence != "" {
raise InvalidInput("BLUNT_SEQUENCE")
}
} else {
if end.sequence.length() > 192 {
raise LimitExceeded("END_LENGTH")
}
let _ = Dna::new(end.sequence)
}
}
if a.kind == Blunt || b.kind == Blunt {
return if a.kind == b.kind { CertainJoin } else { NoJoin }
}
let left = Dna::new(a.sequence)
let right = reverse_complement(Dna::new(b.sequence))
if left.length() > 192 || right.length() > 192 {
raise LimitExceeded("END_LENGTH")
}
if a.kind != b.kind || left.length() != right.length() {
return NoJoin
}
let lm = left.bases.to_array().map(base_mask)
let rm = right.bases.to_array().map(base_mask)
let mut certain = true
for i in 0..