Simulation

Main Simulation Functions

simulate_waveDM

WaveDM.simulate_waveDM — Function

Test function for Milky Way-like simulations with MOND.

This function sets up and runs a simulation of the Milky Way or dwarf galaxies with MOND effects.

Arguments

  • model: Simulation model type (:MW for Milky Way, :dwarf for dwarf galaxies)
  • V: External potential function
  • FDM_mass_ratio: Ratio of FDM mass to total mass
  • FDM_radius_ratio: Ratio of FDM radius to total radius
  • title: Simulation title for output files
  • Xmax, Ymax, Zmax: Simulation box dimensions (in dimensionless units)
  • Tmax: Total simulation time (in dimensionless units)
  • Nt: Number of time steps
  • Nx, Ny, Nz: Number of grid points in each dimension
  • Np: Number of particles for each baryon component
  • absorb_coeff: Absorption coefficient at boundaries
  • StepsBetweenSnapshots: Frequency of visualization updates
  • IC_vel: Initial velocity field (or path to load from file)
  • IC_only: If true, return initial conditions without running simulation
  • static: If true, no velocity, phases are all zero
  • outputdir: Directory for saving output files
  • massRadius: Mass radius for the galaxy
  • baryon_β, baryon_ρ0, baryon_r0: Baryonic density profile parameters
  • halo_β, halo_ρ0, halo_r0, halo_α, halo_γ, halo_Q: Dark halo density profile parameters
  • GravitySolver: Gravitational solver type (Tree() or DirectSummation())
  • boundary: Boundary conditions (Periodic() or Vacuum())
  • SofteningLength: Gravitational softening length
  • baryon_mode: How baryons are treated (:mesh, :particlesstatic, :particlesdynamic, :ignored)
  • gpu: Enable GPU acceleration

Returns

  • If IC_only=true: Initial velocity field IC_vel
  • Otherwise: Results from SPE3D_waveDM function
source

The high-level entry point. It builds the initial conditions (using AstroIC.jl), creates a SimulationGrid, calls SPE3D_waveDM to evolve the SPE, then handles visualization and post-processing. Most of the keyword arguments listed below can be supplied directly to simulate_waveDM; the low-level-only ones are marked in the tables.

SPE3D_waveDM

WaveDM.SPE3D_waveDM — Function

Main simulation function for 3D Schrödinger-Poisson equation with MOND.

This function handles the complete simulation workflow from initial condition setup to result visualization.

Arguments

  • Xmax, Ymax, Zmax: Simulation box dimensions (in dimensionless units)
  • Tmax: Total simulation time (in dimensionless units)
  • Nx, Ny, Nz: Number of grid points in each dimension
  • Nt: Number of time steps
  • autoset_timestep: Automatically set timestep based on stability criteria
  • IC: Initial wavefunction or distribution function
  • V: External potential function
  • κ: Self-interaction parameter
  • baryon: Baryonic density contribution
  • baryon_mode: How baryons are treated (:ignored, :mesh, :particlesstatic, :particlesdynamic)
  • baryon_potential: Pre-computed potentials of static baryons
  • ax_b, ay_b, az_b: Baryonic acceleration components
  • absorb_coeff: Absorption coefficient at boundaries
  • outputdir: Directory for saving output files
  • title: Simulation title for output files
  • boundary: Boundary conditions (Periodic() or Vacuum())
  • SofteningLength: Gravitational softening length
  • gpu: Enable GPU acceleration
  • Realtime: Enable real-time visualization
  • StepsBetweenSnapshots: Frequency of visualization updates
  • FDM_mass_ratio: Ratio of FDM mass to total mass
  • FDM_radius_ratio: Ratio of FDM radius to total radius

Returns

  • ψ: Final wavefunction
  • fig: Overview plot figure
  • figMOND: MOND-related plot figure
  • chi2RC: Chi-squared value for rotation curve fit
  • dfProp: DataFrame with simulation properties
  • dfAcc: DataFrame with acceleration data
  • averaged_ψ2: Time-averaged wavefunction squared
source

The lower-level entry point. It expects you to provide the initial wavefunction IC and (optionally) the baryonic density/potential/forces yourself. Use simulate_waveDM unless you are integrating WaveDM.jl into a larger pipeline.

Note

Defaults in the tables below refer to simulate_waveDM. The low-level SPE3D_waveDM has different defaults: Xmax = 5, Nx = 64, Tmax = 1, Nt = 32, absorb_coeff = 0, gpu = true, and IC = (x,y,z) -> exp(-x²-y²-z²). Additional low-level keywords are forwarded to SPE3D_waveDM; IC itself is not a high-level keyword (use IC_vel).

Simulation Workflow

flowchart LR
    A[Grid setup] --> B[Initial conditions]
    B --> C[Baryon setup<br/>mesh / static / dynamic]
    C --> D[IC potential]
    D --> E[Visualization init]
    E --> F[KDK main loop]
    F -->|every StepsBetweenSnapshots| E
    F --> G[Outputs<br/>CSV, JLD2, PNG, MP4]
  1. Grid setup — uniform Cartesian grid (Nx × Ny × Nz, side lengths 2*Xmax etc.).
  2. Initial conditions — the initial wavefunction (IC_vel at the high level, IC at the low level) and V (external potential) are evaluated on the grid.
  3. Baryon setup — depending on baryon_mode, baryons are placed on the mesh, sampled as static N-body particles, or co-evolved as dynamic N-body particles.
  4. IC potential — the gravitational potential $\Phi_{\rm total}$ at $t = 0$ is computed (FFT for periodic; an isolated tree solve for vacuum in the current driver).
  5. Visualization init — observables for Realtime and the per-snapshot update are wired up.
  6. KDK main loop — apply_kick_step! → apply_drift_step! for Nt steps.
  7. Outputs — per-step and final files (CSV for properties, JLD2 for $\psi$/$\Phi$, PNG/MP4 for visualization).

Key Parameters

Grid and resolution

KeywordDefaultDescription
Xmax, Ymax, Zmax2.0, Xmax, XmaxHalf-side length of the box in dimensionless units (full side L = 2*Xmax); low-level default is 5
Nx, Ny, Nz256, Nx, NxGrid points per axis; powers of two are fastest; low-level default is 64
Tmax0.1Total simulation time in dimensionless units (or u"Gyr" if Unitful is loaded); low-level default is 1
Nt5000Number of time steps (overridden when autoset_timestep = true); low-level default is 32
bulk_perturb, bulk_sizetrue, 8Pooling of the initial velocity field; the paper's Crater II runs use bulk_size = 4

Initial conditions

KeywordDefaultDescription
IC_velnothingHigh-level initial wavefunction: nothing generates it from model; a String loads a JLD2 written by save_IC = true; an array is used directly
ICGaussian $(x,y,z)\mapsto e^{-x^2-y^2-z^2}$Low-level (SPE3D_waveDM) initial wavefunction — a function of $(x,y,z)$ returning a complex number
V(x,y,z,ψ)->0External / self-interaction potential $V(\mathbf r, t, \psi)$
κ0Self-interaction strength. 0 (free), > 0 (repulsive), < 0 (attractive)
baryon0Static baryon density (low-level; used only by baryon_mode = :mesh)
mₐ0.2×10⁻²² eV/c²Boson mass in physical units (drives the dimensionless scale). The paper's Crater II runs use mₐ = 50 * 1e-22 * 1.783e-36u"kg"
Ωₘ₀0.31Present-day matter density parameter
MW_disk_RCfalseFor model = :MW: drive the velocity field from the MW rotation curve
baryon_fraction_limit1Upper bound on the baryon-to-total mass ratio used when generating model = :dwarf ICs

Baryons

KeywordDefaultDescription
baryon_mode:meshOne of :ignored, :mesh, :particles_static, :particles_dynamic. Presets with no baryonic model (e.g. :dwarf_UFDs, :dwarf_NFW, :dwarf_Zhao) only accept :ignored. The paper quotes :ignored as the default; the current code default is :mesh
baryon_potentialnothingPre-computed $\Phi_{\rm baryon}$; skip the Poisson solve
baryon_particlesnothingStructArray of N-body particles (Star / Gas) for static or dynamic modes
SofteningLength0.1u"kpc"Gravitational softening length for the N-body force, of order the mean interparticle spacing $d=(V/N)^{1/3}$. The paper quotes ~1 kpc; the current code uses 0.1 kpc
GravitySolverTree()Tree() ($\mathcal O(N\log N)$, Gadget-2) or DirectSum() ($\mathcal O(N^2)$)

baryon_mode values:

  • :ignored — no baryons. Use this explicitly for dark-matter-only halos and for presets that do not model baryons; the package default is :mesh.
  • :mesh — baryons live on the same FFT grid; potential computed by the same FFT Poisson solver as the DM. Best for spatially extended baryons (e.g. dSph, ETG).
  • :particles_static — baryons are sampled once as N-body particles and held fixed; their gravitational potential is added to $\Phi_{\rm total}$ at every step.
  • :particles_dynamic — baryons are co-evolved with the DM; their potential is re-computed at every step and the DM acceleration is interpolated back to the particles. Most expensive, most realistic.

Tidal field

KeywordDefaultDescription
MW_tidal_fieldfalseAdd a time-dependent MW tidal field to $\Phi_{\rm total}$
LMC_tidal_fieldfalseAdd LMC perturbation (requires MW_tidal_field = true)
MW_tidal_interpolatetrueUse a pre-computed splined MW potential grid (MW_pot) for speed
MW_potnothingPath to a JLD2 file or a pre-built potential grid
MW_pot_Xmax, MW_pot_Ymax, MW_pot_Zmax100.0u"kpc"Half-side lengths of the MW potential interpolation box
MW_pot_N512Resolution of the MW potential interpolation box
export_MW_potfalseIf true, save the MW tidal-field interpolation grid to a JLD2 file
tidal_BG_simnothingPre-built MW background N-body Simulation (bypasses MW_pot)
tidal_lookback_time0.0u"Gyr"How far back in time the tidal field is applied
df_trajempty DataFrameTrajectory table (columns time, x, y, z) from the lookback step. Must be supplied when MW_tidal_field = true
tidal_initial_pos, tidal_initial_velCrater II defaultsInitial Galactocentric position / velocity of the satellite
particles_LMCnothingStructArray of LMC particles (alternative to analytic Plummer)

Boundary conditions

KeywordDefaultDescription
boundaryPeriodic()Periodic() (FFT-based) or Vacuum() (isolated; see Algorithms §4.2)
absorb_coeff10Absorbing-layer coefficient $\alpha$; 0 disables the absorber. The low-level default is 0

Timestep

KeywordDefaultDescription
autoset_timestepfalseChoose $\Delta t$ from the CFL criterion automatically
autoset_timestep_ratio0.9Safety factor $\zeta_t$ for the auto-timestep

Performance

KeywordDefaultDescription
gpufalseUse CUDA.jl for FFT and element-wise work. The low-level default is true
distributed_memoryfalseUse Distributed.jl DArray for grid arrays. The distributed FFT Poisson solver is not implemented in the current release, so the full SSFM loop still requires a single process
pidsworkers()Worker process IDs for distributed runs
KDK_flagtrueIf true, use the second-order kick–drift–kick scheme

Visualization

KeywordDefaultDescription
RealtimetrueOpen an interactive GLMakie window
unicode_plotfalseRender a terminal heat-map of the density slice via UnicodePlots.jl
unicode_heatmap_width150Width in characters of the terminal heat-map
StepsBetweenSnapshots5Update the visualization and save frames every N steps
size(2400, 1400)Figure size of the overview plot
plotOpticalfalsePlot baryonic (optical) properties in addition to wave-DM
plotWaveDMfalsePlot the wave-DM-only view alongside the total
plot_virialfalseAdd a virial-energy axis to the live figure
dynamic_colorrangetrueAuto-scale the colormap to the running maximum density
extract_dwarf_granulefalsePost-process: extract the snapshot best fitting a target profile/RC
extract_mode:profile:profile or :RC extraction mode
extract_min_t0.2u"Gyr"Earliest simulation time considered for extraction
best_fit_halo_massfalseIf true, optimize the halo mass during the RC fit

Output

KeywordDefaultDescription
title"WaveDM_MOND_MW"Run title (used in output filenames); the low-level default is "WaveDM"
outputdirjoinpath(@__DIR__, "output/MOND")Where CSV / JLD2 / PNG / MP4 files are written; the low-level default is joinpath(@__DIR__, "output")
save_ICtrueSave the initial conditions to disk
save_phitrueSave the final $\Phi$ and $\psi$ to JLD2
export_particlesfalseIf true, return the sampled baryon particles (no SPE run)
IC_onlyfalseIf true, return the initial wavefunction (no SPE run)

Minimal Working Examples

1. Smoke test (CPU)

using WaveDM

simulate_waveDM(;
    model = :MW,
    baryon_mode = :ignored,
    Xmax = 2.0,
    Tmax = 0.001,
    Nt   = 3,
    Nx = Ny = Nz = 64,
    gpu = false,
    Realtime = false,
    unicode_plot = true,
    outputdir = mktempdir(),
)

2. GPU production run

using WaveDM
using Unitful

simulate_waveDM(;
    model = :MW,
    Xmax = 5.0,
    Tmax = 5.0u"Gyr",
    Nx = Ny = Nz = 256,
    Nt = 5000,
    autoset_timestep = true,
    gpu = true,
    Realtime = true,
    plot_virial = true,
    title = "MW_256_T5Gyr",
    outputdir = "results/MW_256_T5Gyr",
)

3. Crater II ultra-faint dwarf

using WaveDM
using Unitful

simulate_waveDM(;
    model       = :dwarf_UFDs,
    Galaxy_id   = 6,                 # Crater II
    baryon_mode = :ignored,          # Crater II is DM-dominated
    mₐ          = 50 * 1e-22 * 1.783e-36u"kg",  # m₂₂ = 50
    V           = (x, y, z, ψ) -> 0.0,
    Nx          = 384,
    Xmax        = 20u"kpc",
    Tmax        = 6.0u"Gyr",
    autoset_timestep = true,
    absorb_coeff = 10,
    bulk_size   = 4,                 # value used in the paper
    gpu         = true,
    Realtime    = true,
    title       = "CraterII",
)

4. Crater II with MW tidal field

using WaveDM
using Unitful

simulate_waveDM(;
    model       = :dwarf_UFDs,
    Galaxy_id   = 6,
    baryon_mode = :ignored,
    mₐ          = 50 * 1e-22 * 1.783e-36u"kg",
    V           = (x, y, z, ψ) -> 0.0,
    Nx          = 384,
    Xmax        = 20u"kpc",
    Tmax        = 6.0u"Gyr",
    autoset_timestep = true,
    absorb_coeff = 10,
    MW_tidal_field = true,
    df_traj     = df_traj,          # from the lookback step (see Examples §4)
    tidal_lookback_time = 6.0u"Gyr",
    LMC_tidal_field = true,         # include LMC perturbation
    gpu         = true,
    title       = "CraterII_MW_LMC",
)

5. Baryons as dynamic N-body particles

using WaveDM
using AstroIC
using PhysicalParticles, Unitful

baryons = generate(AstroIC.ExponentialDisk(; ...))  # see AstroIC.jl

simulate_waveDM(;
    Nx = 256,
    Xmax = 5.0,
    Tmax = 2.0u"Gyr",
    autoset_timestep = true,
    baryon_mode       = :particles_dynamic,
    baryon_particles  = baryons,
    GravitySolver     = Tree(),
    SofteningLength   = 0.1u"kpc",           # code default
    gpu = true,
)

6. Distributed run

Warning

The distributed FFT Poisson solver is not implemented in the current release: parallel_poisson raises an error for kind = :distributed. Distributed workers currently accelerate initial-condition generation and N-body work, but a full SPE run must use the serial/threads or GPU backend.

using Distributed
addprocs(8)
@everywhere using WaveDM

simulate_waveDM(;
    Nx = Ny = Nz = 256,
    Tmax = 2.0u"Gyr",
    autoset_timestep = true,
    distributed_memory = true,         # use DArray for grid arrays
    gpu = false,                       # GPU and distributed are not orthogonal but mixing
                                      # them is an advanced use case
)

Return Values

ψ, fig, figMOND, chi2RC, dfProp, dfAcc, averaged_ψ2 = simulate_waveDM(...)
ReturnDescription
ψFinal wavefunction (complex array, dimensionless units)
figOverview Makie figure (snapshot of the live plot at end of run)
figMONDMOND / RAR diagnostic figure, or nothing if baryon_mode = :ignored
chi2RC$\chi^2$ of the rotation-curve fit, or nothing for DM-only runs
dfPropDataFrame with the time series of total mass, mass-fraction radii, virial energies, momenta
dfAccDataFrame with the radial acceleration profile (DM-only or DM+baryon+MOND)
averaged_ψ2Time-averaged $

Output Files

The simulation writes several file types into outputdir:

  • $(title), $(suffix) - Overview IC.png — initial snapshot
  • $(title), $(suffix) - Overview.mp4 — animation of the entire run (if Realtime = true)
  • $(title), $(suffix) - Overview Prop.png — final-state overview
  • $(title), $(suffix) - RC.png / RAR.png — rotation curve / radial acceleration relation
  • $(title), $(suffix) - Prop best fit.jld2 — best-fit snapshot (if extract_dwarf_granule = true)
  • $(title), $(suffix) - acc.csv / acc best fit.csv — radial acceleration data
  • $(title), $(suffix) - best_fit_time.csv — best-fit simulation time
  • $(title), $(suffix) - timestep_*.jld2 — per-snapshot ψ / Φ (if average_snapshots = true)

The suffix is a human-readable summary of the run, e.g. Nx(384), Xmax(20.00), Nt(377), Tmax(6.00), DM_m(1.00).

Performance Considerations

  • GPU vs CPU. gpu = true is most useful for grids $\gtrsim 256^3$. For tiny problems the kernel-launch overhead dominates and CPU is faster. On a 12 GB device, $N \gtrsim 512$ becomes memory-limited.
  • Distributed memory. The distributed FFT Poisson solver is not implemented yet; use distributed workers for IC generation and N-body work only.
  • Grid size. Powers of two (64, 128, 256, …) maximize FFT performance. The code also runs on non-power-of-two grids.
  • Timestep. autoset_timestep = true keeps $\Delta t$ near the CFL limit without manual tuning.
  • StepsBetweenSnapshots. Larger values skip more visualization updates per step, which is useful when the plot is the bottleneck.
  • Memory. On GPU, $N^3$ complex arrays dominate. A $512^3$ single-precision grid is $\sim 2$ GiB; a $1024^3$ grid is $\sim 16$ GiB.
  • KDK_flag. Leave at true. Setting it to false reduces the scheme to first-order Lie splitting and is provided only for code-experimentation purposes.
  • Configs — configuration structures
  • KDK — the kick–drift–kick implementation