knit-graphs 0.0.9__py3-none-any.whl → 0.0.11__py3-none-any.whl
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.
- docs/source/conf.py +71 -71
- docs/source/index.rst +0 -4
- knit_graphs/Course.py +3 -1
- knit_graphs/Knit_Graph.py +80 -45
- knit_graphs/Knit_Graph_Visualizer.py +362 -149
- knit_graphs/Loop.py +77 -11
- knit_graphs/Pull_Direction.py +2 -0
- knit_graphs/Yarn.py +74 -12
- knit_graphs/artin_wale_braids/Crossing_Direction.py +2 -0
- knit_graphs/artin_wale_braids/Loop_Braid_Graph.py +26 -5
- knit_graphs/artin_wale_braids/Wale.py +74 -49
- knit_graphs/artin_wale_braids/Wale_Braid.py +1 -0
- knit_graphs/artin_wale_braids/Wale_Braid_Word.py +9 -8
- knit_graphs/artin_wale_braids/Wale_Group.py +79 -63
- knit_graphs/basic_knit_graph_generators.py +45 -10
- knit_graphs/py.typed +0 -0
- {knit_graphs-0.0.9.dist-info → knit_graphs-0.0.11.dist-info}/METADATA +3 -2
- {knit_graphs-0.0.9.dist-info → knit_graphs-0.0.11.dist-info}/RECORD +20 -19
- {knit_graphs-0.0.9.dist-info → knit_graphs-0.0.11.dist-info}/LICENSE +0 -0
- {knit_graphs-0.0.9.dist-info → knit_graphs-0.0.11.dist-info}/WHEEL +0 -0
knit_graphs/Loop.py
CHANGED
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
This module defines the Loop class which represents individual loops in a knitting pattern.
|
|
4
4
|
Loops are the fundamental building blocks of knitted structures and maintain relationships with parent loops, yarn connections, and float positions.
|
|
5
5
|
"""
|
|
6
|
+
|
|
6
7
|
from __future__ import annotations
|
|
7
8
|
|
|
8
|
-
from typing import TYPE_CHECKING
|
|
9
|
+
from typing import TYPE_CHECKING
|
|
9
10
|
|
|
10
11
|
if TYPE_CHECKING:
|
|
11
12
|
from knit_graphs.Yarn import Yarn
|
|
@@ -47,7 +48,12 @@ class Loop:
|
|
|
47
48
|
Args:
|
|
48
49
|
u (Loop): The first loop in the float pair.
|
|
49
50
|
v (Loop): The second loop in the float pair.
|
|
51
|
+
|
|
52
|
+
Raises:
|
|
53
|
+
ValueError: If u and v are not on the same yarn.
|
|
50
54
|
"""
|
|
55
|
+
if u.yarn != v.yarn:
|
|
56
|
+
raise ValueError("Loops of a float must share a yarn.")
|
|
51
57
|
if u not in self.back_floats:
|
|
52
58
|
self.back_floats[u] = set()
|
|
53
59
|
if v not in self.back_floats:
|
|
@@ -55,6 +61,18 @@ class Loop:
|
|
|
55
61
|
self.back_floats[u].add(v)
|
|
56
62
|
self.back_floats[v].add(u)
|
|
57
63
|
|
|
64
|
+
def remove_loop_from_front_floats(self) -> None:
|
|
65
|
+
"""
|
|
66
|
+
Removes this loop from being in front of all marked floats. Mutates the yarns that own edges of those floats.
|
|
67
|
+
"""
|
|
68
|
+
visited: set[Loop] = set()
|
|
69
|
+
for u, v_loops in self.front_floats.items():
|
|
70
|
+
visited.add(u)
|
|
71
|
+
for v in v_loops:
|
|
72
|
+
if v not in visited and v in u.yarn: # float shares a yarn
|
|
73
|
+
u.yarn.loop_graph.edges[u, v]["Back_Loops"].remove(self)
|
|
74
|
+
self.front_floats = {}
|
|
75
|
+
|
|
58
76
|
def add_loop_behind_float(self, u: Loop, v: Loop) -> None:
|
|
59
77
|
"""Set this loop to be behind the float between loops u and v.
|
|
60
78
|
|
|
@@ -63,7 +81,12 @@ class Loop:
|
|
|
63
81
|
Args:
|
|
64
82
|
u (Loop): The first loop in the float pair.
|
|
65
83
|
v (Loop): The second loop in the float pair.
|
|
84
|
+
|
|
85
|
+
Raises:
|
|
86
|
+
ValueError: If u and v are not on the same yarn.
|
|
66
87
|
"""
|
|
88
|
+
if u.yarn != v.yarn:
|
|
89
|
+
raise ValueError("Loops of a float must share a yarn.")
|
|
67
90
|
if u not in self.front_floats:
|
|
68
91
|
self.front_floats[u] = set()
|
|
69
92
|
if v not in self.front_floats:
|
|
@@ -71,6 +94,18 @@ class Loop:
|
|
|
71
94
|
self.front_floats[u].add(v)
|
|
72
95
|
self.front_floats[v].add(u)
|
|
73
96
|
|
|
97
|
+
def remove_loop_from_back_floats(self) -> None:
|
|
98
|
+
"""
|
|
99
|
+
Removes this loop from being behind of all marked floats. Mutates the yarns that own edges of those floats.
|
|
100
|
+
"""
|
|
101
|
+
visited = set()
|
|
102
|
+
for u, v_loops in self.back_floats.items():
|
|
103
|
+
visited.add(u)
|
|
104
|
+
for v in v_loops:
|
|
105
|
+
if v not in visited and v in u.yarn: # float shares a yarn
|
|
106
|
+
u.yarn.loop_graph.edges[u, v]["Front_Loops"].remove(self)
|
|
107
|
+
self.back_floats = {}
|
|
108
|
+
|
|
74
109
|
def is_in_front_of_float(self, u: Loop, v: Loop) -> bool:
|
|
75
110
|
"""Check if this loop is positioned in front of the float between loops u and v.
|
|
76
111
|
|
|
@@ -101,19 +136,25 @@ class Loop:
|
|
|
101
136
|
Returns:
|
|
102
137
|
Loop | None: The prior loop on the yarn, or None if this is the first loop on the yarn.
|
|
103
138
|
"""
|
|
104
|
-
|
|
105
|
-
if loop is None:
|
|
106
|
-
return None
|
|
107
|
-
else:
|
|
108
|
-
return loop
|
|
139
|
+
return self.yarn.prior_loop(self)
|
|
109
140
|
|
|
110
|
-
def next_loop_on_yarn(self) -> Loop:
|
|
141
|
+
def next_loop_on_yarn(self) -> Loop | None:
|
|
111
142
|
"""Get the loop that follows this loop on the same yarn.
|
|
112
143
|
|
|
113
144
|
Returns:
|
|
114
|
-
Loop: The next loop on the yarn, or None if this is the last loop on the yarn.
|
|
145
|
+
Loop | None: The next loop on the yarn, or None if this is the last loop on the yarn.
|
|
146
|
+
"""
|
|
147
|
+
return self.yarn.next_loop(self)
|
|
148
|
+
|
|
149
|
+
def remove_parent_loops(self) -> list[Loop]:
|
|
150
|
+
"""
|
|
151
|
+
Removes the list of parent loops from this loop.
|
|
152
|
+
Returns:
|
|
153
|
+
list[Loop]: The list of parent loops that were removed.
|
|
115
154
|
"""
|
|
116
|
-
|
|
155
|
+
parents = self.parent_loops
|
|
156
|
+
self.parent_loops = []
|
|
157
|
+
return parents
|
|
117
158
|
|
|
118
159
|
def has_parent_loops(self) -> bool:
|
|
119
160
|
"""Check if this loop has any parent loops connected through stitch edges.
|
|
@@ -135,6 +176,31 @@ class Loop:
|
|
|
135
176
|
else:
|
|
136
177
|
self.parent_loops.append(parent)
|
|
137
178
|
|
|
179
|
+
def remove_parent(self, parent: Loop) -> None:
|
|
180
|
+
"""
|
|
181
|
+
Removes the given parent loop from the set of parents of this loop.
|
|
182
|
+
If the given loop is not a parent of this loop, nothing happens.
|
|
183
|
+
Args:
|
|
184
|
+
parent (Loop): The parent loop to remove.
|
|
185
|
+
"""
|
|
186
|
+
if parent in self.parent_loops:
|
|
187
|
+
self.parent_loops.remove(parent)
|
|
188
|
+
|
|
189
|
+
def ancestor_loops(self) -> set[Loop]:
|
|
190
|
+
"""
|
|
191
|
+
Returns:
|
|
192
|
+
set[Loop]: The set of loops that initiate all wales that lead to this loop. The empty set if this loop has no parents.
|
|
193
|
+
"""
|
|
194
|
+
if not self.has_parent_loops():
|
|
195
|
+
return set()
|
|
196
|
+
ancestors = set()
|
|
197
|
+
for parent_loop in self.parent_loops:
|
|
198
|
+
if parent_loop.has_parent_loops():
|
|
199
|
+
ancestors.update(parent_loop.ancestor_loops())
|
|
200
|
+
else:
|
|
201
|
+
ancestors.add(parent_loop)
|
|
202
|
+
return ancestors
|
|
203
|
+
|
|
138
204
|
@property
|
|
139
205
|
def loop_id(self) -> int:
|
|
140
206
|
"""Get the unique identifier of this loop.
|
|
@@ -160,7 +226,7 @@ class Loop:
|
|
|
160
226
|
"""
|
|
161
227
|
return self.loop_id
|
|
162
228
|
|
|
163
|
-
def __eq__(self, other:
|
|
229
|
+
def __eq__(self, other: object) -> bool:
|
|
164
230
|
"""Check equality with another base loop based on loop_id and type.
|
|
165
231
|
|
|
166
232
|
Args:
|
|
@@ -169,7 +235,7 @@ class Loop:
|
|
|
169
235
|
Returns:
|
|
170
236
|
bool: True if both loops have the same class and loop_id, False otherwise.
|
|
171
237
|
"""
|
|
172
|
-
return isinstance(other, other.__class__) and self.loop_id == other.loop_id
|
|
238
|
+
return isinstance(other, Loop) and isinstance(other, other.__class__) and self.loop_id == other.loop_id
|
|
173
239
|
|
|
174
240
|
def __lt__(self, other: Loop | int) -> bool:
|
|
175
241
|
"""Compare loop_id with another loop or integer for ordering.
|
knit_graphs/Pull_Direction.py
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
This module defines the Pull_Direction enumeration which represents the two ways a loop can be pulled through other loops in knitting: from back to front (knit) or from front to back (purl).
|
|
4
4
|
"""
|
|
5
|
+
|
|
5
6
|
from __future__ import annotations
|
|
6
7
|
|
|
7
8
|
from enum import Enum
|
|
@@ -13,6 +14,7 @@ class Pull_Direction(Enum):
|
|
|
13
14
|
This enumeration represents the two directions that yarn can be pulled through loops to create different stitch types.
|
|
14
15
|
BtF (Back to Front) creates knit stitches, while FtB (Front to Back) creates purl stitches.
|
|
15
16
|
"""
|
|
17
|
+
|
|
16
18
|
BtF = "Knit"
|
|
17
19
|
FtB = "Purl"
|
|
18
20
|
|
knit_graphs/Yarn.py
CHANGED
|
@@ -3,10 +3,12 @@
|
|
|
3
3
|
This module contains the Yarn class and Yarn_Properties dataclass which together represent the physical yarn used in knitting patterns.
|
|
4
4
|
The Yarn class manages the sequence of loops along a yarn and their floating relationships.
|
|
5
5
|
"""
|
|
6
|
+
|
|
6
7
|
from __future__ import annotations
|
|
7
8
|
|
|
9
|
+
from collections.abc import Iterator
|
|
8
10
|
from dataclasses import dataclass
|
|
9
|
-
from typing import TYPE_CHECKING,
|
|
11
|
+
from typing import TYPE_CHECKING, cast
|
|
10
12
|
|
|
11
13
|
from networkx import DiGraph, dfs_edges, dfs_preorder_nodes
|
|
12
14
|
|
|
@@ -22,6 +24,7 @@ class Yarn_Properties:
|
|
|
22
24
|
|
|
23
25
|
This frozen dataclass contains all the physical and visual properties that characterize a yarn, including its structure, weight, and appearance.
|
|
24
26
|
"""
|
|
27
|
+
|
|
25
28
|
name: str = "yarn" # name (str): The name or identifier for this yarn type.
|
|
26
29
|
plies: int = 2 # plies (int): The number of individual strands twisted together to form the yarn.
|
|
27
30
|
weight: float = 28 # weight (float): The weight category or thickness of the yarn.
|
|
@@ -52,7 +55,7 @@ class Yarn_Properties:
|
|
|
52
55
|
"""
|
|
53
56
|
return Yarn_Properties()
|
|
54
57
|
|
|
55
|
-
def __eq__(self, other:
|
|
58
|
+
def __eq__(self, other: object) -> bool:
|
|
56
59
|
"""Check equality with another Yarn_Properties instance.
|
|
57
60
|
|
|
58
61
|
Args:
|
|
@@ -61,7 +64,13 @@ class Yarn_Properties:
|
|
|
61
64
|
Returns:
|
|
62
65
|
bool: True if all properties (name, plies, weight, color) are equal, False otherwise.
|
|
63
66
|
"""
|
|
64
|
-
return
|
|
67
|
+
return (
|
|
68
|
+
isinstance(other, Yarn_Properties)
|
|
69
|
+
and self.name == other.name
|
|
70
|
+
and self.plies == other.plies
|
|
71
|
+
and self.weight == other.weight
|
|
72
|
+
and self.color == other.color
|
|
73
|
+
)
|
|
65
74
|
|
|
66
75
|
def __hash__(self) -> int:
|
|
67
76
|
"""Get hash value for use in sets and dictionaries.
|
|
@@ -83,7 +92,14 @@ class Yarn:
|
|
|
83
92
|
properties (Yarn_Properties): The physical and visual properties of this yarn.
|
|
84
93
|
"""
|
|
85
94
|
|
|
86
|
-
|
|
95
|
+
FRONT_LOOPS: str = "Front_Loops"
|
|
96
|
+
_BACK_LOOPS: str = "Back_Loops"
|
|
97
|
+
|
|
98
|
+
def __init__(
|
|
99
|
+
self,
|
|
100
|
+
yarn_properties: None | Yarn_Properties = None,
|
|
101
|
+
knit_graph: None | Knit_Graph = None,
|
|
102
|
+
):
|
|
87
103
|
"""Initialize a yarn with the specified properties and optional knit graph association.
|
|
88
104
|
|
|
89
105
|
Args:
|
|
@@ -141,7 +157,7 @@ class Yarn:
|
|
|
141
157
|
self.add_loop_in_front_of_float(front_loop, v, u)
|
|
142
158
|
else:
|
|
143
159
|
return
|
|
144
|
-
self.loop_graph.edges[u, v][
|
|
160
|
+
self.loop_graph.edges[u, v][self.FRONT_LOOPS].add(front_loop)
|
|
145
161
|
front_loop.add_loop_in_front_of_float(u, v)
|
|
146
162
|
|
|
147
163
|
def add_loop_behind_float(self, back_loop: Loop, u: Loop, v: Loop) -> None:
|
|
@@ -157,7 +173,7 @@ class Yarn:
|
|
|
157
173
|
self.add_loop_behind_float(back_loop, v, u)
|
|
158
174
|
else:
|
|
159
175
|
return
|
|
160
|
-
self.loop_graph.edges[u, v][
|
|
176
|
+
self.loop_graph.edges[u, v][self._BACK_LOOPS].add(back_loop)
|
|
161
177
|
back_loop.add_loop_behind_float(u, v)
|
|
162
178
|
|
|
163
179
|
def get_loops_in_front_of_float(self, u: Loop, v: Loop) -> set[Loop]:
|
|
@@ -176,7 +192,7 @@ class Yarn:
|
|
|
176
192
|
else:
|
|
177
193
|
return set()
|
|
178
194
|
else:
|
|
179
|
-
return cast(set[Loop], self.loop_graph.edges[u, v][
|
|
195
|
+
return cast(set[Loop], self.loop_graph.edges[u, v][self.FRONT_LOOPS])
|
|
180
196
|
|
|
181
197
|
def get_loops_behind_float(self, u: Loop, v: Loop) -> set[Loop]:
|
|
182
198
|
"""Get all loops positioned behind the float between two loops.
|
|
@@ -194,7 +210,7 @@ class Yarn:
|
|
|
194
210
|
else:
|
|
195
211
|
return set()
|
|
196
212
|
else:
|
|
197
|
-
return cast(set[Loop], self.loop_graph.edges[u, v][
|
|
213
|
+
return cast(set[Loop], self.loop_graph.edges[u, v][self._BACK_LOOPS])
|
|
198
214
|
|
|
199
215
|
@property
|
|
200
216
|
def last_loop(self) -> Loop | None:
|
|
@@ -290,6 +306,46 @@ class Yarn:
|
|
|
290
306
|
else:
|
|
291
307
|
return self.last_loop.loop_id + 1
|
|
292
308
|
|
|
309
|
+
def remove_loop(self, loop: Loop) -> None:
|
|
310
|
+
"""
|
|
311
|
+
Remove the given loop from the yarn.
|
|
312
|
+
Reconnects any neighboring loops to form a new float with the positioned in-front-of or behind the original floats positioned accordingly.
|
|
313
|
+
Resets the first_loop and last_loop properties if the removed loop was the tail of the yarn.
|
|
314
|
+
Args:
|
|
315
|
+
loop (Loop): The loop to remove from the yarn.
|
|
316
|
+
|
|
317
|
+
Raises:
|
|
318
|
+
KeyError: The given loop does not exist in the yarn.
|
|
319
|
+
"""
|
|
320
|
+
if loop not in self:
|
|
321
|
+
raise KeyError(f"Loop {loop} does not exist on yarn {self}.")
|
|
322
|
+
prior_loop = self.prior_loop(loop)
|
|
323
|
+
next_loop = self.next_loop(loop)
|
|
324
|
+
if isinstance(prior_loop, Loop) and isinstance(next_loop, Loop): # Loop is between two floats to be merged.
|
|
325
|
+
front_of_float_loops = self.get_loops_in_front_of_float(prior_loop, loop)
|
|
326
|
+
front_of_float_loops.update(self.get_loops_in_front_of_float(loop, next_loop))
|
|
327
|
+
back_of_float_loops = self.get_loops_behind_float(prior_loop, loop)
|
|
328
|
+
back_of_float_loops.update(self.get_loops_behind_float(loop, next_loop))
|
|
329
|
+
self.loop_graph.remove_node(loop)
|
|
330
|
+
self.loop_graph.add_edge(
|
|
331
|
+
prior_loop,
|
|
332
|
+
next_loop,
|
|
333
|
+
Front_Loops=front_of_float_loops,
|
|
334
|
+
Back_Loops=back_of_float_loops,
|
|
335
|
+
)
|
|
336
|
+
for front_loop in front_of_float_loops:
|
|
337
|
+
front_loop.add_loop_in_front_of_float(prior_loop, next_loop)
|
|
338
|
+
for back_loop in back_of_float_loops:
|
|
339
|
+
back_loop.add_loop_behind_float(prior_loop, next_loop)
|
|
340
|
+
return
|
|
341
|
+
if next_loop is None: # This was the last loop, make the prior loop the last loop.
|
|
342
|
+
assert loop is self.last_loop
|
|
343
|
+
self._last_loop = prior_loop
|
|
344
|
+
if prior_loop is None: # This was the first loop, make the next loop the first loop.
|
|
345
|
+
assert loop is self.first_loop
|
|
346
|
+
self._first_loop = next_loop
|
|
347
|
+
self.loop_graph.remove_node(loop)
|
|
348
|
+
|
|
293
349
|
def add_loop_to_end(self, loop: Loop) -> Loop:
|
|
294
350
|
"""Add an existing loop to the end of this yarn and associated knit graph.
|
|
295
351
|
|
|
@@ -396,8 +452,11 @@ class Yarn:
|
|
|
396
452
|
list[tuple[Loop, Loop, set[Loop]]]: List of tuples containing the two loops defining each float and the set of loops positioned in front of that float.
|
|
397
453
|
Only includes floats that have loops in front of them.
|
|
398
454
|
"""
|
|
399
|
-
return [
|
|
400
|
-
|
|
455
|
+
return [
|
|
456
|
+
(u, v, self.get_loops_in_front_of_float(u, v))
|
|
457
|
+
for u, v in self.edge_iter()
|
|
458
|
+
if len(self.get_loops_in_front_of_float(u, v)) > 0
|
|
459
|
+
]
|
|
401
460
|
|
|
402
461
|
def loops_behind_floats(self) -> list[tuple[Loop, Loop, set[Loop]]]:
|
|
403
462
|
"""Get all float segments with loops positioned behind them.
|
|
@@ -406,8 +465,11 @@ class Yarn:
|
|
|
406
465
|
list[tuple[Loop, Loop, set[Loop]]]: List of tuples containing the two loops defining each float and the set of loops positioned behind that float.
|
|
407
466
|
Only includes floats that have loops behind them.
|
|
408
467
|
"""
|
|
409
|
-
return [
|
|
410
|
-
|
|
468
|
+
return [
|
|
469
|
+
(u, v, self.get_loops_behind_float(u, v))
|
|
470
|
+
for u, v in self.edge_iter()
|
|
471
|
+
if len(self.get_loops_behind_float(u, v)) > 0
|
|
472
|
+
]
|
|
411
473
|
|
|
412
474
|
def __getitem__(self, item: int) -> Loop:
|
|
413
475
|
"""Get a loop by its ID from this yarn.
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
This module defines the Crossing_Direction enumeration which represents the different ways loops can cross over or under each other in cable knitting patterns.
|
|
4
4
|
"""
|
|
5
|
+
|
|
5
6
|
from __future__ import annotations
|
|
6
7
|
|
|
7
8
|
from enum import Enum
|
|
@@ -13,6 +14,7 @@ class Crossing_Direction(Enum):
|
|
|
13
14
|
This enumeration represents the three possible crossing relationships between loops: crossing over to the right, crossing under to the right, or no crossing at all.
|
|
14
15
|
These directions are fundamental to representing cable structures in knitted fabrics.
|
|
15
16
|
"""
|
|
17
|
+
|
|
16
18
|
Over_Right = "+"
|
|
17
19
|
Under_Right = "-"
|
|
18
20
|
No_Cross = "|"
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
This module provides the Loop_Braid_Graph class which tracks crossing relationships between loops in cable knitting patterns using a directed graph structure.
|
|
4
4
|
"""
|
|
5
|
+
|
|
5
6
|
from typing import cast
|
|
6
7
|
|
|
7
8
|
from networkx import DiGraph
|
|
@@ -20,6 +21,8 @@ class Loop_Braid_Graph:
|
|
|
20
21
|
loop_crossing_graph (DiGraph): A NetworkX directed graph storing loop crossing relationships with crossing direction attributes.
|
|
21
22
|
"""
|
|
22
23
|
|
|
24
|
+
_CROSSING = "crossing"
|
|
25
|
+
|
|
23
26
|
def __init__(self) -> None:
|
|
24
27
|
"""Initialize an empty loop braid graph with no crossings."""
|
|
25
28
|
self.loop_crossing_graph: DiGraph = DiGraph()
|
|
@@ -34,6 +37,15 @@ class Loop_Braid_Graph:
|
|
|
34
37
|
"""
|
|
35
38
|
self.loop_crossing_graph.add_edge(left_loop, right_loop, crossing=crossing_direction)
|
|
36
39
|
|
|
40
|
+
def remove_loop(self, loop: Loop) -> None:
|
|
41
|
+
"""
|
|
42
|
+
Removes any crossings that involve the given loop.
|
|
43
|
+
Args:
|
|
44
|
+
loop (Loop): The loop to remove.
|
|
45
|
+
"""
|
|
46
|
+
if loop in self:
|
|
47
|
+
self.loop_crossing_graph.remove_node(loop)
|
|
48
|
+
|
|
37
49
|
def __contains__(self, item: Loop | tuple[Loop, Loop]) -> bool:
|
|
38
50
|
"""Check if a loop or loop pair is contained in the braid graph.
|
|
39
51
|
|
|
@@ -60,8 +72,11 @@ class Loop_Braid_Graph:
|
|
|
60
72
|
if left_loop not in self:
|
|
61
73
|
return []
|
|
62
74
|
else:
|
|
63
|
-
return [
|
|
64
|
-
|
|
75
|
+
return [
|
|
76
|
+
rl
|
|
77
|
+
for rl in self.loop_crossing_graph.successors(left_loop)
|
|
78
|
+
if self.get_crossing(left_loop, rl) is not Crossing_Direction.No_Cross
|
|
79
|
+
]
|
|
65
80
|
|
|
66
81
|
def right_crossing_loops(self, right_loop: Loop) -> list[Loop]:
|
|
67
82
|
"""Get all loops that cross with the given loop when it is on the right side.
|
|
@@ -75,8 +90,11 @@ class Loop_Braid_Graph:
|
|
|
75
90
|
if right_loop not in self:
|
|
76
91
|
return []
|
|
77
92
|
else:
|
|
78
|
-
return [
|
|
79
|
-
|
|
93
|
+
return [
|
|
94
|
+
l
|
|
95
|
+
for l in self.loop_crossing_graph.predecessors(right_loop)
|
|
96
|
+
if self.get_crossing(l, right_loop) is not Crossing_Direction.No_Cross
|
|
97
|
+
]
|
|
80
98
|
|
|
81
99
|
def get_crossing(self, left_loop: Loop, right_loop: Loop) -> Crossing_Direction:
|
|
82
100
|
"""Get the crossing direction between two loops, creating a no-cross edge if none exists.
|
|
@@ -92,4 +110,7 @@ class Loop_Braid_Graph:
|
|
|
92
110
|
"""
|
|
93
111
|
if not self.loop_crossing_graph.has_edge(left_loop, right_loop):
|
|
94
112
|
self.add_crossing(left_loop, right_loop, Crossing_Direction.No_Cross)
|
|
95
|
-
return cast(
|
|
113
|
+
return cast(
|
|
114
|
+
Crossing_Direction,
|
|
115
|
+
self.loop_crossing_graph[left_loop][right_loop][self._CROSSING],
|
|
116
|
+
)
|
|
@@ -2,15 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
This module defines the Wale class which represents a vertical column of stitches in a knitted structure, maintaining the sequence and relationships between loops in that column.
|
|
4
4
|
"""
|
|
5
|
+
|
|
5
6
|
from __future__ import annotations
|
|
6
7
|
|
|
7
|
-
from
|
|
8
|
+
from collections.abc import Iterator
|
|
9
|
+
from typing import TYPE_CHECKING, cast
|
|
8
10
|
|
|
9
11
|
from networkx import DiGraph, dfs_preorder_nodes
|
|
10
12
|
|
|
11
13
|
from knit_graphs.Loop import Loop
|
|
12
14
|
from knit_graphs.Pull_Direction import Pull_Direction
|
|
13
15
|
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from knit_graphs.Knit_Graph import Knit_Graph
|
|
18
|
+
|
|
14
19
|
|
|
15
20
|
class Wale:
|
|
16
21
|
"""A data structure representing stitch relationships between loops in a vertical column of a knitted structure.
|
|
@@ -24,48 +29,46 @@ class Wale:
|
|
|
24
29
|
stitches (DiGraph): Stores the directed graph of stitch connections within this wale.
|
|
25
30
|
"""
|
|
26
31
|
|
|
27
|
-
|
|
32
|
+
_PULL_DIRECTION: str = "pull_direction"
|
|
33
|
+
|
|
34
|
+
def __init__(self, first_loop: Loop, knit_graph: Knit_Graph, end_loop: Loop | None = None) -> None:
|
|
28
35
|
"""Initialize a wale optionally starting with a specified loop.
|
|
29
36
|
|
|
30
37
|
Args:
|
|
31
|
-
first_loop (Loop
|
|
38
|
+
first_loop (Loop): The initial loop to start the wale with.
|
|
39
|
+
knit_graph (Knit_Graph): The knit graph that owns this wale.
|
|
40
|
+
end_loop (Loop, optional):
|
|
41
|
+
The loop to terminate the wale with.
|
|
42
|
+
If no loop is provided or this loop is not found, the wale will terminate at the first loop with no child.
|
|
32
43
|
"""
|
|
33
44
|
self.stitches: DiGraph = DiGraph()
|
|
34
|
-
self.first_loop
|
|
35
|
-
self.
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
+
self.stitches.add_node(first_loop)
|
|
46
|
+
self._knit_graph: Knit_Graph = knit_graph
|
|
47
|
+
self.first_loop: Loop = first_loop
|
|
48
|
+
self.last_loop: Loop = first_loop
|
|
49
|
+
self._build_wale_from_first_loop(end_loop)
|
|
50
|
+
|
|
51
|
+
def _build_wale_from_first_loop(self, end_loop: Loop | None) -> None:
|
|
52
|
+
while self._knit_graph.has_child_loop(self.last_loop):
|
|
53
|
+
child = self._knit_graph.get_child_loop(self.last_loop)
|
|
54
|
+
assert isinstance(child, Loop)
|
|
55
|
+
self.add_loop_to_end(child)
|
|
56
|
+
if end_loop is not None and child is end_loop:
|
|
57
|
+
return # found the end loop, so wrap up the wale
|
|
58
|
+
|
|
59
|
+
def add_loop_to_end(self, loop: Loop) -> None:
|
|
45
60
|
"""
|
|
46
|
-
|
|
47
|
-
self.stitches.add_node(loop)
|
|
48
|
-
self.first_loop = loop
|
|
49
|
-
self.last_loop = loop
|
|
50
|
-
else:
|
|
51
|
-
assert isinstance(pull_direction, Pull_Direction)
|
|
52
|
-
self.stitches.add_edge(self.last_loop, loop, pull_direction=pull_direction)
|
|
53
|
-
self.last_loop = loop
|
|
54
|
-
|
|
55
|
-
def add_loop_to_beginning(self, loop: Loop, pull_direction: Pull_Direction = Pull_Direction.BtF) -> None:
|
|
56
|
-
"""Add a loop to the beginning (bottom) of the wale with the specified pull direction.
|
|
61
|
+
Add a loop to the end (top) of the wale with the specified pull direction.
|
|
57
62
|
|
|
58
63
|
Args:
|
|
59
|
-
loop (Loop): The loop to add to the
|
|
60
|
-
pull_direction (Pull_Direction, optional): The direction to pull the existing first loop through this new loop. Defaults to Pull_Direction.BtF.
|
|
64
|
+
loop (Loop): The loop to add to the end of the wale.
|
|
61
65
|
"""
|
|
62
|
-
|
|
63
|
-
self.
|
|
64
|
-
|
|
65
|
-
self.last_loop
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
self.first_loop = loop
|
|
66
|
+
self.stitches.add_edge(
|
|
67
|
+
self.last_loop,
|
|
68
|
+
loop,
|
|
69
|
+
pull_direction=self._knit_graph.get_pull_direction(self.last_loop, loop),
|
|
70
|
+
)
|
|
71
|
+
self.last_loop = loop
|
|
69
72
|
|
|
70
73
|
def get_stitch_pull_direction(self, u: Loop, v: Loop) -> Pull_Direction:
|
|
71
74
|
"""Get the pull direction of the stitch edge between two loops in this wale.
|
|
@@ -77,10 +80,11 @@ class Wale:
|
|
|
77
80
|
Returns:
|
|
78
81
|
Pull_Direction: The pull direction of the stitch between loops u and v.
|
|
79
82
|
"""
|
|
80
|
-
return cast(Pull_Direction, self.stitches.edges[u, v][
|
|
83
|
+
return cast(Pull_Direction, self.stitches.edges[u, v][self._PULL_DIRECTION])
|
|
81
84
|
|
|
82
85
|
def split_wale(self, split_loop: Loop) -> tuple[Wale, Wale | None]:
|
|
83
|
-
"""
|
|
86
|
+
"""
|
|
87
|
+
Split this wale at the specified loop into two separate wales.
|
|
84
88
|
|
|
85
89
|
The split loop becomes the last loop of the first wale and the first loop of the second wale.
|
|
86
90
|
|
|
@@ -93,19 +97,25 @@ class Wale:
|
|
|
93
97
|
* The first wale (from start to split_loop). This will be the whole wale if the split_loop is not found.
|
|
94
98
|
* The second wale (from split_loop to end). This will be None if the split_loop is not found.
|
|
95
99
|
"""
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
growing_wale = Wale(split_loop)
|
|
103
|
-
found_loop = True
|
|
104
|
-
else:
|
|
105
|
-
growing_wale.add_loop_to_end(l, self.get_stitch_pull_direction(cast(Loop, growing_wale.last_loop), l))
|
|
106
|
-
if not found_loop:
|
|
100
|
+
if split_loop in self:
|
|
101
|
+
return (
|
|
102
|
+
Wale(self.first_loop, self._knit_graph, end_loop=split_loop),
|
|
103
|
+
Wale(split_loop, self._knit_graph, end_loop=self.last_loop),
|
|
104
|
+
)
|
|
105
|
+
else:
|
|
107
106
|
return self, None
|
|
108
|
-
|
|
107
|
+
|
|
108
|
+
def __eq__(self, other: object) -> bool:
|
|
109
|
+
"""
|
|
110
|
+
Args:
|
|
111
|
+
other (Wale): The wale to compare.
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
bool: True if all the loops in both wales are present and in the same order. False, otherwise.
|
|
115
|
+
"""
|
|
116
|
+
if not isinstance(other, Wale) or len(self) != len(other):
|
|
117
|
+
return False
|
|
118
|
+
return not any(l != o for l, o in zip(self, other, strict=False))
|
|
109
119
|
|
|
110
120
|
def __len__(self) -> int:
|
|
111
121
|
"""Get the number of loops in this wale.
|
|
@@ -149,13 +159,28 @@ class Wale:
|
|
|
149
159
|
return bool(self.stitches.has_node(item))
|
|
150
160
|
|
|
151
161
|
def __hash__(self) -> int:
|
|
152
|
-
"""
|
|
162
|
+
"""
|
|
163
|
+
Get the hash value of this wale based on its first loop.
|
|
153
164
|
|
|
154
165
|
Returns:
|
|
155
166
|
int: Hash value based on the first loop in this wale.
|
|
156
167
|
"""
|
|
157
168
|
return hash(self.first_loop)
|
|
158
169
|
|
|
170
|
+
def __str__(self) -> str:
|
|
171
|
+
"""
|
|
172
|
+
Returns:
|
|
173
|
+
str: The string representation of this wale.
|
|
174
|
+
"""
|
|
175
|
+
return f"Wale({self.first_loop}->{self.last_loop})"
|
|
176
|
+
|
|
177
|
+
def __repr__(self) -> str:
|
|
178
|
+
"""
|
|
179
|
+
Returns:
|
|
180
|
+
str: The string representation of this wale.
|
|
181
|
+
"""
|
|
182
|
+
return str(self)
|
|
183
|
+
|
|
159
184
|
def overlaps(self, other: Wale) -> bool:
|
|
160
185
|
"""Check if this wale has any loops in common with another wale.
|
|
161
186
|
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
This module provides the Wale_Braid class which represents complex cable knitting patterns as mathematical braid structures,
|
|
4
4
|
using concepts from algebraic topology to model how wales cross over and under each other.
|
|
5
5
|
"""
|
|
6
|
+
|
|
6
7
|
from knit_graphs.artin_wale_braids.Wale_Braid_Word import Wale_Braid_Word
|
|
7
8
|
from knit_graphs.artin_wale_braids.Wale_Group import Wale_Group
|
|
8
9
|
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
This module provides the Wale_Braid_Word class which represents a single step in a braid operation, describing how loops cross over each other within a course of knitting.
|
|
4
4
|
"""
|
|
5
|
+
|
|
5
6
|
from __future__ import annotations
|
|
6
7
|
|
|
7
8
|
from knit_graphs.artin_wale_braids.Crossing_Direction import Crossing_Direction
|
|
@@ -59,7 +60,7 @@ class Wale_Braid_Word:
|
|
|
59
60
|
new_crossings = {i: ~c for i, c in self.crossings.items()}
|
|
60
61
|
return Wale_Braid_Word(new_loops, new_crossings)
|
|
61
62
|
|
|
62
|
-
def __eq__(self, other:
|
|
63
|
+
def __eq__(self, other: object) -> bool:
|
|
63
64
|
"""Check equality with another wale braid word.
|
|
64
65
|
|
|
65
66
|
Two braid words are equal if they have the same loops in the same order and the same crossing operations at the same indices.
|
|
@@ -70,13 +71,13 @@ class Wale_Braid_Word:
|
|
|
70
71
|
Returns:
|
|
71
72
|
bool: True if both braid words have identical loops, loop ordering, and crossing operations, False otherwise.
|
|
72
73
|
"""
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
74
|
+
return (
|
|
75
|
+
isinstance(other, Wale_Braid_Word)
|
|
76
|
+
and len(self) == len(other)
|
|
77
|
+
and all(l == o for l, o in zip(self.loops, other.loops, strict=False))
|
|
78
|
+
and all(i in other.crossings for i in self.crossings)
|
|
79
|
+
and all(other.crossings[i] == cd for i, cd in self.crossings.items())
|
|
80
|
+
)
|
|
80
81
|
|
|
81
82
|
def is_inversion(self, other: Wale_Braid_Word) -> bool:
|
|
82
83
|
"""Check if another braid word is the inverse of this braid word.
|