β¨ Flexible rectilinear grids in Julia
RectiGrids.jl provides a clean efficient way to create and work with rectilinear grids - essentially cartesian products of values across multiple dimensions. Perfect for parameter sweeps, coordinate systems, and any scenario where you need structured multi-dimensional data.
julia> using RectiGrids
# Create a simple 2D grid
julia> G = grid(1:3, [:x, :y])
2-dimensional KeyedArray(...)
(:x) (:y)
(1) (1, :x) (1, :y)
(2) (2, :x) (2, :y)
(3) (3, :x) (3, :y)
# Access elements like any array
julia> G[2, 1]
(2, :x)
# Or use axiskeys-based indexing
julia> G(2, :y)
(2, :y)- Seamless Array Interface: Grids are
AbstractArrays andKeyedArrays - Named Dimensions: Give meaningful names to your grid dimensions
- Flexible Element Types: Support for tuples, named tuples, custom structs, and more
- Efficient Indexing: Both integer and coordinate-based access
RectiGrids work by creating cartesian products of values. Think of them as "meshgrids" that store the coordinate combinations rather than just providing indexing.
Create grids with positional arguments for unnamed dimensions:
julia> grid(1:3, [:a, :b])
2-dimensional KeyedArray(...)
(:a) (:b)
(1) (1, :a) (1, :b)
(2) (2, :a) (2, :b)
(3) (3, :a) (3, :b)Grids implement the complete AbstractArray interface:
julia> G = grid(20:25, [:x, :y, :z])
julia> G isa AbstractArray
true
julia> size(G)
(6, 3)
julia> eltype(G)
Tuple{Int64, Symbol}
julia> G[3, 1]
(22, :a)Access grid properties and use coordinate-based indexing:
julia> dimnames(G)
(:_, :_)
julia> axiskeys(G, 2) # Get keys for dimension 2
3-element Vector{Symbol}:
:x
:y
:z
julia> G(22, :y) # Coordinate-based access
(22, :y)Give your dimensions meaningful names for cleaner, more readable code:
julia> G = grid(x=1:3, y=[:a, :b])
2-dimensional KeyedArray(NamedDimsArray(...)) with keys:
β x β 3-element UnitRange{Int64}
β y β 2-element Vector{Symbol}
And data, 3Γ2 RectiGrids.RectiGridArr{(:x, :y), NamedTuple{(:x, :y), Tuple{Int64, Symbol}}, 2, ...}:
(:a) (:b)
(1) (x = 1, y = :a) (x = 1, y = :b)
(2) (x = 2, y = :a) (x = 2, y = :b)
(3) (x = 3, y = :a) (x = 3, y = :b)
julia> eltype(G)
NamedTuple{(:x, :y), Tuple{Int64, Symbol}}
julia> G[2, 1]
(x = 2, y = :a)
julia> dimnames(G)
(:x, :y)
julia> axiskeys(G, :y) # Access by dimension name
2-element Vector{Symbol}:
:a
:b
julia> G(x=2, y=:b) # Named coordinate access
(x = 2, y = :b)Create grids with any constructible type:
# StaticArrays for high-performance applications
julia> using StaticArrays
julia> grid(SVector, 1:3, [:a, :b])
2-dimensional KeyedArray(...) with keys:
β 3-element UnitRange{Int64}
β 2-element Vector{Symbol}
And data, 3Γ2 RectiGrids.RectiGridArr{...}:
(:a) (:b)
(1) [1, :a] [1, :b]
(2) [2, :a] [2, :b]
(3) [3, :a] [3, :b]
# Define custom struct
julia> struct Point3D
x::Float64
y::Float64
z::Float64
end
julia> grid(Point3D, 0:2:10, [-1, 0, 1], [0.0, 0.5, 1.0])
3-dimensional KeyedArray(NamedDimsArray(...)) with keys:
β x β 6-element StepRange{Int64,...}
β y β 3-element Vector{Int64}
βͺ z β 3-element Vector{Float64}
And data, 6Γ3Γ3 RectiGrids.RectiGridArr{(:x, :y, :z), Point3D, 3, ...}:
[:, :, 1] =
(-1) (0) (1)
(0) Point3D(0.0, -1.0, 0.0) Point3D(0.0, 0.0, 0.0) Point3D(0.0, 1.0, 0.0)
(2) Point3D(2.0, -1.0, 0.0) Point3D(2.0, 0.0, 0.0) Point3D(2.0, 1.0, 0.0)
(4) Point3D(4.0, -1.0, 0.0) Point3D(4.0, 0.0, 0.0) Point3D(4.0, 1.0, 0.0)
(6) Point3D(6.0, -1.0, 0.0) Point3D(6.0, 0.0, 0.0) Point3D(6.0, 1.0, 0.0)
(8) Point3D(8.0, -1.0, 0.0) Point3D(8.0, 0.0, 0.0) Point3D(8.0, 1.0, 0.0)
(10) Point3D(10.0, -1.0, 0.0) Point3D(10.0, 0.0, 0.0) Point3D(10.0, 1.0, 0.0)
<...>The β operator lets you separate axis keys (used for indexing) from element values (stored in the grid):
julia> grid(id=[:alice, :bob, :charlie], score=[1β85, 2β92, 3β78])
2-dimensional KeyedArray(NamedDimsArray(...)) with keys:
β id β 3-element Vector{Symbol}
β score β 3-element Vector{Int64} # Keys: [1, 2, 3] for indexing
And data, 3Γ3 RectiGrids.RectiGridArr{(:id, :score), @NamedTuple{id::Symbol, score::Int64}, 2, ...}:
(1) (2) (3)
(:alice) (id = :alice, score = 85) (id = :alice, score = 92) (id = :alice, score = 78)
(:bob) (id = :bob, score = 85) (id = :bob, score = 92) (id = :bob, score = 78)
(:charlie) (id = :charlie, score = 85) (id = :charlie, score = 92) (id = :charlie, score = 78)
# Indexing uses the keys [1, 2, 3], but elements contain the values [85, 92, 78]
julia> g(id=:alice, score=2) # Key=2 maps to value=92
(id = :alice, score = 92)
julia> axiskeys(g, :score) # Shows the indexing keys
3-element Vector{Int64}:
1
2
3What's happening:
- Axis keys are
[1, 2, 3](left side ofβ) - used for indexing likeg(score=2) - Element values are
[85, 92, 78](right side ofβ) - stored in the actual grid elements - Each person gets paired with each score value, creating all combinations
Combine multiple grids to create higher-dimensional parameter spaces:
julia> spatial = grid(x=1:3, y=1:2)
julia> temporal = grid(t=0:0.1:1)
julia> spacetime = grid(spatial, temporal) # 3Γ2Γ11 grid
julia> spacetime[1, 1, 5]
(x = 1, y = 1, t = 0.4)# hyperparameter grid
hyperparams = grid(
learning_rate=[0.001, 0.01, 0.1],
batch_size=[32, 64, 128],
dropout=[0.1, 0.2, 0.3]
)
map(hyperparams) do params
train_model(params.learning_rate, params.batch_size, params.dropout)
end# 2D spatial grid for physics simulation
space = grid(x=-5:0.1:5, y=-5:0.1:5)
# Apply function to each coordinate
temperature_field = map(p -> exp(-(p.x^2 + p.y^2)), space)# Multi-factor experimental design
experiment = grid(
treatment=[:control, :drug_a, :drug_b],
dose=[1, 5, 10],
time=[1, 24, 48] # hours
)
# Each element represents a unique experimental condition
map(experiment) do condition
run_experiment(condition.treatment, condition.dose, condition.time)
endgrid([T], args...)- Create grid with positional arguments (unnamed dimensions)grid([T]; kwargs...)- Create grid with keyword arguments (named dimensions)grid(grid1, grid2)- Combine two grids into higher dimensions
Tuple(default for positional args)NamedTuple(default for keyword args)SVectorand other StaticArrays- Any custom struct with appropriate constructor
RectiGrids implement the complete AbstractArray interface:
- Indexing:
g[i, j],g[1:3, :] - Properties:
size(g),length(g),eltype(g),ndims(g) - Iteration:
for item in g - Broadcasting:
map(f, g),g .+ 1
Enhanced indexing and metadata:
dimnames(g)- Get dimension namesaxiskeys(g, dim)- Get keys for a dimensiong(1, :a)- Coordinate-based accessg(x=1, y=:a)- Named coordinate access