np.mgrid and np.ogrid¶
Mental Model
mgrid builds full coordinate grids using slice syntax (like meshgrid but more compact), while ogrid returns open grids -- just the 1D vectors with shapes set up for broadcasting. Use ogrid when you want memory efficiency and mgrid when you need the fully materialized grid.
The deeper distinction is explicit grid vs broadcast grid: mgrid (and
meshgrid without sparse=True) materializes every coordinate pair into full
arrays, while ogrid (and meshgrid(sparse=True)) stores only the 1D axes
and lets broadcasting expand them on the fly. For a 1000x1000 grid, this is
the difference between 8 MB and 16 KB of coordinate storage.
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¶
```python 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.
```python 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.
```python 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'.
```python 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.
```python 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¶
```python 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¶
```python 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.
```python 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.
```python 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¶
```python 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.
```python 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¶
```python 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¶
```python 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¶
```python 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¶
```python 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() ```
When to Use Which¶
text
meshgrid → explicit grids, visualization, when you have pre-built 1D arrays
mgrid → concise syntax, small-to-medium grids, quick prototyping
ogrid → large grids, memory efficiency, broadcasting-aware computation
In practice: start with meshgrid for clarity. Switch to ogrid when memory matters (large grids). Use mgrid when you want compact slice syntax without worrying about memory.
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¶
```python 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() ```
Exercises¶
Exercise 1.
Use np.mgrid to create a 2D integer grid from 0 to 3 in both dimensions. Print the two resulting arrays and their shapes.
Solution to Exercise 1
import numpy as np
Y, X = np.mgrid[0:4, 0:4]
print(f"Y shape: {Y.shape}")
print(f"X shape: {X.shape}")
print(f"Y:\n{Y}")
print(f"X:\n{X}")
Exercise 2.
Use np.ogrid to create open grids for x in [0, 4] and y in [0, 3] (integer steps). Compute x + y using broadcasting and verify the result matches the equivalent np.mgrid computation.
Solution to Exercise 2
import numpy as np
y_o, x_o = np.ogrid[0:4, 0:4]
result_ogrid = y_o + x_o
Y, X = np.mgrid[0:4, 0:4]
result_mgrid = Y + X
print(f"ogrid result:\n{result_ogrid}")
print(f"Match: {np.array_equal(result_ogrid, result_mgrid)}")
Exercise 3.
Use np.mgrid with complex step notation to create a float grid: np.mgrid[0:1:5j, 0:2:3j] (5 points from 0 to 1, 3 points from 0 to 2). Print the resulting shapes and values.
Solution to Exercise 3
import numpy as np
Y, X = np.mgrid[0:1:5j, 0:2:3j]
print(f"Y shape: {Y.shape}") # (5, 3)
print(f"X shape: {X.shape}") # (5, 3)
print(f"Y:\n{Y}")
print(f"X:\n{X}")