effect-math
v0.2.0
Published
Foundational numerics, linear algebra, statistics, and optimization for Effect
Downloads
462
Readme
effect-math
Mathematics for the Effect ecosystem. Numerics, linear algebra, geometry, probability, statistics, distributions, and special functions — with typed errors, immutable carriers, and configurable runtime policies.
Quick start · Domains · Runtime policies · Error handling · API at a glance
Why effect-math?
Most math libraries give you raw functions that throw on bad input, mutate buffers in place, and offer no way to control precision behavior or trace what happened. effect-math is different:
- Immutable
Chunk<number>carriers — no hidden mutation, structurally shareable, persistent - Typed errors — every failure has a
_tagyou can match on. NoNaNsurprises, no silent infinities - Runtime policies via
Layer— inject precision enforcement, backend selection, and diagnostics tracing without changing call sites - Schema-validated boundaries —
onExcessProperty: "error"at every public decode edge - Pure kernels — hot-path functions are synchronous with no Effect overhead. Wrap them in Effect only when you need policies or typed error channels
- No native deps — pure TypeScript. Just
effectas a peer dependency
Installation
npm install effect-math
# or
bun add effect-mathPeer dependency: effect >= 3.20.0
Quick start
Pure kernels work directly — no Effect runtime needed:
import { Chunk } from "effect"
import { dot, normL2, vectorAdd } from "effect-math/LinearAlgebra"
import { euclideanDistance } from "effect-math/Geometry"
import { mean, variance } from "effect-math/Statistics"
import { normalPdf, standardNormalCdf } from "effect-math/Probability"
import { gamma, erf, beta } from "effect-math/Special"
import { normalCdf as distNormalCdf, betaMean, poissonPmf } from "effect-math/Distribution"
import { of, add, abs, sin, complexDerivative } from "effect-math/Complex"
const a = Chunk.fromIterable([1, 2, 3])
const b = Chunk.fromIterable([4, 5, 6])
dot(a, b) // 32
normL2(a) // √14
vectorAdd(a, b) // Chunk(5, 7, 9)
euclideanDistance(Chunk.fromIterable([0, 0]), Chunk.fromIterable([3, 4])) // 5
mean(Chunk.fromIterable([2, 4, 6])) // 4
variance(Chunk.fromIterable([2, 4, 6])) // 4
normalPdf(0, 0, 1) // ≈ 0.3989
standardNormalCdf(0) // 0.5
gamma(5) // 24 (= 4!)
gamma(0.5) // √π ≈ 1.7725
erf(1) // ≈ 0.8427
beta(0.5, 0.5) // π
distNormalCdf(1.96, 0, 1) // ≈ 0.975
betaMean(2, 5) // ≈ 0.2857
poissonPmf(3, 5) // ≈ 0.1404
const z = add(of(1, 2), of(3, 4)) // 4 + 6i
abs(of(3, 4)) // 5
sin(of(1, 1)) // sin(1)cosh(1) + i·cos(1)sinh(1)
complexDerivative(sin, 0) // cos(0) = 1 (machine-precision)When you need precision enforcement or diagnostics, use policy-aware operations — they read runtime services from the Effect context:
import { Chunk, Effect, Layer } from "effect"
import { dotWithPolicies } from "effect-math/LinearAlgebra"
import { BackendPolicyService, DiagnosticsPolicyService, PrecisionPolicyService } from "effect-math/contracts"
const program = Effect.gen(function* () {
const a = Chunk.fromIterable([1, 2, 3])
const b = Chunk.fromIterable([4, 5, 6])
return yield* dotWithPolicies(a, b) // 32
})
const policies = Layer.mergeAll(
Layer.succeed(PrecisionPolicyService, { policy: "strict" }),
Layer.succeed(BackendPolicyService, { policy: "scalar" }),
Layer.succeed(DiagnosticsPolicyService, { policy: "disabled" })
)
Effect.runSync(program.pipe(Effect.provide(policies)))Under "strict" precision, non-finite results fail with a typed error instead of silently returning NaN or Infinity.
Domains
Each domain is a self-contained subpath export with its own schemas, typed errors, and operations.
| Domain | Import | What it does |
| ----------------- | --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Numeric | effect-math/Numeric | Scalar transforms — safe division, log1p, expm1, clamp |
| LinearAlgebra | effect-math/LinearAlgebra | Dense vector/matrix — dot, norms, matvec, transpose |
| Geometry | effect-math/Geometry | Distances (Euclidean, Manhattan, Chebyshev), midpoint, centroid |
| Probability | effect-math/Probability | Normal and uniform PDF/CDF, Shannon entropy |
| Statistics | effect-math/Statistics | Mean, variance, standard deviation, covariance, min/max |
| Special | effect-math/Special | Gamma, beta, erf/erfc, digamma (Lanczos, A&S 7.1.26) |
| Algebra | effect-math/Algebra | Polynomial eval/derivative, GCD, LCM, factorial |
| Calculus | effect-math/Calculus | Derivative limits (derivativeLimit, secondDerivativeLimit), scalar derivatives, multivariate operators (gradient/Jacobian/Hessian/directional/divergence/laplacian), trapezoid/Simpson/adaptive-Simpson integration |
| Optimization | effect-math/Optimization | Bisection root-finding, golden section minimization |
| Distribution | effect-math/Distribution | 10-family algebra — Normal, LogNormal, Exponential, Uniform, Beta, Gamma, Student-t, Categorical, Binomial, Poisson with PDF/CDF, quantile, mean, variance, entropy |
| Complex | effect-math/Complex | Complex arithmetic, trig, polar, Chunk carriers, complex-step derivative |
Internal modules are blocked from import via the package exports map.
Runtime policies
Policy-aware operations read configuration from Effect services. Compose the policies you need using Layer:
| Service | Values | What it controls |
| -------------------------- | ---------------------------------------- | --------------------------------------------------- |
| PrecisionPolicyService | "strict" / "relaxed" | Strict rejects non-finite results as typed errors |
| BackendPolicyService | "typed-array" / "scalar" | Execution strategy for dense operations |
| DiagnosticsPolicyService | "enabled" / "disabled" | Effect.logDebug with timing and metadata |
| RngPolicyService | "deterministic" / "nondeterministic" | Deterministic requires a Seed for reproducibility |
makeDeterministicRuntimePoliciesLayer builds all four from a single config object — useful for reproducible test fixtures.
Error handling
Every domain defines typed errors using Schema.TaggedError. Match on _tag to handle specific failures:
import { Chunk, Effect, Layer } from "effect"
import { normWithPolicies } from "effect-math/LinearAlgebra"
import { DiagnosticsPolicyService, PrecisionPolicyService } from "effect-math/contracts"
const program = normWithPolicies(Chunk.fromIterable([Infinity, 1]), "L2").pipe(
Effect.catchTag("LinearAlgebraDomainViolationError", (e) => Effect.succeed(`caught: ${e.message}`)),
Effect.provide(
Layer.mergeAll(
Layer.succeed(PrecisionPolicyService, { policy: "strict" }),
Layer.succeed(DiagnosticsPolicyService, { policy: "disabled" })
)
)
)| Domain | Error | Raised when |
| ------------- | ---------------------------- | ------------------------------------------ |
| LinearAlgebra | ShapeMismatchError | Dimension incompatibility between operands |
| | SingularMatrixError | Matrix is rank-deficient |
| | DecompositionError | Factorization cannot complete |
| Geometry | GeometryShapeMismatchError | Point dimensions don't match |
| | GeometryDegenerateError | Degenerate geometric configuration |
| Probability | ProbabilityParameterError | Invalid distribution parameters |
| Statistics | StatisticsShapeError | Too few observations for the estimator |
| Special | SpecialParameterError | Invalid parameters (e.g., gamma at poles) |
| Distribution | DistributionDecodeError | Schema decode failure for operation input |
| | DistributionParameterError | Invalid parameters (e.g., σ ≤ 0) |
| Complex | ComplexDivisionByZeroError | Division by zero complex number |
| | ComplexDomainError | Invalid domain (e.g., log of zero) |
Each domain also defines a DomainViolationError raised under "strict" precision when an operation produces a non-finite result.
API at a glance
// Pure kernels — no Effect wrapper
import { dot, normL2, vectorAdd, vectorScale, matvec, transpose, frobeniusNorm } from "effect-math/LinearAlgebra"
import { euclideanDistance, manhattanDistance, chebyshevDistance, midpoint } from "effect-math/Geometry"
import { mean, variance, standardDeviation, covariance, minimum, maximum } from "effect-math/Statistics"
import { normalPdf, normalCdf, uniformPdf, uniformCdf, shannonEntropy } from "effect-math/Probability"
import { safeDivide, log1p, expm1, sum, clamp, between } from "effect-math/Numeric"
import { gamma, lnGamma, beta, erf, erfc, digamma } from "effect-math/Special"
import { polyEval, polyDerivative, gcd, lcm, factorial } from "effect-math/Algebra"
import {
derivativeLimit,
secondDerivativeLimit,
derivative,
secondDerivative,
gradient,
jacobian,
hessian,
directionalDerivative,
divergence,
laplacian,
trapezoid,
simpson,
adaptiveSimpson
} from "effect-math/Calculus"
import { bisect, goldenSection } from "effect-math/Optimization"
import {
normalPdf as dNormalPdf,
normalCdf as dNormalCdf,
normalQuantile,
betaPdf,
betaCdf,
betaQuantile,
gammaPdf,
gammaCdf,
exponentialPdf,
uniformPdf,
studentTPdf,
categoricalPmf,
binomialPmf,
poissonPmf as dPoissonPmf,
normalMean,
normalVariance,
normalEntropy as dNormalEntropy,
betaMean as dBetaMean,
gammaMean
} from "effect-math/Distribution"
import {
of,
add,
multiply,
divide,
conjugate,
abs,
arg,
exp,
log,
pow,
sqrt,
sin,
cos,
tan,
sinh,
cosh,
tanh,
toPolar,
fromPolar,
complexDerivative,
complexDot,
complexNorm,
complexScale,
fromRealChunk,
toRealChunk
} from "effect-math/Complex"
// Policy-aware — read runtime services from Effect context
import { dotWithPolicies, normWithPolicies } from "effect-math/LinearAlgebra"
import { distanceWithPolicies } from "effect-math/Geometry"
import {
summaryStatisticsWithPolicies,
meanWithPolicies,
varianceWithPolicies,
covarianceWithPolicies
} from "effect-math/Statistics"
import {
normalPdfWithPolicies,
normalCdfWithPolicies,
uniformPdfWithPolicies,
uniformCdfWithPolicies,
entropyWithPolicies
} from "effect-math/Probability"
import { sumWithPolicies } from "effect-math/Numeric"
import {
gammaWithPolicies,
erfWithPolicies,
lnGammaWithPolicies,
betaWithPolicies,
erfcWithPolicies,
digammaWithPolicies
} from "effect-math/Special"
import {
polyEvalWithPolicies,
factorialWithPolicies,
polyDerivativeWithPolicies,
gcdWithPolicies,
lcmWithPolicies
} from "effect-math/Algebra"
import {
derivativeLimitWithPolicies,
secondDerivativeLimitWithPolicies,
derivativeWithPolicies,
secondDerivativeWithPolicies,
gradientWithPolicies,
jacobianWithPolicies,
hessianWithPolicies,
directionalDerivativeWithPolicies,
divergenceWithPolicies,
laplacianWithPolicies,
trapezoidWithPolicies,
simpsonWithPolicies,
adaptiveSimpsonWithPolicies
} from "effect-math/Calculus"
import { bisectWithPolicies, goldenSectionWithPolicies } from "effect-math/Optimization"
import { normalPdfWithPolicies, normalCdfWithPolicies, betaCdfWithPolicies } from "effect-math/Distribution"
// Runtime policy services and layer constructors
import {
PrecisionPolicyService,
BackendPolicyService,
DiagnosticsPolicyService,
RngPolicyService,
makeDeterministicRuntimePoliciesLayer
} from "effect-math/contracts"Status
| Tier | Domains | Meaning | | --------------- | -------------------------------------------------------------------------------------------------------------------------- | --------------------------------- | | Provisional | Numeric, LinearAlgebra, Geometry, Probability, Statistics, Special, Algebra, Calculus, Optimization, Distribution, Complex | Functional and tested, may evolve |
Calculus Fixture Provenance
Calculus parity fixtures are mixed-source by operation. trapezoid, simpson, and adaptiveSimpson expectations are generated from SciPy/NumPy reference calls (numpy.trapz, scipy.integrate.simpson, scipy.integrate.quad) in packages/effect-math/scripts/fixtures/calculus.py. derivative, secondDerivative, and multivariate operators (gradient, jacobian, hessian, directionalDerivative, divergence, laplacian) use analytic/reference formulations in the same generator because there is no one-to-one SciPy operator call for those exact contracts.
Acknowledgments
Gamma and log-gamma use the Lanczos approximation (g = 7, 9 coefficients from Godfrey, 2001). Error function uses multi-region rational polynomial coefficients from the Cephes Mathematical Library (Moshier, 1984–2000; BSD license — see THIRD_PARTY_NOTICES). Inverse error function uses rational Chebyshev approximations from Blair, Edwards & Johnson (1976) via Boost.Math (Maddock, 2006; Boost Software License 1.0 applies to coefficient tables). Regularized incomplete gamma and beta use series expansion and modified Lentz continued fractions (Lentz, 1976; Thompson & Barnett, 1986). Polygamma uses recurrence shifting and asymptotic expansion with Bernoulli numbers per A&S §6.4. Digamma uses asymptotic expansion per A&S §6.3.18. Compensated summation follows Kahan (1965). Golden section search follows Kiefer (1953). Complex-step differentiation follows Squire & Trapp (1998). Complex division uses the Smith (1962) method for overflow safety. Beta, Gamma, and Student's t quantiles use Newton–Raphson iteration on the CDF inverse. Numerical kernels are verified against SciPy/NumPy fixtures where those APIs are authoritative and analytic/reference fixtures where direct SciPy parity is not applicable.
Contributing
See the repository for contribution guidelines.
bun run check # Type check
bun run test # Run tests
bun run lint # ESLint with Effect rules
bun run build # ESM + CJS + annotate-pure-callsLicense
MIT — Copyright © 2026 Scene Systems
