diff --git a/benches/benchmark.rs b/benches/benchmark.rs index 624f06c..5c19d17 100644 --- a/benches/benchmark.rs +++ b/benches/benchmark.rs @@ -1,4 +1,5 @@ use chai::config::Config; +use chai::encoder::generic::GenericEncoder; use criterion::{criterion_group, criterion_main, Criterion}; use chai::cli::{Cli, Command}; @@ -32,9 +33,9 @@ fn process_cli_input( b: &mut Criterion, ) -> Result<(), Error> { let representation = Representation::new(config)?; - let encoder = Encoder::new(&representation, elements, &assets)?; - let buffer = Buffer::new(&encoder); - let objective = Objective::new(&representation, encoder, assets)?; + let encoder = GenericEncoder::new(&representation, elements, &assets)?; + let buffer = Buffer::new(&encoder.encodables, encoder.get_space()); + let objective = Objective::new(&representation, Box::new(encoder), assets)?; let constraints = Constraints::new(&representation)?; let mut problem = ElementPlacementProblem::new(representation, constraints, objective, buffer)?; let mut candidate = problem.generate_candidate(); diff --git a/src/encoder.rs b/src/encoder.rs index c614d3d..083f2eb 100644 --- a/src/encoder.rs +++ b/src/encoder.rs @@ -1,15 +1,10 @@ //! 编码引擎 -use rustc_hash::FxHashMap; +use rustc_hash::FxHashSet; -use crate::config::EncoderConfig; -use crate::error::Error; -use crate::representation::{ - Assemble, AssembleList, Assets, AutoSelect, Buffer, Code, Entry, Frequency, Key, KeyMap, - Occupation, Representation, Sequence, MAX_COMBINATION_LENGTH, MAX_WORD_LENGTH, -}; -use std::collections::HashSet; -use std::{cmp::Reverse, fmt::Debug, iter::zip}; +use crate::representation::{Buffer, Frequency, KeyMap, Sequence}; + +pub mod generic; /// 一个可编码对象 #[derive(Debug, Clone)] @@ -18,265 +13,64 @@ pub struct Encodable { pub length: usize, pub sequence: Sequence, pub frequency: u64, - pub level: i64, + pub level: u64, pub hash: u16, pub index: usize, } -pub struct Encoder { - pub encodables: Vec, - pub transition_matrix: Vec>, - pub config: EncoderConfig, - auto_select: AutoSelect, - pub radix: u64, - select_keys: Vec, - pub short_code: Option<[Vec; MAX_WORD_LENGTH]>, -} - #[derive(Debug)] pub struct CompiledScheme { pub prefix: usize, pub select_keys: Vec, } -impl Encoder { - pub fn adapt( - frequency: &Frequency, - words: &HashSet, - ) -> (Frequency, Vec<(String, String, u64)>) { - let mut new_frequency = Frequency::new(); - let mut transition_pairs = Vec::new(); - for (word, value) in frequency { - if words.contains(word) { - new_frequency.insert(word.clone(), new_frequency.get(word).unwrap_or(&0) + *value); - } else { - // 使用逆向最大匹配算法来分词 - let chars: Vec<_> = word.chars().collect(); - let mut end = chars.len(); - let mut last_match: Option = None; - while end > 0 { - let mut start = end - 1; - // 如果最后一个字不在词表里,就不要了 - if !words.contains(&chars[start].to_string()) { - end -= 1; - continue; - } - // 继续向前匹配,看看是否能匹配到更长的词 - while start > 0 - && words.contains(&chars[(start - 1)..end].iter().collect::()) - { - start -= 1; - } - // 确定最大匹配 - let sub_word: String = chars[start..end].iter().collect(); - *new_frequency.entry(sub_word.clone()).or_default() += *value; - if let Some(last) = last_match { - transition_pairs.push((sub_word.clone(), last, *value)); - } - last_match = Some(sub_word); - end = start; - } - } - } - (new_frequency, transition_pairs) - } - - /// 提供配置表示、拆分表、词表和共用资源来创建一个编码引擎 - /// 字需要提供拆分表 - /// 词只需要提供词表,它对应的拆分序列从字推出 - pub fn new( - representation: &Representation, - resource: AssembleList, - assets: &Assets, - ) -> Result { - let encoder = &representation.config.encoder; - let max_length = encoder.max_length; - if max_length >= 8 { - return Err("目前暂不支持最大码长大于等于 8 的方案计算!".into()); - } - - // 预处理词频 - let all_words: HashSet<_> = resource.iter().map(|x| x.name.clone()).collect(); - let (frequency, transition_pairs) = Self::adapt(&assets.frequency, &all_words); - - // 将拆分序列映射降序排列 - let mut encodables = Vec::new(); - for (index, assemble) in resource.into_iter().enumerate() { - let Assemble { - name, - importance, - level, - .. - } = assemble.clone(); - let sequence = representation.transform_elements(assemble)?; - let char_frequency = *frequency.get(&name).unwrap_or(&0); - let frequency = char_frequency * importance / 100; - let hash: u16 = (name.chars().map(|x| x as u32).sum::()) as u16; - encodables.push(Encodable { - name: name.clone(), - length: name.chars().count(), - sequence, - frequency, - level, - hash, - index, - }); - } - - encodables.sort_by_key(|x| Reverse(x.frequency)); - - let map_word_to_index: FxHashMap = encodables - .iter() - .enumerate() - .map(|(index, x)| (x.name.clone(), index)) - .collect(); - let mut transition_matrix = vec![vec![]; encodables.len()]; - for (from, to, value) in transition_pairs { - let from = *map_word_to_index.get(&from).unwrap(); - let to = *map_word_to_index.get(&to).unwrap(); - transition_matrix[from].push((to, value)); - } - for row in transition_matrix.iter_mut() { - row.sort_by_key(|x| x.0); - } - - // 处理自动上屏 - let auto_select = representation.transform_auto_select()?; - - // 处理简码规则 - let mut short_code = None; - if let Some(configs) = &encoder.short_code { - short_code = Some(representation.transform_short_code(configs.clone())?); - } - let encoder = Encoder { - encodables, - transition_matrix, - auto_select, - config: encoder.clone(), - radix: representation.radix, - select_keys: representation.select_keys.clone(), - short_code, - }; - Ok(encoder) - } - - pub fn get_actual_code(&self, code: u64, rank: i8, length: u32) -> (u64, u32) { - if rank == 0 && *self.auto_select.get(code as usize).unwrap_or(&true) { - return (code, length); - } - let select = *self - .select_keys - .get(rank.unsigned_abs() as usize) - .unwrap_or(&self.select_keys[0]) as u64 - * self.radix.pow(length); - (code + select, length + 1) - } - - pub fn encode_full(&self, keymap: &KeyMap, buffer: &mut Buffer, occupation: &mut Occupation) { - let weights = (0..=self.config.max_length) - .map(|x| self.radix.pow(x as u32)) - .collect::>(); - for (encodable, pointer) in zip(&self.encodables, &mut buffer.full) { - let sequence = &encodable.sequence; - let mut code = 0_u64; - for (element, weight) in zip(sequence, &weights) { - code += keymap[*element] as u64 * weight; - } - pointer.code = code; - pointer.rank = occupation.rank(code) as i8; - occupation.insert(code, encodable.hash); - } - } - pub fn encode_short( - &self, - buffer: &mut Buffer, - full_occupation: &Occupation, - short_occupation: &mut Occupation, - ) { - if self.short_code.is_none() { - return; - } - let short_code = self.short_code.as_ref().unwrap(); - // 优先简码 - for ((code, pointer), encodable) in - zip(zip(&buffer.full, &mut buffer.short), &self.encodables) - { - if encodable.level == -1 { - continue; - } - let modulo = self.radix.pow(encodable.level as u32); - let short = code.code % modulo; - pointer.code = short; - pointer.rank = 0; - short_occupation.insert(short, encodable.hash); - } - // 常规简码 - for ((code, pointer), encodable) in - zip(zip(&buffer.full, &mut buffer.short), &self.encodables) - { - let schemes = &short_code[encodable.length - 1]; - if schemes.is_empty() || encodable.level >= 0 { - continue; - } - let full = &code.code; - let mut has_reduced = false; - let hash = encodable.hash; - for scheme in schemes { - let CompiledScheme { - prefix, - select_keys, - } = scheme; - // 如果根本没有这么多码,就放弃 - if *full < self.radix.pow((*prefix - 1) as u32) { +pub fn adapt( + frequency: &Frequency, + words: &FxHashSet, +) -> (Frequency, Vec<(String, String, u64)>) { + let mut new_frequency = Frequency::new(); + let mut transition_pairs = Vec::new(); + for (word, value) in frequency { + if words.contains(word) { + new_frequency.insert(word.clone(), new_frequency.get(word).unwrap_or(&0) + *value); + } else { + // 使用逆向最大匹配算法来分词 + let chars: Vec<_> = word.chars().collect(); + let mut end = chars.len(); + let mut last_match: Option = None; + while end > 0 { + let mut start = end - 1; + // 如果最后一个字不在词表里,就不要了 + if !words.contains(&chars[start].to_string()) { + end -= 1; continue; } - // 首先将全码截取一部分出来 - let modulo = self.radix.pow(*prefix as u32); - let short = full % modulo; - let capacity = select_keys.len() as u8; - if full_occupation.rank(short) + short_occupation.rank_hash(short, hash) >= capacity + // 继续向前匹配,看看是否能匹配到更长的词 + while start > 0 + && words.contains(&chars[(start - 1)..end].iter().collect::()) { - continue; + start -= 1; } - pointer.code = short; - pointer.rank = short_occupation.rank_hash(short, hash) as i8; - short_occupation.insert(short, hash); - has_reduced = true; - break; - } - if !has_reduced { - pointer.code = *full; - pointer.rank = short_occupation.rank_hash(*full, hash) as i8; - short_occupation.insert(*full, hash); + // 确定最大匹配 + let sub_word: String = chars[start..end].iter().collect(); + *new_frequency.entry(sub_word.clone()).or_default() += *value; + if let Some(last) = last_match { + transition_pairs.push((sub_word.clone(), last, *value)); + } + last_match = Some(sub_word); + end = start; } } } - - pub fn encode(&self, keymap: &KeyMap, representation: &Representation) -> Vec { - let mut buffer = Buffer::new(self); - let mut full_occupation = Occupation::new(representation.get_space()); - let mut short_occupation = Occupation::new(representation.get_space()); - self.encode_full(keymap, &mut buffer, &mut full_occupation); - self.encode_short(&mut buffer, &full_occupation, &mut short_occupation); - let mut entries: Vec<(usize, Entry)> = Vec::new(); - let recover = |code: Code| representation.repr_code(code).iter().collect(); - for (index, encodable) in self.encodables.iter().enumerate() { - let entry = Entry { - name: encodable.name.clone(), - full: recover(buffer.full[index].code), - full_rank: buffer.full[index].rank, - short: recover(buffer.short[index].code), - short_rank: buffer.short[index].rank, - }; - entries.push((encodable.index, entry)); - } - entries.sort_by_key(|x| x.0); - entries.into_iter().map(|x| x.1).collect() - } - - pub fn get_space(&self) -> usize { - let max_length = self.config.max_length.min(MAX_COMBINATION_LENGTH); - self.radix.pow(max_length as u32) as usize - } + (new_frequency, transition_pairs) } + +pub trait Encoder { + fn encode_full(&self, keymap: &KeyMap, buffer: &mut Buffer); + fn encode_short(&self, buffer: &mut Buffer); + fn get_radix(&self) -> u64; + fn get_space(&self) -> usize; + fn get_actual_code(&self, code: u64, rank: i8, length: u32) -> (u64, u32); + fn get_transitions(&self, index: usize) -> &[(usize, u64)]; +} \ No newline at end of file diff --git a/src/encoder/generic.rs b/src/encoder/generic.rs new file mode 100644 index 0000000..b827228 --- /dev/null +++ b/src/encoder/generic.rs @@ -0,0 +1,225 @@ + +use rustc_hash::{FxHashMap, FxHashSet}; +use super::{CompiledScheme, Encodable, Encoder}; + +use crate::config::EncoderConfig; +use crate::error::Error; +use crate::representation::{ + Assemble, AssembleList, Assets, AutoSelect, Buffer, Code, Entry, Key, KeyMap, + Representation, MAX_COMBINATION_LENGTH, MAX_WORD_LENGTH, +}; +use std::{cmp::Reverse, iter::zip}; + + +pub struct GenericEncoder { + pub encodables: Vec, + pub transition_matrix: Vec>, + pub config: EncoderConfig, + auto_select: AutoSelect, + pub radix: u64, + select_keys: Vec, + pub short_code: Option<[Vec; MAX_WORD_LENGTH]>, +} + +impl Encoder for GenericEncoder { + fn get_actual_code(&self, code: u64, rank: i8, length: u32) -> (u64, u32) { + if rank == 0 && *self.auto_select.get(code as usize).unwrap_or(&true) { + return (code, length); + } + let select = *self + .select_keys + .get(rank.unsigned_abs() as usize) + .unwrap_or(&self.select_keys[0]) as u64 + * self.radix.pow(length); + (code + select, length + 1) + } + + fn encode_full(&self, keymap: &KeyMap, buffer: &mut Buffer) { + let weights: Vec<_> = (0..=self.config.max_length) + .map(|x| self.radix.pow(x as u32)) + .collect(); + for (encodable, pointer) in zip(&self.encodables, &mut buffer.full) { + let sequence = &encodable.sequence; + let mut code = 0_u64; + for (element, weight) in zip(sequence, &weights) { + code += keymap[*element] as u64 * weight; + } + pointer.code = code; + pointer.rank = buffer.occupation.rank(code) as i8; + buffer.occupation.insert(code, encodable.hash); + } + } + + fn encode_short(&self, buffer: &mut Buffer) { + if self.short_code.is_none() { + return; + } + let short_code = self.short_code.as_ref().unwrap(); + // 优先简码 + for ((code, pointer), encodable) in + zip(zip(&buffer.full, &mut buffer.short), &self.encodables) + { + if encodable.level == u64::MAX { + continue; + } + let modulo = self.radix.pow(encodable.level as u32); + let short = code.code % modulo; + pointer.code = short; + pointer.rank = 0; + buffer.occupation.insert(short, encodable.hash); + } + // 常规简码 + for ((code, pointer), encodable) in + zip(zip(&buffer.full, &mut buffer.short), &self.encodables) + { + let schemes = &short_code[encodable.length - 1]; + if schemes.is_empty() || encodable.level != u64::MAX { + continue; + } + let full = &code.code; + let mut has_reduced = false; + let hash = encodable.hash; + for scheme in schemes { + let CompiledScheme { + prefix, + select_keys, + } = scheme; + // 如果根本没有这么多码,就放弃 + if *full < self.radix.pow((*prefix - 1) as u32) { + continue; + } + // 首先将全码截取一部分出来 + let modulo = self.radix.pow(*prefix as u32); + let short = full % modulo; + let capacity = select_keys.len() as u8; + if buffer.occupation.rank_hash(short, hash) >= capacity { + continue; + } + pointer.code = short; + pointer.rank = buffer.occupation.rank_hash(short, hash) as i8; + buffer.occupation.insert(short, hash); + has_reduced = true; + break; + } + if !has_reduced { + pointer.code = *full; + pointer.rank = buffer.occupation.rank_hash(*full, hash) as i8; + buffer.occupation.insert(*full, hash); + } + } + } + + fn get_space(&self) -> usize { + let max_length = self.config.max_length.min(MAX_COMBINATION_LENGTH); + self.radix.pow(max_length as u32) as usize + } + + fn get_radix(&self) -> u64 { + self.radix + } + + fn get_transitions(&self, index: usize) -> &[(usize, u64)] { + &self.transition_matrix[index] + } +} + +impl GenericEncoder { + /// 提供配置表示、拆分表、词表和共用资源来创建一个编码引擎 + /// 字需要提供拆分表 + /// 词只需要提供词表,它对应的拆分序列从字推出 + pub fn new( + representation: &Representation, + resource: AssembleList, + assets: &Assets, + ) -> Result { + let encoder = &representation.config.encoder; + let max_length = encoder.max_length; + if max_length >= 8 { + return Err("目前暂不支持最大码长大于等于 8 的方案计算!".into()); + } + + // 预处理词频 + let all_words: FxHashSet<_> = resource.iter().map(|x| x.name.clone()).collect(); + let (frequency, transition_pairs) = super::adapt(&assets.frequency, &all_words); + + // 将拆分序列映射降序排列 + let mut encodables = Vec::new(); + for (index, assemble) in resource.into_iter().enumerate() { + let Assemble { + name, + importance, + level, + .. + } = assemble.clone(); + let sequence = representation.transform_elements(assemble)?; + let char_frequency = *frequency.get(&name).unwrap_or(&0); + let frequency = char_frequency * importance / 100; + let hash: u16 = (name.chars().map(|x| x as u32).sum::()) as u16; + encodables.push(Encodable { + name: name.clone(), + length: name.chars().count(), + sequence, + frequency, + level, + hash, + index, + }); + } + + encodables.sort_by_key(|x| Reverse(x.frequency)); + + let map_word_to_index: FxHashMap = encodables + .iter() + .enumerate() + .map(|(index, x)| (x.name.clone(), index)) + .collect(); + let mut transition_matrix = vec![vec![]; encodables.len()]; + for (from, to, value) in transition_pairs { + let from = *map_word_to_index.get(&from).unwrap(); + let to = *map_word_to_index.get(&to).unwrap(); + transition_matrix[from].push((to, value)); + } + for row in transition_matrix.iter_mut() { + row.sort_by_key(|x| x.0); + } + + // 处理自动上屏 + let auto_select = representation.transform_auto_select()?; + + // 处理简码规则 + let mut short_code = None; + if let Some(configs) = &encoder.short_code { + short_code = Some(representation.transform_short_code(configs.clone())?); + } + let encoder = GenericEncoder { + encodables, + transition_matrix, + auto_select, + config: encoder.clone(), + radix: representation.radix, + select_keys: representation.select_keys.clone(), + short_code, + }; + Ok(encoder) + } + + pub fn encode(&self, keymap: &KeyMap, representation: &Representation) -> Vec { + let mut buffer = Buffer::new(&self.encodables, self.get_space()); + self.encode_full(keymap, &mut buffer); + self.encode_short(&mut buffer); + let mut entries: Vec<(usize, Entry)> = Vec::new(); + let recover = |code: Code| representation.repr_code(code).iter().collect(); + for (index, encodable) in self.encodables.iter().enumerate() { + let entry = Entry { + name: encodable.name.clone(), + full: recover(buffer.full[index].code), + full_rank: buffer.full[index].rank, + short: recover(buffer.short[index].code), + short_rank: buffer.short[index].rank, + }; + entries.push((encodable.index, entry)); + } + entries.sort_by_key(|x| x.0); + entries.into_iter().map(|x| x.1).collect() + } +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 4573e6c..ca67f82 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,9 +20,10 @@ use crate::{ }; use config::{ObjectiveConfig, OptimizationConfig}; use console_error_panic_hook::set_once; +use encoder::generic::GenericEncoder; use interface::Interface; use js_sys::Function; -use problem::solve; +use metaheuristics::solve; use representation::{AssembleList, Buffer}; use serde::Serialize; use serde_wasm_bindgen::{from_value, to_value}; @@ -110,9 +111,10 @@ impl WebInterface { metaheuristic: None, }); let representation = Representation::new(config)?; - let encoder = Encoder::new(&representation, self.info.clone(), &self.assets)?; + let encoder = GenericEncoder::new(&representation, self.info.clone(), &self.assets)?; let codes = encoder.encode(&representation.initial, &representation); - let mut buffer = Buffer::new(&encoder); + let encoder = Box::new(encoder); + let mut buffer = Buffer::new(&encoder.encodables, encoder.get_space()); let objective = Objective::new(&representation, encoder, self.assets.clone())?; let (metric, _) = objective.evaluate(&representation.initial, &mut buffer)?; Ok(to_value(&(codes, metric))?) @@ -120,8 +122,9 @@ impl WebInterface { pub fn optimize(&self) -> Result<(), JsError> { let representation = Representation::new(self.config.clone())?; - let encoder = Encoder::new(&representation, self.info.clone(), &self.assets)?; - let mut buffer = Buffer::new(&encoder); + let encoder = GenericEncoder::new(&representation, self.info.clone(), &self.assets)?; + let encoder = Box::new(encoder); + let mut buffer = Buffer::new(&encoder.encodables, encoder.get_space()); let objective = Objective::new(&representation, encoder, self.assets.clone())?; let constraints = Constraints::new(&representation)?; let _ = objective.evaluate(&representation.initial, &mut buffer)?; diff --git a/src/main.rs b/src/main.rs index b5a7a65..d81f8b0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,12 +4,14 @@ //! //! 具体用法详见 README.md 和 config.md。 +use chai::encoder::generic::GenericEncoder; +use chai::metaheuristics::solve; use chai::representation::Buffer; use chai::{representation::Representation, error::Error}; use chai::encoder::Encoder; use chai::objectives::Objective; use chai::constraints::Constraints; -use chai::problem::{solve, ElementPlacementProblem}; +use chai::problem::ElementPlacementProblem; use chai::cli::{Cli, Command}; use clap::Parser; @@ -18,21 +20,21 @@ fn main() -> Result<(), Error> { let (config, resource, assets) = cli.prepare_file(); let config2 = config.clone(); let representation = Representation::new(config)?; - let encoder = Encoder::new(&representation, resource, &assets)?; + let encoder = GenericEncoder::new(&representation, resource, &assets)?; match cli.command { Command::Encode => { let codes = encoder.encode(&representation.initial, &representation); Cli::write_encode_results(codes); } Command::Evaluate => { - let mut buffer = Buffer::new(&encoder); - let objective = Objective::new(&representation, encoder, assets)?; + let mut buffer = Buffer::new(&encoder.encodables, encoder.get_space()); + let objective = Objective::new(&representation, Box::new(encoder), assets)?; let (metric, _) = objective.evaluate(&representation.initial, &mut buffer)?; Cli::report_metric(metric); } Command::Optimize => { - let buffer = Buffer::new(&encoder); - let objective = Objective::new(&representation, encoder, assets)?; + let buffer = Buffer::new(&encoder.encodables, encoder.get_space()); + let objective = Objective::new(&representation, Box::new(encoder), assets)?; let constraints = Constraints::new(&representation)?; let mut problem = ElementPlacementProblem::new(representation, constraints, objective, buffer)?; diff --git a/src/metaheuristics/mod.rs b/src/metaheuristics.rs similarity index 70% rename from src/metaheuristics/mod.rs rename to src/metaheuristics.rs index 29705ec..4275b01 100644 --- a/src/metaheuristics/mod.rs +++ b/src/metaheuristics.rs @@ -5,7 +5,9 @@ //! 但是,为了保证可扩展性,仍然保留了这个库中对于不同类型算法和不同类型问题的特征抽象,即只要一个问题定义了 Metaheuristic 这个 trait,就能用所有不同的算法求解;而任何一个算法都可以只依赖于 Metaheuristic 这个 trait 里提供的方法来求解一个问题。相当于建立了一个多对多的模块化设计,这样也许以后使用遗传算法等其他方法也不需要大改结构。 //! -use crate::interface::Interface; +use std::fmt::Display; + +use crate::{config::SolverConfig, interface::Interface}; pub mod simulated_annealing; /// 任何问题只要实现了这个 trait,就能用所有算法来求解 @@ -44,3 +46,28 @@ pub trait Metaheuristics { interface: &dyn Interface, ); } + +pub fn solve( + problem: &mut dyn Metaheuristics, + solver: &SolverConfig, + interface: &dyn Interface, +) -> T { + interface.prepare_output(); + let SolverConfig { + algorithm, + parameters, + runtime, + report_after, + .. + } = solver.clone(); + if algorithm == "SimulatedAnnealing" { + if let Some(parameters) = parameters { + simulated_annealing::solve(problem, parameters, report_after, interface) + } else { + let runtime = runtime.unwrap_or(10); + simulated_annealing::autosolve(problem, runtime, report_after, interface) + } + } else { + panic!("Unknown algorithm: {}", algorithm) + } +} diff --git a/src/objectives/mod.rs b/src/objectives.rs similarity index 92% rename from src/objectives/mod.rs rename to src/objectives.rs index eb5ee15..2df1c1d 100644 --- a/src/objectives/mod.rs +++ b/src/objectives.rs @@ -16,7 +16,6 @@ use crate::representation::Codes; use crate::representation::DistributionLoss; use crate::representation::KeyMap; use crate::representation::Label; -use crate::representation::Occupation; use crate::representation::Representation; use crate::representation::MAX_COMBINATION_LENGTH; use metric::FingeringMetric; @@ -30,7 +29,7 @@ use std::iter::zip; pub struct Objective { config: ObjectiveConfig, - encoder: Encoder, + encoder: Box, ideal_distribution: Vec, pair_equivalence: Vec, fingering_types: Vec