knit-graphs 0.0.6__py3-none-any.whl → 0.0.8__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.
Files changed (47) hide show
  1. knit_graphs-0.0.6.dist-info/licenses/LICENSE → LICENSE +21 -21
  2. README.md +75 -0
  3. docs/Makefile +20 -0
  4. docs/make.bat +35 -0
  5. docs/source/api/knit_graphs.Course.rst +7 -0
  6. docs/source/api/knit_graphs.Knit_Graph.rst +7 -0
  7. docs/source/api/knit_graphs.Knit_Graph_Visualizer.rst +7 -0
  8. docs/source/api/knit_graphs.Loop.rst +7 -0
  9. docs/source/api/knit_graphs.Pull_Direction.rst +7 -0
  10. docs/source/api/knit_graphs.Yarn.rst +7 -0
  11. docs/source/api/knit_graphs.artin_wale_braids.Crossing_Direction.rst +7 -0
  12. docs/source/api/knit_graphs.artin_wale_braids.Loop_Braid_Graph.rst +7 -0
  13. docs/source/api/knit_graphs.artin_wale_braids.Wale.rst +7 -0
  14. docs/source/api/knit_graphs.artin_wale_braids.Wale_Braid.rst +7 -0
  15. docs/source/api/knit_graphs.artin_wale_braids.Wale_Braid_Word.rst +7 -0
  16. docs/source/api/knit_graphs.artin_wale_braids.Wale_Group.rst +7 -0
  17. docs/source/api/knit_graphs.artin_wale_braids.rst +23 -0
  18. docs/source/api/knit_graphs.basic_knit_graph_generators.rst +7 -0
  19. docs/source/api/knit_graphs.rst +32 -0
  20. docs/source/conf.py +335 -0
  21. docs/source/index.rst +71 -0
  22. docs/source/installation.rst +67 -0
  23. knit_graphs/Course.py +156 -104
  24. knit_graphs/Knit_Graph.py +249 -186
  25. knit_graphs/Knit_Graph_Visualizer.py +675 -0
  26. knit_graphs/Loop.py +141 -155
  27. knit_graphs/Pull_Direction.py +68 -23
  28. knit_graphs/Yarn.py +424 -267
  29. knit_graphs/__init__.py +3 -3
  30. knit_graphs/_base_classes.py +173 -0
  31. knit_graphs/artin_wale_braids/Crossing_Direction.py +74 -15
  32. knit_graphs/artin_wale_braids/Loop_Braid_Graph.py +95 -62
  33. knit_graphs/artin_wale_braids/Wale.py +169 -93
  34. knit_graphs/artin_wale_braids/Wale_Braid.py +50 -30
  35. knit_graphs/artin_wale_braids/Wale_Braid_Word.py +99 -54
  36. knit_graphs/artin_wale_braids/Wale_Group.py +136 -88
  37. knit_graphs/basic_knit_graph_generators.py +251 -0
  38. knit_graphs-0.0.8.dist-info/LICENSE +21 -0
  39. {knit_graphs-0.0.6.dist-info → knit_graphs-0.0.8.dist-info}/METADATA +33 -24
  40. knit_graphs-0.0.8.dist-info/RECORD +42 -0
  41. {knit_graphs-0.0.6.dist-info → knit_graphs-0.0.8.dist-info}/WHEEL +1 -1
  42. knit_graphs/__about__.py +0 -4
  43. knit_graphs/knit_graph_generators/__init__.py +0 -0
  44. knit_graphs/knit_graph_generators/basic_knit_graph_generators.py +0 -248
  45. knit_graphs/knit_graph_visualizer/Stitch_Visualizer.py +0 -427
  46. knit_graphs/knit_graph_visualizer/__init__.py +0 -0
  47. knit_graphs-0.0.6.dist-info/RECORD +0 -22
knit_graphs/Yarn.py CHANGED
@@ -1,267 +1,424 @@
1
- """
2
- The Yarn Data Structure
3
- """
4
- from dataclasses import dataclass, field
5
-
6
- import networkx
7
-
8
- from knit_graphs.Loop import Loop
9
-
10
-
11
- @dataclass(unsafe_hash=True)
12
- class Yarn_Properties:
13
- """
14
- Class Structure for maintaining relevant data about a yarn
15
- """
16
- name: str = field(compare=False, default="yarn")
17
- plies: int = 2
18
- weight: float = 28
19
- color: str = "green"
20
-
21
- def __str__(self):
22
- return f"{self.name}({self.plies}-{self.weight},{self.color})"
23
-
24
- def __repr__(self):
25
- return self.name
26
-
27
-
28
- class Yarn:
29
- """
30
- A class to represent a yarn structure.
31
- Yarns are structured as a list of loops with a pointer to the last loop id
32
- """
33
-
34
- def __init__(self, yarn_properties: None | Yarn_Properties = None):
35
- """
36
- A Graph structure to show the yarn-wise relationship between loops
37
- :param yarn_properties:
38
- """
39
- if yarn_properties is None:
40
- yarn_properties = Yarn_Properties()
41
- self.properties: Yarn_Properties = yarn_properties
42
- self.loop_graph: networkx.DiGraph = networkx.DiGraph()
43
- self._first_loop: Loop | None = None
44
- self._last_loop: Loop | None = None
45
-
46
- def has_float(self, u: Loop, v: Loop) -> bool:
47
- """
48
- :param u: first loop
49
- :param v: second loop
50
- :return: True if there is a float edge between loops
51
- """
52
- return self.loop_graph.has_edge(u, v)
53
-
54
- def add_loop_in_front_of_float(self, front_loop: Loop, u: Loop, v: Loop):
55
- """
56
- Records front_loop falls in front of float between u and v.
57
- :param front_loop: loop in front of float.
58
- :param u: First loop in float.
59
- :param v: Second loop in float.
60
- """
61
- if not self.has_float(u, v):
62
- if self.has_float(v, u):
63
- self.add_loop_in_front_of_float(front_loop, v, u)
64
- else:
65
- return
66
- self.loop_graph.edges[u, v]["Front_Loops"].add(front_loop)
67
- front_loop.add_loop_in_front_of_float(u, v)
68
-
69
- def add_loop_behind_float(self, back_loop: Loop, u: Loop, v: Loop):
70
- """
71
- Records back_loop falls behind the float between u and v
72
- :param back_loop: loop behind the float.
73
- :param u: First loop in float.
74
- :param v: Second loop in float.
75
- """
76
- if not self.has_float(u, v):
77
- if self.has_float(v, u):
78
- self.add_loop_behind_float(back_loop, v, u)
79
- else:
80
- return
81
- self.loop_graph.edges[u, v]["Back_Loops"].add(back_loop)
82
- back_loop.add_loop_behind_float(u, v)
83
-
84
- def get_loops_in_front_of_float(self, u: Loop, v: Loop) -> set[Loop]:
85
- """
86
- :param u:
87
- :param v:
88
- :return: list of loops in front of float between u and v or empty if no float exists
89
- """
90
- if not self.has_float(u, v):
91
- if self.has_float(v, u):
92
- return self.get_loops_in_front_of_float(v, u)
93
- else:
94
- return set()
95
- else:
96
- return self.loop_graph.edges[u, v]['Front_Loops']
97
-
98
- def get_loops_behind_float(self, u: Loop, v: Loop) -> set[Loop]:
99
- """
100
- :param u:
101
- :param v:
102
- :return: list of loops behind float between u and v or empty if no float exists
103
- """
104
- if not self.has_float(u, v):
105
- if self.has_float(v, u):
106
- return self.get_loops_behind_float(v, u)
107
- else:
108
- return set()
109
- else:
110
- return self.loop_graph.edges[u, v]['Back_Loops']
111
-
112
- @property
113
- def last_loop(self) -> Loop | None:
114
- """
115
- :return: Last loop id at the end of the yarn
116
- """
117
- return self._last_loop
118
-
119
- @property
120
- def first_loop(self) -> Loop | None:
121
- """
122
- :return: Last loop id at the end of the yarn
123
- """
124
- return self._first_loop
125
-
126
- @property
127
- def has_loops(self) -> bool:
128
- """
129
- :return: True if the yarn has loops on it
130
- """
131
- return self.last_loop is not None
132
-
133
- @property
134
- def yarn_id(self) -> str:
135
- """
136
- :return: the id of this yarn
137
- """
138
- return str(self.properties)
139
-
140
- def __str__(self):
141
- return self.yarn_id
142
-
143
- def __repr__(self):
144
- return repr(self.properties)
145
-
146
- def __hash__(self):
147
- return hash(self.properties)
148
-
149
- def __len__(self):
150
- return len(self.loop_graph.nodes)
151
-
152
- def make_loop_on_end(self, knit_graph=None) -> Loop:
153
- """
154
- Adds the loop at the end of the yarn.
155
- :param knit_graph: An optional Knit_Graph used to calculate last loop id in knitgraph.
156
- """
157
- loop_id = self._next_loop_id(knit_graph)
158
- loop = Loop(loop_id, self)
159
- return self.add_loop_to_end(knit_graph, loop)
160
-
161
- def _next_loop_id(self, knit_graph) -> int:
162
- if knit_graph is not None:
163
- if knit_graph.last_loop is None:
164
- loop_id = 0
165
- else:
166
- loop_id = knit_graph.last_loop.loop_id + 1
167
- else:
168
- loop_id = self.last_loop.loop_id + 1
169
- return loop_id
170
-
171
- def add_loop_to_end(self, knit_graph, loop: Loop):
172
- """
173
- :param knit_graph: knit graph that holds loop
174
- :param loop: Loop to be at end of yarn
175
- :return: Add loop to end of yarn and knit graph
176
- """
177
- self.insert_loop(loop, self._last_loop)
178
- if knit_graph is not None:
179
- knit_graph.add_loop(loop)
180
- return loop
181
-
182
- def insert_loop(self, loop: Loop, prior_loop: Loop | None = None):
183
- """
184
- Adds the loop at the end of the yarn.
185
- :param prior_loop: The loop that came before this on the yarn. Default to last loop added to end of yarn.
186
- :param loop: The loop to be added to this index. If none, a non-twisted loop will be created.
187
- """
188
- self.loop_graph.add_node(loop)
189
- if not self.has_loops:
190
- self._last_loop = loop
191
- self._first_loop = loop
192
- return
193
- elif prior_loop is None:
194
- prior_loop = self.last_loop
195
- self.loop_graph.add_edge(prior_loop, loop, Front_Loops=set(), Back_Loops=set())
196
- if prior_loop == self.last_loop:
197
- self._last_loop = loop
198
-
199
- def next_loop(self, loop: Loop) -> Loop | None:
200
- """
201
- :param loop: loop to index from. Will raise attribute error if not in yarn
202
- :return: Next loop on yarn after loop or None if last loop
203
- """
204
- if loop not in self:
205
- raise KeyError(f"Loop {loop} is not on Yarn")
206
- successors = [*self.loop_graph.successors(loop)]
207
- if len(successors) > 0:
208
- return successors[0]
209
- else:
210
- return None
211
-
212
- def prior_loop(self, loop: Loop) -> Loop | None:
213
- """
214
- :param loop: loop to index from. Will raise attribute error if not in yarn
215
- :return: Prior loop on yarn before loop or None if first loop
216
- """
217
- if loop not in self:
218
- raise KeyError(f"Loop {loop} is not on Yarn")
219
- predecessors = [*self.loop_graph.predecessors(loop)]
220
- if len(predecessors) > 0:
221
- return predecessors[0]
222
- else:
223
- return None
224
-
225
- def __contains__(self, item: Loop):
226
- """
227
- Return true if the loop is on the yarn
228
- :param item: the loop being checked for in the yarn
229
- :return: true if the loop_id of item or the loop is in the yarn
230
- """
231
- return self.loop_graph.has_node(item)
232
-
233
- def __iter__(self):
234
- return networkx.dfs_preorder_nodes(self.loop_graph, self.first_loop)
235
-
236
- def edge_iter(self):
237
- """
238
- :return: Iterator over edges between loops on yarn.
239
- """
240
- return networkx.dfs_edges(self.loop_graph, self.first_loop)
241
-
242
- def loops_in_front_of_floats(self) -> list[tuple[Loop, Loop, set[Loop]]]:
243
- """
244
- :return: List of loops connecting floats and loops that are positioned in front of the float.
245
- Ignores floats with no loops in front of them.
246
- """
247
- return [(u, v, self.get_loops_in_front_of_float(u, v)) for u, v in self.edge_iter()
248
- if len(self.get_loops_in_front_of_float(u, v)) > 0]
249
-
250
- def loops_behind_floats(self) -> list[tuple[Loop, Loop, set[Loop]]]:
251
- """
252
- :return: List of loops connecting floats and loops that are positioned behind the float.
253
- Ignores floats with no loops behind them.
254
- """
255
- return [(u, v, self.get_loops_behind_float(u, v)) for u, v in self.edge_iter()
256
- if len(self.get_loops_behind_float(u, v)) > 0]
257
-
258
- def __getitem__(self, item: int) -> Loop:
259
- """
260
- Collect the loop of a given id
261
- :param item: the loop_id being checked for in the yarn
262
- :return: the Loop on the yarn with the matching id
263
- """
264
- if item not in self:
265
- raise AttributeError
266
- else:
267
- return self.loop_graph.nodes[item]
1
+ """The Yarn Data Structure.
2
+
3
+ This module contains the Yarn class and Yarn_Properties dataclass which together represent the physical yarn used in knitting patterns.
4
+ The Yarn class manages the sequence of loops along a yarn and their floating relationships.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ from dataclasses import dataclass
9
+ from typing import Iterator, cast
10
+
11
+ from networkx import dfs_edges, dfs_preorder_nodes
12
+
13
+ from knit_graphs._base_classes import _Base_Knit_Graph, _Base_Yarn
14
+ from knit_graphs.Loop import Loop
15
+
16
+
17
+ @dataclass(frozen=True)
18
+ class Yarn_Properties:
19
+ """Dataclass structure for maintaining relevant physical properties of a yarn.
20
+
21
+ This frozen dataclass contains all the physical and visual properties that characterize a yarn, including its structure, weight, and appearance.
22
+ """
23
+ name: str = "yarn" # name (str): The name or identifier for this yarn type.
24
+ plies: int = 2 # plies (int): The number of individual strands twisted together to form the yarn.
25
+ weight: float = 28 # weight (float): The weight category or thickness of the yarn.
26
+ color: str = "green" # color (str): The color of the yarn for visualization purposes.
27
+
28
+ def __str__(self) -> str:
29
+ """Get a formatted string representation of the yarn properties.
30
+
31
+ Returns:
32
+ str: String representation in format "name(plies-weight,color)".
33
+ """
34
+ return f"{self.name}({self.plies}-{self.weight},{self.color})"
35
+
36
+ def __repr__(self) -> str:
37
+ """Get the name of the yarn for debugging purposes.
38
+
39
+ Returns:
40
+ str: The name of the yarn.
41
+ """
42
+ return self.name
43
+
44
+ @staticmethod
45
+ def default_yarn() -> Yarn_Properties:
46
+ """Create a default yarn properties instance.
47
+
48
+ Returns:
49
+ Yarn_Properties: A default yarn instance with standard properties.
50
+ """
51
+ return Yarn_Properties()
52
+
53
+ def __eq__(self, other: Yarn_Properties) -> bool:
54
+ """Check equality with another Yarn_Properties instance.
55
+
56
+ Args:
57
+ other (Yarn_Properties): The other yarn properties to compare with.
58
+
59
+ Returns:
60
+ bool: True if all properties (name, plies, weight, color) are equal, False otherwise.
61
+ """
62
+ return self.name == other.name and self.plies == other.plies and self.weight == other.weight and self.color == other.color
63
+
64
+ def __hash__(self) -> int:
65
+ """Get hash value for use in sets and dictionaries.
66
+
67
+ Returns:
68
+ int: Hash value based on all yarn properties.
69
+ """
70
+ return hash((self.name, self.plies, self.weight, self.color))
71
+
72
+
73
+ class Yarn(_Base_Yarn):
74
+ """A class to represent a yarn structure as a sequence of connected loops.
75
+
76
+ The Yarn class manages a directed graph of loops representing the physical yarn path through a knitted structure.
77
+ It maintains the sequential order of loops and their floating relationships, providing methods for navigation and manipulation of the yarn structure.
78
+
79
+ Attributes:
80
+ properties (Yarn_Properties): The physical and visual properties of this yarn.
81
+ """
82
+
83
+ def __init__(self, yarn_properties: None | Yarn_Properties = None, knit_graph: None | _Base_Knit_Graph = None):
84
+ """Initialize a yarn with the specified properties and optional knit graph association.
85
+
86
+ Args:
87
+ yarn_properties (None | Yarn_Properties, optional): The properties defining this yarn. If None, uses default properties. Defaults to standard properties.
88
+ knit_graph (None | Knit_Graph, optional): The knit graph that will own this yarn. Can be None for standalone yarns. Defaults to None.
89
+ """
90
+ super().__init__()
91
+ if yarn_properties is None:
92
+ yarn_properties = Yarn_Properties.default_yarn()
93
+ self.properties: Yarn_Properties = yarn_properties
94
+ self._first_loop: Loop | None = None
95
+ self._last_loop: Loop | None = None
96
+ self._knit_graph: None | _Base_Knit_Graph = knit_graph
97
+
98
+ @property
99
+ def knit_graph(self) -> None | _Base_Knit_Graph:
100
+ """Get the knit graph that owns this yarn.
101
+
102
+ Returns:
103
+ None | Knit_Graph: The knit graph that owns this yarn, or None if not associated with a graph.
104
+ """
105
+ return self._knit_graph
106
+
107
+ @knit_graph.setter
108
+ def knit_graph(self, knit_graph: _Base_Knit_Graph) -> None:
109
+ """Set the knit graph that owns this yarn.
110
+
111
+ Args:
112
+ knit_graph (Knit_Graph): The knit graph to associate with this yarn.
113
+ """
114
+ self._knit_graph = knit_graph
115
+
116
+ def has_float(self, u: Loop, v: Loop) -> bool:
117
+ """Check if there is a float edge between two loops on this yarn.
118
+
119
+ Args:
120
+ u (Loop): The first loop to check for float connection.
121
+ v (Loop): The second loop to check for float connection.
122
+
123
+ Returns:
124
+ bool: True if there is a float edge between the loops, False otherwise.
125
+ """
126
+ return bool(self.loop_graph.has_edge(u, v))
127
+
128
+ def add_loop_in_front_of_float(self, front_loop: Loop, u: Loop, v: Loop) -> None:
129
+ """Record that a loop falls in front of the float between two other loops.
130
+
131
+ Args:
132
+ front_loop (Loop): The loop that is positioned in front of the float.
133
+ u (Loop): The first loop in the float pair.
134
+ v (Loop): The second loop in the float pair.
135
+ """
136
+ if not self.has_float(u, v):
137
+ if self.has_float(v, u):
138
+ self.add_loop_in_front_of_float(front_loop, v, u)
139
+ else:
140
+ return
141
+ self.loop_graph.edges[u, v]["Front_Loops"].add(front_loop)
142
+ front_loop.add_loop_in_front_of_float(u, v)
143
+
144
+ def add_loop_behind_float(self, back_loop: Loop, u: Loop, v: Loop) -> None:
145
+ """Record that a loop falls behind the float between two other loops.
146
+
147
+ Args:
148
+ back_loop (Loop): The loop that is positioned behind the float.
149
+ u (Loop): The first loop in the float pair.
150
+ v (Loop): The second loop in the float pair.
151
+ """
152
+ if not self.has_float(u, v):
153
+ if self.has_float(v, u):
154
+ self.add_loop_behind_float(back_loop, v, u)
155
+ else:
156
+ return
157
+ self.loop_graph.edges[u, v]["Back_Loops"].add(back_loop)
158
+ back_loop.add_loop_behind_float(u, v)
159
+
160
+ def get_loops_in_front_of_float(self, u: Loop, v: Loop) -> set[Loop]:
161
+ """Get all loops positioned in front of the float between two loops.
162
+
163
+ Args:
164
+ u (Loop): The first loop in the float pair.
165
+ v (Loop): The second loop in the float pair.
166
+
167
+ Returns:
168
+ set[Loop]: Set of loops positioned in front of the float between u and v, or empty set if no float exists.
169
+ """
170
+ if not self.has_float(u, v):
171
+ if self.has_float(v, u):
172
+ return self.get_loops_in_front_of_float(v, u)
173
+ else:
174
+ return set()
175
+ else:
176
+ return cast(set[Loop], self.loop_graph.edges[u, v]['Front_Loops'])
177
+
178
+ def get_loops_behind_float(self, u: Loop, v: Loop) -> set[Loop]:
179
+ """Get all loops positioned behind the float between two loops.
180
+
181
+ Args:
182
+ u (Loop): The first loop in the float pair.
183
+ v (Loop): The second loop in the float pair.
184
+
185
+ Returns:
186
+ set[Loop]: Set of loops positioned behind the float between u and v, or empty set if no float exists.
187
+ """
188
+ if not self.has_float(u, v):
189
+ if self.has_float(v, u):
190
+ return self.get_loops_behind_float(v, u)
191
+ else:
192
+ return set()
193
+ else:
194
+ return cast(set[Loop], self.loop_graph.edges[u, v]['Back_Loops'])
195
+
196
+ @property
197
+ def last_loop(self) -> Loop | None:
198
+ """Get the most recently added loop at the end of the yarn.
199
+
200
+ Returns:
201
+ Loop | None: The last loop on this yarn, or None if no loops have been added.
202
+ """
203
+ return self._last_loop
204
+
205
+ @property
206
+ def first_loop(self) -> Loop | None:
207
+ """Get the first loop at the beginning of the yarn.
208
+
209
+ Returns:
210
+ Loop | None: The first loop on this yarn, or None if no loops have been added.
211
+ """
212
+ return self._first_loop
213
+
214
+ @property
215
+ def has_loops(self) -> bool:
216
+ """Check if the yarn has any loops on it.
217
+
218
+ Returns:
219
+ bool: True if the yarn has at least one loop, False otherwise.
220
+ """
221
+ return self.last_loop is not None
222
+
223
+ @property
224
+ def yarn_id(self) -> str:
225
+ """Get the string identifier for this yarn.
226
+
227
+ Returns:
228
+ str: The string representation of the yarn properties.
229
+ """
230
+ return str(self.properties)
231
+
232
+ def __str__(self) -> str:
233
+ """Get the string representation of this yarn.
234
+
235
+ Returns:
236
+ str: The yarn identifier string.
237
+ """
238
+ return self.yarn_id
239
+
240
+ def __repr__(self) -> str:
241
+ """Get the representation string of this yarn for debugging.
242
+
243
+ Returns:
244
+ str: The representation of the yarn properties.
245
+ """
246
+ return repr(self.properties)
247
+
248
+ def __hash__(self) -> int:
249
+ """Get the hash value of this yarn for use in sets and dictionaries.
250
+
251
+ Returns:
252
+ int: Hash value based on the yarn properties.
253
+ """
254
+ return hash(self.properties)
255
+
256
+ def __len__(self) -> int:
257
+ """Get the number of loops on this yarn.
258
+
259
+ Returns:
260
+ int: The total number of loops on this yarn.
261
+ """
262
+ return len(self.loop_graph.nodes)
263
+
264
+ def make_loop_on_end(self) -> Loop:
265
+ """Create and add a new loop at the end of the yarn.
266
+
267
+ Returns:
268
+ Loop: The newly created loop that was added to the end of this yarn.
269
+ """
270
+ loop_id = self._next_loop_id()
271
+ loop = Loop(loop_id, self)
272
+ return self.add_loop_to_end(loop)
273
+
274
+ def _next_loop_id(self) -> int:
275
+ """Get the ID for the next loop to be added to this yarn.
276
+
277
+ Returns:
278
+ int: The ID of the next loop to be added to this yarn based on the knit graph or, if no knit graph is associated with this yarn, based on the last loop on this yarn.
279
+ """
280
+ if self.knit_graph is not None:
281
+ if self.knit_graph.last_loop is None:
282
+ return 0
283
+ else:
284
+ return self.knit_graph.last_loop.loop_id + 1
285
+ elif self.last_loop is None:
286
+ return 0
287
+ else:
288
+ return self.last_loop.loop_id + 1
289
+
290
+ def add_loop_to_end(self, loop: Loop) -> Loop:
291
+ """Add an existing loop to the end of this yarn and associated knit graph.
292
+
293
+ Args:
294
+ loop (Loop): The loop to be added at the end of this yarn.
295
+
296
+ Returns:
297
+ Loop: The loop that was added to the end of the yarn.
298
+ """
299
+ self.insert_loop(loop, self._last_loop)
300
+ if self.knit_graph is not None:
301
+ self.knit_graph.add_loop(loop)
302
+ return loop
303
+
304
+ def insert_loop(self, loop: Loop, prior_loop: Loop | None = None) -> None:
305
+ """Insert a loop into the yarn sequence after the specified prior loop.
306
+
307
+ Args:
308
+ loop (Loop): The loop to be added to this yarn.
309
+ prior_loop (Loop | None): The loop that should come before this loop on the yarn. If None, defaults to the last loop (adding to end of yarn).
310
+ """
311
+ self.loop_graph.add_node(loop)
312
+ if not self.has_loops:
313
+ self._last_loop = loop
314
+ self._first_loop = loop
315
+ return
316
+ elif prior_loop is None:
317
+ prior_loop = self.last_loop
318
+ self.loop_graph.add_edge(prior_loop, loop, Front_Loops=set(), Back_Loops=set())
319
+ if prior_loop == self.last_loop:
320
+ self._last_loop = loop
321
+
322
+ def next_loop(self, loop: Loop) -> Loop | None:
323
+ """Get the loop that follows the specified loop on this yarn.
324
+
325
+ Args:
326
+ loop (Loop): The loop to find the next loop from.
327
+
328
+ Returns:
329
+ Loop | None: The next loop on yarn after the specified loop, or None if it's the last loop.
330
+
331
+ Raises:
332
+ KeyError: If the specified loop is not on this yarn.
333
+ """
334
+ if loop not in self:
335
+ raise KeyError(f"Loop {loop} is not on Yarn")
336
+ successors = [*self.loop_graph.successors(loop)]
337
+ if len(successors) > 0:
338
+ return cast(Loop, successors[0])
339
+ else:
340
+ return None
341
+
342
+ def prior_loop(self, loop: Loop) -> Loop | None:
343
+ """Get the loop that precedes the specified loop on this yarn.
344
+
345
+ Args:
346
+ loop (Loop): The loop to find the prior loop from.
347
+
348
+ Returns:
349
+ Loop | None: The prior loop on yarn before the specified loop, or None if it's the first loop.
350
+
351
+ Raises:
352
+ KeyError: If the specified loop is not on this yarn.
353
+ """
354
+ if loop not in self:
355
+ raise KeyError(f"Loop {loop} is not on Yarn")
356
+ predecessors = [*self.loop_graph.predecessors(loop)]
357
+ if len(predecessors) > 0:
358
+ return cast(Loop, predecessors[0])
359
+ else:
360
+ return None
361
+
362
+ def __contains__(self, item: Loop | int) -> bool:
363
+ """Check if a loop is contained on this yarn.
364
+
365
+ Args:
366
+ item (Loop | int): The loop or loop ID being checked for in the yarn.
367
+
368
+ Returns:
369
+ bool: True if the loop is on the yarn, False otherwise.
370
+ """
371
+ return bool(self.loop_graph.has_node(item))
372
+
373
+ def __iter__(self) -> Iterator[Loop]:
374
+ """Iterate over loops on this yarn in sequence from first to last.
375
+
376
+ Returns:
377
+ Iterator[Loop]: An iterator over the loops on this yarn in their natural sequence order.
378
+ """
379
+ return cast(Iterator[Loop], dfs_preorder_nodes(self.loop_graph, self.first_loop))
380
+
381
+ def edge_iter(self) -> Iterator[tuple[Loop, Loop]]:
382
+ """Iterate over the edges between loops on this yarn.
383
+
384
+ Returns:
385
+ Iterator[tuple[Loop, Loop]]: An iterator over tuples of connected loops representing the yarn path.
386
+ """
387
+ return cast(Iterator[tuple[Loop, Loop]], dfs_edges(self.loop_graph, self.first_loop))
388
+
389
+ def loops_in_front_of_floats(self) -> list[tuple[Loop, Loop, set[Loop]]]:
390
+ """Get all float segments with loops positioned in front of them.
391
+
392
+ Returns:
393
+ 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.
394
+ Only includes floats that have loops in front of them.
395
+ """
396
+ return [(u, v, self.get_loops_in_front_of_float(u, v)) for u, v in self.edge_iter()
397
+ if len(self.get_loops_in_front_of_float(u, v)) > 0]
398
+
399
+ def loops_behind_floats(self) -> list[tuple[Loop, Loop, set[Loop]]]:
400
+ """Get all float segments with loops positioned behind them.
401
+
402
+ Returns:
403
+ 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.
404
+ Only includes floats that have loops behind them.
405
+ """
406
+ return [(u, v, self.get_loops_behind_float(u, v)) for u, v in self.edge_iter()
407
+ if len(self.get_loops_behind_float(u, v)) > 0]
408
+
409
+ def __getitem__(self, item: int) -> Loop:
410
+ """Get a loop by its ID from this yarn.
411
+
412
+ Args:
413
+ item (int): The loop ID to retrieve from this yarn.
414
+
415
+ Returns:
416
+ Loop: The loop on the yarn with the matching ID.
417
+
418
+ Raises:
419
+ KeyError: If the loop ID is not found on this yarn.
420
+ """
421
+ if item not in self:
422
+ raise KeyError(f"{item} is not a loop on yarn {self}")
423
+ else:
424
+ return cast(Loop, self.loop_graph.nodes[item])