Source code for schrodinger.application.jaguar.scan
"""
Support functions and classes for Jaguar scans.
Copyright Schrodinger, LLC. All rights reserved.
"""
import math
import re
from past.utils import old_div
__cvs_version__ = "$Revision: 1.2 $"
_version = __cvs_version__
[docs]class Scan(object):
    """
    A class for storing information about geometry scan variables.
    """
    precision = 1.0e-3
[docs]    def __init__(self,
                 var_name,
                 initial=None,
                 final=None,
                 steps=None,
                 step_size=None,
                 at_values=None):
        """
        This constructor can be used for two different types of scans -
        regular and irregular. For regular scans, the 'initial' argument
        must be specified along with two out of the three arguments 'final,'
        'steps,' or 'step_size.' For irregular scans, the only argument that
        should be specified is 'at_values.'
        Attributes
        var_name (str)
            The name of the variable the scan is based on.
        initial (float)
            The starting point for a scan.
        final (float)
            The final point for a scan.
        steps (float)
            The number of steps in the scan
        step_size (float)
            The step size of a regular scan.
        at_values (sequence of floats)
            Specific values at which a
        """
        self.var_name = var_name
        self._initial = initial
        self._final = final
        self._steps = steps
        self._step_size = step_size
        self._at_values = at_values 
    def _getFinal(self):
        if self._final is not None:
            return self._final
        elif self._at_values:
            return self._at_values[-1]
        else:
            return self._initial + (self._steps - 1) * self._step_size
    final = property(_getFinal, doc="final scan value")
    def _getInitial(self):
        if self._initial is not None:
            return self._initial
        else:
            return self._at_values[0]
    initial = property(_getInitial, doc="initial scan value")
    def _getSteps(self):
        if self._steps is not None:
            return self._steps
        elif self._at_values:
            return len(self._at_values)
        else:
            return 1 + int(
                math.floor(
                    old_div((self._final - self._initial), self._step_size)))
    steps = property(_getSteps, doc="number of steps in the scan")
    def _getStepSize(self):
        if self._step_size is not None:
            return self._step_size
        elif self._at_values:
            return None
        else:
            return old_div((self._final - self._initial), (self._steps - 1))
    step_size = property(_getStepSize,
                         doc="step size (None for 'at values' scans)")
    def _getAtValues(self):
        if self._at_values:
            return self._at_values
        else:
            return []
    at_values = property(
        _getAtValues, doc="an array of the scan values for 'at values' scans")
    def __eq__(self, other):
        """
        Equality comparison only looks at numeric quantities, not the
        variable name.
        """
        if self.steps != other.steps:
            return False
        if abs(self.initial - other.initial) > self.precision:
            return False
        if abs(self.final - other.final) > self.precision:
            return False
        if abs(self.step_size - other.step_size) > self.precision:
            return False
        return True
    def __ne__(self, other):
        return not self.__eq__(other)
[docs]    @staticmethod
    def parse(zvarstring):
        """
        Parse a scan definition string and return a Scan object.
        """
        # This is defined as a static method (as opposed to a module level
        # function) to allow it to be used when 'from scan import Scan' is
        # the import method.
        zvarsplit = zvarstring.split("=")
        if len(zvarsplit) == 2:
            zvarname, assignment = zvarsplit
        else:
            # The output files print 'ohlen at 1.11 1.12' without the equals
            # sign, so assume that a missing '=' is for this type of scan.
            zvarname, assignment = zvarstring.split(" ", 1)
        zvarname = zvarname.strip()
        scan = None
        # FROM ... TO ... BY
        match = re.match(r"\s*(from\s+)?(\S+)\s+to\s+(\S+)\s+by\s+(\S+)\s*",
                         assignment, re.IGNORECASE)
        if match:
            scan = Scan(zvarname,
                        initial=float(match.group(2)),
                        final=float(match.group(3)),
                        step_size=float(match.group(4)))
        # FROM ... TO ... IN
        if not scan:
            match = re.match(r"\s*(from\s+)?(\S+)\s+to\s+(\S+)\s+in\s+(\S+)\s*",
                             assignment, re.IGNORECASE)
            if match:
                scan = Scan(zvarname,
                            initial=float(match.group(2)),
                            final=float(match.group(3)),
                            steps=int(match.group(4)))
        # FROM ... BY ... IN
        if not scan:
            match = re.match(r"\s*(from\s+)?(\S+)\s+by\s+(\S+)\s+in\s+(\S+)\s*",
                             assignment, re.IGNORECASE)
            if match:
                scan = Scan(zvarname,
                            initial=float(match.group(2)),
                            step_size=float(match.group(3)),
                            steps=int(match.group(4)))
        # AT ...
        if not scan:
            match = re.match(r"\s*at\s+", assignment, re.IGNORECASE)
            if match:
                items = assignment.split()
                at_values = [float(item) for item in items[1:]]
                scan = Scan(zvarname, at_values=at_values)
        return scan