use std::collections::{BTreeMap, BTreeSet};
use std::sync::Arc;
use rustc_hash::FxHashSet;
use crate::analysis::matchtypes::apply_match;
use crate::cyclotomic::IsRing;
use crate::geom::glue::junctions_glueable;
use crate::geom::matches::{EdgeRange, Segment, TileMatch};
use crate::geom::rat::Rat;
use crate::geom::snake::Snake;
use crate::geom::tileset::TileSet;
use crate::stringmatch::BitParallelMatcher;
pub struct BpSeed<T: IsRing> {
tileset: Arc<TileSet<T>>,
matcher: Arc<BitParallelMatcher>,
}
impl<T: IsRing> Clone for BpSeed<T> {
fn clone(&self) -> Self {
BpSeed {
tileset: Arc::clone(&self.tileset),
matcher: Arc::clone(&self.matcher),
}
}
}
impl<T: IsRing> BpSeed<T> {
pub fn new(tileset: Arc<TileSet<T>>) -> Self {
let sequences: Vec<Vec<i8>> = tileset.rats().iter().map(|r| r.seq().to_vec()).collect();
let matcher = Arc::new(BitParallelMatcher::new(&sequences));
BpSeed { tileset, matcher }
}
pub fn tileset(&self) -> &Arc<TileSet<T>> {
&self.tileset
}
}
pub struct MatchFinder<T: IsRing> {
set_a: Arc<TileSet<T>>,
set_b: Arc<TileSet<T>>,
b_matcher: Arc<BitParallelMatcher>,
}
impl<T: IsRing> MatchFinder<T> {
pub fn new(tileset: Arc<TileSet<T>>) -> Self {
let sequences: Vec<Vec<i8>> = tileset.rats().iter().map(|r| r.seq().to_vec()).collect();
let b_matcher = Arc::new(BitParallelMatcher::new(&sequences));
MatchFinder {
set_a: Arc::clone(&tileset),
set_b: tileset,
b_matcher,
}
}
pub fn crossing(a: Arc<TileSet<T>>, b: Arc<TileSet<T>>) -> Self {
Self::crossing_with_seed(a, BpSeed::new(b))
}
pub fn crossing_with_seed(a: Arc<TileSet<T>>, seed: BpSeed<T>) -> Self {
MatchFinder {
set_a: a,
set_b: seed.tileset,
b_matcher: seed.matcher,
}
}
fn maximal_rc_matches(&self, i: usize, j: usize) -> Vec<TileMatch> {
let mut matches = self.b_matcher.stream_boundary(self.set_a.rat(i).seq(), j);
for m in &mut matches {
m.a.tile_id = i;
}
matches
}
fn maximal_rc_matches_at_positions(
&self,
i: usize,
j: usize,
positions: &[usize],
) -> Vec<TileMatch> {
if positions.is_empty() {
return vec![];
}
let boundary = self.set_a.rat(i).seq();
let n_a = boundary.len();
let mut keep = vec![false; n_a];
for &p in positions {
if p < n_a {
keep[p] = true;
}
}
let mut matches = self.b_matcher.stream_boundary(boundary, j);
matches.retain(|m| keep[m.a.range.start_offset]);
for m in &mut matches {
m.a.tile_id = i;
}
matches
}
pub fn set_a(&self) -> &Arc<TileSet<T>> {
&self.set_a
}
pub fn set_b(&self) -> &Arc<TileSet<T>> {
&self.set_b
}
pub fn rat_a(&self, i: usize) -> &Rat<T> {
self.set_a.rat(i)
}
pub fn rat_b(&self, j: usize) -> &Rat<T> {
self.set_b.rat(j)
}
pub fn num_tiles_a(&self) -> usize {
self.set_a.num_tiles()
}
pub fn num_tiles_b(&self) -> usize {
self.set_b.num_tiles()
}
pub fn apply_match(&self, m: &TileMatch) -> Rat<T> {
apply_match(m, self.set_a.rats(), self.set_b.rats())
}
pub fn shared_boundaries(&self, i: usize, j: usize) -> Vec<TileMatch> {
self.maximal_rc_matches(i, j)
}
pub fn valid_matches(&self, i: usize, j: usize) -> Vec<TileMatch> {
let mut m = Self::validated_matches(self.candidates_for_pair(i, j));
Self::sort_by_interval(&mut m);
m
}
pub fn valid_matches_with_rats(&self, i: usize, j: usize) -> Vec<(Rat<T>, TileMatch)> {
let mut m = Self::validated_matches_with_rats(self.candidates_for_pair(i, j));
m.sort_by(|a, b| {
a.1.a
.range
.start_offset
.cmp(&b.1.a.range.start_offset)
.then_with(|| a.1.b.range.start_offset.cmp(&b.1.b.range.start_offset))
});
m
}
}
impl<T: IsRing> MatchFinder<T> {
pub fn valid_matches_filtered(
&self,
i: usize,
j: usize,
active: &[bool],
) -> Vec<(Rat<T>, TileMatch)> {
let a = self.set_a.rat(i);
let b = self.set_b.rat(j);
let n_a = a.len();
let n_b = b.len();
if n_a == 0 || n_b == 0 || active.is_empty() {
return Vec::new();
}
let max_match_len = n_a.min(n_b);
let mut near_active = vec![false; n_a];
for pos in 0..n_a {
if active[pos] {
for d in 0..=max_match_len {
near_active[(pos + n_a - d) % n_a] = true;
near_active[(pos + d) % n_a] = true;
}
}
}
let scan_positions: Vec<usize> = (0..n_a).filter(|&p| near_active[p]).collect();
let mut raw: Vec<(Rat<T>, TileMatch)> = Vec::new();
let cmi_matches = self.maximal_rc_matches_at_positions(i, j, &scan_positions);
for m in &cmi_matches {
let (ns, len, ne) = a.get_match(
(m.a.range.start_offset as i64, m.b.range.start_offset as i64),
b,
);
if len <= 1 {
continue;
}
if !junctions_glueable(a.seq(), ns as usize, len, b.seq(), ne as usize) {
continue;
}
let ns_u = ns.rem_euclid(n_a as i64) as usize;
if !match_touches_active(ns_u, len, n_a, active) {
continue;
}
if let Ok(glued) = a.try_glue_precomputed((ns, len, ne), b, true) {
raw.push((
glued,
TileMatch::new(
Segment::new(i, EdgeRange::new(ns_u, len)),
Segment::new(j, EdgeRange::new(ne.rem_euclid(n_b as i64) as usize, len)),
),
));
}
}
let seq_a = a.seq();
let seq_b = b.seq();
for ia in 0..n_a {
for ib in 0..n_b {
if !junctions_glueable(seq_a, ia, 1, seq_b, ib) {
continue;
}
let (ns, len, ne) = a.get_match((ia as i64, ib as i64), b);
if len != 1 {
continue;
}
let ns_u = ns.rem_euclid(n_a as i64) as usize;
if !match_touches_active(ns_u, len, n_a, active) {
continue;
}
if let Ok(glued) = a.try_glue_precomputed((ns, len, ne), b, true) {
raw.push((
glued,
TileMatch::new(
Segment::new(i, EdgeRange::new(ns_u, len)),
Segment::new(
j,
EdgeRange::new(ne.rem_euclid(n_b as i64) as usize, len),
),
),
));
}
}
}
let mut seen: FxHashSet<Rat<T>> = FxHashSet::default();
raw.into_iter()
.filter(|(rat, _)| seen.insert(rat.clone()))
.collect()
}
pub fn all_valid_matches(&self) -> Vec<TileMatch> {
self.valid_matches_for_pairs(&self.all_pairs())
}
pub fn valid_matches_for_pairs(&self, pairs: &[(usize, usize)]) -> Vec<TileMatch> {
let mut m = Self::validated_matches(self.collect_candidates(pairs));
Self::sort_global(&mut m);
m
}
pub fn valid_results_for_pairs(&self, pairs: &[(usize, usize)]) -> BTreeSet<Rat<T>> {
Self::validated_results(self.collect_candidates(pairs))
}
fn candidates_for_pair(&self, i: usize, j: usize) -> BTreeMap<Rat<T>, Vec<TileMatch>> {
let a = self.set_a.rat(i);
let b = self.set_b.rat(j);
let n_a = a.len();
let n_b = b.len();
let mut groups: BTreeMap<Rat<T>, Vec<TileMatch>> = BTreeMap::new();
if n_a == 0 || n_b == 0 {
return groups;
}
let cmi_matches = self.maximal_rc_matches(i, j);
for m in &cmi_matches {
let (ns, len, ne) = a.get_match(
(m.a.range.start_offset as i64, m.b.range.start_offset as i64),
b,
);
if len <= 1 {
continue;
}
if !junctions_glueable(a.seq(), ns as usize, len, b.seq(), ne as usize) {
continue;
}
if let Ok(glued) = a.try_glue_precomputed((ns, len, ne), b, true) {
groups.entry(glued).or_default().push(TileMatch::new(
Segment::new(i, EdgeRange::new(ns as usize, len)),
Segment::new(j, EdgeRange::new(ne as usize, len)),
));
}
}
let seq_a = a.seq();
let seq_b = b.seq();
for ia in 0..n_a {
for ib in 0..n_b {
if !junctions_glueable(seq_a, ia, 1, seq_b, ib) {
continue;
}
let (ns, len, ne) = a.get_match((ia as i64, ib as i64), b);
if len != 1 {
continue;
}
if let Ok(glued) = a.try_glue_precomputed((ns, len, ne), b, true) {
groups.entry(glued).or_default().push(TileMatch::new(
Segment::new(i, EdgeRange::new(ns as usize, len)),
Segment::new(j, EdgeRange::new(ne as usize, len)),
));
}
}
}
groups
}
fn all_pairs(&self) -> Vec<(usize, usize)> {
let na = self.num_tiles_a();
let nb = self.num_tiles_b();
(0..na).flat_map(|i| (0..nb).map(move |j| (i, j))).collect()
}
fn collect_candidates(&self, pairs: &[(usize, usize)]) -> BTreeMap<Rat<T>, Vec<TileMatch>> {
let mut all: BTreeMap<Rat<T>, Vec<TileMatch>> = BTreeMap::new();
for &(i, j) in pairs {
for (rat, matches) in self.candidates_for_pair(i, j) {
all.entry(rat).or_default().extend(matches);
}
}
all
}
fn validated_matches(groups: BTreeMap<Rat<T>, Vec<TileMatch>>) -> Vec<TileMatch> {
groups
.into_iter()
.filter(|(rat, _)| Snake::<T>::try_from(rat.seq()).is_ok())
.flat_map(|(_, matches)| matches)
.collect()
}
fn validated_matches_with_rats(
groups: BTreeMap<Rat<T>, Vec<TileMatch>>,
) -> Vec<(Rat<T>, TileMatch)> {
groups
.into_iter()
.filter(|(rat, _)| Snake::<T>::try_from(rat.seq()).is_ok())
.flat_map(|(rat, matches)| matches.into_iter().map(move |m| (rat.clone(), m)))
.collect()
}
fn validated_results(groups: BTreeMap<Rat<T>, Vec<TileMatch>>) -> BTreeSet<Rat<T>> {
groups
.into_iter()
.filter(|(rat, _)| Snake::<T>::try_from(rat.seq()).is_ok())
.map(|(rat, _)| rat)
.collect()
}
fn sort_by_interval(results: &mut [TileMatch]) {
results.sort_by(|a, b| {
a.a.range
.start_offset
.cmp(&b.a.range.start_offset)
.then_with(|| a.b.range.start_offset.cmp(&b.b.range.start_offset))
});
}
fn sort_global(results: &mut [TileMatch]) {
results.sort_by(|a, b| {
a.a.tile_id
.cmp(&b.a.tile_id)
.then_with(|| a.b.tile_id.cmp(&b.b.tile_id))
.then_with(|| a.a.range.start_offset.cmp(&b.a.range.start_offset))
.then_with(|| a.b.range.start_offset.cmp(&b.b.range.start_offset))
});
}
}
fn match_touches_active(start_a: usize, mlen: usize, n: usize, active: &[bool]) -> bool {
if mlen == 0 || n == 0 {
return false;
}
let before = (start_a + n - 1) % n;
if active[before] {
return true;
}
let after = (start_a + mlen) % n;
if active[after] {
return true;
}
for i in 0..mlen {
if active[(start_a + i) % n] {
return true;
}
}
false
}