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