Skip to content
This repository has been archived by the owner on Dec 13, 2024. It is now read-only.

Commit

Permalink
Deploy to GitHub pages
Browse files Browse the repository at this point in the history
  • Loading branch information
github-actions[bot] authored Apr 23, 2021
0 parents commit e7b07e2
Show file tree
Hide file tree
Showing 54 changed files with 18,096 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .buildinfo
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Sphinx build info version 1
# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done.
config: eb491e7828014011d4def3d910c8128c
tags: 645f666f9bcd5a90fca523b33c5a78b7
Binary file added .doctrees/api/base.doctree
Binary file not shown.
Binary file added .doctrees/api/core.doctree
Binary file not shown.
Binary file added .doctrees/api/preset.doctree
Binary file not shown.
Binary file added .doctrees/api/util.doctree
Binary file not shown.
Binary file added .doctrees/developer/common.doctree
Binary file not shown.
Binary file added .doctrees/developer/flow.doctree
Binary file not shown.
Binary file added .doctrees/environment.pickle
Binary file not shown.
Binary file added .doctrees/index.doctree
Binary file not shown.
Binary file added .doctrees/user/index.doctree
Binary file not shown.
Empty file added .jekyll
Empty file.
32 changes: 32 additions & 0 deletions _sources/api/base.rst.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
基础
=================

曲线
--------

.. autoclass:: pychai.base.Curve

.. autoclass:: pychai.base.Linear

.. autoclass:: pychai.base.Cubic

笔画
-------

.. autoclass:: pychai.base.Stroke

汉字
------

.. autoclass:: pychai.base.Character

.. autoclass:: pychai.base.Component

.. autoclass:: pychai.base.Compound

选择器
-------

.. autoclass:: pychai.base.Selector


12 changes: 12 additions & 0 deletions _sources/api/core.rst.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
核心
===========

基类
--------

.. autoclass:: pychai.core.Chai

顺序
-------

.. autoclass:: pychai.core.Sequential
22 changes: 22 additions & 0 deletions _sources/api/preset.rst.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
预设筛
=============================

根少筛
--------

.. autofunction:: pychai.preset.length

取大筛
---------

.. autofunction:: pychai.preset.bias

笔顺筛
---------

.. autofunction:: pychai.preset.order

拓扑筛
---------

.. autofunction:: pychai.preset.topology
79 changes: 79 additions & 0 deletions _sources/api/util.rst.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
工具
===========================

「文」数据库预处理
------------------

基本部件是拆分算法所要处理的对象。处理过程中,我们可能需要用到许多种可能的信息,但任何信息都是可以基于图形信息推导得出的。因此,本系统仅存储最原始的数据,即「这个基本部件是怎么写出来的」。

具体而言,我们:

- 将基本部件表达为多个笔画;
- 将笔画表达为笔画类别和多个绘制命令;
- 每个绘制命令由绘制种类和绘制参数组成,具体可参见 Wiki
页面中的「「文」数据库开发规范」。

.. code:: yaml
:
- [横, [m, 25, 470], [h, 967]]
:
- [横, [m, 52, 130], [h, 918]]
- [竖钩, [m, 508, 132], [v, 833]]
在程序中,为了实现上述数据的封装,每个笔画由一个 Stroke
对象表示,每个字由一个 Char
对象表示。当我们取一个字的其中一部分笔画构成一个新字时,我们称新字是源字的「切片」,切片也用一个
Char 对象表示。

「字」数据库预处理
------------------

所有不属于基本部件的汉字均以键值对的形式存储在 ``字.yaml``
中,其值为一个表达式。一个表达式由一个二元运算符和两个操作对象组成,例如:

.. code:: yaml
: [h, 亻, 也]
其中 ``h`` 为左右结构运算符,\ ```` 和 ````
为操作对象。当没有合适的部件来表达一个操作对象的时候,值也可以是另一个表达式,例如:

.. code:: yaml
: [z, 前上, [h, 青下, 刂]]
在程序中,为了便于运算,我们定义了树类(\ ``Tree``\ ),将表达式处理为一个树对象。初始化该树对象时,我们不仅存储了该表达式的结构,还将每个值继续展开直到基本部件,例如:

.. code:: yaml
: [h, 亻, 介]
这里 ```` 是基本部件,而 ```` 不是,它由 ```` 和 ``齐下``
两个更基本的部件组成。总而言之,\ ``Tree`` 对象以 ``name``
储存汉字名称,\ ``structure`` 存储结构运算符,\ ``first`` 和 ``second``
存储运算符的操作对象,这些对象仍然是一个 ``Tree``
对象,如此迭代展到直到基本部件,它的 ``first`` 和 ``second`` 值为
``None``\ ,也即基本部件是树的末端节点。

拓扑缓存(\ ``cache.topology``\
----------------------------------

先考虑较简单的情况,两个由一条 Bezier
曲线构成的笔画之间的关系。此时我们首先解方程:

.. math::
\boldsymbol b_i(t_i)=\boldsymbol b_j(t_j)
若求得这样的解,检验是否有 :math:`0\le t_i,t_j\le 1`\

- 若无解,两笔画相离;
- 若有解,且 :math:`t_i,t_j` 中的一个约等于 0 或
1,那么两笔画相接,一笔画的端位接到另一笔画的中间;
- 若有解,且 :math:`t_i,t_j` 都约等于 0 或
1,那么两笔画相接,一笔画的端位接到另一笔画的端位;
- 若有解,且上述条件不满足,则两笔画相交。

在实践中,上述方程由 Newton 梯度下降法求解。
121 changes: 121 additions & 0 deletions _sources/developer/common.rst.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
常用模板类
===========================

顺序模板类(``core.Sequential``)
---------------------------------

**定义**\ (笔画列表)部件 :math:`c` 所包含的笔画形成的序列
:math:`\operatorname{str}c=(s_1,s_2,\cdots,s_n)`\

**定义**\ (切片)设部件 :math:`c` 的笔画列表的长度为
:math:`n`\ ,我们指定一组指标 :math:`i_1,\cdots,i_n` 使得

.. math::
1\le i_1\le\cdots\le i_n\le n
那么由第 :math:`i_1,\cdots,i_n`
个笔画在平面上按此顺序在原有位置上排布组成的几何图形称为一个切片。

我们将切片记为 :math:`p_i=c[i_1,\cdots,i_n]`\ 。显然,它的笔画序列为

.. math::
\operatorname{str}(c[i_1,\cdots,i_n])=(s_{i_1},\cdots,s_{i_n})
**定义**\ (部件的拆分)给定部件 :math:`c`\ ,我们称由 :math:`k` 个
:math:`c` 的切片构成的 :math:`k` 元组 :math:`d=(p_1,\cdots,p_k)`
是一个拆分,当且仅当任两个切片无共同笔画,且所有切片所含的笔画的并集等于汉字
:math:`c` 的所有笔画。

**定义**\ (部件的拆分集)给定汉字 :math:`c`\ ,由所有可能拆分 :math:`d`
构成的集合 :math:`D`\


退化映射(\ ``base.degenerator.Degenerator``\

根据架构,我们首先需要将基本部件拆分为字根。但是,当我们利用上述坐标数据时,我们就面临着一个问题:用户可能希望将不同的字中不同的切片视为同一字根,尽管这些切片的坐标数据不完全一样。例如,「串」中的两个「口」上面的小、下面的大,坐标数据并不相同,但通常用户会将其看作同一字根。如何实现呢?

一种思路是,直接利用上述全部信息,结合人工智能的分类方法进行分类。然而,这将会花费巨大的运算资源(例如,请了解手写数字的神经网络分类模型),于是我们思考的是,上述信息中是否存在某些简单、信息量少的「关键部分」,使得我们仅通过这些关键部分就能有效的区分不同字根?

「拆」为此进行了尝试,将这种对一个切片提取关键信息的函数称为「退化映射」,得到的关键信息称之为「特征」。这样,我们将一个切片经退化映射处理,得到的特征与字根的特征进行比较,如果它的特征与某个字根的特征相等,就可以将它等同于该字根。相反,如果它不与任何字根的特征相等,它就是一个无效切片,不能作为拆分的一部分。

**定义**\ (退化映射)\ :math:`\mathcal O`
将一个切片或一个字根映射为含有较少信息的对象,且满足:

.. math::
\forall r_1,r_2\in R,r_1\ne r_2\Rightarrow \mathcal O(r_1)\ne \mathcal O(r_2)
:math:`\mathcal O` 在字根集 :math:`R` 上是单射。

**定义**\ (部件的可行拆分集)给定汉字 :math:`c`\ ,它的拆分集是
:math:`D`\ ,则它的可行拆分集 :math:`W` 定义为:

.. math::
W=\{d|d\in D;\forall p\in d, \exists r\in R\text{ s.t. }\mathcal O(r)=\mathcal O(p)\}
即:对于该拆分中的每一个切片,都存在一个字根使得该字根退化后得到的对象与该切片退化后得到的对象一致。

在实际操作中,我们可能会提取多种不同的关键信息,然后将它们组合起来成为一个字的特征。此时每种信息称为一个「域(field)」,生成这个域的函数称为域函数。

为了便于查询,我们计算所有字根的特征,形成一个以特征索引用户字根的字典;此后,每当我们获得了一个基本部件的切片,我们就能取其特征并在字典中查找,查找得到则为有效切片,否则为无效切片。


依次调用上述三个方法,并把得到的码表保存到给定路径下。

顺序中间类(\ ``core.sequential.Sequential``\
------------------------------------------------

幂字典生成(\ ``Sequential.__addPowerDict``\
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

我们现在讨论:给定一个基本部件,我们可以从其中拆出哪些字根?为了满足不同方案的需求,「拆」采用了比较激进的方案——枚举一个基本部件的所有切片,计算该切片的特征,然后在退化字典中查找它所对应的字根,如果能够找到则标记为有效切片,找不到则标记为无效切片。

那么,一个基本部件最多能形成多少种切片?显然,对于一个 n
笔的字,每个笔画都有取和不取两种状态,因而切片就有 2 的 n
次方种可能性。我们因此可以用 n 个布尔值(即 0 或
1)组成的向量来表达这一切片。例如:

- 设字 c 是含有 5 笔的字,则它的所有切片都可以用一个\ **含有 5
个布尔值的向量**\ 表达;
- 取字 c 前 2 笔和最后一笔作为一个切片 s,我们对每个笔画将「取」标记为
1,「不取」标记为 0,那么 s 对应的向量应该是 (1, 1, 0, 0, 1);
- 取完切片 s 之后,余下部分 r 对应的向量应该是 (0, 0, 1, 1, 0)。

进一步抽象之后,我们很自然地联想到可以使用二进制数来表达切片,这样的好处是我们可以通过位运算来快速处理切片。例如:

- 设字 c 是含有 5 笔的字,则它的所有切片都可以用一个\ **含有 5
个位的二进制数**\ 表达;
- 取字 c 前 2 笔作为一个切片 s,我们对每个笔画将「取」标记为
1,「不取」标记为 0,那么 s 对应的二进制数应该是
11001,转换为十进制数是 25;
- 取完切片 s 之后,余下部分 r 对应的向量应该是 00111,转换为十进制数是
6。

现在,我们就可以通过遍历 1 ~ 2n-1 的所有数字来寻找一个字的所有有效切片:

现在,幂字典中记录了每个切片分别对应哪个字根(或者不对应任何字根),由此我们可以正式进入一个字的拆分环节。

可行拆分集生成(\ ``Sequential.__addSchemeList``\
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

在未开始拆分之前,我们将一个字的状态用 2n-1
表示,我们将它称为剩余数。每当我们从这个字上取下一个切片时,我们就将这个切片对应的数从剩余数中减去,得到新的剩余数。那么给定任意一个剩余数,我们如何知道从它身上能取下哪些切片呢?

首先,我们要引入一个限定原则(首笔序原则),即拆分得到的字根列表是按它们首笔笔顺排列的。因此,在每次从没有拆完的部分中取切片的过程中,必须取到该部分的第一笔。例如,第一次拆分时必须取到该字的第一笔。

所以,拆分算法可以概括为:

- 建立两个列表记录拆分状态,一个为未完成列表,一个为完成列表,向未完成列表中加入初始值
(2n - 1),即将整个部件作为一个剩余数;
- 取未完成列表中的某个拆分,将它的最后一个数(即剩余数)经由
``nextRoot``
函数处理,得到所有可能切片,用幂字典检验它们的有效性,如果无效则予以剔除,有效则保留;
- 如果切片恰好等于剩余数,说明这个基本部件被拆完了,我们将它添加到已完成列表中;否则用剩余数减去新切片,将它添加到未完成列表中,形成堆栈;
- 重复上述过程,直到未完成列表全部被清空。
Loading

0 comments on commit e7b07e2

Please sign in to comment.