ExamplesΒΆ

TNG files can be read using the TNGFileIterator class as a file handle, which supports use as a context manager.

The TNGFileIterator has attributes related to the trajectory metadata, such as the number of integrator steps, the number of steps with data, the block_ids available at each step, and the stride at which each block is written.

The TNGFileIterator returns one frame at a time, which is accessed from the TNGFileIterator.current_integrator_step attribute or as part of any slicing or indexing operation. A NumPy array of the right size and datatype must then be provided to a getter method for the data to be read into. The required datatype is dictated by what type of block is being read. Supported datatypes are:

  • TNG_INT_DATA : np.int64

  • TNG_FLOAT_DATA : np.float32

  • TNG_DOUBLE_DATA : np.float64

Helper methods are provided to create np.ndarray instances of the right shape and datatype for a particular block in TNGFileIterator.make_ndarray_for_block_from_name and TNGFileIterator.make_ndarray_for_block_from_name.

An example of how to read positions and box vectors from a TNG file is shown below:

import pytng
import numpy as np

with pytng.TNGFileIterator("traj.tng", 'r') as tng:

   # make a numpy array to hold the data using helper function
   # this array will then be updated in-place

   positions = tng.make_ndarray_for_block_from_name("TNG_TRAJ_POSITIONS")
   box_vec = tng.make_ndarray_for_block_from_name("TNG_TRAJ_BOX_SHAPE")

   # the TNG API uses regular strides for data deposition, here we check
   # that the strides for positions and box_vectors are the same
   # and then iterate over all timesteps with this data
   # len(tng) is the total number of steps in the file

   assert (tng.block_strides["TNG_TRAJ_POSITIONS"] == tng.block_strides["TNG_TRAJ_BOX_SHAPE"])

   for ts in tng[0:len(tng):tng.block_strides["TNG_TRAJ_POSITIONS"]]:
      # read the integrator timestep, modifying the current_integrator_step
      # which is returned as ts

      # get the data from the requested block by supplying NumPy array which
      # is updated in-place or returned

      # update in place or return by value
      ts.get_positions(positions)
      # positions = ts.get_positions(positions) is equivalent

      # you can check if the last read was successful (contained data) easily
      if not ts.read_success:
         raise IOError("No position data at this timestep")

      ts.get_box(box_vec)
      # box_vec = ts.get_box(box_vec) is equivalent

      # you can check if the last read was successful (contained data) easily
      if not ts.read_success:
         raise IOError("No box data at this timestep")

It is also possible to slice and index the file object to select particular frames individually:

import pytng
import numpy as np

with pytng.TNGFileIterator('traj.tng', 'r') as tng:
    positions = tng.make_ndarray_for_block_from_name("TNG_TRAJ_POSITIONS")
    box_vec = tng.make_ndarray_for_block_from_name("TNG_TRAJ_BOX_SHAPE")
    positions = tng[100].get_positions(positions)
    box_vec = tng[200].get_box(box_vec)

If the step to read is not on the stride of the requested datatype, the NumPy array will be returned filled with np.nan. A contrived example of this is given below:

import pytng
import numpy as np

with pytng.TNGFileIterator("traj.tng", 'r') as tng:
   # make array for positions
   positions = tng.make_ndarray_for_block_from_name("TNG_TRAJ_POSITIONS")

   # choose a step
   step = 42

   # check that we are off stride (stride%step != 0)
   assert(tng.block_strides["TNG_TRAJ_POSITIONS"]%step != 0 )

   # slice a single timestep
   ts = tng[step]

   # get the data, which will be returned full of np.nan
   ts.get_positions(positions)

   # the read_success property will indicate that there was no data found
   # when the getter was called
   assert(ts.read_success == False)

   #  but we can also double check that the read was blank
   is_blank_read = np.all(np.isnan(positions)) # this will be true
   if is_blank_read:
      print("This is a blank read")

Available data blocks are listed at the end of this documentation. Common blocks for which there are getter methods include:

  • positions : TNGFileIterator.get_positions

  • box vectors : TNGFileIterator.get_box

  • forces : TNGFileIterator.get_forces

  • velocities : TNGFileIterator.get_velocities

Other blocks can be accessed using the TNGCurrentIntegratorStep.get_blockid method, where the block id needs to be supplied and can be accessed from the TNGFileIterator.block_ids attribute. An example of this is shown below:

import pytng
import numpy as np

with pytng.TNGFileIterator("traj.tng", 'r') as tng:

   # make array for the GMX potential energy block
   Epot = tng.make_ndarray_for_block_from_name("TNG_GMX_ENERGY_POTENTIAL")

   # get the block id for the GMX potential energy block
   Epot_block_id = tng.block_ids["TNG_GMX_ENERGY_POTENTIAL"]

   # get the block data for frame 0 with get_blockid
   tng[0].get_blockid[Epot_block_id]