{"action":"created","comment":{"url":"https://api.github.com/repos/simpeg/simpeg/pulls/comments/100237174","pull_request_review_id":20936475,"id":100237174,"diff_hunk":"@@ -1,450 +1,656 @@\n-from SimPEG import Mesh, Utils, np\n+from __future__ import absolute_import\n+from __future__ import division\n+from __future__ import print_function\n+from __future__ import unicode_literals\n \n+import numpy as np\n+import scipy.sparse as sp\n+from SimPEG import Utils, Props\n \n-class NonLinearMap(object):\n-    \"\"\"\n-    SimPEG NonLinearMap\n \n-    \"\"\"\n+def _get_projections(u):\n+    \"\"\"Get the projections for each domain in the pressure head (u)\"\"\"\n+    nP = len(u)\n+    bools = u >= 0\n+    ind_p = np.where(bools)[0]\n+    ind_n = np.where(~bools)[0]\n+    P_p = sp.csr_matrix((np.ones(len(ind_p)), (ind_p, ind_p)), shape=(nP, nP))\n+    P_n = sp.csr_matrix((np.ones(len(ind_n)), (ind_n, ind_n)), shape=(nP, nP))\n+    return P_p, P_n\n \n-    counter = None   #: A SimPEG.Utils.Counter object\n-    mesh = None      #: A SimPEG Mesh\n \n-    def __init__(self, mesh):\n-        self.mesh = mesh\n+def _partition_args(mesh, Hcond, Theta, hcond_args, theta_args, **kwargs):\n \n-    def _transform(self, u, m):\n-        \"\"\"\n-            :param numpy.array u: fields\n-            :param numpy.array m: model\n-            :rtype: numpy.array\n-            :return: transformed model\n+    hcond_params = {k: kwargs[k] for k in kwargs if k in hcond_args}\n+    theta_params = {k: kwargs[k] for k in kwargs if k in theta_args}\n \n-            The *transform* changes the model into the physical property.\n+    other_params = {\n+        k: kwargs[k] for k in kwargs if k not in hcond_args + theta_args\n+    }\n \n-        \"\"\"\n-        return m\n+    if len(other_params) > 0:\n+        raise Exception('Unknown parameters: {}'.format(other_params))\n \n-    def derivU(self, u, m):\n-        \"\"\"\n-            :param numpy.array u: fields\n-            :param numpy.array m: model\n-            :rtype: scipy.sparse.csr_matrix\n-            :return: derivative of transformed model\n+    hcond = Hcond(mesh, **hcond_params)\n+    theta = Theta(mesh, **theta_params)\n \n-            The *transform* changes the model into the physical property.\n-            The *transformDerivU* provides the derivative of the *transform* with respect to the fields.\n-        \"\"\"\n-        raise NotImplementedError('The transformDerivU is not implemented.')\n+    return hcond, theta\n \n-    def derivM(self, u, m):\n-        \"\"\"\n-            :param numpy.array u: fields\n-            :param numpy.array m: model\n-            :rtype: scipy.sparse.csr_matrix\n-            :return: derivative of transformed model\n \n-            The *transform* changes the model into the physical property.\n-            The *transformDerivU* provides the derivative of the *transform* with respect to the model.\n-        \"\"\"\n-        raise NotImplementedError('The transformDerivM is not implemented.')\n+class NonLinearModel(Props.HasModel):\n+    \"\"\"A non linear model that has dependence on the fields and a model\"\"\"\n+\n+    counter = None   #: A SimPEG.Utils.Counter object\n+    mesh = None      #: A SimPEG Mesh\n+\n+    def __init__(self, mesh, **kwargs):\n+        self.mesh = mesh\n+        super(NonLinearModel, self).__init__(**kwargs)\n \n     @property\n     def nP(self):\n         \"\"\"Number of parameters in the model.\"\"\"\n         return self.mesh.nC\n \n-    def example(self):\n-        raise NotImplementedError('The example is not implemented.')\n \n-    def test(self, m=None):\n-        raise NotImplementedError('The test is not implemented.')\n+class BaseWaterRetention(NonLinearModel):\n \n+    def plot(self, ax=None):\n+        import matplotlib.pyplot as plt\n \n-class RichardsMap(object):\n-    \"\"\"docstring for RichardsMap\"\"\"\n+        if ax is None:\n+            plt.figure()\n+            ax = plt.subplot(111)\n \n-    mesh = None  #: SimPEG mesh\n+        self.validate()\n \n-    @property\n-    def thetaModel(self):\n-        \"\"\"Model for moisture content\"\"\"\n-        return self._thetaModel\n+        h = -np.logspace(-2, 3, 1000)\n+        ax.semilogx(-h, self(h))\n+        ax.set_title('Water retention curve')\n+        ax.set_xlabel('Soil water potential, $- \\psi$')\n+        ax.set_ylabel('Water content, $\\\\theta$')\n \n-    @property\n-    def kModel(self):\n-        \"\"\"Model for hydraulic conductivity\"\"\"\n-        return self._kModel\n \n-    def __init__(self, mesh, thetaModel, kModel):\n-        self.mesh = mesh\n-        assert isinstance(thetaModel, NonLinearMap)\n-        assert isinstance(kModel, NonLinearMap)\n+class BaseHydraulicConductivity(NonLinearModel):\n \n-        self._thetaModel = thetaModel\n-        self._kModel = kModel\n+    def plot(self, ax=None):\n+        import matplotlib.pyplot as plt\n \n-    def theta(self, u, m):\n-        return self.thetaModel.transform(u, m)\n+        if ax is None:\n+            plt.figure()\n+            ax = plt.subplot(111)\n \n-    def thetaDerivM(self, u, m):\n-        return self.thetaModel.transformDerivM(u, m)\n+        self.validate()\n \n-    def thetaDerivU(self, u, m):\n-        return self.thetaModel.transformDerivU(u, m)\n+        h = -np.logspace(-2, 3, 1000)\n+        ax.loglog(-h, self(h))\n+        ax.set_title('Hydraulic conductivity function')\n+        ax.set_xlabel('Soil water potential, $- \\psi$')\n+        ax.set_ylabel('Hydraulic conductivity, $K$')\n \n-    def k(self, u, m):\n-        return self.kModel.transform(u, m)\n \n-    def kDerivM(self, u, m):\n-        return self.kModel.transformDerivM(u, m)\n+class Haverkamp_theta(BaseWaterRetention):\n \n-    def kDerivU(self, u, m):\n-        return self.kModel.transformDerivU(u, m)\n+    theta_r, theta_rMap, theta_rDeriv = Props.Invertible(\n+        \"residual water content [L3L-3]\",\n+        default=0.075\n+    )\n \n-    def plot(self, m):\n-        import matplotlib.pyplot as plt\n+    theta_s, theta_sMap, theta_sDeriv = Props.Invertible(\n+        \"saturated water content [L3L-3]\",\n+        default=0.287\n+    )\n \n-        m = m[0]\n-        h = np.linspace(-100, 20, 1000)\n-        ax = plt.subplot(121)\n-        ax.plot(self.theta(h, m), h)\n-        ax = plt.subplot(122)\n-        ax.semilogx(self.k(h, m), h)\n-\n-    def _assertMatchesPair(self, pair):\n-        assert isinstance(self, pair), (\n-            \"Mapping object must be an instance of a {0!s} class.\".format(\n-                pair.__name__\n-            )\n-        )\n+    alpha, alphaMap, alphaDeriv = Props.Invertible(\n+        \"\",\n+        default=1.611e+06\n+    )\n \n+    beta, betaMap, betaDeriv = Props.Invertible(\n+        \"\",\n+        default=3.96\n+    )\n \n-def _ModelProperty(name, models, doc=None, default=None):\n+    def _get_params(self):\n+        return self.theta_r, self.theta_s, self.alpha, self.beta\n \n-    def fget(self):\n-        model = models[0]\n-        if getattr(self, model, None) is not None:\n-            MOD = getattr(self, model)\n-            return getattr(MOD, name, default)\n-        return default\n+    def __call__(self, u):\n+        theta_r, theta_s, alpha, beta = self._get_params()\n \n-    def fset(self, value):\n-        for model in models:\n-            if getattr(self, model, None) is not None:\n-                MOD = getattr(self, model)\n-                setattr(MOD, name, value)\n+        f = (\n+            alpha *\n+            (theta_s - theta_r) /\n+            (alpha + abs(u)**beta) +\n+            theta_r\n+        )\n \n-    return property(fget, fset=fset, doc=doc)\n+        if np.isscalar(theta_s):\n+            f[u >= 0] = theta_s\n+        else:\n+            f[u >= 0] = theta_s[u >= 0]\n+        return f\n \n+    def derivM(self, u):\n+        \"\"\"derivative with respect to m\n \n-class HaverkampParams(object):\n-    \"\"\"Holds some default parameterizations for the Haverkamp model.\"\"\"\n-    def __init__(self):\n-        pass\n+        .. code::\n \n-    @property\n-    def celia1990(self):\n-        \"\"\"\n-            Parameters used in:\n+            import sympy as sy\n \n-                Celia, Michael A., Efthimios T. Bouloutas, and Rebecca L. Zarba.\n-                \"A general mass-conservative numerical solution for the unsaturated flow equation.\"\n-                Water Resources Research 26.7 (1990): 1483-1496.\n+            alpha, u, beta, theta_r, theta_s = sy.symbols(\n+                'alpha u beta theta_r theta_s', real=True\n+            )\n \n+            f_n = (\n+                alpha *\n+                (theta_s - theta_r) /\n+                (alpha + abs(u)**beta) +\n+                theta_r\n+            )\n         \"\"\"\n-        return {\n-            'alpha': 1.611e+06,\n-            'beta': 3.96,\n-            'theta_r': 0.075,\n-            'theta_s': 0.287,\n-            'Ks': 9.44e-03,\n-            'A': 1.175e+06,\n-            'gamma': 4.74\n-        }\n-\n-\n-class _haverkamp_theta(NonLinearMap):\n-\n-    theta_s = 0.430\n-    theta_r = 0.078\n-    alpha = 0.036\n-    beta = 3.960\n-\n-    def __init__(self, mesh, **kwargs):\n-        NonLinearMap.__init__(self, mesh)\n-        Utils.setKwargs(self, **kwargs)\n-\n-    def setModel(self, m):\n-        self._currentModel = m\n-\n-    def transform(self, u, m):\n-        self.setModel(m)\n-        f = (\n-            self.alpha *\n-            (self.theta_s - self.theta_r) /\n-            (self.alpha + abs(u)**self.beta) +\n-            self.theta_r\n+        return (\n+            self._derivTheta_r(u) +\n+            self._derivTheta_s(u) +\n+            self._derivAlpha(u) +\n+            self._derivBeta(u)\n         )\n \n-        if Utils.isScalar(self.theta_s):\n-            f[u >= 0] = self.theta_s\n-        else:\n-            f[u >= 0] = self.theta_s[u >= 0]\n-        return f\n+    def _derivTheta_r(self, u):\n+        if self.theta_rMap is None:\n+            return Utils.Zero()\n+        theta_r, theta_s, alpha, beta = self._get_params()\n+        ddm = -alpha/(alpha + abs(u)**beta) + 1\n+        ddm[u >= 0] = 0\n+        dT = Utils.sdiag(ddm) * self.theta_rDeriv\n+        return dT\n+\n+    def _derivTheta_s(self, u):\n+        if self.theta_sMap is None:\n+            return Utils.Zero()\n+        theta_r, theta_s, alpha, beta = self._get_params()\n+        P_p, P_n = _get_projections(u)  # Compute the positive/negative domains\n+        dT_p = P_p * self.theta_sDeriv\n+        dT_n = P_n * Utils.sdiag(\n+            alpha/(alpha + abs(u)**beta)\n+        ) * self.theta_sDeriv\n+        return dT_p + dT_n\n+\n+    def _derivAlpha(self, u):\n+        if self.alphaMap is None:\n+            return Utils.Zero()\n+        theta_r, theta_s, alpha, beta = self._get_params()\n+        ddm = -alpha*(-theta_r + theta_s)/(alpha + abs(u)**beta)**2 + (-theta_r + theta_s)/(alpha + abs(u)**beta)\n+        ddm[u >= 0] = 0\n+        dA = Utils.sdiag(ddm) * self.alphaDeriv\n+        return dA\n+\n+    def _derivBeta(self, u):\n+        if self.betaMap is None:\n+            return Utils.Zero()\n+        theta_r, theta_s, alpha, beta = self._get_params()\n+        ddm = -alpha*(-theta_r + theta_s)*np.log(abs(u))*abs(u)**beta/(alpha + abs(u)**beta)**2\n+        ddm[u >= 0] = 0\n+        dN = Utils.sdiag(ddm) * self.betaDeriv\n+        return dN\n+\n+    def derivU(self, u):\n+        theta_r, theta_s, alpha, beta = self._get_params()\n \n-    def transformDerivM(self, u, m):\n-        self.setModel(m)\n-\n-    def transformDerivU(self, u, m):\n-        self.setModel(m)\n         g = (\n-            self.alpha * (\n-                (self.theta_s - self.theta_r) /\n-                (self.alpha + abs(u)**self.beta)**2\n+            alpha * (\n+                (theta_s - theta_r) /\n+                (alpha + abs(u)**beta)**2\n             ) *\n-            (-self.beta * abs(u)**(self.beta-1) * np.sign(u))\n+            (-beta * abs(u)**(beta-1) * np.sign(u))\n         )\n         g[u >= 0] = 0\n         g = Utils.sdiag(g)\n         return g\n \n \n-class _haverkamp_k(NonLinearMap):\n+class Haverkamp_k(BaseHydraulicConductivity):\n \n-    A = 1.175e+06\n-    gamma = 4.74\n-    Ks = np.log(24.96)\n+    Ks, KsMap, KsDeriv = Props.Invertible(\n+        \"Saturated hydraulic conductivity\",\n+        default=9.44e-03\n+    )\n \n-    def __init__(self, mesh, **kwargs):\n-        NonLinearMap.__init__(self, mesh)\n-        Utils.setKwargs(self, **kwargs)\n-\n-    def setModel(self, m):\n-        self._currentModel = m\n-        # TODO: Fix me!\n-        self.Ks = m\n-\n-    def transform(self, u, m):\n-        self.setModel(m)\n-        f = np.exp(self.Ks)*self.A/(self.A+abs(u)**self.gamma)\n-        if Utils.isScalar(self.Ks):\n-            f[u >= 0] = np.exp(self.Ks)\n-        else:\n-            f[u >= 0] = np.exp(self.Ks[u >= 0])\n-        return f\n+    A, AMap, ADeriv = Props.Invertible(\n+        \"fitting parameter\",\n+        default=1.175e+06\n+    )\n+\n+    gamma, gammaMap, gammaDeriv = Props.Invertible(\n+        \"fitting parameter\",\n+        default=4.74\n+    )\n \n-    def transformDerivM(self, u, m):\n-        self.setModel(m)\n-        # A\n-        # dA = np.exp(self.Ks)/(self.A+abs(u)**self.gamma) - np.exp(self.Ks)*self.A/(self.A+abs(u)**self.gamma)**2\n-        # gamma\n-        # dgamma = -(self.A*np.exp(self.Ks)*np.log(abs(u))*abs(u)**self.gamma)/(self.A + abs(u)**self.gamma)**2\n+    def _get_params(self):\n+        return self.Ks, self.A, self.gamma\n \n-        # This assumes that the the model is Ks\n-        return Utils.sdiag(self.transform(u, m))\n+    def __call__(self, u):\n+        Ks, A, gamma = self._get_params()\n+        P_p, P_n = _get_projections(u)  # Compute the positive/negative domains\n+        f_p = P_p * np.ones(len(u)) * Ks  # ensures scalar Ks works\n+        f_n = P_n * Ks * A / (A + abs(u)**gamma)\n+        return f_p + f_n\n \n-    def transformDerivU(self, u, m):\n-        self.setModel(m)\n-        g = -(np.exp(self.Ks)*self.A*self.gamma*abs(u)**(self.gamma-1)*np.sign(u))/((self.A+abs(u)**self.gamma)**2)\n+    def derivU(self, u):\n+        Ks, A, gamma = self._get_params()\n+        g = -(Ks*A*gamma*abs(u)**(gamma-1)*np.sign(u))/((A+abs(u)**gamma)**2)\n         g[u >= 0] = 0\n-        g = Utils.sdiag(g)\n-        return g\n+        return Utils.sdiag(g)\n+\n+    def derivM(self, u):\n+        return self._derivKs(u) + self._derivA(u) + self._derivGamma(u)\n+\n+    def _derivKs(self, u):\n+        if self.KsMap is None:\n+            return Utils.Zero()\n+\n+        Ks, A, gamma = self._get_params()\n+        P_p, P_n = _get_projections(u)  # Compute the positive/negative domains\n+\n+        dKs_dm_p = P_p * self.KsDeriv\n+        dKs_dm_n = P_n * Utils.sdiag(A/(A + abs(u)**gamma)) * self.KsDeriv\n+        return dKs_dm_p + dKs_dm_n\n+\n+    def _derivA(self, u):\n+        if self.AMap is None:\n+            return Utils.Zero()\n+        Ks, A, gamma = self._get_params()\n+        ddm = Ks / (A + abs(u)**gamma) - Ks*A/(A + abs(u)**gamma)**2\n+        ddm[u >= 0] = 0\n+        dA_dm = Utils.sdiag(ddm) * self.ADeriv\n+        return dA_dm\n+\n+    def _derivGamma(self, u):\n+        if self.gammaMap is None:\n+            return Utils.Zero()\n+        Ks, A, gamma = self._get_params()\n+        ddm = -(A*Ks*np.log(abs(u))*abs(u)**gamma)/(A + abs(u)**gamma)**2\n+        ddm[u >= 0] = 0\n+        dGamma_dm = Utils.sdiag(ddm) * self.gammaDeriv\n+        return dGamma_dm\n+\n+\n+def haverkamp(mesh, **kwargs):\n+    return _partition_args(\n+        mesh,\n+        Haverkamp_k,\n+        Haverkamp_theta,\n+        ['Ks', 'A', 'gamma'],\n+        ['alpha', 'beta', 'theta_r', 'theta_s'],\n+        **kwargs\n+    )\n \n \n-class Haverkamp(RichardsMap):\n-    \"\"\"Haverkamp Model\"\"\"\n+class HaverkampParams(object):\n+    \"\"\"Holds some default parameterizations for the Haverkamp model.\"\"\"\n+\n+    @property\n+    def celia1990(self):\n+        \"\"\"Parameters used in:\n \n-    alpha = _ModelProperty('alpha', ['thetaModel'], default=1.6110e+06)\n-    beta = _ModelProperty('beta', ['thetaModel'], default=3.96)\n-    theta_r = _ModelProperty('theta_r', ['thetaModel'], default=0.075)\n-    theta_s = _ModelProperty('theta_s', ['thetaModel'], default=0.287)\n+            Celia, Michael A., Efthimios T. Bouloutas, and Rebecca L. Zarba.\n+            \"A general mass-conservative numerical solution for the unsaturated\n+            flow equation.\" Water Resources Research 26.7 (1990): 1483-1496.\n+        \"\"\"\n+        return {\n+            'alpha': 1.611e+06,\n+            'beta': 3.96,\n+            'theta_r': 0.075,\n+            'theta_s': 0.287,\n+            'Ks': 9.44e-03,\n+            'A': 1.175e+06,\n+            'gamma': 4.74\n+        }\n \n-    Ks = _ModelProperty('Ks', ['kModel'], default=np.log(24.96))\n-    A = _ModelProperty('A', ['kModel'], default=1.1750e+06)\n-    gamma = _ModelProperty('gamma', ['kModel'], default=4.74)\n \n-    def __init__(self, mesh, **kwargs):\n-        RichardsMap.__init__(\n-            self,\n-            mesh,\n-            _haverkamp_theta(mesh),\n-            _haverkamp_k(mesh)\n-        )\n-        Utils.setKwargs(self, **kwargs)\n+class Vangenuchten_theta(BaseWaterRetention):\n \n+    theta_r, theta_rMap, theta_rDeriv = Props.Invertible(\n+        \"residual water content [L3L-3]\",\n+        default=0.078\n+    )\n \n-class _vangenuchten_theta(NonLinearMap):\n+    theta_s, theta_sMap, theta_sDeriv = Props.Invertible(\n+        \"saturated water content [L3L-3]\",\n+        default=0.430\n+    )\n \n-    theta_s = 0.430\n-    theta_r = 0.078\n-    alpha = 0.036\n-    n = 1.560\n+    n, nMap, nDeriv = Props.Invertible(\n+        \"measure of the pore-size distribution, >1\",\n+        default=1.56\n+    )\n \n-    def __init__(self, mesh, **kwargs):\n-        NonLinearMap.__init__(self, mesh)\n-        Utils.setKwargs(self, **kwargs)\n+    alpha, alphaMap, alphaDeriv = Props.Invertible(\n+        \"related to the inverse of the air entry suction [L-1], >0\",\n+        default=0.036\n+    )\n \n-    def setModel(self, m):\n-        self._currentModel = m\n+    def _get_params(self):\n+        return self.theta_r, self.theta_s, self.alpha, self.n\n \n-    def transform(self, u, m):\n-        self.setModel(m)\n-        m = 1 - 1.0 / self.n\n+    def __call__(self, u):\n+        theta_r, theta_s, alpha, n = self._get_params()\n         f = (\n             (\n-                self.theta_s - self.theta_r\n+                theta_s - theta_r\n             ) /\n             (\n-                (1 + abs(self.alpha * u)**self.n)**m\n+                (1.0 + abs(alpha * u)**n) ** (1.0 - 1.0 / n)\n             ) +\n-            self.theta_r\n+            theta_r\n         )\n-        if Utils.isScalar(self.theta_s):\n-            f[u >= 0] = self.theta_s\n+        if np.isscalar(theta_s):\n+            f[u >= 0] = theta_s\n         else:\n-            f[u >= 0] = self.theta_s[u >= 0]\n+            f[u >= 0] = theta_s[u >= 0]\n \n         return f\n \n-    def transformDerivM(self, u, m):\n-        self.setModel(m)\n+    def derivM(self, u):\n+        \"\"\"derivative with respect to m\n \n-    def transformDerivU(self, u, m):\n-        g = -self.alpha*self.n*abs(self.alpha*u)**(self.n - 1)*np.sign(self.alpha*u)*(1./self.n - 1)*(self.theta_r - self.theta_s)*(abs(self.alpha*u)**self.n + 1)**(1./self.n - 2)\n+        .. code::\n+\n+            import sympy as sy\n+\n+            alpha, u, n, I, Ks, theta_r, theta_s = sy.symbols(\n+                'alpha u n I Ks theta_r theta_s', real=True\n+            )\n+\n+            m = 1.0 - 1.0/n\n+            theta_e = 1.0 / ((1.0 + sy.functions.Abs(alpha * u) ** n) ** m)\n+\n+            f_n = (\n+                (\n+                    theta_s - theta_r\n+                ) /\n+                (\n+                    (1.0 + abs(alpha * u)**n) ** (1.0 - 1.0 / n)\n+                ) +\n+                theta_r\n+            )\n+        \"\"\"\n+        return (\n+            self._derivTheta_r(u) +\n+            self._derivTheta_s(u) +\n+            self._derivN(u) +\n+            self._derivAlpha(u)\n+        )\n+\n+    def _derivTheta_r(self, u):\n+        if self.theta_rMap is None:\n+            return Utils.Zero()\n+        theta_r, theta_s, alpha, n = self._get_params()\n+        ddm = -(abs(alpha*u)**n + 1.0)**(-1.0 + 1.0/n) + 1\n+        ddm[u >= 0] = 0\n+        dT = Utils.sdiag(ddm) * self.theta_rDeriv\n+        return dT\n+\n+    def _derivTheta_s(self, u):\n+        if self.theta_sMap is None:\n+            return Utils.Zero()\n+        theta_r, theta_s, alpha, n = self._get_params()\n+        P_p, P_n = _get_projections(u)  # Compute the positive/negative domains\n+        dT_p = P_p * self.theta_sDeriv\n+        dT_n = P_n * Utils.sdiag(\n+            (abs(alpha*u)**n + 1.0)**(-1.0 + 1.0/n)\n+        ) * self.theta_sDeriv\n+        return dT_p + dT_n\n+\n+    def _derivN(self, u):\n+        if self.nMap is None:\n+            return Utils.Zero()\n+        theta_r, theta_s, alpha, n = self._get_params()\n+        ddm = (-theta_r + theta_s)*((-1.0 + 1.0/n)*np.log(abs(alpha*u))*abs(alpha*u)**n/(abs(alpha*u)**n + 1.0) - 1.0*np.log(abs(alpha*u)**n + 1.0)/n**2)*(abs(alpha*u)**n + 1.0)**(-1.0 + 1.0/n)\n+        ddm[u >= 0] = 0\n+        dN = Utils.sdiag(ddm) * self.nDeriv\n+        return dN\n+\n+    def _derivAlpha(self, u):\n+        if self.alphaMap is None:\n+            return Utils.Zero()\n+        theta_r, theta_s, alpha, n = self._get_params()\n+        ddm = n*u*(-1.0 + 1.0/n)*(-theta_r + theta_s)*(abs(alpha*u)**n + 1.0)**(-1.0 + 1.0/n)*abs(alpha*u)**n*np.sign(alpha*u)/((abs(alpha*u)**n + 1.0)*abs(alpha*u))\n+        ddm[u >= 0] = 0\n+        dA = Utils.sdiag(ddm) * self.alphaDeriv\n+        return dA\n+\n+    def derivU(self, u):\n+        theta_r, theta_s, alpha, n = self._get_params()\n+        g = -alpha*n*abs(alpha*u)**(n - 1)*np.sign(alpha*u)*(1./n - 1)*(theta_r - theta_s)*(abs(alpha*u)**n + 1)**(1./n - 2)\n         g[u >= 0] = 0\n         g = Utils.sdiag(g)\n         return g\n \n \n-class _vangenuchten_k(NonLinearMap):\n+class Vangenuchten_k(BaseHydraulicConductivity):\n \n-    I = 0.500\n-    alpha = 0.036\n-    n = 1.560\n-    Ks = np.log(24.96)\n+    Ks, KsMap, KsDeriv = Props.Invertible(\n+        \"Saturated hydraulic conductivity\",\n+        default=24.96\n+    )\n \n-    def __init__(self, mesh, **kwargs):\n-        NonLinearMap.__init__(self, mesh)\n-        Utils.setKwargs(self, **kwargs)\n+    I, IMap, IDeriv = Props.Invertible(\n+        \"\",\n+        default=0.5\n+    )\n \n-    def setModel(self, m):\n-        self._currentModel = m\n-        # TODO: Fix me!\n-        self.Ks = m\n+    n, nMap, nDeriv = Props.Invertible(\n+        \"measure of the pore-size distribution, >1\",\n+        default=1.56\n+    )\n \n-    def transform(self, u, m):\n-        self.setModel(m)\n+    alpha, alphaMap, alphaDeriv = Props.Invertible(\n+        \"related to the inverse of the air entry suction [L-1], >0\",\n+        default=0.036\n+    )\n \n+    def _get_params(self):\n         alpha = self.alpha\n         I = self.I\n         n = self.n\n         Ks = self.Ks\n         m = 1.0 - 1.0/n\n+        return Ks, alpha, I, n, m\n \n-        theta_e = 1.0/((1.0+abs(alpha*u)**n)**m)\n-        f = np.exp(Ks)*theta_e**I * (\n-            (1.0 - (1.0 - theta_e**(1.0/m))**m)**2\n-        )\n-        if Utils.isScalar(self.Ks):\n-            f[u >= 0] = np.exp(self.Ks)\n-        else:\n-            f[u >= 0] = np.exp(self.Ks[u >= 0])\n-        return f\n+    def __call__(self, u):\n+        Ks, alpha, I, n, m = self._get_params()\n \n-    def transformDerivM(self, u, m):\n-        self.setModel(m)\n-        #alpha\n-        # dA = I*u*n*np.exp(Ks)*abs(alpha*u)**(n - 1)*np.sign(alpha*u)*(1.0/n - 1)*((abs(alpha*u)**n + 1)**(1.0/n - 1))**(I - 1)*((1 - 1.0/((abs(alpha*u)**n + 1)**(1.0/n - 1))**(1.0/(1.0/n - 1)))**(1 - 1.0/n) - 1)**2*(abs(alpha*u)**n + 1)**(1.0/n - 2) - (2*u*n*np.exp(Ks)*abs(alpha*u)**(n - 1)*np.sign(alpha*u)*(1.0/n - 1)*((abs(alpha*u)**n + 1)**(1.0/n - 1))**I*((1 - 1.0/((abs(alpha*u)**n + 1)**(1.0/n - 1))**(1.0/(1.0/n - 1)))**(1 - 1.0/n) - 1)*(abs(alpha*u)**n + 1)**(1.0/n - 2))/(((abs(alpha*u)**n + 1)**(1.0/n - 1))**(1.0/(1.0/n - 1) + 1)*(1 - 1.0/((abs(alpha*u)**n + 1)**(1.0/n - 1))**(1.0/(1.0/n - 1)))**(1.0/n));\n-        #n\n-        # dn = 2*np.exp(Ks)*((np.log(1 - 1.0/((abs(alpha*u)**n + 1)**(1.0/n - 1))**(1.0/(1.0/n - 1)))*(1 - 1.0/((abs(alpha*u)**n + 1)**(1.0/n - 1))**(1.0/(1.0/n - 1)))**(1 - 1.0/n))/n**2 + ((1.0/n - 1)*(((np.log(abs(alpha*u)**n + 1)*(abs(alpha*u)**n + 1)**(1.0/n - 1))/n**2 - abs(alpha*u)**n*np.log(abs(alpha*u))*(1.0/n - 1)*(abs(alpha*u)**n + 1)**(1.0/n - 2))/((1.0/n - 1)*((abs(alpha*u)**n + 1)**(1.0/n - 1))**(1.0/(1.0/n - 1) + 1)) - np.log((abs(alpha*u)**n + 1)**(1.0/n - 1))/(n**2*(1.0/n - 1)**2*((abs(alpha*u)**n + 1)**(1.0/n - 1))**(1.0/(1.0/n - 1)))))/(1 - 1.0/((abs(alpha*u)**n + 1)**(1.0/n - 1))**(1.0/(1.0/n - 1)))**(1.0/n))*((abs(alpha*u)**n + 1)**(1.0/n - 1))**I*((1 - 1.0/((abs(alpha*u)**n + 1)**(1.0/n - 1))**(1.0/(1.0/n - 1)))**(1 - 1.0/n) - 1) - I*np.exp(Ks)*((np.log(abs(alpha*u)**n + 1)*(abs(alpha*u)**n + 1)**(1.0/n - 1))/n**2 - abs(alpha*u)**n*np.log(abs(alpha*u))*(1.0/n - 1)*(abs(alpha*u)**n + 1)**(1.0/n - 2))*((abs(alpha*u)**n + 1)**(1.0/n - 1))**(I - 1)*((1 - 1.0/((abs(alpha*u)**n + 1)**(1.0/n - 1))**(1.0/(1.0/n - 1)))**(1 - 1.0/n) - 1)**2;\n-        #I\n-        # dI = np.exp(Ks)*np.log((abs(alpha*u)**n + 1)**(1.0/n - 1))*((abs(alpha*u)**n + 1)**(1.0/n - 1))**I*((1 - 1.0/((abs(alpha*u)**n + 1)**(1.0/n - 1))**(1.0/(1.0/n - 1)))**(1 - 1.0/n) - 1)**2;\n-        return Utils.sdiag(self.transform(u, m)) # This assumes that the the model is Ks\n-\n-    def transformDerivU(self, u, m):\n-        self.setModel(m)\n-        alpha = self.alpha\n-        I = self.I\n-        n = self.n\n-        Ks = self.Ks\n-        m = 1.0 - 1.0/n\n+        P_p, P_n = _get_projections(u)  # Compute the positive/negative domains\n+        theta_e = 1.0 / ((1.0 + abs(alpha * u) ** n) ** m)\n+        f_p = P_p * np.ones(len(u)) * Ks  # ensures scalar Ks works\n+        f_n = P_n * Ks * theta_e ** I * (\n+            (1.0 - (1.0 - theta_e ** (1.0 / m)) ** m) ** 2\n+        )\n+        return f_p + f_n\n \n-        g = I*alpha*n*np.exp(Ks)*abs(alpha*u)**(n - 1.0)*np.sign(alpha*u)*(1.0/n - 1.0)*((abs(alpha*u)**n + 1)**(1.0/n - 1))**(I - 1)*((1 - 1.0/((abs(alpha*u)**n + 1)**(1.0/n - 1))**(1.0/(1.0/n - 1)))**(1 - 1.0/n) - 1)**2*(abs(alpha*u)**n + 1)**(1.0/n - 2) - (2*alpha*n*np.exp(Ks)*abs(alpha*u)**(n - 1)*np.sign(alpha*u)*(1.0/n - 1)*((abs(alpha*u)**n + 1)**(1.0/n - 1))**I*((1 - 1.0/((abs(alpha*u)**n + 1)**(1.0/n - 1))**(1.0/(1.0/n - 1)))**(1 - 1.0/n) - 1)*(abs(alpha*u)**n + 1)**(1.0/n - 2))/(((abs(alpha*u)**n + 1)**(1.0/n - 1))**(1.0/(1.0/n - 1) + 1)*(1 - 1.0/((abs(alpha*u)**n + 1)**(1.0/n - 1))**(1.0/(1.0/n - 1)))**(1.0/n))\n-        g[u >= 0] = 0\n-        g = Utils.sdiag(g)\n-        return g\n+    def derivM(self, u):\n+        \"\"\"derivative with respect to m\n \n+        .. code::\n \n-class VanGenuchten(RichardsMap):\n-    \"\"\"vanGenuchten Model\"\"\"\n+            import sympy as sy\n \n-    theta_r = _ModelProperty('theta_r', ['thetaModel'], default=0.075)\n-    theta_s = _ModelProperty('theta_s', ['thetaModel'], default=0.287)\n+            alpha, u, n, I, Ks, theta_r, theta_s = sy.symbols(\n+                'alpha u n I Ks theta_r theta_s', real=True\n+            )\n \n-    alpha = _ModelProperty('alpha',   ['thetaModel', 'kModel'], default=0.036)\n-    n = _ModelProperty('n', ['thetaModel', 'kModel'], default=1.560)\n+            m = 1.0 - 1.0/n\n+            theta_e = 1.0 / ((1.0 + sy.functions.Abs(alpha * u) ** n) ** m)\n \n-    Ks = _ModelProperty('Ks', ['kModel'], default=np.log(24.96))\n-    I = _ModelProperty('I', ['kModel'], default=0.500)\n+            f_n = Ks * theta_e ** I * (\n+                (1.0 - (1.0 - theta_e ** (1.0 / m)) ** m) ** 2\n+            )\n \n-    def __init__(self, mesh, **kwargs):\n-        RichardsMap.__init__(\n-            self,\n-            mesh,\n-            _vangenuchten_theta(mesh),\n-            _vangenuchten_k(mesh)\n+            f_n = (\n+                (\n+                    theta_s - theta_r\n+                ) /\n+                (\n+                    (1.0 + abs(alpha * u)**n) ** (1.0 - 1.0 / n)\n+                ) +\n+                theta_r\n+            )\n+        \"\"\"\n+        return (\n+            self._derivKs(u) +\n+            self._derivI(u) +\n+            self._derivN(u) +\n+            self._derivAlpha(u)\n         )\n-        Utils.setKwargs(self, **kwargs)\n \n+    def _derivKs(self, u):\n+        if self.KsMap is None:\n+            return Utils.Zero()\n+\n+        Ks, alpha, I, n, m = self._get_params()\n+        P_p, P_n = _get_projections(u)  # Compute the positive/negative domains\n+        theta_e = 1.0 / ((1.0 + abs(alpha * u) ** n) ** m)\n+        dKs_dm_p = P_p * self.KsDeriv\n+        dKs_dm_n = P_n * Utils.sdiag(\n+            theta_e ** I * ((1.0 - (1.0 - theta_e ** (1.0 / m)) ** m) ** 2)\n+        ) * self.KsDeriv\n+        return dKs_dm_p + dKs_dm_n\n+\n+    def _derivAlpha(self, u):\n+        if self.alphaMap is None:\n+            return Utils.Zero()\n+        Ks, alpha, I, n, m = self._get_params()\n+        ddm = I*u*n*Ks*abs(alpha*u)**(n - 1)*np.sign(alpha*u)*(1.0/n - 1)*((abs(alpha*u)**n + 1)**(1.0/n - 1))**(I - 1)*((1 - 1.0/((abs(alpha*u)**n + 1)**(1.0/n - 1))**(1.0/(1.0/n - 1)))**(1 - 1.0/n) - 1)**2*(abs(alpha*u)**n + 1)**(1.0/n - 2) - (2*u*n*Ks*abs(alpha*u)**(n - 1)*np.sign(alpha*u)*(1.0/n - 1)*((abs(alpha*u)**n + 1)**(1.0/n - 1))**I*((1 - 1.0/((abs(alpha*u)**n + 1)**(1.0/n - 1))**(1.0/(1.0/n - 1)))**(1 - 1.0/n) - 1)*(abs(alpha*u)**n + 1)**(1.0/n - 2))/(((abs(alpha*u)**n + 1)**(1.0/n - 1))**(1.0/(1.0/n - 1) + 1)*(1 - 1.0/((abs(alpha*u)**n + 1)**(1.0/n - 1))**(1.0/(1.0/n - 1)))**(1.0/n))\n+        ddm[u >= 0] = 0\n+        dA = Utils.sdiag(ddm) * self.alphaDeriv\n+        return dA\n+\n+    def _derivN(self, u):\n+        if self.nMap is None:\n+            return Utils.Zero()\n+        Ks, alpha, I, n, m = self._get_params()\n+        ddm = 1.0*I*Ks*(1.0*(abs(alpha*u)**n + 1.0)**(-1.0 + 1.0/n))**I*((-1.0 + 1.0/n)*np.log(abs(alpha*u))*abs(alpha*u)**n/(abs(alpha*u)**n + 1.0) - 1.0*np.log(abs(alpha*u)**n + 1.0)/n**2)*(-(-(1.0*(abs(alpha*u)**n + 1.0)**(-1.0 + 1.0/n))**(1.0/(1.0 - 1.0/n)) + 1.0)**(1.0 - 1.0/n) + 1.0)**2*(abs(alpha*u)**n + 1.0)**(-1.0 + 1.0/n)*(abs(alpha*u)**n + 1.0)**(1.0 - 1.0/n) - 2*Ks*(1.0*(abs(alpha*u)**n + 1.0)**(-1.0 + 1.0/n))**I*(-(1.0*(abs(alpha*u)**n + 1.0)**(-1.0 + 1.0/n))**(1.0/(1.0 - 1.0/n)) + 1.0)**(1.0 - 1.0/n)*(-(1.0*(abs(alpha*u)**n + 1.0)**(-1.0 + 1.0/n))**(1.0/(1.0 - 1.0/n))*(1.0 - 1.0/n)*(1.0*((-1.0 + 1.0/n)*np.log(abs(alpha*u))*abs(alpha*u)**n/(abs(alpha*u)**n + 1.0) - 1.0*np.log(abs(alpha*u)**n + 1.0)/n**2)*(abs(alpha*u)**n + 1.0)**(-1.0 + 1.0/n)*(abs(alpha*u)**n + 1.0)**(1.0 - 1.0/n)/(1.0 - 1.0/n) - 1.0*np.log(1.0*(abs(alpha*u)**n + 1.0)**(-1.0 + 1.0/n))/(n**2*(1.0 - 1.0/n)**2))/(-(1.0*(abs(alpha*u)**n + 1.0)**(-1.0 + 1.0/n))**(1.0/(1.0 - 1.0/n)) + 1.0) + 1.0*np.log(-(1.0*(abs(alpha*u)**n + 1.0)**(-1.0 + 1.0/n))**(1.0/(1.0 - 1.0/n)) + 1.0)/n**2)*(-(-(1.0*(abs(alpha*u)**n + 1.0)**(-1.0 + 1.0/n))**(1.0/(1.0 - 1.0/n)) + 1.0)**(1.0 - 1.0/n) + 1.0)\n+        ddm[u >= 0] = 0\n+        dn = Utils.sdiag(ddm) * self.nDeriv\n+        return dn\n+\n+    def _derivI(self, u):\n+        if self.IMap is None:\n+            return Utils.Zero()\n+        Ks, alpha, I, n, m = self._get_params()\n+        ddm = Ks*(1.0*(abs(alpha*u)**n + 1.0)**(-1.0 + 1.0/n))**I*(-(-(1.0*(abs(alpha*u)**n + 1.0)**(-1.0 + 1.0/n))**(1.0/(1.0 - 1.0/n)) + 1.0)**(1.0 - 1.0/n) + 1.0)**2*np.log(1.0*(abs(alpha*u)**n + 1.0)**(-1.0 + 1.0/n))\n+        ddm[u >= 0] = 0\n+        dI = Utils.sdiag(ddm) * self.IDeriv\n+        return dI\n+\n+    def derivU(self, u):\n+        Ks, alpha, I, n, m = self._get_params()\n+        ddm = I*alpha*n*Ks*abs(alpha*u)**(n - 1.0)*np.sign(alpha*u)*(1.0/n - 1.0)*((abs(alpha*u)**n + 1)**(1.0/n - 1))**(I - 1)*((1 - 1.0/((abs(alpha*u)**n + 1)**(1.0/n - 1))**(1.0/(1.0/n - 1)))**(1 - 1.0/n) - 1)**2*(abs(alpha*u)**n + 1)**(1.0/n - 2) - (2*alpha*n*Ks*abs(alpha*u)**(n - 1)*np.sign(alpha*u)*(1.0/n - 1)*((abs(alpha*u)**n + 1)**(1.0/n - 1))**I*((1 - 1.0/((abs(alpha*u)**n + 1)**(1.0/n - 1))**(1.0/(1.0/n - 1)))**(1 - 1.0/n) - 1)*(abs(alpha*u)**n + 1)**(1.0/n - 2))/(((abs(alpha*u)**n + 1)**(1.0/n - 1))**(1.0/(1.0/n - 1) + 1)*(1 - 1.0/((abs(alpha*u)**n + 1)**(1.0/n - 1))**(1.0/(1.0/n - 1)))**(1.0/n))\n+        ddm[u >= 0] = 0\n+        g = Utils.sdiag(ddm)\n+        return g\n \n-class VanGenuchtenParams(object):\n-    \"\"\"\n-        The RETC code for quantifying the hydraulic functions of unsaturated soils,\n-        Van Genuchten, M Th, Leij, F J, Yates, S R\n \n-        Table 3: Average values for selected soil water retention and hydraulic\n-        conductivity parameters for 11 major soil textural groups\n-        according to Rawls et al. [1982]\n+def van_genuchten(mesh, **kwargs):\n+    return _partition_args(\n+        mesh,\n+        Vangenuchten_k,\n+        Vangenuchten_theta,\n+        ['alpha', 'n', 'Ks', 'I'],\n+        ['alpha', 'n', 'theta_r', 'theta_s'],\n+        **kwargs\n+    )\n \n+\n+class VanGenuchtenParams(object):\n+    \"\"\"The RETC code for quantifying the hydraulic functions of unsaturated\n+    soils, Van Genuchten, M Th, Leij, F J, Yates, S R\n+\n+    Table 3: Average values for selected soil water retention and hydraulic\n+    conductivity parameters for 11 major soil textural groups\n+    according to Rawls et al. [1982]\n     \"\"\"\n-    def __init__(self): pass\n+\n     @property\n     def sand(self):\n-        return {\"theta_r\": 0.020, \"theta_s\": 0.417, \"alpha\": 0.138*100., \"n\": 1.592, \"Ks\": 504.0/100./24./60./60.}\n+        return {\n+            \"theta_r\": 0.020, \"theta_s\": 0.417, \"alpha\": 0.138*100.,\n+            \"n\": 1.592, \"Ks\": 504.0/100./24./60./60.\n+        }","path":"SimPEG/FLOW/Richards/Empirical.py","position":877,"original_position":877,"commit_id":"4a97ee05bb0170349a412af92ff146f7888daf24","original_commit_id":"4a97ee05bb0170349a412af92ff146f7888daf24","user":{"login":"lheagy","id":6361812,"avatar_url":"https://avatars.githubusercontent.com/u/6361812?v=3","gravatar_id":"","url":"https://api.github.com/users/lheagy","html_url":"https://github.com/lheagy","followers_url":"https://api.github.com/users/lheagy/followers","following_url":"https://api.github.com/users/lheagy/following{/other_user}","gists_url":"https://api.github.com/users/lheagy/gists{/gist_id}","starred_url":"https://api.github.com/users/lheagy/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lheagy/subscriptions","organizations_url":"https://api.github.com/users/lheagy/orgs","repos_url":"https://api.github.com/users/lheagy/repos","events_url":"https://api.github.com/users/lheagy/events{/privacy}","received_events_url":"https://api.github.com/users/lheagy/received_events","type":"User","site_admin":false},"body":"cool!","created_at":"2017-02-09T06:19:14Z","updated_at":"2017-02-09T06:19:14Z","html_url":"https://github.com/simpeg/simpeg/pull/542#discussion_r100237174","pull_request_url":"https://api.github.com/repos/simpeg/simpeg/pulls/542","_links":{"self":{"href":"https://api.github.com/repos/simpeg/simpeg/pulls/comments/100237174"},"html":{"href":"https://github.com/simpeg/simpeg/pull/542#discussion_r100237174"},"pull_request":{"href":"https://api.github.com/repos/simpeg/simpeg/pulls/542"}}},"pull_request":{"url":"https://api.github.com/repos/simpeg/simpeg/pulls/542","id":104651927,"html_url":"https://github.com/simpeg/simpeg/pull/542","diff_url":"https://github.com/simpeg/simpeg/pull/542.diff","patch_url":"https://github.com/simpeg/simpeg/pull/542.patch","issue_url":"https://api.github.com/repos/simpeg/simpeg/issues/542","number":542,"state":"open","locked":false,"title":"Remove meshing, updates to Richards","user":{"login":"rowanc1","id":913249,"avatar_url":"https://avatars.githubusercontent.com/u/913249?v=3","gravatar_id":"","url":"https://api.github.com/users/rowanc1","html_url":"https://github.com/rowanc1","followers_url":"https://api.github.com/users/rowanc1/followers","following_url":"https://api.github.com/users/rowanc1/following{/other_user}","gists_url":"https://api.github.com/users/rowanc1/gists{/gist_id}","starred_url":"https://api.github.com/users/rowanc1/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rowanc1/subscriptions","organizations_url":"https://api.github.com/users/rowanc1/orgs","repos_url":"https://api.github.com/users/rowanc1/repos","events_url":"https://api.github.com/users/rowanc1/events{/privacy}","received_events_url":"https://api.github.com/users/rowanc1/received_events","type":"User","site_admin":false},"body":"First, apologies for conflating two PRs into one and for the size again... I was writing up my thesis.\r\n\r\n# Major Changes\r\n\r\n* I have removed the meshing, and some of the testing in favour of `discretize` (which was pulled out and improved by @jcapriot)\r\n* There are major updates to Richards equation and a number of new examples. This includes the ability to invert for any parameter inside the empirical functions.\r\n* `np` and `sp` are no longer exported by `from SimPEG import *` #533 \r\n\r\n## Minor changes\r\n\r\n* Removed `utils.isScalar` in favour of the numpy version.\r\n* `Identity` and `Zero` are pointers into the `discretize` package\r\n* Validation on `HasModel` classes is now recursive.\r\n\r\n# Todo\r\n\r\n- [ ] Remove the finite volume docs, and add pointers to new docs\r\n- [ ] There was a plotting test/example which I don't think has made it into discretize yet? @lheagy \r\n- [x] There were some tests that may not have been deleted properly, or are hanging around (FDEM/TDEM)\r\n- [ ] Check compatibility with the ObjFunctions branch.\r\n\r\n","created_at":"2017-02-04T06:55:53Z","updated_at":"2017-02-09T06:19:14Z","closed_at":null,"merged_at":null,"merge_commit_sha":"cfbc7ecc38644824cb7b54bd088ea99125d804a4","assignee":null,"assignees":[],"milestone":null,"commits_url":"https://api.github.com/repos/simpeg/simpeg/pulls/542/commits","review_comments_url":"https://api.github.com/repos/simpeg/simpeg/pulls/542/comments","review_comment_url":"https://api.github.com/repos/simpeg/simpeg/pulls/comments{/number}","comments_url":"https://api.github.com/repos/simpeg/simpeg/issues/542/comments","statuses_url":"https://api.github.com/repos/simpeg/simpeg/statuses/4a97ee05bb0170349a412af92ff146f7888daf24","head":{"label":"simpeg:feat/discretize","ref":"feat/discretize","sha":"4a97ee05bb0170349a412af92ff146f7888daf24","user":{"login":"simpeg","id":6043842,"avatar_url":"https://avatars.githubusercontent.com/u/6043842?v=3","gravatar_id":"","url":"https://api.github.com/users/simpeg","html_url":"https://github.com/simpeg","followers_url":"https://api.github.com/users/simpeg/followers","following_url":"https://api.github.com/users/simpeg/following{/other_user}","gists_url":"https://api.github.com/users/simpeg/gists{/gist_id}","starred_url":"https://api.github.com/users/simpeg/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/simpeg/subscriptions","organizations_url":"https://api.github.com/users/simpeg/orgs","repos_url":"https://api.github.com/users/simpeg/repos","events_url":"https://api.github.com/users/simpeg/events{/privacy}","received_events_url":"https://api.github.com/users/simpeg/received_events","type":"Organization","site_admin":false},"repo":{"id":14727320,"name":"simpeg","full_name":"simpeg/simpeg","owner":{"login":"simpeg","id":6043842,"avatar_url":"https://avatars.githubusercontent.com/u/6043842?v=3","gravatar_id":"","url":"https://api.github.com/users/simpeg","html_url":"https://github.com/simpeg","followers_url":"https://api.github.com/users/simpeg/followers","following_url":"https://api.github.com/users/simpeg/following{/other_user}","gists_url":"https://api.github.com/users/simpeg/gists{/gist_id}","starred_url":"https://api.github.com/users/simpeg/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/simpeg/subscriptions","organizations_url":"https://api.github.com/users/simpeg/orgs","repos_url":"https://api.github.com/users/simpeg/repos","events_url":"https://api.github.com/users/simpeg/events{/privacy}","received_events_url":"https://api.github.com/users/simpeg/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/simpeg/simpeg","description":"Simulation and Parameter Estimation in Geophysics  -  A python package for simulation and gradient based parameter estimation in the context of geophysical applications.","fork":false,"url":"https://api.github.com/repos/simpeg/simpeg","forks_url":"https://api.github.com/repos/simpeg/simpeg/forks","keys_url":"https://api.github.com/repos/simpeg/simpeg/keys{/key_id}","collaborators_url":"https://api.github.com/repos/simpeg/simpeg/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/simpeg/simpeg/teams","hooks_url":"https://api.github.com/repos/simpeg/simpeg/hooks","issue_events_url":"https://api.github.com/repos/simpeg/simpeg/issues/events{/number}","events_url":"https://api.github.com/repos/simpeg/simpeg/events","assignees_url":"https://api.github.com/repos/simpeg/simpeg/assignees{/user}","branches_url":"https://api.github.com/repos/simpeg/simpeg/branches{/branch}","tags_url":"https://api.github.com/repos/simpeg/simpeg/tags","blobs_url":"https://api.github.com/repos/simpeg/simpeg/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/simpeg/simpeg/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/simpeg/simpeg/git/refs{/sha}","trees_url":"https://api.github.com/repos/simpeg/simpeg/git/trees{/sha}","statuses_url":"https://api.github.com/repos/simpeg/simpeg/statuses/{sha}","languages_url":"https://api.github.com/repos/simpeg/simpeg/languages","stargazers_url":"https://api.github.com/repos/simpeg/simpeg/stargazers","contributors_url":"https://api.github.com/repos/simpeg/simpeg/contributors","subscribers_url":"https://api.github.com/repos/simpeg/simpeg/subscribers","subscription_url":"https://api.github.com/repos/simpeg/simpeg/subscription","commits_url":"https://api.github.com/repos/simpeg/simpeg/commits{/sha}","git_commits_url":"https://api.github.com/repos/simpeg/simpeg/git/commits{/sha}","comments_url":"https://api.github.com/repos/simpeg/simpeg/comments{/number}","issue_comment_url":"https://api.github.com/repos/simpeg/simpeg/issues/comments{/number}","contents_url":"https://api.github.com/repos/simpeg/simpeg/contents/{+path}","compare_url":"https://api.github.com/repos/simpeg/simpeg/compare/{base}...{head}","merges_url":"https://api.github.com/repos/simpeg/simpeg/merges","archive_url":"https://api.github.com/repos/simpeg/simpeg/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/simpeg/simpeg/downloads","issues_url":"https://api.github.com/repos/simpeg/simpeg/issues{/number}","pulls_url":"https://api.github.com/repos/simpeg/simpeg/pulls{/number}","milestones_url":"https://api.github.com/repos/simpeg/simpeg/milestones{/number}","notifications_url":"https://api.github.com/repos/simpeg/simpeg/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/simpeg/simpeg/labels{/name}","releases_url":"https://api.github.com/repos/simpeg/simpeg/releases{/id}","deployments_url":"https://api.github.com/repos/simpeg/simpeg/deployments","created_at":"2013-11-26T19:46:36Z","updated_at":"2016-12-27T05:14:36Z","pushed_at":"2017-02-08T16:49:05Z","git_url":"git://github.com/simpeg/simpeg.git","ssh_url":"git@github.com:simpeg/simpeg.git","clone_url":"https://github.com/simpeg/simpeg.git","svn_url":"https://github.com/simpeg/simpeg","homepage":"http://simpeg.xyz","size":50414,"stargazers_count":42,"watchers_count":42,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":19,"mirror_url":null,"open_issues_count":71,"forks":19,"open_issues":71,"watchers":42,"default_branch":"master"}},"base":{"label":"simpeg:dev","ref":"dev","sha":"7fd7d41a49953b25c15c5aef3ee76985b6d0f577","user":{"login":"simpeg","id":6043842,"avatar_url":"https://avatars.githubusercontent.com/u/6043842?v=3","gravatar_id":"","url":"https://api.github.com/users/simpeg","html_url":"https://github.com/simpeg","followers_url":"https://api.github.com/users/simpeg/followers","following_url":"https://api.github.com/users/simpeg/following{/other_user}","gists_url":"https://api.github.com/users/simpeg/gists{/gist_id}","starred_url":"https://api.github.com/users/simpeg/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/simpeg/subscriptions","organizations_url":"https://api.github.com/users/simpeg/orgs","repos_url":"https://api.github.com/users/simpeg/repos","events_url":"https://api.github.com/users/simpeg/events{/privacy}","received_events_url":"https://api.github.com/users/simpeg/received_events","type":"Organization","site_admin":false},"repo":{"id":14727320,"name":"simpeg","full_name":"simpeg/simpeg","owner":{"login":"simpeg","id":6043842,"avatar_url":"https://avatars.githubusercontent.com/u/6043842?v=3","gravatar_id":"","url":"https://api.github.com/users/simpeg","html_url":"https://github.com/simpeg","followers_url":"https://api.github.com/users/simpeg/followers","following_url":"https://api.github.com/users/simpeg/following{/other_user}","gists_url":"https://api.github.com/users/simpeg/gists{/gist_id}","starred_url":"https://api.github.com/users/simpeg/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/simpeg/subscriptions","organizations_url":"https://api.github.com/users/simpeg/orgs","repos_url":"https://api.github.com/users/simpeg/repos","events_url":"https://api.github.com/users/simpeg/events{/privacy}","received_events_url":"https://api.github.com/users/simpeg/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/simpeg/simpeg","description":"Simulation and Parameter Estimation in Geophysics  -  A python package for simulation and gradient based parameter estimation in the context of geophysical applications.","fork":false,"url":"https://api.github.com/repos/simpeg/simpeg","forks_url":"https://api.github.com/repos/simpeg/simpeg/forks","keys_url":"https://api.github.com/repos/simpeg/simpeg/keys{/key_id}","collaborators_url":"https://api.github.com/repos/simpeg/simpeg/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/simpeg/simpeg/teams","hooks_url":"https://api.github.com/repos/simpeg/simpeg/hooks","issue_events_url":"https://api.github.com/repos/simpeg/simpeg/issues/events{/number}","events_url":"https://api.github.com/repos/simpeg/simpeg/events","assignees_url":"https://api.github.com/repos/simpeg/simpeg/assignees{/user}","branches_url":"https://api.github.com/repos/simpeg/simpeg/branches{/branch}","tags_url":"https://api.github.com/repos/simpeg/simpeg/tags","blobs_url":"https://api.github.com/repos/simpeg/simpeg/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/simpeg/simpeg/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/simpeg/simpeg/git/refs{/sha}","trees_url":"https://api.github.com/repos/simpeg/simpeg/git/trees{/sha}","statuses_url":"https://api.github.com/repos/simpeg/simpeg/statuses/{sha}","languages_url":"https://api.github.com/repos/simpeg/simpeg/languages","stargazers_url":"https://api.github.com/repos/simpeg/simpeg/stargazers","contributors_url":"https://api.github.com/repos/simpeg/simpeg/contributors","subscribers_url":"https://api.github.com/repos/simpeg/simpeg/subscribers","subscription_url":"https://api.github.com/repos/simpeg/simpeg/subscription","commits_url":"https://api.github.com/repos/simpeg/simpeg/commits{/sha}","git_commits_url":"https://api.github.com/repos/simpeg/simpeg/git/commits{/sha}","comments_url":"https://api.github.com/repos/simpeg/simpeg/comments{/number}","issue_comment_url":"https://api.github.com/repos/simpeg/simpeg/issues/comments{/number}","contents_url":"https://api.github.com/repos/simpeg/simpeg/contents/{+path}","compare_url":"https://api.github.com/repos/simpeg/simpeg/compare/{base}...{head}","merges_url":"https://api.github.com/repos/simpeg/simpeg/merges","archive_url":"https://api.github.com/repos/simpeg/simpeg/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/simpeg/simpeg/downloads","issues_url":"https://api.github.com/repos/simpeg/simpeg/issues{/number}","pulls_url":"https://api.github.com/repos/simpeg/simpeg/pulls{/number}","milestones_url":"https://api.github.com/repos/simpeg/simpeg/milestones{/number}","notifications_url":"https://api.github.com/repos/simpeg/simpeg/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/simpeg/simpeg/labels{/name}","releases_url":"https://api.github.com/repos/simpeg/simpeg/releases{/id}","deployments_url":"https://api.github.com/repos/simpeg/simpeg/deployments","created_at":"2013-11-26T19:46:36Z","updated_at":"2016-12-27T05:14:36Z","pushed_at":"2017-02-08T16:49:05Z","git_url":"git://github.com/simpeg/simpeg.git","ssh_url":"git@github.com:simpeg/simpeg.git","clone_url":"https://github.com/simpeg/simpeg.git","svn_url":"https://github.com/simpeg/simpeg","homepage":"http://simpeg.xyz","size":50414,"stargazers_count":42,"watchers_count":42,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":19,"mirror_url":null,"open_issues_count":71,"forks":19,"open_issues":71,"watchers":42,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/simpeg/simpeg/pulls/542"},"html":{"href":"https://github.com/simpeg/simpeg/pull/542"},"issue":{"href":"https://api.github.com/repos/simpeg/simpeg/issues/542"},"comments":{"href":"https://api.github.com/repos/simpeg/simpeg/issues/542/comments"},"review_comments":{"href":"https://api.github.com/repos/simpeg/simpeg/pulls/542/comments"},"review_comment":{"href":"https://api.github.com/repos/simpeg/simpeg/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/simpeg/simpeg/pulls/542/commits"},"statuses":{"href":"https://api.github.com/repos/simpeg/simpeg/statuses/4a97ee05bb0170349a412af92ff146f7888daf24"}}}}
{"actor":{"display_login":"lheagy"}}