#![allow(clippy::type_complexity)]
use std::{
collections::{HashMap, HashSet},
fmt::{Debug, Formatter, Write},
time::Instant,
};
use bellframe::{Mask, Row, RowBuf, SameStageVec, Truth};
use itertools::Itertools;
use super::{ChunkEquivalenceMap, ChunkIdInFirstPart};
use crate::{
graph::{Chunk, ChunkId, PerPartLength, RowIdx},
parameters::Parameters,
};
pub(super) fn set_links(
chunks: &mut HashMap<ChunkId, Chunk>,
chunk_equiv_map: &mut ChunkEquivalenceMap,
params: &Parameters,
) {
let start = Instant::now();
let chunk_ids_and_lengths = chunks
.iter()
.map(|(id, chunk)| (id.clone(), chunk.per_part_length))
.collect::<HashSet<_>>();
let falseness_table = FalsenessTable::new(&chunk_ids_and_lengths, params);
log::debug!(" Falseness table built in {:.2?}", start.elapsed());
let start = Instant::now();
chunks.retain(|id, chunk| {
falseness_table
.set_falseness_links(
id,
chunk.per_part_length,
&mut chunk.false_chunks,
chunk_equiv_map,
&chunk_ids_and_lengths,
)
.is_true() });
log::debug!(" Falseness links set in {:.2?}", start.elapsed());
}
#[derive(Debug, Clone)]
struct FalsenessTable {
falseness_entries: HashMap<ChunkRange, FalsenessEntry>,
}
#[derive(Debug, Clone)]
enum FalsenessEntry {
SelfFalse,
FalseCourseHeads(Vec<(Mask, Vec<(ChunkRange, RowBuf)>)>),
}
impl FalsenessTable {
fn new(chunks: &HashSet<(ChunkId, PerPartLength)>, params: &Parameters) -> Self {
let mut masks_used = HashSet::<(ChunkRange, Mask)>::new();
for (method_idx, method_data) in params.methods.iter_enumerated() {
for lead_head_mask in method_data.allowed_lead_head_masks(params) {
for (id, len) in chunks {
if id.method == method_idx && lead_head_mask.matches(&id.lead_head) {
masks_used
.insert((ChunkRange::new(id.row_idx, *len), lead_head_mask.clone()));
}
}
}
}
let mut masks_used_in_all_parts = masks_used
.iter()
.cartesian_product(params.part_head_group.rows())
.map(|((range, mask), part_head)| (*range, part_head * mask))
.collect::<HashSet<_>>();
reduce_masks(&mut masks_used, &mut masks_used_in_all_parts, params);
let (self_false_ranges, row_groups) = group_rows(masks_used_in_all_parts, params);
let false_chunk_transpositions =
generate_false_chunk_transpositions(&masks_used, &row_groups);
let falseness_entries =
generate_falseness_entries(self_false_ranges, false_chunk_transpositions);
Self { falseness_entries }
}
fn set_falseness_links(
&self,
id: &ChunkId,
length: PerPartLength,
false_chunk_vec: &mut Vec<ChunkId>,
chunk_equiv_map: &mut ChunkEquivalenceMap,
chunk_ids_and_lengths: &HashSet<(ChunkId, PerPartLength)>,
) -> Truth {
let fchs = match &self.falseness_entries[&ChunkRange::new(id.row_idx, length)] {
FalsenessEntry::FalseCourseHeads(fchs) => fchs,
FalsenessEntry::SelfFalse => return Truth::False,
};
false_chunk_vec.clear();
for (mask, false_ranges) in fchs {
if mask.matches(&id.lead_head) {
for (false_range, lead_head_transposition) in false_ranges {
let false_lead_head = id.lead_head.as_ref() * lead_head_transposition;
let false_id = ChunkIdInFirstPart {
lead_head: false_lead_head,
row_idx: false_range.start,
};
let (equiv_false_id, ph_rotation) = chunk_equiv_map.normalise(&false_id);
if &equiv_false_id == id && !ph_rotation.is_identity() {
return Truth::False; }
let false_id_and_len = (equiv_false_id.clone(), false_range.len);
if chunk_ids_and_lengths.contains(&false_id_and_len) {
false_chunk_vec.push(equiv_false_id);
}
}
}
}
Truth::True
}
}
fn generate_falseness_entries(
self_false_ranges: HashSet<ChunkRange>,
false_chunk_transpositions: FalseTranspositions,
) -> HashMap<ChunkRange, FalsenessEntry> {
let mut falseness_entries = HashMap::<ChunkRange, FalsenessEntry>::new();
for range in self_false_ranges {
falseness_entries.insert(range, FalsenessEntry::SelfFalse);
}
let mut false_entries_by_range =
HashMap::<ChunkRange, Vec<(Mask, Vec<(ChunkRange, RowBuf)>)>>::new();
for ((range, mask), false_ranges) in false_chunk_transpositions {
let mut false_chunks = Vec::<(ChunkRange, RowBuf)>::new();
for ((false_range, _false_mask), false_transpositions) in false_ranges {
for transposition in false_transpositions {
false_chunks.push((*false_range, transposition));
}
}
false_entries_by_range
.entry(*range)
.or_default()
.push((mask.clone(), false_chunks));
}
for (range, entry) in false_entries_by_range {
falseness_entries.insert(range, FalsenessEntry::FalseCourseHeads(entry));
}
falseness_entries
}
type RowGroups = HashMap<Mask, SameStageVec>;
type FalseTranspositions<'masks, 'groups> =
HashMap<&'masks (ChunkRange, Mask), HashMap<&'groups (ChunkRange, Mask), HashSet<RowBuf>>>;
fn reduce_masks(
masks_used: &mut HashSet<(ChunkRange, Mask)>,
masks_used_in_all_parts: &mut HashSet<(ChunkRange, Mask)>,
params: &Parameters,
) {
let mut fixed_bell_mask = Mask::any(params.stage);
for (bell, place) in params.fixed_bells() {
fixed_bell_mask
.set_bell(bell, place)
.expect("Fixed bells shouldn't repeat");
}
let masks_by_range = masks_used_in_all_parts
.iter()
.into_group_map_by(|(range, _masks)| *range);
let reduced_ranges = masks_by_range
.into_iter()
.filter(|(_range, masks)| masks.len() > 50) .map(|(range, _mask)| range)
.collect::<HashSet<_>>();
for mask_set in [masks_used_in_all_parts, masks_used] {
mask_set.retain(|(range, _mask)| !reduced_ranges.contains(range));
mask_set.extend(
reduced_ranges
.iter()
.map(|range| (*range, fixed_bell_mask.clone())),
);
}
if !reduced_ranges.is_empty() {
let fmt_range = |range: &ChunkRange| -> String {
let method = ¶ms.methods[range.start.method];
let mut s = method.shorthand();
if range.start.sub_lead_idx == 0 && range.len.as_usize() == method.lead_len() {
} else {
write!(
s,
"({:?}+{})",
range.start.sub_lead_idx,
range.len.as_usize()
)
.unwrap();
}
s
};
log::debug!(
"Computing all false lead heads for {}",
reduced_ranges.iter().map(fmt_range).join(", ")
);
}
}
fn group_rows(
masks_used_in_all_parts: HashSet<(ChunkRange, Mask)>,
params: &Parameters,
) -> (HashSet<ChunkRange>, HashMap<(ChunkRange, Mask), RowGroups>) {
let mut self_false_ranges = HashSet::<ChunkRange>::new();
let mut row_groups = HashMap::<(ChunkRange, Mask), RowGroups>::new();
'range_mask_loop: for (range, mask) in &masks_used_in_all_parts {
let plain_course = params.methods[range.start.method].plain_course();
if self_false_ranges.contains(range) {
continue;
}
let mut rows_so_far = HashSet::<&Row>::new();
let mut row_groups_for_this_range: RowGroups = HashMap::new();
for offset in 0..range.len.as_usize() {
let row_index = (range.start.sub_lead_idx + offset) % plain_course.len();
let row = plain_course.get_row(row_index).unwrap();
if !rows_so_far.insert(row) {
self_false_ranges.insert(*range);
continue 'range_mask_loop;
}
let transposed_mask = mask * row;
row_groups_for_this_range
.entry(transposed_mask)
.or_insert_with(|| SameStageVec::new(params.stage))
.push(row);
}
row_groups.insert((*range, mask.clone()), row_groups_for_this_range);
}
for (range, _) in row_groups.keys() {
assert!(!self_false_ranges.contains(range));
}
(self_false_ranges, row_groups)
}
fn generate_false_chunk_transpositions<'masks, 'groups>(
masks_used: &'masks HashSet<(ChunkRange, Mask)>,
row_groups: &'groups HashMap<(ChunkRange, Mask), RowGroups>,
) -> FalseTranspositions<'masks, 'groups> {
let mut false_chunk_transpositions: FalseTranspositions = HashMap::new();
for (range_mask1, (range_mask2, row_groups2)) in masks_used.iter().cartesian_product(row_groups)
{
let row_groups1 = match row_groups.get(range_mask1) {
Some(rg) => rg,
None => continue, };
let fch_entry = false_chunk_transpositions
.entry(range_mask1)
.or_default()
.entry(range_mask2)
.or_default();
for ((row_mask1, rows1), (row_mask2, rows2)) in
row_groups1.iter().cartesian_product(row_groups2)
{
if row_mask1.is_compatible_with(row_mask2) {
for (row1, row2) in rows1.iter().cartesian_product(rows2) {
let false_course_head = Row::solve_xa_equals_b(row2, row1);
fch_entry.insert(false_course_head);
}
}
}
}
false_chunk_transpositions
}
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
struct ChunkRange {
start: RowIdx,
len: PerPartLength,
}
impl ChunkRange {
fn new(start: RowIdx, len: PerPartLength) -> Self {
Self { start, len }
}
}
impl Debug for ChunkRange {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"ChunkRange({:?},{}+{})",
self.start.method, self.start.sub_lead_idx, self.len
)
}
}