knit-graphs 0.0.6__py3-none-any.whl → 0.0.7__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 (33) 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.artin_wale_braids.rst +58 -0
  6. docs/source/api/knit_graphs.rst +74 -0
  7. docs/source/conf.py +335 -0
  8. docs/source/index.rst +71 -0
  9. docs/source/installation.rst +67 -0
  10. knit_graphs/Course.py +156 -104
  11. knit_graphs/Knit_Graph.py +249 -186
  12. knit_graphs/Knit_Graph_Visualizer.py +680 -0
  13. knit_graphs/Loop.py +141 -155
  14. knit_graphs/Pull_Direction.py +68 -23
  15. knit_graphs/Yarn.py +424 -267
  16. knit_graphs/__init__.py +3 -3
  17. knit_graphs/_base_classes.py +173 -0
  18. knit_graphs/artin_wale_braids/Crossing_Direction.py +74 -15
  19. knit_graphs/artin_wale_braids/Loop_Braid_Graph.py +95 -62
  20. knit_graphs/artin_wale_braids/Wale.py +169 -93
  21. knit_graphs/artin_wale_braids/Wale_Braid.py +50 -30
  22. knit_graphs/artin_wale_braids/Wale_Braid_Word.py +99 -54
  23. knit_graphs/artin_wale_braids/Wale_Group.py +136 -88
  24. knit_graphs/{knit_graph_generators/basic_knit_graph_generators.py → basic_knit_graph_generators.py} +302 -248
  25. knit_graphs-0.0.7.dist-info/LICENSE +21 -0
  26. {knit_graphs-0.0.6.dist-info → knit_graphs-0.0.7.dist-info}/METADATA +33 -24
  27. knit_graphs-0.0.7.dist-info/RECORD +29 -0
  28. {knit_graphs-0.0.6.dist-info → knit_graphs-0.0.7.dist-info}/WHEEL +1 -1
  29. knit_graphs/__about__.py +0 -4
  30. knit_graphs/knit_graph_generators/__init__.py +0 -0
  31. knit_graphs/knit_graph_visualizer/Stitch_Visualizer.py +0 -427
  32. knit_graphs/knit_graph_visualizer/__init__.py +0 -0
  33. knit_graphs-0.0.6.dist-info/RECORD +0 -22
knit_graphs/Knit_Graph.py CHANGED
@@ -1,186 +1,249 @@
1
- """The graph structure used to represent knitted objects"""
2
- import networkx
3
-
4
- from knit_graphs.Course import Course
5
- from knit_graphs.Loop import Loop
6
- from knit_graphs.Pull_Direction import Pull_Direction
7
- from knit_graphs.Yarn import Yarn
8
- from knit_graphs.artin_wale_braids.Crossing_Direction import Crossing_Direction
9
- from knit_graphs.artin_wale_braids.Loop_Braid_Graph import Loop_Braid_Graph
10
- from knit_graphs.artin_wale_braids.Wale import Wale
11
- from knit_graphs.artin_wale_braids.Wale_Group import Wale_Group
12
-
13
-
14
- class Knit_Graph:
15
- """
16
- A representation of knitted structures as connections between loops on yarns
17
- """
18
-
19
- def __init__(self):
20
- self.stitch_graph: networkx.DiGraph = networkx.DiGraph()
21
- self.braid_graph: Loop_Braid_Graph = Loop_Braid_Graph()
22
- self._last_loop: None | Loop = None
23
- self.yarns: dict[Yarn, Yarn] = {}
24
-
25
- @property
26
- def last_loop(self) -> None | Loop:
27
- """
28
- :return: Last loop added to graph
29
- """
30
- return self._last_loop
31
-
32
- @property
33
- def has_loop(self) -> bool:
34
- """
35
- :return: True if graph has loops
36
- """
37
- return self._last_loop is not None
38
-
39
- def add_crossing(self, left_loop: Loop, right_loop: Loop, crossing_direction: Crossing_Direction):
40
- """
41
- Adds edge between loops with attribute of the crossing direction.
42
- :param left_loop: Loop on the left side of the crossing.
43
- :param right_loop: Loop on the right side of the crossing.
44
- :param crossing_direction: The crossing direction is (over, under, none) between loops.
45
- """
46
- self.braid_graph.add_crossing(left_loop, right_loop, crossing_direction)
47
-
48
- def add_loop(self, loop: Loop):
49
- """
50
- Adds a loop to the graph
51
- :param loop: the loop to be added in as a node in the graph
52
- """
53
- self.stitch_graph.add_node(loop)
54
- if loop.yarn not in self.yarns:
55
- self.add_yarn(loop.yarn)
56
- if self._last_loop is None or loop > self._last_loop:
57
- self._last_loop = loop
58
-
59
- def add_yarn(self, yarn: Yarn):
60
- """
61
- Adds a yarn to the graph. Assumes that loops do not need to be added
62
- :param yarn: the yarn to be added to the graph structure
63
- """
64
- self.yarns[yarn] = yarn
65
-
66
- def connect_loops(self, parent_loop: Loop, child_loop: Loop,
67
- pull_direction: Pull_Direction = Pull_Direction.BtF,
68
- stack_position: int | None = None):
69
- """
70
- Creates a stitch-edge by connecting a parent and child loop
71
- :param parent_loop: the id of the parent loop to connect to this child
72
- :param child_loop: the id of the child loop to connect to the parent
73
- :param pull_direction: the direction the child is pulled through the parent
74
- :param stack_position: The position to insert the parent into, by default add on top of the stack
75
- """
76
- if parent_loop not in self:
77
- raise KeyError(f"parent loop {parent_loop} not in Knit Graph")
78
- if child_loop not in self:
79
- raise KeyError(f"child loop {parent_loop} not in Knit Graph")
80
- self.stitch_graph.add_edge(parent_loop, child_loop, pull_direction=pull_direction)
81
- child_loop.add_parent_loop(parent_loop, stack_position)
82
-
83
- def get_wale_starting_with_loop(self, first_loop: Loop) -> Wale:
84
- """
85
- :param first_loop:
86
- :return: A wale starting from given loop in knit graph.
87
- """
88
- wale = Wale(first_loop)
89
- cur_loop = first_loop
90
- while len(self.stitch_graph.successors(cur_loop)) == 1:
91
- cur_loop = [*self.stitch_graph.successors(cur_loop)][0]
92
- wale.add_loop_to_end(cur_loop, self.stitch_graph.edges[wale.last_loop, cur_loop]['pull_direction'])
93
- return wale
94
-
95
- def get_wales_ending_with_loop(self, last_loop: Loop) -> list[Wale]:
96
- """
97
- :param last_loop: last loop of joined set of wales
98
- :return: the set of wales that end at this loop, only multiple wales if this is a child of a decrease.
99
- """
100
- wales = []
101
- for top_stitch_parent in self.stitch_graph.predecessors(last_loop):
102
- wale = Wale(last_loop)
103
- wale.add_loop_to_beginning(top_stitch_parent, self.stitch_graph.edges[top_stitch_parent, last_loop]['pull_direction'])
104
- cur_loop = top_stitch_parent
105
- while len(cur_loop.parent_loops) == 1: # stop at split for decrease or start of wale
106
- cur_loop = [*self.stitch_graph.predecessors(cur_loop)][0]
107
- wale.add_loop_to_beginning(cur_loop, self.stitch_graph.edges[cur_loop, wale.first_loop]['pull_direction'])
108
- wales.append(wale)
109
- return wales
110
-
111
- def get_courses(self) -> list[Course]:
112
- """
113
- :return: A list of courses in their order formed in the knit graph.
114
- The first set of loops in the graph is on course 0.
115
- A course change occurs when a loop has a parent loop in the last course.
116
- """
117
- courses = []
118
- course = Course()
119
- for loop in sorted([*self.stitch_graph.nodes]):
120
- for parent in self.stitch_graph.predecessors(loop):
121
- if parent in course: # start a new course
122
- courses.append(course)
123
- course = Course()
124
- break
125
- course.add_loop(loop)
126
- courses.append(course)
127
- return courses
128
-
129
- def get_wale_groups(self) -> dict[Loop, Wale_Group]:
130
- """
131
- :return: Dictionary of terminal loops to the wale group they terminate
132
- """
133
- wale_groups = {}
134
- for loop in self.stitch_graph.nodes:
135
- if self.is_terminal_loop(loop):
136
- wale_groups.update({loop: Wale_Group(wale, self) for wale in self.get_wales_ending_with_loop(loop)})
137
- return wale_groups
138
-
139
- def __contains__(self, item: Loop):
140
- """
141
- Returns true if the item is in the graph
142
- :param item: the loop being checked for in the graph
143
- :return: true if the loop_id of item or the loop is in the graph
144
- """
145
- return self.stitch_graph.has_node(item)
146
-
147
- def get_stitch_edge(self, parent: Loop, child: Loop, stitch_property: str | None = None):
148
- """
149
- Shortcut to get stitch-edge data from loops or ids
150
- :param stitch_property: property of edge to return
151
- :param parent: parent loop or id of parent loop
152
- :param child: child loop or id of child loop
153
- :return: the edge data for this stitch edge
154
- """
155
- if self.stitch_graph.has_edge(parent, child):
156
- edge = self.stitch_graph.get_edge_data(parent, child)
157
- if stitch_property is not None:
158
- return edge[stitch_property]
159
- else:
160
- return edge
161
- else:
162
- return None
163
-
164
- def get_child_loop(self, loop: Loop) -> Loop | None:
165
- """
166
- :param loop: loop_id to look for child from.
167
- :return: child loop_id or None if no child loop
168
- """
169
- successors = [*self.stitch_graph.successors(loop)]
170
- if len(successors) == 0:
171
- return None
172
- return successors[0]
173
-
174
- def has_child_loop(self, loop: Loop) -> bool:
175
- """
176
- :param loop:
177
- :return: True if loop has a child loop
178
- """
179
- return self.get_child_loop(loop) is not None
180
-
181
- def is_terminal_loop(self, loop: Loop) -> bool:
182
- """
183
- :param loop:
184
- :return: True if loop has no child
185
- """
186
- return not self.has_child_loop(loop)
1
+ """The graph structure used to represent knitted objects.
2
+
3
+ This module contains the main Knit_Graph class which serves as the central data structure for representing knitted fabrics.
4
+ It manages the relationships between loops, yarns, and structural elements like courses and wales.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ from typing import Any, cast
9
+
10
+ from knit_graphs._base_classes import _Base_Knit_Graph
11
+ from knit_graphs.artin_wale_braids.Crossing_Direction import Crossing_Direction
12
+ from knit_graphs.artin_wale_braids.Loop_Braid_Graph import Loop_Braid_Graph
13
+ from knit_graphs.artin_wale_braids.Wale import Wale
14
+ from knit_graphs.artin_wale_braids.Wale_Group import Wale_Group
15
+ from knit_graphs.Course import Course
16
+ from knit_graphs.Loop import Loop
17
+ from knit_graphs.Pull_Direction import Pull_Direction
18
+ from knit_graphs.Yarn import Yarn
19
+
20
+
21
+ class Knit_Graph(_Base_Knit_Graph):
22
+ """A representation of knitted structures as connections between loops on yarns.
23
+
24
+ The Knit_Graph class is the main data structure for representing knitted fabrics.
25
+ It maintains a directed graph of loops connected by stitch edges, manages yarn relationships,
26
+ and provides methods for analyzing the structure of the knitted fabric including courses, wales, and cable crossings.
27
+ """
28
+
29
+ def __init__(self) -> None:
30
+ """Initialize an empty knit graph with no loops or yarns."""
31
+ super().__init__()
32
+ self.braid_graph: Loop_Braid_Graph = Loop_Braid_Graph()
33
+ self._last_loop: None | Loop = None
34
+ self.yarns: set[Yarn] = set()
35
+
36
+ @property
37
+ def last_loop(self) -> None | Loop:
38
+ """Get the most recently added loop in the graph.
39
+
40
+ Returns:
41
+ None | Loop: The last loop added to the graph, or None if no loops have been added.
42
+ """
43
+ return self._last_loop
44
+
45
+ @property
46
+ def has_loop(self) -> bool:
47
+ """Check if the graph contains any loops.
48
+
49
+ Returns:
50
+ bool: True if the graph has at least one loop, False otherwise.
51
+ """
52
+ return self.last_loop is not None
53
+
54
+ def add_crossing(self, left_loop: Loop, right_loop: Loop, crossing_direction: Crossing_Direction) -> None:
55
+ """Add a cable crossing between two loops with the specified crossing direction.
56
+
57
+ Args:
58
+ left_loop (Loop): The loop on the left side of the crossing.
59
+ right_loop (Loop): The loop on the right side of the crossing.
60
+ crossing_direction (Crossing_Direction): The direction of the crossing (over, under, or none) between the loops.
61
+ """
62
+ self.braid_graph.add_crossing(left_loop, right_loop, crossing_direction)
63
+
64
+ def add_loop(self, loop: Loop) -> None:
65
+ """Add a loop to the knit graph as a node.
66
+
67
+ Args:
68
+ 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.
69
+ """
70
+ self.stitch_graph.add_node(loop)
71
+ if loop.yarn not in self.yarns:
72
+ self.add_yarn(cast(Yarn, loop.yarn))
73
+ if self._last_loop is None or loop > self._last_loop:
74
+ self._last_loop = loop
75
+
76
+ def add_yarn(self, yarn: Yarn) -> None:
77
+ """Add a yarn to the graph without adding its loops.
78
+
79
+ Args:
80
+ yarn (Yarn): The yarn to be added to the graph structure. This method assumes that loops do not need to be added separately.
81
+ """
82
+ self.yarns.add(yarn)
83
+
84
+ def connect_loops(self, parent_loop: Loop, child_loop: Loop,
85
+ pull_direction: Pull_Direction = Pull_Direction.BtF,
86
+ stack_position: int | None = None) -> None:
87
+ """Create a stitch edge by connecting a parent and child loop.
88
+
89
+ Args:
90
+ parent_loop (Loop): The parent loop to connect to the child loop.
91
+ child_loop (Loop): The child loop to connect to the parent loop.
92
+ pull_direction (Pull_Direction): The direction the child is pulled through the parent. Defaults to Pull_Direction.BtF (knit stitch).
93
+ stack_position (int | None, optional): The position to insert the parent into the child's parent stack. If None, adds on top of the stack. Defaults to None.
94
+
95
+ Raises:
96
+ KeyError: If either the parent_loop or child_loop is not already in the knit graph.
97
+ """
98
+ if parent_loop not in self:
99
+ raise KeyError(f"parent loop {parent_loop} not in Knit Graph")
100
+ if child_loop not in self:
101
+ raise KeyError(f"child loop {parent_loop} not in Knit Graph")
102
+ self.stitch_graph.add_edge(parent_loop, child_loop, pull_direction=pull_direction)
103
+ child_loop.add_parent_loop(parent_loop, stack_position)
104
+
105
+ def get_wale_starting_with_loop(self, first_loop: Loop) -> Wale:
106
+ """Get a wale (vertical column of stitches) starting from the specified loop.
107
+
108
+ Args:
109
+ first_loop (Loop): The loop at the start of the wale to be constructed.
110
+
111
+ Returns:
112
+ Wale: A wale object representing the vertical column of stitches starting from the given loop.
113
+ """
114
+ wale = Wale(first_loop)
115
+ cur_loop = first_loop
116
+ while len(self.stitch_graph.successors(cur_loop)) == 1:
117
+ cur_loop = [*self.stitch_graph.successors(cur_loop)][0]
118
+ assert isinstance(wale.last_loop, Loop)
119
+ wale.add_loop_to_end(cur_loop, self.get_pull_direction(wale.last_loop, cur_loop))
120
+ return wale
121
+
122
+ def get_wales_ending_with_loop(self, last_loop: Loop) -> list[Wale]:
123
+ """Get all wales (vertical columns of stitches) that end at the specified loop.
124
+
125
+ Args:
126
+ last_loop (Loop): The last loop of the joined set of wales.
127
+
128
+ Returns:
129
+ list[Wale]: The set of wales that end at this loop. Only returns multiple wales if this loop is a child of a decrease stitch.
130
+ """
131
+ wales = []
132
+ for top_stitch_parent in self.stitch_graph.predecessors(last_loop):
133
+ wale = Wale(last_loop)
134
+ wale.add_loop_to_beginning(top_stitch_parent, cast(Pull_Direction, self.get_pull_direction(top_stitch_parent, last_loop)))
135
+ cur_loop = top_stitch_parent
136
+ while len(cur_loop.parent_loops) == 1: # stop at split for decrease or start of wale
137
+ cur_loop = [*self.stitch_graph.predecessors(cur_loop)][0]
138
+ wale.add_loop_to_beginning(cur_loop, cast(Pull_Direction, self.get_pull_direction(cur_loop, cast(Loop, wale.first_loop))))
139
+ wales.append(wale)
140
+ return wales
141
+
142
+ def get_courses(self) -> list[Course]:
143
+ """Get all courses (horizontal rows) in the knit graph in chronological order.
144
+
145
+ Returns:
146
+ list[Course]: A list of courses representing horizontal rows of loops.
147
+ The first course contains the initial set of loops. A course change occurs when a loop has a parent loop in the previous course.
148
+ """
149
+ courses = []
150
+ course = Course(self)
151
+ for loop in sorted([*self.stitch_graph.nodes]):
152
+ for parent in self.stitch_graph.predecessors(loop):
153
+ if parent in course: # start a new course
154
+ courses.append(course)
155
+ course = Course(self)
156
+ break
157
+ course.add_loop(loop)
158
+ courses.append(course)
159
+ return courses
160
+
161
+ def get_wale_groups(self) -> dict[Loop, Wale_Group]:
162
+ """Get wale groups organized by their terminal loops.
163
+
164
+ Returns:
165
+ dict[Loop, Wale_Group]: Dictionary mapping terminal loops to the wale groups they terminate. Each wale group represents a collection of wales that end at the same terminal loop.
166
+ """
167
+ wale_groups = {}
168
+ for loop in self.stitch_graph.nodes:
169
+ if self.is_terminal_loop(loop):
170
+ wale_groups.update({loop: Wale_Group(wale, self) for wale in self.get_wales_ending_with_loop(loop)})
171
+ return wale_groups
172
+
173
+ def __contains__(self, item: Loop) -> bool:
174
+ """Check if a loop is contained in the knit graph.
175
+
176
+ Args:
177
+ item (Loop): The loop being checked for in the graph.
178
+
179
+ Returns:
180
+ bool: True if the loop is in the graph, False otherwise.
181
+ """
182
+ return bool(self.stitch_graph.has_node(item))
183
+
184
+ def get_pull_direction(self, parent: Loop, child: Loop) -> Pull_Direction | None:
185
+ """Get the pull direction of the stitch edge between parent and child loops.
186
+
187
+ Args:
188
+ parent (Loop): The parent loop of the stitch edge.
189
+ child (Loop): The child loop of the stitch edge.
190
+
191
+ Returns:
192
+ 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.
193
+ """
194
+ edge = self.get_stitch_edge(parent, child)
195
+ if edge is None:
196
+ return None
197
+ else:
198
+ return cast(Pull_Direction, edge['pull_direction'])
199
+
200
+ def get_stitch_edge(self, parent: Loop, child: Loop) -> dict[str, Any] | None:
201
+ """Get the stitch edge data between two loops.
202
+
203
+ Args:
204
+ parent (Loop): The parent loop of the stitch edge.
205
+ child (Loop): The child loop of the stitch edge.
206
+
207
+ Returns:
208
+ dict[str, Any] | None: The edge data dictionary for this stitch edge, or None if no edge exists between these loops.
209
+ """
210
+ if self.stitch_graph.has_edge(parent, child):
211
+ return cast(dict[str, Any], self.stitch_graph.get_edge_data(parent, child))
212
+ else:
213
+ return None
214
+
215
+ def get_child_loop(self, loop: Loop) -> Loop | None:
216
+ """Get the child loop of the specified parent loop.
217
+
218
+ Args:
219
+ loop (Loop): The loop to look for a child loop from.
220
+
221
+ Returns:
222
+ Loop | None: The child loop if one exists, or None if no child loop is found.
223
+ """
224
+ successors = [*self.stitch_graph.successors(loop)]
225
+ if len(successors) == 0:
226
+ return None
227
+ return cast(Loop, successors[0])
228
+
229
+ def has_child_loop(self, loop: Loop) -> bool:
230
+ """Check if a loop has a child loop connected to it.
231
+
232
+ Args:
233
+ loop (Loop): The loop to check for child connections.
234
+
235
+ Returns:
236
+ bool: True if the loop has a child loop, False otherwise.
237
+ """
238
+ return self.get_child_loop(loop) is not None
239
+
240
+ def is_terminal_loop(self, loop: Loop) -> bool:
241
+ """Check if a loop is terminal (has no child loops and terminates a wale).
242
+
243
+ Args:
244
+ loop (Loop): The loop to check for terminal status.
245
+
246
+ Returns:
247
+ bool: True if the loop has no child loops and terminates a wale, False otherwise.
248
+ """
249
+ return not self.has_child_loop(loop)