Source code for schrodinger.application.jaguar.jaguar_diff

"""
Classes for parsing Jaguar output files and accessing output properties
programmatically.

Copyright Schrodinger, LLC. All rights reserved.

"""

import math

from schrodinger.structutils.rmsd import calculate_in_place_rmsd

_SIMPLE, _FLOAT_PRECISION, _LIST_PRECISION, _PATH_PRECISION, _LIST_OBJECTS = list(
    range(5))


[docs]class CaseInsensitiveString(str, object): """ A class that behaves like a regular string except that comparisons are done on a case insensitive basis. """ def __eq__(this, that): return this.lower() == that.lower() def __ne__(this, that): return this.lower() != that.lower() def __lt__(this, that): return this.lower() < that.lower() def __le__(this, that): return this.lower() <= that.lower() def __gt__(this, that): return this.lower() > that.lower() def __ge__(this, that): return this.lower() >= that.lower() def __hash__(self): return hash(self.lower())
[docs]def compare_a_none(a, b): """ Comparison if one or both values are a None. Assume None is always less than any value """ if a is None and b is None: return 0 elif a is not None and b is None: return 1 else: return -1
[docs]def compare_float(a, b, precision): """ Compare two floats to the specified precision. """ diff = a - b if abs(diff) <= precision: return 0 elif diff > 0: return 1 else: return -1
[docs]def compare_angle(a, b, precision): """ Compare two angles to the specified precision, taking into account modulo 360 being equivalent. We also don't care about the sign. :rtype: int :return: 0 if angles considered equivalent, 1 if angles considered different """ diff = math.fmod(abs(a - b), 360) if diff <= precision: return 0 if 360 - diff <= precision: return 0 return 1
def _diff_simple(a, b, attr): """ :return True if two _SIMPLE attributes differ; else False """ a_attr = getattr(a, attr, None) b_attr = getattr(b, attr, None) # different if both attr exist and differ if a_attr is not None and b_attr is not None: if a_attr != b_attr: return True # different if exactly one attribute exists elif (a_attr is not None and b_attr is None) or \ (a_attr is None and b_attr is not None): return True return False def _diff_float(a, b, attr, precision): """ :return True if two _FLOAT_PRECISION attributes differ; else False """ a_attr = getattr(a, attr, None) b_attr = getattr(b, attr, None) try: if compare_float(a_attr, b_attr, precision): return True except TypeError: if a_attr is not None or b_attr is not None: return True return False def _diff_list(a, b, attr, precision): """ :return set(['attr']) if _LIST_PRECISION attributes differ globally; set of ('attr', list index) if any elements differ; empty set otherwise """ a_attr = getattr(a, attr, []) b_attr = getattr(b, attr, []) # look for global differences try: if len(a_attr) != len(b_attr): return set([attr]) except TypeError: if a_attr is None and b_attr is None: return set() elif a_attr is not None or b_attr is not None: return set([attr]) else: raise # drill down to compare element by element diffs diff = set() for i, (a_value, b_value) in enumerate(zip(a_attr, b_attr)): if compare_float(a_value, b_value, precision): diff.add((attr, i)) return diff def _diff_structure_list_rmsd(a, b, attr, tolerance): """ Compare an RMSD the structures in the lists. If any are greater than tolerance we add the name path_structure_rmsd to a set, which is returned. rmsd values are stored in attribute path_structure_rmsd. """ differing = set() # Adding these properties gives some error reporting a.path_structure_rmsd = [] b.path_structure_rmsd = [] for st1, st2 in zip(getattr(a, attr, []), getattr(b, attr, [])): atlist1 = list(range(1, st1.atom_total + 1)) atlist2 = list(range(1, st2.atom_total + 1)) rmsd = calculate_in_place_rmsd(st1, atlist1, st2, atlist2) a.path_structure_rmsd.append(0.0) b.path_structure_rmsd.append(rmsd) if rmsd > tolerance: differing.add("path_structure_rmsd") return differing def _diff_list_objects(a, b, attr): """ Diff between lists of generic objects. Assumes that the object defines __eq__, along with `_diff_precision` (if needed) for the comparison. For diffing a list of floats, use `_diff_list`. For diffing a single object, use `_diff_simple`. :return set(['attr']) if _OBJECTS differ globally; set of ('attr', list index) if any elements differ; empty set otherwise """ a_attr = getattr(a, attr, []) b_attr = getattr(b, attr, []) # look for global differences try: if len(a_attr) != len(b_attr): return set([attr]) # compare None except TypeError: if a_attr is None and b_attr is None: return set() elif a_attr is not None or b_attr is not None: return set([attr]) else: raise # drill down to compare element by element diffs diff = set() for i, (a_obj, b_obj) in enumerate(zip(a_attr, b_attr)): if a_obj != b_obj: diff.add((attr, i)) return diff def _diff(a, b, short_circuit=False, factor=1.0): """ A general function for comparing the attributes of two objects. Return a set of attribute names that fail the equality test. The interface the objects must provide is as follows: 1) A list of _Attribute instances stored under the attribute name '_attributes'. 2) A named attribute for any named precision in _Attributes that have float or list comparisons. Parameters short_circuit (bool) If True, return as soon as a difference is found. factor (float) A constant factor to use to loosen floating point comparisons by. """ differing = set() for attr_desc in a._attributes: attr = attr_desc.name if attr_desc.comparison == _SIMPLE: if _diff_simple(a, b, attr): differing.add(attr) elif attr_desc.comparison == _FLOAT_PRECISION: precision = getattr(a, attr_desc.precision, 0.0) * factor if _diff_float(a, b, attr, precision): differing.add(attr) elif attr_desc.comparison == _LIST_PRECISION: precision = getattr(a, attr_desc.precision, 0.0) * factor differing.update(_diff_list(a, b, attr, precision)) elif attr_desc.comparison == _PATH_PRECISION: precision = getattr(a, attr_desc.precision, 0.0) * factor differing.update(_diff_structure_list_rmsd(a, b, attr, precision)) elif attr_desc.comparison == _LIST_OBJECTS: differing.update(_diff_list_objects(a, b, attr)) if short_circuit and differing: return differing return differing