use std::{
collections::HashSet,
marker::PhantomData,
ops::{Deref, Range, RangeInclusive},
sync::atomic::AtomicBool,
};
use bellframe::{
method::LABEL_LEAD_END, music::AtRowPositions, Bell, Mask, PlaceNot, Row, RowBuf, Stage, Stroke,
};
use itertools::Itertools;
use crate::{
graph::ChunkId,
group::PartHeadGroup,
utils::{
lengths::{PerPartLength, TotalLength},
Boundary,
},
Composition, Config, Search, Update,
};
#[derive(Debug, Clone)]
pub struct Parameters {
pub length: RangeInclusive<TotalLength>,
pub stage: Stage,
pub num_comps: usize,
pub require_truth: bool,
pub methods: MethodVec<Method>,
pub splice_style: SpliceStyle,
pub splice_weight: f32,
pub calls: CallVec<Call>,
pub call_display_style: CallDisplayStyle, pub atw_weight: Option<f32>,
pub require_atw: bool,
pub start_row: RowBuf,
pub end_row: RowBuf,
pub part_head_group: PartHeadGroup,
pub course_weights: Vec<(Mask, f32)>,
pub music_types: MusicTypeVec<MusicType>,
pub start_stroke: Stroke,
}
impl Parameters {
pub fn run(self) -> crate::Result<Vec<Composition>> {
self.run_with_config(Config::default())
}
pub fn run_with_config(self, config: Config) -> crate::Result<Vec<Composition>> {
let mut comps = Vec::<Composition>::new();
let update_fn = |update| {
if let Update::Comp(comp) = update {
comps.push(comp);
}
};
Search::new(self, config)?.run(update_fn, &AtomicBool::new(false));
Ok(comps)
}
pub fn max_length(&self) -> TotalLength {
*self.length.end()
}
pub fn is_spliced(&self) -> bool {
self.methods.len() > 1
}
pub fn num_parts(&self) -> usize {
self.part_head_group.size()
}
pub fn is_multipart(&self) -> bool {
self.num_parts() > 1
}
pub fn boundary_row(&self, boundary: Boundary) -> &Row {
match boundary {
Boundary::Start => &self.start_row,
Boundary::End => &self.end_row,
}
}
pub fn fixed_bells(&self) -> Vec<(Bell, usize)> {
let mut fixed_bells = self.stage.bells().collect_vec();
for m in &self.methods {
let f = self.fixed_bells_of_method(m);
fixed_bells.retain(|b| f.contains(b));
}
fixed_bells
.iter()
.map(|b| (self.start_row[b.index()], b.index()))
.collect_vec()
}
pub fn working_bells(&self) -> Vec<Bell> {
self.stage
.bells()
.filter(|b| !self.is_fixed_bell(*b))
.collect_vec()
}
pub fn is_fixed_bell(&self, bell: Bell) -> bool {
self.fixed_bells().iter().any(|(b, _place)| *b == bell)
}
pub fn lead_labels_used(&self) -> HashSet<String> {
let mut defined_labels = HashSet::<String>::new();
for m in &self.methods {
for labels in m.first_lead().annots() {
defined_labels.extend(labels.iter().cloned());
}
}
defined_labels
}
pub fn method_id_to_idx(&self, id: MethodId) -> MethodIdx {
self.methods.position(|m| m.id == id).unwrap()
}
pub fn call_id_to_idx(&self, id: CallId) -> CallIdx {
self.calls.position(|c| c.id == id).unwrap()
}
pub fn music_type_id_to_idx(&self, id: MusicTypeId) -> MusicTypeIdx {
self.music_types.position(|mt| mt.id == id).unwrap()
}
pub fn get_method_by_id(&self, id: MethodId) -> &Method {
&self.methods[self.method_id_to_idx(id)]
}
pub fn get_call_by_id(&self, id: CallId) -> &Call {
&self.calls[self.call_id_to_idx(id)]
}
pub fn get_music_type_by_id(&self, id: MusicTypeId) -> &MusicType {
&self.music_types[self.music_type_id_to_idx(id)]
}
pub(crate) fn chunk_lead_regions(
&self,
id: &ChunkId,
length: PerPartLength,
) -> Vec<(RowBuf, Range<usize>)> {
let method = &self.methods[id.method];
let mut lead_head: RowBuf = id.lead_head.deref().to_owned();
let mut length_left = length.as_usize();
let mut sub_lead_idx = id.sub_lead_idx;
let mut lead_regions = Vec::new();
while length_left > 0 {
let length_left_in_lead = method.lead_len() - sub_lead_idx;
let chunk_len = usize::min(length_left_in_lead, length_left);
let chunk_end = sub_lead_idx + chunk_len;
lead_regions.push((lead_head.clone(), sub_lead_idx..chunk_end));
assert!(chunk_len <= method.lead_len());
length_left -= chunk_len;
sub_lead_idx += chunk_len;
assert!(sub_lead_idx <= method.lead_len());
if sub_lead_idx == method.lead_len() {
sub_lead_idx = 0;
lead_head *= method.lead_head();
}
}
lead_regions
}
fn fixed_bells_of_method(&self, method: &bellframe::Method) -> HashSet<Bell> {
let mut fixed_bells: HashSet<Bell> = method.lead_head().fixed_bells().collect();
for call in &self.calls {
Self::filter_bells_fixed_by_call(method, call, &mut fixed_bells);
}
fixed_bells
}
fn filter_bells_fixed_by_call(
method: &bellframe::Method,
call: &Call,
set: &mut HashSet<Bell>,
) {
for sub_lead_idx_after_call in method.label_indices(&call.label_from) {
let idx_before_call =
(sub_lead_idx_after_call + method.lead_len() - 1) % method.lead_len();
let idx_after_call = idx_before_call + 1;
let row_before_call = method.first_lead().get_row(idx_before_call).unwrap();
let row_after_no_call = method.first_lead().get_row(idx_after_call).unwrap();
let mut row_after_call = row_before_call.to_owned();
call.place_notation.permute(&mut row_after_call);
for (bell_after_no_call, bell_after_call) in
row_after_no_call.bell_iter().zip(&row_after_call)
{
if bell_after_call != bell_after_no_call {
set.remove(&bell_after_call);
}
}
}
}
pub(crate) fn method_list_string(&self, methods: &[MethodIdx]) -> String {
if methods.len() == self.methods.len() {
"all methods".to_owned()
} else {
let shorthand = |idx: &MethodIdx| -> String { self.methods[*idx].shorthand() };
match methods {
[] => unreachable!(),
[idx] => shorthand(idx),
[idxs @ .., idx2, idx3] => {
let mut s = String::new();
for idx in idxs {
s.push_str(&shorthand(idx));
s.push_str(", ");
}
s.push_str(&shorthand(idx2));
s.push_str(" and ");
s.push_str(&shorthand(idx3));
s
}
}
}
}
pub fn get_method(&self, id: MethodId) -> &Method {
self.methods.iter().find(|mt| mt.id == id).unwrap()
}
pub fn get_call(&self, id: CallId) -> &Call {
self.calls.iter().find(|mt| mt.id == id).unwrap()
}
pub fn get_music_type(&self, id: MusicTypeId) -> &MusicType {
self.music_types.iter().find(|mt| mt.id == id).unwrap()
}
}
#[derive(Debug, Clone)]
pub struct Method {
pub id: MethodId,
pub inner: bellframe::Method,
pub custom_shorthand: String,
pub count_range: OptionalRangeInclusive,
pub start_indices: Vec<isize>,
pub end_indices: Vec<isize>,
pub allowed_courses: Vec<CourseSet>,
}
impl Method {
pub fn shorthand(&self) -> String {
if self.custom_shorthand.is_empty() {
default_shorthand(&self.title())
} else {
self.custom_shorthand.clone()
}
}
pub fn add_sub_lead_idx(&self, sub_lead_idx: usize, len: PerPartLength) -> usize {
(sub_lead_idx + len.as_usize()) % self.lead_len()
}
#[allow(clippy::type_complexity)]
pub fn start_and_end_locations(
&self,
params: &Parameters,
) -> (Vec<(RowBuf, usize)>, Vec<(RowBuf, usize)>) {
let start_locations = self.boundary_locations(¶ms.start_row, Boundary::Start, params);
let end_locations = self.boundary_locations(¶ms.end_row, Boundary::End, params);
(start_locations, end_locations)
}
pub fn boundary_locations(
&self,
row: &Row,
boundary: Boundary,
params: &Parameters,
) -> Vec<(RowBuf, usize)> {
let mut locations = Vec::new();
for sub_lead_idx in self.wrapped_indices(boundary, params) {
let lead_head = Row::solve_xa_equals_b(self.row_in_plain_lead(sub_lead_idx), row);
if self.is_lead_head_allowed(&lead_head, params) {
locations.push((lead_head, sub_lead_idx));
}
}
locations
}
pub fn wrapped_indices(&self, boundary: Boundary, params: &Parameters) -> Vec<usize> {
let (start_indices, end_indices) = self.wrapped_start_end_indices(params);
match boundary {
Boundary::Start => start_indices,
Boundary::End => end_indices,
}
}
pub fn wrapped_start_end_indices(&self, params: &Parameters) -> (Vec<usize>, Vec<usize>) {
let mut start_indices = self.wrap_sub_lead_indices(&self.start_indices);
let mut end_indices = self.wrap_sub_lead_indices(&self.end_indices);
if params.part_head_group.is_multi_part() {
let r#union = start_indices
.iter()
.filter(|idx| end_indices.contains(idx))
.copied()
.collect_vec();
start_indices = r#union.clone();
end_indices = r#union;
}
(start_indices, end_indices)
}
fn wrap_sub_lead_indices(&self, indices: &[isize]) -> Vec<usize> {
let wrap_index = |idx: &isize| -> usize {
let lead_len_i = self.inner.lead_len() as isize;
(*idx % lead_len_i + lead_len_i) as usize % self.inner.lead_len()
};
indices.iter().map(wrap_index).collect_vec()
}
pub fn is_lead_head_allowed(&self, row: &Row, params: &Parameters) -> bool {
self.allowed_lead_head_masks(params)
.into_iter()
.any(|m| m.matches(row))
}
pub fn allowed_lead_head_masks(&self, params: &Parameters) -> Vec<Mask> {
self.allowed_lead_head_masks_with_extras(params).0
}
pub fn allowed_lead_head_masks_with_extras(
&self,
params: &Parameters,
) -> (Vec<Mask>, ExtraMasks) {
CourseSet::to_lead_masks(&self.allowed_courses, &self.inner, params)
}
}
impl std::ops::Deref for Method {
type Target = bellframe::Method;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct MethodId(pub u16);
impl From<u16> for MethodId {
fn from(value: u16) -> Self {
Self(value)
}
}
impl From<MethodId> for u16 {
fn from(value: MethodId) -> Self {
value.0
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub enum SpliceStyle {
LeadLabels,
Calls,
}
impl Default for SpliceStyle {
fn default() -> Self {
Self::LeadLabels
}
}
pub fn default_shorthand(title: &str) -> String {
title
.chars()
.next()
.expect("Can't have empty method title")
.to_string()
}
#[derive(Debug, Clone)]
pub struct Call {
pub id: CallId,
pub symbol: String,
pub calling_positions: Vec<String>,
pub label_from: String,
pub label_to: String,
pub place_notation: PlaceNot,
pub weight: f32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct CallId(pub u16);
impl From<u16> for CallId {
fn from(value: u16) -> Self {
Self(value)
}
}
impl From<CallId> for u16 {
fn from(value: CallId) -> Self {
value.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum CallDisplayStyle {
Positional,
CallingPositions(Bell),
}
impl Call {
pub(crate) fn short_symbol(&self) -> &str {
match self.symbol.as_str() {
"-" | "–" => "", s => s, }
}
pub fn lead_end_call(id: CallId, place_not: PlaceNot, symbol: &str, weight: f32) -> Self {
Self {
id,
symbol: symbol.to_owned(),
calling_positions: default_calling_positions(&place_not),
label_from: LABEL_LEAD_END.to_owned(),
label_to: LABEL_LEAD_END.to_owned(),
place_notation: place_not,
weight,
}
}
}
#[derive(Debug, Clone, Copy)]
pub enum BaseCallType {
Near,
Far,
}
pub const DEFAULT_BOB_WEIGHT: f32 = -1.8;
pub const DEFAULT_SINGLE_WEIGHT: f32 = -2.3;
pub const DEFAULT_MISC_CALL_WEIGHT: f32 = -3.0;
pub fn base_calls(
id_generator: &mut IdGenerator<CallId>,
type_: BaseCallType,
bob_weight: Option<f32>,
single_weight: Option<f32>,
stage: Stage,
) -> CallVec<Call> {
let n = stage.num_bells_u8();
let mut calls = CallVec::new();
if let Some(bob_weight) = bob_weight {
let bob_pn = match type_ {
BaseCallType::Near => PlaceNot::parse("14", stage).unwrap(),
BaseCallType::Far => PlaceNot::from_slice(&mut [0, n - 3], stage).unwrap(),
};
calls.push(Call::lead_end_call(
id_generator.next(),
bob_pn,
"-",
bob_weight,
));
}
if let Some(single_weight) = single_weight {
let single_pn = match type_ {
BaseCallType::Near => PlaceNot::parse("1234", stage).unwrap(),
BaseCallType::Far => {
PlaceNot::from_slice(&mut [0, n - 3, n - 2, n - 1], stage).unwrap()
}
};
calls.push(Call::lead_end_call(
id_generator.next(),
single_pn,
"s",
single_weight,
));
}
calls
}
#[allow(clippy::branches_sharing_code)]
pub fn default_calling_positions(place_not: &PlaceNot) -> Vec<String> {
let named_positions = "LIBFVXSEN";
let mut positions =
named_positions
.chars()
.map(|c| c.to_string())
.chain((named_positions.len()..).map(|i| format!("{}ths", i + 1)))
.take(place_not.stage().num_bells())
.collect_vec();
macro_rules! replace_pos {
($idx: expr, $new_val: expr) => {
if let Some(v) = positions.get_mut($idx) {
v.clear();
v.push($new_val);
}
};
}
if place_not.contains(1) {
replace_pos!(1, 'B');
replace_pos!(2, 'T');
}
macro_rules! replace_mwh {
($ind: expr, $new_val: expr) => {
if let Some(place) = place_not.stage().num_bells().checked_sub(1 + $ind) {
if place >= 4 {
if let Some(v) = positions.get_mut(place) {
v.clear();
v.push($new_val);
}
}
}
};
}
if place_not.stage().is_even() {
replace_mwh!(2, 'M');
replace_mwh!(1, 'W');
replace_mwh!(0, 'H');
} else {
replace_mwh!(2, 'W');
replace_mwh!(1, 'M');
replace_mwh!(0, 'H');
}
positions
}
#[derive(Debug, Clone)]
pub struct MusicType {
pub id: MusicTypeId,
pub inner: bellframe::MusicType,
pub optional_weights: AtRowPositions<Option<f32>>,
pub count_range: OptionalRangeInclusive,
}
impl MusicType {
pub fn as_overall_score(&self, counts: AtRowPositions<usize>) -> f32 {
(counts.map(|x| x as f32) * self.weights()).total()
}
pub fn masked_total(&self, counts: AtRowPositions<usize>) -> usize {
counts.masked(!self.mask(), 0).total()
}
pub fn weights(&self) -> AtRowPositions<f32> {
self.optional_weights.map(|v| v.unwrap_or(0.0))
}
pub fn mask(&self) -> AtRowPositions<bool> {
self.optional_weights.map(|v| v.is_some())
}
}
impl Deref for MusicType {
type Target = bellframe::MusicType;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct MusicTypeId(pub u16);
impl From<u16> for MusicTypeId {
fn from(value: u16) -> Self {
Self(value)
}
}
impl From<MusicTypeId> for u16 {
fn from(value: MusicTypeId) -> Self {
value.0
}
}
#[derive(Debug, Clone)]
pub struct CourseSet {
pub masks: Vec<Mask>,
pub any_stroke: bool,
pub any_bells: bool,
}
type ExtraMasks = Vec<(Mask, RowBuf)>;
impl CourseSet {
pub(crate) fn to_lead_masks(
allowed_courses: &[CourseSet],
method: &bellframe::Method,
params: &Parameters,
) -> (Vec<Mask>, ExtraMasks) {
let fixed_bells = params.fixed_bells();
let specified_lead_head_masks: HashSet<Mask> = allowed_courses
.iter()
.flat_map(|c| c.as_lead_masks(method, &fixed_bells))
.collect();
let specified_course_head_masks: HashSet<Mask> = allowed_courses
.iter()
.flat_map(|c| c.as_course_masks(method, &fixed_bells))
.collect();
let mut extra_course_masks: ExtraMasks = Vec::new();
let mut lead_head_masks = HashSet::<Mask>::new();
for specified_course_mask in specified_course_head_masks {
for part_head in params.part_head_group.rows() {
let ch_in_new_part = part_head * &specified_course_mask;
for lead_head in method.lead_head().closure() {
lead_head_masks.insert(&ch_in_new_part * &lead_head);
}
let is_specified = specified_lead_head_masks
.iter()
.any(|m| ch_in_new_part.is_subset_of(m));
if !is_specified {
extra_course_masks.push((specified_course_mask.clone(), part_head.to_owned()));
}
}
}
let mut filtered_lead_head_masks = Vec::new();
for mask in &lead_head_masks {
let is_implied_by_another_mask = lead_head_masks
.iter()
.any(|mask2| mask.is_strict_subset_of(mask2));
if !is_implied_by_another_mask {
filtered_lead_head_masks.push(mask.clone());
}
}
(filtered_lead_head_masks, extra_course_masks)
}
fn as_lead_masks(
&self,
method: &bellframe::Method,
fixed_bells: &[(Bell, usize)],
) -> Vec<Mask> {
let course_masks = self.as_course_masks(method, fixed_bells);
let mut lead_head_masks = Vec::new();
for course_mask in course_masks {
for lead_head in method.lead_head().closure() {
lead_head_masks.push(&course_mask * lead_head);
}
}
lead_head_masks
}
fn as_course_masks(
&self,
method: &bellframe::Method,
fixed_bells: &[(Bell, usize)],
) -> Vec<Mask> {
let mut course_masks: Vec<Mask> = self.masks.clone();
if self.any_bells {
let mut expanded_masks = Vec::new();
for mask in &course_masks {
let min_bell = mask.bells().flatten().min().unwrap_or(Bell::TREBLE);
let max_bell = mask.bells().flatten().max().unwrap_or(Bell::TREBLE);
let min_offset = -(min_bell.index_u8() as i16);
let max_offset = (method.stage().num_bells_u8() - max_bell.number()) as i16;
for offset in min_offset..=max_offset {
expanded_masks.push(
Mask::from_bells(
mask.bells()
.map(|maybe_bell| maybe_bell.map(|b| b + offset)),
)
.unwrap(), );
}
}
course_masks = expanded_masks;
}
if self.any_stroke {
course_masks = course_masks
.into_iter()
.flat_map(|mask| [&mask * method.lead_end(), mask])
.collect_vec();
}
course_masks
.into_iter()
.filter_map(|mask| try_fixing_bells(mask, fixed_bells))
.collect_vec()
}
}
impl From<Mask> for CourseSet {
fn from(value: Mask) -> Self {
Self::from(vec![value])
}
}
impl From<Vec<Mask>> for CourseSet {
fn from(masks: Vec<Mask>) -> Self {
Self {
masks,
any_bells: false,
any_stroke: false,
}
}
}
fn try_fixing_bells(mut mask: Mask, fixed_bells: &[(Bell, usize)]) -> Option<Mask> {
for &(bell, place) in fixed_bells {
mask.set_bell(bell, place).ok()?;
}
Some(mask)
}
#[derive(Debug, Clone, Copy, Default)]
pub struct OptionalRangeInclusive {
pub min: Option<usize>,
pub max: Option<usize>,
}
impl OptionalRangeInclusive {
pub const OPEN: Self = Self {
min: None,
max: None,
};
pub fn is_set(self) -> bool {
self.min.is_some() || self.max.is_some()
}
pub fn or(self, other: Self) -> Self {
Self {
min: self.min.or(other.min),
max: self.max.or(other.max),
}
}
pub fn or_range(self, other: &Range<usize>) -> Range<usize> {
let min = self.min.unwrap_or(other.start);
let max = self
.max
.map(|x| x + 1) .unwrap_or(other.end);
min..max
}
}
#[derive(Debug, Clone)]
pub struct IdGenerator<Id> {
next_id: u16,
_id: PhantomData<Id>,
}
impl<Id: From<u16> + Into<u16>> IdGenerator<Id> {
pub fn starting_at_zero() -> Self {
Self {
next_id: 0,
_id: PhantomData,
}
}
#[allow(clippy::should_implement_trait)]
pub fn next(&mut self) -> Id {
let id = Id::from(self.next_id);
self.next_id += 1;
id
}
}
index_vec::define_index_type! { pub struct MethodIdx = usize; }
index_vec::define_index_type! { pub struct CallIdx = usize; }
index_vec::define_index_type! { pub struct MusicTypeIdx = usize; }
pub type MethodVec<T> = index_vec::IndexVec<MethodIdx, T>;
pub type CallVec<T> = index_vec::IndexVec<CallIdx, T>;
pub type MusicTypeVec<T> = index_vec::IndexVec<MusicTypeIdx, T>;
#[cfg(test)]
mod tests {
use bellframe::{PlaceNot, Stage};
use itertools::Itertools;
fn char_vec(string: &str) -> Vec<String> {
string.chars().map(|c| c.to_string()).collect_vec()
}
#[test]
fn default_calling_positions() {
#[rustfmt::skip]
let cases = &[
("145", Stage::DOUBLES, char_vec("LIBFH")),
("125", Stage::DOUBLES, char_vec("LBTFH")),
("1", Stage::DOUBLES, char_vec("LIBFH")),
("14", Stage::MINOR, char_vec("LIBFWH")),
("1234", Stage::MINOR, char_vec("LBTFWH")),
("1456", Stage::MINOR, char_vec("LIBFWH")),
("147", Stage::TRIPLES, char_vec("LIBFWMH")),
("12347", Stage::TRIPLES, char_vec("LBTFWMH")),
("14", Stage::MAJOR, char_vec("LIBFVMWH")),
("1234", Stage::MAJOR, char_vec("LBTFVMWH")),
("16", Stage::MAJOR, char_vec("LIBFVMWH")),
("1678", Stage::MAJOR, char_vec("LIBFVMWH")),
("1256", Stage::MAJOR, char_vec("LBTFVMWH")),
("123456", Stage::MAJOR, char_vec("LBTFVMWH")),
("14", Stage::ROYAL, char_vec("LIBFVXSMWH")),
("16", Stage::ROYAL, char_vec("LIBFVXSMWH")),
("18", Stage::ROYAL, char_vec("LIBFVXSMWH")),
("1890", Stage::ROYAL, char_vec("LIBFVXSMWH")),
("14", Stage::MAXIMUS, char_vec("LIBFVXSENMWH")),
("1234", Stage::MAXIMUS, char_vec("LBTFVXSENMWH")),
];
for (pn_str, stage, exp_positions) in cases {
let positions =
super::default_calling_positions(&PlaceNot::parse(pn_str, *stage).unwrap());
assert_eq!(positions, *exp_positions);
}
}
}