Skip to content

Latest commit

 

History

History
1051 lines (764 loc) · 25.1 KB

File metadata and controls

1051 lines (764 loc) · 25.1 KB

Usage Guide

Linear Algebra

Vector

from axiompy import Axiom

v = Axiom.Vector([1.0, 2.0, 3.0])
u = Axiom.Vector([4.0, 5.0, 6.0])

v + u         # element-wise add
v - u         # element-wise subtract
v * 2.0       # scalar multiply
v * u         # dot product (returns float)
v / 2.0       # scalar divide
v.magnitude() # Euclidean norm
v.normalize() # unit vector
v.angle_between(u)              # radians
v.angle_between(u, in_degrees=True)  # degrees
v.cross(u)    # 3-D cross product

# Indexing
v[0]          # 1.0
v.to_list()   # [1.0, 2.0, 3.0]

Matrix

M = Axiom.Matrix([[1, 2], [3, 4]])
N = Axiom.Matrix([[5, 6], [7, 8]])

M + N         # element-wise add
M - N         # element-wise subtract
M * 2.0       # scalar multiply
M @ N         # matrix multiply
M @ v         # matrix-vector multiply (v is Vector)
M ** 2        # matrix power

M.determinant # property
M.trace       # property
M.rank        # property (int)
M.inverse     # property (Matrix)
M.T           # transpose
M.shape       # (rows, cols)
M.to_list()   # nested list

Factory methods

Axiom.linalg.identity(3)          # 3x3 identity
Axiom.linalg.zeros((2, 3))        # 2x3 zeros
Axiom.linalg.ones((4, 1))         # 4x1 ones

Advanced linear algebra (Phase 5.1)

# Singular value decomposition
U, S, Vt = M.svd_decompose()

# Eigen decomposition (symmetric matrices)
M.eigenvalues()
M.eigenvectors()

# Condition number (via SVD)
M.condition_number()

# Moore-Penrose pseudoinverse
M.pinv()

# Least-squares solution to Ax = b
x = Axiom.linalg.least_squares(A_mat, b_vec)

# Cross-product matrix (skew-symmetric)
Axiom.linalg.cross_product_matrix(v)

# Householder reflection
Axiom.linalg.householder(v)

Graph Analysis

g = Axiom.Graph()
g.add_edge("A", "B")
g.add_edge("A", "C")
g.add_edge("B", "C")
g.add_edge("C", "A")
g.add_edge("D", "C")

ranks = Axiom.graph_analysis.pagerank(g)
# {"C": 0.3941, "A": 0.3725, "B": 0.1958, "D": 0.0375}

pagerank accepts optional keyword arguments:

  • damping (default 0.85) -- PageRank damping factor
  • max_iter (default 100) -- maximum iterations
  • tol (default 1e-6) -- convergence tolerance
  • personalization (dict, optional) -- per-node teleport weights

Advanced graph algorithms (Phase 5.2)

# Minimum spanning tree (Kruskal's)
g.minimum_spanning_tree()

# Bipartiteness
g.is_bipartite()
g.bipartite_sets()

# Topological sort (DAG only)
g.topological_sort()

# Maximum flow (Edmonds-Karp)
g.max_flow("source", "sink")

# Graph diameter (longest shortest path)
g.diameter()

Number Theory

# Chinese Remainder Theorem
#   x = 2 (mod 3)
#   x = 3 (mod 5)
#   x = 2 (mod 7)
Axiom.number_theory.chinese_remainder_theorem([3, 5, 7], [2, 3, 2])
# 23

# Modular inverse of 7 modulo 26
Axiom.number_theory.mod_inverse(7, 26)   # 15

# Extended Euclidean algorithm
Axiom.number_theory.extended_gcd(30, 20)  # (10, 1, -1)

Advanced number theory (Phase 5.9)

# Probabilistic primality test (Miller-Rabin)
Axiom.number_theory.miller_rabin(7919)

# Prime generation
Axiom.number_theory.next_prime(100)       # 101
Axiom.number_theory.nth_prime(10)         # 29

# Quadratic residue symbols
Axiom.number_theory.legendre_symbol(2, 7)
Axiom.number_theory.jacobi_symbol(7, 15)

# Discrete logarithm (baby-step giant-step)
Axiom.number_theory.discrete_log(2, 8, 13)  # 3

# Fibonacci and Lucas numbers
Axiom.number_theory.fibonacci(10)   # 55
Axiom.number_theory.lucas(10)       # 123

Automatic Differentiation

AxiomPy provides a reverse-mode (backpropagation) automatic differentiation engine.

from axiompy import Axiom

x = Axiom.autodiff.Variable(2.0)
y = Axiom.autodiff.Variable(3.0)
z = x * y + x
z.backward()
x.grad  # 4.0  (dz/dx)
y.grad  # 2.0  (dz/dy)

Supported operators: +, -, *, /, ** (integer power), neg.

Built-in functions on Variable:

x = Axiom.autodiff.Variable(1.5)
f = Axiom.autodiff.sin(x ** 2)
f.backward()
x.grad  # df/dx = 2 * 1.5 * cos(1.5^2)

Available functions: sin, exp, log, tanh, sigmoid, sqrt.

Optimization helper:

from axiompy import Axiom

f = lambda x: x ** 2 + 2 * x + 1
minimum = Axiom.autodiff.gradient_descent(f, start=5.0, lr=0.1, steps=100)
# -1.0

Advanced autodiff (Phase 5.6)

# Instance methods
x = Axiom.autodiff.Variable(1.0)
x.tanh()     # same as Axiom.autodiff.tanh(x)
x.sigmoid()

# Computational graph visualization
z.to_ascii()

# Adam optimizer
Axiom.autodiff.adam(f, [0.0, 0.0], steps=100, lr=0.1)

# Hessian via forward-over-reverse
Axiom.autodiff.hessian(f, [1.0, 1.0])

Electromagnetism

from axiompy.electromagnetism import Electromagnetism

q = 1e-9
charges = [
    Electromagnetism.Charge(q, (0, 0, 0)),
    Electromagnetism.Charge(-q, (1, 0, 0)),
]

E = Axiom.electromagnetism.calculate_electric_field(charges, (0.5, 0.5, 0))
# Vector([25.42, 0.0, 0.0])

Electromagnetism.Charge is a namedtuple with fields q (coulombs) and position (3-tuple). calculate_electric_field returns a Vector.

Advanced electromagnetism (Phase 5.10)

# Electric potential (scalar)
Electromagnetism.electric_potential(charges, point)

# Magnetic field via Biot-Savart (moving charges)
Electromagnetism.calculate_magnetic_field(charges, point, velocities)

# Electric dipole moment
Electromagnetism.dipole_moment(charges)

# Field superposition
Electromagnetism.combine_fields([field1, field2])

Statistics

data = Axiom.Matrix([
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9],
])
Axiom.stats.covariance_matrix(data)

Rows are observations, columns are variables.

Advanced statistics (Phase 5.3)

# Hypothesis tests
Statistics.ttest_1samp([1, 2, 3, 4, 5], 3.0)
Statistics.ttest_ind([1, 2, 3], [2, 3, 4])
Statistics.ttest_paired([1, 2, 3], [2, 3, 4])
Statistics.chisquare([20, 30, 25, 25], [25, 25, 25, 25])
Statistics.f_oneway([1, 2, 3], [2, 3, 4], [3, 4, 5])

# Distribution functions
Statistics.uniform_pdf(0.5)
Statistics.uniform_cdf(0.5)
Statistics.exponential_pdf(1.0, rate=2.0)
Statistics.binomial_pmf(3, 5, 0.5)

# Robust statistics
Statistics.zscore([1, 2, 3, 4, 5])    # standardized scores
Statistics.mad([1, 2, 3, 4, 5])       # median absolute deviation

# Covariance matrix with raw arrays and ddof
Statistics.covariance_matrix([[1, 2], [3, 4], [5, 6]], ddof=0)

Visualization

ASCII line plot

x = [i * 0.4 for i in range(20)]
y = [v**2 for v in x]
Axiom.viz.plot_ascii(x, y)

Optional keyword arguments: width (default 60), height (default 15), title (str), xlabel (str), ylabel (str), extra_series (list of (x_vals, y_vals, label) tuples for multi-series with legend).

Advanced visualization (Phase 5.11)

# Histogram (using numpy.histogram internally)
Axiom.viz.plot_histogram(data, bins=10, title="Histogram")

# Horizontal bar chart
Axiom.viz.plot_bar(["A", "B", "C"], [30, 55, 20], title="Bar chart")

# Compact scatter plot (thin wrapper around plot_ascii)
Axiom.viz.plot_scatter(x, y, title="Scatter")

ASCII field plot

from axiompy import Axiom

def dipole(p):
    r1 = (p[0] - 0.5, p[1], p[2])
    r2 = (p[0] + 0.5, p[1], p[2])
    E1 = Axiom.electromagnetism.calculate_electric_field(
        [Axiom.electromagnetism.Charge(1e-9, (0.5, 0, 0))], p
    )
    return E1.to_list()

# Axiom.viz.plot_field_ascii(dipole, center=(0, 0, 0), size=4)

Calculus

from axiompy import Axiom
from axiompy.calculus import Calculus

f = lambda x: x ** 2

# Derivative at a point (central difference)
Calculus.numerical_derivative(f, 3.0)   # 6.0

# Integration
Calculus.integrate_trapezoid(f, 0, 1)    # ~ 1/3
Calculus.integrate_simpson(f, 0, 1)      # ~ 1/3
Calculus.integrate_monte_carlo(f, 0, 1)  # ~ 1/3

# Multivariate numerical gradient
g = lambda p: p[0]**2 + p[1]**2
Calculus.gradient(g, [1.0, 2.0])  # [2.0, 4.0]

Advanced calculus (Phase 5.4)

# Richardson extrapolation (high-order derivative)
Calculus.richardson_extrapolation(math.sin, 0.0)

# Gaussian quadrature (n = 2..8 supported)
Calculus.integrate_gauss_legendre(lambda x: x**2, 0, 1, n=5)

# Romberg integration
Calculus.integrate_romberg(lambda x: x**2, 0, 1)

# Numerical Jacobian matrix
Calculus.jacobian(lambda p: [p[0]**2 + p[1], p[0] - p[1]**3], [1.0, 2.0])

# Numerical Hessian matrix
Calculus.hessian(lambda p: p[0]**2 + 3*p[0]*p[1] + p[1]**2, [1.0, 1.0])

# ODE solvers
Calculus.ode_euler(lambda t, y: y, 1.0, (0.0, 2.0), dt=0.01)
Calculus.ode_rk4(lambda t, y: y, 1.0, (0.0, 2.0), dt=0.01)

Polynomials

from axiompy.polynomial import Polynomial

p = Polynomial([1, -3, 2])     # 2x^2 - 3x + 1
p(2)                           # 3
p.degree                        # 2
p.derivative()                  # Polynomial(4x - 3)
p.integral()                    # indefinite integral

# Roots via companion matrix
p.roots()                       # [0.5, 1.0]

# Lagrange interpolation
Polynomial.lagrange_interpolate([0, 1, 2], [0, 1, 4])  # x^2

Also accessible as Axiom.Polynomial.

Advanced polynomial features (Phase 5.8)

# Division with remainder
p // q                          # quotient
p % q                           # remainder

# Polynomial GCD (Euclidean algorithm)
p.gcd(q)

# Composition p(q(x))
p.compose(q)

# Chebyshev roots
Polynomial.chebyshev_roots(5)

# Least-squares polynomial fit
Polynomial.fit([0, 1, 2, 3, 4], [0, 1, 4, 9, 16], degree=2)

Optimization

from axiompy import Axiom
from axiompy.optimization import Optimization

f = lambda x: x ** 2 + 2 * x + 1
df = lambda x: 2 * x + 2
d2f = lambda x: 2.0

Optimization.gradient_descent(f, df, start=5.0, lr=0.1, steps=100)  # -1.0
Optimization.newton_method(f, df, d2f, start=5.0, steps=10)          # -1.0

# Root finding
Optimization.bisection(lambda x: x**2 - 4, 0, 5)  # 2.0

Advanced optimization (Phase 5.5)

# Golden section search (1-D)
Optimization.golden_section(lambda x: (x-2)**2, 0, 5)

# Conjugate gradient for SPD systems
Optimization.conjugate_gradient([[4, 1], [1, 3]], [1, 2], [0, 0])

# Nelder-Mead simplex (derivative-free)
Optimization.nelder_mead(lambda p: (p[0]-1)**2 + (p[1]-2)**2, [0, 0])

# L-BFGS (limited-memory BFGS)
Optimization.lbfgs(f, grad_f, start)

# Simulated annealing
Optimization.simulated_annealing(lambda p: (p[0]-3)**2 + (p[1]+1)**2, [0, 0])

Signal Processing

from axiompy.signal import Signal
import math

# DFT / FFT
x = [math.sin(2 * math.pi * 2 * k / 8) for k in range(8)]
X = Signal.dft(x)             # frequency domain
recovered = Signal.idft(X)    # back to time domain

# FFT (requires power-of-2 length, falls back to DFT otherwise)
X2 = Signal.fft(x)
rec2 = Signal.ifft(X2)

# Convolution
Signal.convolve([1, 2, 3], [0, 1, 0.5])  # [0.0, 1.0, 2.5, 4.0, 1.5]

# Moving average
Signal.moving_average([1, 2, 3, 4, 5], window=3)  # [2.0, 3.0, 4.0]

Advanced signal processing (Phase 5.7)

# Zero-pad to next power of 2
Signal.pad_next_power_of_two([1, 2, 3])

# Autocorrelation / cross-correlation (via FFT)
Signal.autocorrelation(x)
Signal.cross_correlation(x, y)

# FIR low-pass filter design (windowed sinc)
Signal.sinc_filter(cutoff=100, fs=1000, taps=31)

# Downsample / upsample
Signal.downsample(x, factor=2)
Signal.upsample(x, factor=2)

# Spectrogram (STFT magnitude)
Signal.spectrogram(x, window_size=64, hop_size=32)

Geometry / Spatial (Phase 6.7)

from axiompy import Axiom

# Primitives
p = Axiom.Point(1, 2, 3)
line = Axiom.Line(Axiom.Point(0, 0), Axiom.Point(1, 1))
plane = Axiom.Plane(Axiom.Point(0, 0, 0), Axiom.Point(0, 0, 1))
sphere = Axiom.Sphere(Axiom.Point(0, 0, 0), 1.0)

# Distance & projection
Axiom.distance(p1, p2)                       # Euclidean distance
Axiom.closest_point_on_line(line, p)         # orthogonal projection
Axiom.project_point_on_plane(plane, p)

# Intersection
line.intersection(other_line)
sphere.intersect_line(ray_line)              # 0, 1, or 2 hit points

# Algorithms
Axiom.convex_hull(points)                    # Andrew's monotone chain
Axiom.closest_pair(points)                   # divide & conquer

Cryptography (Phase 6.8)

from axiompy import Axiom

# RSA key generation & encryption
pub, priv = Axiom.rsa_keygen(512)
ct = Axiom.rsa_encrypt(42, pub)
pt = Axiom.rsa_decrypt(ct, priv)

# ElGamal
pub, priv = Axiom.elgamal_keygen(128)
ct = Axiom.elgamal_encrypt(99, pub)
pt = Axiom.elgamal_decrypt(ct, priv)

# Diffie-Hellman key exchange
p = 0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A63A3621
pub_a, pub_b, s_a, s_b = Axiom.diffie_hellman_key_exchange(p, 2, 12345, 67890)

# SHA-256 hash
Axiom.sha256(b"hello")  # "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"

Fractals / Chaos (Phase 6.6)

from axiompy import Axiom

# Mandelbrot & Julia sets (return 2D int grids)
grid = Axiom.mandelbrot(width=80, height=40, max_iter=100)
grid = Axiom.julia(-0.7+0.27j, width=80, height=40, max_iter=100)

# Logistic map
orbit = Axiom.logistic_map(r=3.8, x0=0.5, n=100)

# Bifurcation diagram points
pts = Axiom.bifurcation_diagram(2.5, 4.0, r_steps=500)

# Lyapunov exponent
f = lambda x: 4.0 * x * (1 - x)
lam = Axiom.lyapunov_exponent(f, x0=0.5, n=1000)

Bayesian Statistics (Phase 6.5)

from axiompy import Axiom

# Conjugate families
bb = Axiom.BetaBinomial(alpha=2, beta=2)          # coin bias prior
post_bb = bb.posterior(k=7, n=10)                 # 7 heads in 10 flips
post_bb.mean()                                    # posterior mean
post_bb.credible_interval()                       # 95% approx CI

nn = Axiom.NormalNormal(mu0=0, sigma0=10)         # vague prior for mean
post_nn = nn.posterior([1.2, 1.5, 0.8], sigma2=0.25)

pg = Axiom.PoissonGamma(shape=1, rate=1)
post_pg = pg.posterior([2, 0, 3, 1, 2])

# Generic conjugate update
post = Axiom.posterior(bb, 'binomial', (7, 10))

# MCMC with Metropolis
def log_pdf(x):
    return -0.5 * x[0]**2
samples = Axiom.mcmc_metropolis(log_pdf, [0.0], steps=2000, proposal_std=1.0)
intervals = Axiom.credible_interval(samples, mass=0.95)

ODE Solvers (Phase 6.4)

from axiompy import Axiom

# Unified IVP solver with multiple methods
rhs = lambda t, y: [y[0]]  # dy/dt = y
ts, ys = Axiom.solve_ivp(rhs, [1.0], (0, 1), method='rk4', dt=0.01)
# methods: 'euler', 'rk4', 'rk45' (adaptive), 'adams_bashforth'

# BVP via shooting method
# y'' = -y, y(0)=0, y(pi/2)=1
def bvp_rhs(t, y):
    return [y[1], -y[0]]
def bvp_bc(y_end):
    return [y_end[0] - 1.0]
ts, ys = Axiom.solve_bvp(bvp_rhs, bvp_bc, (0, math.pi/2), [0, 1])

# System factories
pend = Axiom.pendulum_odes(g=9.81, L=1.0, b=0.2)
ts, ys = Axiom.solve_ivp(pend, [0.5, 0.0], (0, 10), method='rk4')

lv = Axiom.lotka_volterra_odes(alpha=1.0, beta=0.1, gamma=1.5, delta=0.075)
ts, ys = Axiom.solve_ivp(lv, [10.0, 2.0], (0, 50), method='rk4')

Special Functions (Phase 6.3)

from axiompy import Axiom

# Gamma & Beta
Axiom.gamma(5)            # 24.0
Axiom.beta(2, 2)          # 0.1666...

# Error functions
Axiom.erf(1.0)            # 0.8427...
Axiom.erfc(1.0)           # 0.1573...

# Bessel functions
Axiom.bessel_j(0, 1.0)    # J0(1) ≈ 0.765
Axiom.bessel_y(0, 1.0)    # Y0(1) ≈ 0.088

# Legendre polynomials
Axiom.legendre_p(2, 0.5)  # -0.125

# Combinatorial
Axiom.factorial(10)       # 3628800
Axiom.binomial(10, 3)     # 120
Axiom.double_factorial(7) # 105 (7!! = 7*5*3*1)

Tensors (Phase 6.2)

from axiompy import Axiom

# Create tensors
t = Axiom.Tensor([[1, 2, 3], [4, 5, 6]])
t.shape   # (2, 3)
t.ndim    # 2
t.size    # 6

# Arithmetic (broadcasting supported)
t + 10
t * Axiom.Tensor([1, 2, 3])

# Factory methods
Axiom.Tensor.zeros(2, 3)
Axiom.Tensor.ones(4)
Axiom.Tensor.eye(3)
Axiom.Tensor.linspace(0, 1, 5)
Axiom.Tensor.arange(0, 10, 2)

# Reductions
t.sum()   # scalar Tensor
t.mean()
t.max()
t.min()
t.std()

# Tensor operations
Axiom.Tensor.contract(a, b, axes=1)      # matmul via contraction
Axiom.Tensor.outer(a, b)                 # outer product
Axiom.Tensor.kronecker(a, b)             # Kronecker product
Axiom.Tensor.einsum("ij,jk->ik", a, b)   # Einstein summation

# Reshape / transpose
t.reshape(3, 2)
t.flatten()
t.T

Complex Numbers (Phase 6.1)

from axiompy import Axiom

# Create complex numbers
z = Axiom.ComplexNumber(3, 4)
z_polar = Axiom.ComplexNumber.from_polar(2.0, math.pi / 3)

# Properties and methods
z.real          # 3.0
z.imag          # 4.0
z.conjugate     # ComplexNumber(3, -4)
z.modulus()     # 5.0
z.argument()    # 0.9273 rad
z.power(2)      # De Moivre power

# Roots of unity
Axiom.ComplexNumber.roots_of_unity(5)  # 5 roots

# CFFT alias (delegates to Signal.fft)
Axiom.ComplexNumber.cfft([1, 0, -1, 0])

# Complex containers
v = Axiom.ComplexVector([1+2j, 3+4j])
m = Axiom.ComplexMatrix([[1j, 2j], [3j, 4j]])

Lazy Evaluation (Phase 7.3)

Deferred computation builds expression trees for Vector/Matrix operations, materialized only when .compute() is called.

from axiompy import Axiom

v = Axiom.Vector([1, 2, 3])
w = Axiom.Vector([4, 5, 6])

# Build a lazy expression tree (no computation yet)
expr = (Axiom.lazy.lazy(v) + Axiom.lazy.lazy(w)) * 2.0 - Axiom.lazy.lazy(v)

# Materialize
result = expr.compute()      # Vector([9.0, 12.0, 15.0])

# Or use the scope shorthand
result = Axiom.lazy.compute(expr)

# Lazy expressions support all operators:
expr = Axiom.lazy.lazy(v) * 3.0          # scalar multiply
expr = Axiom.lazy.lazy(A).T              # transpose
expr = Axiom.lazy.lazy(A) @ v            # matmul
expr = Axiom.lazy.lazy(A) ** 2           # matrix power
expr = Axiom.lazy.lazy(A) + 5.0          # scalar add (broadcast)
expr = 10.0 - Axiom.lazy.lazy(A)         # rsub with broadcast

Sparse Matrix (Phase 7.4)

A memory-efficient sparse matrix in COO format, with CSR for fast matrix-vector products.

from axiompy import Axiom

# Convert a dense matrix to sparse
M = Axiom.Matrix([[5, 0, 0], [0, 0, 3], [0, 0, 0]])
sp = M.to_sparse()
sp.shape       # (3, 3)
sp.nnz         # 2
sp.density     # 2/9 ≈ 0.2222

# Convert back to dense
dense = sp.to_dense()

# Sparse identity
I = Axiom.SparseMatrix.identity(5)

# COO components
rows, cols, data = sp.to_coo()

# Arithmetic
sp + sp                     # sparse-sparse addition
sp + 1.0                    # scalar addition (broadcast)
sp * 2.0                    # scalar multiplication

# Matrix-vector product (O(nnz) via CSR)
v = Axiom.Vector([1.0, 2.0, 3.0])
result = sp @ v

# Sparse-sparse matrix product
A = Axiom.SparseMatrix([0, 1], [0, 1], [2.0, 3.0], (2, 2))
B = Axiom.SparseMatrix([0, 1], [0, 1], [4.0, 5.0], (2, 2))
C = A @ B

# Direct construction
sp2 = Axiom.SparseMatrix(
    rows=[0, 2], cols=[0, 1], data=[1.5, 2.7], shape=(3, 3)
)

Out-of-Core & Dtype (Phase 7.5)

Memory-mapped arrays (out-of-core)

from axiompy import Axiom
import numpy as np

# Create a zero-initialized memory-mapped array
mm = Axiom.MmapArray.zeros((10000, 500), path="/tmp/large.mmap")
mm.open()
mm[0, 0] = 42.0

# Chunked matrix-vector product (never loads full array into RAM)
v = Axiom.Vector(np.ones(500))
result = mm.matmul(v)

# Chunked in-place arithmetic
mm.add(5.0)
mm.mul(2.0)

# Convert to dense Matrix (loads into RAM — use with care)
M = Axiom.Matrix.from_mmap(mm)

# Context manager pattern
with Axiom.MmapArray.zeros((3, 3), path="/tmp/ctx.mmap") as m:
    m[0, :] = [1, 2, 3]
    result = m.matmul(Axiom.Vector([1, 2, 3]))

# From existing numpy array
arr = np.array([[1.0, 2.0], [3.0, 4.0]])
mm2 = Axiom.MmapArray.from_array(arr, path="/tmp/from_arr.mmap")

Numerical dtype support

v = Axiom.Vector([1.5, 2.5, 3.5])
v.dtype              # dtype('float64')

v32 = v.astype("float32")
v32.dtype            # dtype('float32')

vi = v.astype("int32")
vi.to_list()         # [1, 2, 3]

M = Axiom.Matrix([[1, 2], [3, 4]])
M.dtype              # dtype('float64')
M.astype("float32")

Jupyter Integration (Phase 8.1)

Rich display in notebooks

Vector, Matrix, Polynomial, and SparseMatrix automatically render as formatted HTML or LaTeX when displayed in a Jupyter notebook cell:

from axiompy import Axiom

v = Axiom.Vector([1.5, 2.0, 3.0])
v  # rendered as \begin{pmatrix} 1.5 & 2 & 3 \end{pmatrix}

m = Axiom.Matrix([[1, 2], [3, 4]])
m  # rendered as a 2x2 pmatrix

p = Axiom.Polynomial([1, -2, 3])
p  # rendered as 3x^2 - 2x + 1

You can also get LaTeX strings explicitly:

v.to_latex()   # "\\begin{pmatrix}1.5 & 2 & 3\\end{pmatrix}"
m.to_latex()   # "\\begin{pmatrix}1 & 2\\\\3 & 4\\end{pmatrix}"
p.to_latex()   # "3x^{2} - 2x + 1"

Interactive widgets (requires ipywidgets)

Explore polynomials or matrices live with sliders. Install the extra:

pip install ipywidgets
from axiompy import Axiom

# Adjust polynomial coefficients with sliders, see value at x
Axiom.PolynomialSliders(coeffs=[1.0, 0.0, 0.0], x0=0.0)

# Explore a 2x2 matrix — determinant, trace, eigenvalues update live
Axiom.MatrixExplorer()

CLI Tool (Phase 8.2)

The axiompy package installs a command-line tool with three subcommands:

axiompy shell    # Interactive REPL with Axiom pre-imported as A
axiompy demo     # Run through all example scripts
axiompy info     # Print version, backend, config

axiompy shell

Launches an interactive Python REPL (IPython if installed, otherwise standard interactive Python) with the Axiom facade pre-imported as A.

A.Vector([1, 2, 3]) @ A.Matrix([[1, 0], [0, 1], [0, 0]])

axiompy demo

Executes every .py file in the examples/ directory and reports the output (or any failures).

axiompy info

Prints version, Python version, platform, active backend, and current config:

AxiomPy version:    4.0.0
Python version:     3.14.4
Platform:           linux
Active backend:     NumpyBackend
Config — precision: 6
Config — dtype:     float64
Config — verbose:   False

Error handling

All custom exceptions inherit from AxiomError (defined in _base.py):

from axiompy._base import AxiomError

try:
    inv = Axiom.number_theory.mod_inverse(2, 4)
except AxiomError as e:
    print(e)  # "Modular inverse does not exist for 2 and 4"

Configuration

AxiomPy can be configured via pyproject.toml or programmatically:

[tool.axiompy]
precision = 6    # rounding precision for repr
dtype = "float64"
verbose = false   # enables debug logging

Programmatic configuration at runtime:

from axiompy import AxiomConfig

AxiomConfig.configure(precision=3, verbose=True)
Axiom.config.precision  # 3

# Reset to defaults
AxiomConfig.reset()

Numeric Backend

Backend system allows swapping the array library:

from axiompy import Axiom

Axiom.backend  # NumpyBackend instance

# Switch backends
Axiom.set_backend("numpy")       # NumPy-backed (default)
Axiom.set_backend("pure")        # pure-Python (no numpy dependency)
Axiom.set_backend("jax")         # JAX JIT-compiled (requires jax)
Axiom.set_backend("cupy")        # CUDA-accelerated (requires cupy + GPU)

Custom backends can be registered:

from axiompy import Backend, register_backend

class MyBackend(Backend):
    def array(self, data, dtype=None): ...
    def dot(self, a, b): ...
    # ... implement all abstract methods

register_backend("mybackend", MyBackend)
Axiom.set_backend("mybackend")

Logging

Each module has a logging.Logger at axiompy.<module>. Set log level via the facade or directly:

import logging
logging.getLogger("axiompy").setLevel(logging.DEBUG)

When verbose = true in the config, debug logging is enabled automatically.

Publication (Phase 8.3)

Creating a release

# Tag the current version
git tag v$(python -c "from axiompy import __version__; print(__version__)")
git push origin --tags

Then create a GitHub Release from the tag — the .github/workflows/publish.yml workflow automatically builds and publishes to PyPI.

Conda-forge

A recipe/meta.yaml is provided for conda-forge feedstock. The actual conda-forge submission must be made via a pull request to the staged-recipes <https://github.com/conda-forge/staged-recipes>_ repository.

Zenodo citation

The .zenodo.json file contains metadata for Zenodo DOI registration. When a GitHub Release is published, Zenodo automatically creates a new DOI version (once the Zenodo-GitHub integration is enabled for the repository).

Benchmarking (Phase 8.4)

Benchmarks compare AxiomPy operations against equivalent numpy/scipy calls.

# Run all benchmarks
uv run pytest benchmarks/ --benchmark-only

# Compare specific groups
uv run pytest benchmarks/ --benchmark-only --benchmark-group-by=group

# Store results as JSON for historical comparison
uv run pytest benchmarks/ --benchmark-only --benchmark-json output.json

Benchmark groups:

  • vector-creation, vector-dot, vector-norm, vector-add
  • matrix-matmul, matrix-determinant, matrix-inverse
  • poly-eval, poly-roots

A CI workflow (.github/workflows/benchmark.yml) is provided for optional on-label runs; it is disabled by default and must be triggered manually via the GitHub Actions UI.

Running tests

uv sync --dev
uv run pytest tests/ -v