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.
- docs/source/conf.py +71 -71
- docs/source/index.rst +0 -4
- knit_graphs/Course.py +64 -41
- knit_graphs/Knit_Graph.py +70 -162
- knit_graphs/Knit_Graph_Visualizer.py +386 -185
- knit_graphs/Loop.py +124 -117
- knit_graphs/Pull_Direction.py +5 -2
- knit_graphs/Yarn.py +257 -219
- knit_graphs/artin_wale_braids/Crossing_Direction.py +2 -0
- knit_graphs/artin_wale_braids/Loop_Braid_Graph.py +47 -56
- knit_graphs/artin_wale_braids/Wale.py +67 -79
- knit_graphs/artin_wale_braids/Wale_Braid.py +8 -2
- knit_graphs/artin_wale_braids/Wale_Braid_Word.py +45 -31
- knit_graphs/artin_wale_braids/Wale_Group.py +53 -43
- knit_graphs/basic_knit_graph_generators.py +96 -140
- knit_graphs/directed_loop_graph.py +454 -0
- knit_graphs/knit_graph_builder.py +187 -0
- knit_graphs/knit_graph_errors/__init__.py +0 -0
- knit_graphs/knit_graph_errors/knit_graph_error.py +30 -0
- knit_graphs/py.typed +0 -0
- {knit_graphs-0.0.10.dist-info → knit_graphs-0.0.12.dist-info}/METADATA +3 -2
- {knit_graphs-0.0.10.dist-info → knit_graphs-0.0.12.dist-info}/RECORD +24 -19
- {knit_graphs-0.0.10.dist-info → knit_graphs-0.0.12.dist-info}/LICENSE +0 -0
- {knit_graphs-0.0.10.dist-info → knit_graphs-0.0.12.dist-info}/WHEEL +0 -0
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
This module defines the Crossing_Direction enumeration which represents the different ways loops can cross over or under each other in cable knitting patterns.
|
|
4
4
|
"""
|
|
5
|
+
|
|
5
6
|
from __future__ import annotations
|
|
6
7
|
|
|
7
8
|
from enum import Enum
|
|
@@ -13,6 +14,7 @@ class Crossing_Direction(Enum):
|
|
|
13
14
|
This enumeration represents the three possible crossing relationships between loops: crossing over to the right, crossing under to the right, or no crossing at all.
|
|
14
15
|
These directions are fundamental to representing cable structures in knitted fabrics.
|
|
15
16
|
"""
|
|
17
|
+
|
|
16
18
|
Over_Right = "+"
|
|
17
19
|
Under_Right = "-"
|
|
18
20
|
No_Cross = "|"
|
|
@@ -2,78 +2,82 @@
|
|
|
2
2
|
|
|
3
3
|
This module provides the Loop_Braid_Graph class which tracks crossing relationships between loops in cable knitting patterns using a directed graph structure.
|
|
4
4
|
"""
|
|
5
|
-
from typing import cast
|
|
6
5
|
|
|
7
|
-
from
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from typing import TypeVar
|
|
8
9
|
|
|
9
10
|
from knit_graphs.artin_wale_braids.Crossing_Direction import Crossing_Direction
|
|
11
|
+
from knit_graphs.directed_loop_graph import Directed_Loop_Graph
|
|
10
12
|
from knit_graphs.Loop import Loop
|
|
11
13
|
|
|
14
|
+
LoopT = TypeVar("LoopT", bound=Loop)
|
|
15
|
+
|
|
12
16
|
|
|
13
|
-
class Loop_Braid_Graph:
|
|
17
|
+
class Loop_Braid_Graph(Directed_Loop_Graph[LoopT, Crossing_Direction]):
|
|
14
18
|
"""A graph structure that tracks crossing braid edges between loops in cable patterns.
|
|
15
19
|
|
|
16
20
|
This class maintains a directed graph where nodes are loops and edges represent cable crossings between those loops.
|
|
17
21
|
It provides methods to add crossings, query crossing relationships, and determine which loops cross with a given loop.
|
|
18
|
-
|
|
19
|
-
Attributes:
|
|
20
|
-
loop_crossing_graph (DiGraph): A NetworkX directed graph storing loop crossing relationships with crossing direction attributes.
|
|
21
22
|
"""
|
|
22
|
-
_CROSSING = "crossing"
|
|
23
23
|
|
|
24
24
|
def __init__(self) -> None:
|
|
25
25
|
"""Initialize an empty loop braid graph with no crossings."""
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
def add_crossing(self, left_loop: Loop, right_loop: Loop, crossing_direction: Crossing_Direction) -> None:
|
|
29
|
-
"""Add a crossing edge between two loops with the specified crossing direction.
|
|
26
|
+
super().__init__()
|
|
30
27
|
|
|
28
|
+
def get_crossing(self, left_loop: LoopT, right_loop: LoopT) -> Crossing_Direction:
|
|
29
|
+
"""
|
|
31
30
|
Args:
|
|
32
31
|
left_loop (Loop): The loop on the left side of the crossing.
|
|
33
32
|
right_loop (Loop): The loop on the right side of the crossing.
|
|
34
|
-
crossing_direction (Crossing_Direction): The direction of the crossing (over, under, or none) between the loops.
|
|
35
|
-
"""
|
|
36
|
-
self.loop_crossing_graph.add_edge(left_loop, right_loop, crossing=crossing_direction)
|
|
37
33
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
Removes any crossings that involve the given loop.
|
|
41
|
-
Args:
|
|
42
|
-
loop (Loop): The loop to remove.
|
|
34
|
+
Returns:
|
|
35
|
+
Crossing_Direction: The crossing direction between the left and right loop. Defaults to No_Cross if no explicit crossing was previously defined.
|
|
43
36
|
"""
|
|
44
|
-
if
|
|
45
|
-
self.
|
|
37
|
+
if self.has_edge(left_loop, right_loop):
|
|
38
|
+
return self.get_edge(left_loop, right_loop)
|
|
39
|
+
elif self.has_edge(right_loop, left_loop):
|
|
40
|
+
return self.get_edge(right_loop, left_loop).opposite
|
|
41
|
+
else:
|
|
42
|
+
return Crossing_Direction.No_Cross
|
|
46
43
|
|
|
47
|
-
def
|
|
48
|
-
"""
|
|
44
|
+
def add_crossing(self, left_loop: LoopT, right_loop: LoopT, crossing_direction: Crossing_Direction) -> None:
|
|
45
|
+
"""Add a crossing edge between two loops with the specified crossing direction.
|
|
49
46
|
|
|
50
|
-
|
|
51
|
-
item (Loop | tuple[Loop, Loop]): Either a single loop to check for node membership, or a tuple of loops to check for edge membership.
|
|
47
|
+
If the opposite crossing edge has already been defined, it will be removed from the graph so that only one crossing relationship is ever defined for each pair of the loops.
|
|
52
48
|
|
|
53
|
-
|
|
54
|
-
|
|
49
|
+
Args:
|
|
50
|
+
left_loop (Loop): The loop on the left side of the crossing.
|
|
51
|
+
right_loop (Loop): The loop on the right side of the crossing.
|
|
52
|
+
crossing_direction (Crossing_Direction): The direction of the crossing (over, under, or none) between the loops.
|
|
55
53
|
"""
|
|
56
|
-
if
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
54
|
+
if self.has_edge(right_loop, left_loop): # Remove the edge that will be implied by the new crossing formation.
|
|
55
|
+
self.remove_edge(right_loop, left_loop)
|
|
56
|
+
if left_loop not in self:
|
|
57
|
+
self.add_loop(left_loop)
|
|
58
|
+
if right_loop not in self:
|
|
59
|
+
self.add_loop(right_loop)
|
|
60
|
+
self.add_edge(left_loop, right_loop, crossing_direction)
|
|
60
61
|
|
|
61
|
-
def left_crossing_loops(self, left_loop:
|
|
62
|
+
def left_crossing_loops(self, left_loop: LoopT) -> set[LoopT]:
|
|
62
63
|
"""Get all loops that cross with the given loop when it is on the left side.
|
|
63
64
|
|
|
64
65
|
Args:
|
|
65
66
|
left_loop (Loop): The loop on the left side of potential crossings.
|
|
66
67
|
|
|
67
68
|
Returns:
|
|
68
|
-
|
|
69
|
+
set[Loop]: Set of loops that this loop crosses over on the right side. Empty set if the loop is not in the graph or has no crossings.
|
|
69
70
|
"""
|
|
70
71
|
if left_loop not in self:
|
|
71
|
-
return
|
|
72
|
+
return set()
|
|
72
73
|
else:
|
|
73
|
-
return
|
|
74
|
-
|
|
74
|
+
return {
|
|
75
|
+
rl
|
|
76
|
+
for rl in self.successors(left_loop)
|
|
77
|
+
if self.get_crossing(left_loop, rl) is not Crossing_Direction.No_Cross
|
|
78
|
+
}
|
|
75
79
|
|
|
76
|
-
def right_crossing_loops(self, right_loop:
|
|
80
|
+
def right_crossing_loops(self, right_loop: LoopT) -> set[LoopT]:
|
|
77
81
|
"""Get all loops that cross with the given loop when it is on the right side.
|
|
78
82
|
|
|
79
83
|
Args:
|
|
@@ -83,23 +87,10 @@ class Loop_Braid_Graph:
|
|
|
83
87
|
list[Loop]: List of loops that cross this loop from the left side. Empty list if the loop is not in the graph or has no crossings.
|
|
84
88
|
"""
|
|
85
89
|
if right_loop not in self:
|
|
86
|
-
return
|
|
90
|
+
return set()
|
|
87
91
|
else:
|
|
88
|
-
return
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
If no edge exists between the loops, this method automatically adds a no-crossing edge to maintain consistency in the graph structure.
|
|
95
|
-
|
|
96
|
-
Args:
|
|
97
|
-
left_loop (Loop): The loop on the left side of the crossing.
|
|
98
|
-
right_loop (Loop): The loop on the right side of the crossing.
|
|
99
|
-
|
|
100
|
-
Returns:
|
|
101
|
-
Crossing_Direction: The crossing direction between the left and right loop. Defaults to No_Cross if no explicit crossing was previously defined.
|
|
102
|
-
"""
|
|
103
|
-
if not self.loop_crossing_graph.has_edge(left_loop, right_loop):
|
|
104
|
-
self.add_crossing(left_loop, right_loop, Crossing_Direction.No_Cross)
|
|
105
|
-
return cast(Crossing_Direction, self.loop_crossing_graph[left_loop][right_loop][self._CROSSING])
|
|
92
|
+
return {
|
|
93
|
+
l
|
|
94
|
+
for l in self.predecessors(right_loop)
|
|
95
|
+
if self.get_crossing(l, right_loop) is not Crossing_Direction.No_Cross
|
|
96
|
+
}
|
|
@@ -2,20 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
This module defines the Wale class which represents a vertical column of stitches in a knitted structure, maintaining the sequence and relationships between loops in that column.
|
|
4
4
|
"""
|
|
5
|
-
from __future__ import annotations
|
|
6
5
|
|
|
7
|
-
from
|
|
6
|
+
from __future__ import annotations
|
|
8
7
|
|
|
9
|
-
from
|
|
8
|
+
from typing import TYPE_CHECKING, TypeVar, overload
|
|
10
9
|
|
|
10
|
+
from knit_graphs.directed_loop_graph import Directed_Loop_Graph
|
|
11
11
|
from knit_graphs.Loop import Loop
|
|
12
12
|
from knit_graphs.Pull_Direction import Pull_Direction
|
|
13
13
|
|
|
14
14
|
if TYPE_CHECKING:
|
|
15
15
|
from knit_graphs.Knit_Graph import Knit_Graph
|
|
16
16
|
|
|
17
|
+
LoopT = TypeVar("LoopT", bound=Loop)
|
|
18
|
+
|
|
17
19
|
|
|
18
|
-
class Wale:
|
|
20
|
+
class Wale(Directed_Loop_Graph[LoopT, Pull_Direction]):
|
|
19
21
|
"""A data structure representing stitch relationships between loops in a vertical column of a knitted structure.
|
|
20
22
|
|
|
21
23
|
A wale represents a vertical sequence of loops connected by stitch edges, forming a column in the knitted fabric.
|
|
@@ -24,11 +26,9 @@ class Wale:
|
|
|
24
26
|
Attributes:
|
|
25
27
|
first_loop (Loop | None): The first (bottom) loop in the wale sequence.
|
|
26
28
|
last_loop (Loop | None): The last (top) loop in the wale sequence.
|
|
27
|
-
stitches (DiGraph): Stores the directed graph of stitch connections within this wale.
|
|
28
29
|
"""
|
|
29
|
-
_PULL_DIRECTION: str = "pull_direction"
|
|
30
30
|
|
|
31
|
-
def __init__(self, first_loop:
|
|
31
|
+
def __init__(self, first_loop: LoopT, knit_graph: Knit_Graph[LoopT], end_loop: LoopT | None = None) -> None:
|
|
32
32
|
"""Initialize a wale optionally starting with a specified loop.
|
|
33
33
|
|
|
34
34
|
Args:
|
|
@@ -38,44 +38,36 @@ class Wale:
|
|
|
38
38
|
The loop to terminate the wale with.
|
|
39
39
|
If no loop is provided or this loop is not found, the wale will terminate at the first loop with no child.
|
|
40
40
|
"""
|
|
41
|
-
|
|
42
|
-
self.
|
|
43
|
-
self._knit_graph: Knit_Graph = knit_graph
|
|
44
|
-
self.first_loop:
|
|
45
|
-
self.last_loop:
|
|
41
|
+
super().__init__()
|
|
42
|
+
self.add_loop(first_loop)
|
|
43
|
+
self._knit_graph: Knit_Graph[LoopT] = knit_graph
|
|
44
|
+
self.first_loop: LoopT = first_loop
|
|
45
|
+
self.last_loop: LoopT = first_loop
|
|
46
46
|
self._build_wale_from_first_loop(end_loop)
|
|
47
47
|
|
|
48
|
-
def
|
|
49
|
-
while self._knit_graph.has_child_loop(self.last_loop):
|
|
50
|
-
child = self._knit_graph.get_child_loop(self.last_loop)
|
|
51
|
-
assert isinstance(child, Loop)
|
|
52
|
-
self.add_loop_to_end(child)
|
|
53
|
-
if end_loop is not None and child is end_loop:
|
|
54
|
-
return # found the end loop, so wrap up the wale
|
|
55
|
-
|
|
56
|
-
def add_loop_to_end(self, loop: Loop) -> None:
|
|
48
|
+
def overlaps(self, other: Wale) -> bool:
|
|
57
49
|
"""
|
|
58
|
-
Add a loop to the end (top) of the wale with the specified pull direction.
|
|
59
|
-
|
|
60
50
|
Args:
|
|
61
|
-
|
|
51
|
+
other (Wale): The other wale to compare against for overlapping loops.
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
bool: True if the other wale has any overlapping loop(s) with this wale, False otherwise.
|
|
62
55
|
"""
|
|
63
|
-
|
|
64
|
-
self.last_loop = loop
|
|
56
|
+
return any(loop in other for loop in self)
|
|
65
57
|
|
|
66
|
-
def
|
|
58
|
+
def get_pull_direction(self, u: LoopT, v: LoopT) -> Pull_Direction:
|
|
67
59
|
"""Get the pull direction of the stitch edge between two loops in this wale.
|
|
68
60
|
|
|
69
61
|
Args:
|
|
70
|
-
u (
|
|
71
|
-
v (
|
|
62
|
+
u (LoopT): The parent loop in the stitch connection.
|
|
63
|
+
v (LoopT): The child loop in the stitch connection.
|
|
72
64
|
|
|
73
65
|
Returns:
|
|
74
66
|
Pull_Direction: The pull direction of the stitch between loops u and v.
|
|
75
67
|
"""
|
|
76
|
-
return
|
|
68
|
+
return self.get_edge(u, v)
|
|
77
69
|
|
|
78
|
-
def split_wale(self, split_loop:
|
|
70
|
+
def split_wale(self, split_loop: LoopT) -> tuple[Wale[LoopT], Wale[LoopT] | None]:
|
|
79
71
|
"""
|
|
80
72
|
Split this wale at the specified loop into two separate wales.
|
|
81
73
|
|
|
@@ -85,18 +77,36 @@ class Wale:
|
|
|
85
77
|
split_loop (Loop): The loop at which to split the wale. This loop will appear in both resulting wales.
|
|
86
78
|
|
|
87
79
|
Returns:
|
|
88
|
-
tuple[Wale, Wale | None]:
|
|
80
|
+
tuple[Wale[LoopT], Wale[LoopT] | None]:
|
|
89
81
|
A tuple containing:
|
|
90
82
|
* The first wale (from start to split_loop). This will be the whole wale if the split_loop is not found.
|
|
91
83
|
* The second wale (from split_loop to end). This will be None if the split_loop is not found.
|
|
92
84
|
"""
|
|
93
85
|
if split_loop in self:
|
|
94
|
-
return (
|
|
95
|
-
|
|
86
|
+
return (
|
|
87
|
+
Wale[LoopT](self.first_loop, self._knit_graph, end_loop=split_loop),
|
|
88
|
+
Wale[LoopT](split_loop, self._knit_graph, end_loop=self.last_loop),
|
|
89
|
+
)
|
|
96
90
|
else:
|
|
97
91
|
return self, None
|
|
98
92
|
|
|
99
|
-
def
|
|
93
|
+
def _build_wale_from_first_loop(self, end_loop: LoopT | None) -> None:
|
|
94
|
+
for child in [*self._knit_graph.dfs_preorder_loops(self.first_loop)][1:]:
|
|
95
|
+
self.add_loop_to_end(child)
|
|
96
|
+
if end_loop is not None and child is end_loop:
|
|
97
|
+
return # found the end loop, so wrap up the wale
|
|
98
|
+
|
|
99
|
+
def add_loop_to_end(self, loop: LoopT) -> None:
|
|
100
|
+
"""
|
|
101
|
+
Add a loop to the end (top) of the wale.
|
|
102
|
+
Args:
|
|
103
|
+
loop (T): The loop to add to the end of the wale.
|
|
104
|
+
"""
|
|
105
|
+
self.add_loop(loop)
|
|
106
|
+
self.add_edge(self.last_loop, loop, self._knit_graph.get_edge(self.last_loop, loop))
|
|
107
|
+
self.last_loop = loop
|
|
108
|
+
|
|
109
|
+
def __eq__(self, other: object) -> bool:
|
|
100
110
|
"""
|
|
101
111
|
Args:
|
|
102
112
|
other (Wale): The wale to compare.
|
|
@@ -104,50 +114,39 @@ class Wale:
|
|
|
104
114
|
Returns:
|
|
105
115
|
bool: True if all the loops in both wales are present and in the same order. False, otherwise.
|
|
106
116
|
"""
|
|
107
|
-
if len(self) != len(other):
|
|
117
|
+
if not isinstance(other, Wale) or len(self) != len(other):
|
|
108
118
|
return False
|
|
109
|
-
return not any(l != o for l, o in zip(self, other))
|
|
119
|
+
return not any(l != o for l, o in zip(self, other, strict=False))
|
|
110
120
|
|
|
111
|
-
|
|
112
|
-
|
|
121
|
+
@overload
|
|
122
|
+
def __getitem__(self, item: int) -> LoopT: ...
|
|
113
123
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
"""
|
|
117
|
-
return len(self.stitches.nodes)
|
|
118
|
-
|
|
119
|
-
def __iter__(self) -> Iterator[Loop]:
|
|
120
|
-
"""Iterate over loops in this wale from first to last.
|
|
124
|
+
@overload
|
|
125
|
+
def __getitem__(self, item: tuple[LoopT | int, LoopT | int]) -> Pull_Direction: ...
|
|
121
126
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
"""
|
|
125
|
-
return cast(Iterator[Loop], dfs_preorder_nodes(self.stitches, source=self.first_loop))
|
|
127
|
+
@overload
|
|
128
|
+
def __getitem__(self, item: slice) -> list[LoopT]: ...
|
|
126
129
|
|
|
127
|
-
def __getitem__(self, item: int | slice) ->
|
|
128
|
-
"""Get loop
|
|
130
|
+
def __getitem__(self, item: int | tuple[LoopT | int, LoopT | int] | slice) -> LoopT | Pull_Direction | list[LoopT]:
|
|
131
|
+
"""Get a loop by its ID from this yarn.
|
|
129
132
|
|
|
130
133
|
Args:
|
|
131
|
-
item (int |
|
|
134
|
+
item (int | tuple[LoopT | int, LoopT | int] | slice):
|
|
135
|
+
The loop ,loop ID, or float between two loops to retrieve from this yarn.
|
|
136
|
+
If given a slice, it will retrieve the elements between the specified indices in the standard ordering of loops along the wale.
|
|
132
137
|
|
|
133
138
|
Returns:
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
return cast(Loop, self.stitches.nodes[item])
|
|
138
|
-
elif isinstance(item, slice):
|
|
139
|
-
return list(self)[item]
|
|
140
|
-
|
|
141
|
-
def __contains__(self, item: Loop) -> bool:
|
|
142
|
-
"""Check if a loop is contained in this wale.
|
|
143
|
-
|
|
144
|
-
Args:
|
|
145
|
-
item (Loop): The loop to check for membership in this wale.
|
|
139
|
+
LoopT: The loop on the yarn with the matching ID.
|
|
140
|
+
Stitch_Edge: The edge data for the given pair of loops forming a stitch in the wale.
|
|
141
|
+
list[LoopT]: The loops in the slice of the wale based on their ordering along the wale.
|
|
146
142
|
|
|
147
|
-
|
|
148
|
-
|
|
143
|
+
Raises:
|
|
144
|
+
KeyError: If the item is not found on this yarn.
|
|
149
145
|
"""
|
|
150
|
-
|
|
146
|
+
if isinstance(item, slice):
|
|
147
|
+
return list(self)[item]
|
|
148
|
+
else:
|
|
149
|
+
return super().__getitem__(item)
|
|
151
150
|
|
|
152
151
|
def __hash__(self) -> int:
|
|
153
152
|
"""
|
|
@@ -171,14 +170,3 @@ class Wale:
|
|
|
171
170
|
str: The string representation of this wale.
|
|
172
171
|
"""
|
|
173
172
|
return str(self)
|
|
174
|
-
|
|
175
|
-
def overlaps(self, other: Wale) -> bool:
|
|
176
|
-
"""Check if this wale has any loops in common with another wale.
|
|
177
|
-
|
|
178
|
-
Args:
|
|
179
|
-
other (Wale): The other wale to compare against for overlapping loops.
|
|
180
|
-
|
|
181
|
-
Returns:
|
|
182
|
-
bool: True if the other wale has any overlapping loop(s) with this wale, False otherwise.
|
|
183
|
-
"""
|
|
184
|
-
return any(loop in other for loop in self)
|
|
@@ -3,11 +3,17 @@
|
|
|
3
3
|
This module provides the Wale_Braid class which represents complex cable knitting patterns as mathematical braid structures,
|
|
4
4
|
using concepts from algebraic topology to model how wales cross over and under each other.
|
|
5
5
|
"""
|
|
6
|
+
|
|
7
|
+
from typing import Generic, TypeVar
|
|
8
|
+
|
|
6
9
|
from knit_graphs.artin_wale_braids.Wale_Braid_Word import Wale_Braid_Word
|
|
7
10
|
from knit_graphs.artin_wale_braids.Wale_Group import Wale_Group
|
|
11
|
+
from knit_graphs.Loop import Loop
|
|
12
|
+
|
|
13
|
+
LoopT = TypeVar("LoopT", bound=Loop)
|
|
8
14
|
|
|
9
15
|
|
|
10
|
-
class Wale_Braid:
|
|
16
|
+
class Wale_Braid(Generic[LoopT]):
|
|
11
17
|
"""A model of knitted structure as a set of crossing wales using Artin braid groups.
|
|
12
18
|
|
|
13
19
|
This class represents complex cable knitting patterns using mathematical braid theory,
|
|
@@ -19,7 +25,7 @@ class Wale_Braid:
|
|
|
19
25
|
wale_words (list[Wale_Braid_Word]): The sequence of braid words that describe the crossing operations between wales.
|
|
20
26
|
"""
|
|
21
27
|
|
|
22
|
-
def __init__(self, wale_groups: list[Wale_Group], wale_words: list[Wale_Braid_Word]) -> None:
|
|
28
|
+
def __init__(self, wale_groups: list[Wale_Group], wale_words: list[Wale_Braid_Word[LoopT]]) -> None:
|
|
23
29
|
"""Initialize a wale braid with the specified groups and braid words.
|
|
24
30
|
|
|
25
31
|
Args:
|
|
@@ -2,35 +2,43 @@
|
|
|
2
2
|
|
|
3
3
|
This module provides the Wale_Braid_Word class which represents a single step in a braid operation, describing how loops cross over each other within a course of knitting.
|
|
4
4
|
"""
|
|
5
|
+
|
|
5
6
|
from __future__ import annotations
|
|
6
7
|
|
|
8
|
+
from collections.abc import Iterator
|
|
9
|
+
from typing import Generic, TypeVar
|
|
10
|
+
|
|
7
11
|
from knit_graphs.artin_wale_braids.Crossing_Direction import Crossing_Direction
|
|
8
12
|
from knit_graphs.Loop import Loop
|
|
9
13
|
|
|
14
|
+
LoopT = TypeVar("LoopT", bound=Loop)
|
|
10
15
|
|
|
11
|
-
|
|
16
|
+
|
|
17
|
+
class Wale_Braid_Word(Generic[LoopT]):
|
|
12
18
|
"""A representation of loop crossings over a set of loops in a common course.
|
|
13
19
|
|
|
14
20
|
This class represents a single braid word in the mathematical sense, describing how a set of loops cross over or under each other within a horizontal course.
|
|
15
21
|
Each braid word can be inverted and applied to determine the new ordering of loops after the crossing operations.
|
|
16
22
|
|
|
17
23
|
Attributes:
|
|
18
|
-
loops (list[
|
|
24
|
+
loops (list[LoopT]): The ordered list of loops that participate in this braid word.
|
|
19
25
|
crossings (dict[int, Crossing_Direction]): A mapping from loop indices to their crossing directions, where each key represents the index of a loop that crosses with the loop at index+1.
|
|
20
26
|
"""
|
|
21
27
|
|
|
22
|
-
def __init__(self, loops: list[
|
|
28
|
+
def __init__(self, loops: list[LoopT], crossings: dict[int, Crossing_Direction]) -> None:
|
|
23
29
|
"""Initialize a wale braid word with the specified loops and crossing operations.
|
|
24
30
|
|
|
25
31
|
Args:
|
|
26
|
-
loops (list[
|
|
27
|
-
crossings (dict[int, Crossing_Direction]):
|
|
28
|
-
|
|
32
|
+
loops (list[LoopT]): The ordered list of loops that participate in this braid word.
|
|
33
|
+
crossings (dict[int, Crossing_Direction]):
|
|
34
|
+
A dictionary mapping loop indices to crossing directions.
|
|
35
|
+
Each index-key indicates that the loop at that index crosses with the loop at index+1 in the specified direction.
|
|
29
36
|
"""
|
|
30
|
-
self.loops: list[
|
|
37
|
+
self.loops: list[LoopT] = loops
|
|
31
38
|
self.crossings: dict[int, Crossing_Direction] = crossings
|
|
32
39
|
|
|
33
|
-
|
|
40
|
+
@property
|
|
41
|
+
def new_loop_order(self) -> list[LoopT]:
|
|
34
42
|
"""Calculate the new ordering of loops after applying all crossing operations.
|
|
35
43
|
|
|
36
44
|
This method applies all the crossing operations defined in this braid word to determine the final ordering of loops.
|
|
@@ -39,7 +47,7 @@ class Wale_Braid_Word:
|
|
|
39
47
|
Returns:
|
|
40
48
|
list[Loop]: The list of loops in their new order after all crossing operations have been applied.
|
|
41
49
|
"""
|
|
42
|
-
new_loops
|
|
50
|
+
new_loops = [*self]
|
|
43
51
|
for i in sorted(self.crossings.keys(), reverse=True):
|
|
44
52
|
moves_left = new_loops[i + 1]
|
|
45
53
|
new_loops[i + 1] = new_loops[i]
|
|
@@ -47,19 +55,30 @@ class Wale_Braid_Word:
|
|
|
47
55
|
|
|
48
56
|
return new_loops
|
|
49
57
|
|
|
50
|
-
def
|
|
58
|
+
def is_inversion(self, other: Wale_Braid_Word) -> bool:
|
|
59
|
+
"""Check if another braid word is the inverse of this braid word.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
other (Wale_Braid_Word): The other braid word to compare against for inversion relationship.
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
bool: True if the other braid word is equal to the inverse of this braid word, False otherwise.
|
|
66
|
+
"""
|
|
67
|
+
invert = ~self
|
|
68
|
+
return other == invert
|
|
69
|
+
|
|
70
|
+
def __invert__(self) -> Wale_Braid_Word[LoopT]:
|
|
51
71
|
"""Create the inverse braid word that undoes the operations of this braid word.
|
|
52
72
|
|
|
53
73
|
The inverse braid word uses the new loop order as its starting configuration and inverts all crossing directions to reverse the effect of the original braid word.
|
|
54
74
|
|
|
55
75
|
Returns:
|
|
56
|
-
Wale_Braid_Word: A new braid word that represents the inverse operation of this braid word.
|
|
76
|
+
Wale_Braid_Word[LoopT]: A new braid word that represents the inverse operation of this braid word.
|
|
57
77
|
"""
|
|
58
|
-
new_loops = self.new_loop_order()
|
|
59
78
|
new_crossings = {i: ~c for i, c in self.crossings.items()}
|
|
60
|
-
return Wale_Braid_Word(
|
|
79
|
+
return Wale_Braid_Word[LoopT](self.new_loop_order, new_crossings)
|
|
61
80
|
|
|
62
|
-
def __eq__(self, other:
|
|
81
|
+
def __eq__(self, other: object) -> bool:
|
|
63
82
|
"""Check equality with another wale braid word.
|
|
64
83
|
|
|
65
84
|
Two braid words are equal if they have the same loops in the same order and the same crossing operations at the same indices.
|
|
@@ -70,25 +89,20 @@ class Wale_Braid_Word:
|
|
|
70
89
|
Returns:
|
|
71
90
|
bool: True if both braid words have identical loops, loop ordering, and crossing operations, False otherwise.
|
|
72
91
|
"""
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
def
|
|
82
|
-
"""
|
|
83
|
-
|
|
84
|
-
Args:
|
|
85
|
-
other (Wale_Braid_Word): The other braid word to compare against for inversion relationship.
|
|
86
|
-
|
|
92
|
+
return (
|
|
93
|
+
isinstance(other, Wale_Braid_Word)
|
|
94
|
+
and len(self) == len(other)
|
|
95
|
+
and all(l == o for l, o in zip(self.loops, other.loops, strict=False))
|
|
96
|
+
and all(i in other.crossings for i in self.crossings)
|
|
97
|
+
and all(other.crossings[i] == cd for i, cd in self.crossings.items())
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
def __iter__(self) -> Iterator[LoopT]:
|
|
101
|
+
"""
|
|
87
102
|
Returns:
|
|
88
|
-
|
|
103
|
+
Iterator[LoopT]: Iteration over the loops in the braid word.
|
|
89
104
|
"""
|
|
90
|
-
|
|
91
|
-
return other == invert
|
|
105
|
+
return iter(self.loops)
|
|
92
106
|
|
|
93
107
|
def __len__(self) -> int:
|
|
94
108
|
"""Get the number of loops in this braid word.
|