LinAlgKit Tutorial¶
A comprehensive guide to using LinAlgKit for linear algebra operations in Python.
Table of Contents¶
- Installation
- Getting Started
- Creating Matrices
- Basic Operations
- Matrix Analysis
- Working with NumPy
- Practical Examples
- Tips and Best Practices
Installation¶
From PyPI (Recommended)¶
pip install LinAlgKit
From Source¶
git clone https://github.com/SciComputeOrg/LinAlgKit.git
cd LinAlgKit
pip install -e .
Verify Installation¶
import LinAlgKit as lk
print(f"LinAlgKit version: {lk.__version__}")
print(f"Backend: {lk.BACKEND}")
Getting Started¶
LinAlgKit provides a clean, Pythonic interface for matrix operations:
import LinAlgKit as lk
import numpy as np
# Create a 2x2 matrix
A = lk.Matrix.from_numpy([[1, 2], [3, 4]])
# Create an identity matrix
I = lk.Matrix.identity(2)
# Perform operations
result = A + I
print(result.to_numpy())
# [[2. 2.]
# [3. 5.]]
Creating Matrices¶
Method 1: From Dimensions¶
Create a matrix with specified dimensions and optional fill value:
import LinAlgKit as lk
# 3x3 zero matrix
zeros = lk.Matrix(3, 3)
print(zeros.to_numpy())
# [[0. 0. 0.]
# [0. 0. 0.]
# [0. 0. 0.]]
# 2x4 matrix filled with 5.0
filled = lk.Matrix(2, 4, 5.0)
print(filled.to_numpy())
# [[5. 5. 5. 5.]
# [5. 5. 5. 5.]]
Method 2: From NumPy Arrays¶
Import existing NumPy arrays directly:
import numpy as np
import LinAlgKit as lk
# From a NumPy array
arr = np.array([[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0],
[7.0, 8.0, 9.0]])
A = lk.Matrix.from_numpy(arr)
# From a Python list (converted automatically)
B = lk.Matrix.from_numpy([[1, 2], [3, 4]])
Method 3: Static Constructors¶
Use convenient factory methods:
import LinAlgKit as lk
# Identity matrix
I = lk.Matrix.identity(3)
print(I.to_numpy())
# [[1. 0. 0.]
# [0. 1. 0.]
# [0. 0. 1.]]
# Zero matrix
Z = lk.Matrix.zeros(2, 3)
print(Z.to_numpy())
# [[0. 0. 0.]
# [0. 0. 0.]]
# Ones matrix
O = lk.Matrix.ones(2, 3)
print(O.to_numpy())
# [[1. 1. 1.]
# [1. 1. 1.]]
Choosing the Right Type¶
LinAlgKit offers three matrix types:
import LinAlgKit as lk
import numpy as np
# Matrix - double precision (float64) - default choice
A = lk.Matrix.from_numpy([[1.0, 2.0], [3.0, 4.0]])
# MatrixF - single precision (float32) - memory efficient
B = lk.MatrixF.from_numpy(np.array([[1, 2], [3, 4]], dtype=np.float32))
# MatrixI - integer - for discrete math
C = lk.MatrixI.from_numpy([[1, 0], [0, 1]])
| Type | When to Use |
|---|---|
Matrix |
General purpose, high precision needed |
MatrixF |
Large matrices, GPU compatibility |
MatrixI |
Graph algorithms, counting, indexing |
Basic Operations¶
Matrix Addition and Subtraction¶
import LinAlgKit as lk
A = lk.Matrix.from_numpy([[1, 2], [3, 4]])
B = lk.Matrix.from_numpy([[5, 6], [7, 8]])
# Addition
C = A + B
print("A + B =")
print(C.to_numpy())
# [[ 6. 8.]
# [10. 12.]]
# Subtraction
D = A - B
print("A - B =")
print(D.to_numpy())
# [[-4. -4.]
# [-4. -4.]]
Matrix Multiplication¶
import LinAlgKit as lk
A = lk.Matrix.from_numpy([[1, 2], [3, 4]])
B = lk.Matrix.from_numpy([[5, 6], [7, 8]])
# Matrix multiplication (A @ B)
C = A * B
print("A × B =")
print(C.to_numpy())
# [[19. 22.]
# [43. 50.]]
Scalar Multiplication¶
import LinAlgKit as lk
A = lk.Matrix.from_numpy([[1, 2], [3, 4]])
# Scalar on right
B = A * 3
print("A × 3 =")
print(B.to_numpy())
# [[ 3. 6.]
# [ 9. 12.]]
# Scalar on left
C = 2 * A
print("2 × A =")
print(C.to_numpy())
# [[2. 4.]
# [6. 8.]]
Transpose¶
import LinAlgKit as lk
A = lk.Matrix.from_numpy([[1, 2, 3],
[4, 5, 6]])
print("A =")
print(A.to_numpy())
# [[1. 2. 3.]
# [4. 5. 6.]]
AT = A.transpose()
print("A^T =")
print(AT.to_numpy())
# [[1. 4.]
# [2. 5.]
# [3. 6.]]
Matrix Analysis¶
Trace¶
The trace is the sum of diagonal elements:
import LinAlgKit as lk
A = lk.Matrix.from_numpy([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
tr = A.trace()
print(f"trace(A) = {tr}") # 15.0 (1 + 5 + 9)
Determinant¶
The determinant indicates whether a matrix is invertible:
import LinAlgKit as lk
# Invertible matrix (det ≠ 0)
A = lk.Matrix.from_numpy([[1, 2], [3, 4]])
print(f"det(A) = {A.determinant()}") # -2.0
# Singular matrix (det = 0)
B = lk.Matrix.from_numpy([[1, 2], [2, 4]])
print(f"det(B) = {B.determinant()}") # 0.0
# 3x3 matrix
C = lk.Matrix.from_numpy([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
print(f"det(C) = {C.determinant()}") # ~0.0 (singular)
Element Access¶
import LinAlgKit as lk
A = lk.Matrix.from_numpy([[1, 2, 3],
[4, 5, 6]])
# Get element (0-indexed)
print(A[0, 0]) # 1.0
print(A[1, 2]) # 6.0
# Set element
A[0, 0] = 10.0
print(A.to_numpy())
# [[10. 2. 3.]
# [ 4. 5. 6.]]
Matrix Properties¶
import LinAlgKit as lk
A = lk.Matrix.from_numpy([[1, 2, 3],
[4, 5, 6]])
print(f"Rows: {A.rows}") # 2
print(f"Columns: {A.cols}") # 3
Working with NumPy¶
Converting Between LinAlgKit and NumPy¶
import numpy as np
import LinAlgKit as lk
# NumPy → LinAlgKit
np_array = np.random.rand(3, 3)
lk_matrix = lk.Matrix.from_numpy(np_array)
# LinAlgKit → NumPy
back_to_numpy = lk_matrix.to_numpy()
# Verify
print(np.allclose(np_array, back_to_numpy)) # True
Using LinAlgKit with NumPy Functions¶
import numpy as np
import LinAlgKit as lk
A = lk.Matrix.from_numpy([[4, -2], [-2, 4]])
# Use NumPy's advanced functions on LinAlgKit matrices
eigenvalues = np.linalg.eigvals(A.to_numpy())
print(f"Eigenvalues: {eigenvalues}") # [6. 2.]
rank = np.linalg.matrix_rank(A.to_numpy())
print(f"Rank: {rank}") # 2
Functional API¶
LinAlgKit provides NumPy-compatible functional helpers:
from LinAlgKit import array, zeros, ones, eye, matmul, transpose, trace, det
# Create arrays
A = array([[1, 2], [3, 4]])
I = eye(2)
Z = zeros((3, 3))
# Operations
B = matmul(A, I)
print(transpose(A))
print(f"trace: {trace(A)}")
print(f"det: {det(A)}")
Practical Examples¶
Example 1: Computing Powers of a Matrix¶
import LinAlgKit as lk
def matrix_power(A, n):
"""Compute A^n"""
result = lk.Matrix.identity(A.rows)
for _ in range(n):
result = result * A
return result
A = lk.Matrix.from_numpy([[1, 1], [1, 0]])
A5 = matrix_power(A, 5)
print("A^5 =")
print(A5.to_numpy())
# [[8. 5.]
# [5. 3.]] - Fibonacci numbers!
Example 2: Gram-Schmidt Orthogonalization¶
import numpy as np
import LinAlgKit as lk
def gram_schmidt(A):
"""Orthogonalize columns of A"""
m, n = A.rows, A.cols
Q = lk.Matrix.zeros(m, n)
np_A = A.to_numpy()
np_Q = Q.to_numpy()
for j in range(n):
v = np_A[:, j].copy()
for i in range(j):
v -= np.dot(np_Q[:, i], np_A[:, j]) * np_Q[:, i]
np_Q[:, j] = v / np.linalg.norm(v)
return lk.Matrix.from_numpy(np_Q)
A = lk.Matrix.from_numpy([[1, 1], [0, 1], [1, 0]])
Q = gram_schmidt(A)
print("Orthogonal Q:")
print(Q.to_numpy())
Example 3: Checking Matrix Properties¶
import numpy as np
import LinAlgKit as lk
def analyze_matrix(A):
"""Print various properties of a matrix"""
np_A = A.to_numpy()
print(f"Shape: {A.rows} × {A.cols}")
print(f"Trace: {A.trace()}")
if A.rows == A.cols:
print(f"Determinant: {A.determinant()}")
# Check symmetry
AT = A.transpose().to_numpy()
is_symmetric = np.allclose(np_A, AT)
print(f"Symmetric: {is_symmetric}")
# Check if identity
I = np.eye(A.rows)
is_identity = np.allclose(np_A, I)
print(f"Is Identity: {is_identity}")
# Analyze a matrix
A = lk.Matrix.from_numpy([[4, -2, 0],
[-2, 4, -2],
[0, -2, 4]])
analyze_matrix(A)
Example 4: Building a Transformation Matrix¶
import numpy as np
import LinAlgKit as lk
def rotation_matrix_2d(theta):
"""Create a 2D rotation matrix for angle theta (radians)"""
c, s = np.cos(theta), np.sin(theta)
return lk.Matrix.from_numpy([[c, -s], [s, c]])
def scale_matrix_2d(sx, sy):
"""Create a 2D scaling matrix"""
return lk.Matrix.from_numpy([[sx, 0], [0, sy]])
# 45-degree rotation
R = rotation_matrix_2d(np.pi / 4)
print("Rotation (45°):")
print(R.to_numpy())
# Scale by 2x horizontally, 0.5x vertically
S = scale_matrix_2d(2, 0.5)
print("Scale:")
print(S.to_numpy())
# Combined transformation (scale then rotate)
T = R * S
print("Combined:")
print(T.to_numpy())
Tips and Best Practices¶
1. Use Static Constructors for Common Matrices¶
# Good - clear and efficient
I = lk.Matrix.identity(3)
Z = lk.Matrix.zeros(2, 4)
# Less clear
I = lk.Matrix.from_numpy(np.eye(3))
Z = lk.Matrix(2, 4, 0)
2. Check Dimensions Before Operations¶
def safe_multiply(A, B):
if A.cols != B.rows:
raise ValueError(f"Cannot multiply {A.rows}×{A.cols} by {B.rows}×{B.cols}")
return A * B
3. Use the Right Matrix Type¶
# For precision-critical calculations
A = lk.Matrix.from_numpy(precise_data) # float64
# For large datasets where memory matters
B = lk.MatrixF.from_numpy(large_data) # float32
# For counting/graph operations
C = lk.MatrixI.from_numpy(adjacency) # int
4. Leverage NumPy for Advanced Operations¶
LinAlgKit focuses on the essentials. For advanced operations, convert to NumPy:
import numpy as np
import LinAlgKit as lk
A = lk.Matrix.from_numpy([[1, 2], [3, 4]])
# SVD (not in LinAlgKit yet)
U, S, Vt = np.linalg.svd(A.to_numpy())
# Eigendecomposition
eigenvalues, eigenvectors = np.linalg.eig(A.to_numpy())
5. Remember Copy Semantics¶
from_numpy() and to_numpy() both create copies:
arr = np.array([[1, 2], [3, 4]])
A = lk.Matrix.from_numpy(arr)
# Modifying arr does NOT affect A
arr[0, 0] = 999
print(A[0, 0]) # Still 1.0
# Modifying the returned array does NOT affect A
result = A.to_numpy()
result[0, 0] = 888
print(A[0, 0]) # Still 1.0
Next Steps¶
- Read the full API Reference for detailed method documentation
- Check the Performance Guide for optimization tips
- See FUTURE_UPDATES.md for upcoming features
- Contribute on GitHub
Happy computing! 🧮