1pub mod config;
6pub mod data;
7pub mod encoders;
8pub mod objectives;
9pub mod operators;
10pub mod optimizers;
11
12use chrono::Local;
13use clap::{Parser, Subcommand};
14use config::{ObjectiveConfig, OptimizationConfig, SolverConfig, 配置};
15use console_error_panic_hook::set_once;
16use csv::{ReaderBuilder, WriterBuilder};
17use data::{原始可编码对象, 数据};
18use data::{原始当量信息, 原始键位分布信息, 码表项};
19use encoders::default::默认编码器;
20use encoders::编码器;
21use js_sys::Function;
22use objectives::default::默认目标函数;
23use objectives::{metric::Metric, 目标函数};
24use operators::default::默认操作;
25use optimizers::{优化方法, 优化问题};
26use serde::{Deserialize, Serialize};
27use serde_wasm_bindgen::{from_value, to_value};
28use serde_with::skip_serializing_none;
29use std::collections::HashMap;
30use std::fs::{create_dir_all, read_to_string, write, OpenOptions};
31use std::io::Write;
32use std::iter::FromIterator;
33use std::path::{Path, PathBuf};
34use wasm_bindgen::{prelude::*, JsError};
35
36#[derive(Debug, Clone)]
38pub struct 错误 {
39 pub message: String,
40}
41
42impl From<String> for 错误 {
43 fn from(value: String) -> Self {
44 Self { message: value }
45 }
46}
47
48impl From<&str> for 错误 {
49 fn from(value: &str) -> Self {
50 Self {
51 message: value.to_string(),
52 }
53 }
54}
55
56impl From<错误> for JsError {
57 fn from(value: 错误) -> Self {
58 JsError::new(&value.message)
59 }
60}
61
62#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct 图形界面参数 {
65 pub 配置: 配置,
66 pub 词列表: Vec<原始可编码对象>,
67 pub 原始键位分布信息: 原始键位分布信息,
68 pub 原始当量信息: 原始当量信息,
69}
70
71impl Default for 图形界面参数 {
72 fn default() -> Self {
73 Self {
74 配置: 配置::default(),
75 词列表: vec![],
76 原始键位分布信息: HashMap::new(),
77 原始当量信息: HashMap::new(),
78 }
79 }
80}
81
82#[derive(Serialize)]
84#[serde(tag = "type", rename_all = "snake_case")]
85#[skip_serializing_none]
86pub enum 消息 {
87 TrialMax {
88 temperature: f64,
89 accept_rate: f64,
90 },
91 TrialMin {
92 temperature: f64,
93 improve_rate: f64,
94 },
95 Parameters {
96 t_max: f64,
97 t_min: f64,
98 },
99 Progress {
100 steps: usize,
101 temperature: f64,
102 metric: Metric,
103 },
104 BetterSolution {
105 metric: Metric,
106 config: 配置,
107 save: bool,
108 },
109 Elapsed(u128),
110}
111
112pub trait 界面 {
116 fn 发送(&self, 消息: 消息);
117}
118
119#[wasm_bindgen]
121pub struct Web {
122 回调: Function,
123 参数: 图形界面参数,
124}
125
126#[wasm_bindgen]
128pub fn validate(js_config: JsValue) -> Result<JsValue, JsError> {
129 set_once();
130 let config: 配置 = from_value(js_config)?;
131 let config_str = serde_yaml::to_string(&config).unwrap();
132 Ok(to_value(&config_str)?)
133}
134
135#[wasm_bindgen]
136impl Web {
137 pub fn new(回调: Function) -> Web {
138 set_once();
139 let 参数 = 图形界面参数::default();
140 Self { 回调, 参数 }
141 }
142
143 pub fn sync(&mut self, 前端参数: JsValue) -> Result<(), JsError> {
144 self.参数 = from_value(前端参数)?;
145 Ok(())
146 }
147
148 pub fn encode_evaluate(&self, 前端目标函数配置: JsValue) -> Result<JsValue, JsError> {
149 let 目标函数配置: ObjectiveConfig = from_value(前端目标函数配置)?;
150 let 图形界面参数 {
151 mut 配置,
152 原始键位分布信息,
153 原始当量信息,
154 词列表,
155 } = self.参数.clone();
156 配置.optimization = Some(OptimizationConfig {
157 objective: 目标函数配置,
158 constraints: None,
159 metaheuristic: None,
160 });
161 let 数据 = 数据::新建(配置, 词列表, 原始键位分布信息, 原始当量信息)?;
162 let mut 编码器 = 默认编码器::新建(&数据)?;
163 let mut 编码结果 = 编码器.编码(&数据.初始映射, &None).clone();
164 let 码表 = 数据.生成码表(&编码结果);
165 let mut 目标函数 = 默认目标函数::新建(&数据)?;
166 let (指标, _) = 目标函数.计算(&mut 编码结果);
167 Ok(to_value(&(码表, 指标))?)
168 }
169
170 pub fn optimize(&self) -> Result<(), JsError> {
171 let 图形界面参数 {
172 配置,
173 原始键位分布信息,
174 原始当量信息,
175 词列表,
176 } = self.参数.clone();
177 let 优化方法配置 = 配置.clone().optimization.unwrap().metaheuristic.unwrap();
178 let 数据 = 数据::新建(配置, 词列表, 原始键位分布信息, 原始当量信息)?;
179 let 编码器 = 默认编码器::新建(&数据)?;
180 let 目标函数 = 默认目标函数::新建(&数据)?;
181 let 操作 = 默认操作::新建(&数据)?;
182 let mut 问题 = 优化问题::新建(数据, 编码器, 目标函数, 操作);
183 let SolverConfig::SimulatedAnnealing(退火) = 优化方法配置;
184 退火.优化(&mut 问题, self);
185 Ok(())
186 }
187}
188
189impl 界面 for Web {
190 fn 发送(&self, message: 消息) {
191 let js_message = to_value(&message).unwrap();
192 self.回调.call1(&JsValue::null(), &js_message).unwrap();
193 }
194}
195
196#[derive(Parser, Clone)]
198#[command(name = "汉字自动拆分系统")]
199#[command(author, version, about, long_about)]
200#[command(propagate_version = true)]
201pub struct 命令行参数 {
202 #[command(subcommand)]
203 pub command: 命令,
204 pub config: Option<PathBuf>,
206 #[arg(short, long, value_name = "FILE")]
208 pub encodables: Option<PathBuf>,
209 #[arg(short, long, value_name = "FILE")]
211 pub key_distribution: Option<PathBuf>,
212 #[arg(short, long, value_name = "FILE")]
214 pub pair_equivalence: Option<PathBuf>,
215 #[arg(short, long)]
217 pub threads: Option<usize>,
218}
219
220#[derive(Subcommand, Clone)]
222pub enum 命令 {
223 Encode,
225 Optimize,
227}
228
229pub struct 命令行 {
231 pub 参数: 命令行参数,
232 pub 输出目录: PathBuf,
233}
234
235impl 命令行 {
236 pub fn 新建(args: 命令行参数, maybe_output_dir: Option<PathBuf>) -> Self {
237 let output_dir = maybe_output_dir.unwrap_or_else(|| {
238 let time = Local::now().format("%m-%d+%H_%M_%S").to_string();
239 PathBuf::from(format!("output-{}", time))
240 });
241 create_dir_all(output_dir.clone()).unwrap();
242 Self {
243 参数: args,
244 输出目录: output_dir,
245 }
246 }
247
248 pub fn 读取(name: &str) -> 数据 {
249 let config = format!("examples/{}.yaml", name);
250 let elements = format!("examples/{}.txt", name);
251 let 参数 = 命令行参数 {
252 command: 命令::Optimize,
253 config: Some(PathBuf::from(config)),
254 encodables: Some(PathBuf::from(elements)),
255 key_distribution: None,
256 pair_equivalence: None,
257 threads: None,
258 };
259 let cli = 命令行::新建(参数, None);
260 cli.准备数据()
261 }
262
263 fn read<I, T>(path: PathBuf) -> T
264 where
265 I: for<'de> Deserialize<'de>,
266 T: FromIterator<I>,
267 {
268 let mut reader = ReaderBuilder::new()
269 .delimiter(b'\t')
270 .has_headers(false)
271 .flexible(true)
272 .from_path(path)
273 .unwrap();
274 reader.deserialize().map(|x| x.unwrap()).collect()
275 }
276
277 pub fn 准备数据(&self) -> 数据 {
278 let 命令行参数 {
279 config,
280 encodables: elements,
281 key_distribution,
282 pair_equivalence,
283 ..
284 } = self.参数.clone();
285 let config_path = config.unwrap_or(PathBuf::from("config.yaml"));
286 let config_content = read_to_string(&config_path)
287 .unwrap_or_else(|_| panic!("文件 {} 不存在", config_path.display()));
288 let config: 配置 = serde_yaml::from_str(&config_content).unwrap();
289 let elements_path = elements.unwrap_or(PathBuf::from("elements.txt"));
290 let encodables: Vec<原始可编码对象> = Self::read(elements_path);
291
292 let assets_dir = Path::new("assets");
293 let keq_path = key_distribution.unwrap_or(assets_dir.join("key_distribution.txt"));
294 let key_distribution: 原始键位分布信息 = Self::read(keq_path);
295 let peq_path = pair_equivalence.unwrap_or(assets_dir.join("pair_equivalence.txt"));
296 let pair_equivalence: 原始当量信息 = Self::read(peq_path);
297 数据::新建(config, encodables, key_distribution, pair_equivalence).unwrap()
298 }
299
300 pub fn 输出编码结果(&self, entries: Vec<码表项>) {
301 let path = self.输出目录.join("编码.txt");
302 let mut writer = WriterBuilder::new()
303 .delimiter(b'\t')
304 .has_headers(false)
305 .from_path(&path)
306 .unwrap();
307 for 码表项 {
308 name,
309 full,
310 full_rank,
311 short,
312 short_rank,
313 } in entries
314 {
315 writer
316 .serialize((&name, &full, &full_rank, &short, &short_rank))
317 .unwrap();
318 }
319 writer.flush().unwrap();
320 println!("已完成编码,结果保存在 {} 中", path.clone().display());
321 }
322
323 pub fn 输出评测指标(&self, metric: Metric) {
324 let path = self.输出目录.join("评测指标.yaml");
325 print!("{}", metric);
326 let metric_str = serde_yaml::to_string(&metric).unwrap();
327 write(&path, metric_str).unwrap();
328 }
329
330 pub fn 生成子命令行(&self, index: usize) -> 命令行 {
331 let child_dir = self.输出目录.join(format!("{}", index));
332 命令行::新建(self.参数.clone(), Some(child_dir))
333 }
334}
335
336impl 界面 for 命令行 {
337 fn 发送(&self, message: 消息) {
338 let mut writer: Box<dyn Write> = if self.参数.threads.is_some() {
339 let log_path = self.输出目录.join("log.txt");
340 let file = OpenOptions::new()
341 .create(true) .append(true) .open(log_path)
344 .expect("Failed to open file");
345 Box::new(file)
346 } else {
347 Box::new(std::io::stdout())
348 };
349 let result = match message {
350 消息::TrialMax {
351 temperature,
352 accept_rate,
353 } => writeln!(
354 &mut writer,
355 "若温度为 {:.2e},接受率为 {:.2}%",
356 temperature,
357 accept_rate * 100.0
358 ),
359 消息::TrialMin {
360 temperature,
361 improve_rate,
362 } => writeln!(
363 &mut writer,
364 "若温度为 {:.2e},改进率为 {:.2}%",
365 temperature,
366 improve_rate * 100.0
367 ),
368 消息::Parameters { t_max, t_min } => writeln!(
369 &mut writer,
370 "参数寻找完成,从最高温 {} 降到最低温 {}……",
371 t_max, t_min
372 ),
373 消息::Elapsed(time) => writeln!(&mut writer, "计算一次评测用时:{} μs", time),
374 消息::Progress {
375 steps,
376 temperature,
377 metric,
378 } => writeln!(
379 &mut writer,
380 "已执行 {} 步,当前温度为 {:.2e},当前评测指标如下:\n{}",
381 steps, temperature, metric
382 ),
383 消息::BetterSolution {
384 metric,
385 config,
386 save,
387 } => {
388 let time = Local::now();
389 let prefix = time.format("%m-%d+%H_%M_%S_%3f").to_string();
390 let config_path = self.输出目录.join(format!("{}.yaml", prefix));
391 let metric_path = self.输出目录.join(format!("{}.metric.yaml", prefix));
392 let mut res1 = writeln!(
393 &mut writer,
394 "{} 系统搜索到了一个更好的方案,评测指标如下:\n{}",
395 time.format("%H:%M:%S"),
396 metric
397 );
398 let config = serde_yaml::to_string(&config).unwrap();
399 let metric = serde_yaml::to_string(&metric).unwrap();
400 if save {
401 write(metric_path, metric).unwrap();
402 write(config_path, config).unwrap();
403 let res2 = writeln!(
404 &mut writer,
405 "方案文件保存于 {}.yaml 中,评测指标保存于 {}.metric.yaml 中",
406 prefix, prefix
407 );
408 res1 = res1.and(res2);
409 }
410 res1
411 }
412 };
413 result.unwrap();
414 }
415}