use sim_lib_discrete_comb::permutations;
use sim_lib_pitch_set::PitchClassMask;
use crate::{RowError, RowFamilySet, RowOperation, ToneRow};
const COMBINATORIAL_PARTITIONS: [usize; 4] = [2, 3, 4, 6];
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CombinatorialBlockEvidence {
pub source_block_index: usize,
pub partner_block_index: usize,
pub source_ordinals: Vec<u8>,
pub partner_ordinals: Vec<u8>,
pub source: PitchClassMask,
pub complement: PitchClassMask,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CombinatorialPartition {
pub block_size: usize,
pub partner_block_order: Vec<usize>,
pub blocks: Vec<CombinatorialBlockEvidence>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CombinatorialPartner {
pub operation: RowOperation,
pub source: PitchClassMask,
pub complement: PitchClassMask,
pub partition: CombinatorialPartition,
}
#[derive(Clone, Debug, PartialEq, Eq, Default)]
pub struct CombinatorialityReport {
pub partners: Vec<CombinatorialPartner>,
}
pub fn analyze_combinatoriality(row: &ToneRow) -> CombinatorialityReport {
let family = RowFamilySet::of(row);
let mut partners = Vec::new();
for alias in family.aliases() {
for partition_size in COMBINATORIAL_PARTITIONS {
if let Ok(Some(partner)) =
analyze_combinatoriality_partition(row, alias.operation, partition_size)
{
partners.push(partner);
}
}
}
CombinatorialityReport { partners }
}
pub fn analyze_combinatoriality_partition(
row: &ToneRow,
operation: RowOperation,
partition_size: usize,
) -> Result<Option<CombinatorialPartner>, RowError> {
validate_partition_size(partition_size)?;
let source_blocks = contiguous_masks(row, partition_size);
let partner_row = row.apply(operation).into_row();
let partner_blocks = contiguous_masks(&partner_row, partition_size);
let block_count = source_blocks.len();
let aggregate = PitchClassMask::from_pitch_classes(row.classes());
for partner_block_order in permutations(block_count) {
let blocks = source_blocks
.iter()
.enumerate()
.map(|(source_block_index, source)| {
let partner_block_index = partner_block_order[source_block_index];
let complement = partner_blocks[partner_block_index];
let start = source_block_index * partition_size;
let partner_start = partner_block_index * partition_size;
CombinatorialBlockEvidence {
source_block_index,
partner_block_index,
source_ordinals: (start..start + partition_size)
.map(|ordinal| ordinal as u8)
.collect(),
partner_ordinals: (partner_start..partner_start + partition_size)
.map(|ordinal| ordinal as u8)
.collect(),
source: *source,
complement,
}
})
.collect::<Vec<_>>();
let exact_cover = blocks.iter().all(|block| {
block.source.is_disjoint_from(block.complement)
&& block.source.union(block.complement) == aggregate
});
if exact_cover {
let partition = CombinatorialPartition {
block_size: partition_size,
partner_block_order,
blocks,
};
return Ok(Some(CombinatorialPartner {
operation,
source: partition.blocks[0].source,
complement: partition.blocks[0].complement,
partition,
}));
}
}
Ok(None)
}
fn validate_partition_size(size: usize) -> Result<(), RowError> {
if COMBINATORIAL_PARTITIONS.contains(&size) {
Ok(())
} else {
Err(RowError::InvalidPartitionSize { size })
}
}
fn contiguous_masks(row: &ToneRow, partition_size: usize) -> Vec<PitchClassMask> {
row.classes()
.chunks(partition_size)
.map(PitchClassMask::from_pitch_classes)
.collect()
}