pd-code-connected-sum 0.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 TopologicalKnotIndexer
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,46 @@
1
+ Metadata-Version: 2.4
2
+ Name: pd-code-connected-sum
3
+ Version: 0.0.1
4
+ Summary: make connected sum for link pd_code
5
+ License: MIT
6
+ License-File: LICENSE
7
+ Author: GGN_2015
8
+ Author-email: neko@jlulug.org
9
+ Requires-Python: >=3.10
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Programming Language :: Python :: 3.14
17
+ Requires-Dist: pd-code-sanity
18
+ Description-Content-Type: text/markdown
19
+
20
+ # pd_code_connected_sum
21
+ caculate connected sum for link pd_code.
22
+
23
+ ## Install
24
+
25
+ ```bash
26
+ pip install pd-code-connected-sum
27
+ ```
28
+
29
+ ## Usage
30
+
31
+ ```python
32
+ import pd_code_connected_sum
33
+
34
+ # L2a1
35
+ link_pd_code_1 = [[4, 1, 3, 2], [2, 3, 1, 4]]
36
+
37
+ # L4a1
38
+ link_pd_code_2 = [[6, 1, 7, 2], [8, 3, 5, 4], [2, 5, 3, 6], [4, 7, 1, 8]]
39
+
40
+ connect_pos_1 = 1
41
+ connect_pos_2 = 1
42
+
43
+ print(pd_code_connected_sum.connected_sum(
44
+ link_pd_code_1, link_pd_code_2, connect_pos_1, connect_pos_2))
45
+ ```
46
+
@@ -0,0 +1,26 @@
1
+ # pd_code_connected_sum
2
+ caculate connected sum for link pd_code.
3
+
4
+ ## Install
5
+
6
+ ```bash
7
+ pip install pd-code-connected-sum
8
+ ```
9
+
10
+ ## Usage
11
+
12
+ ```python
13
+ import pd_code_connected_sum
14
+
15
+ # L2a1
16
+ link_pd_code_1 = [[4, 1, 3, 2], [2, 3, 1, 4]]
17
+
18
+ # L4a1
19
+ link_pd_code_2 = [[6, 1, 7, 2], [8, 3, 5, 4], [2, 5, 3, 6], [4, 7, 1, 8]]
20
+
21
+ connect_pos_1 = 1
22
+ connect_pos_2 = 1
23
+
24
+ print(pd_code_connected_sum.connected_sum(
25
+ link_pd_code_1, link_pd_code_2, connect_pos_1, connect_pos_2))
26
+ ```
@@ -0,0 +1,5 @@
1
+ from .main import connected_sum
2
+
3
+ __all__ = [
4
+ "connected_sum"
5
+ ]
@@ -0,0 +1,306 @@
1
+ import pd_code_sanity
2
+ import json
3
+
4
+ # 为编码增加前缀
5
+ def add_prefix(pd_code:list[list], prefix:str) -> list[list]:
6
+ pd_code = json.loads(json.dumps(pd_code))
7
+ for i in range(len(pd_code)):
8
+ for j in range(len(pd_code[i])):
9
+ pd_code[i][j] = prefix + str(pd_code[i][j])
10
+ return json.loads(json.dumps(pd_code))
11
+
12
+ # 提取原始数值
13
+ def get_raw_val(val_in_crs:int|str) -> int:
14
+ if isinstance(val_in_crs, str):
15
+ return int(val_in_crs.split("_")[-1])
16
+ return int(val_in_crs)
17
+
18
+ def add_edge(pre:dict, nxt:dict, val1, val2):
19
+ raw_val1 = get_raw_val(val1)
20
+ raw_val2 = get_raw_val(val2)
21
+
22
+ # 记录前后关系
23
+ pre_val = -1
24
+ nxt_val = -1
25
+
26
+ # 确定前后关系
27
+ if abs(raw_val1 - raw_val2) == 1:
28
+ if raw_val1 < raw_val2:
29
+ pre_val = val1
30
+ nxt_val = val2
31
+ else:
32
+ pre_val = val2
33
+ nxt_val = val1
34
+ else:
35
+ if raw_val1 < raw_val2:
36
+ pre_val = val2
37
+ nxt_val = val1
38
+ else:
39
+ pre_val = val1
40
+ nxt_val = val2
41
+
42
+ # 设置 dict 值
43
+ pre[nxt_val] = pre_val
44
+ nxt[pre_val] = nxt_val
45
+
46
+ # 计算 pre 和 nxt
47
+ # 没有 r1 crossing 就不会出现混乱
48
+ def get_pre_nxt(pd_code:list[list]) -> tuple[dict, dict]:
49
+ pre = dict()
50
+ nxt = dict()
51
+ for crossing in pd_code:
52
+ if len(set(crossing)) > 2: # 至少有三个不同的数
53
+ add_edge(pre, nxt, crossing[0], crossing[2])
54
+ add_edge(pre, nxt, crossing[1], crossing[3])
55
+ else:
56
+ two_values = list(set(crossing)) # 只有两个不同的数
57
+ if len(two_values) != 2:
58
+ raise AssertionError()
59
+ for i in range(2):
60
+ pre[two_values[i]] = two_values[1-i] # 互为前驱后继
61
+ nxt[two_values[i]] = two_values[1-i]
62
+
63
+ # 获取数值合集
64
+ num_set = get_num_set(pd_code)
65
+ for num in num_set:
66
+ if pre.get(num) is None:
67
+ pre[num] = nxt[num]
68
+ if nxt.get(num) is None:
69
+ nxt[num] = pre[num]
70
+ return pre, nxt
71
+
72
+ # 替换一个值恰好一次
73
+ def replace_val(crossing:list, val_now:str, val_new:str):
74
+ cnt = 0
75
+ for i in range(len(crossing)):
76
+ if crossing[i] == val_now:
77
+ crossing[i] = val_new
78
+ cnt += 1
79
+ if cnt != 1:
80
+ raise ValueError()
81
+
82
+ # 检查一个 crossing 是否包含某个值
83
+ def check_has(crossing:list, val_now:str) -> bool:
84
+ for i in range(len(crossing)):
85
+ if crossing[i] == val_now:
86
+ return True
87
+ return False
88
+
89
+ # 检查两个元素同时存在
90
+ def check_has2(crossing:list, val1:str, val2:str) -> bool:
91
+ return (
92
+ check_has(crossing, val1) and
93
+ check_has(crossing, val2))
94
+
95
+ def get_num_set(pd_code:list[list]):
96
+ val_set = set()
97
+ for crossing in pd_code:
98
+ for term in crossing:
99
+ if term not in val_set:
100
+ val_set.add(term)
101
+ return sorted(list(val_set))
102
+
103
+ # 按照顺序对循环进行遍历
104
+ def dfs(val, nxt:dict[str, str], vis: set[int], idx:dict[str, int]):
105
+ if val in vis:
106
+ return
107
+ assert nxt.get(val) is not None
108
+
109
+ # 向已经访问元素队列中添加该节点
110
+ vis.add(val)
111
+ idx[val] = len(vis) # 当前元素个数,就是当前节点新编号
112
+
113
+ if nxt[val] not in vis:
114
+ dfs(nxt[val], nxt, vis, idx)
115
+
116
+ # 在有向图中重新编号
117
+ def get_new_number_map(nxt:dict[str, str], num_set:list[int]) -> dict[str, int]:
118
+ vis = set()
119
+ idx = dict()
120
+ for val in num_set:
121
+ if val not in vis:
122
+ dfs(val, nxt, vis, idx)
123
+ return idx
124
+
125
+ # 检查有多少个 crossing 包含了 val_now
126
+ def count_crossing_exists(pd_code_now:list[list], val_now) -> int:
127
+ ans = 0
128
+ for crossing in pd_code_now:
129
+ if check_has(crossing, val_now):
130
+ ans += 1
131
+ return ans
132
+
133
+ # 找到同一个连通分量中合法的位置
134
+ def find_avaible_pos(pd_code:list[list], val_now):
135
+ for crossing in pd_code:
136
+ if check_has(crossing, val_now):
137
+ values = []
138
+ for i in range(len(crossing)):
139
+ if crossing[i] != val_now and crossing[i] not in values:
140
+ values.append(crossing[i])
141
+ if len(values) == 1:
142
+ return None
143
+ else:
144
+ return values[0]
145
+
146
+ # 在这里报错说明 pd_code 中没找到 val_now
147
+ raise AssertionError()
148
+
149
+ # 删除 pd_code1 中 val_1 所在的 crossing
150
+ # 然后把两个扭结直接合并
151
+ # 然后重新编号
152
+ def del_component_and_merge(pd_code1, pd_code2, val_1, val_2) -> list[list]:
153
+ suc = False
154
+ for i in range(len(pd_code1)):
155
+ if check_has(pd_code1[i], val_1):
156
+ suc = True
157
+ pd_code1 = pd_code1[:i] + pd_code1[i+1:] # 跳过第 i 个元素
158
+
159
+ # 这里报错说明没找到 val_1 对应的 component
160
+ if not suc:
161
+ raise AssertionError()
162
+
163
+ # 获取元素集合
164
+ num_set_1 = add_prefix([get_num_set(pd_code1)], "a_")[0]
165
+ num_set_2 = add_prefix([get_num_set(pd_code2)], "b_")[0]
166
+
167
+ # 增加前缀编码
168
+ pd_code1 = add_prefix(pd_code1, "a_")
169
+ pd_code2 = add_prefix(pd_code2, "b_")
170
+
171
+ # 计算前驱后继
172
+ _, nxt1 = get_pre_nxt(pd_code1)
173
+ _, nxt2 = get_pre_nxt(pd_code2)
174
+ nxt = nxt1 | nxt2
175
+
176
+ # 合并得到新的 pd_code
177
+ new_pd_code = json.loads(json.dumps(pd_code1 + pd_code2))
178
+
179
+ # 获得新的编号方式
180
+ num_map = get_new_number_map(nxt, num_set_1 + num_set_2)
181
+ for crossing in new_pd_code:
182
+ for i in range(len(crossing)):
183
+ if num_map.get(crossing[i]) is None:
184
+ assert AssertionError()
185
+ crossing[i] = num_map[crossing[i]]
186
+ return sorted(new_pd_code)
187
+
188
+ # 将 pd_code1 里面的 val_1 和 pd_code2 里面的 val_2 连接起来
189
+ def connected_sum(
190
+ pd_code1:list[list], pd_code2:list[list], val_1, val_2) -> list[list]:
191
+
192
+ # 如果有一个扭结平凡,则不需要连接
193
+ if pd_code1 == [] or pd_code2 == []:
194
+ return json.loads(json.dumps(pd_code1 + pd_code2))
195
+
196
+ # 检查 pd_code 的弱合法性
197
+ for pd_code in [pd_code1, pd_code2]:
198
+ if not pd_code_sanity.sanity(pd_code):
199
+ raise TypeError()
200
+
201
+ # 检查 val_1 和 val_2 是否出现了
202
+ lis_wrap_1 = [0]
203
+ lis_wrap_2 = [0]
204
+ lis_wrap = [lis_wrap_1, lis_wrap_2]
205
+ for cnt_wrap, val_now, pd_now in [
206
+ (lis_wrap_1, val_1, pd_code1),
207
+ (lis_wrap_2, val_2, pd_code2)
208
+ ]:
209
+ for crossing in pd_now:
210
+ for term in crossing:
211
+ if term == val_now:
212
+ cnt_wrap[0] += 1
213
+
214
+ # 检查出现次数是否正确
215
+ for i in range(2):
216
+ if lis_wrap[i][0] != 2:
217
+ raise ValueError(f"val_{i+1} not in pd_code{i+1}")
218
+
219
+ # 分别计算两个需要连接的地方
220
+ # 是不是在一个 r1 crossing 上
221
+ # 如果 has_val_x_cnt[0] != 2
222
+ # 则说明,val_x 在一个 r1 crossing
223
+ # 其中 x = 1 或者 2
224
+ has_val_1_cnt = [0]
225
+ has_val_2_cnt = [0]
226
+ for pd_code_now, has_val_cnt, val_now in [
227
+ (pd_code1, has_val_1_cnt, val_1),
228
+ (pd_code2, has_val_2_cnt, val_2)
229
+ ]:
230
+ has_val_cnt[0] = count_crossing_exists(pd_code_now, val_now)
231
+
232
+ vals = [val_1, val_2]
233
+ for val_pos, pd_code_now, pd_code_other, has_cnt_now in [
234
+ (0, pd_code1, pd_code2, has_val_1_cnt),
235
+ (1, pd_code2, pd_code1, has_val_2_cnt),
236
+ ]:
237
+ if has_cnt_now[0] != 2:
238
+
239
+ # 在同一个连通分量中
240
+ # 找到一个出现在多个 crossing 中的编号
241
+ vals_new = find_avaible_pos(pd_code_now, vals[val_pos])
242
+ if vals_new is None:
243
+
244
+ # 遇到了 [a, b, b, a] 或者 [a, a, b, b] 之类的
245
+ # 直接删掉当前 components 然后 merge 即可
246
+ return del_component_and_merge(
247
+ pd_code_now, pd_code_other, vals[val_pos], vals[1-val_pos])
248
+
249
+ vals[val_pos] = vals_new
250
+
251
+ # 覆盖旧的元素
252
+ val_1, val_2 = vals
253
+
254
+ # 获取元素集合
255
+ num_set_1 = add_prefix([get_num_set(pd_code1)], "a_")[0]
256
+ num_set_2 = add_prefix([get_num_set(pd_code2)], "b_")[0]
257
+
258
+ # 增加前缀编码
259
+ pd_code1 = add_prefix(pd_code1, "a_")
260
+ pd_code2 = add_prefix(pd_code2, "b_")
261
+
262
+ # 由于不存在 nugatory crossing 和 r1 crossing
263
+ # 所以两侧各有两个相关 crossing
264
+ _, nxt1 = get_pre_nxt(pd_code1)
265
+ _, nxt2 = get_pre_nxt(pd_code2)
266
+ nxt = nxt1 | nxt2
267
+
268
+ # 带有字母前缀的成分
269
+ a_val_1 = f"a_{val_1}"
270
+ b_val_2 = f"b_{val_2}"
271
+
272
+ # 将两组 pd_code 合并
273
+ # 需要注意 nxt[a_val_1] 和 pre[a_val_1] 可能相同
274
+ # 所以要保证只替换一次
275
+ suc_1 = False
276
+ suc_2 = False
277
+ new_pd_code = json.loads(json.dumps(pd_code1 + pd_code2))
278
+ for crossing in new_pd_code:
279
+ if not suc_1 and check_has2(crossing, a_val_1, nxt[a_val_1]):
280
+ replace_val(crossing, a_val_1, b_val_2)
281
+ suc_1 = True
282
+ if not suc_2 and check_has2(crossing, b_val_2, nxt[b_val_2]):
283
+ replace_val(crossing, b_val_2, a_val_1)
284
+ suc_2 = True
285
+ if suc_1 and suc_2:
286
+ break
287
+
288
+ # 调整前驱后继关系
289
+ nxt[b_val_2] = nxt1[a_val_1]
290
+ nxt[a_val_1] = nxt2[b_val_2]
291
+
292
+ # 获得新的编号方式
293
+ num_map = get_new_number_map(nxt, num_set_1 + num_set_2)
294
+ for crossing in new_pd_code:
295
+ for i in range(len(crossing)):
296
+ if num_map.get(crossing[i]) is None:
297
+ assert AssertionError()
298
+ crossing[i] = num_map[crossing[i]]
299
+
300
+ ans_pd_code = sorted(new_pd_code)
301
+ if not pd_code_sanity.sanity(ans_pd_code):
302
+ raise AssertionError()
303
+ return ans_pd_code
304
+
305
+ if __name__ == "__main__":
306
+ print(connected_sum([[1, 2, 2, 1]], [[1, 2, 2, 1]], 1, 1))
@@ -0,0 +1,18 @@
1
+ [project]
2
+ name = "pd-code-connected-sum"
3
+ version = "0.0.1"
4
+ description = "make connected sum for link pd_code"
5
+ authors = [
6
+ {name = "GGN_2015",email = "neko@jlulug.org"}
7
+ ]
8
+ license = {text = "MIT"}
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ dependencies = [
12
+ "pd-code-sanity"
13
+ ]
14
+
15
+
16
+ [build-system]
17
+ requires = ["poetry-core>=2.0.0,<3.0.0"]
18
+ build-backend = "poetry.core.masonry.api"