Skip to content

Commit 6a4588f

Browse files
committed
Adding documentation for power method
1 parent 74f4cb3 commit 6a4588f

File tree

6 files changed

+180
-11
lines changed

6 files changed

+180
-11
lines changed

.github/workflows/docs.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,12 @@ jobs:
4444
uses: actions/upload-artifact@v4
4545
with:
4646
name: documentation
47-
path: docs/html/
47+
path: docs/_build/html/
4848

4949
- name: Deploy
50-
if: success() && github.ref == 'refs/heads/main'
50+
if: success()
5151
uses: peaceiris/actions-gh-pages@v3
5252
with:
5353
github_token: ${{ secrets.GITHUB_TOKEN }}
54-
publish_dir: docs/html
54+
publish_dir: docs/_build/html
5555
allow_empty_commit: true

docs/conf.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@
66
# -- Project information -----------------------------------------------------
77
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
88

9+
import os
10+
import sys
11+
12+
sys.path.insert(0, os.path.abspath("../src"))
13+
914
project = "final_project"
1015
copyright = "2025, Gaspare Li Causi, Lorenzo Tomada"
1116
author = "Gaspare Li Causi, Lorenzo Tomada"
@@ -14,7 +19,12 @@
1419
# -- General configuration ---------------------------------------------------
1520
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
1621

17-
extensions = []
22+
# here are a few extensions that are not strictly necessary, but might help:
23+
extensions = [
24+
"sphinx.ext.autodoc",
25+
"sphinx.ext.napoleon",
26+
"sphinx.ext.autosummary",
27+
]
1828

1929
templates_path = ["_templates"]
2030
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]

docs/index.rst

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,24 @@ Welcome to the documentation for the final project of the course in Development
1010
We are currently working on the project and on the documentation.
1111
Stay tuned!
1212

13-
Add your content using ``reStructuredText`` syntax. See the
13+
Recall that the documentation is written following
1414
`reStructuredText <https://www.sphinx-doc.org/en/master/usage/restructuredtext/index.html>`_
15-
documentation for details.
1615

1716

17+
Table of Contents
18+
=================
19+
1820
.. toctree::
1921
:maxdepth: 2
20-
:caption: Contents:
22+
:caption: Contents:
23+
24+
pyclassify
25+
26+
Module Documentation
27+
====================
28+
29+
.. automodule:: pyclassify
30+
:members:
31+
:undoc-members:
32+
:show-inheritance:
33+
:noindex:

docs/pyclassify.rst

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
PyClassify Module Documentation
2+
===============================
3+
4+
This module contains all the functions for the PyClassify package.
5+
6+
Functions:
7+
==========
8+
9+
.. automodule:: pyclassify
10+
:members:
11+
:undoc-members:
12+
:show-inheritance:
13+
14+
Submodule: utils
15+
================
16+
17+
.. automodule:: pyclassify.utils
18+
:members:
19+
:undoc-members:
20+
:show-inheritance:

src/pyclassify/eigenvalues.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,52 @@
77

88
@profile
99
def eigenvalues_np(A, symmetric=True):
10+
"""
11+
Compute the eigenvalues of a square matrix using NumPy's `eig` or `eigh` function.
12+
13+
This function checks if the input matrix is square (and is actually a matrix) using 'check_A_square_matrix', and then computes its eigenvalues.
14+
If the matrix is symmetric, it uses `eigh` (which is more efficient for symmetric matrices).
15+
Otherwise, it uses `eig`.
16+
17+
Args:
18+
A (np.ndarray): A square matrix whose eigenvalues are to be computed.
19+
symmetric (bool, optional): If True, assumes the matrix is symmetric and uses `eigh` for
20+
faster computation. If False, uses `eig` for general matrices
21+
(default is True).
22+
23+
Returns:
24+
np.ndarray: An array containing the eigenvalues of the matrix `A`.
25+
26+
Raises:
27+
TypeError: If the input is not a NumPy array or a SciPy sparse matrix.
28+
ValueError: If number of rows != number of columns.
29+
"""
1030
check_A_square_matrix(A)
1131
eigenvalues, _ = eigh(A) if symmetric else eig(A)
1232
return eigenvalues
1333

1434

1535
@profile
1636
def eigenvalues_sp(A, symmetric=True):
37+
"""
38+
Compute the eigenvalues of a sparse matrix using SciPy's `eigsh` or `eigs` function.
39+
40+
This function checks if the input matrix is square, then computes its eigenvalues using
41+
SciPy's sparse linear algebra solvers. For symmetric matrices, it uses `eigsh` for
42+
more efficient computation. For non-symmetric matrices, it uses `eigs`.
43+
44+
Args:
45+
A (sp.spmatrix): A square sparse matrix whose eigenvalues are to be computed.
46+
symmetric (bool, optional): If True, assumes the matrix is symmetric and uses `eigsh`.
47+
If False, uses `eigs` for general matrices (default is True).
48+
49+
Returns:
50+
np.ndarray: An array containing the eigenvalues of the sparse matrix `A`.
51+
52+
Raises:
53+
TypeError: If the input is not a NumPy array or a SciPy sparse matrix.
54+
ValueError: If number of rows != number of columns.
55+
"""
1756
check_A_square_matrix(A)
1857
eigenvalues, _ = (
1958
sp.linalg.eigsh(A, k=A.shape[0] - 1)
@@ -25,6 +64,23 @@ def eigenvalues_sp(A, symmetric=True):
2564

2665
@profile
2766
def power_method(A, max_iter=500, tol=1e-4, x=None):
67+
"""
68+
Compute the dominant eigenvalue of a square matrix using the power method.
69+
70+
Args:
71+
A (np.ndarray or sp.spmatrix): A square matrix whose dominant eigenvalue is to be computed.
72+
max_iter (int, optional): Maximum number of iterations to perform (default is 500).
73+
tol (float, optional): Tolerance for convergence based on the relative change between iterations
74+
(default is 1e-4).
75+
x (np.ndarray, optional): Initial guess for the eigenvector. If None, a random vector is generated.
76+
77+
Returns:
78+
float: The approximated dominant eigenvalue of the matrix `A`, computed as the Rayleigh quotient x @ A @ x.
79+
80+
Raises:
81+
TypeError: If the input is not a NumPy array or a SciPy sparse matrix.
82+
ValueError: If number of rows != number of columns.
83+
"""
2884
check_A_square_matrix(A)
2985
if x is None:
3086
x = np.random.rand(A.shape[0])
@@ -46,4 +102,20 @@ def power_method(A, max_iter=500, tol=1e-4, x=None):
46102

47103
@profile
48104
def power_method_numba(A):
105+
"""
106+
Compute the dominant eigenvalue of a matrix using the power method, with Numba optimization.
107+
108+
This function wraps the `power_method` function and applies Numba's Just-In-Time (JIT) compilation
109+
to optimize the performance of the power method for large matrices. The function leverages the
110+
`power_method_numba_helper` function for the actual computation. The reason for that is the following: profiling
111+
directly a function decorated with @numba.jit does not actually keep track of the calls and execution time due to
112+
numba technicalities. Therefore, the helper function is profiled instead.
113+
Remark that numba does not support scipy sparse matrices, so the input matrix must be a NumPy array.
114+
115+
Args:
116+
A (np.ndarray): A square matrix whose dominant eigenvalue is to be computed.
117+
118+
Returns:
119+
float: The approximated dominant eigenvalue of the matrix `A`.
120+
"""
49121
return power_method_numba_helper(A)

src/pyclassify/utils.py

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,19 @@
99

1010

1111
def check_A_square_matrix(A):
12+
"""
13+
Checks if the input matrix is a square matrix of type NumPy ndarray or SciPy sparse matrix.
14+
This is done to ensure that the input matrix `A` is both:
15+
1. Of type `np.ndarray` (NumPy array) or `scipy.sparse` (SciPy sparse matrix).
16+
2. A square matrix.
17+
18+
Args:
19+
A (np.ndarray or sp.spmatrix): The matrix to be checked.
20+
21+
Raises:
22+
TypeError: If the input is not a NumPy array or a SciPy sparse matrix.
23+
ValueError: If number of rows != number of columns.
24+
"""
1225
if not isinstance(A, (np.ndarray, sp.spmatrix)):
1326
raise TypeError("Input matrix must be a NumPy array or a SciPy sparse matrix!")
1427
if A.shape[0] != A.shape[1]:
@@ -17,13 +30,49 @@ def check_A_square_matrix(A):
1730

1831
@profile
1932
def make_symmetric(A):
33+
"""
34+
Ensures the input matrix is symmetric by averaging it with its transpose.
35+
36+
This function first checks if the matrix is square using the `check_A_square_matrix` function.
37+
Then, it makes the matrix symmetric by averaging it with its transpose.
38+
39+
Args:
40+
A (np.ndarray or sp.spmatrix): The input square matrix to be made symmetric.
41+
42+
Returns:
43+
np.ndarray or sp.spmatrix: The symmetric version of the input matrix.
44+
45+
Raises:
46+
TypeError: If the input matrix is not a NumPy array or SciPy sparse matrix.
47+
ValueError: If the input matrix is not square.
48+
"""
2049
check_A_square_matrix(A)
2150
A_sym = (A + A.T) / 2
2251
return A_sym
2352

2453

2554
@numba.njit(nogil=True, parallel=True)
2655
def power_method_numba_helper(A, max_iter=500, tol=1e-4, x=None):
56+
"""
57+
Approximate the dominant eigenvalue of a square matrix using the power method.
58+
59+
This helper function applies the power method to a matrix A to estimate its dominant eigenvalue.
60+
It returns the Rayleigh quotient, x @ A @ x, which serves as an approximation of the dominant eigenvalue.
61+
62+
The function is optimized with Numba using the 'njit' decorator with nogil and parallel options.
63+
64+
Args:
65+
A (np.ndarray): A square matrix.
66+
max_iter (int, optional): Maximum number of iterations to perform (default is 500).
67+
tol (float, optional): Tolerance for convergence based on the relative change between iterations (default is 1e-4).
68+
x (np.ndarray, optional): Initial guess for the eigenvector. If None, a random vector is generated.
69+
70+
Returns:
71+
float: The approximated dominant eigenvalue of the matrix A.
72+
73+
Raises:
74+
ValueError: If the input matrix A is not square. The check is not done using 'check_A_square_matrix' because of numba technicalities.
75+
"""
2776
if A.shape[0] != A.shape[1]:
2877
raise ValueError("Matrix must be square!") # explain why re-written
2978
if x is None:
@@ -46,11 +95,16 @@ def power_method_numba_helper(A, max_iter=500, tol=1e-4, x=None):
4695

4796
def read_config(file: str) -> dict:
4897
"""
49-
To read the desired configuration file, passed in input as a string
50-
Input:
51-
file: str (representing the location of file)
98+
Reads a YAML configuration file and returns its contents as a dictionary.
99+
100+
This function constructs the absolute path to a YAML file (by appending the '.yaml' extension
101+
to the provided base file name), opens the file, and parses its content using yaml.safe_load.
102+
103+
Args:
104+
file (str): The base name of the YAML file (without the '.yaml' extension).
105+
52106
Returns:
53-
dict (containing the configuration parameters needed)
107+
dict: A dictionary containing the configuration parameters loaded from the YAML file.
54108
"""
55109
filepath = os.path.abspath(f"{file}.yaml")
56110
with open(filepath, "r") as stream:

0 commit comments

Comments
 (0)