"""
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(range(4))
[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(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))
if short_circuit and differing:
return differing
return differing