Multik Documentation

repository·develop·Indexed 20 days ago

https://github.com/kotlin/multik

A multi-dimensional array library for Kotlin providing high-performance array operations, linear algebra, and statistical functions. It supports Kotlin Multiplatform and Jupyter Notebooks, offering the mk API for math, statistics, and linear algebra, as well as tools for indexing, slicing, and in-place operations.

Tokens
62.1K
Snippets
261
Records
347
Agent score
71%

What's inside Multik

  1. What is Multik?

    develop

    Multik is a multiplatform library for multidimensional array operations in Kotlin. It provides high-speed mathematical and arithmetic operations, linear algebra, statistical procedures, and transformation/sorting utilities.

    Key advantages over the Kotlin standard library include:

    • Reduced Complexity: Simplifies matrix and multidimensional array manipulations.
    • Improved Readability: Uses intuitive operators (like * for element-wise multiplication) instead of nested loops.
    • Static Typing & Dimensional Consistency: Detects data type and dimension mismatches at compile-time.
    • High Performance: Uses contiguous memory blocks and optional native backends like OpenBLAS.
    val a = mk.ndarray(mk[mk[1, 2, 3], mk[4, 5, 6]])
    val b = mk.ndarray(mk[mk[7, 8, 9], mk[10, 11, 12]])
    val c = a * b
    println(c)
    /*
    [[7, 16, 27],
     [40, 55, 72]]
     */
  2. Perform descriptive statistics with mk.stat

    develop

    Multik provides statistical operations on ndarray objects through the mk.stat entry point. The available functions are defined in the Statistics interface and allow you to calculate descriptive statistics for all elements in an array or along a specific axis.

    Available functions:

    • mean: Calculates the arithmetic mean of all elements, or along a specified axis.
    • median: Calculates the median value of all elements.
    • average: Calculates the weighted average of all elements.
    val a = mk.ndarray(mk[1.0, 2.0, 3.0, 4.0, 5.0])
    
    mk.stat.mean(a)      // 3.0
    mk.stat.median(a)    // 3.0
    mk.stat.average(a)   // 3.0 (uniform weights)
  3. Use universal operations on Multik NDArrays

    develop
    Universal operations are extension functions on MultiArray<T, D> that follow Kotlin collection conventions. They allow you to perform collection-style transformations, filtering, aggregation, and conversions directly on NDArrays. These operations are categorized into several functional groups such as Predicates, Traversal, Transformation, Filtering, Aggregation, Search, Ordering, Partitioning, Grouping, and Conversion.
  4. Perform linear algebra operations with mk.linalg

    develop

    Multik provides linear algebra operations for 2D matrices and 1D vectors through the mk.linalg entry point. These operations are implemented via the LinAlg and LinAlgEx interfaces and are accessible through convenience extension functions and infix operators.

    Supported operations include:

    • Dot product: dot (matrix-matrix, matrix-vector, or vector-vector product).
    • Matrix inverse: inv (inverse of a square matrix).
    • Matrix power: pow (raise a square matrix to an integer power).
    • Decompositions: qr (QR decomposition), plu (PLU decomposition), and svd (Singular Value Decomposition, experimental).
    • Eigenvalues: eig (eigenvalues and eigenvectors) and eigVals (eigenvalues only).
    • Solving systems: solve (solves the linear system Ax = b).
    • Norms: norm (matrix or vector norms such as Frobenius, 1-norm, infinity, and max).
    val a = mk.ndarray(mk[mk[1.0, 2.0], mk[3.0, 4.0]])
    val b = mk.ndarray(mk[mk[5.0, 6.0], mk[7.0, 8.0]])
    
    // Dot product
    val c = mk.linalg.dot(a, b)        // 2×2 matrix product
    
    // Inverse
    val aInv = mk.linalg.inv(a)        // inverse of a
    
    // Solve Ax = b
    val x = mk.linalg.solve(a, mk.ndarray(mk[1.0, 2.0]))
  5. Understand the difference between views and copies in Multik

    develop

    In Multik, arrays can either be views or copies:

    • View: An ndarray that references the same underlying data as another array. Views are memory-efficient and fast, but any changes made to the original (base) array will be reflected in the view.
    • Copy: An ndarray that owns its own independent storage. Changes to the original array do not affect a copy.

    You can check if an array is a view by inspecting its base property; if base != null, the array is a view.

  6. Use mathematical constants with Multik

    develop

    Multik does not provide its own mathematical constants. For standard mathematical operations on NDArrays, use the constants provided by the Kotlin standard library kotlin.math (such as PI and E).

    import kotlin.math.PI
    import kotlin.math.E
    
    val angles = mk.linspace<Double>(0, 1, 100) * PI  // 0 to PI
    val exponential = mk.d1array(5) { E.pow(it.toDouble()) }
  7. Understand the NDArray core data type

    develop

    The NDArray is Multik's fundamental data type for dense, homogeneous numeric data. It is characterized by four key properties:

    • Dimension (dim): The number of axes (e.g., D1, D2, D3, D4, or DN).
    • Shape (shape): The sizes of each axis (e.g., (2, 3)).
    • Size (size): The total number of elements in the array.
    • DType (dtype): The type of elements stored (e.g., Int, Double, or complex types).
  8. Understand the NDArray class

    develop

    The NDArray is Multik's core multidimensional array. It implements MutableMultiArray and is parameterized by the element type T and a dimension marker D (such as D1, D2, D3, D4, or DN).

    Common type aliases for convenience include:

    • D1Array<T>
    • D2Array<T>
    • D3Array<T>
    • D4Array<T>
    // Example of using a type alias for a 2D array
    val matrix: D2Array<Double> = ...
  9. How Multik achieves high performance

    develop

    Multik's performance is driven by two main factors:

    1. Contiguous Memory: At the core of an NDArray is a single primitive array. Even for multidimensional arrays (e.g., 3D), the data resides in a single contiguous memory block. This is significantly faster than Kotlin's standard library approach of using arrays of objects scattered across memory.
    2. Native Backends: For maximum performance, Multik can push operations to native code using the OpenBLAS library, which provides highly optimized implementations of mathematical routines.
  10. Use Slice for range-based indexing

    develop

    A Slice represents a start–stop–step range along a single axis. It implements Indexing and ClosedRange<Int>.

    Properties:

    • start: First index (inclusive). Use -1 to indicate the beginning.
    • stop: Last index (inclusive). Use -1 to indicate the end.
    • step: Stride between elements. Must be a positive integer.

    Creating Slices:

    • Via RInt: 0.r..4.r results in Slice(0, 4, 1).
    • From IntRange: (0..4).toSlice().
    • With step: 0..4..2 results in Slice(0, 4, 2).
    • Using sl helpers: sl.first..3 (start to 3), 2..sl.last (2 to end), or sl.bounds (entire axis).
    // Example of slice creation patterns
    val s1 = 0.r..4.r          // Slice(0, 4, 1)
    val s2 = 0.r until 4.r      // Slice(0, 3, 1)
    val s3 = 0..4..2            // Slice(0, 4, 2)
  11. Core concepts of Multik

    develop

    The central type in Multik is NDArray, which acts as a container for dense, homogeneous numeric data. To work with Multik, you should understand these key terms:

    • Dimension (dim): The number of axes (e.g., 1D, 2D, 3D, or ND).
    • Shape (shape): The size of each axis (e.g., (2, 3) for a 2x3 matrix).
    • Strides (strides): The steps in storage required to move along each axis.
    • DType (dtype): The element type of the array (e.g., Int, Double, or complex numbers).
    • Engine: The execution backend responsible for performing math, linear algebra, and statistics operations.
  12. Multik vs NumPy: Key differences and workarounds

    develop

    If you are coming from NumPy, be aware of the following differences:

    FeatureMultik SupportWorkaround
    BroadcastingNoUse .map or create a same-shaped array of the scalar value.
    Boolean Masks / whereNoUse filter, mapIndexed, or manual iteration.
    Matrix MultiplicationYesUse mk.linalg.dot(a, b) or a dot b. (Note: * is element-wise).

    Example: Filtering (instead of boolean masking)

    val a = mk.ndarray(mk[1, 2, 3, 4, 5])
    a.filter { it > 3 } // Returns [4, 5]