# 2D Turbulence Simulation (SciPy / CuPy)

Source code: https://github.com/mannetroll/cupystorm

A Direct Numerical Simulation (DNS) code for **2D homogeneous incompressible turbulence**

It supports:

- **SciPy / NumPy** for CPU runs
- **CuPy** (optional) for GPU acceleration on CUDA devices (e.g. RTX 3090)

## one-liner CPU/SciPy

```
$ curl -LsSf https://astral.sh/uv/install.sh | sh
$ uv cache clean mannetroll-cupystorm
$ uv run --python 3.13 --with "mannetroll-cupystorm==0.1.2" turbulence
```

## one-liner GPU/CuPy

```
$ uv run --python 3.13 --with "mannetroll-cupystorm[cuda]==0.1.2" turbulence
```

### DNS solver
The solver includes:

- **PAO-style random-field initialization**
- **3/2 de-aliasing** in spectral space
- **Crank–Nicolson** time integration
- **CFL-based adaptive time stepping** (Δt updated from the current flow state)

### cupystorm GUI (PySide6)
Run an cupystorm window that:

- Displays the flow field as a live image (fast Indexed8 palette rendering)
- Lets you switch displayed variable:
  - **U**, **V** (velocity components)
  - **K** (kinetic energy)
  - **Ω** (vorticity)
  - **φ** (stream function)
- Lets you switch **colormap** (several built-in palettes)
- Lets you change simulation parameters on the fly:
  - Grid size **N**
  - Reynolds number **Re**
  - Initial spectrum peak **K0**
  - CFL number **CFL**
  - Max steps / auto-reset limit
  - GUI update interval (how often to refresh the display)
- Includes force/initialization modes:
  - **Navier-Stokes**: PAO spectrum init
  - **High-k + Ekman/Rayleigh**: High-k spectral forcing + Ekman/Rayleigh large-scale drag
  - **Rain**: random “kick” forcing events
  - **Circle**: a forcing point moves on a circular trajectory (continuous forcing)
  - **Mouse**: apply forcing by click/drag in the image

### Keyboard shortcuts
Single-key shortcuts (application-wide) for fast control:

- **V**: cycle variable
- **C**: cycle colormap
- **N**: cycle grid size
- **R**: cycle Reynolds number
- **K**: cycle K0
- **L**: cycle CFL
- **S**: cycle max steps
- **U**: cycle update interval

### Saving / exporting
From the GUI you can:

- **Save the current frame** as a PNG image
- **Dump full-resolution fields** to a folder as PGM images:
  - u-velocity, v-velocity, kinetic energy, vorticity

### Display scaling
To keep the GUI responsive for large grids, the displayed image is automatically upscaled/downscaled depending on `N`. The window is resized accordingly when you change `N`.

## Installation

### Using uv

From the project root:

    uv sync
    uv run turbulence

## High-k spectral forcing + Ekman/Rayleigh large-scale drag
### The flow remains fully turbulent after 1,000,000 iterations (256 x 256) CuPy

![CuPy](https://raw.githubusercontent.com/mannetroll/cupystorm/v0.1.2/wekman.png)

## Rain (256 x 256) SciPy

![SciPy](https://raw.githubusercontent.com/mannetroll/cupystorm/v0.1.2/wrain.png)

## Circle (256 x 256) SciPy

![SciPy](https://raw.githubusercontent.com/mannetroll/cupystorm/v0.1.2/wcircle.png)

## Mouse (256 x 256) SciPy

![SciPy](https://raw.githubusercontent.com/mannetroll/cupystorm/v0.1.2/wmouse.png)


### Full CLI

    $ python -m cupystorm.turbo_simulator N Re K0 STEPS CFL BACKEND [MODE]

Where:

- N        — grid size
- Re       — Reynolds number
- K0       — peak wavenumber of the energy spectrum
- STEPS    — number of time steps
- CFL      — target CFL number
- BACKEND  — "cpu", "gpu", or "auto"
- MODE     — "pao" | "highh" | "rain" | "circle" | "mouse"

Examples:

    # CPU run (SciPy with 4 workers)
    $ python -m cupystorm.turbo_simulator 256 10000 10 1001 0.75 cpu

    # Auto-select backend (GPU if CuPy + CUDA are available)
    $ python -m cupystorm.turbo_simulator 256 10000 10 1001 0.75 auto

## Enabling GPU with CuPy (CUDA 13)

On a CUDA machine (e.g. RTX 5090):

Download: https://developer.nvidia.com/cuda-downloads

1. Check that the driver/CUDA are available:

       $ nvidia-smi

2. Install CuPy into the uv environment:

       $ uv sync --extra cuda
       $ uv run turbulence

3. Verify that CuPy sees the GPU:

       $ uv run python -c "import cupy as cp; x = cp.arange(5); print(x, x.device)"

4. Run in GPU mode:

       $ uv run python -m cupystorm.turbo_simulator 256 10000 10 1001 0.75 gpu

Or let the backend auto-detect:

       $ uv run python -m cupystorm.turbo_simulator 256 10000 10 1001 0.75 auto

## The DNS with CuPy (7776 x 7776) Navier-Stokes

![CuPy](https://raw.githubusercontent.com/mannetroll/cupystorm/v0.1.2/window7776.png)

## Profiling

### cProfile (CPU)

    $ python -m cProfile -o turbo_simulator.prof -m cupystorm.turbo_simulator    

Inspect the results:

    $ python -m pstats turbo_simulator.prof
    # inside pstats:
    turbo_simulator.prof% sort time
    turbo_simulator.prof% stats 20

### GUI profiling with SnakeViz

Install SnakeViz:

    $ uv pip install snakeviz

Visualize the profile:

    $ snakeviz turbo_simulator.prof

### Memory & CPU profiling with Scalene (GUI)

Install Scalene:

    $ uv pip install "scalene==1.5.55"

Run with a GUI report:

    $ scalene -m cupystorm.turbo_simulator 256 10000 10 201 0.75 cpu

### Memory & CPU profiling with Scalene (CLI only)

For a terminal-only summary:

    $ scalene --cli --cpu -m cupystorm.turbo_simulator 256 10000 10 201 0.75 cpu

## License

Copyright © 2026 Mannetroll
