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