# 7.10. IMDReader — `MDAnalysis.coordinates.IMD`

This module provides support for reading molecular dynamics simulation data via the Interactive Molecular Dynamics (IMD) protocol v3. The IMD protocol allows two-way communicating molecular simulation data through a socket. Via IMD, a simulation engine sends data to a receiver (in this case, the IMDClient) and the receiver can send forces and specific control requests (such as pausing, resuming, or terminating the simulation) back to the simulation engine.

Note

This reader only supports IMDv3, which is implemented in GROMACS, LAMMPS, and NAMD at varying stages of development. See the imdclient simulation engine docs for more. While IMDv2 is widely available in simulation engines, it was designed primarily for visualization and gaps are allowed in the stream (i.e., an inconsistent number of integrator time steps between transmitted coordinate arrays is allowed)

The `IMDReader` connects to a simulation via a socket and receives coordinate, velocity, force, and energy data as the simulation progresses. This allows for real-time monitoring and analysis of ongoing simulations. It uses the imdclient package (dependency) to implement the IMDv3 protocol and manage the socket connection and data parsing.

See also

`IMDReader`

  Technical details and parameter options for the reader class

imdclient documentation

  Complete documentation for the IMDClient package

IMDClient GitHub repository

  Source code and development resources

## 7.10.1. Usage Example

As an example of reading a stream, after configuring GROMACS to run a simulation with IMDv3 enabled (see the imdclient simulation engine docs for up-to-date resources on configuring each simulation engine), use the following commands:

    gmx mdrun -v -nt 4 -imdwait -imdport 8889

The `IMDReader` can then connect to the running simulation and stream data in real time:

    import MDAnalysis as mda
    u = mda.Universe("topol.tpr", "imd://localhost:8889", buffer_size=10*1024*1024)

    print("    time [         position         ] [         velocity         ] [           force          ] [            box           ]")
    sel = u.select_atoms("all")  # Select all atoms; adjust selection as needed
    for ts in u.trajectory:
        print(f'{ts.time:8.3f} {sel[0].position} {sel[0].velocity} {sel[0].force} {u.dimensions[0:3]}')

Important

Jupyter Notebook Users: When using IMDReader in Jupyter notebooks, be aware that kernel restarts will not gracefully close active IMD connections. This can leave socket connections open, potentially preventing new connections to the same stream.

Always use `try/except/finally` blocks to ensure proper cleanup:

    import MDAnalysis as mda

    try:
        u = mda.Universe("topol.tpr", "imd://localhost:8889")
    except Exception as e:
        print(f"Error during connection: {e}")
    else:
        try:
            # Your analysis code here
            for ts in u.trajectory:
                # Process each frame
                pass
        finally:
            # Ensure connection is closed
            u.trajectory.close()

Always explicitly call `u.trajectory.close()` when finished with analysis to ensure connection is closed properly.

## 7.10.2. Important Limitations

Warning

The IMDReader has some important limitations that are inherent in streaming data.

Since IMD streams data in real-time from a running simulation, it has fundamental constraints that differ from traditional trajectory readers:

- No random access: Cannot jump to arbitrary frame numbers or seek backwards

- Forward-only access: You can only move forward through frames as they arrive

- No trajectory length: The total number of frames is unknown until the simulation ends

- Single-use iteration: Cannot restart or rewind once the stream has been consumed

- No independent copies: Cannot create separate reader instances for the same stream

- No stream restart: Cannot reconnect or reopen once the connection is closed

- No bulk operations: Cannot extract all data at once using timeseries methods

- Limited multiprocessing: Cannot split reader across processes for parallel analysis

- Single client connection: Only one reader can connect to an IMD stream at a time

- No trajectory Writing: Complimentary IMD Writer class is not available for streaming data

See also

See `StreamReaderBase` for technical details.

## 7.10.3. Multiple Client Connections

The ability to establish multiple simultaneous connections to the same IMD port is MD engine implementation dependent. Some simulation engines may allow multiple clients to connect concurrently, while others may reject or fail additional connection attempts.

See the imdclient simulation engine docs for further details.

Important

Even when multiple connections are supported by the simulation engine, each connection receives its own independent data stream. These streams may contain different data depending on the simulation engine’s configuration, so multiple connections should not be assumed to provide identical data streams.

## 7.10.4. Classes

class MDAnalysis.coordinates.IMD.IMDReader(filename, n_atoms=None, buffer_size=10485760, **kwargs)

  Coordinate reader implementing the IMDv3 protocol for streaming simulation data.

  This class handles the technical aspects of connecting to IMD-enabled simulation engines and processing the incoming data stream. For usage examples and protocol overview, see the module documentation above.

  The reader manages socket connections, data buffering, and frame parsing according to the IMDv3 specification. It automatically handles different data packet types (coordinates, velocities, forces, energies, timing) and populates MDAnalysis timestep objects accordingly.

  Parameters:

    - filename (a string of the form "imd://host:port" where host is the hostname) – or IP address of the listening simulation engine’s IMD server and port is the port number.

    - n_atoms (int (optional)) – number of atoms in the system. defaults to number of atoms in the topology. Don’t set this unless you know what you’re doing.

    - buffer_size (int (optional) default=10*(1024**2)) – number of bytes of memory to allocate to the `IMDClient`’s internal buffer. Defaults to 10 megabytes. Larger buffers can improve performance for analyses with periodic heavy computation.

    - **kwargs (dict (optional)) – keyword arguments passed to the constructed `IMDClient`

  Notes

  The IMDReader provides access to additional simulation data through the timestep’s data attribute (ts.data). The following keys may be available depending on what the simulation engine transmits:

  - dt float

      Time step size in picoseconds (from the IMD_TIME packet of the IMDv3 protocol)

  - step int

      Current simulation step number (from the IMD_TIME packet of the IMDv3 protocol)

  - Energy termsfloat

      Various energy components (e.g., ‘potential’, ‘kinetic’, ‘total’, etc.) from the IMD_ENERGIES packet of the IMDv3 protocol.

  Note

  For important limitations inherent to streaming data, see the module documentation above and `StreamReaderBase` for more technical details.

  Added in version 2.10.0.

  OtherWriter(filename, **kwargs)

    Writer creation is not supported for streaming trajectories.

    OtherWriter initialization requires frame-based parameters and trajectory indexing information. Streaming readers process data sequentially without meaningful frame indexing, making writer setup impossible.

    Parameters:

      - filename (str) – Output filename (ignored, as method is not supported)

      - **kwargs – Additional keyword arguments (ignored, as method is not supported)

    Raises:

      RuntimeError – Always raised, as writer creation is not supported for streaming trajectories

  Writer(filename, **kwargs)

    Writer creation is not supported for streaming trajectories.

    Writer creation requires trajectory metadata that streaming readers cannot provide due to their sequential processing nature.

    Parameters:

      - filename (str) – Output filename (ignored, as method is not supported)

      - **kwargs – Additional keyword arguments (ignored, as method is not supported)

    Raises:

      RuntimeError – Always raised, as writer creation is not supported for streaming trajectories

  add_auxiliary(aux_spec: str | Dict[str, str] = None, auxdata: str | AuxReader = None, format: str = None, **kwargs) → None

    Add auxiliary data to be read alongside trajectory.

    Auxiliary data may be any data timeseries from the trajectory additional to that read in by the trajectory reader. auxdata can be an `AuxReader` instance, or the data itself as e.g. a filename; in the latter case an appropriate `AuxReader` is guessed from the data/file format. An appropriate format may also be directly provided as a key word argument.

    On adding, the AuxReader is initially matched to the current timestep of the trajectory, and will be updated when the trajectory timestep changes (through a call to `next()` or jumping timesteps with `trajectory[i]`).

    The representative value(s) of the auxiliary data for each timestep (as calculated by the `AuxReader`) are stored in the current timestep in the `ts.aux` namespace under aux_spec; e.g. to add additional pull force data stored in pull-force.xvg:

        u = MDAnalysis.Universe(PDB, XTC)
        u.trajectory.add_auxiliary('pull', 'pull-force.xvg')

    The representative value for the current timestep may then be accessed as `u.trajectory.ts.aux.pull` or `u.trajectory.ts.aux['pull']`.

    The following applies to energy readers like the `EDRReader`.

    All data that is present in the (energy) file can be added by omitting aux_spec like so:

        u.trajectory.add_auxiliary(auxdata="ener.edr")

    aux_spec is expected to be a dictionary that maps the desired attribute name in the `ts.aux` namespace to the precise data to be added as identified by a `data_selector`:

        term_dict = {"temp": "Temperature", "epot": "Potential"}
        u.trajectory.add_auxiliary(term_dict, "ener.edr")

    Adding this data can be useful, for example, to filter trajectory frames based on non-coordinate data like the potential energy of each time step. Trajectory slicing allows working on a subset of frames:

        selected_frames = np.array([ts.frame for ts in u.trajectory
                                    if ts.aux.epot < some_threshold])
        subset = u.trajectory[selected_frames]

    See also

    `remove_auxiliary()`

    Note

    Auxiliary data is assumed to be time-ordered, with no duplicates. See the Auxiliary API.

  add_transformations(*transformations)

    Add all transformations to be applied to the trajectory.

    This function take as list of transformations as an argument. These transformations are functions that will be called by the Reader and given a `Timestep` object as argument, which will be transformed and returned to the Reader. The transformations can be part of the `transformations` module, or created by the user, and are stored as a list transformations. This list can only be modified once, and further calls of this function will raise an exception.

        u = MDAnalysis.Universe(topology, coordinates)
        workflow = [some_transform, another_transform, this_transform]
        u.trajectory.add_transformations(*workflow)

    The transformations are applied in the order given in the list transformations, i.e., the first transformation is the first or innermost one to be applied to the `Timestep`. The example above would be equivalent to

        for ts in u.trajectory:
           ts = this_transform(another_transform(some_transform(ts)))

    Parameters:

      transform_list (list) – list of all the transformations that will be applied to the coordinates in the order given in the list

    See also

    `MDAnalysis.transformations`

  property aux_list

    Lists the names of added auxiliary data.

  check_slice_indices(start, stop, step)

    Check and validate slice indices for streaming trajectories.

    Streaming trajectories have fundamental constraints that differ from traditional trajectory files:

    - No start/stop indices: Since streams process data continuously without knowing the total length, `start` and `stop` must be `None`

    - Step-only slicing: Only the `step` parameter is meaningful, controlling how many frames to skip during iteration

    - Forward-only: `step` must be positive (> 0) as streams cannot be processed backward in time

    Parameters:

      - start (int or None) – Starting frame index. Must be `None` for streaming readers.

      - stop (int or None) – Ending frame index. Must be `None` for streaming readers.

      - step (int or None) – Step size for iteration. Must be positive integer or `None` (equivalent to 1).

    Returns:

      (start, stop, step) with validated values

    Return type:

      tuple

    Raises:

      ValueError – If `start` or `stop` are not `None`, or if `step` is not a positive integer.

    Examples

    Valid streaming slices:

        traj[:]        # All frames (step=None, equivalent to step=1)
        traj[::2]      # Every 2nd frame
        traj[::10]     # Every 10th frame

    Invalid streaming slices:

        traj[5:]       # Cannot specify start index
        traj[:100]     # Cannot specify stop index
        traj[5:100:2]  # Cannot specify start or stop indices
        traj[::-1]     # Cannot go backwards (negative step)

    See also

    `__getitem__`, `StreamFrameIteratorSliced`

    Added in version 2.10.0.

  close()

    Gracefully shut down the reader. Stops the producer thread.

  convert_forces_from_native(force, inplace=True)

    Conversion of forces array force from native to base units

    Parameters:

      - force (array_like) – Forces to transform

      - inplace (bool (optional)) – Whether to modify the array inplace, overwriting previous data

    Note

    By default, the input force is modified in place and also returned. In-place operations improve performance because allocating new arrays is avoided.

    Added in version 0.7.7.

  convert_forces_to_native(force, inplace=True)

    Conversion of force array force from base to native units.

    Parameters:

      - force (array_like) – Forces to transform

      - inplace (bool (optional)) – Whether to modify the array inplace, overwriting previous data

    Note

    By default, the input force is modified in place and also returned. In-place operations improve performance because allocating new arrays is avoided.

    Added in version 0.7.7.

  convert_pos_from_native(x, inplace=True)

    Conversion of coordinate array x from native units to base units.

    Parameters:

      - x (array_like) – Positions to transform

      - inplace (bool (optional)) – Whether to modify the array inplace, overwriting previous data

    Note

    By default, the input x is modified in place and also returned. In-place operations improve performance because allocating new arrays is avoided.

    Changed in version 0.7.5: Keyword inplace can be set to `False` so that a modified copy is returned unless no conversion takes place, in which case the reference to the unmodified x is returned.

  convert_pos_to_native(x, inplace=True)

    Conversion of coordinate array x from base units to native units.

    Parameters:

      - x (array_like) – Positions to transform

      - inplace (bool (optional)) – Whether to modify the array inplace, overwriting previous data

    Note

    By default, the input x is modified in place and also returned. In-place operations improve performance because allocating new arrays is avoided.

    Changed in version 0.7.5: Keyword inplace can be set to `False` so that a modified copy is returned unless no conversion takes place, in which case the reference to the unmodified x is returned.

  convert_time_from_native(t, inplace=True)

    Convert time t from native units to base units.

    Parameters:

      - t (array_like) – Time values to transform

      - inplace (bool (optional)) – Whether to modify the array inplace, overwriting previous data

    Note

    By default, the input t is modified in place and also returned (although note that scalar values t are passed by value in Python and hence an in-place modification has no effect on the caller.) In-place operations improve performance because allocating new arrays is avoided.

    Changed in version 0.7.5: Keyword inplace can be set to `False` so that a modified copy is returned unless no conversion takes place, in which case the reference to the unmodified x is returned.

  convert_time_to_native(t, inplace=True)

    Convert time t from base units to native units.

    Parameters:

      - t (array_like) – Time values to transform

      - inplace (bool, optional) – Whether to modify the array inplace, overwriting previous data

    Note

    By default, the input t is modified in place and also returned. (Also note that scalar values t are passed by value in Python and hence an in-place modification has no effect on the caller.)

    Changed in version 0.7.5: Keyword inplace can be set to `False` so that a modified copy is returned unless no conversion takes place, in which case the reference to the unmodified x is returned.

  convert_velocities_from_native(v, inplace=True)

    Conversion of velocities array v from native to base units

    Parameters:

      - v (array_like) – Velocities to transform

      - inplace (bool (optional)) – Whether to modify the array inplace, overwriting previous data

    Note

    By default, the input v is modified in place and also returned. In-place operations improve performance because allocating new arrays is avoided.

    Added in version 0.7.5.

  convert_velocities_to_native(v, inplace=True)

    Conversion of coordinate array v from base to native units

    Parameters:

      - v (array_like) – Velocities to transform

      - inplace (bool (optional)) – Whether to modify the array inplace, overwriting previous data

    Note

    By default, the input v is modified in place and also returned. In-place operations improve performance because allocating new arrays is avoided.

    Added in version 0.7.5.

  copy()

    Reader copying is not supported for streaming trajectories.

    Streaming readers maintain internal state and connection resources that cannot be duplicated. Each stream connection is unique and cannot be copied.

    Raises:

      RuntimeError – Always raised, as copying is not supported for streaming trajectories

  property dt: float

    Time between two trajectory frames in picoseconds.

  property frame: int

    Frame number of the current time step.

    This is a simple short cut to `Timestep.frame`.

  get_aux_attribute(auxname, attrname)

    Get the value of attrname from the auxiliary auxname

    Parameters:

      - auxname (str) – Name of the auxiliary to get value for

      - attrname (str) – Name of gettable attribute in the auxiliary reader

    See also

    `set_aux_attribute()`

  get_aux_descriptions(auxnames=None)

    Get descriptions to allow reloading the specified auxiliaries.

    If no auxnames are provided, defaults to the full list of added auxiliaries.

    Passing the resultant description to `add_auxiliary()` will allow recreation of the auxiliary. e.g., to duplicate all auxiliaries into a second trajectory:

        descriptions = trajectory_1.get_aux_descriptions()
        for aux in descriptions:
            trajectory_2.add_auxiliary(**aux)

    Returns:

      List of dictionaries of the args/kwargs describing each auxiliary.

    Return type:

      list

    See also

    `MDAnalysis.auxiliary.base.AuxReader.get_description()`

  iter_as_aux(auxname)

    Iterate through timesteps for which there is at least one assigned step from the auxiliary auxname within the cutoff specified in auxname.

    See also

    `next_as_aux()`, `iter_auxiliary()`

  iter_auxiliary(auxname, start=None, stop=None, step=None, selected=None)

    Iterate through the auxiliary auxname independently of the trajectory.

    Will iterate over the specified steps of the auxiliary (defaults to all steps). Allows to access all values in an auxiliary, including those out of the time range of the trajectory, without having to also iterate through the trajectory.

    After interation, the auxiliary will be repositioned at the current step.

    Parameters:

      - auxname (str) – Name of the auxiliary to iterate over.

      - (start (optional) – Options for iterating over a slice of the auxiliary.

      - stop (optional) – Options for iterating over a slice of the auxiliary.

      - step) (optional) – Options for iterating over a slice of the auxiliary.

      - selected (lst | ndarray, optional) – List of steps to iterate over.

    Yields:

      `AuxStep` object

    See also

    `iter_as_aux()`

  property n_frames

    Changes as stream is processed unlike other readers

  next()

    Advance to the next timestep in the streaming trajectory.

    Streaming readers process frames sequentially and cannot rewind once iteration completes. Use `for ts in trajectory` for iteration.

    Returns:

      The next timestep in the stream

    Return type:

      Timestep

    Raises:

      StopIteration – When the stream ends or no more frames are available

  next_as_aux(auxname)

    Move to the next timestep for which there is at least one step from the auxiliary auxname within the cutoff specified in auxname.

    This allows progression through the trajectory without encountering `NaN` representative values (unless these are specifically part of the auxiliary data).

    If the auxiliary cutoff is not set, where auxiliary steps are less frequent (`auxiliary.dt > trajectory.dt`), this allows progression at the auxiliary pace (rounded to nearest timestep); while if the auxiliary steps are more frequent, this will work the same as calling `next()`.

    See the Auxiliary API.

    See also

    `iter_as_aux()`

  classmethod parse_n_atoms(filename, **kwargs)

    Read the coordinate file and deduce the number of atoms

    Returns:

      n_atoms – the number of atoms in the coordinate file

    Return type:

      int

    Raises:

      NotImplementedError – when the number of atoms can’t be deduced

  remove_auxiliary(auxname)

    Clear data and close the `AuxReader` for the auxiliary auxname.

    See also

    `add_auxiliary()`

  rename_aux(auxname, new)

    Change the name of the auxiliary auxname to new.

    Provided there is not already an auxiliary named new, the auxiliary name will be changed in ts.aux namespace, the trajectory’s list of added auxiliaries, and in the auxiliary reader itself.

    Parameters:

      - auxname (str) – Name of the auxiliary to rename

      - new (str) – New name to try set

    Raises:

      ValueError – If the name new is already in use by an existing auxiliary.

  rewind()

    Rewinding is not supported for streaming trajectories.

    Streaming readers process data continuously from streams and cannot restart or go backward in the stream once consumed.

    Raises:

      RuntimeError – Always raised, as rewinding is not supported for streaming trajectories

  set_aux_attribute(auxname, attrname, new)

    Set the value of attrname in the auxiliary auxname.

    Parameters:

      - auxname (str) – Name of the auxiliary to alter

      - attrname (str) – Name of settable attribute in the auxiliary reader

      - new – New value to try set attrname to

    See also

    `get_aux_attribute()`, `rename_aux()`

  property time

    Time of the current frame in MDAnalysis time units (typically ps).

    This is either read straight from the Timestep, or calculated as time = `Timestep.frame` * `Timestep.dt`

  timeseries(**kwargs)

    Timeseries extraction is not supported for streaming trajectories.

    Streaming readers cannot randomly access frames or store bulk coordinate data in memory, which `timeseries()` requires. Use sequential frame iteration instead.

    Parameters:

      **kwargs – Any keyword arguments (ignored, as method is not supported)

    Raises:

      RuntimeError – Always raised, as timeseries extraction is not supported for streaming trajectories

  property totaltime: float

    Total length of the trajectory

    The time is calculated as `(n_frames - 1) * dt`, i.e., we assume that the first frame no time as elapsed. Thus, a trajectory with two frames will be considered to have a length of a single time step dt and a “trajectory” with a single frame will be reported as length 0.

  property transformations

    Returns the list of transformations

  units = {'length': None, 'time': None, 'velocity': None}

    dict with units of of time and length (and velocity, force, … for formats that support it)