Matrices
A matrix is a rectangular grid of numbers. In panchi, Matrix represents an m×n matrix and supports the arithmetic and structural operations you would expect from a mathematical matrix.
Construction
import panchi as pan
A = pan.Matrix([[1, 2, 3],
[4, 5, 6]])
print(A.shape) # (2, 3)
print(A.rows) # 2
print(A.cols) # 3
Every row must have the same number of columns. Inconsistent rows raise a ValueError.
Matrices accept fractions as strings:
A = pan.Matrix([["1/2", "1/3"],
["1/4", "1/5"]])
See Exact Arithmetic for more on working with rational numbers.
Arithmetic
A = pan.Matrix([[1, 2], [3, 4]])
B = pan.Matrix([[5, 6], [7, 8]])
print(A + B) # element-wise addition
print(A - B) # element-wise subtraction
print(2 * A) # scalar multiplication
print(A * 2) # same as above
print(-A) # negation
Matrix multiplication
Matrix multiplication uses the @ operator, following Python convention:
print(A @ B) # [[19, 22], [43, 50]]
@ is used for matrix–matrix and matrix–vector multiplication. * is reserved for scalar multiplication only.
Multiplying a matrix by a vector also uses @ and returns a Vector:
v = pan.Vector([1, 0])
print(A @ v) # [1, 3]
Geometrically, multiplying by a matrix is a transformation of space. The matrix below stretches the plane by 2 along x and 3 along y — every point moves, and the basis vectors land on the matrix's columns:
Matrix([[2, 0], [0, 3]]) acting on the plane. See Linear Transformations for more.Matrix powers
print(A ** 2) # A @ A
print(A ** 0) # identity matrix of matching size
Powers are only defined for square matrices.
Transpose
print(A.T) # shorthand property
print(A.transpose()) # equivalent method
Properties
A = pan.Matrix([[1, 2], [3, 4]])
print(A.trace) # 5 — sum of diagonal (square only)
print(A.is_square) # True
The determinant is a free function (like solve, inverse, and determinant_lu), not a
property — it is an algorithm, so it lives in panchi.algorithms rather than on the matrix:
print(pan.determinant(A)) # -2 — cofactor expansion, exact (square only)
Rank, invertibility, and symmetry
Rank and its companions are free functions too. rank counts the linearly independent rows
(equivalently columns) of a matrix; nullity is the dimension of its null space; together they
satisfy the rank–nullity theorem, rank(A) + nullity(A) == A.cols:
A = pan.Matrix([[1, 2, 3], [4, 5, 6]])
print(pan.rank(A)) # 2
print(pan.nullity(A)) # 1 — and 2 + 1 == A.cols
is_invertible and is_symmetric answer the two most common structural questions. is_invertible
is decided by rank (square and full rank), which avoids the O(n!) cofactor determinant:
print(pan.is_invertible(pan.Matrix([[1, 2], [3, 4]]))) # True
print(pan.is_invertible(pan.Matrix([[1, 2], [2, 4]]))) # False — singular
print(pan.is_symmetric(pan.Matrix([[1, 2], [2, 1]]))) # True
rank is deliberately one function for both matrices and vector spaces — a subspace's rank is
the rank of its generating matrix, so rank(pan.column_space(A)) == rank(A).
Identity matrices
Every matrix has a left and right identity — the square identity matrices of the appropriate size for multiplication on each side:
A = pan.Matrix([[1, 2, 3], [4, 5, 6]]) # 2×3
print(A.left_identity) # 2×2 identity
print(A.right_identity) # 3×3 identity
# These satisfy:
assert A.left_identity @ A == A
assert A @ A.right_identity == A
Indexing and element access
Index a single element with a (row, column) pair, and assign to it the same way. Element assignment is validated exactly like the constructor — string fractions are parsed, and non-numbers are rejected:
A = pan.Matrix([[1, 2], [3, 4]])
A[0, 1] # 2 — read one element
A[1, 0] = "1/2" # write one element (parsed to Fraction(1, 2))
A[-1, -1] # 4 — negative indices work
Indexing a single row returns a copy of that row, so mutating it never changes the matrix — assign single elements through A[i, j] = value instead:
A[0] # [1, 2] — a copy of row 0
A[0][0] = 99 # modifies the copy, NOT the matrix
A[0, 0] # still 1
Row and column access
row_vectors and col_vectors mirror the mathematical operators Row(A) and Col(A), returning the row and column vectors of a matrix respectively.
A = pan.Matrix([[1, 2, 3], [4, 5, 6]])
A.row_vectors # [Vector([1, 2, 3]), Vector([4, 5, 6])]
A.col_vectors # [Vector([1, 4]), Vector([2, 5]), Vector([3, 6])]
A.row_vectors[0] # Vector([1, 2, 3])
A.col_vectors[1] # Vector([2, 5])
Both properties return copies — modifying the result does not affect the original matrix.
The columns of a matrix are vectors in their own right. Here are the two columns of A = [[1, 2], [3, 4]] — col 1 = (1, 3) and col 2 = (2, 4) — drawn as arrows:
A = [[1, 2], [3, 4]], plotted with Animator2D.plot_vectors.Factory functions
pan.identity(3) # 3×3 identity
pan.zero_matrix(2, 3) # 2×3 matrix of zeros
pan.one_matrix(2, 3) # 2×3 matrix of ones
pan.diagonal([1, 2, 3]) # 3×3 diagonal matrix
pan.random_matrix(3, 3) # random entries
Conversion and copying
A.to_list() # returns a 2D list copy of the data
A.copy() # returns an independent Matrix copy