Tarang.jl
Tarang.jl
A High-Performance Spectral PDE Solver for Julia
CPU | GPU | MPI Distributed | Symbolic Equations
Features
Spectral Methods
- Fourier, Chebyshev, and Legendre bases
- Spectral accuracy for smooth solutions
- Automatic differentiation operators
GPU Acceleration
- Native CUDA support with cuFFT
- KernelAbstractions.jl backend
- Automatic CPU/GPU dispatch
MPI Parallelization
- PencilArrays pencil decomposition
- Scalable to thousands of cores
- Efficient distributed FFTs
Symbolic Equations
- Natural mathematical syntax for PDEs
- Automatic operator construction
- Flexible boundary conditions
Quick Start
Problem API and execution support
| Problem | Solver | Purpose |
|---|---|---|
InitialValueProblem | InitialValueSolver | Time evolution |
LinearBoundaryValueProblem | BoundaryValueSolver | Steady linear equations |
NonlinearBoundaryValueProblem | BoundaryValueSolver | Steady nonlinear equations |
EigenvalueProblem | EigenvalueSolver | Eigenvalues and modes |
The abbreviated IVP, LBVP, NLBVP, and EVP aliases have been removed. Use the corresponding full names above when updating an existing script.
Boundary conditions support spatial values in linear and nonlinear steady solves, and parameterized moving values during time stepping. Normal-coordinate references use the wall position. Structured stress-free conditions preserve scalar component selection; periodic markers record metadata without adding constraints.
CPU concurrency uses exclusive scratch storage for independent Fourier derivatives and shared-factor matrix solves. GPU linear boundary solves keep solve buffers on the device. Nonlinear GPU boundary-value and GPU eigenvalue solves remain unsupported. Consult the time-stepper execution table for the supported combinations of operators, CPU, MPI, and GPU execution.
This site's dev version follows main; stable follows tagged releases. Pull-request previews are separate builds, so unreleased changes do not appear in the stable manual automatically.
Installation
using Pkg
Pkg.add(url="https://github.com/subhk/Tarang.jl")That is the whole serial installation. MPI, PencilArrays, PencilFFTs and KernelAbstractions are hard dependencies and are installed with Tarang. For a distributed script, add MPI as a direct dependency of the script's project and install its compatible launcher once on Unix, macOS, or WSL:
julia --project=. -e 'using Pkg; Pkg.add("MPI"); using MPI; MPI.install_mpiexecjl()'On native Windows, launch through MPI.mpiexec() as shown in the installation guide.
GPU support is the one opt-in: CUDA is a weak dependency loaded through a package extension, so add it only if you want it.
julia --project=@v#.# -e 'using Pkg; Pkg.add("CUDA")' # enables TarangCUDAExt1D Diffusion
using Tarang
domain = PeriodicDomain(64) # 64-point periodic [0, 2π]
T = ScalarField(domain, "T") # Temperature field
problem = InitialValueProblem([T])
add_parameters!(problem, kappa=0.01)
add_equation!(problem, "∂t(T) - kappa*Δ(T) = 0")
set!(T, x -> sin(x)) # Initial condition
solver = InitialValueSolver(problem, RK222(); dt=0.01)
run!(solver; stop_time=1.0) # That's it!The single mode sin(x) decays as exp(-κt); after t = 1 the solver gives max|T| = 0.99004983 against the exact 0.99004983 — a relative error of 4e-12.
2D Rayleigh-Benard Convection
A bounded (Chebyshev) direction needs the tau method: one tau variable per boundary condition, lifted into the equations. See The Tau Method for Boundary Conditions for the why.
using Tarang
Lx, Lz = 4.0, 1.0
Nx, Nz = 64, 32
Rayleigh, Prandtl = 2e4, 1.0
coords = CartesianCoordinates("x", "z")
dist = Distributor(coords; dtype=Float64, device=CPU())
xbasis = RealFourier(coords["x"]; size=Nx, bounds=(0.0, Lx), dealias=3/2)
zbasis = ChebyshevT(coords["z"]; size=Nz, bounds=(0.0, Lz), dealias=3/2)
domain = Domain(dist, (xbasis, zbasis))
p = ScalarField(domain, "p") # Pressure
T = ScalarField(domain, "T") # Temperature
u = VectorField(domain, "u") # Velocity
# One tau per boundary condition, carrying the Fourier bases (one per x-mode).
tau_p = ScalarField(dist, "tau_p", (), Float64) # pressure gauge
tau_T1 = ScalarField(dist, "tau_T1", (xbasis,), Float64)
tau_T2 = ScalarField(dist, "tau_T2", (xbasis,), Float64)
tau_u1 = VectorField(dist, coords, "tau_u1", (xbasis,), Float64)
tau_u2 = VectorField(dist, coords, "tau_u2", (xbasis,), Float64)
# First-order reduction: grad_X = ∇X + ẑ·lift(τ)
ex, ez = unit_vector_fields(coords, dist)
lift_basis = derivative_basis(zbasis, 1)
τ_lift(A) = lift(A, lift_basis, -1)
grad_u = grad(u) + ez * τ_lift(tau_u1)
grad_T = grad(T) + ez * τ_lift(tau_T1)
problem = InitialValueProblem([p, T, u, tau_p, tau_T1, tau_T2, tau_u1, tau_u2])
add_parameters!(problem, nu=Prandtl, buoy=Rayleigh*Prandtl, ez=ez,
grad_u=grad_u, grad_T=grad_T, τ_lift=τ_lift)
add_equation!(problem, "trace(grad_u) + tau_p = 0") # continuity
add_equation!(problem, "∂t(T) - div(grad_T) + τ_lift(tau_T2) = -u⋅∇(T)")
add_equation!(problem, "∂t(u) - nu*div(grad_u) + ∇(p) - buoy*T*ez + τ_lift(tau_u2) = -u⋅∇(u)")
# Boundary conditions go through add_bc!, never add_equation!.
add_bc!(problem, "T(z=0) = 1") # hot bottom
add_bc!(problem, "T(z=$Lz) = 0") # cold top
add_bc!(problem, "u(z=0) = 0") # no-slip
add_bc!(problem, "u(z=$Lz) = 0")
add_bc!(problem, "integ(p) = 0") # pressure gauge
# Conduction profile 1 - z, plus wall-damped noise to seed convection.
x, z = local_grids(dist, xbasis, zbasis)
fill_random!(T, "g"; seed=42, distribution="normal", scale=1e-3)
get_grid_data(T) .*= z' .* (1.0 .- z')
get_grid_data(T) .+= 1.0 .- z'
ensure_layout!(T, :c)
solver = InitialValueSolver(problem, RK222(); dt=1e-4)
diagnose(solver) # Print solver summary
run!(solver; stop_time=0.1, log_interval=100)Time is measured in thermal diffusion times, so buoy = Ra·Pr and nu = Pr. Over those first 1000 steps the seeded noise grows into rolls (max|u_z| reaches 3.76) while the walls stay pinned to machine precision: max|T(z=0) − 1| = 3.6e-15, max|T(z=Lz)| = 8.1e-15.
This is a Quick Start, not a production run. Push Rayleigh to 2e6 with the same fixed dt=1e-4 and the velocities outrun the CFL limit — the run goes to NaN within ~100 steps, at 64×32 and at 128×64. Real runs let a CFL controller choose dt (run!(solver; cfl=cfl, ...)); see examples/ivp/rayleigh_benard_2d.jl for the full 256×64, Ra = 2e6 version.
add_bc!(problem, "T(z=Lz) = 0") does not work: the parser resolves names against the problem's variables and add_parameters! entries, never your script's globals. A bare global warns (Unknown variable: Lz) and then fails the matrix build outright. Use a literal ("T(z=1) = 0") or interpolate the value in ("T(z=$Lz) = 0") — both enforce the boundary exactly.
Two things here are serial-only. Under MPI the Chebyshev axis must come first (Domain(dist, (zbasis, xbasis))) because a decomposed axis cannot hold a Chebyshev transform — and per-mode tau fields ((xbasis,)) need the Fourier axis first, so distributed runs use bare () taus. -u⋅∇(T) also puts a Chebyshev derivative on the explicit side, which the distributed solver rejects. Pure-Fourier problems parallelize with no changes at all; see Running with MPI.
Use diagnose(solver) at any time to inspect field layout, compiled RHS status, dealiasing mode, and memory usage.
GPU Example
using Tarang, CUDA
# Just add device=GPU() — everything else stays the same
domain = PeriodicDomain(128, 128; device=GPU(), dtype=Float32)
field = ScalarField(domain, "u")
forward_transform!(field) # Uses cuFFT automaticallydevice is the only line that changes: the same script with device=CPU() runs on the CPU, and dtype=Float32 gives ComplexF32 coefficients either way. GPU() needs CUDA.jl loaded — without it the constructor throws rather than silently falling back.
Scientific Applications
Navier-Stokes, Rayleigh-Benard convection, channel flow, jets
LES models (Smagorinsky, AMD), stochastic forcing, GQL approximation
MHD with magnetic fields, dynamo problems, magnetic dissipation
Rotating shallow water, stratified turbulence, surface dynamics
Problem Types
| Type | Description | Example |
|---|---|---|
| InitialValueProblem | Initial Value Problems | Time-dependent Navier-Stokes |
| LinearBoundaryValueProblem | Linear Boundary Value Problems | Poisson equation |
| NonlinearBoundaryValueProblem | Nonlinear Boundary Value Problems | Steady nonlinear systems |
| EigenvalueProblem | Eigenvalue Problems | Linear stability analysis |
Spectral Bases
| Basis | Domain | Usage |
|---|---|---|
RealFourier | Periodic | Horizontal directions, real-valued fields |
ComplexFourier | Periodic | Complex-valued fields |
ChebyshevT | Bounded | Wall-bounded domains, boundary conditions |
Legendre | Bounded | Alternative to Chebyshev |
Documentation
Setup and configuration
Step-by-step guides
CUDA acceleration
Complete documentation
Installation Options
| Setup | Command | Use Case |
|---|---|---|
| Default | Pkg.add(url="...") | Single-process CPU |
| MPI | Add MPI; use mpiexecjl (Unix/WSL) or MPI.mpiexec() (Windows) | Multi-process CPU with the matching MPI runtime |
| GPU | julia --project=@v#.# -e 'using Pkg; Pkg.add("CUDA")' | NVIDIA GPU acceleration (loads TarangCUDAExt) |
| Cluster MPI | MPIPreferences.use_system_binary() | Bind MPI.jl to the cluster's own MPI |
Tarang installs MPI, MPIPreferences, PencilArrays, PencilFFTs and KernelAbstractions transitively. Julia scripts that import MPI directly must also add it to their active project. Then install MPI.jl's project-aware launcher once with MPI.install_mpiexecjl() on Unix/WSL and run mpiexecjl --project=. -n 4 julia run.jl; on Windows, use MPI.mpiexec() as described above. Only CUDA is a [weakdeps] package extension.
- Julia 1.10 or later
- For GPU: NVIDIA GPU with CUDA support
- For MPI on a cluster: call
MPIPreferences.use_system_binary()once so MPI.jl uses the site's MPI (and its launcher) instead of the bundled one — see Running with MPI.
Contributing
We welcome contributions! See our GitHub repository for:
- Bug reports and feature requests
- Documentation improvements
- Pull requests
Citation
If you use Tarang.jl in your research, please cite:
@software{tarang_jl,
author = {Kar, Subhajit},
title = {Tarang.jl: A Spectral PDE Solver for Julia},
url = {https://github.com/subhk/Tarang.jl},
year = {2024}
}License
Tarang.jl is released under the MIT License.
Made for the scientific computing community