Source code for unittest_utils

#!/usr/bin/env python
#
# Author: Ying Xiong.
# Created: Feb 15, 2014.

"""Utility functions for unit test."""

import numpy as np

[docs]def check_near(v1, v2, tol, raise_exception=True): """Check whether scalar/vector/matrix 'v1' and 'v2' are close to each other under tolerance tol, in the sense that:: (absolute) ||v1 - v2|| <= tol, **or** (relative) ||v1 - v2|| / max(||v1||, ||v2||, eps) <= tol, where ||.|| is the Frobenius norm.""" diff = v1 - v2 errAbs = np.linalg.norm(diff) errRel = errAbs / max(np.linalg.norm(v1), np.linalg.norm(v2), np.spacing(1)) if (errAbs <= tol) or (errRel <= tol): return True else: errMsg = "".join([ "||v1-v2|| = %f\n" % errAbs, "||v1-v2|| / max(||v1||, ||v2||, eps) = %f\n" % errRel, "Tolerance = %f\n" % tol]) if (raise_exception): raise Exception(errMsg) return False
[docs]def check_near_abs(v1, v2, tol, raise_exception=True): """Same as 'check_near' but only check in the "absolute" sense.""" diff = v1 - v2 errAbs = np.linalg.norm(diff) if (errAbs <= tol): return True else: errMsg = "".join([ "||v1-v2|| = %f\n" % errAbs, "Tolerance = %f\n" % tol]) if (raise_exception): raise Exception(join(errMsg)) return False
[docs]def check_near_rel(v1, v2, tol, raise_exception=True): """Same as 'check_near' but only check in the "relative" sense.""" diff = v1 - v2 errRel = np.linalg.norm(diff) / \ max(np.linalg.norm(v1), np.linalg.norm(v2), np.spacing(1)) if (errRel <= tol): return True else: errMsg = "".join([ "||v1-v2|| / max(||v1||, ||v2||, eps) = %f\n" % errRel, "Tolerance = %f\n" % tol]) if (raise_exception): raise Exception(errMsg) return False
[docs]def check_gradient(fcn, dfcn, N, x0=None, dx=None, delta=1e-4, m=0.01, M=10, raise_exception=True): """Numerically check whether `dfcn` calculates the gradient of `fcn`. More specifically, this function checks whether the following quantities are close to each other * `f(x) - f(x0)` * `(x-x0) \cdot f'(x0)` We consider them to be close enough if **either one** of the following is true 1. the absolute difference is smaller than `(m * ||x-x0||)`; 2. the relative difference is smaller than `(M * ||x-x0||)`. Parameters ---------- fcn: function handler Takes a single (vector or scalar) as input and outputs a scalar. dfcn: function handler Takes a single (vector or scalar) as input and outputs a vector output for gradient of 'fcn'. NOTE: Another option is to let `dfcn=None` (or something else that is not callable, e.g. []), and fcn return a 2-tuple for both fucntion value and its gradient. N: int The dimensionality of input to the fucntion, which is a Nx1 vector. x0: The initial input point evaluated by the function, with default {randn(N)}. dx, delta: The direction of evaluation point moves, such that:: x = x0 + delta*dx with 'dx' a unit Nx1 vector and 'delta' a scalar. m, M: float, optional The thresholds described above. """ # Set default. if (not x0): x0 = np.random.randn(N) if (not dx): dx = np.random.randn(N) dx /= np.linalg.norm(dx) # Evaluate the functions. x = x0 + delta*dx if hasattr(dfcn, "__call__"): y0 = fcn(x0) dy0 = dfcn(x0) y = fcn(x) else: y0, dy0 = fcn(x0) y, _ = fcn(x) # Do the check. v1 = y - y0 v2 = np.dot(x-x0, dy0) if (check_near_abs(v1, v2, m*delta, raise_exception=False) or check_near_rel(v1, v2, M*delta, raise_exception=False)): return True else: errMsg = "".join([ "f(x) - f(x0) = %e\n" % v1, "(x-x0) * f'(x0) = %e\n" % v2, "Absolute difference = %e" % abs(v1-v2), " > m * ||x-x0||", " = %s * %s = %s\n" % (str(m), str(delta), str(m*delta)), "Relative difference = %e > " % (abs(v1-v2)/max(abs(v1),abs(v2))), " > M*||x-x0|| ", " = %s * %s = %s\n" % (str(M), str(delta), str(M*delta))]) if (raise_exception): raise Exception(errMsg) return False
[docs]def check_jacobian(fcn, dfcn, N, x0=None, dx=None, delta=1e-4, m=0.01, M=10, raise_exception=True): """Numerically check whether `dfcn` calculates the Jacobian of `fcn`. More specifically, whether the following vectors are close to each other * `f(x) - f(x0)` * `J(x0) \cdot (x-x0)` We consider them to be close enough if **either one** of the following is true 1. "absolutely" close with tolerance `m*||x-x0||` (see `check_near_abs`); 2. "relatively" close with tolerance `M*||x-x0||` (see `check_near_rel`). Parameters ----------- fcn: function handler Takes a single (vector or scalar) as input and outputs a vector. dfcn: function handler Takes a single (vector or scalar) as input and outputs a matrix for Jacobian of `fcn`. NOTE: Another option is to let dfcn=None (or something else that is not callable, e.g. `[]`), and `fcn` return a 2-tuple for both fucntion value and its Jacobian. The rest is the same as `check_gradient`. """ # Set default. if (not x0): x0 = np.random.randn(N) if (not dx): dx = np.random.randn(N) dx /= np.linalg.norm(dx) # Evaluate the functions. x = x0 + delta*dx if hasattr(dfcn, "__call__"): y0 = fcn(x0) J0 = dfcn(x0) y = fcn(x) else: y0, J0 = fcn(x0) y, _ = fcn(x) # Do the check. v1 = y - y0 v2 = np.dot(J0, x-x0) if (check_near_abs(v1, v2, m*delta, raise_exception=False) or check_near_rel(v1, v2, M*delta, raise_exception=False)): return True else: absErr = np.linalg.norm(v1-v2) relErr = absErr / max(np.linalg.norm(v1),np.linalg.norm(v2)) errMsg = "".join([ "Absolute difference = %e" % absErr, " > m * ||x-x0||", " = %s * %s = %s\n" % (str(m), str(delta), str(m*delta)), "Relative difference = %e > " % relErr, " > M*||x-x0|| ", " = %s * %s = %s\n" % (str(M), str(delta), str(M*delta))]) if (raise_exception): raise Exception(errMsg) return False