knit-graphs 0.0.10__py3-none-any.whl → 0.0.12__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 +64 -41
- knit_graphs/Knit_Graph.py +70 -162
- knit_graphs/Knit_Graph_Visualizer.py +386 -185
- knit_graphs/Loop.py +124 -117
- knit_graphs/Pull_Direction.py +5 -2
- knit_graphs/Yarn.py +257 -219
- knit_graphs/artin_wale_braids/Crossing_Direction.py +2 -0
- knit_graphs/artin_wale_braids/Loop_Braid_Graph.py +47 -56
- knit_graphs/artin_wale_braids/Wale.py +67 -79
- knit_graphs/artin_wale_braids/Wale_Braid.py +8 -2
- knit_graphs/artin_wale_braids/Wale_Braid_Word.py +45 -31
- knit_graphs/artin_wale_braids/Wale_Group.py +53 -43
- knit_graphs/basic_knit_graph_generators.py +96 -140
- knit_graphs/directed_loop_graph.py +454 -0
- knit_graphs/knit_graph_builder.py +187 -0
- knit_graphs/knit_graph_errors/__init__.py +0 -0
- knit_graphs/knit_graph_errors/knit_graph_error.py +30 -0
- knit_graphs/py.typed +0 -0
- {knit_graphs-0.0.10.dist-info → knit_graphs-0.0.12.dist-info}/METADATA +3 -2
- {knit_graphs-0.0.10.dist-info → knit_graphs-0.0.12.dist-info}/RECORD +24 -19
- {knit_graphs-0.0.10.dist-info → knit_graphs-0.0.12.dist-info}/LICENSE +0 -0
- {knit_graphs-0.0.10.dist-info → knit_graphs-0.0.12.dist-info}/WHEEL +0 -0
knit_graphs/Knit_Graph.py
CHANGED
|
@@ -3,23 +3,26 @@
|
|
|
3
3
|
This module contains the main Knit_Graph class which serves as the central data structure for representing knitted fabrics.
|
|
4
4
|
It manages the relationships between loops, yarns, and structural elements like courses and wales.
|
|
5
5
|
"""
|
|
6
|
-
from __future__ import annotations
|
|
7
6
|
|
|
8
|
-
from
|
|
7
|
+
from __future__ import annotations
|
|
9
8
|
|
|
10
|
-
from
|
|
9
|
+
from collections.abc import Iterator
|
|
10
|
+
from typing import TypeVar
|
|
11
11
|
|
|
12
12
|
from knit_graphs.artin_wale_braids.Crossing_Direction import Crossing_Direction
|
|
13
13
|
from knit_graphs.artin_wale_braids.Loop_Braid_Graph import Loop_Braid_Graph
|
|
14
14
|
from knit_graphs.artin_wale_braids.Wale import Wale
|
|
15
15
|
from knit_graphs.artin_wale_braids.Wale_Group import Wale_Group
|
|
16
16
|
from knit_graphs.Course import Course
|
|
17
|
+
from knit_graphs.directed_loop_graph import Directed_Loop_Graph
|
|
17
18
|
from knit_graphs.Loop import Loop
|
|
18
19
|
from knit_graphs.Pull_Direction import Pull_Direction
|
|
19
20
|
from knit_graphs.Yarn import Yarn
|
|
20
21
|
|
|
22
|
+
LoopT = TypeVar("LoopT", bound=Loop)
|
|
21
23
|
|
|
22
|
-
|
|
24
|
+
|
|
25
|
+
class Knit_Graph(Directed_Loop_Graph[LoopT, Pull_Direction]):
|
|
23
26
|
"""A representation of knitted structures as connections between loops on yarns.
|
|
24
27
|
|
|
25
28
|
The Knit_Graph class is the main data structure for representing knitted fabrics.
|
|
@@ -29,30 +32,44 @@ class Knit_Graph:
|
|
|
29
32
|
|
|
30
33
|
def __init__(self) -> None:
|
|
31
34
|
"""Initialize an empty knit graph with no loops or yarns."""
|
|
32
|
-
|
|
33
|
-
self.braid_graph: Loop_Braid_Graph = Loop_Braid_Graph()
|
|
34
|
-
self._last_loop:
|
|
35
|
-
self.yarns: set[Yarn] = set()
|
|
35
|
+
super().__init__()
|
|
36
|
+
self.braid_graph: Loop_Braid_Graph[LoopT] = Loop_Braid_Graph()
|
|
37
|
+
self._last_loop: LoopT | None = None
|
|
38
|
+
self.yarns: set[Yarn[LoopT]] = set()
|
|
36
39
|
|
|
37
40
|
@property
|
|
38
|
-
def last_loop(self) ->
|
|
41
|
+
def last_loop(self) -> LoopT | None:
|
|
39
42
|
"""Get the most recently added loop in the graph.
|
|
40
43
|
|
|
41
44
|
Returns:
|
|
42
|
-
|
|
45
|
+
Loop | None: The last loop added to the graph, or None if no loops have been added.
|
|
43
46
|
"""
|
|
44
47
|
return self._last_loop
|
|
45
48
|
|
|
46
49
|
@property
|
|
47
|
-
def
|
|
48
|
-
"""
|
|
50
|
+
def stitch_iter(self) -> Iterator[tuple[LoopT, LoopT, Pull_Direction]]:
|
|
51
|
+
"""
|
|
52
|
+
Returns:
|
|
53
|
+
Iterator[tuple[LoopT, LoopT, Pull_Direction]]: Iterator over the edges and edge-data in the graph.
|
|
54
|
+
|
|
55
|
+
Notes:
|
|
56
|
+
No guarantees about the order of the edges.
|
|
57
|
+
"""
|
|
58
|
+
return self.edge_iter
|
|
59
|
+
|
|
60
|
+
def get_pull_direction(self, parent: LoopT | int, child: LoopT | int) -> Pull_Direction:
|
|
61
|
+
"""Get the pull direction of the stitch edge between parent and child loops.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
parent (Loop | int): The parent loop of the stitch edge.
|
|
65
|
+
child (Loop | int): The child loop of the stitch edge.
|
|
49
66
|
|
|
50
67
|
Returns:
|
|
51
|
-
|
|
68
|
+
Pull_Direction: The pull direction of the stitch-edge between the parent and child.
|
|
52
69
|
"""
|
|
53
|
-
return self.
|
|
70
|
+
return self.get_edge(parent, child)
|
|
54
71
|
|
|
55
|
-
def add_crossing(self, left_loop:
|
|
72
|
+
def add_crossing(self, left_loop: LoopT, right_loop: LoopT, crossing_direction: Crossing_Direction) -> None:
|
|
56
73
|
"""Add a cable crossing between two loops with the specified crossing direction.
|
|
57
74
|
|
|
58
75
|
Args:
|
|
@@ -62,41 +79,41 @@ class Knit_Graph:
|
|
|
62
79
|
"""
|
|
63
80
|
self.braid_graph.add_crossing(left_loop, right_loop, crossing_direction)
|
|
64
81
|
|
|
65
|
-
def add_loop(self, loop:
|
|
82
|
+
def add_loop(self, loop: LoopT) -> None:
|
|
66
83
|
"""Add a loop to the knit graph as a node.
|
|
67
84
|
|
|
68
85
|
Args:
|
|
69
86
|
loop (Loop): The loop to be added as a node in the graph. If the loop's yarn is not already in the graph, it will be added automatically.
|
|
70
87
|
"""
|
|
71
|
-
|
|
88
|
+
super().add_loop(loop)
|
|
72
89
|
if loop.yarn not in self.yarns:
|
|
73
90
|
self.add_yarn(loop.yarn)
|
|
74
91
|
if self._last_loop is None or loop > self._last_loop:
|
|
75
92
|
self._last_loop = loop
|
|
76
93
|
|
|
77
|
-
def remove_loop(self, loop:
|
|
94
|
+
def remove_loop(self, loop: LoopT | int) -> None:
|
|
78
95
|
"""
|
|
79
96
|
Remove the given loop from the knit graph.
|
|
80
97
|
Args:
|
|
81
|
-
loop (Loop): The loop to be removed.
|
|
98
|
+
loop (Loop | int): The loop or loop_id to be removed.
|
|
82
99
|
|
|
83
100
|
Raises:
|
|
84
101
|
KeyError: If the loop is not in the knit graph.
|
|
85
102
|
|
|
86
103
|
"""
|
|
87
|
-
if loop
|
|
88
|
-
|
|
89
|
-
|
|
104
|
+
if isinstance(loop, int):
|
|
105
|
+
loop = self.get_loop(loop)
|
|
106
|
+
if loop in self.braid_graph:
|
|
107
|
+
self.braid_graph.remove_loop(loop) # remove any crossing associated with this loop.
|
|
90
108
|
# Remove any stitch edges involving this loop.
|
|
91
|
-
loop.remove_parent_loops()
|
|
92
109
|
if self.has_child_loop(loop):
|
|
93
110
|
child_loop = self.get_child_loop(loop)
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
111
|
+
if child_loop is not None:
|
|
112
|
+
child_loop.remove_parent(loop)
|
|
113
|
+
super().remove_loop(loop)
|
|
97
114
|
# Remove loop from any floating positions
|
|
98
|
-
|
|
99
|
-
|
|
115
|
+
for yarn in self.yarns:
|
|
116
|
+
yarn.remove_loop_relative_to_floats(loop)
|
|
100
117
|
# Remove loop from yarn
|
|
101
118
|
yarn = loop.yarn
|
|
102
119
|
yarn.remove_loop(loop)
|
|
@@ -105,22 +122,25 @@ class Knit_Graph:
|
|
|
105
122
|
# Reset last loop
|
|
106
123
|
if loop is self.last_loop:
|
|
107
124
|
if len(self.yarns) == 0: # No loops left
|
|
108
|
-
assert len(self.stitch_graph.nodes) == 0
|
|
109
125
|
self._last_loop = None
|
|
110
126
|
else: # Set to the newest loop formed at the end of any yarns.
|
|
111
|
-
self._last_loop = max(y.last_loop for y in self.yarns if
|
|
127
|
+
self._last_loop = max(y.last_loop for y in self.yarns if y.last_loop is not None)
|
|
112
128
|
|
|
113
|
-
def add_yarn(self, yarn: Yarn) -> None:
|
|
129
|
+
def add_yarn(self, yarn: Yarn[LoopT]) -> None:
|
|
114
130
|
"""Add a yarn to the graph without adding its loops.
|
|
115
131
|
|
|
116
132
|
Args:
|
|
117
|
-
yarn (Yarn): The yarn to be added to the graph structure. This method assumes that loops do not need to be added separately.
|
|
133
|
+
yarn (Yarn[LoopT]): The yarn to be added to the graph structure. This method assumes that loops do not need to be added separately.
|
|
118
134
|
"""
|
|
119
135
|
self.yarns.add(yarn)
|
|
120
136
|
|
|
121
|
-
def connect_loops(
|
|
122
|
-
|
|
123
|
-
|
|
137
|
+
def connect_loops(
|
|
138
|
+
self,
|
|
139
|
+
parent_loop: LoopT,
|
|
140
|
+
child_loop: LoopT,
|
|
141
|
+
pull_direction: Pull_Direction = Pull_Direction.BtF,
|
|
142
|
+
stack_position: int | None = None,
|
|
143
|
+
) -> None:
|
|
124
144
|
"""Create a stitch edge by connecting a parent and child loop.
|
|
125
145
|
|
|
126
146
|
Args:
|
|
@@ -132,28 +152,24 @@ class Knit_Graph:
|
|
|
132
152
|
Raises:
|
|
133
153
|
KeyError: If either the parent_loop or child_loop is not already in the knit graph.
|
|
134
154
|
"""
|
|
135
|
-
|
|
136
|
-
raise KeyError(f"parent loop {parent_loop} not in Knit Graph")
|
|
137
|
-
if child_loop not in self:
|
|
138
|
-
raise KeyError(f"child loop {parent_loop} not in Knit Graph")
|
|
139
|
-
self.stitch_graph.add_edge(parent_loop, child_loop, pull_direction=pull_direction)
|
|
155
|
+
super().add_edge(parent_loop, child_loop, pull_direction)
|
|
140
156
|
child_loop.add_parent_loop(parent_loop, stack_position)
|
|
141
157
|
|
|
142
|
-
def get_wales_ending_with_loop(self, last_loop:
|
|
158
|
+
def get_wales_ending_with_loop(self, last_loop: LoopT) -> set[Wale[LoopT]]:
|
|
143
159
|
"""Get all wales (vertical columns of stitches) that end at the specified loop.
|
|
144
160
|
|
|
145
161
|
Args:
|
|
146
162
|
last_loop (Loop): The last loop of the joined set of wales.
|
|
147
163
|
|
|
148
164
|
Returns:
|
|
149
|
-
set[Wale]: The set of wales that end at this loop.
|
|
165
|
+
set[Wale[LoopT]]: The set of wales that end at this loop.
|
|
150
166
|
"""
|
|
151
167
|
if len(last_loop.parent_loops) == 0:
|
|
152
|
-
return {Wale(last_loop, self)}
|
|
153
|
-
|
|
154
|
-
return {Wale(
|
|
168
|
+
return {Wale[LoopT](last_loop, self)}
|
|
169
|
+
sources = self.source_loops(last_loop)
|
|
170
|
+
return {Wale[LoopT](source, self) for source in sources}
|
|
155
171
|
|
|
156
|
-
def get_terminal_wales(self) -> dict[
|
|
172
|
+
def get_terminal_wales(self) -> dict[LoopT, list[Wale]]:
|
|
157
173
|
"""
|
|
158
174
|
Get wale groups organized by their terminal loops.
|
|
159
175
|
|
|
@@ -161,24 +177,24 @@ class Knit_Graph:
|
|
|
161
177
|
dict[Loop, list[Wale]]: Dictionary mapping terminal loops to list of wales that terminate that wale.
|
|
162
178
|
"""
|
|
163
179
|
wale_groups = {}
|
|
164
|
-
for loop in self.terminal_loops
|
|
165
|
-
wale_groups[loop] =
|
|
180
|
+
for loop in self.terminal_loops:
|
|
181
|
+
wale_groups[loop] = list(self.get_wales_ending_with_loop(loop))
|
|
166
182
|
return wale_groups
|
|
167
183
|
|
|
168
|
-
def get_courses(self) -> list[Course]:
|
|
184
|
+
def get_courses(self) -> list[Course[LoopT]]:
|
|
169
185
|
"""Get all courses (horizontal rows) in the knit graph in chronological order.
|
|
170
186
|
|
|
171
187
|
Returns:
|
|
172
|
-
list[Course]: A list of courses representing horizontal rows of loops.
|
|
188
|
+
list[Course[LoopT]: A list of courses representing horizontal rows of loops.
|
|
173
189
|
The first course contains the initial set of loops. A course change occurs when a loop has a parent loop in the previous course.
|
|
174
190
|
"""
|
|
175
191
|
courses = []
|
|
176
|
-
course = Course(self)
|
|
177
|
-
for loop in self.sorted_loops
|
|
192
|
+
course = Course(0, self)
|
|
193
|
+
for loop in self.sorted_loops:
|
|
178
194
|
for parent in loop.parent_loops:
|
|
179
195
|
if parent in course: # start a new course
|
|
180
196
|
courses.append(course)
|
|
181
|
-
course = Course(self)
|
|
197
|
+
course = Course(course.course_number + 1, self)
|
|
182
198
|
break
|
|
183
199
|
course.add_loop(loop)
|
|
184
200
|
courses.append(course)
|
|
@@ -190,112 +206,4 @@ class Knit_Graph:
|
|
|
190
206
|
Returns:
|
|
191
207
|
set[Wale_Group]: The set of wale-groups that lead to the terminal loops of this graph. Each wale group represents a collection of wales that end at the same terminal loop.
|
|
192
208
|
"""
|
|
193
|
-
return
|
|
194
|
-
|
|
195
|
-
def __contains__(self, item: Loop | tuple[Loop, Loop]) -> bool:
|
|
196
|
-
"""Check if a loop is contained in the knit graph.
|
|
197
|
-
|
|
198
|
-
Args:
|
|
199
|
-
item (Loop | tuple[Loop, Loop]): The loop being checked for in the graph or the parent-child stitch edge to check for in the knit graph.
|
|
200
|
-
|
|
201
|
-
Returns:
|
|
202
|
-
bool: True if the given loop or stitch edge is in the graph, False otherwise.
|
|
203
|
-
"""
|
|
204
|
-
if isinstance(item, Loop):
|
|
205
|
-
return bool(self.stitch_graph.has_node(item))
|
|
206
|
-
else:
|
|
207
|
-
return bool(self.stitch_graph.has_edge(item[0], item[1]))
|
|
208
|
-
|
|
209
|
-
def __iter__(self) -> Iterator[Loop]:
|
|
210
|
-
"""
|
|
211
|
-
Returns:
|
|
212
|
-
Iterator[Loop]: An iterator over all loops in the knit graph.
|
|
213
|
-
"""
|
|
214
|
-
return cast(Iterator[Loop], iter(self.stitch_graph.nodes))
|
|
215
|
-
|
|
216
|
-
def __getitem__(self, item: int) -> Loop:
|
|
217
|
-
loop = next((l for l in self if l.loop_id == item), None)
|
|
218
|
-
if loop is None:
|
|
219
|
-
raise KeyError(f"Loop of id {item} not in knit graph")
|
|
220
|
-
return loop
|
|
221
|
-
|
|
222
|
-
def sorted_loops(self) -> list[Loop]:
|
|
223
|
-
"""
|
|
224
|
-
Returns:
|
|
225
|
-
list[Loop]: The list of loops in the stitch graph sorted from the earliest formed loop to the latest formed loop.
|
|
226
|
-
"""
|
|
227
|
-
return sorted(list(self.stitch_graph.nodes))
|
|
228
|
-
|
|
229
|
-
def get_pull_direction(self, parent: Loop, child: Loop) -> Pull_Direction | None:
|
|
230
|
-
"""Get the pull direction of the stitch edge between parent and child loops.
|
|
231
|
-
|
|
232
|
-
Args:
|
|
233
|
-
parent (Loop): The parent loop of the stitch edge.
|
|
234
|
-
child (Loop): The child loop of the stitch edge.
|
|
235
|
-
|
|
236
|
-
Returns:
|
|
237
|
-
Pull_Direction | None: The pull direction of the stitch-edge between the parent and child, or None if there is no edge between these loops.
|
|
238
|
-
"""
|
|
239
|
-
edge = self.get_stitch_edge(parent, child)
|
|
240
|
-
if edge is None:
|
|
241
|
-
return None
|
|
242
|
-
else:
|
|
243
|
-
return cast(Pull_Direction, edge['pull_direction'])
|
|
244
|
-
|
|
245
|
-
def get_stitch_edge(self, parent: Loop, child: Loop) -> dict[str, Any] | None:
|
|
246
|
-
"""Get the stitch edge data between two loops.
|
|
247
|
-
|
|
248
|
-
Args:
|
|
249
|
-
parent (Loop): The parent loop of the stitch edge.
|
|
250
|
-
child (Loop): The child loop of the stitch edge.
|
|
251
|
-
|
|
252
|
-
Returns:
|
|
253
|
-
dict[str, Any] | None: The edge data dictionary for this stitch edge, or None if no edge exists between these loops.
|
|
254
|
-
"""
|
|
255
|
-
if self.stitch_graph.has_edge(parent, child):
|
|
256
|
-
return cast(dict[str, Any], self.stitch_graph.get_edge_data(parent, child))
|
|
257
|
-
else:
|
|
258
|
-
return None
|
|
259
|
-
|
|
260
|
-
def get_child_loop(self, loop: Loop) -> Loop | None:
|
|
261
|
-
"""Get the child loop of the specified parent loop.
|
|
262
|
-
|
|
263
|
-
Args:
|
|
264
|
-
loop (Loop): The loop to look for a child loop from.
|
|
265
|
-
|
|
266
|
-
Returns:
|
|
267
|
-
Loop | None: The child loop if one exists, or None if no child loop is found.
|
|
268
|
-
"""
|
|
269
|
-
successors = [*self.stitch_graph.successors(loop)]
|
|
270
|
-
if len(successors) == 0:
|
|
271
|
-
return None
|
|
272
|
-
return cast(Loop, successors[0])
|
|
273
|
-
|
|
274
|
-
def has_child_loop(self, loop: Loop) -> bool:
|
|
275
|
-
"""Check if a loop has a child loop connected to it.
|
|
276
|
-
|
|
277
|
-
Args:
|
|
278
|
-
loop (Loop): The loop to check for child connections.
|
|
279
|
-
|
|
280
|
-
Returns:
|
|
281
|
-
bool: True if the loop has a child loop, False otherwise.
|
|
282
|
-
"""
|
|
283
|
-
return self.get_child_loop(loop) is not None
|
|
284
|
-
|
|
285
|
-
def is_terminal_loop(self, loop: Loop) -> bool:
|
|
286
|
-
"""Check if a loop is terminal (has no child loops and terminates a wale).
|
|
287
|
-
|
|
288
|
-
Args:
|
|
289
|
-
loop (Loop): The loop to check for terminal status.
|
|
290
|
-
|
|
291
|
-
Returns:
|
|
292
|
-
bool: True if the loop has no child loops and terminates a wale, False otherwise.
|
|
293
|
-
"""
|
|
294
|
-
return not self.has_child_loop(loop)
|
|
295
|
-
|
|
296
|
-
def terminal_loops(self) -> Iterator[Loop]:
|
|
297
|
-
"""
|
|
298
|
-
Returns:
|
|
299
|
-
Iterator[Loop]: An iterator over all terminal loops in the knit graph.
|
|
300
|
-
"""
|
|
301
|
-
return iter(l for l in self if self.is_terminal_loop(l))
|
|
209
|
+
return {Wale_Group(terminal_loop, self) for terminal_loop in self.terminal_loops}
|