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