Math.js

repository·develop·Indexed 12 days ago

https://github.com/josdejong/mathjs

An extensive math library for JavaScript and Node.js (version 15.2.0) featuring a flexible expression parser, symbolic computation, and support for complex data types including BigNumbers, fractions, units, and matrices. It includes a CLI for evaluating expressions and a JavaScript port of CSparse for sparse matrix operations.

Tokens
65.6K
Snippets
232
Records
299
Agent score
95%

What's inside Math.js

  1. Overview of Math.js features

    develop

    Math.js is an extensive math library for JavaScript and Node.js designed for mathematical computations. Key capabilities include:

    • Flexible Expression Parser: Supports symbolic computation and parsing of mathematical strings.
    • Built-in Library: A large set of mathematical functions and constants.
    • Advanced Data Types: Integrated support for working with numbers, BigNumbers, complex numbers, fractions, units, and matrices.
    • Environment Agnostic: Works in the browser, Node.js, and any JavaScript engine.
  2. Use the math namespace for mathematical operations

    develop
    The math namespace is the primary entry point for all math.js functionality. It contains all mathematical functions and constants. While many data types have their own classes, it is recommended to use the functions within the math namespace to interact with these types rather than calling class methods directly. This ensures better compatibility and follows the intended library pattern.
  3. Element-wise operations between Sparse matrices

    develop

    Element-wise operations between two SparseMatrix objects can vary in complexity and output type. The choice of algorithm affects whether the result remains sparse or becomes dense.

    Available Algorithms

    AlgorithmMnemonicInputOutput TypeDescription
    04SidSidx(sparse, sparse)SparseMatrixOperates on nonzero values where both matrices have a value at (i,j).
    05SfSfx(sparse, sparse)SparseMatrixOperates on every nonzero value in either A or B.
    06S0S0x(sparse, sparse)SparseMatrixOperates only when both matrices contain a value at (i,j), otherwise result is 0.
    07SSfx(sparse, sparse)DenseMatrixIterates over all values of both matrices.
    08S0Sidx(sparse, sparse)SparseMatrixOperates when both have values; uses A's value if B's is zero.
    09S0Sfx(sparse, sparse)SparseMatrixIterates only on nonzero values of matrix A.

    Algorithm Details

    Algorithm 04 (SidSid)

    • Logic: Cij = x(Aij, Bij) if Aij != 0 && Bij != 0. If only one is non-zero, the result is that non-zero value.
    • Complexity: Invokes x() NZ times (number of overlapping nonzero items).

    Algorithm 05 (SfSf)

    • Logic: Cij = x(Aij, Bij) if Aij != 0 || Bij != 0, else 0.
    • Complexity: Invokes x() for all nonzero values in A, B, and their intersection.

    Algorithm 06 (S0S0)

    • Logic: Cij = x(Aij, Bij) if Aij != 0 && Bij != 0, else 0.
    • Complexity: Invokes x() NZ times (overlapping nonzero items).

    Algorithm 07 (SSf)

    • Logic: Cij = x(Aij, Bij) for all indices.
    • Complexity: Invokes x() M*N times.

    Algorithm 08 (S0Sid)

    • Logic: Cij = x(Aij, Bij) if Aij != 0 && Bij != 0. If Aij != 0 and Bij == 0, Cij = Aij.
    • Complexity: Invokes x() NZ times (overlapping nonzero items).

    Algorithm 09 (S0Sf)

    • Logic: Cij = x(Aij, Bij) if Aij != 0, else 0.
    • Complexity: Invokes x() NZA times (nonzero items in A).
  4. Work with Objects in math.js expressions

    develop

    Objects in math.js use curly brackets {} with comma-separated key: value pairs. Keys can be symbols (e.g., prop) or strings (e.g., "prop").

    Important Difference from JavaScript: When setting a property value using assignment (e.g., obj.prop = 43), math.js returns the entire object rather than the newly assigned value.

    math.evaluate('{a: 2 + 1, b: 4}')         // {a: 3, b: 4}
    math.evaluate('{"a": 2 + 1, "b": 4}')     // {a: 3, b: 4}
    
    // Retrieve properties
    math.evaluate('obj.prop', scope)          // 42
    
    // Set properties (returns the whole object!)
    math.evaluate('obj.prop = 43', scope)     // {prop: 43}
  5. Element-wise operations between Dense and Sparse matrices

    develop

    When performing element-wise operations between a DenseMatrix and a SparseMatrix, the implementation follows one of several algorithms depending on the desired output type and efficiency requirements. These algorithms are identified by mnemonics in the source code using the format numberxmnemonic (e.g., 01xDSid).

    Available Algorithms

    AlgorithmMnemonicInputOutput TypeDescription
    01DSidx(dense, sparse)DenseMatrixClones the dense matrix and updates it using only the nonzero items from the sparse matrix.
    02DS0x(dense, sparse)SparseMatrixIterates over nonzero items in the sparse matrix. Result is sparse because zero elements in the sparse matrix result in zero in the output.
    03DSfx(dense, sparse)DenseMatrixIterates over all items (nonzero and zero) in the sparse matrix.

    Algorithm Details

    Algorithm 01 (DSid)

    • Logic: Cij = x(Dij, Sij) if Sij != 0, else Cij = Dij.
    • Complexity: Invokes x() NZ times (number of nonzero items in SparseMatrix).

    Algorithm 02 (DS0)

    • Logic: Cij = x(Dij, Sij) if Sij != 0, else Cij = 0.
    • Complexity: Invokes x() NZ times.

    Algorithm 03 (DSf)

    • Logic: Cij = x(Dij, Sij) if Sij != 0, else Cij = x(Dij, 0).
    • Complexity: Invokes x() M*N times (total elements).
  6. Use multiple reviver functions with JSON.parse

    develop

    If you are using math.js alongside other custom data types that require their own reviver functions, you can combine them by cascading the functions within a single wrapper function.

    const reviver = function (key, value) {
      return reviver1(key, reviver2(key, value))
    }
  7. Work with Matrices

    develop

    Math.js provides two types of matrix classes for different storage needs:

    • DenseMatrix: For standard, densely populated matrices.
    • SparseMatrix: For matrices where most elements are zero.

    Note: It is not recommended to use the Matrix class API directly. Prefer using the functions in the math namespace for matrix operations.

  8. Understand mathjs bundle size composition

    develop

    When optimizing your bundle, be aware that even if you only select a few functions, the bundle size may be larger than expected because functions often depend on heavy data classes.

    A typical mathjs bundle composition is roughly:

    • ~5%: Core functionality (create, import, factory, typed-function, etc.).
    • ~30%: Data classes (Complex, BigNumber, Fraction, Unit, SparseMatrix, DenseMatrix).
    • ~25%: Expression parser (half of which is embedded documentation).
    • ~40%: Built-in functions (approx. 200) and constants.

    To analyze your specific bundle, it is recommended to use a tool like source-map-explorer.

  9. Use factory functions for dependency injection

    develop

    When importing functions from separate files, they may not have access to the specific math.js instance they are being imported into. Factory functions solve this by allowing you to inject dependencies (like multiply or unaryMinus) at creation time.

    This pattern ensures your functions work correctly across different math.js configurations (e.g., when switching between standard numbers and BigNumber or Decimal).

    Syntax

    factory(name: string, dependencies: string[], create: function, meta?: Object)

    • name: The name of the created function.
    • dependencies: An array of names of the functions/values to inject.
    • create: A function that receives an object containing the dependencies as its first argument.
    • meta: An optional object for configuration:
      • isClass: If true, the function is treated as a class (not exposed in the expression parser).
      • lazy: If true (default), the function is only constructed when used. Set lazy: false to force immediate creation.
      • isTransformFunction: If true, it is imported only in the internal mathWithTransform namespace for the parser.
      • recreateOnConfigChange: If true, the factory is re-run when math.js configuration changes (useful for constants like pi).
      • formerly: A string providing a deprecated synonym for the function name.
    import { factory, create, all } from 'mathjs'
    
    // Define the factory
    const name = 'negativeSquare'
    const dependencies = ['multiply', 'unaryMinus']
    const createNegativeSquare = factory(name, dependencies, function ({ multiply, unaryMinus }) {
        return function negativeSquare (x) {
          return unaryMinus(multiply(x, x))
        }
      })
    
    // Import the factory into a mathjs instance
    const math = create(all)
    math.import(createNegativeSquare)
    
    console.log(math.negativeSquare(4)) // -16
    console.log(math.evaluate('negativeSquare(5)')) // -25
  10. Choose between dense and sparse matrix storage

    develop

    Math.js supports two storage types for matrices. Choosing the correct one can significantly impact memory usage and calculation speed:

    • Dense matrix ('dense'): The default type. It supports multidimensional matrices and is suitable for matrices where most elements are non-zero.
    • Sparse matrix ('sparse'): A two-dimensional implementation optimized for matrices that contain mostly zeros. It saves memory and can speed up calculations for such data.

    You can specify the type when using construction functions like math.matrix, math.diag, math.identity, math.ones, and math.zeros by passing 'sparse' as an argument.

    // create sparse matrices
    const m1 = math.matrix([[0, 1], [0, 0]], 'sparse')
    const m2 = math.identity(1000, 1000, 'sparse')
  11. Understand the Mathjs library structure and interfaces

    develop

    Mathjs is written in JavaScript, but provides TypeScript definitions for its core patterns. The library operates through three primary interfaces:

    1. MathJsInstance: The core interface returned by the create function. It contains all mathjs functions and constants as standard methods.
    2. MathJsChain: An interface returned by the chain function. It allows for method chaining (e.g., chain(2).add(3).done()). In this interface, methods are defined with the chain instance this as the first argument.
    3. Static Exports: The library also provides a static instance where functions can be imported directly (e.g., import { add } from 'mathjs').

    Additionally, the library exports collections of factory functions. You can create a custom instance by passing dependencies to create, such as create(addDependencies) or create(all) to import everything.

    // Using the core instance
    import { create, all } from 'mathjs';
    const math = create(all);
    const result = math.add(2, 3);
    
    // Using the chain interface
    const chainedResult = math.chain(2).add(3).done();
    
    // Using static imports
    import { add } from 'mathjs';
    const staticResult = add(2, 3);