DISCO Convolutions for Neural Operators¶
| Metadata | Value |
|---|---|
| Level | Intermediate |
| Runtime | ~5 min (CPU) |
| Prerequisites | JAX, Flax NNX, Convolution basics |
| Format | Python + Jupyter |
Overview¶
Discrete-Continuous (DISCO) convolutions generalize standard convolutions to work on both structured and unstructured grids. Unlike standard convolutions that require regular grid spacing, DISCO convolutions can operate on arbitrary point distributions, making them ideal for scientific applications with irregular meshes.
This example reproduces and extends the classic 'Einstein' demo from the NeuralOperator library, demonstrating basic DISCO convolution, equidistant optimization for regular grids, and encoder-decoder architectures built with DISCO layers.
What You'll Learn¶
- Apply
DiscreteContinuousConv2dfor general convolution on arbitrary grids - Use
EquidistantDiscreteContinuousConv2dfor optimized regular grid processing - Build encoder-decoder architectures with
create_disco_encoderandcreate_disco_decoder - Compare performance between regular and equidistant DISCO variants
Coming from NeuralOperator (PyTorch)?¶
| NeuralOperator (PyTorch) | Opifex (JAX) |
|---|---|
DiscreteContinuousConv2d(in_ch, out_ch, kernel) |
DiscreteContinuousConv2d(in_channels=, out_channels=, kernel_size=, rngs=) |
| N/A (no equidistant variant) | EquidistantDiscreteContinuousConv2d(grid_spacing=) |
| Manual encoder/decoder construction | create_disco_encoder() / create_disco_decoder() |
Key difference: Opifex adds an optimized equidistant variant and factory functions
for building encoder-decoder architectures. Explicit rngs parameter ensures reproducibility.
Files¶
- Python Script:
examples/layers/disco_convolutions_example.py - Jupyter Notebook:
examples/layers/disco_convolutions_example.ipynb
Quick Start¶
Core Concepts¶
What Are DISCO Convolutions?¶
Standard convolutions assume data lives on a regular grid with uniform spacing. DISCO (Discrete-Continuous) convolutions remove this assumption by defining the convolution kernel as a continuous function that can be evaluated at arbitrary input/output locations.
graph LR
subgraph Standard["Standard Convolution"]
A["Regular Grid<br/>(uniform spacing)"]
end
subgraph DISCO["DISCO Convolution"]
B["Arbitrary Grid<br/>(any spacing)"]
end
A --> C["Fixed Kernel<br/>Positions"]
B --> D["Continuous Kernel<br/>Evaluation"]
C --> E["Output"]
D --> E
style Standard fill:#fff3e0
style DISCO fill:#e3f2fd
Equidistant Optimization¶
When data happens to be on a regular grid, EquidistantDiscreteContinuousConv2d
exploits the uniform spacing for faster computation while maintaining the same
mathematical framework.
Implementation¶
Step 1: Basic DISCO Convolution¶
from opifex.neural.operators.specialized.disco import DiscreteContinuousConv2d
disco_conv = DiscreteContinuousConv2d(
in_channels=1,
out_channels=4,
kernel_size=3,
activation=jax.nn.gelu,
rngs=nnx.Rngs(42),
)
output = disco_conv(input_tensor) # (1, 32, 32, 1) -> (1, 32, 32, 4)
Terminal Output:
Basic DISCO Convolution Demonstration
Image Size: 32x32
Channels: 1 -> 4
Kernel Size: 3x3
Input Shape: (1, 32, 32, 1)
Output Shape: (1, 32, 32, 4)
Convolution Time: 805.47 ms
Step 2: Equidistant Optimization¶
For regular grids, the equidistant variant provides a speedup:
from opifex.neural.operators.specialized.disco import EquidistantDiscreteContinuousConv2d
equidistant_conv = EquidistantDiscreteContinuousConv2d(
in_channels=2,
out_channels=3,
kernel_size=3,
grid_spacing=0.1,
rngs=nnx.Rngs(44),
)
Terminal Output:
Equidistant DISCO Convolution Demonstration
Image Size: 48x48
Regular DISCO Time: 202.55 ms
Equidistant DISCO Time: 28.85 ms
Speedup Factor: 7.02x
Step 3: Encoder-Decoder Architecture¶
Build a full autoencoder using DISCO layers:
from opifex.neural.operators.specialized.disco import (
create_disco_encoder,
create_disco_decoder,
)
encoder = create_disco_encoder(
in_channels=1,
hidden_channels=(16, 32, 64),
kernel_size=3,
use_equidistant=True,
rngs=nnx.Rngs(45),
)
decoder = create_disco_decoder(
hidden_channels=(64, 32, 16),
out_channels=1,
kernel_size=3,
use_equidistant=True,
rngs=nnx.Rngs(46),
)
Terminal Output:
DISCO Encoder-Decoder Architecture Demonstration
Encoded Shape: (1, 4, 4, 64)
Reconstructed Shape: (1, 32, 32, 1)
Reconstruction Error: 6037.575195
Visualization¶

Results Summary¶
| Component | Description | Key Metric |
|---|---|---|
| Basic DISCO Conv | General convolution (32x32, 1->4 channels) | Baseline timing |
| Equidistant DISCO | Optimized for regular grids | Speedup factor varies |
| Encoder-Decoder | DISCO autoencoder (1->64->1 channels) | Reconstruction MSE |
Next Steps¶
Experiments to Try¶
- Larger kernels: Increase
kernel_sizeto 5 or 7 for broader receptive fields - More channels: Try
out_channels=16for richer feature extraction - Irregular grids: Apply DISCO convolutions to unstructured mesh data
Related Examples¶
| Example | Level | What You'll Learn |
|---|---|---|
| Grid Embeddings | Beginner | Spatial coordinate injection for neural operators |
| Fourier Continuation | Intermediate | Boundary handling for spectral methods |
| FNO Darcy Full | Intermediate | Full neural operator training pipeline |
API Reference¶
DiscreteContinuousConv2d- General DISCO convolution layerEquidistantDiscreteContinuousConv2d- Optimized regular grid DISCOcreate_disco_encoder- DISCO encoder factory functioncreate_disco_decoder- DISCO decoder factory function
Troubleshooting¶
Shape mismatch in encoder-decoder¶
Symptom: Decoder output shape doesn't match encoder input shape.
Cause: Hidden channel sequence in decoder must be reversed from encoder.
Solution:
# Encoder: (1) -> (16, 32, 64)
# Decoder: (64, 32, 16) -> (1) # Reversed!
encoder = create_disco_encoder(in_channels=1, hidden_channels=(16, 32, 64), ...)
decoder = create_disco_decoder(hidden_channels=(64, 32, 16), out_channels=1, ...)
Slow DISCO convolution¶
Symptom: DISCO conv is much slower than expected.
Solution: For regular grids, use EquidistantDiscreteContinuousConv2d with
appropriate grid_spacing for optimized computation.