phasma 0.0.1

Terminal interface for the caustic Vlasov-Poisson solver
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
# phasma

**Terminal interface for the [caustic](https://github.com/resonant-jovian/caustic) Vlasov-Poisson solver.**

[![Crates.io](https://img.shields.io/crates/v/phasma.svg)](https://crates.io/crates/phasma)
[![License: GPL-3.0](https://img.shields.io/badge/License-GPL--3.0-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)

---

phasma is a terminal application built with [ratatui](https://ratatui.rs/) that provides a full interactive workflow for setting up, running, and monitoring caustic simulations — no GUI or web stack required. Everything runs in your terminal over SSH, in tmux, on a headless compute node, wherever you need it.

## Installation

### From crates.io

```bash
cargo install phasma
```

### From source

```bash
git clone https://github.com/resonant-jovian/phasma
cd phasma
cargo build --release
./target/release/phasma
```

### Man page

```bash
phasma --generate-man | sudo tee /usr/local/share/man/man1/phasma.1 > /dev/null
sudo mandb
```

### Dependencies

- **caustic** (the solver library) — pulled automatically as a cargo dependency
- **ratatui** + **crossterm** — terminal rendering (no external deps)

## Usage

```bash
# Launch the interactive TUI
phasma

# Launch with a config pre-loaded
phasma --config my_run.toml

# Launch and immediately start running
phasma --config my_run.toml --run

# Batch mode: no TUI, output saved to disk
phasma --config my_run.toml --batch

# Replay a completed batch run in the TUI
phasma --playback output/run_20260310_143022/

# Side-by-side comparison of two runs
phasma --compare output/run_A/ output/run_B/

# Monitor a batch job in progress
phasma --monitor output/run_20260310_143022/

# Generate a config interactively
phasma --wizard

# Parameter sweep
phasma --sweep sweep.toml

# Convergence study
phasma --convergence convergence.toml

# Regression test (CI-compatible, exits 0 or 1)
phasma --regression-test output/reference_run/

# Compare multiple runs, write Markdown report
phasma --batch-compare output/run_A/ output/run_B/ output/run_C/ --report results.md
```

## CLI reference

| Flag | Argument | Description |
|---|---|---|
| `-c`, `--config` | `PATH` | Path to simulation config file (TOML). Required for `--run`, `--batch`, `--save-preset`. |
| `--run` || Start simulation immediately on launch (TUI mode). |
| `--batch` || Headless batch mode. Saves diagnostics.csv, JSON snapshots, and metadata.json to a timestamped output directory. Progress printed to stderr. |
| `--playback` | `DIR` | Replay saved snapshots from a batch output directory in the TUI. Supports scrubbing (Left/Right), play/pause (Space). |
| `--compare` | `DIR DIR` | Side-by-side TUI comparison of two batch output directories. Press `c` to cycle Run A / Run B / Difference views. |
| `--sweep` | `TOML` | Parameter sweep. Runs a batch sim for every combination in a Cartesian product of parameter values. See [Sweep config]#sweep-config. |
| `--convergence` | `TOML` | Convergence study. Runs at increasing resolutions and computes convergence rates. See [Convergence config]#convergence-config. |
| `--regression-test` | `DIR` | Re-run a saved config and compare against the reference output. Exits 0 on pass, 1 on fail. |
| `--monitor` | `DIR` | Watch a batch job's output directory and display new snapshots in the TUI as they appear. |
| `--tail` | `DIR` | Like `--monitor` but always auto-advances to the latest snapshot. |
| `--wizard` || Interactive guided wizard that prompts for all parameters and writes a TOML config file. |
| `--save-preset` | `NAME` | Save the config from `--config` as a named preset to `~/.config/phasma/presets/NAME.toml`. |
| `--batch-compare` | `DIR DIR [...]` | Generate a Markdown comparison report across 2+ batch output directories. |
| `--report` | `PATH` | Output file for `--batch-compare` report (default: `comparison_report.md`). |
| `--generate-man` || Print a roff man page to stdout. |
| `-h`, `--help` || Print help. |
| `-V`, `--version` || Print version. |

### Batch output directory layout

When running with `--batch`, phasma creates:

```
output/<prefix>_YYYYMMDD_HHMMSS/
  config.toml          -- copy of input config
  diagnostics.csv      -- time series (appended each step)
  snapshots/
    state_000000.json   -- periodic SimState snapshots
    state_000001.json
    ...
    state_final.json    -- last state
  metadata.json         -- version, timing, exit reason, snapshot count
```

This directory is the input for `--playback`, `--monitor`, `--compare`, `--regression-test`, and `--batch-compare`.

## Config file reference

phasma configuration uses TOML format. All sections and all fields are optional — sensible defaults are provided for everything. A minimal config can be as short as:

```toml
[model]
type = "plummer"
```

The full config has 9 top-level sections: `[domain]`, `[model]`, `[solver]`, `[time]`, `[output]`, `[exit]`, `[performance]`, `[playback]`, `[appearance]`.

---

### `[domain]` — Simulation domain

| Key | Type | Default | Accepted values | Description |
|---|---|---|---|---|
| `spatial_extent` | float | `10.0` | Any `> 0` | Half-width of the spatial box in each dimension. Domain spans `[-L, L]^3`. |
| `velocity_extent` | float | `5.0` | Any `> 0` | Half-width of the velocity box in each dimension. Domain spans `[-V, V]^3`. |
| `spatial_resolution` | integer | `8` | Any `> 0` | Number of grid cells per spatial dimension. Total spatial cells = N^3. |
| `velocity_resolution` | integer | `8` | Any `> 0` | Number of grid cells per velocity dimension. Total velocity cells = N^3. |
| `boundary` | string | `"periodic\|truncated"` | `"periodic\|truncated"`, `"periodic\|open"`, `"isolated\|truncated"` | Spatial boundary condition \| velocity boundary condition. |
| `coordinates` | string | `"cartesian"` | `"cartesian"` | Coordinate system. |
| `gravitational_constant` | float | `1.0` | Any `> 0` | Value of G in simulation units. |

Memory usage scales as `spatial_resolution^3 * velocity_resolution^3 * 8 bytes`. A 32^3 x 32^3 grid requires ~8 GB.

```toml
[domain]
spatial_extent = 10.0
velocity_extent = 5.0
spatial_resolution = 16
velocity_resolution = 16
boundary = "periodic|truncated"
coordinates = "cartesian"
gravitational_constant = 1.0
```

---

### `[model]` — Initial conditions

| Key | Type | Default | Accepted values | Description |
|---|---|---|---|---|
| `type` | string | `"plummer"` | `"plummer"`, `"hernquist"`, `"king"`, `"nfw"`, `"zeldovich"`, `"merger"`, `"custom_file"` | Initial condition model. |
| `total_mass` | float | `1.0` | Any `> 0` | Total mass of the system. Alias: `mass`. |
| `scale_radius` | float | `1.0` | Any `> 0` | Characteristic scale radius of the model. |

Models with additional parameters require a sub-table:

#### `[model.king]` — King model (tidally truncated)

| Key | Type | Required | Accepted values | Description |
|---|---|---|---|---|
| `w0` | float | yes | Typical `3.0``9.0` | Dimensionless central potential. Higher values = more concentrated. W0=6 gives r_t/r_0 ~ 30. |

```toml
[model]
type = "king"
total_mass = 1.0
scale_radius = 1.0

[model.king]
w0 = 6.0
```

#### `[model.nfw]` — NFW dark matter halo

| Key | Type | Required | Accepted values | Description |
|---|---|---|---|---|
| `concentration` | float | yes | Typical `5.0``20.0` | Concentration parameter c = r_vir / r_s. Higher = more centrally concentrated. |

```toml
[model]
type = "nfw"
total_mass = 1.0
scale_radius = 1.0

[model.nfw]
concentration = 10.0
```

#### `[model.zeldovich]` — Zel'dovich pancake

| Key | Type | Required | Accepted values | Description |
|---|---|---|---|---|
| `amplitude` | float | yes | Typical `0.1``1.0` | Perturbation amplitude. |
| `wave_number` | float | yes | Any `> 0` | Mode wave number. Caustic forms at t = 1 / (amplitude * wave_number). |

```toml
[model]
type = "zeldovich"
total_mass = 1.0
scale_radius = 1.0

[model.zeldovich]
amplitude = 0.3
wave_number = 1.0
```

#### `[model.merger]` — Two-body merger

| Key | Type | Required | Accepted values | Description |
|---|---|---|---|---|
| `separation` | float | yes | Any `> 0` | Initial distance between the two bodies. |
| `mass_ratio` | float | yes | Any `> 0` | Mass ratio m2/m1. Use `1.0` for equal mass. |

```toml
[model]
type = "merger"
total_mass = 2.0
scale_radius = 1.0

[model.merger]
separation = 6.0
mass_ratio = 1.0
```

#### `[model.uniform_perturbation]` — Perturbed uniform background

| Key | Type | Required | Accepted values | Description |
|---|---|---|---|---|
| `mode_m` | integer | yes | Any `> 0` | Perturbation mode number. |
| `amplitude` | float | yes | Any `> 0` | Perturbation amplitude. |

#### `[model.custom_function]` — Custom shared library

| Key | Type | Required | Accepted values | Description |
|---|---|---|---|---|
| `library_path` | string | yes | File path | Path to a shared library (.so / .dylib). |
| `function_name` | string | yes | Symbol name | Name of the function to call. |

#### `[model.custom_file]` — Custom data file

| Key | Type | Required | Accepted values | Description |
|---|---|---|---|---|
| `file_path` | string | yes | File path | Path to a .npy file containing the 6D distribution function. Array shape must match domain resolution. |
| `format` | string | yes | `"npy"` | File format. |

```toml
[model]
type = "custom_file"
total_mass = 1.0
scale_radius = 1.0

[model.custom_file]
file_path = "my_ic.npy"
format = "npy"
```

---

### `[solver]` — Numerical methods

| Key | Type | Default | Accepted values | Description |
|---|---|---|---|---|
| `representation` | string | `"uniform"` | `"uniform"` | Phase-space representation. Uniform 6D grid. |
| `poisson` | string | `"fft_periodic"` | `"fft_periodic"`, `"fft"`, `"fft_isolated"` | Poisson solver. `fft` is an alias for `fft_periodic`. Use `fft_isolated` for non-periodic (Hockney-Eastwood zero-padded). |
| `advection` | string | `"semi_lagrangian"` | `"semi_lagrangian"` | Advection scheme. Semi-Lagrangian with Catmull-Rom interpolation. |
| `integrator` | string | `"strang"` | `"strang"`, `"yoshida"`, `"lie"` | Time integrator. `strang` = 2nd-order symplectic, `yoshida` = 4th-order (7 sub-steps), `lie` = 1st-order. |

```toml
[solver]
representation = "uniform"
poisson = "fft_periodic"
advection = "semi_lagrangian"
integrator = "yoshida"
```

---

### `[time]` — Time stepping

| Key | Type | Default | Accepted values | Description |
|---|---|---|---|---|
| `t_final` | float | `10.0` | Any `> 0` | Simulation end time. |
| `dt_mode` | string | `"adaptive"` | `"adaptive"`, `"fixed"` | Timestep mode. Adaptive uses CFL constraints. Alias: `dt`. |
| `dt_fixed` | float | `0.1` | Any `> 0` | Fixed timestep (only used when `dt_mode = "fixed"`). |
| `cfl_factor` | float | `0.5` | `(0, 1]` | CFL safety factor for adaptive timestep. Lower = more conservative. |
| `dt_min` | float | `1e-6` | Any `> 0` | Minimum allowed timestep (adaptive mode). |
| `dt_max` | float | `1.0` | Any `> 0` | Maximum allowed timestep (adaptive mode). |

```toml
[time]
t_final = 20.0
dt_mode = "adaptive"
cfl_factor = 0.5
dt_min = 1e-6
dt_max = 1.0
```

---

### `[output]` — Output settings

| Key | Type | Default | Accepted values | Description |
|---|---|---|---|---|
| `directory` | string | `"output"` | Any path | Base output directory. Batch mode creates timestamped subdirectories here. |
| `prefix` | string | `"run"` | Any string | Prefix for output subdirectory names (e.g. `run_20260310_143022`). |
| `snapshot_interval` | float | `1.0` | Any `> 0` | Simulation time between snapshot saves. Alias: `interval`. |
| `checkpoint_interval` | float | `10.0` | Any `> 0` | Simulation time between checkpoint saves. |
| `diagnostics_interval` | float | `0.1` | Any `> 0` | Simulation time between diagnostics CSV rows. |
| `format` | string | `"binary"` | `"binary"` | Snapshot format. |

```toml
[output]
directory = "output"
prefix = "run"
snapshot_interval = 1.0
checkpoint_interval = 10.0
diagnostics_interval = 0.1
format = "binary"
```

---

### `[exit]` — Exit / termination conditions

The simulation terminates when **any** enabled condition triggers.

| Key | Type | Default | Accepted values | Description |
|---|---|---|---|---|
| `energy_drift_tolerance` | float | `0.5` | Any `> 0` | Max allowed \|Delta E / E_0\|. Simulation exits if exceeded. Alias: `energy_tolerance`. |
| `mass_drift_tolerance` | float | `0.1` | Any `> 0` | Max allowed \|Delta M / M_0\|. Alias: `mass_threshold`. |
| `virial_equilibrium` | bool | `false` | `true`, `false` | Exit when virial ratio 2T/\|W\| stabilizes within tolerance. |
| `virial_tolerance` | float | `0.05` | Any `> 0` | Tolerance for virial equilibrium detection. |
| `wall_clock_limit` | float | none | Any `> 0` (seconds) | Maximum wall-clock time in seconds. None = no limit. |
| `rank_saturation` | bool | `false` | `true`, `false` | Exit on tensor rank saturation (for tensor representations). |
| `rank_saturation_steps` | integer | `5` | Any `> 0` | Number of consecutive steps at max rank before exit. |
| `cfl_violation` | bool | `true` | `true`, `false` | Exit on CFL condition violation. |
| `steady_state` | bool | `false` | `true`, `false` | Exit when the solution reaches steady state. |
| `steady_state_tolerance` | float | `1e-6` | Any `> 0` | Threshold for steady state detection. |

```toml
[exit]
energy_drift_tolerance = 0.05
mass_drift_tolerance = 0.01
virial_equilibrium = true
virial_tolerance = 0.05
wall_clock_limit = 3600.0
cfl_violation = true
steady_state = false
```

---

### `[performance]` — Performance tuning

| Key | Type | Default | Accepted values | Description |
|---|---|---|---|---|
| `num_threads` | integer | `0` | `0` = all available, or any `> 0` | Number of rayon threads. |
| `memory_budget_gb` | float | `4.0` | Any `> 0` | Memory budget in GB. Validation warns if the grid exceeds this. |
| `rank_budget_warn` | integer | `64` | Any `> 0` | Warn if tensor rank exceeds this value. |
| `simd` | bool | `true` | `true`, `false` | Enable SIMD optimizations. |
| `allocator` | string | `"system"` | `"system"`, `"jemalloc"`, `"mimalloc"` | Memory allocator (requires corresponding cargo feature). |

```toml
[performance]
num_threads = 0
memory_budget_gb = 8.0
simd = true
allocator = "system"
```

---

### `[playback]` — Playback settings

Used when replaying saved snapshots with `--playback`.

| Key | Type | Default | Accepted values | Description |
|---|---|---|---|---|
| `source_directory` | string | none | Directory path | Path to snapshot directory. |
| `source_prefix` | string | none | Any string | Filename prefix filter. |
| `source_format` | string | `"binary"` | `"binary"` | Snapshot format. |
| `fps` | float | `10.0` | Any `> 0` | Playback frames per second. |
| `loop_playback` | bool | `false` | `true`, `false` | Loop back to start when playback reaches the end. |
| `start_time` | float | none | Any `>= 0` | Start playback at this simulation time. |
| `end_time` | float | none | Any `> start_time` | End playback at this simulation time. |

```toml
[playback]
fps = 15.0
loop_playback = true
```

---

### `[appearance]` — TUI appearance

| Key | Type | Default | Accepted values | Description |
|---|---|---|---|---|
| `theme` | string | `"dark"` | `"dark"`, `"light"`, `"solarized"`, `"gruvbox"` | Color theme. |
| `colormap_default` | string | `"viridis"` | `"viridis"`, `"inferno"`, `"plasma"`, `"magma"`, `"grayscale"`, `"cubehelix"`, `"coolwarm"` | Default colormap for density and phase-space heatmaps. |
| `braille_density` | bool | `true` | `true`, `false` | Use braille characters for density rendering. |
| `border_style` | string | `"rounded"` | `"rounded"`, `"plain"`, `"double"` | Widget border style. |
| `square_pixels` | bool | `true` | `true`, `false` | Compensate for non-square terminal cells in heatmaps. |
| `aspect_ratio_mode` | string | `"letterbox"` | `"letterbox"`, `"stretch"` | How to handle aspect ratio mismatch. |
| `cell_aspect_ratio` | float | `0.5` | Any `> 0` | Terminal cell width/height ratio (most terminals ~ 0.5). |
| `min_columns` | integer | `80` | Any `> 0` | Minimum terminal width (columns). |
| `min_rows` | integer | `24` | Any `> 0` | Minimum terminal height (rows). |

```toml
[appearance]
theme = "dark"
colormap_default = "viridis"
braille_density = true
border_style = "rounded"
square_pixels = true
```

---

## Sweep config

The `--sweep` flag takes a TOML file that specifies a parameter sweep:

```toml
base_config = "configs/plummer.toml"
output_dir = "output/sweep"           # default: "output/sweep"

[sweep]
parameters = ["domain.spatial_resolution", "solver.integrator"]

[sweep.values]
"domain.spatial_resolution" = [8, 16, 32]
"solver.integrator" = ["strang", "yoshida"]

[sweep.run]
parallel = 2     # concurrent runs (default: 1)
```

| Key | Type | Required | Default | Description |
|---|---|---|---|---|
| `base_config` | string | yes || Path to the base simulation config TOML. |
| `output_dir` | string | no | `"output/sweep"` | Output directory for all sweep runs. |
| `sweep.parameters` | array of strings | yes || Dotted config paths to vary (e.g. `"domain.spatial_resolution"`). |
| `sweep.values.<param>` | array | yes || Values for each parameter. Cartesian product of all parameter values is generated. |
| `sweep.run.parallel` | integer | no | `1` | Number of concurrent runs. |

Parameter paths use dot notation matching the config structure (e.g. `domain.spatial_resolution`, `solver.integrator`, `time.t_final`, `model.total_mass`).

---

## Convergence config

The `--convergence` flag takes a TOML file:

```toml
base_config = "configs/plummer.toml"
output_dir = "output/convergence"     # default: "output/convergence"

[convergence]
resolutions = [8, 16, 32, 64]
velocity_scale = true                 # set velocity_resolution = spatial_resolution (default: true)
metrics = ["energy_drift", "mass_drift"]  # default: ["energy_drift", "mass_drift"]
```

| Key | Type | Required | Default | Description |
|---|---|---|---|---|
| `base_config` | string | yes || Path to the base simulation config TOML. |
| `output_dir` | string | no | `"output/convergence"` | Output directory. |
| `convergence.resolutions` | array of integers | yes || List of spatial resolutions to test. |
| `convergence.velocity_scale` | bool | no | `true` | Also set velocity_resolution = spatial_resolution for each run. |
| `convergence.metrics` | array of strings | no | `["energy_drift", "mass_drift"]` | Metrics to compute convergence rates for. |

Convergence rates are computed as `log2(error_N / error_2N)` between consecutive resolution pairs.

---

## Supported models

| Model | Description | Config key |
|---|---|---|
| Plummer | Isotropic sphere with analytic DF | `plummer` |
| Hernquist | Galaxy model with closed-form DF | `hernquist` |
| King | Tidally truncated (Poisson-Boltzmann ODE) | `king` |
| NFW | Dark matter halo (numerical Eddington inversion) | `nfw` |
| Zel'dovich | Single-mode cosmological pancake | `zeldovich` |
| Merger | Two-body superposition of any equilibrium ICs | `merger` |
| Custom file | User-provided 6D array (.npy) | `custom_file` |

## Preset configurations

phasma ships with 8 preset configurations:

| Preset | Model | Grid | Integrator | Use case |
|---|---|---|---|---|
| `speed_priority` | Plummer | 8^3 x 8^3 | Strang | Fast iteration, smoke tests |
| `resolution_priority` | Plummer | 32^3 x 32^3 | Yoshida | High-accuracy production runs |
| `conservation_priority` | Plummer | 16^3 x 16^3 | Yoshida | Conservation law validation |
| `cosmological` | Zel'dovich | 32^3 x 32^3 | Strang | Caustic formation |
| `king_equilibrium` | King (W0=6) | 16^3 x 16^3 | Strang | Tidally truncated equilibrium |
| `nfw_dark_matter` | NFW (c=10) | 16^3 x 16^3 | Yoshida | Dark matter halo |
| `hernquist_galaxy` | Hernquist | 16^3 x 16^3 | Yoshida | Galaxy model |
| `merger` | 2x Plummer | 16^3 x 16^3 | Strang | Two-body interaction |

```bash
phasma --config configs/speed_priority.toml --run
phasma --config configs/cosmological.toml --batch
phasma --save-preset my_plummer --config configs/speed_priority.toml
```

## Features

### Interactive simulation setup

Configure every parameter from the TUI without editing config files:

- **Model selection** — cycle through initial condition types with `Tab`/arrow keys
- **Parameter entry** — inline numeric fields for mass, scale radius, domain extents, resolution, time range
- **Solver selection** — pick Poisson method, advection scheme, and integrator from dropdown menus
- **Validation** — constraints checked live (velocity domain vs escape velocity, resolution vs memory budget, required sub-config fields)

### Live monitoring

While the simulation runs, phasma displays real-time dashboards across 10 tabs:

- **F2 Run Control** — progress gauge, density and phase-space thumbnails, energy conservation chart, diagnostics sidebar with virial ratio 2T/|W|
- **F3 Density** — 2D heatmap of projected density with axis selection and zoom
- **F4 Phase Space** — f(x_i, v_j) marginal projections for all 9 dimension pairs, with zoom
- **F5 Energy** — conservation time series: E(t), T(t), W(t), drift Delta E/E_0, mass drift, Casimir drift, entropy
- **F6 Rank** — rank monitoring (placeholder)
- **F7 Profiles** — spherically averaged rho(r), velocity dispersion, mass profile, circular velocity, anisotropy beta(r)
- **F8 Performance** — step timing, adaptive timestep evolution, cumulative wall time, throughput stats
- **F9 Poisson** — Poisson solver detail (placeholder)
- **F10 Settings** — theme and colormap selection

All time-series charts display the full simulation history from t=0 to the current time, with downsampled older data transitioning seamlessly to high-resolution recent data.

### History scrubbing

phasma stores the last 100 simulation snapshots in a ring buffer. Use `Left`/`Right` to scrub backward/forward through history on any visualization tab. Press `Backspace` to jump back to live.

### Playback mode

Use `--playback DIR` to replay a completed batch run in the TUI. Supports play/pause with `Space`, frame stepping with `Left`/`Right`, and all the same visualization tabs as live mode.

### Comparison mode

Use `--compare DIR_A DIR_B` to load two runs side-by-side. Press `c` to cycle between Run A, Run B, and element-wise Difference views on the density and phase-space tabs.

### Monitor / tail

Use `--monitor DIR` to watch a batch job in progress — the TUI updates as new snapshots appear. `--tail DIR` does the same but always auto-advances to the latest snapshot.

## Keyboard controls

| Key | Action |
|---|---|
| `F1` -- `F10` | Switch tabs (Setup, Run, Density, Phase, Energy, Rank, Profiles, Perf, Poisson, Settings) |
| `Tab` / `Shift+Tab` | Next / previous tab |
| `Space` | Pause / resume simulation (global) |
| `Left` / `Right` | Scrub backward / forward through history |
| `Backspace` | Jump to live (exit scrub mode) |
| `?` | Toggle help overlay |
| `q` | Quit (with confirmation if sim is running) |

**Density (F3):**

| Key | Action |
|---|---|
| `x` / `y` / `z` | Change projection axis |
| `+` / `-` / scroll | Zoom in / out |
| `r` | Reset zoom |
| `l` | Toggle log scale |
| `c` | Cycle colormap |
| `i` | Toggle info bar |

**Phase Space (F4):**

| Key | Action |
|---|---|
| `1`-`3` | Select spatial dimension (x, y, z) |
| `4`-`6` | Select velocity dimension (vx, vy, vz) |
| `+` / `-` / scroll | Zoom in / out |
| `r` / `0` | Reset zoom |
| `l` | Toggle log scale |
| `c` | Cycle colormap / cycle comparison view (in `--compare` mode) |
| `i` | Toggle info bar |

**Energy (F5):**

| Key | Action |
|---|---|
| `t` / `k` / `w` | Toggle traces: total energy / kinetic / potential |
| `d` | Toggle drift view (Delta E/E_0) |
| `1`-`4` | Select panel (energy, mass, Casimir, entropy) |

**Run Control (F2):**

| Key | Action |
|---|---|
| `p` / `Space` | Pause / resume |
| `s` | Stop simulation |
| `r` | Restart simulation |
| `1`-`3` | Log filter: all / warn+ / error only |

**Profiles (F7):**

| Key | Action |
|---|---|
| `1`-`5` | Select profile: density, dispersion, mass, v_circ, anisotropy |
| `l` | Toggle log scale |
| `a` | Toggle analytic overlay |

**Global:**

| Key | Action |
|---|---|
| `e` | Open export menu (`1`-`9` to quick-select format) |
| `T` | Cycle theme |
| `C` | Cycle colormap (global) |

## Project structure

```
phasma/
├── Cargo.toml
├── README.md
├── configs/                    # Preset TOML configurations
│   ├── speed_priority.toml
│   ├── resolution_priority.toml
│   ├── conservation_priority.toml
│   ├── cosmological.toml
│   ├── king_equilibrium.toml
│   ├── nfw_dark_matter.toml
│   ├── hernquist_galaxy.toml
│   └── merger.toml
└── src/
    ├── main.rs                 # Entry point, mode dispatch
    ├── sim.rs                  # caustic integration (IC/solver/integrator dispatch)
    ├── config/
    │   ├── mod.rs              # PhasmaConfig schema (serde, all sections)
    │   ├── presets.rs           # Named preset save/load
    │   ├── validate.rs          # Config validation
    │   └── history.rs           # Recent config tracking
    ├── runner/
    │   ├── mod.rs              # RunMetadata, module re-exports
    │   ├── batch.rs            # Headless batch runner with disk output
    │   ├── live.rs             # TUI live runner
    │   ├── wizard.rs           # Interactive config wizard
    │   ├── sweep.rs            # Parameter sweep
    │   ├── convergence.rs      # Convergence study
    │   ├── compare.rs          # Batch comparison report
    │   ├── regression.rs       # Regression testing
    │   └── monitor.rs          # Filesystem watcher for --monitor/--tail
    ├── data/
    │   ├── mod.rs              # DataProvider trait
    │   ├── live.rs             # Live data provider (diagnostics store, scrub history)
    │   ├── playback.rs         # Playback data provider
    │   └── comparison.rs       # Comparison data provider (A/B/diff)
    ├── tui/
    │   ├── app.rs              # Application state machine
    │   ├── cli.rs              # CLI argument definitions (clap)
    │   ├── tabs/               # Tab implementations (setup, run_control, density, ...)
    │   ├── widgets/            # Reusable widgets (heatmap, colorbar, sparkline table)
    │   ├── status_bar.rs       # Bottom status bar (ETA, throughput, memory)
    │   ├── help.rs             # Help overlay
    │   ├── export_menu.rs      # Export format selector
    │   └── ...
    ├── export/                 # Export formats (CSV, JSON, NPY, Parquet, VTK, ZIP)
    ├── colormaps/              # Terminal colormap implementations
    └── themes.rs               # Color themes (dark, light, solarized, gruvbox)
```

## Relationship to caustic

phasma is a **consumer** of the `caustic` library. It provides no solver logic — it constructs a `caustic::Simulation` from user input, runs it on a background thread, and renders the diagnostics that `caustic` produces.

If you want to embed caustic in your own application, script, or pipeline, use the library directly. phasma is for interactive exploration and monitoring long-running jobs from a terminal.

**`caustic` (lib)** <-- depends on <-- **`phasma` (bin)**

| caustic provides | phasma provides |
|---|---|
| Simulation engine | ratatui TUI |
| Phase-space representations | Config loading (TOML presets) |
| Poisson solvers (FFT periodic/isolated) | Real-time density/phase-space heatmaps |
| Advection (semi-Lagrangian) | Energy conservation charts |
| Time integrators (Strang/Yoshida/Lie) | History scrubbing and playback |
| Diagnostics API | Batch mode, sweeps, convergence studies |
| Exit conditions | Export (CSV, JSON, NPY, Parquet, VTK, ZIP) |

## Minimum supported Rust version

phasma targets **stable Rust 1.85+** (edition 2024).

## License

This project is licensed under the [GNU General Public License v3.0](https://www.gnu.org/licenses/gpl-3.0.en.html). See [LICENSE](LICENSE) for details.

## Citation

If you use phasma in academic work, please cite:

```bibtex
@software{phasma,
  title  = {phasma: Terminal interface for the caustic Vlasov-Poisson solver},
  url    = {https://github.com/resonant-jovian/phasma},
  year   = {2026}
}
```