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.
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 typing import Any, Iterator, cast
7
+ from __future__ import annotations
9
8
 
10
- from networkx import DiGraph
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
- class Knit_Graph:
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
- self.stitch_graph: DiGraph = DiGraph()
33
- self.braid_graph: Loop_Braid_Graph = Loop_Braid_Graph()
34
- self._last_loop: None | Loop = None
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) -> None | Loop:
41
+ def last_loop(self) -> LoopT | None:
39
42
  """Get the most recently added loop in the graph.
40
43
 
41
44
  Returns:
42
- None | Loop: The last loop added to the graph, or None if no loops have been added.
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 has_loop(self) -> bool:
48
- """Check if the graph contains any loops.
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
- bool: True if the graph has at least one loop, False otherwise.
68
+ Pull_Direction: The pull direction of the stitch-edge between the parent and child.
52
69
  """
53
- return self.last_loop is not None
70
+ return self.get_edge(parent, child)
54
71
 
55
- def add_crossing(self, left_loop: Loop, right_loop: Loop, crossing_direction: Crossing_Direction) -> None:
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: Loop) -> None:
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
- self.stitch_graph.add_node(loop)
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: Loop) -> None:
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 not in self:
88
- raise KeyError(f"Loop {loop} not on the knit graph")
89
- self.braid_graph.remove_loop(loop) # remove any crossing associated with this loop.
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
- assert isinstance(child_loop, Loop)
95
- child_loop.remove_parent(loop)
96
- self.stitch_graph.remove_node(loop)
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
- loop.remove_loop_from_front_floats()
99
- loop.remove_loop_from_back_floats()
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 isinstance(y.last_loop, Loop))
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(self, parent_loop: Loop, child_loop: Loop,
122
- pull_direction: Pull_Direction = Pull_Direction.BtF,
123
- stack_position: int | None = None) -> None:
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
- if parent_loop not in self:
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: Loop) -> set[Wale]:
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
- ancestors = last_loop.ancestor_loops()
154
- return {Wale(l, self) for l in ancestors}
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[Loop, list[Wale]]:
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] = [wale for wale in self.get_wales_ending_with_loop(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 set(Wale_Group(l, self) for l in self.terminal_loops())
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}