knit-graphs 0.0.8__tar.gz → 0.0.10__tar.gz

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 (40) hide show
  1. {knit_graphs-0.0.8 → knit_graphs-0.0.10}/PKG-INFO +4 -4
  2. {knit_graphs-0.0.8 → knit_graphs-0.0.10}/pyproject.toml +4 -67
  3. {knit_graphs-0.0.8 → knit_graphs-0.0.10}/src/knit_graphs/Course.py +24 -5
  4. {knit_graphs-0.0.8 → knit_graphs-0.0.10}/src/knit_graphs/Knit_Graph.py +98 -46
  5. {knit_graphs-0.0.8 → knit_graphs-0.0.10}/src/knit_graphs/Loop.py +134 -19
  6. {knit_graphs-0.0.8 → knit_graphs-0.0.10}/src/knit_graphs/Yarn.py +53 -13
  7. {knit_graphs-0.0.8 → knit_graphs-0.0.10}/src/knit_graphs/artin_wale_braids/Loop_Braid_Graph.py +11 -1
  8. {knit_graphs-0.0.8 → knit_graphs-0.0.10}/src/knit_graphs/artin_wale_braids/Wale.py +68 -53
  9. {knit_graphs-0.0.8 → knit_graphs-0.0.10}/src/knit_graphs/artin_wale_braids/Wale_Group.py +80 -65
  10. knit_graphs-0.0.8/src/knit_graphs/_base_classes.py +0 -173
  11. {knit_graphs-0.0.8 → knit_graphs-0.0.10}/LICENSE +0 -0
  12. {knit_graphs-0.0.8 → knit_graphs-0.0.10}/README.md +0 -0
  13. {knit_graphs-0.0.8 → knit_graphs-0.0.10}/docs/Makefile +0 -0
  14. {knit_graphs-0.0.8 → knit_graphs-0.0.10}/docs/make.bat +0 -0
  15. {knit_graphs-0.0.8 → knit_graphs-0.0.10}/docs/source/api/knit_graphs.Course.rst +0 -0
  16. {knit_graphs-0.0.8 → knit_graphs-0.0.10}/docs/source/api/knit_graphs.Knit_Graph.rst +0 -0
  17. {knit_graphs-0.0.8 → knit_graphs-0.0.10}/docs/source/api/knit_graphs.Knit_Graph_Visualizer.rst +0 -0
  18. {knit_graphs-0.0.8 → knit_graphs-0.0.10}/docs/source/api/knit_graphs.Loop.rst +0 -0
  19. {knit_graphs-0.0.8 → knit_graphs-0.0.10}/docs/source/api/knit_graphs.Pull_Direction.rst +0 -0
  20. {knit_graphs-0.0.8 → knit_graphs-0.0.10}/docs/source/api/knit_graphs.Yarn.rst +0 -0
  21. {knit_graphs-0.0.8 → knit_graphs-0.0.10}/docs/source/api/knit_graphs.artin_wale_braids.Crossing_Direction.rst +0 -0
  22. {knit_graphs-0.0.8 → knit_graphs-0.0.10}/docs/source/api/knit_graphs.artin_wale_braids.Loop_Braid_Graph.rst +0 -0
  23. {knit_graphs-0.0.8 → knit_graphs-0.0.10}/docs/source/api/knit_graphs.artin_wale_braids.Wale.rst +0 -0
  24. {knit_graphs-0.0.8 → knit_graphs-0.0.10}/docs/source/api/knit_graphs.artin_wale_braids.Wale_Braid.rst +0 -0
  25. {knit_graphs-0.0.8 → knit_graphs-0.0.10}/docs/source/api/knit_graphs.artin_wale_braids.Wale_Braid_Word.rst +0 -0
  26. {knit_graphs-0.0.8 → knit_graphs-0.0.10}/docs/source/api/knit_graphs.artin_wale_braids.Wale_Group.rst +0 -0
  27. {knit_graphs-0.0.8 → knit_graphs-0.0.10}/docs/source/api/knit_graphs.artin_wale_braids.rst +0 -0
  28. {knit_graphs-0.0.8 → knit_graphs-0.0.10}/docs/source/api/knit_graphs.basic_knit_graph_generators.rst +0 -0
  29. {knit_graphs-0.0.8 → knit_graphs-0.0.10}/docs/source/api/knit_graphs.rst +0 -0
  30. {knit_graphs-0.0.8 → knit_graphs-0.0.10}/docs/source/conf.py +0 -0
  31. {knit_graphs-0.0.8 → knit_graphs-0.0.10}/docs/source/index.rst +0 -0
  32. {knit_graphs-0.0.8 → knit_graphs-0.0.10}/docs/source/installation.rst +0 -0
  33. {knit_graphs-0.0.8 → knit_graphs-0.0.10}/src/knit_graphs/Knit_Graph_Visualizer.py +0 -0
  34. {knit_graphs-0.0.8 → knit_graphs-0.0.10}/src/knit_graphs/Pull_Direction.py +0 -0
  35. {knit_graphs-0.0.8 → knit_graphs-0.0.10}/src/knit_graphs/__init__.py +0 -0
  36. {knit_graphs-0.0.8 → knit_graphs-0.0.10}/src/knit_graphs/artin_wale_braids/Crossing_Direction.py +0 -0
  37. {knit_graphs-0.0.8 → knit_graphs-0.0.10}/src/knit_graphs/artin_wale_braids/Wale_Braid.py +0 -0
  38. {knit_graphs-0.0.8 → knit_graphs-0.0.10}/src/knit_graphs/artin_wale_braids/Wale_Braid_Word.py +0 -0
  39. {knit_graphs-0.0.8 → knit_graphs-0.0.10}/src/knit_graphs/artin_wale_braids/__init__.py +0 -0
  40. {knit_graphs-0.0.8 → knit_graphs-0.0.10}/src/knit_graphs/basic_knit_graph_generators.py +0 -0
@@ -1,8 +1,8 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: knit-graphs
3
- Version: 0.0.8
3
+ Version: 0.0.10
4
4
  Summary: A graph representation of knitted structures where each loop is a node and edges represent yarn and stitch relationships.
5
- Home-page: https://mhofmann-Khoury.github.io/knit-graphs/
5
+ Home-page: https://mhofmann-khoury.github.io/knit_graph/
6
6
  License: MIT
7
7
  Keywords: ACT Lab,Northeastern University,knit,machine knit,fabrication,textile
8
8
  Author: Megan Hofmann
@@ -22,8 +22,8 @@ Classifier: Programming Language :: Python :: 3.13
22
22
  Classifier: Topic :: Scientific/Engineering
23
23
  Requires-Dist: networkx (>=3.5)
24
24
  Requires-Dist: plotly (>=6.3.0,<7.0.0)
25
- Project-URL: Documentation, https://mhofmann-Khoury.github.io/knit-graphs/
26
- Project-URL: Repository, https://github.com/mhofmann-Khoury/knit-graphs
25
+ Project-URL: Documentation, https://mhofmann-khoury.github.io/knit_graph/
26
+ Project-URL: Repository, https://github.com/mhofmann-Khoury/knit_graph/
27
27
  Description-Content-Type: text/markdown
28
28
 
29
29
  # knit_graphs
@@ -12,15 +12,15 @@ build-backend = "poetry.core.masonry.api" # Use Poetry's build system
12
12
  # All the information about your project that will appear on PyPI
13
13
  [tool.poetry]
14
14
  name = "knit-graphs"
15
- version = "0.0.8"
15
+ version = "0.0.10"
16
16
  description = "A graph representation of knitted structures where each loop is a node and edges represent yarn and stitch relationships."
17
17
  authors = ["Megan Hofmann <m.hofmann@northeastern.edu>"]
18
18
  maintainers = ["Megan Hofmann <m.hofmann@northeastern.edu>"]
19
19
  license = "MIT" # License type (shows up on PyPI)
20
20
  readme = "README.md" # File to use as long description on PyPI
21
- homepage = "https://mhofmann-Khoury.github.io/knit-graphs/"
22
- repository = "https://github.com/mhofmann-Khoury/knit-graphs"
23
- documentation = "https://mhofmann-Khoury.github.io/knit-graphs/"
21
+ homepage = "https://mhofmann-khoury.github.io/knit_graph/"
22
+ repository = "https://github.com/mhofmann-Khoury/knit_graph/"
23
+ documentation = "https://mhofmann-khoury.github.io/knit_graph/"
24
24
  keywords = ["ACT Lab", "Northeastern University", "knit", "machine knit", "fabrication", "textile"]
25
25
  classifiers = [ # PyPI classifiers for categorization
26
26
  "Development Status :: 3 - Alpha", # Update as project matures (Alpha -> Beta -> Production/Stable)
@@ -52,8 +52,6 @@ include = [
52
52
  "README.md", # Project description
53
53
  "LICENSE", # License file
54
54
  "docs/**/*", # All documentation files
55
- # "src/**/data/**/*", # Example: include data files
56
- # "src/**/templates/**/*", # Example: include template files
57
55
  ]
58
56
 
59
57
  # Exclude files from the distribution package (keeps package size down)
@@ -80,29 +78,11 @@ exclude = [
80
78
  python = ">=3.11,<3.14"
81
79
  networkx = ">=3.5"
82
80
  plotly = "^6.3.0"
83
- # Examples:
84
- # requests = "^2.31.0" # For HTTP requests
85
- # pydantic = "^2.0.0" # For data validation
86
- # click = "^8.1.0" # For CLI applications
87
- # numpy = "^1.24.0" # For numerical computing
88
- # pandas = "^2.0.0" # For data manipulation
89
-
90
- # =============================================================================
91
- # OPTIONAL DEPENDENCIES (EXTRAS)
92
- # =============================================================================
93
- # Optional dependency groups that users can install with pip install "package[extra]"
94
- #[tool.poetry.extras]
95
- # Examples:
96
- # cli = ["click", "rich"] # For command-line interface features
97
- # viz = ["matplotlib", "plotly"] # For visualization features
98
- # dev = ["pytest", "mypy"] # For development tools (though prefer dev dependencies)
99
81
 
100
82
  # =============================================================================
101
83
  # DEVELOPMENT DEPENDENCIES
102
84
  # =============================================================================
103
85
  # These packages are only needed during development and testing
104
-
105
-
106
86
  [tool.poetry.group.dev.dependencies]
107
87
  importlib-resources = ">=6.5.2"
108
88
 
@@ -136,49 +116,12 @@ sphinx-autoapi = "^3.0.0" # Automatically generates API documentation
136
116
  # -------------------------------------------------------------------------
137
117
  # DEVELOPMENT TOOLS AND UTILITIES
138
118
  # -------------------------------------------------------------------------
139
- ipython = "^8.14.0" # Enhanced interactive Python shell for debugging
140
- rich = "^13.5.0" # Beautiful terminal output and debugging
141
119
  tox = "^4.11.0" # Test across multiple Python versions locally
142
120
 
143
121
  # -------------------------------------------------------------------------
144
122
  # RELEASE AND BUILD TOOLS
145
123
  # -------------------------------------------------------------------------
146
124
  build = "^0.10.0" # PEP 517 build tool (for creating distributions)
147
- bump2version = "^1.0.0" # Automated version bumping tool
148
-
149
- # -------------------------------------------------------------------------
150
- # PLATFORM-SPECIFIC DEPENDENCIES
151
- # -------------------------------------------------------------------------
152
- # Only install on Windows systems (for colored terminal output)
153
- colorama = {version = "^0.4.6", markers = "sys_platform == 'win32'"}
154
-
155
- # =============================================================================
156
- # DOCUMENTATION-SPECIFIC DEPENDENCIES
157
- # =============================================================================
158
- # Separate dependency group for building documentation (allows selective installation)
159
- [tool.poetry.group.docs.dependencies]
160
- sphinx = "^7.1.0" # Documentation generator (main tool)
161
- sphinx-rtd-theme = "^1.3.0" # Professional-looking theme
162
- sphinx-autodoc-typehints = "^1.24.0" # Automatically include type hints in docs
163
- sphinx-autoapi = "^3.0.0" # Automatically generates API documentation
164
- myst-parser = "^2.0.0" # Support for Markdown files in documentation
165
-
166
- # =============================================================================
167
- # COMMAND LINE SCRIPTS
168
- # =============================================================================
169
- # Define command-line entry points for your package
170
- #[tool.poetry.scripts]
171
- # Example:
172
- # your-command = "your_project_name.cli:main" # Creates 'your-command' executable
173
-
174
- # =============================================================================
175
- # PROJECT URLS FOR PYPI
176
- # =============================================================================
177
- # Additional URLs that will be displayed on your PyPI project page
178
- #[tool.poetry.urls]
179
- #"Bug Tracker" = "https://github.com/mhofmann-Khoury/knit-graphs/issues"
180
- #"Changelog" = "https://github.com/mhofmann-Khoury/knit-graphs/blob/main/CHANGELOG.md"
181
- #"Discussions" = "https://github.com/your-username/your-project-name/discussions"
182
125
 
183
126
  # =============================================================================
184
127
  # ISORT IMPORT SORTER CONFIGURATION
@@ -244,12 +187,6 @@ check_untyped_defs = false # Don't type-check untyped test methods
244
187
  warn_return_any = false # Don't warn about returning 'Any' in tests
245
188
  warn_unused_ignores = false # Don't warn about unused type ignores in tests
246
189
 
247
- # Add overrides for specific third-party libraries that may have type issues
248
- # Example:
249
- # [[tool.mypy.overrides]]
250
- # module = ["problematic_library.*"]
251
- # ignore_errors = true
252
-
253
190
  # =============================================================================
254
191
  # UNITTEST TESTING FRAMEWORK CONFIGURATION
255
192
  # =============================================================================
@@ -4,11 +4,13 @@ This module contains the Course class which represents a horizontal row of loops
4
4
  """
5
5
  from __future__ import annotations
6
6
 
7
- from typing import Iterator, cast
7
+ from typing import TYPE_CHECKING, Iterator, cast
8
8
 
9
- from knit_graphs._base_classes import _Base_Knit_Graph
10
9
  from knit_graphs.Loop import Loop
11
10
 
11
+ if TYPE_CHECKING:
12
+ from knit_graphs.Knit_Graph import Knit_Graph
13
+
12
14
 
13
15
  class Course:
14
16
  """Course object for organizing loops into knitting rows.
@@ -17,15 +19,32 @@ class Course:
17
19
  It maintains an ordered list of loops and provides methods for analyzing the structure and relationships between courses in the knitted fabric.
18
20
  """
19
21
 
20
- def __init__(self, _knit_graph: _Base_Knit_Graph) -> None:
22
+ def __init__(self, knit_graph: Knit_Graph) -> None:
21
23
  """Initialize an empty course associated with a knit graph.
22
24
 
23
25
  Args:
24
- _knit_graph (Knit_Graph): The knit graph that this course belongs to.
26
+ knit_graph (Knit_Graph): The knit graph that this course belongs to.
25
27
  """
26
- self.loops_in_order: list[Loop] = []
28
+ self._knit_graph: Knit_Graph = knit_graph
29
+ self._loops_in_order: list[Loop] = []
27
30
  self._loop_set: set[Loop] = set()
28
31
 
32
+ @property
33
+ def loops_in_order(self) -> list[Loop]:
34
+ """
35
+ Returns:
36
+ list[Loop]: The list of loops in this course.
37
+ """
38
+ return self._loops_in_order
39
+
40
+ @property
41
+ def knit_graph(self) -> Knit_Graph:
42
+ """
43
+ Returns:
44
+ Knit_Graph: The knit graph that this course belongs to.
45
+ """
46
+ return self._knit_graph
47
+
29
48
  def add_loop(self, loop: Loop, index: int | None = None) -> None:
30
49
  """Add a loop to the course at the specified index or at the end.
31
50
 
@@ -5,9 +5,10 @@ It manages the relationships between loops, yarns, and structural elements like
5
5
  """
6
6
  from __future__ import annotations
7
7
 
8
- from typing import Any, cast
8
+ from typing import Any, Iterator, cast
9
+
10
+ from networkx import DiGraph
9
11
 
10
- from knit_graphs._base_classes import _Base_Knit_Graph
11
12
  from knit_graphs.artin_wale_braids.Crossing_Direction import Crossing_Direction
12
13
  from knit_graphs.artin_wale_braids.Loop_Braid_Graph import Loop_Braid_Graph
13
14
  from knit_graphs.artin_wale_braids.Wale import Wale
@@ -18,7 +19,7 @@ from knit_graphs.Pull_Direction import Pull_Direction
18
19
  from knit_graphs.Yarn import Yarn
19
20
 
20
21
 
21
- class Knit_Graph(_Base_Knit_Graph):
22
+ class Knit_Graph:
22
23
  """A representation of knitted structures as connections between loops on yarns.
23
24
 
24
25
  The Knit_Graph class is the main data structure for representing knitted fabrics.
@@ -28,7 +29,7 @@ class Knit_Graph(_Base_Knit_Graph):
28
29
 
29
30
  def __init__(self) -> None:
30
31
  """Initialize an empty knit graph with no loops or yarns."""
31
- super().__init__()
32
+ self.stitch_graph: DiGraph = DiGraph()
32
33
  self.braid_graph: Loop_Braid_Graph = Loop_Braid_Graph()
33
34
  self._last_loop: None | Loop = None
34
35
  self.yarns: set[Yarn] = set()
@@ -69,10 +70,46 @@ class Knit_Graph(_Base_Knit_Graph):
69
70
  """
70
71
  self.stitch_graph.add_node(loop)
71
72
  if loop.yarn not in self.yarns:
72
- self.add_yarn(cast(Yarn, loop.yarn))
73
+ self.add_yarn(loop.yarn)
73
74
  if self._last_loop is None or loop > self._last_loop:
74
75
  self._last_loop = loop
75
76
 
77
+ def remove_loop(self, loop: Loop) -> None:
78
+ """
79
+ Remove the given loop from the knit graph.
80
+ Args:
81
+ loop (Loop): The loop to be removed.
82
+
83
+ Raises:
84
+ KeyError: If the loop is not in the knit graph.
85
+
86
+ """
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.
90
+ # Remove any stitch edges involving this loop.
91
+ loop.remove_parent_loops()
92
+ if self.has_child_loop(loop):
93
+ 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)
97
+ # Remove loop from any floating positions
98
+ loop.remove_loop_from_front_floats()
99
+ loop.remove_loop_from_back_floats()
100
+ # Remove loop from yarn
101
+ yarn = loop.yarn
102
+ yarn.remove_loop(loop)
103
+ if len(yarn) == 0: # This was the only loop on that yarn
104
+ self.yarns.discard(yarn)
105
+ # Reset last loop
106
+ if loop is self.last_loop:
107
+ if len(self.yarns) == 0: # No loops left
108
+ assert len(self.stitch_graph.nodes) == 0
109
+ self._last_loop = None
110
+ 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))
112
+
76
113
  def add_yarn(self, yarn: Yarn) -> None:
77
114
  """Add a yarn to the graph without adding its loops.
78
115
 
@@ -102,42 +139,31 @@ class Knit_Graph(_Base_Knit_Graph):
102
139
  self.stitch_graph.add_edge(parent_loop, child_loop, pull_direction=pull_direction)
103
140
  child_loop.add_parent_loop(parent_loop, stack_position)
104
141
 
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.
142
+ def get_wales_ending_with_loop(self, last_loop: Loop) -> set[Wale]:
143
+ """Get all wales (vertical columns of stitches) that end at the specified loop.
107
144
 
108
145
  Args:
109
- first_loop (Loop): The loop at the start of the wale to be constructed.
146
+ last_loop (Loop): The last loop of the joined set of wales.
110
147
 
111
148
  Returns:
112
- Wale: A wale object representing the vertical column of stitches starting from the given loop.
149
+ set[Wale]: The set of wales that end at this loop.
113
150
  """
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.
151
+ 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}
124
155
 
125
- Args:
126
- last_loop (Loop): The last loop of the joined set of wales.
156
+ def get_terminal_wales(self) -> dict[Loop, list[Wale]]:
157
+ """
158
+ Get wale groups organized by their terminal loops.
127
159
 
128
160
  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
161
+ dict[Loop, list[Wale]]: Dictionary mapping terminal loops to list of wales that terminate that wale.
162
+ """
163
+ wale_groups = {}
164
+ for loop in self.terminal_loops():
165
+ wale_groups[loop] = [wale for wale in self.get_wales_ending_with_loop(loop)]
166
+ return wale_groups
141
167
 
142
168
  def get_courses(self) -> list[Course]:
143
169
  """Get all courses (horizontal rows) in the knit graph in chronological order.
@@ -148,8 +174,8 @@ class Knit_Graph(_Base_Knit_Graph):
148
174
  """
149
175
  courses = []
150
176
  course = Course(self)
151
- for loop in sorted([*self.stitch_graph.nodes]):
152
- for parent in self.stitch_graph.predecessors(loop):
177
+ for loop in self.sorted_loops():
178
+ for parent in loop.parent_loops:
153
179
  if parent in course: # start a new course
154
180
  courses.append(course)
155
181
  course = Course(self)
@@ -158,28 +184,47 @@ class Knit_Graph(_Base_Knit_Graph):
158
184
  courses.append(course)
159
185
  return courses
160
186
 
161
- def get_wale_groups(self) -> dict[Loop, Wale_Group]:
187
+ def get_wale_groups(self) -> set[Wale_Group]:
162
188
  """Get wale groups organized by their terminal loops.
163
189
 
164
190
  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.
191
+ 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.
166
192
  """
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
193
+ return set(Wale_Group(l, self) for l in self.terminal_loops())
172
194
 
173
- def __contains__(self, item: Loop) -> bool:
195
+ def __contains__(self, item: Loop | tuple[Loop, Loop]) -> bool:
174
196
  """Check if a loop is contained in the knit graph.
175
197
 
176
198
  Args:
177
- item (Loop): The loop being checked for in the graph.
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.
178
200
 
179
201
  Returns:
180
- bool: True if the loop is in the graph, False otherwise.
202
+ bool: True if the given loop or stitch edge is in the graph, False otherwise.
181
203
  """
182
- return bool(self.stitch_graph.has_node(item))
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))
183
228
 
184
229
  def get_pull_direction(self, parent: Loop, child: Loop) -> Pull_Direction | None:
185
230
  """Get the pull direction of the stitch edge between parent and child loops.
@@ -247,3 +292,10 @@ class Knit_Graph(_Base_Knit_Graph):
247
292
  bool: True if the loop has no child loops and terminates a wale, False otherwise.
248
293
  """
249
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))
@@ -5,12 +5,13 @@ Loops are the fundamental building blocks of knitted structures and maintain rel
5
5
  """
6
6
  from __future__ import annotations
7
7
 
8
- from typing import cast
8
+ from typing import TYPE_CHECKING
9
9
 
10
- from knit_graphs._base_classes import _Base_Loop, _Base_Yarn
10
+ if TYPE_CHECKING:
11
+ from knit_graphs.Yarn import Yarn
11
12
 
12
13
 
13
- class Loop(_Base_Loop):
14
+ class Loop:
14
15
  """A class to represent a single loop structure for modeling a single loop in a knitting pattern.
15
16
 
16
17
  The Loop class manages yarn relationships, parent-child connections for stitches, and float positioning for complex knitting structures.
@@ -23,15 +24,17 @@ class Loop(_Base_Loop):
23
24
  back_floats (dict[Loop, set[Loop]]): A dictionary tracking loops that this loop floats behind.
24
25
  """
25
26
 
26
- def __init__(self, loop_id: int, yarn: _Base_Yarn) -> None:
27
+ def __init__(self, loop_id: int, yarn: Yarn) -> None:
27
28
  """Construct a Loop object with the specified identifier and yarn.
28
29
 
29
30
  Args:
30
31
  loop_id (int): A unique identifier for the loop, must be non-negative.
31
- yarn (_Base_Yarn): The yarn that creates and holds this loop.
32
+ yarn (Yarn): The yarn that creates and holds this loop.
32
33
  """
33
- super().__init__(loop_id)
34
- self.yarn: _Base_Yarn = yarn
34
+ if loop_id < 0:
35
+ raise ValueError(f"Loop identifier must be non-negative but got {loop_id}")
36
+ self._loop_id: int = loop_id
37
+ self.yarn: Yarn = yarn
35
38
  self.parent_loops: list[Loop] = []
36
39
  self.front_floats: dict[Loop, set[Loop]] = {}
37
40
  self.back_floats: dict[Loop, set[Loop]] = {}
@@ -44,7 +47,12 @@ class Loop(_Base_Loop):
44
47
  Args:
45
48
  u (Loop): The first loop in the float pair.
46
49
  v (Loop): The second loop in the float pair.
50
+
51
+ Raises:
52
+ ValueError: If u and v are not on the same yarn.
47
53
  """
54
+ if u.yarn != v.yarn:
55
+ raise ValueError("Loops of a float must share a yarn.")
48
56
  if u not in self.back_floats:
49
57
  self.back_floats[u] = set()
50
58
  if v not in self.back_floats:
@@ -52,6 +60,18 @@ class Loop(_Base_Loop):
52
60
  self.back_floats[u].add(v)
53
61
  self.back_floats[v].add(u)
54
62
 
63
+ def remove_loop_from_front_floats(self) -> None:
64
+ """
65
+ Removes this loop from being in front of all marked floats. Mutates the yarns that own edges of those floats.
66
+ """
67
+ visited: set[Loop] = set()
68
+ for u, v_loops in self.front_floats.items():
69
+ visited.add(u)
70
+ for v in v_loops:
71
+ if v not in visited and v in u.yarn: # float shares a yarn
72
+ u.yarn.loop_graph.edges[u, v]["Back_Loops"].remove(self)
73
+ self.front_floats = {}
74
+
55
75
  def add_loop_behind_float(self, u: Loop, v: Loop) -> None:
56
76
  """Set this loop to be behind the float between loops u and v.
57
77
 
@@ -60,7 +80,12 @@ class Loop(_Base_Loop):
60
80
  Args:
61
81
  u (Loop): The first loop in the float pair.
62
82
  v (Loop): The second loop in the float pair.
83
+
84
+ Raises:
85
+ ValueError: If u and v are not on the same yarn.
63
86
  """
87
+ if u.yarn != v.yarn:
88
+ raise ValueError("Loops of a float must share a yarn.")
64
89
  if u not in self.front_floats:
65
90
  self.front_floats[u] = set()
66
91
  if v not in self.front_floats:
@@ -68,6 +93,18 @@ class Loop(_Base_Loop):
68
93
  self.front_floats[u].add(v)
69
94
  self.front_floats[v].add(u)
70
95
 
96
+ def remove_loop_from_back_floats(self) -> None:
97
+ """
98
+ Removes this loop from being behind of all marked floats. Mutates the yarns that own edges of those floats.
99
+ """
100
+ visited = set()
101
+ for u, v_loops in self.back_floats.items():
102
+ visited.add(u)
103
+ for v in v_loops:
104
+ if v not in visited and v in u.yarn: # float shares a yarn
105
+ u.yarn.loop_graph.edges[u, v]["Front_Loops"].remove(self)
106
+ self.back_floats = {}
107
+
71
108
  def is_in_front_of_float(self, u: Loop, v: Loop) -> bool:
72
109
  """Check if this loop is positioned in front of the float between loops u and v.
73
110
 
@@ -98,19 +135,25 @@ class Loop(_Base_Loop):
98
135
  Returns:
99
136
  Loop | None: The prior loop on the yarn, or None if this is the first loop on the yarn.
100
137
  """
101
- loop = self.yarn.prior_loop(self)
102
- if loop is None:
103
- return None
104
- else:
105
- return cast(Loop, loop)
138
+ return self.yarn.prior_loop(self)
106
139
 
107
- def next_loop_on_yarn(self) -> Loop:
140
+ def next_loop_on_yarn(self) -> Loop | None:
108
141
  """Get the loop that follows this loop on the same yarn.
109
142
 
110
143
  Returns:
111
- Loop: The next loop on the yarn, or None if this is the last loop on the yarn.
144
+ Loop | None: The next loop on the yarn, or None if this is the last loop on the yarn.
112
145
  """
113
- return cast(Loop, self.yarn.next_loop(self))
146
+ return self.yarn.next_loop(self)
147
+
148
+ def remove_parent_loops(self) -> list[Loop]:
149
+ """
150
+ Removes the list of parent loops from this loop.
151
+ Returns:
152
+ list[Loop]: The list of parent loops that were removed.
153
+ """
154
+ parents = self.parent_loops
155
+ self.parent_loops = []
156
+ return parents
114
157
 
115
158
  def has_parent_loops(self) -> bool:
116
159
  """Check if this loop has any parent loops connected through stitch edges.
@@ -132,10 +175,82 @@ class Loop(_Base_Loop):
132
175
  else:
133
176
  self.parent_loops.append(parent)
134
177
 
135
- def __str__(self) -> str:
136
- """Get a string representation of this loop.
178
+ def remove_parent(self, parent: Loop) -> None:
179
+ """
180
+ Removes the given parent loop from the set of parents of this loop.
181
+ If the given loop is not a parent of this loop, nothing happens.
182
+ Args:
183
+ parent (Loop): The parent loop to remove.
184
+ """
185
+ if parent in self.parent_loops:
186
+ self.parent_loops.remove(parent)
187
+
188
+ def ancestor_loops(self) -> set[Loop]:
189
+ """
190
+ Returns:
191
+ set[Loop]: The set of loops that initiate all wales that lead to this loop. The empty set if this loop has no parents.
192
+ """
193
+ if not self.has_parent_loops():
194
+ return set()
195
+ ancestors = set()
196
+ for parent_loop in self.parent_loops:
197
+ if parent_loop.has_parent_loops():
198
+ ancestors.update(parent_loop.ancestor_loops())
199
+ else:
200
+ ancestors.add(parent_loop)
201
+ return ancestors
202
+
203
+ @property
204
+ def loop_id(self) -> int:
205
+ """Get the unique identifier of this loop.
206
+
207
+ Returns:
208
+ int: The id of the loop.
209
+ """
210
+ return self._loop_id
211
+
212
+ def __hash__(self) -> int:
213
+ """Return hash value based on loop_id for use in sets and dictionaries.
214
+
215
+ Returns:
216
+ int: Hash value of the loop_id.
217
+ """
218
+ return self.loop_id
219
+
220
+ def __int__(self) -> int:
221
+ """Convert loop to integer representation using loop_id.
222
+
223
+ Returns:
224
+ int: The loop_id as an integer.
225
+ """
226
+ return self.loop_id
227
+
228
+ def __eq__(self, other: Loop) -> bool:
229
+ """Check equality with another base loop based on loop_id and type.
230
+
231
+ Args:
232
+ other (Loop): The other loop to compare with.
233
+
234
+ Returns:
235
+ bool: True if both loops have the same class and loop_id, False otherwise.
236
+ """
237
+ return isinstance(other, other.__class__) and self.loop_id == other.loop_id
238
+
239
+ def __lt__(self, other: Loop | int) -> bool:
240
+ """Compare loop_id with another loop or integer for ordering.
241
+
242
+ Args:
243
+ other (Loop | int): The other loop or integer to compare with.
244
+
245
+ Returns:
246
+ bool: True if this loop's id is less than the other's id.
247
+ """
248
+ return int(self.loop_id) < int(other)
249
+
250
+ def __repr__(self) -> str:
251
+ """Return string representation of the loop.
137
252
 
138
253
  Returns:
139
- str: String representation showing the loop ID and associated yarn.
254
+ str: String representation showing "Loop {loop_id}".
140
255
  """
141
- return f"{self.loop_id} on yarn {self.yarn}"
256
+ return f"Loop {self.loop_id}"