Skip to content

Pavlo3P/SpaceCore

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

210 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

SpaceCore

CI PyPI Python License

In most numerical code a vector is just an array. The mathematics that gives it meaning — which space it belongs to, what inner product measures its length, how an operator's adjoint is actually defined — lives in the author's head and in comments rather than in the code. When that structure stays implicit, it is easy to combine elements from incompatible spaces, assume a Euclidean inner product where the geometry is weighted, or take a matrix transpose where the true adjoint is something else.

SpaceCore makes that structure explicit. A space is a typed object that knows how to validate its elements, compute inner products, flatten structured values, and represent adjoints. An operator is a typed map A : X -> Y between spaces, not merely an array, so the spaces it connects — and the rules they carry — are part of its definition.

The execution backend is explicit too. A Context owns the backend operations, the default dtype, and the validation policy used by spaces and operators. NumPy is the baseline backend; JAX, Torch, and CuPy are optional backends when their extras are installed.

Install

pip install spacecore
pip install "spacecore[jax]"
pip install "spacecore[torch]"
pip install "spacecore[cupy]"

Python 3.11+ is required.

Quick Start

import numpy as np
import spacecore as sc

ctx = sc.Context(sc.NumpyOps(), dtype=np.float64)
X = sc.DenseCoordinateSpace((2,), ctx)
A = sc.DenseLinOp(ctx.asarray([[2.0, 0.0], [0.0, 3.0]]), X, X, ctx)
b = ctx.asarray([4.0, 9.0])

result = sc.cg(A, b, tol=1e-12, maxiter=10)
print(result.x)
print(result.converged)

Expected output:

[2. 3.]
True

Core Ideas

Spaces. DenseCoordinateSpace, DenseVectorSpace, ElementwiseJordanSpace, EuclideanElementwiseJordanSpace, HermitianSpace, TreeSpace, and StackedSpace describe element structure and geometry. Dense coordinate spaces can use Euclidean or weighted inner products. Each space also exposes a field ("real" or "complex") — the mathematical scalar-field contract derived from the context dtype — which capability guards consult instead of inspecting precision-bearing dtypes.

import numpy as np
import spacecore as sc

ctx = sc.Context(sc.NumpyOps(), dtype=np.float64)
weights = ctx.asarray([2.0, 5.0])
X = sc.DenseCoordinateSpace((2,), ctx, geometry=sc.WeightedInnerProduct(weights))
x = ctx.asarray([1.0, 2.0])
y = ctx.asarray([3.0, 4.0])

print(X.inner(x, y))
print(X.riesz(x))
print(X.field)

Expected output:

46.0
[ 2. 10.]
real

Linear operators. DenseLinOp, SparseLinOp, DiagonalLinOp, MatrixFreeLinOp, IdentityLinOp, ZeroLinOp, and the lazy algebraic operators (ComposedLinOp, ScaledLinOp, SumLinOp) represent maps A : X -> Y. apply computes the forward map. rapply computes the metric adjoint: the coordinate conjugate transpose only agrees with it when both spaces use Euclidean geometry. The algebraic operators infer Hermiticity structurally, so a normal operator A.H @ A + lam * Identity is recognized as self-adjoint.

import numpy as np
import spacecore as sc

ctx = sc.Context(sc.NumpyOps(), dtype=np.float64)
X = sc.DenseCoordinateSpace((2,), ctx)
A = sc.DiagonalLinOp(ctx.asarray([2.0, 3.0]), X, ctx)

print(A.apply(ctx.asarray([1.0, 2.0])))
print(A.rapply(ctx.asarray([1.0, 1.0])))

M = sc.DenseLinOp(ctx.asarray([[1.0, 2.0], [0.0, 1.0]]), X, X, ctx)
print((M.H @ M).is_hermitian())

Expected output:

[2. 6.]
[2. 3.]
True

Structured (tree) spaces. TreeSpace is the finite direct-product space: it organizes leaf spaces by an optree definition, so an element can be a named, nested Python structure (tuple, dict, namedtuple, or any nesting of them) rather than a flat vector. Operations like inner act across the whole tree, and BlockDiagonalLinOp, BlockMatrixLinOp, and the TreeLinOp base operate over tree domains with correct metric-adjoint behavior.

A block-diagonal operator can be built directly from a matching tree of blocks; its domain and codomain are inferred from the blocks, so the names and nesting are carried through to the result.

import numpy as np
import spacecore as sc

ctx = sc.Context(sc.NumpyOps(), dtype=np.float64)
position = sc.DenseCoordinateSpace((3,), ctx)
linear = sc.DenseCoordinateSpace((3,), ctx)
angular = sc.DenseCoordinateSpace((1,), ctx)

# A named, nested tree of blocks: a top-level "position" block and a grouped
# "twist" block with its own "linear" and "angular" sub-blocks.
A = sc.BlockDiagonalLinOp({
    "position": sc.IdentityLinOp(position, ctx),
    "twist": {
        "linear": sc.DiagonalLinOp(ctx.asarray([2.0, 2.0, 2.0]), linear, ctx),
        "angular": sc.DiagonalLinOp(ctx.asarray([0.5]), angular, ctx),
    },
})

x = A.dom.element({
    "position": ctx.asarray([1.0, 0.0, 0.0]),
    "twist": {
        "linear": ctx.asarray([1.0, 1.0, 1.0]),
        "angular": ctx.asarray([4.0]),
    },
})

print(A.apply(x))
print(A.dom.inner(x, x))

Expected output:

{'position': array([1., 0., 0.]), 'twist': {'linear': array([2., 2., 2.]), 'angular': array([2.])}}
20.0

Functionals. LinearFunctional, InnerProductFunctional, MatrixFreeLinearFunctional, QuadraticForm, and LinOpQuadraticForm model scalar-valued maps on spaces. Gradients are represented in the domain geometry.

Everyday toolbox. spacecore.functional.tools (re-exported at the top level) adds named constructors over that machinery, with no new core types: least_squares for ½‖Ax−b‖², coordinate norms (SquaredL2NormFunctional, LpNormFunctional, L1NormFunctional), NegativeEntropyFunctional, KLDivergenceFunctional, HuberFunctional, the spectral SpectralLpNormFunctional/NuclearNormFunctional, and the metric-aware proximal primitive generalized_shrinkage with the wrappers prox_l1, prox_l2sq, and project_nonneg. Each objective's gradient is the metric (Riesz) gradient under the domain geometry, and the proximal step is taken in the space metric.

Linear algebra. cg, lsqr, lanczos_smallest, power_iteration, and expm_multiply operate on SpaceCore operators and spaces. They document their mathematical preconditions and reject provably invalid inputs at entry; for example cg expects a square Hermitian positive definite map A : X -> X with respect to X.inner, and rejects an operator that is provably non-self-adjoint in its geometry. These native solvers are intentionally small — a correctness baseline and a substrate for space-aware algorithms rather than a full solver suite; backend-specific fast paths and external adapters can be layered on top.

External optimizer adapters. minimize_scipy, line_search_scipy, and minimize_optax (in spacecore.optimize) drive mature external optimizers from a Functional. Because SpaceCore gradients are metric (Riesz) gradients while NumPy/JAX optimizers expect coordinate gradients, each adapter applies the X.riesz(F.grad(x)) handoff for you — the identity on a Euclidean space and mandatory on a weighted one. The external optimizer owns the loop; the SciPy adapters require a real domain and minimize_optax needs a JAX backend (pip install spacecore[optax]).

Backends. NumpyOps is always available. JaxOps, TorchOps, and CuPyOps are exported only when their optional dependencies are installed. Backend portability means SpaceCore uses the same abstract operations and data model; it does not erase backend-specific dtype, device, sparse, tracing, or autograd behavior. Cross-backend agreement is documented in the backend conformance matrix and deviation catalog.

Validation Policy

A Context carries a check_level that determines how aggressively spaces, operators, functionals, and solver preconditions validate their inputs. The ordered levels are CHECK_LEVELS = ("none", "cheap", "standard", "strict"): cheap covers shape/dtype/backend/tree-structure, standard adds membership and Hermitian checks, and strict adds bounded expensive probes. Checks are opt-in per context, so hot paths can run unvalidated while development and tests run strict.

import numpy as np
import spacecore as sc

ctx = sc.Context(sc.NumpyOps(), dtype=np.float64, check_level="standard")
X = sc.DenseCoordinateSpace((2,), ctx)
A = sc.DenseLinOp(ctx.asarray([[2.0, 0.0], [0.0, 3.0]]), X, X, ctx)

try:
    A.apply(ctx.asarray([1.0, 2.0, 3.0]))  # wrong shape
except sc.SpaceValidationError as exc:
    print(exc)

Expected output:

Expected shape (2,), got (3,)

Batching

A space describes one element type. Batched computation is handled by vectorized application methods such as vapply, rvapply, vvalue, and backend vectorization. Batching does not change the mathematical domain or codomain of an operator unless the operator itself is explicitly built over a stacked or tree space. Iterative solvers are unbatched in this release; batched-input invocations raise a clear shape error.

Performance

The typed, validated API is designed to add little run-time overhead over working with raw arrays: operator and functional hot paths use check-free fast paths, and iterative solvers validate their inputs once at entry rather than on every iteration. A unified benchmark framework is available via python -m bench.

Documentation

Project Status

SpaceCore is experimental 0.4.x software. The 0.4.0 release stabilizes the typed linear-algebra core as a validated algebra of structured mathematical objects — a public check-policy, the dtype/scalar-field contract, the TreeSpace direct-product abstraction with block-structured operators, an everyday functional and proximal toolbox, external optimizer adapters, reusable test generators, and a backend conformance matrix. Core abstractions are usable for research and prototyping, but API details may still change before a stable release.

Contributing

Bug reports, feature requests, and PRs are welcome. See CONTRIBUTING.md.

License

Apache 2.0. See LICENSE.

Citation

@software{spacecore,
  author = {Pavlo Pelikh},
  title = {SpaceCore: Backend-aware vector spaces and linear operators},
  url = {https://github.com/Pavlo3P/SpaceCore},
  year = {2026},
}

About

Framework for working with vector spaces and linear operators.

Resources

License

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors