> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/atomind-ai/mlip-arena/llms.txt
> Use this file to discover all available pages before exploring further.

# MD — Molecular Dynamics

> Run NVE, NVT, or NPT molecular dynamics simulations using ASE integrators with optional temperature and pressure schedules.

## Overview

The `MD` task drives ASE molecular dynamics simulations in the NVE, NVT, or NPT ensemble. It supports temperature and pressure schedules (time-varying control), trajectory restart, and multiple integrators per ensemble. The task is registered in Prefect as **`MD`** with a `TASK_SOURCE + INPUTS` cache policy.

## Function signature

```python theme={null}
from mlip_arena.tasks.md import run as MD

result = MD(
    atoms,
    calculator,
    ensemble="nvt",
    dynamics="langevin",
    time_step=None,
    total_time=1000,
    temperature=300.0,
    pressure=None,
    dynamics_kwargs=None,
    velocity_seed=None,
    zero_linear_momentum=True,
    zero_angular_momentum=True,
    traj_file=None,
    traj_interval=1,
    restart=True,
)
```

## Parameters

<ParamField path="body.atoms" type="ase.Atoms" required>
  The input atomic structure. A copy is made internally.
</ParamField>

<ParamField path="body.calculator" type="ase.calculators.calculator.BaseCalculator" required>
  ASE-compatible calculator for energy and force evaluations.
</ParamField>

<ParamField path="body.ensemble" type="string" default="nvt">
  Statistical ensemble for the simulation. Accepted values:

  | Value   | Description                                          |
  | ------- | ---------------------------------------------------- |
  | `"nve"` | Microcanonical — constant energy, volume             |
  | `"nvt"` | Canonical — constant temperature, volume             |
  | `"npt"` | Isothermal-isobaric — constant temperature, pressure |
</ParamField>

<ParamField path="body.dynamics" type="string | MolecularDynamics" default="langevin">
  Integrator string name or an ASE `MolecularDynamics` subclass. Available string values per ensemble:

  | Ensemble | Accepted strings                                           |
  | -------- | ---------------------------------------------------------- |
  | `nve`    | `"velocityverlet"`                                         |
  | `nvt`    | `"langevin"`, `"nose-hoover"`, `"andersen"`, `"berendsen"` |
  | `npt`    | `"nose-hoover"`, `"berendsen"`                             |

  String matching is case-insensitive.
</ParamField>

<ParamField path="body.time_step" type="number | None" default="None">
  Integration time step in femtoseconds. When `None`, defaults to `0.5 fs` if the structure contains hydrogen isotopes, or `2.0 fs` otherwise.
</ParamField>

<ParamField path="body.total_time" type="number" default="1000">
  Total simulation time in femtoseconds. The number of MD steps is computed as `int(total_time / time_step)`.
</ParamField>

<ParamField path="body.temperature" type="number | Sequence | np.ndarray | None" default="300.0">
  Temperature target in Kelvin.

  * **Scalar** (`float`): constant temperature throughout the run.
  * **Sequence / 1-D array**: linearly interpolated onto all MD steps to create a temperature schedule (ramp or arbitrary profile).
  * **`None`** / ignored for `"nve"` ensemble.
</ParamField>

<ParamField path="body.pressure" type="number | Sequence | np.ndarray | None" default="None">
  External pressure in eV/Å³.

  * **Scalar** (`float`): constant pressure throughout the run.
  * **Sequence / 1-D array**: linearly interpolated to create a pressure schedule.
  * Required only for `"npt"` ensemble; ignored for `"nve"` and `"nvt"`.
</ParamField>

<ParamField path="body.dynamics_kwargs" type="dict | None" default="None">
  Extra keyword arguments forwarded to the integrator constructor. For the Langevin integrator, `friction` defaults to `10.0 ps⁻¹` (same default as VASP) when not specified. For NPT, the special key `fraction_traceless` (default `1.0`) controls the traceless part of the stress.
</ParamField>

<ParamField path="body.velocity_seed" type="number | None" default="None">
  Integer seed for `numpy.random.default_rng` used to draw the initial Maxwell–Boltzmann velocity distribution. Set for reproducible runs.
</ParamField>

<ParamField path="body.zero_linear_momentum" type="boolean" default="true">
  Remove net linear momentum from initial velocities using ASE `Stationary`.
</ParamField>

<ParamField path="body.zero_angular_momentum" type="boolean" default="true">
  Remove net angular momentum from initial velocities using ASE `ZeroRotation`.
</ParamField>

<ParamField path="body.traj_file" type="string | Path | None" default="None">
  Path to an ASE trajectory file (`.traj`) for writing simulation frames. Parent directories are created automatically. When `None`, no trajectory is written.
</ParamField>

<ParamField path="body.traj_interval" type="number" default="1">
  Write a frame to the trajectory file every `traj_interval` steps.
</ParamField>

<ParamField path="body.restart" type="boolean" default="true">
  When `True` and `traj_file` already exists, the simulation resumes from the last frame (positions and momenta are restored). If reading the existing trajectory fails, the run starts fresh.
</ParamField>

## Return value

```python theme={null}
{
    "atoms":   Atoms,    # final structure after the simulation
    "runtime": timedelta, # wall-clock time of the MD loop
    "n_steps": int,      # number of steps actually performed
}
```

<ResponseField name="atoms" type="ase.Atoms">
  Final atomic structure with positions and momenta from the last MD step.
</ResponseField>

<ResponseField name="runtime" type="datetime.timedelta">
  Wall-clock duration of the MD integration loop.
</ResponseField>

<ResponseField name="n_steps" type="int">
  Number of MD steps performed (may be less than `total_time / time_step` when restarting from a partially completed trajectory).
</ResponseField>

## Examples

<Tabs>
  <Tab title="NVE">
    ```python theme={null}
    from ase.build import bulk
    from mlip_arena.models import MLIPEnum
    from mlip_arena.tasks.md import run as MD
    from mlip_arena.tasks.utils import get_calculator

    atoms = bulk("Cu", "fcc", a=3.6).repeat(3)
    calculator = get_calculator(MLIPEnum["MACE-MP(M)"])

    # Microcanonical simulation — no thermostat
    result = MD(
        atoms=atoms,
        calculator=calculator,
        ensemble="nve",
        dynamics="velocityverlet",
        time_step=2.0,
        total_time=5000,  # 5 ps
        temperature=300.0,  # used only for initial velocity distribution
        velocity_seed=42,
    )

    print(f"Simulated {result['n_steps']} steps in {result['runtime']}")
    ```
  </Tab>

  <Tab title="NVT (Langevin)">
    ```python theme={null}
    from ase.build import bulk
    from mlip_arena.models import MLIPEnum
    from mlip_arena.tasks.md import run as MD
    from mlip_arena.tasks.utils import get_calculator

    atoms = bulk("Cu", "fcc", a=3.6).repeat(3)
    calculator = get_calculator(MLIPEnum["MACE-MP(M)"])

    # Canonical MD with Langevin thermostat
    result = MD(
        atoms=atoms,
        calculator=calculator,
        ensemble="nvt",
        dynamics="langevin",
        time_step=2.0,
        total_time=10_000,  # 10 ps
        temperature=600.0,  # K
        velocity_seed=0,
        traj_file="cu_nvt.traj",
        traj_interval=10,
    )
    ```
  </Tab>

  <Tab title="NVT (temperature ramp)">
    ```python theme={null}
    import numpy as np
    from ase.build import bulk
    from mlip_arena.models import MLIPEnum
    from mlip_arena.tasks.md import run as MD
    from mlip_arena.tasks.utils import get_calculator

    atoms = bulk("Cu", "fcc", a=3.6).repeat(3)
    calculator = get_calculator(MLIPEnum["MACE-MP(M)"])

    # Ramp temperature from 300 K to 1200 K
    result = MD(
        atoms=atoms,
        calculator=calculator,
        ensemble="nvt",
        dynamics="nose-hoover",
        total_time=20_000,
        temperature=[300, 1200],  # linearly interpolated
        velocity_seed=7,
    )
    ```
  </Tab>

  <Tab title="NPT">
    ```python theme={null}
    from ase import units
    from ase.build import bulk
    from mlip_arena.models import MLIPEnum
    from mlip_arena.tasks.md import run as MD
    from mlip_arena.tasks.utils import get_calculator

    atoms = bulk("Cu", "fcc", a=3.6).repeat(3)
    calculator = get_calculator(MLIPEnum["MACE-MP(M)"])

    # Isothermal-isobaric MD with Nose-Hoover barostat
    result = MD(
        atoms=atoms,
        calculator=calculator,
        ensemble="npt",
        dynamics="nose-hoover",
        time_step=2.0,
        total_time=10_000,
        temperature=300.0,
        pressure=1.01325e-4 * units.bar,  # ~1 atm in eV/Å³
        velocity_seed=1,
        traj_file="cu_npt.traj",
    )
    ```
  </Tab>
</Tabs>

<Note>
  For NPT dynamics the cell is transformed to upper triangular form automatically (required by the ASE `NPT` implementation). If you supply a non-upper-triangular cell, it will be rotated; the physical geometry is preserved.
</Note>
