Skip to main content

Overview

The tools module provides various utility functions and classes for configuration, parallelization, array operations, and post-processing.

Configuration

Config System

from dedalus.tools.config import config
Dedalus uses a ConfigParser-based configuration system.

Configuration Files

Read in order (later files override earlier):
  1. Package defaults: dedalus/dedalus.cfg
  2. User config: ~/.dedalus/dedalus.cfg
  3. Local config: ./dedalus.cfg

Example Configuration

# ~/.dedalus/dedalus.cfg

[parallelism]  
TRANSPOSE_LIBRARY = fftw
GROUP_TRANSPOSES = True

[linear algebra]
MATRIX_FACTORIZER = SuperluNaturalSpsolve

[memory]  
FIELD_CACHE_SIZE = 128

[logging]
stdout_level = info
file_level = debug

Accessing Config

from dedalus.tools.config import config

# Get values
transpose_lib = config['parallelism'].get('TRANSPOSE_LIBRARY')
cache_size = config['memory'].getint('FIELD_CACHE_SIZE')  

# Check boolean
group_transposes = config['parallelism'].getboolean('GROUP_TRANSPOSES')

Parallel Tools

Sync

from dedalus.tools.parallel import Sync
Context manager for MPI synchronization.

Parameters

  • comm (MPI.Comm, optional): Communicator (default: COMM_WORLD)
  • enter (bool, optional): Barrier on enter (default: True)
  • exit (bool, optional): Barrier on exit (default: True)

Example

from dedalus.tools.parallel import Sync  
from mpi4py import MPI
import time

comm = MPI.COMM_WORLD

# Synchronize before and after block
with Sync(comm):
    # All processes wait here  
    time.sleep(comm.rank * 0.1)
    print(f"Process {comm.rank}")
# All processes wait here before continuing

parallel_mkdir

from dedalus.tools.parallel import parallel_mkdir
Create directory from root process only.

Parameters

  • path (str or Path): Directory path
  • comm (MPI.Comm, optional): Communicator

Example

from dedalus.tools.parallel import parallel_mkdir
import pathlib

output_dir = pathlib.Path('output')  
parallel_mkdir(output_dir)

Post-Processing

Merging Output Files

from dedalus.tools import post  
merge_process_files(base_path, cleanup=False) - Merge distributed output files

Parameters

  • base_path (str): Base path to output directory
  • cleanup (bool, optional): Delete process files after merging

Example

from dedalus.tools import post

# Merge output from parallel run
post.merge_process_files('snapshots', cleanup=True)  
post.merge_process_files('analysis', cleanup=False)

Post-Processing Script Template

import h5py  
import numpy as np
import matplotlib.pyplot as plt
from dedalus.tools import post

# Merge files
post.merge_process_files('snapshots', cleanup=True)

# Read merged data  
with h5py.File('snapshots/snapshots_s1.h5', 'r') as f:
    # Load data
    u = f['tasks']['velocity'][:]
    x = f['tasks']['velocity'].dims[1][0][:]  
    t = f['scales']['sim_time'][:]
    
    # Plot
    plt.figure(figsize=(10, 6))
    for i in range(0, len(t), 10):
        plt.plot(x, u[i], label=f't={t[i]:.2f}')  
    plt.xlabel('x')
    plt.ylabel('u')
    plt.legend()
    plt.savefig('evolution.png', dpi=150)

Array Tools

reshape_vector

from dedalus.tools.array import reshape_vector
Reshape 1D array as multidimensional vector.

Parameters

  • data (ndarray): 1D array
  • dim (int): Target dimensionality
  • axis (int): Axis for data

Example

import numpy as np
from dedalus.tools.array import reshape_vector

x = np.linspace(0, 1, 64)

# Reshape for broadcasting  
x_2d = reshape_vector(x, dim=2, axis=0)  # Shape: (64, 1)
x_3d = reshape_vector(x, dim=3, axis=1)  # Shape: (1, 64, 1)

axslice

from dedalus.tools.array import axslice
Create slice along specific axis.

Parameters

  • axis (int): Axis to slice
  • start (int): Start index
  • stop (int): Stop index
  • step (int, optional): Step size

Example

import numpy as np  
from dedalus.tools.array import axslice

data = np.random.rand(10, 20, 30)

# Slice along axis 1
sliced = data[axslice(1, 5, 15)]  # data[:, 5:15, :]

Exception Classes

from dedalus.tools.exceptions import *
Dedalus-specific exceptions:
  • UnsupportedEquationError: Equation violates problem constraints
  • NonlinearOperatorError: Nonlinear operator used where linear required
  • UndefinedParityError: Parity undefined for operation
  • SymbolicParsingError: Error parsing symbolic expressions

Example

import dedalus.public as d3  
from dedalus.tools.exceptions import UnsupportedEquationError

try:
    problem = d3.LBVP([u])
    # This will raise UnsupportedEquationError (nonlinear RHS)  
    problem.add_equation("dx(u) = u**2")
except UnsupportedEquationError as e:
    print(f"Invalid equation: {e}")

Logging

import logging
logger = logging.getLogger(__name__)
Dedalus uses Python’s logging module.

Configuration

# In dedalus.cfg
[logging]
stdout_level = info
file_level = debug
filename = dedalus.log

Usage in Scripts

import logging
logger = logging.getLogger(__name__)

# Set level for your script
logger.setLevel(logging.INFO)

# Log messages  
logger.debug("Detailed debug information")
logger.info("Iteration %d complete" % iteration)
logger.warning("CFL condition violated")
logger.error("Solver failed to converge")  

Cache Tools

CachedMethod

from dedalus.tools.cache import CachedMethod  
Decorator for caching method results.

Example

from dedalus.tools.cache import CachedMethod

class MyClass:  
    @CachedMethod
    def expensive_computation(self, x):
        # Only computed once per unique x
        return x**2 + 2*x + 1

obj = MyClass()  
result1 = obj.expensive_computation(5)  # Computed
result2 = obj.expensive_computation(5)  # Cached

CachedAttribute

from dedalus.tools.cache import CachedAttribute
Descriptor for caching attribute values.

Example

from dedalus.tools.cache import CachedAttribute  
import numpy as np

class MyClass:
    @CachedAttribute
    def random_array(self):
        # Only generated once
        return np.random.rand(1000)  

obj = MyClass()
arr1 = obj.random_array  # Generated
arr2 = obj.random_array  # Same array

General Utilities

unify

from dedalus.tools.general import unify
Unify values from a collection, ensuring all are equal.

Example

from dedalus.tools.general import unify

# Returns common value if all equal
result = unify([1, 1, 1, 1])  # Returns 1  

# Raises error if not all equal
try:
    result = unify([1, 2, 3])
except ValueError:
    print("Values not unified")  

OrderedSet

from dedalus.tools.general import OrderedSet  
Set that maintains insertion order.

Example

from dedalus.tools.general import OrderedSet

s = OrderedSet([3, 1, 4, 1, 5])  
print(list(s))  # [3, 1, 4, 5]

Random Arrays

ChunkedRandomArray

from dedalus.tools.random_arrays import ChunkedRandomArray
Generate distributed random arrays reproducibly.

Example

from dedalus.tools.random_arrays import ChunkedRandomArray  
import numpy as np

# Create reproducible random array across processes
shape = (64, 64, 64)
seed = 42
random_gen = ChunkedRandomArray(shape, seed=seed)  
local_data = random_gen.get_local_data()

Performance Profiling

Dedalus includes profiling support:
# In dedalus.cfg  
[profiling]
PROFILE_DEFAULT = False
PARALLEL_PROFILE_DEFAULT = False
PROFILE_DIRECTORY = profiles

Usage

import dedalus.public as d3

# Enable profiling for solver
solver = problem.build_solver(d3.RK443)
solver.profile = True

# Run simulation  
while solver.proceed:
    solver.step(dt)

# Profile data saved to profiles/

Quick Domain Setup

The quick_domains module provides convenience functions for common domain configurations.

Quick Domain Functions

from dedalus.extras import quick_domains
Available functions: 1D Domains:
  • fourier(N, dealias=3/2, dtype=np.float64) - 1D periodic Fourier domain
  • chebyshev(N, dealias=3/2, dtype=np.float64) - 1D Chebyshev domain
2D Domains:
  • fourier_2d(N, dealias=3/2, dtype=np.float64) - 2D periodic box
  • channel_2d(N, dealias=3/2, dtype=np.float64) - 2D channel (Fourier × Chebyshev)
3D Domains:
  • fourier_3d(N, dealias=3/2, dtype=np.float64) - 3D periodic box
  • channel_3d(N, dealias=3/2, dtype=np.float64) - 3D channel (Fourier × Fourier × Chebyshev)

Returns

Each function returns (coords, dist, bases):
  • coords: Coordinate system
  • dist: Distributor
  • bases: Basis object (or tuple of bases for multi-dimensional)

Example

from dedalus.extras import quick_domains

# Quick 2D channel setup
coords, dist, (xbasis, ybasis) = quick_domains.channel_2d(N=64, dealias=3/2)

# Create fields
u = dist.VectorField(coords, bases=(xbasis, ybasis))
p = dist.Field(bases=(xbasis, ybasis))

# Ready to set up problem
problem = d3.IVP([u, p], namespace=locals())
These helpers are useful for quick prototyping and testing. For production simulations, explicitly construct coordinate systems and bases for full control over bounds and parameters.

See Also

Build docs developers (and LLMs) love