knit-graphs 0.0.9__py3-none-any.whl → 0.0.11__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 CHANGED
@@ -1,11 +1,11 @@
1
1
  """
2
- Configuration file for the Sphinx documentation builder.
3
- =============================================================================
4
- SPHINX DOCUMENTATION CONFIGURATION
5
- =============================================================================
6
- This file configures how Sphinx generates documentation from your Python code.
7
- For the full list of built-in configuration values, see:
8
- https://www.sphinx-doc.org/en/master/usage/configuration.html
2
+ Configuration file for the Sphinx documentation builder.
3
+ =============================================================================
4
+ SPHINX DOCUMENTATION CONFIGURATION
5
+ =============================================================================
6
+ This file configures how Sphinx generates documentation from your Python code.
7
+ For the full list of built-in configuration values, see:
8
+ https://www.sphinx-doc.org/en/master/usage/configuration.html
9
9
  """
10
10
 
11
11
  import os
@@ -19,17 +19,17 @@ from importlib.metadata import PackageNotFoundError, version
19
19
  # Add the project root and source directory to Python path so Sphinx can import your modules
20
20
 
21
21
  # Path to your source code (adjust if not using src/ layout)
22
- sys.path.insert(0, os.path.abspath('..')) # Project root
23
- sys.path.insert(0, os.path.abspath('../src')) # Source directory
24
- sys.path.insert(0, os.path.abspath('.')) # Docs directory
22
+ sys.path.insert(0, os.path.abspath("..")) # Project root
23
+ sys.path.insert(0, os.path.abspath("../src")) # Source directory
24
+ sys.path.insert(0, os.path.abspath(".")) # Docs directory
25
25
 
26
26
  # =============================================================================
27
27
  # PROJECT INFORMATION
28
28
  # =============================================================================
29
29
 
30
- project = 'knit-graphs'
31
- copyright = f'{datetime.now().year}, Megan Hofmann'
32
- author = 'Megan Hofmann'
30
+ project = "knit-graphs"
31
+ copyright = f"{datetime.now().year}, Megan Hofmann"
32
+ author = "Megan Hofmann"
33
33
 
34
34
  try:
35
35
  # Get version from installed package metadata
@@ -49,19 +49,17 @@ release = version
49
49
  # Extensions to enable (these add functionality to Sphinx)
50
50
  extensions = [
51
51
  # Core Sphinx extensions
52
- 'sphinx.ext.autodoc', # Generate docs from docstrings
53
- 'sphinx.ext.autosummary', # Generate summary tables automatically
54
- 'sphinx.ext.viewcode', # Add [source] links to documentation
55
- 'sphinx.ext.napoleon', # Support Google/NumPy docstring styles
56
- 'sphinx.ext.intersphinx', # Link to other project docs (e.g., Python docs)
57
- 'sphinx.ext.githubpages', # Publish to GitHub Pages (.nojekyll file)
58
- 'sphinx.ext.todo', # Support TODO items in docs
59
- 'sphinx.ext.coverage', # Check documentation coverage
60
- 'sphinx.ext.doctest', # Test code snippets in documentation
61
-
52
+ "sphinx.ext.autodoc", # Generate docs from docstrings
53
+ "sphinx.ext.autosummary", # Generate summary tables automatically
54
+ "sphinx.ext.viewcode", # Add [source] links to documentation
55
+ "sphinx.ext.napoleon", # Support Google/NumPy docstring styles
56
+ "sphinx.ext.intersphinx", # Link to other project docs (e.g., Python docs)
57
+ "sphinx.ext.githubpages", # Publish to GitHub Pages (.nojekyll file)
58
+ "sphinx.ext.todo", # Support TODO items in docs
59
+ "sphinx.ext.coverage", # Check documentation coverage
60
+ "sphinx.ext.doctest", # Test code snippets in documentation
62
61
  # Third-party extensions (these need to be installed)
63
- 'sphinx_autodoc_typehints', # Better type hint rendering
64
- 'myst_parser', # Support for Markdown files (optional)
62
+ "myst_parser", # Support for Markdown files (optional)
65
63
  ]
66
64
 
67
65
  # =============================================================================
@@ -70,19 +68,19 @@ extensions = [
70
68
 
71
69
  # File extensions that Sphinx will process
72
70
  source_suffix = {
73
- '.rst': None, # RestructuredText (default)
74
- '.md': 'myst_parser', # Markdown (requires myst_parser extension)
71
+ ".rst": None, # RestructuredText (default)
72
+ ".md": "myst_parser", # Markdown (requires myst_parser extension)
75
73
  }
76
74
 
77
75
  # The master toctree document (main page)
78
- master_doc = 'index'
76
+ master_doc = "index"
79
77
 
80
78
  # Files and directories to exclude from processing
81
79
  exclude_patterns = [
82
- '_build', # Build output directory
83
- 'Thumbs.db', # Windows thumbnail cache
84
- '.DS_Store', # macOS metadata
85
- '**.ipynb_checkpoints', # Jupyter notebook checkpoints
80
+ "_build", # Build output directory
81
+ "Thumbs.db", # Windows thumbnail cache
82
+ ".DS_Store", # macOS metadata
83
+ "**.ipynb_checkpoints", # Jupyter notebook checkpoints
86
84
  ]
87
85
 
88
86
  # =============================================================================
@@ -90,31 +88,29 @@ exclude_patterns = [
90
88
  # =============================================================================
91
89
 
92
90
  # The theme to use for HTML pages
93
- html_theme = 'sphinx_rtd_theme' # Read the Docs theme (clean, professional)
91
+ html_theme = "sphinx_rtd_theme" # Read the Docs theme (clean, professional)
94
92
 
95
93
  # Directories containing static files (CSS, JS, images)
96
- html_static_path = ['_static']
94
+ html_static_path = ["_static"]
97
95
 
98
96
 
99
97
  # Theme-specific options
100
98
  html_theme_options = {
101
- 'logo_only': False, # Show project name with logo
102
- 'display_version': True, # Show version in sidebar
103
- 'prev_next_buttons_location': 'bottom', # Navigation button placement
104
- 'style_external_links': True, # Style external links differently
105
- 'vcs_pageview_mode': '', # Version control integration
106
- 'style_nav_header_background': '#2980B9', # Header background color
107
-
99
+ "logo_only": False, # Show project name with logo
100
+ "prev_next_buttons_location": "bottom", # Navigation button placement
101
+ "style_external_links": True, # Style external links differently
102
+ "vcs_pageview_mode": "", # Version control integration
103
+ "style_nav_header_background": "#2980B9", # Header background color
108
104
  # Table of contents options
109
- 'collapse_navigation': True, # Collapse subsections in nav
110
- 'sticky_navigation': True, # Keep navigation visible on scroll
111
- 'navigation_depth': 4, # Maximum navigation depth
112
- 'includehidden': True, # Include hidden toctrees
113
- 'titles_only': False, # Show subsection titles in nav
105
+ "collapse_navigation": True, # Collapse subsections in nav
106
+ "sticky_navigation": True, # Keep navigation visible on scroll
107
+ "navigation_depth": 4, # Maximum navigation depth
108
+ "includehidden": True, # Include hidden toctrees
109
+ "titles_only": False, # Show subsection titles in nav
114
110
  }
115
111
 
116
112
  # Additional HTML options
117
- html_title = f'{project} Documentation' # Browser window title
113
+ html_title = f"{project} Documentation" # Browser window title
118
114
  html_short_title = project # Short title for navigation
119
115
 
120
116
 
@@ -136,20 +132,20 @@ html_short_title = project # Short title for navigation
136
132
 
137
133
  # Default options for all autodoc directives
138
134
  autodoc_default_options = {
139
- 'members': True, # Include all members
140
- 'member-order': 'bysource', # Order members as they appear in source
141
- 'special-members': '__init__', # Include __init__ methods
142
- 'undoc-members': True, # Include members without docstrings
143
- 'exclude-members': '__weakref__', # Exclude certain members
144
- 'show-inheritance': True, # Show class inheritance
145
- 'inherited-members': True, # Include inherited methods
135
+ "members": True, # Include all members
136
+ "member-order": "bysource", # Order members as they appear in source
137
+ "special-members": "__init__", # Include __init__ methods
138
+ "undoc-members": True, # Include members without docstrings
139
+ "exclude-members": "__weakref__", # Exclude certain members
140
+ "show-inheritance": True, # Show class inheritance
141
+ "inherited-members": True, # Include inherited methods
146
142
  }
147
143
 
148
144
  # How to display class signatures
149
145
  autodoc_class_signature = "mixed" # Show __init__ parameters with class
150
146
 
151
147
  # Order of members in documentation
152
- autodoc_member_order = 'bysource' # bysource, alphabetical, or groupwise
148
+ autodoc_member_order = "bysource" # bysource, alphabetical, or groupwise
153
149
 
154
150
  # Mock imports for modules that might not be available during doc building
155
151
  # Add any modules that cause import errors during doc building
@@ -159,6 +155,10 @@ autodoc_mock_imports = [
159
155
  # 'some_optional_dependency',
160
156
  ]
161
157
 
158
+ # Enable type hints in signatures
159
+ autodoc_typehints = "signature" # Show type hints in function signatures
160
+ autodoc_typehints_description_target = "documented" # Where to show type info
161
+
162
162
  # =============================================================================
163
163
  # AUTOSUMMARY CONFIGURATION
164
164
  # =============================================================================
@@ -193,13 +193,13 @@ napoleon_attr_annotations = True # Include attribute annotations
193
193
  # Links to external documentation
194
194
 
195
195
  intersphinx_mapping = {
196
- 'python': ('https://docs.python.org/3', None),
197
- 'numpy': ('https://numpy.org/doc/stable/', None),
198
- 'pandas': ('https://pandas.pydata.org/pandas-docs/stable/', None),
199
- 'matplotlib': ('https://matplotlib.org/stable/', None),
200
- 'scipy': ('https://docs.scipy.org/doc/scipy/', None),
201
- 'sklearn': ('https://scikit-learn.org/stable/', None),
202
- 'typing': ('https://typing.readthedocs.io/en/latest/', None),
196
+ "python": ("https://docs.python.org/3", None),
197
+ "numpy": ("https://numpy.org/doc/stable/", None),
198
+ "pandas": ("https://pandas.pydata.org/pandas-docs/stable/", None),
199
+ "matplotlib": ("https://matplotlib.org/stable/", None),
200
+ "scipy": ("https://docs.scipy.org/doc/scipy/", None),
201
+ "sklearn": ("https://scikit-learn.org/stable/", None),
202
+ "typing": ("https://typing.readthedocs.io/en/latest/", None),
203
203
  }
204
204
 
205
205
  # =============================================================================
@@ -207,10 +207,8 @@ intersphinx_mapping = {
207
207
  # =============================================================================
208
208
  # Controls how type hints are displayed in documentation
209
209
 
210
- typehints_fully_qualified = False # Use short names for types
210
+ # Note: These settings work with sphinx.ext.autodoc built-in type hint support
211
211
  always_document_param_types = True # Always show parameter types
212
- typehints_document_rtype = True # Document return types
213
- typehints_use_rtype = True # Use :rtype: directive for return types
214
212
 
215
213
  # =============================================================================
216
214
  # TODO EXTENSION CONFIGURATION
@@ -245,16 +243,17 @@ add_module_names = False # Don't prepend module names to functions
245
243
  show_authors = False # Don't show author info by default
246
244
 
247
245
  # Syntax highlighting style
248
- pygments_style = 'sphinx' # Code highlighting theme
246
+ pygments_style = "sphinx" # Code highlighting theme
249
247
 
250
248
  # Language for content that doesn't specify a language
251
- language = 'en'
249
+ language = "en"
252
250
 
253
251
 
254
252
  # =============================================================================
255
253
  # CUSTOM FUNCTIONS AND SETUP
256
254
  # =============================================================================
257
255
 
256
+
258
257
  def autodoc_skip_member(app, what, name, obj, skip, options):
259
258
  """
260
259
  Custom function to control which members are included in documentation.
@@ -288,15 +287,16 @@ def setup(app):
288
287
  app: The Sphinx application instance
289
288
  """
290
289
  # Connect custom functions to Sphinx events
291
- app.connect('autodoc-skip-member', autodoc_skip_member)
290
+ app.connect("autodoc-skip-member", autodoc_skip_member)
292
291
 
293
292
  # Return extension metadata
294
293
  return {
295
- 'version': version,
296
- 'parallel_read_safe': True,
297
- 'parallel_write_safe': True,
294
+ "version": version,
295
+ "parallel_read_safe": True,
296
+ "parallel_write_safe": True,
298
297
  }
299
298
 
299
+
300
300
  # =============================================================================
301
301
  # CHECKLIST FOR SPHINX SETUP
302
302
  # =============================================================================
docs/source/index.rst CHANGED
@@ -3,10 +3,6 @@ knit-graphs
3
3
 
4
4
  A graph representation of knitted structures where each loop is a node and edges represent yarn and stitch relationships.
5
5
 
6
- .. image:: https://img.shields.io/github/workflow/status/mhofmann-Khoury/knit-graphs/CI
7
- :target: https://github.com/mhofmann-Khoury/knit-graphs/actions
8
- :alt: Build Status
9
-
10
6
  .. image:: https://img.shields.io/pypi/v/knit-graphs
11
7
  :target: https://pypi.org/project/knit-graphs/
12
8
  :alt: PyPI Version
knit_graphs/Course.py CHANGED
@@ -2,9 +2,11 @@
2
2
 
3
3
  This module contains the Course class which represents a horizontal row of loops in a knitting pattern.
4
4
  """
5
+
5
6
  from __future__ import annotations
6
7
 
7
- from typing import TYPE_CHECKING, Iterator, cast
8
+ from collections.abc import Iterator
9
+ from typing import TYPE_CHECKING, cast
8
10
 
9
11
  from knit_graphs.Loop import Loop
10
12
 
knit_graphs/Knit_Graph.py CHANGED
@@ -3,9 +3,11 @@
3
3
  This module contains the main Knit_Graph class which serves as the central data structure for representing knitted fabrics.
4
4
  It manages the relationships between loops, yarns, and structural elements like courses and wales.
5
5
  """
6
+
6
7
  from __future__ import annotations
7
8
 
8
- from typing import Any, Iterator, cast
9
+ from collections.abc import Iterator
10
+ from typing import Any, cast
9
11
 
10
12
  from networkx import DiGraph
11
13
 
@@ -18,9 +20,6 @@ from knit_graphs.Loop import Loop
18
20
  from knit_graphs.Pull_Direction import Pull_Direction
19
21
  from knit_graphs.Yarn import Yarn
20
22
 
21
- # from knit_graphs.artin_wale_braids.Wale import Wale
22
- # from knit_graphs.artin_wale_braids.Wale_Group import Wale_Group
23
-
24
23
 
25
24
  class Knit_Graph:
26
25
  """A representation of knitted structures as connections between loops on yarns.
@@ -77,6 +76,42 @@ class Knit_Graph:
77
76
  if self._last_loop is None or loop > self._last_loop:
78
77
  self._last_loop = loop
79
78
 
79
+ def remove_loop(self, loop: Loop) -> None:
80
+ """
81
+ Remove the given loop from the knit graph.
82
+ Args:
83
+ loop (Loop): The loop to be removed.
84
+
85
+ Raises:
86
+ KeyError: If the loop is not in the knit graph.
87
+
88
+ """
89
+ if loop not in self:
90
+ raise KeyError(f"Loop {loop} not on the knit graph")
91
+ self.braid_graph.remove_loop(loop) # remove any crossing associated with this loop.
92
+ # Remove any stitch edges involving this loop.
93
+ loop.remove_parent_loops()
94
+ if self.has_child_loop(loop):
95
+ child_loop = self.get_child_loop(loop)
96
+ assert isinstance(child_loop, Loop)
97
+ child_loop.remove_parent(loop)
98
+ self.stitch_graph.remove_node(loop)
99
+ # Remove loop from any floating positions
100
+ loop.remove_loop_from_front_floats()
101
+ loop.remove_loop_from_back_floats()
102
+ # Remove loop from yarn
103
+ yarn = loop.yarn
104
+ yarn.remove_loop(loop)
105
+ if len(yarn) == 0: # This was the only loop on that yarn
106
+ self.yarns.discard(yarn)
107
+ # Reset last loop
108
+ if loop is self.last_loop:
109
+ if len(self.yarns) == 0: # No loops left
110
+ assert len(self.stitch_graph.nodes) == 0
111
+ self._last_loop = None
112
+ else: # Set to the newest loop formed at the end of any yarns.
113
+ self._last_loop = max(y.last_loop for y in self.yarns if isinstance(y.last_loop, Loop))
114
+
80
115
  def add_yarn(self, yarn: Yarn) -> None:
81
116
  """Add a yarn to the graph without adding its loops.
82
117
 
@@ -85,9 +120,13 @@ class Knit_Graph:
85
120
  """
86
121
  self.yarns.add(yarn)
87
122
 
88
- def connect_loops(self, parent_loop: Loop, child_loop: Loop,
89
- pull_direction: Pull_Direction = Pull_Direction.BtF,
90
- stack_position: int | None = None) -> None:
123
+ def connect_loops(
124
+ self,
125
+ parent_loop: Loop,
126
+ child_loop: Loop,
127
+ pull_direction: Pull_Direction = Pull_Direction.BtF,
128
+ stack_position: int | None = None,
129
+ ) -> None:
91
130
  """Create a stitch edge by connecting a parent and child loop.
92
131
 
93
132
  Args:
@@ -106,44 +145,31 @@ class Knit_Graph:
106
145
  self.stitch_graph.add_edge(parent_loop, child_loop, pull_direction=pull_direction)
107
146
  child_loop.add_parent_loop(parent_loop, stack_position)
108
147
 
109
- def get_wale_starting_with_loop(self, first_loop: Loop) -> Wale:
110
- """Get a wale (vertical column of stitches) starting from the specified loop.
148
+ def get_wales_ending_with_loop(self, last_loop: Loop) -> set[Wale]:
149
+ """Get all wales (vertical columns of stitches) that end at the specified loop.
111
150
 
112
151
  Args:
113
- first_loop (Loop): The loop at the start of the wale to be constructed.
152
+ last_loop (Loop): The last loop of the joined set of wales.
114
153
 
115
154
  Returns:
116
- Wale: A wale object representing the vertical column of stitches starting from the given loop.
155
+ set[Wale]: The set of wales that end at this loop.
117
156
  """
118
- wale = Wale(first_loop)
119
- cur_loop = first_loop
120
- while len(self.stitch_graph.successors(cur_loop)) == 1:
121
- cur_loop = [*self.stitch_graph.successors(cur_loop)][0]
122
- assert isinstance(wale.last_loop, Loop)
123
- wale.add_loop_to_end(cur_loop, self.get_pull_direction(wale.last_loop, cur_loop))
124
- return wale
125
-
126
- def get_wales_ending_with_loop(self, last_loop: Loop) -> list[Wale]:
127
- """Get all wales (vertical columns of stitches) that end at the specified loop.
157
+ if len(last_loop.parent_loops) == 0:
158
+ return {Wale(last_loop, self)}
159
+ ancestors = last_loop.ancestor_loops()
160
+ return {Wale(ancestor_loop, self) for ancestor_loop in ancestors}
128
161
 
129
- Args:
130
- last_loop (Loop): The last loop of the joined set of wales.
162
+ def get_terminal_wales(self) -> dict[Loop, list[Wale]]:
163
+ """
164
+ Get wale groups organized by their terminal loops.
131
165
 
132
166
  Returns:
133
- 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.
167
+ dict[Loop, list[Wale]]: Dictionary mapping terminal loops to list of wales that terminate that wale.
134
168
  """
135
- wales = []
136
- if len(last_loop.parent_loops) == 0:
137
- return [Wale(last_loop)]
138
- for top_stitch_parent in last_loop.parent_loops:
139
- wale = Wale(last_loop)
140
- wale.add_loop_to_beginning(top_stitch_parent, cast(Pull_Direction, self.get_pull_direction(top_stitch_parent, last_loop)))
141
- cur_loop = top_stitch_parent
142
- while len(cur_loop.parent_loops) == 1: # stop at split for decrease or start of wale
143
- cur_loop = cur_loop.parent_loops[0]
144
- wale.add_loop_to_beginning(cur_loop, cast(Pull_Direction, self.get_pull_direction(cur_loop, cast(Loop, wale.first_loop))))
145
- wales.append(wale)
146
- return wales
169
+ wale_groups = {}
170
+ for loop in self.terminal_loops():
171
+ wale_groups[loop] = list(self.get_wales_ending_with_loop(loop))
172
+ return wale_groups
147
173
 
148
174
  def get_courses(self) -> list[Course]:
149
175
  """Get all courses (horizontal rows) in the knit graph in chronological order.
@@ -164,17 +190,13 @@ class Knit_Graph:
164
190
  courses.append(course)
165
191
  return courses
166
192
 
167
- def get_wale_groups(self) -> dict[Loop, Wale_Group]:
193
+ def get_wale_groups(self) -> set[Wale_Group]:
168
194
  """Get wale groups organized by their terminal loops.
169
195
 
170
196
  Returns:
171
- 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.
197
+ 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.
172
198
  """
173
- wale_groups = {}
174
- for loop in self:
175
- if self.is_terminal_loop(loop):
176
- wale_groups.update({loop: Wale_Group(wale, self) for wale in self.get_wales_ending_with_loop(loop)})
177
- return wale_groups
199
+ return {Wale_Group(terminal_loop, self) for terminal_loop in self.terminal_loops()}
178
200
 
179
201
  def __contains__(self, item: Loop | tuple[Loop, Loop]) -> bool:
180
202
  """Check if a loop is contained in the knit graph.
@@ -197,12 +219,18 @@ class Knit_Graph:
197
219
  """
198
220
  return cast(Iterator[Loop], iter(self.stitch_graph.nodes))
199
221
 
222
+ def __getitem__(self, item: int) -> Loop:
223
+ loop = next((loop for loop in self if loop.loop_id == item), None)
224
+ if loop is None:
225
+ raise KeyError(f"Loop of id {item} not in knit graph")
226
+ return loop
227
+
200
228
  def sorted_loops(self) -> list[Loop]:
201
229
  """
202
230
  Returns:
203
231
  list[Loop]: The list of loops in the stitch graph sorted from the earliest formed loop to the latest formed loop.
204
232
  """
205
- return sorted(list(self.stitch_graph.nodes))
233
+ return sorted(self.stitch_graph.nodes)
206
234
 
207
235
  def get_pull_direction(self, parent: Loop, child: Loop) -> Pull_Direction | None:
208
236
  """Get the pull direction of the stitch edge between parent and child loops.
@@ -218,7 +246,7 @@ class Knit_Graph:
218
246
  if edge is None:
219
247
  return None
220
248
  else:
221
- return cast(Pull_Direction, edge['pull_direction'])
249
+ return cast(Pull_Direction, edge["pull_direction"])
222
250
 
223
251
  def get_stitch_edge(self, parent: Loop, child: Loop) -> dict[str, Any] | None:
224
252
  """Get the stitch edge data between two loops.
@@ -270,3 +298,10 @@ class Knit_Graph:
270
298
  bool: True if the loop has no child loops and terminates a wale, False otherwise.
271
299
  """
272
300
  return not self.has_child_loop(loop)
301
+
302
+ def terminal_loops(self) -> Iterator[Loop]:
303
+ """
304
+ Returns:
305
+ Iterator[Loop]: An iterator over all terminal loops in the knit graph.
306
+ """
307
+ return iter(loop for loop in self if self.is_terminal_loop(loop))