Source code for namdtools.core.core

"""
core.py
written in Python3
author: C. Lockhart <chrisblockhart@gmail.com>

>>> from namdtools import run_namd
>>> result = run_namd('system.namd', 'system.log'   , wait=True)
>>> print(result.log_)


>>> import namdtools
>>> config = namdtools.read_configuration('production.namd')
>>> config.run(10000)
"""

from namdtools.exceptions import NAMDError
import namdtools.options as options

import os
from subprocess import Popen


# NAMD configuration
class Configuration:
    """
    NAMD configuration file.
    """

    def __init__(self, filename=None):
        """
        Initialize instance of NAMD configuration.

        Parameters
        ----------
        filename : str
            (Optional) Name of configuration file to read.
        """

        if filename is not None:
            self._read_configuration(filename)

    def _read_configuration(self, filename):
        """
        Read configuration file.

        Note:
        """
        # TODO does the order in which arguments are read in matter?
        pass

    def run(self, n_steps):
        """

        Parameters
        ----------
        n_steps : int

        Returns
        -------

        """

        pass


# NAMD log
class Log:
    """
    NAMD log.
    """

    __slots__ = '_data'

    # Initialize log
    def __init__(self, data):
        """
        Initialize NAMD log

        Parameters
        ----------
        data : pandas.DataFrame
        """

        self.data = data

    # Create data property
    @property
    def data(self):
        return self._data

    # Set the data, must be pandas DataFrame
    @data.setter
    def data(self, data):
        """
        Set the data in the NAMD log.

        Parameters
        ----------
        data : pandas.DataFrame
        """

        import pandas as pd

        if not isinstance(data, pd.DataFrame):
            raise AttributeError('must be pandas.DataFrame')

        self._data = data

    # Get unknown function calls and run them as pandas
    def __getattr__(self, item):
        return getattr(self._data, item)

    def __getitem__(self, item):
        return self._data[item]

    def __len__(self):
        return len(self._data)

    def __setitem__(self, key, value):
        self._data[key] = value


# NAMD controller
[docs]class NAMD: """ Controls NAMD with Python. """ # Initialize class instance
[docs] def __init__(self, configuration_path=None, log_path=None, executable=None, wait=True): """ Initialize the NAMD runner. Parameters ---------- configuration_path : str (Optional) Path to NAMD configuration. log_path : str (Optional) Path to log file. executable : str or list (Optional) NAMD executable for command line. This will be built if not provided. wait : bool Should we wait for the NAMD job to finish? Or should it run in the background? (Default: True) """ # Set the NAMD configuration path self._configuration_path = None if configuration_path is not None: self.configuration_path = configuration_path # Set the log path self._log_path = None if configuration_path is not None and log_path is None: self.log_path = os.path.splitext(configuration_path)[0] + '.log' if log_path is not None: self.log_path = log_path # Set the NAMD executable self._executable = None if executable is not None: self.executable = executable else: self.executable = _compile_namd_executable() # Job monitoring variables self._wait = bool(wait) self._process = None
# Get the configuration path @property def configuration_path(self): """ Get the NAMD configuration path. Returns ------- str NAMD configuration path. """ return self._configuration_path # Set the configuration path @configuration_path.setter def configuration_path(self, configuration_path): """ Set the NAMD configuration path. Parameters ---------- configuration_path : str NAMD configuration path. """ # Check that the configuration file exists if not os.path.exists(configuration_path): raise AttributeError('%s does not exist' % configuration_path) # Set the path self._configuration_path = configuration_path # Get the NAMD executable @property def executable(self): """ Get the NAMD executable with options. Returns ------- str or list NAMD executable with options. """ return self._executable # Set the NAMD executable @executable.setter def executable(self, executable): """ Set the executable. Parameters ---------- executable : str or list NAMD command to run through subprocess. """ # If executable is a string, crudely split it to list if isinstance(executable, str): executable = executable.split() # Make sure we have a list if not isinstance(executable, list): raise AttributeError('must be string or list') # Set the executable self._executable = executable @property def pid(self): # Has the job started? if self._process is None: raise NAMDError('job not found') # Return pid return self._process.pid
[docs] def poll(self): """ Check if NAMD is still running. Returns ------- None or intb None if NAMD is running, return code if simulations are finished. """ return None if self._process is None else self._process.poll()
@property def log_path(self): """ Logfile Returns ------- """ return self._log_path @log_path.setter def log_path(self, log_path): self._log_path = log_path # Start simulations
[docs] def start(self): """ Start NAMD simulation. """ # Check if NAMD is already running if self.poll(): raise NAMDError('NAMD already running') # Open output file and run with open(self._log_path, 'w') as stream: self._process = Popen(self._executable + [self._configuration_path], stdout=stream) # Should we wait? if self._wait: self._process.wait() if self._process.poll() != 0: raise NAMDError('NAMD job did not finish successfully')
# Stop simulations def stop(self): # Check if NAMD is already running if self.poll(): self._process.kill() # Otherwise, alert that NAMD is not running # TODO should this be warning? else: pass # Is the NAMD job successful? @property def success(self): # Pull the status from the return code status = self.poll() # If status == 0, the job completed successfully if status == 0: status = 0 # Otherwise, we were not successful elif status is not None: status = False # Return if successful return status
# Run namd on configuration file and write to log file
[docs]def run_namd(configuration_file, log_file, wait=True): """ Run NAMD on `configuration_file` and write to `log_file`. Parameters ---------- configuration_file : str Path to the NAMD configuration file. log_file : str Path to log file to write out. wait : bool Should we wait for the NAMD job to finish? Or should we launch in the background? Returns ------- NAMD Instance of NAMD controller. """ # Create NAMD instance job = NAMD(configuration_file, log_file, wait=wait) # Start NAMD job.start() # Was run successful? if not job.success: raise NAMDError('failed to successfully complete') # Return return job
# Extract energy from log file def extract_energy(log_file): import pandas as pd # Initialize DataFrame information columns = None records = [] # Read through log file and extract energy records with open(log_file, 'r') as stream: for line in stream.readlines(): # Read first ETITLE if columns is None and line[:6] == 'ETITLE': columns = line.lower().split()[1:] # Save each energy record if line[:6] == 'ENERGY': records.append(line.split()[1:]) # Return DataFrame return pd.DataFrame(records, columns=columns).set_index(columns[0]) def read_configuration(path): pass # Compile namd command def _compile_namd_executable(): """ Compile namd executable to run through subprocess. Returns ------- list List of command-line arguments to send to subprocess. """ # Set up dummy list to store command cmd = [] # Add charmrun to command if options.use_charmrun: cmd.append(_first_available([ options.charmrun_path, os.path.join(os.getcwd(), 'charmrun'), 'charmrun' ])) for arg in options.charmrun_args: cmd.append(str(arg)) # Add namd to command if options.namd_path is None: raise NAMDError('what kind of monster sets options.namd to None?') cmd.append(_first_available([ options.namd_path, os.path.join(os.getcwd(), 'namd2'), os.path.join(os.getcwd(), 'namd2.exe'), 'namd2', 'namd2.exe' ])) for arg in options.namd_args: cmd.append(str(arg)) # Return return cmd # Helper function to find first available existing path def _first_available(paths): """ Loop through `paths` and find the first available. Parameters ---------- paths : list List of paths. Returns ------- str First path found. """ # Loop over all paths and return first path that exists for path in paths: if os.path.exists(path): return path # If we haven't found a path, throw an error raise FileNotFoundError('%s not found' % paths[-1])