oxDNA Analysis Tools

oxDNA Analysis Tools (oat in short) is a suite of command line Python tools and importable modules for performing structural analyses of oxDNA simulations. oxDNA Analysis Tools has been updated to use the new topology.

Command Line Interface

The scripts can be run via the command line with:

oat <script name> <script arguments>

There are Bash and Zsh autocompletes avilable for the script names, which can be activated by copying the file /oxDNA/analysis/oat-completion.sh to your autocompletes file (in Bash: ~/.bash_completion) or adding source /path/to/oxDNA/analysis/oat-completion.sh to your rc file and restarting your command line session. Note that for Zsh, you must be using the modern completion engine by adding

autoload -U compinit
compinit

to your .zshrc file.

Documentation for individual scripts:

Writing your own oat scripts

The scripts in this library are organized in a specific way to facilitate composability and allow use both as command-line tools and as an importable Python library. When contributing to the package, please first take a look at skeleton.py, which is a ‘skeleton’ of a trajectory analysis script. You might also want to look at the RyeReader, which contains file-reading utilities and the built-in data structures

If you make changes to the Cython file reader, the pre-transpiled .c file is currently in the .gitignore because of random strings that change every time its built. To override .gitignore, use git add -f analysis/src/oxDNA_analysis_tools/cython_utils/get_confs.c.

Unit tests

For development purposes, oat has comprehensive unit tests implemented in Pytest which can be found in oxDNA/analysis/tests/. To run all tests, first install oat either via make install as described in the installation instructions, or with python -m pip install oxDNA/analysis/, then run:

pytest oxDNA/analysis/tests/test_cli/`

Or run a specific test:

pytest oxDNA/analysis/tests/test_cli/test_mean.py

Test coverage can be checked using Coverage.py:

coverage run --source=oxDNA_analysis_tools -m pytest tests/test_cli/ && coverage combine && coverage html && open htmlcov/index.html 

If a test fails on your machine, please consider opening an issue on GitHub to let us know!

Scripting interface

The scripting API for oat can be imported into Python scripts as the oxDNA_analysis_tools package. For example, to use the optimized oxDNA trajectory reader you would include the following lines in your script:

from oxDNA_analysis_tools.UTILS.RyeReader import describe, get_confs

# get the top and traj names from the command line or hardcode them

top_info, traj_info = describe(top, traj)
confs = get_confs(top_info, traj_info, start_conf, n_confs)

The mean.py script located at oxDNA/analysis/src/oxDNA_analysis_tools/mean.py has been commented in detail to give a full example of how to use oxDNA_analysis_tools to write your own analyses.

Full API documentation:

Top-level API

Utility API

Forces API

Constants

There are a couple global constants which oat uses across scripts. These can be found in oxDNA/analysis/src/oxDNA_analysis_tools/UTILS/constants.py. They can be set persistently on an installed copy of oat using oat config with a flag, where the following options are available:

oat config -n 100 # Load configurations in chunks of 100 per process while reading trajectories
oat config -f 300 # Set output figure DPI to 300

Analysis notebooks

As simulations become more prevalent and analysis pipelines more complicated, it can be beneficial for researchers to define their entire simulation/analysis pipeline in Python notebooks, much as is often done in the machine learning community. The modular nature of oxpy and oxDNA_analysis_tools lends itself to composing analyses together to ask specific scientific questions.

This section represents a minimal example of how to run, analyze and visualize a simulation from within a Jupyter Notebook. For a more comprehensive example which includes live plotting of values while the simulation is running, see the OXPY_JUPYTER example in the oxDNA/examples directory.

From a directory containing an oxDNA input file, and initial configuration/topology files:

First, we will set up some functions for running simulations with oxpy Instead of modifying an existing input file, you can also simply provide the entire input file as a dict to kwargs and remove the init_from_filename call.

import oxpy
import multiprocessing

# Running oxpy multiple times from the same jupyter kernel crashes the kernel for some reason
# So we will be starting simulations from a separate thread.
def spawn(f, kwargs=None):
    if kwargs == None:
        kwargs = {}
    p = multiprocessing.Process(target=f, kwargs = kwargs)
    p.start()
    return p

# Sets up and runs the simulation
def run(**kwargs):
    with oxpy.Context():
        inp = oxpy.InputFile()
        inp.init_from_filename("input")

        # Modify inputfile at runtime
        # all input parameters provided to oxDNA need to be strings 
        for k,v in kwargs.items(): 
            inp[k] = v
        
        manager = oxpy.OxpyManager(inp)
        manager.run_complete()

p = None

Next, we will initiate a simulation.
For this example, we set the number of steps and print intervals to be smaller such that this simulation finishes in a reasonable amount of time.

# Kill any currently running simulations
try:
    p.terminate()
except:
    pass

# You can pass modifications to a default input file as dict of key : value pairs
# Both key and values must be strings
input_mods = {
    "steps" : "1e6",
    "print_conf_interval" : "1e4",
    "print_energy_every" : "1e4"
}

# Run a simulation and obtain a reference to the background process
p = spawn(run, input_mods)

# The input file used to run the simulation gets lost in the subprocess, 
# so make it again so we have access to it for analysis
with oxpy.Context():
    inp = oxpy.InputFile()
    inp.init_from_filename("input")
    for k,v in input_mods.items(): 
        inp[k] = v

You can stop a running simulation by terminating its process:

# You can stop the simulation at any time by running this cell.
p.terminate()

Use oat to compute the mean structure and RMSF

from oxDNA_analysis_tools.mean import mean
from oxDNA_analysis_tools.deviations import deviations
from oxDNA_analysis_tools.UTILS.RyeReader import describe

# Get the trajectory and topology file names from the oxpy.InputFile object
top = inp["topology"]
traj = inp["trajectory_file"]
top_info, traj_info = describe(top, traj)

# Compute the mean structure and RMSFs
mean_conf = mean(traj_info, top_info, ncpus=4)
RMSDs, RMSFs = deviations(traj_info, top_info, mean_conf, ncpus=4)

#They come out as numpy arrays, need to be a dict with a list for visualization
RMSFs = {"RMSF": RMSFs.tolist()}

oxView iframes can be embedded in Jupyter Notebooks with oat’s oxView lib

from oxDNA_analysis_tools.UTILS.oxview import oxdna_conf

# Display python objects in an oxview iframe
oxdna_conf(top_info, mean_conf, RMSFs)

Which will produce an oxView iframe:

Citation

If you find oxDNA Analysis Tools and oxView useful, please cite our paper:

Erik Poppleton, Joakim Bohlin, Michael Matthies, Shuchi Sharma, Fei Zhang, Petr Šulc: Design, optimization and analysis of large DNA and RNA nanostructures through interactive visualization, editing and molecular simulation, Nucleic Acids Research, Volume 48, Issue 12, Page e72 (2020). DOI: 10.1093/nar/gkaa417

And additionally for oxView, please also cite:

Joakim Bohlin, Michael Matthies, Jonah Procyk, Erik Poppleton, Aatmik Mallya, Hao Yan, Petr Šulc: Design and simulation of DNA, RNA and hybrid protein–nucleic acid nanostructures with oxView. Nature Protocols, (2022). DOI: 10.1038/s41596-022-00688-5