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/Yarn.py CHANGED
@@ -8,15 +8,17 @@ from __future__ import annotations
8
8
 
9
9
  from collections.abc import Iterator
10
10
  from dataclasses import dataclass
11
- from typing import TYPE_CHECKING, cast
12
-
13
- from networkx import DiGraph, dfs_edges, dfs_preorder_nodes
11
+ from typing import TYPE_CHECKING, Self, TypeVar, cast, overload
14
12
 
13
+ from knit_graphs.directed_loop_graph import Directed_Loop_Graph, Float_Edge
14
+ from knit_graphs.knit_graph_errors.knit_graph_error import Use_Cut_Yarn_ValueError
15
15
  from knit_graphs.Loop import Loop
16
16
 
17
17
  if TYPE_CHECKING:
18
18
  from knit_graphs.Knit_Graph import Knit_Graph
19
19
 
20
+ LoopT = TypeVar("LoopT", bound=Loop)
21
+
20
22
 
21
23
  @dataclass(frozen=True)
22
24
  class Yarn_Properties:
@@ -26,8 +28,6 @@ class Yarn_Properties:
26
28
  """
27
29
 
28
30
  name: str = "yarn" # name (str): The name or identifier for this yarn type.
29
- plies: int = 2 # plies (int): The number of individual strands twisted together to form the yarn.
30
- weight: float = 28 # weight (float): The weight category or thickness of the yarn.
31
31
  color: str = "green" # color (str): The color of the yarn for visualization purposes.
32
32
 
33
33
  def __str__(self) -> str:
@@ -36,7 +36,7 @@ class Yarn_Properties:
36
36
  Returns:
37
37
  str: String representation in format "name(plies-weight,color)".
38
38
  """
39
- return f"{self.name}({self.plies}-{self.weight},{self.color})"
39
+ return f"{self.name}({self.color})"
40
40
 
41
41
  def __repr__(self) -> str:
42
42
  """Get the name of the yarn for debugging purposes.
@@ -64,13 +64,7 @@ class Yarn_Properties:
64
64
  Returns:
65
65
  bool: True if all properties (name, plies, weight, color) are equal, False otherwise.
66
66
  """
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
- )
67
+ return isinstance(other, Yarn_Properties) and self.name == other.name and self.color == other.color
74
68
 
75
69
  def __hash__(self) -> int:
76
70
  """Get hash value for use in sets and dictionaries.
@@ -78,53 +72,57 @@ class Yarn_Properties:
78
72
  Returns:
79
73
  int: Hash value based on all yarn properties.
80
74
  """
81
- return hash((self.name, self.plies, self.weight, self.color))
75
+ return hash((self.name, self.color))
82
76
 
83
77
 
84
- class Yarn:
78
+ class Yarn(Directed_Loop_Graph[LoopT, Float_Edge[LoopT]]):
85
79
  """A class to represent a yarn structure as a sequence of connected loops.
86
80
 
87
81
  The Yarn class manages a directed graph of loops representing the physical yarn path through a knitted structure.
88
82
  It maintains the sequential order of loops and their floating relationships, providing methods for navigation and manipulation of the yarn structure.
89
83
 
90
84
  Attributes:
91
- loop_graph (DiGraph): The directed graph loops connected by yarn-wise float edges.
92
85
  properties (Yarn_Properties): The physical and visual properties of this yarn.
93
86
  """
94
87
 
95
- FRONT_LOOPS: str = "Front_Loops"
96
- _BACK_LOOPS: str = "Back_Loops"
97
-
98
88
  def __init__(
99
89
  self,
100
- yarn_properties: None | Yarn_Properties = None,
101
- knit_graph: None | Knit_Graph = None,
90
+ yarn_properties: Yarn_Properties | None = None,
91
+ knit_graph: Knit_Graph[LoopT] | None = None,
92
+ instance: int = 0,
93
+ loop_class: type[LoopT] | None = None,
102
94
  ):
103
95
  """Initialize a yarn with the specified properties and optional knit graph association.
104
96
 
105
97
  Args:
106
98
  yarn_properties (None | Yarn_Properties, optional): The properties defining this yarn. If None, uses default properties. Defaults to standard properties.
107
99
  knit_graph (None | Knit_Graph, optional): The knit graph that will own this yarn. Can be None for standalone yarns. Defaults to None.
100
+ instance (int, optional): The instance of this yarn. As new yarns are formed by cuts, the instance will increase. Defaults to 0 (first instance of this yarn).
101
+ loop_class (type[LoopT], optional): The type of loop to create by default in this class. Defaults to Loop class.
108
102
  """
109
- self.loop_graph: DiGraph = DiGraph()
103
+ super().__init__()
104
+ self._instance: int = instance
105
+ self._is_cut: bool = False
106
+ self._loop_class: type[LoopT] = loop_class if loop_class is not None else cast(type[LoopT], Loop)
110
107
  if yarn_properties is None:
111
108
  yarn_properties = Yarn_Properties.default_yarn()
112
109
  self.properties: Yarn_Properties = yarn_properties
113
- self._first_loop: Loop | None = None
114
- self._last_loop: Loop | None = None
115
- self._knit_graph: None | Knit_Graph = knit_graph
110
+ self._first_loop: LoopT | None = None
111
+ self._last_loop: LoopT | None = None
112
+ self._knit_graph: Knit_Graph[LoopT] = knit_graph if knit_graph is not None else Knit_Graph[LoopT]()
113
+ if self not in self.knit_graph.yarns:
114
+ self.knit_graph.add_yarn(self)
116
115
 
117
116
  @property
118
- def knit_graph(self) -> None | Knit_Graph:
119
- """Get the knit graph that owns this yarn.
120
-
117
+ def knit_graph(self) -> Knit_Graph[LoopT]:
118
+ """
121
119
  Returns:
122
- None | Knit_Graph: The knit graph that owns this yarn, or None if not associated with a graph.
120
+ Knit_Graph | Knit_Graph: The knit graph that owns this yarn, or None if not associated with a graph.
123
121
  """
124
122
  return self._knit_graph
125
123
 
126
124
  @knit_graph.setter
127
- def knit_graph(self, knit_graph: Knit_Graph) -> None:
125
+ def knit_graph(self, knit_graph: Knit_Graph[LoopT]) -> None:
128
126
  """Set the knit graph that owns this yarn.
129
127
 
130
128
  Args:
@@ -132,88 +130,8 @@ class Yarn:
132
130
  """
133
131
  self._knit_graph = knit_graph
134
132
 
135
- def has_float(self, u: Loop, v: Loop) -> bool:
136
- """Check if there is a float edge between two loops on this yarn.
137
-
138
- Args:
139
- u (Loop): The first loop to check for float connection.
140
- v (Loop): The second loop to check for float connection.
141
-
142
- Returns:
143
- bool: True if there is a float edge between the loops, False otherwise.
144
- """
145
- return bool(self.loop_graph.has_edge(u, v))
146
-
147
- def add_loop_in_front_of_float(self, front_loop: Loop, u: Loop, v: Loop) -> None:
148
- """Record that a loop falls in front of the float between two other loops.
149
-
150
- Args:
151
- front_loop (Loop): The loop that is positioned in front of the float.
152
- u (Loop): The first loop in the float pair.
153
- v (Loop): The second loop in the float pair.
154
- """
155
- if not self.has_float(u, v):
156
- if self.has_float(v, u):
157
- self.add_loop_in_front_of_float(front_loop, v, u)
158
- else:
159
- return
160
- self.loop_graph.edges[u, v][self.FRONT_LOOPS].add(front_loop)
161
- front_loop.add_loop_in_front_of_float(u, v)
162
-
163
- def add_loop_behind_float(self, back_loop: Loop, u: Loop, v: Loop) -> None:
164
- """Record that a loop falls behind the float between two other loops.
165
-
166
- Args:
167
- back_loop (Loop): The loop that is positioned behind the float.
168
- u (Loop): The first loop in the float pair.
169
- v (Loop): The second loop in the float pair.
170
- """
171
- if not self.has_float(u, v):
172
- if self.has_float(v, u):
173
- self.add_loop_behind_float(back_loop, v, u)
174
- else:
175
- return
176
- self.loop_graph.edges[u, v][self._BACK_LOOPS].add(back_loop)
177
- back_loop.add_loop_behind_float(u, v)
178
-
179
- def get_loops_in_front_of_float(self, u: Loop, v: Loop) -> set[Loop]:
180
- """Get all loops positioned in front of the float between two loops.
181
-
182
- Args:
183
- u (Loop): The first loop in the float pair.
184
- v (Loop): The second loop in the float pair.
185
-
186
- Returns:
187
- set[Loop]: Set of loops positioned in front of the float between u and v, or empty set if no float exists.
188
- """
189
- if not self.has_float(u, v):
190
- if self.has_float(v, u):
191
- return self.get_loops_in_front_of_float(v, u)
192
- else:
193
- return set()
194
- else:
195
- return cast(set[Loop], self.loop_graph.edges[u, v][self.FRONT_LOOPS])
196
-
197
- def get_loops_behind_float(self, u: Loop, v: Loop) -> set[Loop]:
198
- """Get all loops positioned behind the float between two loops.
199
-
200
- Args:
201
- u (Loop): The first loop in the float pair.
202
- v (Loop): The second loop in the float pair.
203
-
204
- Returns:
205
- set[Loop]: Set of loops positioned behind the float between u and v, or empty set if no float exists.
206
- """
207
- if not self.has_float(u, v):
208
- if self.has_float(v, u):
209
- return self.get_loops_behind_float(v, u)
210
- else:
211
- return set()
212
- else:
213
- return cast(set[Loop], self.loop_graph.edges[u, v][self._BACK_LOOPS])
214
-
215
133
  @property
216
- def last_loop(self) -> Loop | None:
134
+ def last_loop(self) -> LoopT | None:
217
135
  """Get the most recently added loop at the end of the yarn.
218
136
 
219
137
  Returns:
@@ -222,7 +140,7 @@ class Yarn:
222
140
  return self._last_loop
223
141
 
224
142
  @property
225
- def first_loop(self) -> Loop | None:
143
+ def first_loop(self) -> LoopT | None:
226
144
  """Get the first loop at the beginning of the yarn.
227
145
 
228
146
  Returns:
@@ -239,6 +157,14 @@ class Yarn:
239
157
  """
240
158
  return self.last_loop is not None
241
159
 
160
+ @property
161
+ def is_cut(self) -> bool:
162
+ """
163
+ Returns:
164
+ bool: True if yarn has been cut and will no longer form loops, False otherwise.
165
+ """
166
+ return self._is_cut
167
+
242
168
  @property
243
169
  def yarn_id(self) -> str:
244
170
  """Get the string identifier for this yarn.
@@ -248,105 +174,194 @@ class Yarn:
248
174
  """
249
175
  return str(self.properties)
250
176
 
251
- def __str__(self) -> str:
252
- """Get the string representation of this yarn.
177
+ @property
178
+ def float_iter(self) -> Iterator[tuple[LoopT, LoopT]]:
179
+ """
180
+ Returns:
181
+ Iterator[tuple[Loop, Loop]]: An iterator over tuples of connected loops representing the yarn path.
182
+ """
183
+ if self.first_loop is None:
184
+ return iter([])
185
+ return self.dfs_edges(self.first_loop)
186
+
187
+ def loops_in_front_of_floats(self) -> Iterator[tuple[LoopT, LoopT, set[LoopT]]]:
188
+ """Get all float segments with loops positioned in front of them.
253
189
 
254
190
  Returns:
255
- str: The yarn identifier string.
191
+ 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.
192
+ Only includes floats that have loops in front of them.
256
193
  """
257
- return self.yarn_id
194
+ return ((u, v, d.front_loops) for u, v, d in self.edge_iter if len(d.front_loops) > 0)
258
195
 
259
- def __repr__(self) -> str:
260
- """Get the representation string of this yarn for debugging.
196
+ def loops_behind_floats(self) -> Iterator[tuple[LoopT, LoopT, set[LoopT]]]:
197
+ """Get all float segments with loops positioned behind them.
261
198
 
262
199
  Returns:
263
- str: The representation of the yarn properties.
200
+ 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.
201
+ Only includes floats that have loops behind them.
264
202
  """
265
- return repr(self.properties)
203
+ return ((u, v, d.back_loops) for u, v, d in self.edge_iter if len(d.back_loops) > 0)
266
204
 
267
- def __hash__(self) -> int:
268
- """Get the hash value of this yarn for use in sets and dictionaries.
205
+ def next_loop(self, loop: LoopT) -> LoopT | None:
206
+ """
207
+ Args:
208
+ loop (LoopT): The loop to find the next loop from.
269
209
 
270
210
  Returns:
271
- int: Hash value based on the yarn properties.
211
+ LoopT | None: The next loop on yarn after the specified loop, or None if it's the last loop.
212
+
213
+ Raises:
214
+ KeyError: If the specified loop is not on this yarn.
272
215
  """
273
- return hash(self.properties)
216
+ if loop not in self:
217
+ raise KeyError(f"Loop {loop} is not on Yarn")
218
+ successors = self.successors(loop)
219
+ return successors.pop() if len(successors) > 0 else None
274
220
 
275
- def __len__(self) -> int:
276
- """Get the number of loops on this yarn.
221
+ def prior_loop(self, loop: LoopT) -> LoopT | None:
222
+ """
223
+ Args:
224
+ loop (Loop): The loop to find the prior loop from.
277
225
 
278
226
  Returns:
279
- int: The total number of loops on this yarn.
227
+ Loop | None: The prior loop on yarn before the specified loop, or None if it's the first loop.
228
+
229
+ Raises:
230
+ KeyError: If the specified loop is not on this yarn.
280
231
  """
281
- return len(self.loop_graph.nodes)
232
+ if loop not in self:
233
+ raise KeyError(f"Loop {loop} is not on Yarn")
234
+ predecessors = self.predecessors(loop)
235
+ if len(predecessors) > 0:
236
+ return predecessors.pop()
237
+ else:
238
+ return None
239
+
240
+ def has_float(self, u: LoopT, v: LoopT) -> bool:
241
+ """Check if there is a float edge between two loops on this yarn.
242
+
243
+ Args:
244
+ u (Loop): The first loop to check for float connection.
245
+ v (Loop): The second loop to check for float connection.
246
+
247
+ Returns:
248
+ bool: True if there is a float edge between the loops, False otherwise.
249
+ """
250
+ return bool(self.has_edge(u, v))
251
+
252
+ def get_loops_in_front_of_float(self, u: LoopT, v: LoopT) -> set[LoopT]:
253
+ """Get all loops positioned in front of the float between two loops.
254
+
255
+ Args:
256
+ u (Loop): The first loop in the float pair.
257
+ v (Loop): The second loop in the float pair.
258
+
259
+ Returns:
260
+ set[Loop]: Set of loops positioned in front of the float between u and v, or empty set if no float exists.
261
+ """
262
+ if not self.has_float(u, v):
263
+ if self.has_float(v, u):
264
+ return self.get_loops_in_front_of_float(v, u)
265
+ else:
266
+ return set()
267
+ else:
268
+ return self.get_edge(u, v).front_loops
269
+
270
+ def get_loops_behind_float(self, u: LoopT, v: LoopT) -> set[LoopT]:
271
+ """Get all loops positioned behind the float between two loops.
282
272
 
283
- def make_loop_on_end(self) -> Loop:
273
+ Args:
274
+ u (Loop): The first loop in the float pair.
275
+ v (Loop): The second loop in the float pair.
276
+
277
+ Returns:
278
+ set[Loop]: Set of loops positioned behind the float between u and v, or empty set if no float exists.
279
+ """
280
+ if not self.has_float(u, v):
281
+ if self.has_float(v, u):
282
+ return self.get_loops_behind_float(v, u)
283
+ else:
284
+ return set()
285
+ else:
286
+ return self.get_edge(u, v).back_loops
287
+
288
+ def make_loop_on_end(self) -> LoopT:
284
289
  """Create and add a new loop at the end of the yarn.
285
290
 
286
291
  Returns:
287
292
  Loop: The newly created loop that was added to the end of this yarn.
288
293
  """
289
294
  loop_id = self._next_loop_id()
290
- loop = Loop(loop_id, self)
291
- return self.add_loop_to_end(loop)
295
+ return self.add_loop_to_end(self._new_loop(loop_id))
292
296
 
293
297
  def _next_loop_id(self) -> int:
294
- """Get the ID for the next loop to be added to this yarn.
295
-
298
+ """
296
299
  Returns:
297
300
  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.
298
301
  """
299
- if self.knit_graph is not None:
300
- if self.knit_graph.last_loop is None:
301
- return 0
302
- else:
303
- return self.knit_graph.last_loop.loop_id + 1
304
- elif self.last_loop is None:
302
+ if self.knit_graph.last_loop is None:
305
303
  return 0
306
304
  else:
307
- return self.last_loop.loop_id + 1
305
+ return self.knit_graph.last_loop.loop_id + 1
306
+
307
+ def _new_loop(self, loop_id: int) -> LoopT:
308
+ """
309
+ Args:
310
+ loop_id (int): The loop id to create a new loop.
311
+
312
+ Returns:
313
+ LoopT: A loop on this yarn with the given id.
314
+
315
+ Raises:
316
+ Use_Cut_Yarn_ValueError: If the yarn has been cut and can no longer form loops.
317
+ """
318
+ if self.is_cut:
319
+ raise Use_Cut_Yarn_ValueError(self)
320
+ elif self.first_loop is not None:
321
+ return self.first_loop.__class__(loop_id, self, self.knit_graph) # type: ignore[arg-type]
322
+ else:
323
+ return self._loop_class(loop_id, self, self.knit_graph) # type: ignore[arg-type]
308
324
 
309
- def remove_loop(self, loop: Loop) -> None:
325
+ def remove_loop(self, loop: LoopT | int) -> None:
310
326
  """
311
327
  Remove the given loop from the yarn.
312
328
  Reconnects any neighboring loops to form a new float with the positioned in-front-of or behind the original floats positioned accordingly.
313
329
  Resets the first_loop and last_loop properties if the removed loop was the tail of the yarn.
314
330
  Args:
315
- loop (Loop): The loop to remove from the yarn.
331
+ loop (LoopT): The loop to remove from the yarn.
316
332
 
317
333
  Raises:
318
334
  KeyError: The given loop does not exist in the yarn.
319
335
  """
320
336
  if loop not in self:
321
337
  raise KeyError(f"Loop {loop} does not exist on yarn {self}.")
338
+ elif isinstance(loop, int):
339
+ loop = self[loop]
322
340
  prior_loop = self.prior_loop(loop)
323
341
  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.
342
+ if prior_loop is not None and next_loop is not None: # Loop is between two floats to be merged.
325
343
  front_of_float_loops = self.get_loops_in_front_of_float(prior_loop, loop)
326
344
  front_of_float_loops.update(self.get_loops_in_front_of_float(loop, next_loop))
327
345
  back_of_float_loops = self.get_loops_behind_float(prior_loop, loop)
328
346
  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(
347
+ super().remove_loop(loop)
348
+ self.add_edge(
331
349
  prior_loop,
332
350
  next_loop,
333
- Front_Loops=front_of_float_loops,
334
- Back_Loops=back_of_float_loops,
351
+ Float_Edge[LoopT](front_loops=front_of_float_loops, back_loops=back_of_float_loops),
335
352
  )
336
353
  for front_loop in front_of_float_loops:
337
354
  front_loop.add_loop_in_front_of_float(prior_loop, next_loop)
338
355
  for back_loop in back_of_float_loops:
339
356
  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
-
349
- def add_loop_to_end(self, loop: Loop) -> Loop:
357
+ else:
358
+ super().remove_loop(loop)
359
+ if next_loop is None: # This was the last loop, make the prior loop the last loop.
360
+ self._last_loop = prior_loop
361
+ if prior_loop is None: # This was the first loop, make the next loop the first loop.
362
+ self._first_loop = next_loop
363
+
364
+ def add_loop_to_end(self, loop: LoopT) -> LoopT:
350
365
  """Add an existing loop to the end of this yarn and associated knit graph.
351
366
 
352
367
  Args:
@@ -360,130 +375,128 @@ class Yarn:
360
375
  self.knit_graph.add_loop(loop)
361
376
  return loop
362
377
 
363
- def insert_loop(self, loop: Loop, prior_loop: Loop | None = None) -> None:
378
+ def insert_loop(self, loop: LoopT, prior_loop: LoopT | None = None) -> None:
364
379
  """Insert a loop into the yarn sequence after the specified prior loop.
365
380
 
366
381
  Args:
367
382
  loop (Loop): The loop to be added to this yarn.
368
383
  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).
369
384
  """
370
- self.loop_graph.add_node(loop)
371
- if not self.has_loops:
385
+ super().add_loop(loop)
386
+ if self.last_loop is None:
372
387
  self._last_loop = loop
373
388
  self._first_loop = loop
374
389
  return
375
- elif prior_loop is None:
390
+ if prior_loop is None:
376
391
  prior_loop = self.last_loop
377
- self.loop_graph.add_edge(prior_loop, loop, Front_Loops=set(), Back_Loops=set())
392
+ super().add_edge(prior_loop, loop, Float_Edge[LoopT]())
378
393
  if prior_loop == self.last_loop:
379
394
  self._last_loop = loop
380
395
 
381
- def next_loop(self, loop: Loop) -> Loop | None:
382
- """Get the loop that follows the specified loop on this yarn.
396
+ def add_loop_in_front_of_float(self, front_loop: LoopT, start_of_float: LoopT) -> None:
397
+ """Record that a loop falls in front of the float between two other loops.
383
398
 
384
399
  Args:
385
- loop (Loop): The loop to find the next loop from.
386
-
387
- Returns:
388
- Loop | None: The next loop on yarn after the specified loop, or None if it's the last loop.
389
-
390
- Raises:
391
- KeyError: If the specified loop is not on this yarn.
400
+ front_loop (Loop): The loop that is positioned in front of the float.
401
+ start_of_float (Loop): The first loop in the float pair.
392
402
  """
393
- if loop not in self:
394
- raise KeyError(f"Loop {loop} is not on Yarn")
395
- successors = [*self.loop_graph.successors(loop)]
396
- if len(successors) > 0:
397
- return cast(Loop, successors[0])
398
- else:
399
- return None
400
-
401
- def prior_loop(self, loop: Loop) -> Loop | None:
402
- """Get the loop that precedes the specified loop on this yarn.
403
+ end_of_float = self.next_loop(start_of_float)
404
+ if end_of_float is not None:
405
+ self.get_edge(start_of_float, end_of_float).add_loop_in_front_of_float(front_loop)
406
+ front_loop.add_loop_in_front_of_float(start_of_float, end_of_float)
403
407
 
408
+ def remove_loop_relative_to_floats(self, loop: LoopT) -> None:
409
+ """
410
+ Removes the given loop from positions relative to floats along this yarn.
404
411
  Args:
405
- loop (Loop): The loop to find the prior loop from.
406
-
407
- Returns:
408
- Loop | None: The prior loop on yarn before the specified loop, or None if it's the first loop.
409
-
410
- Raises:
411
- KeyError: If the specified loop is not on this yarn.
412
+ loop (LoopT): The loop to remove from positions relative to floats on this yarn.
412
413
  """
413
- if loop not in self:
414
- raise KeyError(f"Loop {loop} is not on Yarn")
415
- predecessors = [*self.loop_graph.predecessors(loop)]
416
- if len(predecessors) > 0:
417
- return cast(Loop, predecessors[0])
418
- else:
419
- return None
414
+ for _u, _v, float_data in self.edge_iter:
415
+ float_data.remove_loop_relative_to_floats(loop)
420
416
 
421
- def __contains__(self, item: Loop | int) -> bool:
422
- """Check if a loop is contained on this yarn.
417
+ def add_loop_behind_float(self, back_loop: LoopT, start_of_float: LoopT) -> None:
418
+ """Record that a loop falls in front of the float between two other loops.
423
419
 
424
420
  Args:
425
- item (Loop | int): The loop or loop ID being checked for in the yarn.
421
+ back_loop (Loop): The loop that is positioned behind the float.
422
+ start_of_float (Loop): The first loop in the float pair.
423
+ """
424
+ end_of_float = self.next_loop(start_of_float)
425
+ if end_of_float is not None:
426
+ self.get_edge(start_of_float, end_of_float).add_loop_behind_float(back_loop)
427
+ back_loop.add_loop_behind_float(start_of_float, end_of_float)
428
+
429
+ def cut_yarn(self) -> Self:
430
+ """Cut yarn to make it no longer active and create a new yarn instance of the same type.
426
431
 
427
432
  Returns:
428
- bool: True if the loop is on the yarn, False otherwise.
433
+ Yarn[LoopT]: New yarn of the same type after cutting this yarn.
429
434
  """
430
- return bool(self.loop_graph.has_node(item))
435
+ self._is_cut = True
436
+ return self.__class__(self.properties, self.knit_graph, self._instance + 1, self._loop_class)
431
437
 
432
- def __iter__(self) -> Iterator[Loop]:
433
- """Iterate over loops on this yarn in sequence from first to last.
438
+ def __str__(self) -> str:
439
+ """Get the string representation of this yarn.
434
440
 
435
441
  Returns:
436
- Iterator[Loop]: An iterator over the loops on this yarn in their natural sequence order.
442
+ str: The yarn identifier string.
437
443
  """
438
- return cast(Iterator[Loop], dfs_preorder_nodes(self.loop_graph, self.first_loop))
444
+ return self.yarn_id
439
445
 
440
- def edge_iter(self) -> Iterator[tuple[Loop, Loop]]:
441
- """Iterate over the edges between loops on this yarn.
446
+ def __repr__(self) -> str:
447
+ """Get the representation string of this yarn for debugging.
442
448
 
443
449
  Returns:
444
- Iterator[tuple[Loop, Loop]]: An iterator over tuples of connected loops representing the yarn path.
450
+ str: The representation of the yarn properties.
445
451
  """
446
- return cast(Iterator[tuple[Loop, Loop]], dfs_edges(self.loop_graph, self.first_loop))
452
+ return repr(self.properties)
447
453
 
448
- def loops_in_front_of_floats(self) -> list[tuple[Loop, Loop, set[Loop]]]:
449
- """Get all float segments with loops positioned in front of them.
454
+ def __hash__(self) -> int:
455
+ """Get the hash value of this yarn for use in sets and dictionaries.
450
456
 
451
457
  Returns:
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.
453
- Only includes floats that have loops in front of them.
458
+ int: Hash value based on the yarn properties.
454
459
  """
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
- ]
460
+ return hash((self._instance, self.properties))
460
461
 
461
- def loops_behind_floats(self) -> list[tuple[Loop, Loop, set[Loop]]]:
462
- """Get all float segments with loops positioned behind them.
462
+ def __iter__(self) -> Iterator[LoopT]:
463
+ """Iterate over loops on this yarn in sequence from first to last.
463
464
 
464
465
  Returns:
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.
466
- Only includes floats that have loops behind them.
466
+ Iterator[Loop]: An iterator over the loops on this yarn in their natural sequence order.
467
467
  """
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
- ]
468
+ if self.first_loop is None:
469
+ return iter([])
470
+ return self.dfs_preorder_loops(self.first_loop)
471
+
472
+ @overload
473
+ def __getitem__(self, item: int) -> LoopT: ...
474
+
475
+ @overload
476
+ def __getitem__(self, item: tuple[LoopT | int, LoopT | int]) -> Float_Edge[LoopT]: ...
477
+
478
+ @overload
479
+ def __getitem__(self, item: slice) -> list[LoopT]: ...
473
480
 
474
- def __getitem__(self, item: int) -> Loop:
481
+ def __getitem__(
482
+ self, item: int | tuple[LoopT | int, LoopT | int] | slice
483
+ ) -> LoopT | Float_Edge[LoopT] | list[LoopT]:
475
484
  """Get a loop by its ID from this yarn.
476
485
 
477
486
  Args:
478
- item (int): The loop ID to retrieve from this yarn.
487
+ item (int | tuple[LoopT | int, LoopT | int] | slice):
488
+ The loop ,loop ID, or float between two loops to retrieve from this yarn.
489
+ If given a slice, it will retrieve the elements between the specified indices in the standard ordering of loops along the yarn.
479
490
 
480
491
  Returns:
481
- Loop: The loop on the yarn with the matching ID.
492
+ LoopT: The loop on the yarn with the matching ID.
493
+ Float_Edge[LoopT]: The float data for the given pair of loops forming a float.
494
+ list[LoopT]: The loops in the slice of the yarn based on their ordering along the yarn.
482
495
 
483
496
  Raises:
484
- KeyError: If the loop ID is not found on this yarn.
497
+ KeyError: If the item is not found on this yarn.
485
498
  """
486
- if item not in self:
487
- raise KeyError(f"{item} is not a loop on yarn {self}")
499
+ if isinstance(item, slice):
500
+ return list(self)[item]
488
501
  else:
489
- return cast(Loop, self.loop_graph.nodes[item])
502
+ return super().__getitem__(item)