Mojo NumPy Integration

NumPy is Python's most-used numerical computing library. It provides multi-dimensional arrays, mathematical functions, linear algebra routines, and random number generation — all backed by optimized C and Fortran code. Mojo runs NumPy directly through its Python interop layer, letting you combine NumPy's rich ecosystem with Mojo's native performance for the computationally heavy parts.

The Collaboration Model

  NumPy's strengths                Mojo's strengths
  ─────────────────────            ─────────────────────────
  Array creation and I/O           SIMD vectorized loops
  Broadcasting rules               Manual memory control
  Linear algebra (LAPACK)          Zero-overhead parallelism
  Random number generation         Compile-time specialization
  Plotting integration             Direct hardware targeting

  Best strategy:
  Load data with NumPy → compute hot loops in Mojo → output with NumPy

Importing NumPy

from python import Python

fn main() raises:
    var np = Python.import_module("numpy")
    print(np.__version__)   # e.g. 1.26.4

Creating NumPy Arrays

from python import Python

fn main() raises:
    var np = Python.import_module("numpy")

    # From a Python list
    var a = np.array([1.0, 2.0, 3.0, 4.0, 5.0])
    print(a)           # [1. 2. 3. 4. 5.]
    print(a.shape)     # (5,)
    print(a.dtype)     # float64

    # 2D array (matrix)
    var mat = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
    print(mat)
    print(mat.shape)   # (3, 3)

    # Zeros, ones, range
    var zeros = np.zeros([4, 4])
    var ones  = np.ones([2, 3])
    var rng   = np.arange(0, 10, 2)   # [0 2 4 6 8]
    var space = np.linspace(0.0, 1.0, 5)  # [0. 0.25 0.5 0.75 1.]
    print(rng)
    print(space)

NumPy Array Operations

from python import Python

fn main() raises:
    var np = Python.import_module("numpy")

    var a = np.array([10.0, 20.0, 30.0, 40.0])
    var b = np.array([ 1.0,  2.0,  3.0,  4.0])

    # Element-wise arithmetic
    print(a + b)    # [11. 22. 33. 44.]
    print(a * b)    # [10. 40. 90. 160.]
    print(a / b)    # [10. 10. 10. 10.]

    # Scalar broadcast
    print(a * 2.0)  # [20. 40. 60. 80.]
    print(a + 5.0)  # [15. 25. 35. 45.]

    # Reductions
    print(np.sum(a))    # 100.0
    print(np.mean(a))   # 25.0
    print(np.max(a))    # 40.0
    print(np.min(a))    # 10.0
    print(np.std(a))    # standard deviation

NumPy Linear Algebra

from python import Python

fn main() raises:
    var np = Python.import_module("numpy")

    var A = np.array([[2.0, 1.0], [5.0, 3.0]])
    var b = np.array([4.0, 7.0])

    # Solve the linear system Ax = b
    var x = np.linalg.solve(A, b)
    print(x)       # [5. -6.]  — the solution

    # Matrix multiply
    var C = np.array([[1.0, 2.0], [3.0, 4.0]])
    var D = np.array([[5.0, 6.0], [7.0, 8.0]])
    print(np.matmul(C, D))
    # [[19. 22.]
    #  [43. 50.]]

    # Determinant and inverse
    print(np.linalg.det(C))    # -2.0
    print(np.linalg.inv(C))

Passing NumPy Arrays to Mojo Functions

The key performance pattern: use NumPy to set up data, then hand a raw buffer pointer to Mojo code for the heavy computation loop.

from python import Python, PythonObject
from memory import UnsafePointer

fn mojo_scale(data: UnsafePointer[Float64], n: Int, factor: Float64):
    for i in range(n):
        data[i] *= factor

fn main() raises:
    var np = Python.import_module("numpy")
    var ctypes = Python.import_module("ctypes")

    # Create a NumPy array of float64
    var arr = np.array([1.0, 2.0, 3.0, 4.0, 5.0], dtype=np.float64)
    print("Before:", arr)   # [1. 2. 3. 4. 5.]

    # Get a raw C pointer to the array's buffer
    var n = int(arr.size)
    var ptr_val = arr.ctypes.data_as(ctypes.POINTER(ctypes.c_double))
    var raw = UnsafePointer[Float64].address_of(ptr_val[0])

    # Run Mojo computation directly on NumPy's memory
    mojo_scale(raw, n, 3.0)

    print("After:", arr)   # [3. 6. 9. 12. 15.] — NumPy array modified in-place
Memory sharing diagram:

  NumPy array in Python
  ┌──────────────────────────────────┐
  │  buffer: [ 1.0 | 2.0 | 3.0 ... ] │
  │           ↑                      │
  │         data pointer             │
  └──────────────────────────────────┘
                  │
                  │  UnsafePointer points here
                  ↓
  Mojo mojo_scale() operates directly on the same memory.
  No copy. No overhead.

NumPy Random Numbers

from python import Python

fn main() raises:
    var np = Python.import_module("numpy")
    var rng = np.random.default_rng(seed=42)

    # Uniform random floats in [0, 1)
    var uniform = rng.random(size=5)
    print(uniform)

    # Normal distribution (mean=0, std=1)
    var normal = rng.standard_normal(size=5)
    print(normal)

    # Random integers in [0, 100)
    var ints = rng.integers(0, 100, size=5)
    print(ints)

    # Shuffle an array
    var data = np.arange(10)
    rng.shuffle(data)
    print(data)

NumPy Indexing and Slicing

from python import Python

fn main() raises:
    var np = Python.import_module("numpy")
    var m = np.arange(12).reshape([3, 4])
    print(m)
    # [[ 0  1  2  3]
    #  [ 4  5  6  7]
    #  [ 8  9 10 11]]

    print(m[1, 2])       # 6   — row 1, col 2
    print(m[0])          # [0 1 2 3]  — first row
    print(m[:, 1])       # [1 5 9]    — second column
    print(m[0:2, 1:3])   # [[1 2] [5 6]]  — submatrix
    print(m[m > 5])      # [6 7 8 9 10 11]  — boolean mask

Saving and Loading NumPy Data

from python import Python

fn main() raises:
    var np = Python.import_module("numpy")

    # Save to binary .npy file
    var weights = np.random.default_rng(0).standard_normal(size=[128, 64])
    np.save("weights.npy", weights)
    print("Saved:", weights.shape)

    # Load back
    var loaded = np.load("weights.npy")
    print("Loaded:", loaded.shape)   # (128, 64)

    # Save multiple arrays to .npz archive
    var biases = np.zeros(64)
    np.savez("model.npz", weights=weights, biases=biases)

    # Load from archive
    var archive = np.load("model.npz")
    print(archive["weights"].shape)  # (128, 64)
    print(archive["biases"].shape)   # (64,)

Key Takeaways

Import NumPy with Python.import_module("numpy") inside any Mojo function. NumPy arrays support element-wise arithmetic, broadcasting, and reductions with one-line calls. Use np.linalg for solving linear systems, inverses, and determinants. Share memory between NumPy and Mojo by extracting the array's data pointer with ctypes and casting it to an UnsafePointer — zero copy, full Mojo speed on NumPy's buffer. Save arrays to .npy binary files for fast reloading and use .npz archives for multi-array checkpoints. The golden workflow: load and preprocess with NumPy, compute hot loops with Mojo, output and visualize with Python.

Leave a Comment

Your email address will not be published. Required fields are marked *