npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@danielsimonjr/mathts-functions

v0.73.0

Published

Mathematical functions for MathTS - arithmetic, algebra, trigonometry, statistics, and more

Readme

@danielsimonjr/mathts-functions

Math functions for MathTS.

The mathematical function library for MathTS — arithmetic, trigonometry, algebra, statistics, signal processing, and more — dispatched polymorphically via typed-function across number / Complex / Fraction / BigNumber / Matrix.

Install

npm install @danielsimonjr/mathts-functions

What it provides

  • Hundreds of typed functions (add, sin, svd-aware ops, parallelFFT, …).
  • A wired parse / evaluate against the full activated math scope.
  • scalar / evaluateScalar — evaluate a typed scalar tree (+ - * / ^, ln → natural log, log → log10, and the other scalar calls). A formula string is rejected. compileScalar evaluates the same tree over Float64Array columns and marks a row invalid where evaluateScalar would throw. compileScalarGradient reuses that tape to return exact ∂f/∂parameter for each row. fitScalarParameters fits those constants to data via levenbergMarquardt, bfgs, or nelderMead (multi-restart search with optional bounds).
  • levenbergMarquardt accepts an optional analytic jacobian in LMOptions; the default remains forward-difference numericJacobian. Stiff solveODE, solveDAE, and solveBVP use the same helper (forward differences; ODE/DAE use step: { rel: 1e-7, floor: 1e-8 }, BVP shooting uses h: 1e-7).
  • minimize (Nelder–Mead) delegates to nelderMead with an absolute simplex step; nelderMead keeps relative defaults for fitScalarParameters.
  • scalarToNode / nodeToScalar / simplifyScalar / scalarToTex — convert between scalar trees and wired expression nodes (no formula string); simplifyScalar uses the same simplify as the expression language.
  • paretoFront / scoreFront / selectFrontModel — hall of fame per complexity, log-loss scores on the log scale, and accuracy / score / best selection.
  • frontToLatexTable / frontToJson — export a scored front (M15). frontToLatexTable(scored, { precision }) writes one tabular with columns complexity, loss, score, equation and a header row; loss and score are toPrecision(precision) text (default 6, an integer from 1 to 100), a null score (the linear loss scale) is an empty cell, and each equation cell is scalarToTex inside $…$ because a tabular cell is text mode and \cdot, \frac and \sin need math mode (an infinite score is $\infty$). frontToJson(scored) returns { schemaVersion: 1, rows: [{ complexity, loss, score, key, tex }] } with key = scalarCanonicalKey(item) and tex = scalarToTex(item); a non-finite loss or score is the string 'Infinity', '-Infinity' or 'NaN', so the report survives JSON.stringify and JSON.parse (an exact fit scores Infinity, and JSON would turn that into the linear scale's null). A non-finite complexity throws RangeError.
  • inferScalarDimension / solveParameterDimensions — SI dimension of a scalar tree with exact Fraction exponents on SiDimensionVector ([L,M,T,I,Θ,N,J]), or one of four named violations with the path of the failing node (add-mismatch, transcendental-arg-not-dimensionless, non-literal-exponent-on-dimensional-base, unknown-symbol). solveParameterDimensions solves the dimension of each unknown fitted parameter from the equations the tree implies (+ operands agree, transcendental arguments are dimensionless, the root equals target), exactly via rationalNullspace; it returns solved, underdetermined, or inconsistent.
  • weightedMutation / mutateConstant / mutateOperator / mutateFeature / swapOperands / addNode / insertNode / deleteNode / rotateTree / randomizeTree / simplifyConstants / identityMutation / crossoverSubtrees — variation operators over a scalar tree. Each takes (node, rng, ctx) with a SeededRng and ctx = { grammar, perturbationFactor, temperature }, never changes its input, and returns null when the mutation does not apply or the result fails scalarGrammarAccepts. weightedMutation(weights?) picks one operator by DEFAULT_MUTATION_WEIGHTS (summing to 8.512392). The identity operator is named identityMutation because identity is the matrix identity export of this package; its weight key is still identity.
  • searchScalars — evolutionary search over scalar trees, an async generator of SearchProgress that returns a ScalarSearchResult. One population evolves by tournament selection and weightedMutation; it starts from seeds plus the smallest trees of enumerateScalars (both filtered by scalarGrammarAccepts and, when set, grammar.admit; a rejected seed is skipped, a rejected mutation is a failed attempt), refits parameters of a tree with probability optimizeProbability (fitScalarParameters), and keeps a hall of fame whose Pareto front is reported through paretoFront / scoreFront (modelSelection adds selectFrontModel; bestLossFactor forwards to its bestLossFactor when modelSelection is set, default 1.5). crossoverProbability defaults to 0 (no coin, mutation-only). Above 0 each offspring may take crossoverSubtrees of two tournament parents and otherwise falls through to mutation; the inherited parameters stay the first parent's. admit must be a pure function of the node; the search does not copy it. Loss is the sum of squared errors by default (loss: 'l2'); set loss to 'l1' or 'huber' and the hall and every refit use that same named loss from fitScalarParameters. Optional rowWeights (one finite >= 0 weight per row) apply to both. An optional fit object is forwarded to fitScalarParameters: restarts (default 2), maxIterations (default 8), bounds, algorithm, and initial (default the inherited vector); a bad bound or length throws RangeError before the first yield. A tree that is invalid on any positive-weight row has loss Infinity and never reaches the front; set invalidRows: 'penalize' to score such a tree as the fitter's invalid-row penalty (1e8) times its invalid rows, plus the chosen loss on the valid rows, instead. fittedParameters gives the values behind each front row's loss. options.rng is never advanced, so one seed gives one result. runScalarSearch(problem, options) is the one-call form: it consumes the generator and returns the final result; its options take seed (exactly one of seed and rng) and its problem may give columns as a record keyed by variable name instead of an aligned array. maxEvaluations caps evaluations, initial population included. With populations > 1 independent islands (rng.fork('island:' + index)) evolve side by side; after every generation each island's worst trees are replaced by copies of other islands' best (fractionReplaced, default 0.00036), the constant-perturbation temperature cools from initialTemperature (3.17) by 0.995 per generation (annealing), and tournaments rank by loss * exp(adaptiveParsimonyScaling * share) where share is how common the tree's size is (adaptiveParsimony, scaling 1040). Annealing and adaptive parsimony default to on only for several islands, so populations: 1 is the single-population search to the last bit. parallel: { timeoutMs?, pool? } scores each generation's trees on worker threads once observed has 100 or more rows (scalar-batch.worker.js, a second tsdown entry loaded by URL); parameter refits and every random draw stay on the caller thread, so a parallel run equals the serial run for the same seed, bit for bit. complexityWeights (a ScalarComplexityWeights from @danielsimonjr/mathts-expression) weighs the nodes of the complexity the hall, the truncation tie-break, and adaptive parsimony rank by; omitted, it is today's node count, and a non-finite weight throws RangeError before the first yield. simplify ('off' | 'constants' | 'algebraic', default 'off') simplifies each seed and each bred offspring once, before the grammar check and the score: 'constants' folds numeric-only subtrees (simplifyConstants), 'algebraic' runs simplifyScalar; a ScalarBuildError, RangeError, grammar rejection (including admit), or row-wise evaluation mismatch keeps the unsimplified tree (simplifyFallbacks on the result), and enumerated trees are not simplified. timeoutMs (default 30000, must be positive) bounds every worker call, queueing included. A pool you pass is yours to terminate; otherwise the search starts one when it first needs it and terminates it when the generator ends or is closed. The 100-row floor comes from the M3 benchmark (the tape against the per-row interpreter, on the caller thread); no worker round trip was timed, so parallel is opt-in and carries no speed-up claim.
  • numericallyEquivalent — sample two scalar trees with createSeededRng, evaluate via compileScalar, compare with core nearlyEqual. Numerical agreement alone never proves symbolic equivalence; a consistent verdict is evidence, not proof.
import { createSeededRng } from '@danielsimonjr/mathts-core';
import {
  compileScalar,
  compileScalarGradient,
  fitScalarParameters,
  numericallyEquivalent,
  frontToJson,
  frontToLatexTable,
  paretoFront,
  scoreFront,
  selectFrontModel,
  scalar,
  simplifyScalar,
  scalarToTex,
  runScalarSearch,
} from '@danielsimonjr/mathts-functions';

const node = scalar.op('*', [scalar.symbol('x'), scalar.symbol('x')]);
const linear = scalar.op('+', [
  scalar.op('*', [scalar.symbol('a'), scalar.symbol('x')]),
  scalar.symbol('b'),
]);
const gradCompiled = compileScalarGradient(linear, { variables: ['x'], parameters: ['a', 'b'] });
const { values, grad } = gradCompiled.evaluate([new Float64Array([3])], [2, -1]);
values[0]; // 5
[...grad]; // 3, 1 — ∂/∂a = x, ∂/∂b = 1
const quotient = scalar.op('/', [
  scalar.symbol('a'),
  scalar.op('/', [scalar.symbol('b'), scalar.symbol('c')]),
]);
scalarToTex(simplifyScalar(quotient)); // \frac{ a\cdot c}{ b}

const compiled = compileScalar(node, { variables: ['x'] });
compiled.evaluate([new Float64Array([2, 3])]).values; // Float64Array [4, 9]

const slope = scalar.op('*', [scalar.symbol('a'), scalar.symbol('x')]);
const xCol = new Float64Array([1, 2, 3]);
const yCol = new Float64Array([2, 4, 6]);
fitScalarParameters(slope, [xCol], yCol, ['x'], { parameters: ['a'], restarts: 0 }).parameters[0]; // 2

const front = paretoFront([
  { loss: 0.5, complexity: 1, item: 'x' },
  { loss: 0.1, complexity: 3, item: 'x^3' },
]);
selectFrontModel(scoreFront(front), 'best').item; // 'x^3'
frontToJson(scoreFront([{ loss: 0.5, complexity: 1, item: scalar.symbol('x') }])).rows[0]!.key; // '{"k":"s","v":"x"}'

const rng = createSeededRng(1);
const id = scalar.op('+', [
  scalar.op('^', [scalar.call('sin', scalar.symbol('x')), scalar.number(2)]),
  scalar.op('^', [scalar.call('cos', scalar.symbol('x')), scalar.number(2)]),
]);
numericallyEquivalent(id, scalar.number(1), { domain: { x: [0, 1] }, rng }).verdict; // 'consistent' — evidence, not proof

const quadGrammar = {
  variables: ['x'],
  binary: ['+', '*', '^'],
  unary: [],
  literals: [1],
  maxSize: 7,
} as const;
const quad = scalar.op('+', [
  scalar.op('*', [scalar.symbol('x'), scalar.symbol('x')]),
  scalar.symbol('x'),
]);
const xs = new Float64Array(20);
for (let i = 0; i < 20; i += 1) xs[i] = -1 + (2 * i) / 19;
const quadResult = await runScalarSearch(
  {
    variables: ['x'],
    columns: { x: xs },
    observed: compileScalar(quad, { variables: ['x'] }).evaluate([xs]).values,
  },
  { grammar: quadGrammar, seed: 7, populationSize: 8, tournamentSize: 4, maxEvaluations: 120 }
);
quadResult.front.at(-1)?.item; // x^2 + x — the planted law, at loss 0
quadResult.stopReason; // 'max-evaluations' — runScalarSearch consumes the generator for you
  • Per-domain focused entry points are also published: @danielsimonjr/mathts-{arithmetic,trigonometry,statistics,signal}.

License

MIT (c) Daniel Simon Jr.