knit-graphs 0.0.1__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 ADDED
@@ -0,0 +1,104 @@
1
+ """Course representation of a section of knitting with no parent loops."""
2
+ from knit_graphs.Loop import Loop
3
+
4
+
5
+ class Course:
6
+ """
7
+ Course object for organizing loops into knitting rows
8
+ """
9
+
10
+ def __init__(self):
11
+ self.loops_in_order: list[Loop] = []
12
+ self._loop_set: dict[Loop, Loop] = {}
13
+
14
+ def add_loop(self, loop: Loop, index: int | None = None):
15
+ """
16
+ Add the loop at the given index or to the end of the course
17
+ :param loop: loop to add
18
+ :param index: index to insert at or None if adding to end
19
+ """
20
+ for parent_loop in loop.parent_loops:
21
+ assert parent_loop not in self, f"{loop} has parent {parent_loop}, cannot be added to same course"
22
+ self._loop_set[loop] = loop
23
+ if index is None:
24
+ self.loops_in_order.append(loop)
25
+ else:
26
+ self.loops_in_order.insert(index, loop)
27
+
28
+ def has_increase(self) -> bool:
29
+ """
30
+ :return: True if course has at least one yarn over to start new wales.
31
+ """
32
+ for loop in self:
33
+ if not loop.has_parent_loops(): # Yarn over
34
+ return True
35
+ return False
36
+
37
+ def has_decrease(self) -> bool:
38
+ """
39
+ :return: True if course has at least one decrease, merging two wales
40
+ """
41
+ for loop in self:
42
+ if len(loop.parent_loops) > 1:
43
+ return True
44
+ return False
45
+
46
+ def has_terminal_loop(self, knit_graph) -> bool:
47
+ """\
48
+ :param knit_graph: Knit graph to get child loop data from
49
+ :return: True if this course contains a terminal loop with no child
50
+ """
51
+ for loop in self:
52
+ if knit_graph.get_child_loop(loop) is None:
53
+ return True
54
+ return False
55
+
56
+ def __getitem__(self, index: int | slice) -> Loop | list[Loop]:
57
+ return self.loops_in_order[index]
58
+
59
+ def index(self, loop: Loop) -> int:
60
+ """
61
+ Searches for index of given loop_id
62
+ :param loop: loop_id or loop to find
63
+ :return: index of the loop_id
64
+ """
65
+ return self.loops_in_order.index(loop)
66
+
67
+ def in_round_with(self, next_course) -> bool:
68
+ """
69
+ :param next_course: another course who should follow this course
70
+ :return: True if the next course starts at the beginning of this course
71
+ """
72
+ next_start = next_course[0]
73
+ i = 1
74
+ while not next_start.has_parent_loops():
75
+ next_start = next_course[i]
76
+ i += 1
77
+ return self[0] in next_start.parent_loops
78
+
79
+ def in_row_with(self, next_course) -> bool:
80
+ """
81
+ :param next_course: another course that should follow this course.
82
+ :return: True if the next course starts at the end of this course.
83
+ """
84
+ next_start = next_course[0]
85
+ i = 1
86
+ while not next_start.has_parent_loops():
87
+ next_start = next_course[i]
88
+ i += 1
89
+ return self[-1] in next_start.parent_loops
90
+
91
+ def __contains__(self, loop: Loop) -> bool:
92
+ return loop in self._loop_set
93
+
94
+ def __iter__(self):
95
+ return self.loops_in_order.__iter__()
96
+
97
+ def __len__(self):
98
+ return len(self.loops_in_order)
99
+
100
+ def __str__(self):
101
+ return str(self.loops_in_order)
102
+
103
+ def __repr__(self):
104
+ return str(self)
@@ -0,0 +1,187 @@
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 dictionary of loop_ids to the course they are on,
114
+ a dictionary or course ids to the loops on that course in the order of creation.
115
+ The first set of loops in the graph is on course 0.
116
+ A course change occurs when a loop has a parent loop in the last course.
117
+ """
118
+ courses = []
119
+ course = Course()
120
+ for loop in sorted([*self.stitch_graph.nodes]):
121
+ for parent in self.stitch_graph.predecessors(loop):
122
+ if parent in course: # start a new course
123
+ courses.append(course)
124
+ course = Course()
125
+ break
126
+ course.add_loop(loop)
127
+ courses.append(course)
128
+ return courses
129
+
130
+ def get_wale_groups(self) -> dict[Loop, Wale_Group]:
131
+ """
132
+ :return: Dictionary of terminal loops to the wale group they terminate
133
+ """
134
+ wale_groups = {}
135
+ for loop in self.stitch_graph.nodes:
136
+ if self.is_terminal_loop(loop):
137
+ wale_groups.update({loop: Wale_Group(wale, self) for wale in self.get_wales_ending_with_loop(loop)})
138
+ return wale_groups
139
+
140
+ def __contains__(self, item: Loop):
141
+ """
142
+ Returns true if the item is in the graph
143
+ :param item: the loop being checked for in the graph
144
+ :return: true if the loop_id of item or the loop is in the graph
145
+ """
146
+ return self.stitch_graph.has_node(item)
147
+
148
+ def get_stitch_edge(self, parent: Loop, child: Loop, stitch_property: str | None = None):
149
+ """
150
+ Shortcut to get stitch-edge data from loops or ids
151
+ :param stitch_property: property of edge to return
152
+ :param parent: parent loop or id of parent loop
153
+ :param child: child loop or id of child loop
154
+ :return: the edge data for this stitch edge
155
+ """
156
+ if self.stitch_graph.has_edge(parent, child):
157
+ edge = self.stitch_graph.get_edge_data(parent, child)
158
+ if stitch_property is not None:
159
+ return edge[stitch_property]
160
+ else:
161
+ return edge
162
+ else:
163
+ return None
164
+
165
+ def get_child_loop(self, loop: Loop) -> Loop | None:
166
+ """
167
+ :param loop: loop_id to look for child from.
168
+ :return: child loop_id or None if no child loop
169
+ """
170
+ successors = [*self.stitch_graph.successors(loop)]
171
+ if len(successors) == 0:
172
+ return None
173
+ return successors[0]
174
+
175
+ def has_child_loop(self, loop: Loop) -> bool:
176
+ """
177
+ :param loop:
178
+ :return: True if loop has a child loop
179
+ """
180
+ return self.get_child_loop(loop) is not None
181
+
182
+ def is_terminal_loop(self, loop: Loop) -> bool:
183
+ """
184
+ :param loop:
185
+ :return: True if loop has no child
186
+ """
187
+ return not self.has_child_loop(loop)
knit_graphs/Loop.py ADDED
@@ -0,0 +1,155 @@
1
+ """Module containing the Loop Class"""
2
+
3
+
4
+ class Loop:
5
+ """
6
+ A class to represent a single loop structure for modeling a single loop in a knitting pattern.
7
+
8
+ Attributes:
9
+ -----------
10
+ _loop_id : int
11
+ a unique identifier for the loop
12
+ yarn : Yarn
13
+ the Yarn variable that creates and holds this loop
14
+ parent_loops : list
15
+ the list of parent loops
16
+ front_floats : dict
17
+ a dictionary of loops in front of the float
18
+ back_floats : dict
19
+ a dictionary of loops behind the float
20
+
21
+ Methods:
22
+ -----------
23
+ add_loop_in_front_of_float(u, v)
24
+ Set this loop to be in front of the float between u and v
25
+ add_loop_behind_float(u, v)
26
+ Set this loop to be behind the float between u and v
27
+ is_in_front_of_float(u, v)
28
+ Check if the float between u and v is in front of this loop
29
+ is_behind_float(u, v)
30
+ Check if the float between u and v is behind this loop
31
+ prior_loop_on_yarn()
32
+ Return the prior loop on yarn or None if first loop on yarn
33
+ next_loop_on_yarn()
34
+ Return the next loop on yarn or None if last loop on yarn
35
+ has_parent_loops()
36
+ Check if loop has stitch-edge parents
37
+ add_parent_loop(parent, stack_position)
38
+ Add the parent Loop onto the stack of parent_loops
39
+ """
40
+
41
+ def __init__(self, loop_id: int, yarn):
42
+ """
43
+ Constructs the Loop object.
44
+
45
+ Parameters:
46
+ -----------
47
+ loop_id : int
48
+ a unique identifier for the loop, must be non-negative
49
+ yarn : Yarn
50
+ the Yarn variable that creates and holds this loop
51
+ """
52
+ assert loop_id >= 0, f"{loop_id}: Loop_id must be non-negative"
53
+ self._loop_id: int = loop_id
54
+ self.yarn = yarn
55
+ self.parent_loops: list[Loop] = []
56
+ self.front_floats: dict[Loop, set[Loop]] = {}
57
+ self.back_floats: dict[Loop, set[Loop]] = {}
58
+
59
+ def add_loop_in_front_of_float(self, u, v):
60
+ """
61
+ Set this loop to be in front of the float between u and v
62
+ :param u: First loop in float
63
+ :param v: Second loop in float
64
+ """
65
+ if u not in self.back_floats:
66
+ self.back_floats[u] = set()
67
+ if v not in self.back_floats:
68
+ self.back_floats[v] = set()
69
+ self.back_floats[u].add(v)
70
+ self.back_floats[v].add(u)
71
+
72
+ def add_loop_behind_float(self, u, v):
73
+ """
74
+ Set this loop to be behind the float between u and v
75
+ :param u: First loop in float
76
+ :param v: Second loop in float
77
+ """
78
+ if u not in self.front_floats:
79
+ self.front_floats[u] = set()
80
+ if v not in self.front_floats:
81
+ self.front_floats[v] = set()
82
+ self.front_floats[u].add(v)
83
+ self.front_floats[v].add(u)
84
+
85
+ def is_in_front_of_float(self, u, v) -> bool:
86
+ """
87
+ :param u: First loop in float.
88
+ :param v: Second loop in float.
89
+ :return: True if the float between u and v is in front of this loop.
90
+ """
91
+ return u in self.back_floats and v in self.back_floats and v in self.back_floats[u]
92
+
93
+ def is_behind_float(self, u, v) -> bool:
94
+ """
95
+ :param u: First loop in float.
96
+ :param v: Second loop in float.
97
+ :return: True if the float between u and v is behind this loop.
98
+ """
99
+ return u in self.front_floats and v in self.front_floats and v in self.front_floats[u]
100
+
101
+ def prior_loop_on_yarn(self):
102
+ """
103
+ :return: Prior loop on yarn or None if first loop on yarn
104
+ """
105
+ return self.yarn.prior_loop(self)
106
+
107
+ def next_loop_on_yarn(self):
108
+ """
109
+ :return: Next loop on yarn or Non if last loop on yarn
110
+ """
111
+ return self.yarn.next_loop(self)
112
+
113
+ def has_parent_loops(self) -> bool:
114
+ """
115
+ :return: True if loop has stitch-edge parents
116
+ """
117
+ return len(self.parent_loops) > 0
118
+
119
+ def add_parent_loop(self, parent, stack_position: int | None = None):
120
+ """
121
+ Adds the parent Loop onto the stack of parent_loops
122
+ :param parent: the Loop to be added onto the stack
123
+ :param stack_position: The position to insert the parent into, by default add on top of the stack
124
+ """
125
+ if stack_position is not None:
126
+ self.parent_loops.insert(stack_position, parent)
127
+ else:
128
+ self.parent_loops.append(parent)
129
+
130
+ @property
131
+ def loop_id(self) -> int:
132
+ """
133
+ :return: the id of the loop
134
+ """
135
+ return self._loop_id
136
+
137
+ def __hash__(self):
138
+ return self.loop_id
139
+
140
+ def __eq__(self, other):
141
+ return isinstance(other, Loop) and self.loop_id == other.loop_id and self.yarn == other.yarn
142
+
143
+ def __lt__(self, other):
144
+ assert isinstance(other, Loop)
145
+ return self.loop_id < other.loop_id
146
+
147
+ def __gt__(self, other):
148
+ assert isinstance(other, Loop)
149
+ return self.loop_id > other.loop_id
150
+
151
+ def __str__(self):
152
+ return f"{self.loop_id} on yarn {self.yarn}"
153
+
154
+ def __repr__(self):
155
+ return str(self.loop_id)
@@ -0,0 +1,23 @@
1
+ """Enumerator used to define the two pull-direction of a loop through other loops"""
2
+ from enum import Enum
3
+
4
+
5
+ class Pull_Direction(Enum):
6
+ """An enumerator of the two pull-directions of a loop."""
7
+ BtF = "Knit"
8
+ FtB = "Purl"
9
+
10
+ def opposite(self):
11
+ """
12
+ :return: returns the opposite pull direction of self
13
+ """
14
+ if self is Pull_Direction.BtF:
15
+ return Pull_Direction.FtB
16
+ else:
17
+ return Pull_Direction.BtF
18
+
19
+ def __str__(self):
20
+ return self.value
21
+
22
+ def __repr__(self):
23
+ return self.value