{"action":"created","comment":{"url":"https://api.github.com/repos/odlgroup/odl/pulls/comments/68189147","id":68189147,"diff_hunk":"@@ -0,0 +1,1173 @@\n+# Copyright 2014-2016 The ODL development group\n+#\n+# This file is part of ODL.\n+#\n+# ODL is free software: you can redistribute it and/or modify\n+# it under the terms of the GNU General Public License as published by\n+# the Free Software Foundation, either version 3 of the License, or\n+# (at your option) any later version.\n+#\n+# ODL is distributed in the hope that it will be useful,\n+# but WITHOUT ANY WARRANTY; without even the implied warranty of\n+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n+# GNU General Public License for more details.\n+#\n+# You should have received a copy of the GNU General Public License\n+# along with ODL.  If not, see <http://www.gnu.org/licenses/>.\n+\n+\"\"\"\n+Example of shape-based image reconstruction using linearized deformations.\n+\"\"\"\n+\n+# Imports for common Python 2/3 codebase\n+from __future__ import print_function, division, absolute_import\n+from future import standard_library\n+from builtins import super\n+from numbers import Number\n+from odl.operator.operator import Operator, OperatorComp\n+import odl\n+import numpy as np\n+import time\n+import matplotlib.pyplot as plt\n+standard_library.install_aliases()\n+\n+\n+class Functional(Operator):\n+\n+    \"\"\"Quick hack for a functional class.\"\"\"\n+\n+    def __init__(self, domain, linear=False):\n+        \"\"\"Initialize a new instance.\n+\n+        Parameters\n+        ----------\n+        domain : `LinearSpace`\n+            Set of elements on which the functional can be evaluated\n+        linear : `bool`, optional\n+            If `True`, assume that the functional is linear\n+        \"\"\"\n+        super().__init__(domain=domain, range=domain.field, linear=linear)\n+\n+    def gradient(self, x, out=None):\n+        \"\"\"Evaluate the gradient of the functional.\n+\n+        Parameters\n+        ----------\n+        x : domain element-like\n+            Point in which to evaluate the gradient\n+        out : domain element, optional\n+            Element into which the result is written\n+\n+        Returns\n+        -------\n+        out : domain element\n+            Result of the gradient calcuation. If ``out`` was given,\n+            the returned object is a reference to it.\n+        \"\"\"\n+        raise NotImplementedError\n+\n+    def __mul__(self, other):\n+        \"\"\"Return ``self * other``.\n+\n+        If ``other`` is an operator, this corresponds to\n+        operator composition:\n+\n+            ``op1 * op2 <==> (x --> op1(op2(x))``\n+\n+        If ``other`` is a scalar, this corresponds to right\n+        multiplication of scalars with operators:\n+\n+            ``op * scalar <==> (x --> op(scalar * x))``\n+\n+        If ``other`` is a vector, this corresponds to right\n+        multiplication of vectors with operators:\n+\n+            ``op * vector <==> (x --> op(vector * x))``\n+\n+        Note that left and right multiplications are generally\n+        different.\n+\n+        Parameters\n+        ----------\n+        other : {`Operator`, `LinearSpaceVector`, scalar}\n+            `Operator`:\n+                The `Operator.domain` of ``other`` must match this\n+                operator's `Operator.range`.\n+\n+            `LinearSpaceVector`:\n+                ``other`` must be an element of this operator's\n+                `Operator.domain`.\n+\n+            scalar:\n+                The `Operator.domain` of this operator must be a\n+                `LinearSpace` and ``other`` must be an\n+                element of the ``field`` of this operator's\n+                `Operator.domain`.\n+\n+        Returns\n+        -------\n+        mul : `Functional`\n+            Multiplication result\n+\n+            If ``other`` is an `Operator`, ``mul`` is a\n+            `FunctionalComp`.\n+\n+            If ``other`` is a scalar, ``mul`` is a\n+            `FunctionalRightScalarMult`.\n+\n+            If ``other`` is a vector, ``mul`` is a\n+            `FunctionalRightVectorMult`.\n+\n+        \"\"\"\n+        if isinstance(other, Operator):\n+            return FunctionalComp(self, other)\n+        elif isinstance(other, Number):\n+            # Left multiplication is more efficient, so we can use this in the\n+            # case of linear operator.\n+            raise NotImplementedError\n+            if self.is_linear:\n+                return OperatorLeftScalarMult(self, other)\n+            else:\n+                return OperatorRightScalarMult(self, other)\n+        elif isinstance(other, LinearSpaceVector) and other in self.domain:\n+            raise NotImplementedError\n+            return OperatorRightVectorMult(self, other.copy())\n+        else:\n+            return NotImplemented\n+\n+\n+class FunctionalComp(Functional, OperatorComp):\n+\n+    \"\"\"Composition of a functional with an operator.\"\"\"\n+\n+    def __init__(self, func, op, tmp1=None, tmp2=None):\n+        \"\"\"Initialize a new instance.\n+\n+        Parameters\n+        ----------\n+        func : `Functional`\n+            The left (\"outer\") operator\n+        op : `Operator`\n+            The right (\"inner\") operator. Its range must coincide with the\n+            domain of ``func``.\n+        tmp1 : `element` of the range of ``op``, optional\n+            Used to avoid the creation of a temporary when applying ``op``\n+        tmp2 : `element` of the range of ``op``, optional\n+            Used to avoid the creation of a temporary when applying the\n+            gradient of ``func``\n+        \"\"\"\n+        if not isinstance(func, Functional):\n+            raise TypeError('functional {!r} is not a Functional instance.'\n+                            ''.format(func))\n+\n+        OperatorComp.__init__(self, left=func, right=op, tmp=tmp1)\n+\n+        if tmp2 is not None and tmp2 not in self._left.domain:\n+            raise TypeError('second temporary {!r} not in the domain '\n+                            '{!r} of the functional.'\n+                            ''.format(tmp2, self._left.domain))\n+        self._tmp2 = tmp2\n+\n+    def gradient(self, x, out=None):\n+        \"\"\"Gradient of the compositon according to the chain rule.\n+\n+        Parameters\n+        ----------\n+        x : domain element-like\n+            Point in which to evaluate the gradient\n+        out : domain element, optional\n+            Element into which the result is written\n+\n+        Returns\n+        -------\n+        out : domain element\n+            Result of the gradient calcuation. If ``out`` was given,\n+            the returned object is a reference to it.\n+        \"\"\"\n+        if out is None:\n+            # adj_op = self._right.derivative(x).adjoint\n+            # return adj_op(self._left.gradient(self._right(x)))\n+            return self._right.derivative(x).adjoint(\n+                self._left.gradient(self._right(x)))\n+        else:\n+            if self._tmp is not None:\n+                tmp_op_ran = self._right(x, out=self._tmp)\n+            else:\n+                tmp_op_ran = self._right(x)\n+\n+            if self._tmp2 is not None:\n+                tmp_dom = self._left.gradient(tmp_op_ran, out=self._tmp2)\n+            else:\n+                tmp_dom = self._left.gradient(tmp_op_ran)\n+\n+            self._right.derivative(x).adjoint(tmp_dom, out=out)\n+\n+\n+class DisplacementOperator(Operator):\n+\n+    \"\"\"Operator mapping parameters to an inverse displacement for\n+    the domain of the target.\n+\n+    This operator computes for the momenta::\n+\n+        alpha --> D(alpha)\n+\n+    where\n+\n+        D(alpha)(y) = v(y).\n+\n+    The operator vector field ``v`` depends on the deformation parameters\n+    ``alpha_j`` as follows::\n+\n+        v(y) = sum_j (K(y, y_j) * alpha_j)\n+\n+    Here, ``K`` is the RKHS kernel matrix and each ``alpha_j`` is an\n+    element of ``R^n``, can be seen as the momenta alpha at control point y_j.\n+    \"\"\"\n+\n+    def __init__(self, par_space, control_points, discr_space, ft_kernel):\n+        \"\"\"Initialize a new instance.\n+\n+        Parameters\n+        ----------\n+        par_space : `ProductSpace` or `Rn`\n+            Space of the parameters. For one-dimensional deformations,\n+            `Rn` can be used. Otherwise, a `ProductSpace` with ``n``\n+            components is expected.\n+        control_points : `TensorGrid` or `array-like`\n+            The points ``x_j`` controlling the deformation. They can\n+            be given either as a tensor grid or as a point array. In\n+            the latter case, its shape must be ``(N, n)``, where\n+            ``n`` is the dimension of the template space, and ``N``\n+            the number of ``alpha_j``, i.e. the size of (each\n+            component of) ``par_space``.\n+        discr_space : `DiscreteSpace`\n+            Space of the image grid of the template.\n+        kernel : `callable`\n+            Function to determine the kernel at the control points ``K(y_j)``\n+            The function must accept a real variable and return a real number.\n+        \"\"\"\n+        # TODO: use kernel operator instead of function & matrix\n+        if isinstance(par_space, odl.Fn):\n+            # Make a product space with one component\n+            par_space = odl.ProductSpace(par_space, 1)\n+        elif isinstance(par_space, odl.ProductSpace):\n+            pass\n+        else:\n+            raise TypeError('expected Rn or ProductSpace as par_space, got '\n+                            '{!r}.'.format(par_space))\n+\n+        if par_space.size != discr_space.ndim:\n+            raise ValueError('dimensions of product space and image grid space'\n+                             ' do not match ({} != {})'\n+                             ''.format(par_space.size, discr_space.ndim))\n+\n+        # The operator maps from the parameter space to an inverse\n+        # displacement for the domain of the target.\n+\n+        self.discr_space = discr_space\n+        self.range_space = odl.ProductSpace(self.discr_space,\n+                                            self.discr_space.ndim)\n+\n+        super().__init__(par_space, self.range_space, linear=True)\n+\n+        self.ft_kernel = ft_kernel\n+\n+        if not isinstance(control_points, odl.TensorGrid):\n+            self._control_pts = np.asarray(control_points)\n+            if self._control_pts.shape != (self.num_contr_pts, self.ndim):\n+                raise ValueError(\n+                    'expected control point array of shape {}, got {}.'\n+                    ''.format((self.num_contr_pts, self.ndim),\n+                              self.control_points.shape))\n+        else:\n+            self._control_pts = control_points\n+\n+        # TODO: check that number of control points is the same as alphas\n+\n+    @property\n+    def ndim(self):\n+        \"\"\"Number of dimensions of the deformation.\"\"\"\n+        return self.domain.ndim\n+\n+    @property\n+    def contr_pts_is_grid(self):\n+        \"\"\"`True` if the control points are given as a grid.\"\"\"\n+        return isinstance(self.control_points, odl.TensorGrid)\n+\n+    @property\n+    def control_points(self):\n+        \"\"\"The control points for the deformations.\"\"\"\n+        return self._control_pts\n+\n+    @property\n+    def num_contr_pts(self):\n+        \"\"\"Number of control points for the deformations.\"\"\"\n+        if self.contr_pts_is_grid:\n+            return self.control_points.size\n+        else:\n+            return len(self.control_points)\n+\n+    @property\n+    def image_grid(self):\n+        \"\"\"Spatial sampling grid of the image space.\"\"\"\n+        return self.discr_space.grid\n+\n+    def displacement_ft(self, alphas):\n+        \"\"\"Calculate the inverse translation at point y by n-D FFT.\n+\n+        inverse displacement: v(y)\n+\n+        Parameters\n+        ----------\n+        alphas: `ProductSpaceVector`\n+            Deformation parameters for control points. It has ``n``\n+            components, each of which has size ``N``. Here, ``n`` is\n+            the number of dimensions and ``N`` the number of control\n+            points. Note that here N = M.\n+        kernel: Gaussian kernel fuction\n+        \"\"\"\n+        ft_momenta = vectorial_ft_fit_op(alphas)\n+        ft_displacement = self.ft_kernel * ft_momenta\n+        return vectorial_ft_fit_op.inverse(ft_displacement)\n+        # scaling\n+#        return (vectorial_ft_op_inverse(ft_displacement) /\n+#                self.discr_space.cell_volume * 2.0 * np.pi)\n+\n+    def _call(self, alphas):\n+        \"\"\"Implementation of ``self(alphas, out)``.\n+\n+        Parameters\n+        ----------\n+        alphas: `ProductSpaceVector`\n+            Deformation parameters for control points. It has ``n``\n+            components, each of which has size ``N``. Here, ``n`` is\n+            the number of dimensions and ``N`` the number of control\n+            points.\n+\n+        out : `DiscreteLpVector`\n+            Element where the result is stored\n+        \"\"\"\n+\n+        return self.displacement_ft(alphas)\n+\n+    def derivative(self, alphas):\n+        \"\"\"Frechet derivative of this operator in ``alphas``.\n+\n+        Parameters\n+        ----------\n+        alphas: `ProductSpaceVector`\n+            Deformation parameters for control points. It has ``n``\n+            components, each of which has size ``N``. Here, ``n`` is\n+            the number of dimensions and ``N`` the number of control\n+            points.\n+\n+        Returns\n+        -------\n+        deriv : `Operator`\n+            The derivative of this operator, evaluated at ``alphas``\n+        \"\"\"\n+        deriv_op = DisplacementDerivative(\n+            alphas, self.control_points, self.discr_space, self.ft_kernel)\n+\n+        return deriv_op\n+\n+\n+class DisplacementDerivative(DisplacementOperator):\n+\n+    \"\"\"Frechet derivative of the displacement operator at alphas.\"\"\"\n+\n+    def __init__(self, alphas, control_points, discr_space, ft_kernel):\n+        \"\"\"Initialize a new instance.\n+\n+        Parameters\n+        ----------\n+        alphas : `ProductSpaceVector`\n+            Displacement parameters in which the derivative is evaluated\n+        control_points : `TensorGrid` or `array-like`\n+            The points ``x_j`` controlling the deformation. They can\n+            be given either as a tensor grid or as a point array. In\n+            the latter case, its shape must be ``(N, n)``, where\n+            ``n`` is the dimension of the template space, and ``N``\n+            the number of ``alpha_j``, i.e. the size of (each\n+            component of) ``par_space``.\n+        discr_space : `DiscreteSpace`\n+            Space of the image grid of the template.\n+        kernel : `callable`\n+            Function to determine the kernel at the control points ``K(y_j)``\n+            The function must accept a real variable and return a real number.\n+        \"\"\"\n+\n+        super().__init__(alphas.space, control_points, discr_space, ft_kernel)\n+        self.discr_space = discr_space\n+        self.range_space = odl.ProductSpace(self.discr_space,\n+                                            self.discr_space.ndim)\n+        Operator.__init__(self, alphas.space, self.range_space, linear=True)\n+        self.alphas = alphas\n+\n+    def _call(self, betas):\n+        \"\"\"Implementation of ``self(betas)``.\n+\n+        Parameters\n+        ----------\n+        betas: `ProductSpaceVector`\n+            Deformation parameters for control points. It has ``n``\n+            components, each of which has size ``N``. Here, ``n`` is\n+            the number of dimensions and ``N`` the number of control\n+            points. It should be in the same space as alpha.\n+        \"\"\"\n+\n+        return self.displacement_ft(betas)\n+\n+    @property\n+    def adjoint(self):\n+        \"\"\"Adjoint of the displacement derivative.\"\"\"\n+        adj_op = DisplacementDerivativeAdjoint(\n+            self.alphas, self.control_points, self.discr_space, self.ft_kernel)\n+\n+        return adj_op\n+\n+\n+class DisplacementDerivativeAdjoint(DisplacementDerivative):\n+\n+    \"\"\"Adjoint of the Displacement operator derivative.\n+    \"\"\"\n+    def __init__(self, alphas, control_points, discr_space, ft_kernel):\n+        \"\"\"Initialize a new instance.\n+\n+        Parameters\n+        ----------\n+        alphas : `ProductSpaceVector`\n+            Displacement parameters in which the derivative is evaluated\n+        control_points : `TensorGrid` or `array-like`\n+            The points ``x_j`` controlling the deformation. They can\n+            be given either as a tensor grid or as a point array. In\n+            the latter case, its shape must be ``(N, n)``, where\n+            ``n`` is the dimension of the template space, and ``N``\n+            the number of ``alpha_j``, i.e. the size of (each\n+            component of) ``par_space``.\n+        discr_space : `DiscreteSpace`\n+            Space of the image grid of the template.\n+        kernel : `callable`\n+            Function to determine the kernel at the control points ``K(y_j)``\n+            The function must accept a real variable and return a real number.\n+        \"\"\"\n+\n+        super().__init__(alphas, control_points, discr_space, ft_kernel)\n+\n+        # Switch domain and range\n+        self._domain, self._range = self._range, self._domain\n+\n+    def _call(self, grad_func):\n+        \"\"\"Implement ``self(func)```.\n+\n+        Parameters\n+        ----------\n+        func : `DiscreteLpVector`\n+            Element of the image's gradient space.\n+        \"\"\"\n+        # If control grid is not the image grid, the following result for\n+        # the ajoint is not right. Because the kernel matrix in fitting\n+        # term is not symmetric.\n+        return self.displacement_ft(grad_func)\n+\n+\n+class LinearizedDeformationOperator(Operator):\n+\n+    \"\"\"Operator mapping parameters to a fixed deformed template.\n+\n+    This operator computes for a fixed template ``I`` the deformed\n+    template::\n+\n+        v --> I(x + v(x))\n+\n+    where the vector field ``v`` depends on the deformation parameters\n+    ``alpha_j`` as follows::\n+\n+        v(x) = sum_j (K(x, x_j) * alpha_j)\n+\n+    Here, ``K`` is the RKHS kernel matrix and each ``alpha_j`` is an\n+    element of ``R^n``.\n+    \"\"\"\n+\n+    def __init__(self, template):\n+        \"\"\"Initialize a new instance.\n+\n+        Parameters\n+        ----------\n+        template : `DiscreteLpVector`\n+            Fixed template deformed by the vector field. Its space\n+            must have the same number of dimensions as ``par_space``.\n+        \"\"\"\n+        # The operator maps from the parameter space to the template\n+        # (image) space.\n+        self.template = template\n+        self.domain_space = odl.ProductSpace(self.template.space,\n+                                             self.template.space.ndim)\n+\n+        super().__init__(self.domain_space, self.template.space, linear=False)\n+\n+    def _call(self, displacement):\n+        \"\"\"Implementation of ``self(displacement)``.\n+\n+        Parameters\n+        ----------\n+        displacement: `ProductSpaceVector`\n+            Linearized deformation parameters for image grid points.\n+        \"\"\"\n+        image_pts = self.template.space.grid.points()\n+        image_pts += np.asarray(displacement).T\n+\n+        return self.template.interpolation(image_pts.T, bounds_check=False)\n+\n+    def linear_deform(self, template, displacement):\n+        \"\"\"Implementation of ``self(template, displacement)``.\n+\n+        Parameters\n+        ----------\n+        template : `DiscreteLpVector`\n+            Fixed template deformed by the vector field. Its space\n+            must have the same number of dimensions as ``par_space``.\n+        displacement: `ProductSpaceVector`\n+            Linearized deformation parameters for image grid points.\n+        \"\"\"\n+        image_pts = template.space.grid.points()\n+        image_pts += np.asarray(displacement).T\n+        return template.interpolation(image_pts.T, bounds_check=False)\n+\n+    def derivative(self, displacement):\n+        \"\"\"Frechet derivative of this operator in ``disp``.\n+\n+        Parameters\n+        ----------\n+        displacement: `ProductSpaceVector`\n+            Linearized deformation parameters for image grid points.\n+\n+        Returns\n+        -------\n+        deriv : `Operator`\n+        The derivative of this operator, evaluated at ``displacement``\n+        \"\"\"\n+        deriv_op = LinearizedDeformationDerivative(self.template,\n+                                                   displacement)\n+        return deriv_op\n+\n+\n+class LinearizedDeformationDerivative(LinearizedDeformationOperator):\n+\n+    \"\"\"Frechet derivative of the Linearized deformation operator.\"\"\"\n+\n+    def __init__(self, template, displacement):\n+        \"\"\"Initialize a new instance.\n+\n+        Parameters\n+        ----------\n+        displacement: `ProductSpaceVector`\n+            Linearized deformation parameters for image grid points.\n+        template : `DiscreteLpVector`\n+            Fixed template deformed by the vector field. Its space\n+            must have the same number of dimensions as ``par_space``.\n+        \"\"\"\n+\n+        super().__init__(template)\n+\n+        self.template = template\n+        self.displacement = displacement\n+\n+        Operator.__init__(self, self.displacement.space, self.template.space,\n+                          linear=True)\n+\n+    def derivative(self, *args, **kwargs):\n+        raise NotImplementedError\n+\n+    def _deform_grad(self, grad_f):\n+        \"\"\"Compute the deformation of template's gradient.\n+\n+        deformed template's gradient: \\gradient I(x + v(x))\n+\n+        Parameters\n+        ----------\n+        grad_f: `ProductSpaceVector`\n+            Gradient of the template, i.e. a vector field on the\n+            image domain\n+        \"\"\"\n+        temp = [self.linear_deform(gf, self.displacement) for gf in grad_f]\n+        return self.displacement.space.element(temp)\n+\n+    def _call(self, disp_like):\n+        \"\"\"Implementation of ``self(disp_like)``.\n+\n+        Parameters\n+        ----------\n+        disp_like: `ProductSpaceVector`\n+            Like the linearized deformation parameters for image grid points.\n+        \"\"\"\n+        grad = odl.Gradient(self._range)\n+        template_grad = grad(self.template)\n+        def_grad = self._deform_grad(template_grad)\n+\n+        result = def_grad[0] * disp_like[0].reshape(self.image_grid.shape,\n+                                                    order=self.range.order)\n+        for gf, d in zip(def_grad[1:], disp_like[1:]):\n+            result += gf * d.reshape(self.image_grid.shape,\n+                                     order=self.range.order)\n+        return result\n+\n+    @property\n+    def adjoint(self):\n+        \"\"\"Adjoint of the template deformation derivative.\"\"\"\n+        adj_op = LinearizedDeformationDerivativeAdjoint(\n+            self.template, self.displacement)\n+        return adj_op\n+\n+\n+class LinearizedDeformationDerivativeAdjoint(LinearizedDeformationDerivative):\n+\n+    \"\"\"Adjoint of the template deformation operator derivative.\n+\n+    TODO: write a bit more\n+    \"\"\"\n+    def __init__(self, template, displacement):\n+        \"\"\"Initialize a new instance.\n+\n+        Parameters\n+        ----------\n+        template : `DiscreteLpVector`\n+            Fixed template deformed by the vector field. Its space\n+            must have the same number of dimensions as ``par_space``.\n+        displacement: `ProductSpaceVector`\n+            Linearized deformation parameters for image grid points.\n+        \"\"\"\n+        super().__init__(template, displacement)\n+\n+        # Switch domain and range\n+        self._domain, self._range = self._range, self._domain","path":"deform/linear_deformation.py","position":null,"original_position":644,"commit_id":"b03b590a852f968dae481187d06e07b5b08bb7c8","original_commit_id":"4baf3af28b666f4c6670b1974284399d40fdc639","user":{"login":"kohr-h","id":5030250,"avatar_url":"https://avatars.githubusercontent.com/u/5030250?v=3","gravatar_id":"","url":"https://api.github.com/users/kohr-h","html_url":"https://github.com/kohr-h","followers_url":"https://api.github.com/users/kohr-h/followers","following_url":"https://api.github.com/users/kohr-h/following{/other_user}","gists_url":"https://api.github.com/users/kohr-h/gists{/gist_id}","starred_url":"https://api.github.com/users/kohr-h/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kohr-h/subscriptions","organizations_url":"https://api.github.com/users/kohr-h/orgs","repos_url":"https://api.github.com/users/kohr-h/repos","events_url":"https://api.github.com/users/kohr-h/events{/privacy}","received_events_url":"https://api.github.com/users/kohr-h/received_events","type":"User","site_admin":false},"body":"OK, I see what you mean. Basically the operator cannot consider `domain` and `range` as \"its own\" but rather something that was given from the parent class(es). In any case, we should consider the \"common ancestor\" principle then, seem to be the best (most explicit) way to avoid tons of duplicate code.","created_at":"2016-06-23T07:45:24Z","updated_at":"2016-06-23T07:45:24Z","html_url":"https://github.com/odlgroup/odl/pull/448#discussion_r68189147","pull_request_url":"https://api.github.com/repos/odlgroup/odl/pulls/448","_links":{"self":{"href":"https://api.github.com/repos/odlgroup/odl/pulls/comments/68189147"},"html":{"href":"https://github.com/odlgroup/odl/pull/448#discussion_r68189147"},"pull_request":{"href":"https://api.github.com/repos/odlgroup/odl/pulls/448"}}},"pull_request":{"url":"https://api.github.com/repos/odlgroup/odl/pulls/448","id":73547495,"html_url":"https://github.com/odlgroup/odl/pull/448","diff_url":"https://github.com/odlgroup/odl/pull/448.diff","patch_url":"https://github.com/odlgroup/odl/pull/448.patch","issue_url":"https://api.github.com/repos/odlgroup/odl/issues/448","number":448,"state":"open","locked":false,"title":"Linearized deformations","user":{"login":"chongchenmath","id":19347429,"avatar_url":"https://avatars.githubusercontent.com/u/19347429?v=3","gravatar_id":"","url":"https://api.github.com/users/chongchenmath","html_url":"https://github.com/chongchenmath","followers_url":"https://api.github.com/users/chongchenmath/followers","following_url":"https://api.github.com/users/chongchenmath/following{/other_user}","gists_url":"https://api.github.com/users/chongchenmath/gists{/gist_id}","starred_url":"https://api.github.com/users/chongchenmath/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/chongchenmath/subscriptions","organizations_url":"https://api.github.com/users/chongchenmath/orgs","repos_url":"https://api.github.com/users/chongchenmath/repos","events_url":"https://api.github.com/users/chongchenmath/events{/privacy}","received_events_url":"https://api.github.com/users/chongchenmath/received_events","type":"User","site_admin":false},"body":"The branch of Linearized deformations has almost been done for test. ","created_at":"2016-06-13T09:42:49Z","updated_at":"2016-06-23T07:45:24Z","closed_at":null,"merged_at":null,"merge_commit_sha":"f782234de4ac41c3d53e368815168577fc4be9a4","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/odlgroup/odl/pulls/448/commits","review_comments_url":"https://api.github.com/repos/odlgroup/odl/pulls/448/comments","review_comment_url":"https://api.github.com/repos/odlgroup/odl/pulls/comments{/number}","comments_url":"https://api.github.com/repos/odlgroup/odl/issues/448/comments","statuses_url":"https://api.github.com/repos/odlgroup/odl/statuses/b03b590a852f968dae481187d06e07b5b08bb7c8","head":{"label":"odlgroup:linearized_deformations","ref":"linearized_deformations","sha":"b03b590a852f968dae481187d06e07b5b08bb7c8","user":{"login":"odlgroup","id":15667171,"avatar_url":"https://avatars.githubusercontent.com/u/15667171?v=3","gravatar_id":"","url":"https://api.github.com/users/odlgroup","html_url":"https://github.com/odlgroup","followers_url":"https://api.github.com/users/odlgroup/followers","following_url":"https://api.github.com/users/odlgroup/following{/other_user}","gists_url":"https://api.github.com/users/odlgroup/gists{/gist_id}","starred_url":"https://api.github.com/users/odlgroup/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/odlgroup/subscriptions","organizations_url":"https://api.github.com/users/odlgroup/orgs","repos_url":"https://api.github.com/users/odlgroup/repos","events_url":"https://api.github.com/users/odlgroup/events{/privacy}","received_events_url":"https://api.github.com/users/odlgroup/received_events","type":"Organization","site_admin":false},"repo":{"id":45596393,"name":"odl","full_name":"odlgroup/odl","owner":{"login":"odlgroup","id":15667171,"avatar_url":"https://avatars.githubusercontent.com/u/15667171?v=3","gravatar_id":"","url":"https://api.github.com/users/odlgroup","html_url":"https://github.com/odlgroup","followers_url":"https://api.github.com/users/odlgroup/followers","following_url":"https://api.github.com/users/odlgroup/following{/other_user}","gists_url":"https://api.github.com/users/odlgroup/gists{/gist_id}","starred_url":"https://api.github.com/users/odlgroup/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/odlgroup/subscriptions","organizations_url":"https://api.github.com/users/odlgroup/orgs","repos_url":"https://api.github.com/users/odlgroup/repos","events_url":"https://api.github.com/users/odlgroup/events{/privacy}","received_events_url":"https://api.github.com/users/odlgroup/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/odlgroup/odl","description":"Operator Discretization Library","fork":false,"url":"https://api.github.com/repos/odlgroup/odl","forks_url":"https://api.github.com/repos/odlgroup/odl/forks","keys_url":"https://api.github.com/repos/odlgroup/odl/keys{/key_id}","collaborators_url":"https://api.github.com/repos/odlgroup/odl/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/odlgroup/odl/teams","hooks_url":"https://api.github.com/repos/odlgroup/odl/hooks","issue_events_url":"https://api.github.com/repos/odlgroup/odl/issues/events{/number}","events_url":"https://api.github.com/repos/odlgroup/odl/events","assignees_url":"https://api.github.com/repos/odlgroup/odl/assignees{/user}","branches_url":"https://api.github.com/repos/odlgroup/odl/branches{/branch}","tags_url":"https://api.github.com/repos/odlgroup/odl/tags","blobs_url":"https://api.github.com/repos/odlgroup/odl/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/odlgroup/odl/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/odlgroup/odl/git/refs{/sha}","trees_url":"https://api.github.com/repos/odlgroup/odl/git/trees{/sha}","statuses_url":"https://api.github.com/repos/odlgroup/odl/statuses/{sha}","languages_url":"https://api.github.com/repos/odlgroup/odl/languages","stargazers_url":"https://api.github.com/repos/odlgroup/odl/stargazers","contributors_url":"https://api.github.com/repos/odlgroup/odl/contributors","subscribers_url":"https://api.github.com/repos/odlgroup/odl/subscribers","subscription_url":"https://api.github.com/repos/odlgroup/odl/subscription","commits_url":"https://api.github.com/repos/odlgroup/odl/commits{/sha}","git_commits_url":"https://api.github.com/repos/odlgroup/odl/git/commits{/sha}","comments_url":"https://api.github.com/repos/odlgroup/odl/comments{/number}","issue_comment_url":"https://api.github.com/repos/odlgroup/odl/issues/comments{/number}","contents_url":"https://api.github.com/repos/odlgroup/odl/contents/{+path}","compare_url":"https://api.github.com/repos/odlgroup/odl/compare/{base}...{head}","merges_url":"https://api.github.com/repos/odlgroup/odl/merges","archive_url":"https://api.github.com/repos/odlgroup/odl/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/odlgroup/odl/downloads","issues_url":"https://api.github.com/repos/odlgroup/odl/issues{/number}","pulls_url":"https://api.github.com/repos/odlgroup/odl/pulls{/number}","milestones_url":"https://api.github.com/repos/odlgroup/odl/milestones{/number}","notifications_url":"https://api.github.com/repos/odlgroup/odl/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/odlgroup/odl/labels{/name}","releases_url":"https://api.github.com/repos/odlgroup/odl/releases{/id}","deployments_url":"https://api.github.com/repos/odlgroup/odl/deployments","created_at":"2015-11-05T07:57:55Z","updated_at":"2016-06-16T12:20:53Z","pushed_at":"2016-06-22T16:59:48Z","git_url":"git://github.com/odlgroup/odl.git","ssh_url":"git@github.com:odlgroup/odl.git","clone_url":"https://github.com/odlgroup/odl.git","svn_url":"https://github.com/odlgroup/odl","homepage":null,"size":18461,"stargazers_count":8,"watchers_count":8,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":4,"mirror_url":null,"open_issues_count":120,"forks":4,"open_issues":120,"watchers":8,"default_branch":"master"}},"base":{"label":"odlgroup:master","ref":"master","sha":"3543720d91841c602bfebf94bdbe26986a6f3718","user":{"login":"odlgroup","id":15667171,"avatar_url":"https://avatars.githubusercontent.com/u/15667171?v=3","gravatar_id":"","url":"https://api.github.com/users/odlgroup","html_url":"https://github.com/odlgroup","followers_url":"https://api.github.com/users/odlgroup/followers","following_url":"https://api.github.com/users/odlgroup/following{/other_user}","gists_url":"https://api.github.com/users/odlgroup/gists{/gist_id}","starred_url":"https://api.github.com/users/odlgroup/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/odlgroup/subscriptions","organizations_url":"https://api.github.com/users/odlgroup/orgs","repos_url":"https://api.github.com/users/odlgroup/repos","events_url":"https://api.github.com/users/odlgroup/events{/privacy}","received_events_url":"https://api.github.com/users/odlgroup/received_events","type":"Organization","site_admin":false},"repo":{"id":45596393,"name":"odl","full_name":"odlgroup/odl","owner":{"login":"odlgroup","id":15667171,"avatar_url":"https://avatars.githubusercontent.com/u/15667171?v=3","gravatar_id":"","url":"https://api.github.com/users/odlgroup","html_url":"https://github.com/odlgroup","followers_url":"https://api.github.com/users/odlgroup/followers","following_url":"https://api.github.com/users/odlgroup/following{/other_user}","gists_url":"https://api.github.com/users/odlgroup/gists{/gist_id}","starred_url":"https://api.github.com/users/odlgroup/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/odlgroup/subscriptions","organizations_url":"https://api.github.com/users/odlgroup/orgs","repos_url":"https://api.github.com/users/odlgroup/repos","events_url":"https://api.github.com/users/odlgroup/events{/privacy}","received_events_url":"https://api.github.com/users/odlgroup/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/odlgroup/odl","description":"Operator Discretization Library","fork":false,"url":"https://api.github.com/repos/odlgroup/odl","forks_url":"https://api.github.com/repos/odlgroup/odl/forks","keys_url":"https://api.github.com/repos/odlgroup/odl/keys{/key_id}","collaborators_url":"https://api.github.com/repos/odlgroup/odl/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/odlgroup/odl/teams","hooks_url":"https://api.github.com/repos/odlgroup/odl/hooks","issue_events_url":"https://api.github.com/repos/odlgroup/odl/issues/events{/number}","events_url":"https://api.github.com/repos/odlgroup/odl/events","assignees_url":"https://api.github.com/repos/odlgroup/odl/assignees{/user}","branches_url":"https://api.github.com/repos/odlgroup/odl/branches{/branch}","tags_url":"https://api.github.com/repos/odlgroup/odl/tags","blobs_url":"https://api.github.com/repos/odlgroup/odl/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/odlgroup/odl/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/odlgroup/odl/git/refs{/sha}","trees_url":"https://api.github.com/repos/odlgroup/odl/git/trees{/sha}","statuses_url":"https://api.github.com/repos/odlgroup/odl/statuses/{sha}","languages_url":"https://api.github.com/repos/odlgroup/odl/languages","stargazers_url":"https://api.github.com/repos/odlgroup/odl/stargazers","contributors_url":"https://api.github.com/repos/odlgroup/odl/contributors","subscribers_url":"https://api.github.com/repos/odlgroup/odl/subscribers","subscription_url":"https://api.github.com/repos/odlgroup/odl/subscription","commits_url":"https://api.github.com/repos/odlgroup/odl/commits{/sha}","git_commits_url":"https://api.github.com/repos/odlgroup/odl/git/commits{/sha}","comments_url":"https://api.github.com/repos/odlgroup/odl/comments{/number}","issue_comment_url":"https://api.github.com/repos/odlgroup/odl/issues/comments{/number}","contents_url":"https://api.github.com/repos/odlgroup/odl/contents/{+path}","compare_url":"https://api.github.com/repos/odlgroup/odl/compare/{base}...{head}","merges_url":"https://api.github.com/repos/odlgroup/odl/merges","archive_url":"https://api.github.com/repos/odlgroup/odl/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/odlgroup/odl/downloads","issues_url":"https://api.github.com/repos/odlgroup/odl/issues{/number}","pulls_url":"https://api.github.com/repos/odlgroup/odl/pulls{/number}","milestones_url":"https://api.github.com/repos/odlgroup/odl/milestones{/number}","notifications_url":"https://api.github.com/repos/odlgroup/odl/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/odlgroup/odl/labels{/name}","releases_url":"https://api.github.com/repos/odlgroup/odl/releases{/id}","deployments_url":"https://api.github.com/repos/odlgroup/odl/deployments","created_at":"2015-11-05T07:57:55Z","updated_at":"2016-06-16T12:20:53Z","pushed_at":"2016-06-22T16:59:48Z","git_url":"git://github.com/odlgroup/odl.git","ssh_url":"git@github.com:odlgroup/odl.git","clone_url":"https://github.com/odlgroup/odl.git","svn_url":"https://github.com/odlgroup/odl","homepage":null,"size":18461,"stargazers_count":8,"watchers_count":8,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":4,"mirror_url":null,"open_issues_count":120,"forks":4,"open_issues":120,"watchers":8,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/odlgroup/odl/pulls/448"},"html":{"href":"https://github.com/odlgroup/odl/pull/448"},"issue":{"href":"https://api.github.com/repos/odlgroup/odl/issues/448"},"comments":{"href":"https://api.github.com/repos/odlgroup/odl/issues/448/comments"},"review_comments":{"href":"https://api.github.com/repos/odlgroup/odl/pulls/448/comments"},"review_comment":{"href":"https://api.github.com/repos/odlgroup/odl/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/odlgroup/odl/pulls/448/commits"},"statuses":{"href":"https://api.github.com/repos/odlgroup/odl/statuses/b03b590a852f968dae481187d06e07b5b08bb7c8"}}}}
{"actor":{"display_login":"kohr-h"}}