Skip to main content

Very elegant. Very Lumpy.

Project description

Lumpy

Very elegant. Very Lumpy.

Lumpy is a tiny semantic linear algebra wrapper over NumPy.

NumPy is excellent, but its core abstraction is the n-dimensional array, not the mathematical vector or matrix. Lumpy uses NumPy underneath while enforcing a more linear-algebraic convention:

  • vectors are column vectors
  • matrices are built from columns
  • rows are transposes or row slices
  • projections are onto column spaces
  • subspace tools return bases as columns

Installation

pip install lumpy-la

Basic usage

import lumpy as la

Vectors

v = la.vec(1, 2, 3)

v.shape
# (3, 1)

Vectors are always column vectors.

$$ \mathbf{v}\in \mathbb{R}^{n\times 1} $$

v.T
# array([[1., 2., 3.]])

$$ \mathbf{v}^{T}\in \mathbb{R}^{1\times n} $$

Matrices are built from columns

A = la.mat(
    [1, 2, 3],
    [4, 5, 6]
)

A
# array([[1., 4.],
#        [2., 5.],
#        [3., 6.]])

You can also pass existing column vectors:

u = la.vec(1, 2, 3)
v = la.vec(4, 5, 6)

A = la.mat(u, v) 

Matrices from rows

mat() constructs matrices from columns. Sometimes, though, it is useful to type a matrix in the same visual layout used on paper.

A = la.matt(
    [1, 2, 3],
    [4, 5, 6]
)

A
# array([[1., 2., 3.],
#        [4., 5., 6.]])

matt() preserves the usual visual layout of matrices, while mat() preserves Lumpy’s column-oriented semantics.

Standard basis vectors

Lumpy uses Python-style zero-indexing.

la.e(3, 0)
# array([[1.],
#        [0.],
#        [0.]])

So la.e(n, i) corresponds to the mathematical basis vector $\mathbf{e}_{i+1}\in \mathbb{R}^{n}$ .

Identity

la.eye(3)
# array([[1., 0., 0.],
#        [0., 1., 0.],
#        [0., 0., 1.]])

la.I is the same function — a paper-like alias for eye. It is reachable as la.I, but deliberately not exported by from lumpy import *, so the single-letter name I is never bound in your namespace unless you ask for it.

Basic operations

u = la.vec(1, 2, 3)
v = la.vec(4, 5, 6)

la.inner(u, v)
# array([[32.]])

la.dot(u, v)
# 32.0

la.norm(v)
# 8.774964387392123

inner preserves matrix structure.

$$ \langle \mathbf{u},\mathbf{v} \rangle = [\mathbf{u}\cdot \mathbf{v}]\in \mathbb{C}^{1\times 1} $$

dot returns a scalar.

$$ \mathbf{u} \cdot \mathbf{v} \in \mathbb{C} $$

For real input the result is real, since $\mathbb{R}\subset\mathbb{C}$; over $\mathbb{C}$ both use the conjugate transpose (the Hermitian inner product).

Unit vectors and normalization

v = la.vec(3, 4)

la.unit(v)
# array([[0.6],
#        [0.8]])

For matrices, normalize normalizes each column:

A = la.mat(
    [3, 4],
    [5, 12]
)

la.normalize(A)
# array([[0.6       , 0.38461538],
#        [0.8       , 0.92307692]])

Columns and rows

A = la.mat(
    [1, 2, 3],
    [4, 5, 6]
)

la.col(A, 0)
# array([[1.],
#        [2.],
#        [3.]])

la.row(A, 1)
# array([[2., 5.]])

col preserves column-vector shape.

Projection

Project onto the column space of a matrix:

A = la.mat(
    [1, 0, 0],
    [0, 1, 0]
)

v = la.vec(3, 4, 5)

la.proj(A, v)
# array([[3.],
#        [4.],
#        [0.]])

This projects $\mathbf{v}$ onto the $xy$-plane.

Vector projection is just the rank-one case:

u = la.vec(1, 1, 0)
v = la.vec(2, 3, 4)

la.proj(u, v)

Subspaces

A = la.mat(
    [1, 2],
    [2, 4]
)

la.rank(A)
# 1

la.independent(A)
# False

An orthonormal basis for the column space:

Q = la.orth(A)

Q.T @ Q
# approximately identity

Nullspace basis:

N = la.null(A)

A @ N
# approximately zero

Lumpy returns subspace bases as columns.

A = la.matt(
    [1, 2, 3],
    [2, 4, 6]
)

la.row_space(A)

The left nullspace is the nullspace of $A^{T}$:

$$ \mathrm{Null}\left(A^{T}\right) $$

la.left_null(A)

SVD

By default, Lumpy's SVD returns only rank-relevant singular directions:

A = la.mat(
    [1, 0],
    [2, 0]
)

U, s, Vt = la.svd(A)

For full ambient bases:

U, s, Vt = la.svd(A, full_matrices=True)

Many Lumpy subspace functions (rank, orth, null, proj, svd, …) need to decide which singular values count as nonzero. By default this uses a relative, scale-invariant tolerance — a singular value is treated as zero when it falls below max(s) * eps * max(m, n) (the same rule as numpy.linalg.matrix_rank). Because both the singular values and the cutoff scale with A, the rank decision is unaffected by rescaling:

la.rank(A) == la.rank(1e8 * A)   # True

Pass an explicit number as tol for an absolute cutoff instead:

la.rank(A, tol=1e-9)             # absolute threshold

Geometry

u = la.vec(1, 0)
v = la.vec(0, 1)

la.angle(u, v)
# 1.5707963267948966

la.dist(u, v)
# 1.4142135623730951

Solving systems

Prefer solve() over explicitly computing an inverse.

A = la.matt(
    [2, 0],
    [0, 3]
)

b = la.vec(4, 9)
la.solve(A, b)

# array([[2.],
#        [3.]])

For overdetermined systems, use least squares:

A = la.matt(
    [1, 1],
    [1, 2],
    [1, 3]
)

b = la.vec(1, 2, 2)

la.lstsq(A, b)

Complex vectors and matrices

Lumpy is correct over both ℝ and ℂ. inner, dot, outer, proj, null, row_space, and left_null use the conjugate transpose (adj), which reduces to the ordinary transpose for real input — so real code is unchanged while complex code does the right thing (e.g. dot(u, u) is real and nonnegative).

Constructors auto-upcast: la.vec(1, 2, 3) is float64, la.vec(1+2j, 3) is complex128. Pass dtype=None to defer to NumPy's inference, or an explicit dtype to force one.

Because ⟨u, v⟩ is complex, angle takes a kind:

u = la.vec(1 + 1j, 2)
v = la.vec(1j, 1)

la.angle(u, v)                   # "re": arccos(Re⟨u,v⟩/(‖u‖‖v‖)), range [0, π]
la.angle(u, v, kind="hermitian") # angle between the complex lines, range [0, π/2]
la.angle(u, v, kind="branch")    # "re" for real input, "hermitian" for complex

"re" is the default: it equals the ordinary angle for real input and depends only on the vectors' values, never on whether they are stored as real or complex.

Running tests

python -m pytest

Design principle

Lumpy is not trying to replace NumPy.

It is a small semantic layer for doing linear algebra with conventions that feel closer to the math.

import numpy as np
import lumpy as la

Use NumPy for general arrays.
Use Lumpy when you want vectors, matrices, projections, and subspaces to behave like linear algebra objects.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

lumpy_la-1.0.0.tar.gz (14.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

lumpy_la-1.0.0-py3-none-any.whl (11.3 kB view details)

Uploaded Python 3

File details

Details for the file lumpy_la-1.0.0.tar.gz.

File metadata

  • Download URL: lumpy_la-1.0.0.tar.gz
  • Upload date:
  • Size: 14.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.4

File hashes

Hashes for lumpy_la-1.0.0.tar.gz
Algorithm Hash digest
SHA256 58ea240bad25db620459bd0606e4ac82e4d97ca50331f9e365a029386f1ba2c7
MD5 0689057b151912129ab17616fe7b577b
BLAKE2b-256 867348ebfc04559f9fb7c4860ed8e0766a8e1d32b6af4f0ad648993c352df3cc

See more details on using hashes here.

File details

Details for the file lumpy_la-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: lumpy_la-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 11.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.4

File hashes

Hashes for lumpy_la-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 4e4f098d7907d8f8883092864f3a07927596c15385398176e1800e4d337dc5d4
MD5 95631019f96fb5ecfaf49b3778bf6b2b
BLAKE2b-256 673e3d344495136a1d895dce2741bf0143e12f162d32a9f72ae0589d11ad525d

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page