Skip to content

Instantly share code, notes, and snippets.

@TallJimbo
Created March 5, 2015 18:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save TallJimbo/2c395544d972b1309ab8 to your computer and use it in GitHub Desktop.
Save TallJimbo/2c395544d972b1309ab8 to your computer and use it in GitHub Desktop.
LSST DM Fuzzy Comparison Proposal (RFC-28)
class Comparison(object):
"""Interface definition for comparison objects.
This class doesn't actually have to exist - it's just for exposition.
A comparison for a new type can be defined by implementing a class with
signatures like this one, then calling ComparisonRegistry.register().
"""
def __init__(self,
rtol=None, # relative threshold for floating point comparisons
atol=None, # absolute threshold for floating point comparisons
printDiff=False, # print the difference to this file-like object (e.g. stdout)
plotDiff=False, # plot any differences using matplotlib
displayDiff=False, # display any differences using ds9
requireEqualTypes=False, # require same types (including C++ types, if applicable)
**kwds
):
"""Constructor signatures for comparison objects.
Not all comparison constructors need to take all of these keyword arguments, and they may
have additional arguments not listed here. However,
- If they have any argument like one of the ones above, they should use the exact name
used above and give it the same semantics if at all possible.
- All comparison constructors should silently ignore unrecognized **kwd arguments (this
allows these to be passed recursively to other comparison constructors without worrying
about the details of exactly which options are supported).
All of the diff reporting methods should do absolutely nothing when objects are considered
equal.
"""
raise NotImplementedError()
def __call__(self, lhs, rhs):
"""Compare two objects, returning True if they should be considered equal according to the
criteria passed to the constructor, and report any differences as requested by the
constructor.
"""
raise NotImplementedError()
class ComparisonRegistry(object):
"""Class that manages a registry of fuzzy comparison objects.
Instances of this class should never exist; the only useful methods are class methods.
"""
_registry = {}
@classmethod
def register(cls, cmpClass, type_):
"""Register a comparison class for the given type.
"""
cls._registry[type_] = cmpClass
@classmethod
def get(cls, type_):
"""Return a comparison class for the given type.
"""
cmpClass = cls._registry.get(type_, None)
if cmpClass is not None:
return cmpClass(**kwds)
for b in type_.__bases__:
cmpClass = cls._registry.get(b, None)
if cmpClass is not None:
return cmpClass(**kwds)
return None
def compare(a, b, **kwds):
"""High-level interface to fuzzy comparisons: return True if a and b are equal according to criteria
defined by **kwds.
"""
cmpClass = ComparisonRegistry.get(type(a)) or ComparisonRegistry.get(type(b))
comparison = cmpClass(**kwds)
return comparison(a, b)
class TestCase(unittest.TestCase): # this class already exists in lsst.utils
"""Base class for LSST Python unit tests, providing useful extra comparisons.
This should be the interface to fuzzy comparisons typically used by test code.
"""
def assertClose(self, a, b, **kwds): # this method already exists; just adding a hook to the beginning
cmpClass = ComparisonRegistry.get(type(a)) or ComparisonRegistry.get(type(b))
if cmpClass is not None:
diff = cStringIO.StringIO()
kwds.setdefault("printDiff", diff)
comparison = cmpClass(**kwds)
return self.assertTrue(comparison(a, b), msg=diff.getvalue())
# rest of the existing method continues here, comparing scalars and NumPy arrays
pass
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment