Skip to content

np.mgrid and np.ogrid

np.mgrid Basics

np.mgrid is an index trick that creates dense multi-dimensional mesh grids using slice notation. Unlike np.meshgrid, which takes arrays as arguments, np.mgrid uses Python's slice syntax directly.

1. Basic Syntax

import numpy as np

def main():
    # np.mgrid[start:stop:step, start:stop:step]
    X, Y = np.mgrid[0:3, 0:4]

    print("X :")
    print(X)
    print()
    print("Y :")
    print(Y)

if __name__ == "__main__":
    main()

Output:

X :
[[0 0 0 0]
 [1 1 1 1]
 [2 2 2 2]]

Y :
[[0 1 2 3]
 [0 1 2 3]
 [0 1 2 3]]

2. Integer Step

With integer step, np.mgrid works like np.arange for each dimension.

import numpy as np

def main():
    # Step of 2
    X, Y = np.mgrid[0:6:2, 0:9:3]

    print("X (step=2) :")
    print(X)
    print()
    print("Y (step=3) :")
    print(Y)

if __name__ == "__main__":
    main()

Output:

X (step=2) :
[[0 0 0]
 [2 2 2]
 [4 4 4]]

Y (step=3) :
[[0 3 6]
 [0 3 6]
 [0 3 6]]

3. Complex Step

When the step is a complex number (e.g., 5j), the integer part specifies the number of points, and the stop value is included.

import numpy as np

def main():
    # 5j means 5 points from start to stop (inclusive)
    X, Y = np.mgrid[0:4:5j, 0:2:3j]

    print("X (5 points from 0 to 4) :")
    print(X)
    print()
    print("Y (3 points from 0 to 2) :")
    print(Y)

if __name__ == "__main__":
    main()

Output:

X (5 points from 0 to 4) :
[[0. 0. 0.]
 [1. 1. 1.]
 [2. 2. 2.]
 [3. 3. 3.]
 [4. 4. 4.]]

Y (3 points from 0 to 2) :
[[0. 1. 2.]
 [0. 1. 2.]
 [0. 1. 2.]
 [0. 1. 2.]
 [0. 1. 2.]]

mgrid vs meshgrid

1. Equivalent Results

np.mgrid with complex step produces the same result as np.meshgrid with indexing='ij'.

import numpy as np

def main():
    # Using np.meshgrid
    x = np.linspace(0, 2, 3)
    y = np.linspace(0, 3, 4)
    X1, Y1 = np.meshgrid(x, y, indexing='ij')

    # Using np.mgrid
    X2, Y2 = np.mgrid[0:2:3j, 0:3:4j]

    print("meshgrid X shape:", X1.shape)
    print("mgrid X shape:", X2.shape)
    print()
    print("Arrays equal:", np.allclose(X1, X2) and np.allclose(Y1, Y2))

if __name__ == "__main__":
    main()

Output:

meshgrid X shape: (3, 4)
mgrid X shape: (3, 4)

Arrays equal: True

2. Indexing Convention

np.mgrid uses matrix (ij) indexing by default, while np.meshgrid uses Cartesian (xy) indexing.

import numpy as np

def main():
    # mgrid: first index varies along first axis
    X_mgrid, Y_mgrid = np.mgrid[0:2:3j, 0:3:4j]

    # meshgrid xy: first input varies along second axis
    x = np.linspace(0, 2, 3)
    y = np.linspace(0, 3, 4)
    X_xy, Y_xy = np.meshgrid(x, y, indexing='xy')

    print(f"mgrid shape: {X_mgrid.shape}")      # (3, 4)
    print(f"meshgrid xy shape: {X_xy.shape}")   # (4, 3)

if __name__ == "__main__":
    main()

3. Syntax Comparison

import numpy as np

def main():
    # meshgrid approach
    x = np.linspace(-1, 1, 50)
    y = np.linspace(-1, 1, 50)
    X1, Y1 = np.meshgrid(x, y)

    # mgrid approach (more concise)
    Y2, X2 = np.mgrid[-1:1:50j, -1:1:50j]

    # Note: mgrid order is reversed for xy convention
    print("Both create 50x50 grids")
    print(f"meshgrid shape: {X1.shape}")
    print(f"mgrid shape: {X2.shape}")

if __name__ == "__main__":
    main()

np.ogrid Basics

np.ogrid creates open (sparse) grids that broadcast to the full grid shape without allocating full memory.

1. Basic Syntax

import numpy as np

def main():
    # ogrid returns 1D arrays shaped for broadcasting
    X, Y = np.ogrid[0:3, 0:4]

    print("X shape:", X.shape)
    print("Y shape:", Y.shape)
    print()
    print("X :")
    print(X)
    print()
    print("Y :")
    print(Y)

if __name__ == "__main__":
    main()

Output:

X shape: (3, 1)
Y shape: (1, 4)

X :
[[0]
 [1]
 [2]]

Y :
[[0 1 2 3]]

2. Broadcasting Behavior

Operations on ogrid arrays broadcast to the full grid.

import numpy as np

def main():
    X, Y = np.ogrid[0:3, 0:4]

    # Broadcasting creates full grid
    Z = X + Y

    print(f"X shape: {X.shape}")
    print(f"Y shape: {Y.shape}")
    print(f"Z shape: {Z.shape}")
    print()
    print("Z = X + Y :")
    print(Z)

if __name__ == "__main__":
    main()

Output:

X shape: (3, 1)
Y shape: (1, 4)
Z shape: (3, 4)

Z = X + Y :
[[0 1 2 3]
 [1 2 3 4]
 [2 3 4 5]]

3. Complex Step

Like mgrid, ogrid supports complex step for linspace-like behavior.

import numpy as np

def main():
    X, Y = np.ogrid[0:1:5j, 0:2:3j]

    print("X (5 points, 0 to 1):")
    print(X)
    print()
    print("Y (3 points, 0 to 2):")
    print(Y)

if __name__ == "__main__":
    main()

Output:

X (5 points, 0 to 1):
[[0.  ]
 [0.25]
 [0.5 ]
 [0.75]
 [1.  ]]

Y (3 points, 0 to 2):
[[0. 1. 2.]]

Memory Comparison

1. Dense vs Sparse

import numpy as np

def main():
    n = 1000

    # Dense grid (mgrid)
    X_dense, Y_dense = np.mgrid[0:1:n*1j, 0:1:n*1j]

    # Sparse grid (ogrid)
    X_sparse, Y_sparse = np.ogrid[0:1:n*1j, 0:1:n*1j]

    dense_bytes = X_dense.nbytes + Y_dense.nbytes
    sparse_bytes = X_sparse.nbytes + Y_sparse.nbytes

    print(f"Dense (mgrid): {dense_bytes:,} bytes")
    print(f"Sparse (ogrid): {sparse_bytes:,} bytes")
    print(f"Ratio: {dense_bytes / sparse_bytes:.0f}x")

if __name__ == "__main__":
    main()

Output:

Dense (mgrid): 16,000,000 bytes
Sparse (ogrid): 16,000 bytes
Ratio: 1000x

2. Equivalent Computation

Both produce identical results when used in computations.

import numpy as np

def f(X, Y):
    return np.sin(X) * np.cos(Y)

def main():
    # Dense
    X_d, Y_d = np.mgrid[-np.pi:np.pi:100j, -np.pi:np.pi:100j]
    Z_dense = f(X_d, Y_d)

    # Sparse
    X_s, Y_s = np.ogrid[-np.pi:np.pi:100j, -np.pi:np.pi:100j]
    Z_sparse = f(X_s, Y_s)

    print(f"Results equal: {np.allclose(Z_dense, Z_sparse)}")
    print(f"Output shape: {Z_sparse.shape}")

if __name__ == "__main__":
    main()

3. When to Use Each

import numpy as np

def main():
    """
    Use mgrid when:
    - You need the full coordinate arrays
    - Memory is not a concern
    - Working with small grids

    Use ogrid when:
    - Memory efficiency matters
    - Working with large grids
    - Only computing derived values (not storing coordinates)
    """

    # ogrid for large computation
    X, Y = np.ogrid[-10:10:1000j, -10:10:1000j]
    Z = np.exp(-(X**2 + Y**2) / 10)

    print(f"Computed {Z.size:,} values")
    print(f"Coordinate memory: {X.nbytes + Y.nbytes:,} bytes")

if __name__ == "__main__":
    main()

Practical Examples

1. Distance Matrix

import numpy as np

def main():
    # Points in 1D
    points = np.array([0, 1, 4, 7, 10])
    n = len(points)

    # Use ogrid for indices
    i, j = np.ogrid[0:n, 0:n]

    # Pairwise distances
    distances = np.abs(points[i] - points[j])

    print("Points:", points)
    print()
    print("Distance matrix:")
    print(distances)

if __name__ == "__main__":
    main()

Output:

Points: [ 0  1  4  7 10]

Distance matrix:
[[ 0  1  4  7 10]
 [ 1  0  3  6  9]
 [ 4  3  0  3  6]
 [ 7  6  3  0  3]
 [10  9  6  3  0]]

2. Circular Mask

import numpy as np

def main():
    size = 11
    radius = 4

    # Center coordinates
    Y, X = np.ogrid[0:size, 0:size]
    center = size // 2

    # Distance from center
    dist = np.sqrt((X - center)**2 + (Y - center)**2)

    # Circular mask
    mask = dist <= radius

    print("Circular mask:")
    print(mask.astype(int))

if __name__ == "__main__":
    main()

3. 2D Gaussian

import numpy as np

def main():
    # Parameters
    mu_x, mu_y = 0, 0
    sigma = 1

    # Sparse grid
    Y, X = np.ogrid[-3:3:100j, -3:3:100j]

    # 2D Gaussian (unnormalized)
    Z = np.exp(-((X - mu_x)**2 + (Y - mu_y)**2) / (2 * sigma**2))

    print(f"Shape: {Z.shape}")
    print(f"Max value: {Z.max():.4f}")
    print(f"Value at (0,0): {Z[50, 50]:.4f}")

if __name__ == "__main__":
    main()

Summary Table

Feature np.meshgrid np.mgrid np.ogrid
Input Arrays Slice notation Slice notation
Default indexing xy (Cartesian) ij (matrix) ij (matrix)
Output Dense arrays Dense arrays Sparse arrays
Memory Full allocation Full allocation Minimal
Complex step No Yes Yes
Stop inclusive Depends on input With complex step With complex step

Quick Reference

import numpy as np

def main():
    # All create equivalent grids for computation

    # Method 1: meshgrid with arrays
    x = np.linspace(0, 1, 5)
    y = np.linspace(0, 1, 5)
    X1, Y1 = np.meshgrid(x, y)

    # Method 2: mgrid with complex step
    X2, Y2 = np.mgrid[0:1:5j, 0:1:5j]

    # Method 3: ogrid (memory efficient)
    X3, Y3 = np.ogrid[0:1:5j, 0:1:5j]

    # All produce same Z
    Z1 = X1**2 + Y1.T**2  # Note: transpose for xy indexing
    Z2 = X2**2 + Y2**2
    Z3 = X3**2 + Y3**2

    print("All methods work for grid computation")

if __name__ == "__main__":
    main()