Ejemplo n.º 1
0
    def coefficients_from_source(self, avec, bvec):
        from sumpy.kernel import DirectionalSourceDerivative
        kernel = self.kernel

        from sumpy.tools import mi_power, mi_factorial

        if isinstance(kernel, DirectionalSourceDerivative):
            if kernel.get_base_kernel() is not kernel.inner_kernel:
                raise NotImplementedError("more than one source derivative "
                        "not supported at present")

            from sumpy.symbolic import make_sympy_vector
            dir_vec = make_sympy_vector(kernel.dir_vec_name, kernel.dim)

            coeff_identifiers = self.get_full_coefficient_identifiers()
            result = [0] * len(coeff_identifiers)

            for idim in range(kernel.dim):
                for i, mi in enumerate(coeff_identifiers):
                    if mi[idim] == 0:
                        continue

                    derivative_mi = tuple(mi_i - 1 if iaxis == idim else mi_i
                            for iaxis, mi_i in enumerate(mi))

                    result[i] += (
                        - mi_power(avec, derivative_mi) * mi[idim]
                        * dir_vec[idim])
            for i, mi in enumerate(coeff_identifiers):
                result[i] /= mi_factorial(mi)
        else:
            result = [
                    mi_power(avec, mi) / mi_factorial(mi)
                    for mi in self.get_full_coefficient_identifiers()]
        return self.full_to_stored(result)
Ejemplo n.º 2
0
    def translate_from(self, src_expansion, src_coeff_exprs, src_rscale,
            dvec, tgt_rscale):
        if not isinstance(src_expansion, type(self)):
            raise RuntimeError("do not know how to translate %s to "
                    "Taylor multipole expansion"
                               % type(src_expansion).__name__)

        if not self.use_rscale:
            src_rscale = 1
            tgt_rscale = 1

        logger.info("building translation operator: %s(%d) -> %s(%d): start"
                % (type(src_expansion).__name__,
                    src_expansion.order,
                    type(self).__name__,
                    self.order))

        from sumpy.tools import mi_factorial

        src_mi_to_index = dict((mi, i) for i, mi in enumerate(
            src_expansion.get_coefficient_identifiers()))

        for i, mi in enumerate(src_expansion.get_coefficient_identifiers()):
            src_coeff_exprs[i] *= mi_factorial(mi)

        result = [0] * len(self.get_full_coefficient_identifiers())
        from pytools import generate_nonnegative_integer_tuples_below as gnitb

        for i, tgt_mi in enumerate(
                self.get_full_coefficient_identifiers()):

            tgt_mi_plus_one = tuple(mi_i + 1 for mi_i in tgt_mi)

            for src_mi in gnitb(tgt_mi_plus_one):
                try:
                    src_index = src_mi_to_index[src_mi]
                except KeyError:
                    # Omitted coefficients: not life-threatening
                    continue

                contrib = src_coeff_exprs[src_index]

                for idim in range(self.dim):
                    n = tgt_mi[idim]
                    k = src_mi[idim]
                    assert n >= k
                    from sympy import binomial
                    contrib *= (binomial(n, k)
                            * sym.UnevaluatedExpr(dvec[idim]/tgt_rscale)**(n-k))

                result[i] += (
                        contrib
                        * sym.UnevaluatedExpr(src_rscale/tgt_rscale)**sum(src_mi))

            result[i] /= mi_factorial(tgt_mi)

        logger.info("building translation operator: done")
        return (
            self.derivative_wrangler.get_stored_mpole_coefficients_from_full(
                result, tgt_rscale))
Ejemplo n.º 3
0
    def translate_from(self, src_expansion, src_coeff_exprs, src_rscale,
            dvec, tgt_rscale):
        if not isinstance(src_expansion, type(self)):
            raise RuntimeError("do not know how to translate %s to "
                    "Taylor multipole expansion"
                               % type(src_expansion).__name__)

        if not self.use_rscale:
            src_rscale = 1
            tgt_rscale = 1

        logger.info("building translation operator: %s(%d) -> %s(%d): start"
                % (type(src_expansion).__name__,
                    src_expansion.order,
                    type(self).__name__,
                    self.order))

        from sumpy.tools import mi_factorial

        src_mi_to_index = dict((mi, i) for i, mi in enumerate(
            src_expansion.get_coefficient_identifiers()))

        for i, mi in enumerate(src_expansion.get_coefficient_identifiers()):
            src_coeff_exprs[i] *= mi_factorial(mi)

        result = [0] * len(self.get_full_coefficient_identifiers())
        from pytools import generate_nonnegative_integer_tuples_below as gnitb

        for i, tgt_mi in enumerate(
                self.get_full_coefficient_identifiers()):

            tgt_mi_plus_one = tuple(mi_i + 1 for mi_i in tgt_mi)

            for src_mi in gnitb(tgt_mi_plus_one):
                try:
                    src_index = src_mi_to_index[src_mi]
                except KeyError:
                    # Omitted coefficients: not life-threatening
                    continue

                contrib = src_coeff_exprs[src_index]

                for idim in range(self.dim):
                    n = tgt_mi[idim]
                    k = src_mi[idim]
                    assert n >= k
                    from sympy import binomial
                    contrib *= (binomial(n, k)
                            * sym.UnevaluatedExpr(dvec[idim]/tgt_rscale)**(n-k))

                result[i] += (
                        contrib
                        * sym.UnevaluatedExpr(src_rscale/tgt_rscale)**sum(src_mi))

            result[i] /= mi_factorial(tgt_mi)

        logger.info("building translation operator: done")
        return (
            self.derivative_wrangler.get_stored_mpole_coefficients_from_full(
                result, tgt_rscale))
Ejemplo n.º 4
0
    def coefficients_from_source(self, avec, bvec, rscale, sac=None):
        from sumpy.kernel import DirectionalSourceDerivative
        kernel = self.kernel

        from sumpy.tools import mi_power, mi_factorial

        if not self.use_rscale:
            rscale = 1

        if isinstance(kernel, DirectionalSourceDerivative):
            from sumpy.symbolic import make_sym_vector

            dir_vecs = []
            tmp_kernel = kernel
            while isinstance(tmp_kernel, DirectionalSourceDerivative):
                dir_vecs.append(
                    make_sym_vector(tmp_kernel.dir_vec_name, kernel.dim))
                tmp_kernel = tmp_kernel.inner_kernel

            if kernel.get_base_kernel() is not tmp_kernel:
                raise NotImplementedError("Unknown kernel wrapper.")

            nderivs = len(dir_vecs)

            coeff_identifiers = self.get_full_coefficient_identifiers()
            result = [0] * len(coeff_identifiers)

            for i, mi in enumerate(coeff_identifiers):
                # One source derivative is the dot product of the gradient and
                # directional vector.
                # For multiple derivatives, gradient of gradients is taken.
                # For eg: in 3D, 2 source derivatives gives 9 terms and
                # cartesian_product below enumerates these 9 terms.
                for deriv_terms in cartesian_product(*[range(kernel.dim)] *
                                                     nderivs):
                    prod = 1
                    derivative_mi = list(mi)
                    for nderivative, deriv_dim in enumerate(deriv_terms):
                        prod *= -derivative_mi[deriv_dim]
                        prod *= dir_vecs[nderivative][deriv_dim]
                        derivative_mi[deriv_dim] -= 1
                    if any(v < 0 for v in derivative_mi):
                        continue
                    result[i] += mi_power(avec, derivative_mi) * prod

            for i, mi in enumerate(coeff_identifiers):
                result[i] /= (mi_factorial(mi) * rscale**sum(mi))
        else:
            avec = [sym.UnevaluatedExpr(a * rscale**-1) for a in avec]

            result = [
                mi_power(avec, mi) / mi_factorial(mi)
                for mi in self.get_full_coefficient_identifiers()
            ]
        return (self.expansion_terms_wrangler.
                get_stored_mpole_coefficients_from_full(result,
                                                        rscale,
                                                        sac=sac))
Ejemplo n.º 5
0
    def evaluate(self, coeffs, bvec):
        from sumpy.tools import mi_power, mi_factorial

        evaluated_coeffs = self.stored_to_full(coeffs)
        result = sum(
            coeff * self.kernel.postprocess_at_target(mi_power(bvec, mi), bvec) / mi_factorial(mi)
            for coeff, mi in zip(evaluated_coeffs, self.get_full_coefficient_identifiers())
        )
        return result
Ejemplo n.º 6
0
 def evaluate(self, coeffs, bvec, rscale):
     from sumpy.tools import mi_power, mi_factorial
     evaluated_coeffs = (
         self.derivative_wrangler.get_full_kernel_derivatives_from_stored(
             coeffs, rscale))
     bvec = bvec * rscale**-1
     result = sum(
         coeff * mi_power(bvec, mi) / mi_factorial(mi) for coeff, mi in zip(
             evaluated_coeffs, self.get_full_coefficient_identifiers()))
     return result
Ejemplo n.º 7
0
    def coefficients_from_source(self, avec, bvec, rscale):
        from sumpy.kernel import DirectionalSourceDerivative
        kernel = self.kernel

        from sumpy.tools import mi_power, mi_factorial

        if not self.use_rscale:
            rscale = 1

        if isinstance(kernel, DirectionalSourceDerivative):
            if kernel.get_base_kernel() is not kernel.inner_kernel:
                raise NotImplementedError("more than one source derivative "
                        "not supported at present")

            from sumpy.symbolic import make_sym_vector
            dir_vec = make_sym_vector(kernel.dir_vec_name, kernel.dim)

            coeff_identifiers = self.get_full_coefficient_identifiers()
            result = [0] * len(coeff_identifiers)

            for idim in range(kernel.dim):
                for i, mi in enumerate(coeff_identifiers):
                    if mi[idim] == 0:
                        continue

                    derivative_mi = tuple(mi_i - 1 if iaxis == idim else mi_i
                            for iaxis, mi_i in enumerate(mi))

                    result[i] += (
                        - mi_power(avec, derivative_mi) * mi[idim]
                        * dir_vec[idim])
            for i, mi in enumerate(coeff_identifiers):
                result[i] /= (mi_factorial(mi) * rscale ** sum(mi))
        else:
            avec = avec * rscale**-1

            result = [
                    mi_power(avec, mi) / mi_factorial(mi)
                    for mi in self.get_full_coefficient_identifiers()]
        return (
            self.derivative_wrangler.get_stored_mpole_coefficients_from_full(
                result, rscale))
Ejemplo n.º 8
0
 def evaluate(self, coeffs, bvec, rscale):
     from sumpy.tools import mi_power, mi_factorial
     evaluated_coeffs = (
         self.derivative_wrangler.get_full_kernel_derivatives_from_stored(
             coeffs, rscale))
     bvec = bvec * rscale**-1
     result = sum(
             coeff
             * mi_power(bvec, mi, evaluate=False)
             / mi_factorial(mi)
             for coeff, mi in zip(
                     evaluated_coeffs, self.get_full_coefficient_identifiers()))
     return result
Ejemplo n.º 9
0
    def translate_from(self, src_expansion, src_coeff_exprs, src_rscale,
            dvec, tgt_rscale, sac=None, _fast_version=True):
        if not isinstance(src_expansion, type(self)):
            raise RuntimeError("do not know how to translate %s to "
                    "Taylor multipole expansion"
                               % type(src_expansion).__name__)

        if not self.use_rscale:
            src_rscale = 1
            tgt_rscale = 1

        logger.info("building translation operator: %s(%d) -> %s(%d): start"
                % (type(src_expansion).__name__,
                    src_expansion.order,
                    type(self).__name__,
                    self.order))

        from sumpy.tools import mi_factorial

        src_mi_to_index = {mi: i for i, mi in enumerate(
            src_expansion.get_coefficient_identifiers())}

        tgt_mi_to_index = {mi: i for i, mi in enumerate(
            self.get_full_coefficient_identifiers())}

        # This algorithm uses the observation that M2M coefficients
        # have the following form in 2D
        #
        # $T_{m, n} = \sum_{i\le m, j\le n} C_{i, j}
        #             d_x^i d_y^j \binom{m}{i} \binom{n}{j}$
        # and can be rewritten as follows.
        #
        # Let $Y_{m, n} = \sum_{i\le m} C_{i, n} d_x^i \binom{m}{i}$.
        #
        # Then, $T_{m, n} = \sum_{j\le n} Y_{m, j} d_y^j \binom{n}{j}$.
        #
        # $Y_{m, n}$ are $p^2$ temporary variables that are
        # reused for different M2M coefficients and costs $p$ per variable.
        # Total cost for calculating $Y_{m, n}$ is $p^3$ and similar
        # for $T_{m, n}$. For compressed Taylor series this can be done
        # more efficiently.

        # Let's take the example u_xy + u_x + u_y = 0.
        # In the diagram below, C depicts a non zero source coefficient.
        # We divide these into two hyperplanes.
        #
        #  C              C             0
        #  C 0            C 0           0 0
        #  C 0 0       =  C 0 0      +  0 0 0
        #  C 0 0 0        C 0 0 0       0 0 0 0
        #  C C C C C      C 0 0 0 0     0 C C C C
        #
        # The calculations done when naively translating first hyperplane of the
        # source coefficients (C) to target coefficients (T) are shown
        # below in the graph. Each connection represents a O(1) calculation,
        # and the arrows go "up and to the right".
        #
        #  ┌─→C             T
        #  │  ↑
        #  │┌→C→0←─────┐->  T T
        #  ││ ↑ ↑      │
        #  ││ ┌─┘┌────┐│
        #  ││↱C→0↲0←─┐││    T T T
        #  │││└───⬏  │││
        #  └└└C→0 0 0│││    T T T T
        #     └───⬏ ↑│││
        #     └─────┘│││
        #     └──────┘││
        #     └───────┘│
        #     └────────┘
        #
        # By using temporaries (Y), this can be reduced as shown below.
        #
        #  ┌→C           Y             T
        #  │ ↑
        #  │↱C 0     ->  Y→0       ->  T T
        #  ││↑
        #  ││C 0 0       Y→0 0         T T T
        #  ││↑           └───⬏
        #  └└C 0 0 0     Y 0 0 0       T T T T
        #                └───⬏ ↑
        #                └─────┘
        #
        # Note that in the above calculation data is propagated upwards
        # in the first pass and then rightwards in the second pass.
        # Data propagation with zeros are not shown as they are not calculated.
        # If the propagation was done rightwards first and upwards second
        # number of calculations are higher as shown below.
        #
        #    C             ┌→Y           T
        #                  │ ↑
        #    C→0       ->  │↱Y↱Y     ->  T T
        #                  ││↑│↑
        #    C→0 0         ││Y│Y Y       T T T
        #    └───⬏         ││↑│↑ ↑
        #    C→0 0 0       └└Y└Y Y Y     T T T T
        #    └───⬏ ↑
        #    └─────┘
        #
        # For the second hyperplane, data is propogated rightwards first
        # and then upwards second which is opposite to that of the first
        # hyperplane.
        #
        #    0              0            0
        #
        #    0 0       ->   0↱0      ->  0 T
        #                    │↑
        #    0 0 0          0│0 0        0 T T
        #                    │↑ ↑
        #    0 C→C→C        0└Y Y Y      0 T T T
        #      └───⬏
        #
        # In other words, we're better off computing the translation
        # one dimension at a time. If the coefficient-identifying multi-indices
        # in the source expansion have the form (0, m) and (n, 0), where m>=0, n>=1,
        # then we calculate the output from (0, m) with the second
        # dimension as the fastest varying dimension and then calculate
        # the output from (n, 0) with the first dimension as the fastest
        # varying dimension.

        tgt_hyperplanes = \
            self.expansion_terms_wrangler._split_coeffs_into_hyperplanes()
        result = [0] * len(self.get_full_coefficient_identifiers())

        # axis morally iterates over 'hyperplane directions'
        for axis in range(self.dim):
            # {{{ index gymnastics

            # First, let's write source coefficients in target coefficient
            # indices. If target order is lower than source order, then
            # we will discard higher order terms from source coefficients.
            cur_dim_input_coeffs = \
                [0] * len(self.get_full_coefficient_identifiers())
            for d, mis in tgt_hyperplanes:
                # Only consider hyperplanes perpendicular to *axis*.
                if d != axis:
                    continue
                for mi in mis:
                    # When target order is higher than source order, we assume
                    # that the higher order source coefficients were zero.
                    if mi not in src_mi_to_index:
                        continue

                    src_idx = src_mi_to_index[mi]
                    tgt_idx = tgt_mi_to_index[mi]
                    cur_dim_input_coeffs[tgt_idx] = src_coeff_exprs[src_idx] * \
                            sym.UnevaluatedExpr(src_rscale/tgt_rscale)**sum(mi)

            if all(coeff == 0 for coeff in cur_dim_input_coeffs):
                continue

            # }}}

            # {{{ translation

            # As explained above using the unicode art, we use the orthogonal axis
            # as the last dimension to vary to reduce the number of operations.
            dims = list(range(axis)) + \
                   list(range(axis+1, self.dim)) + [axis]

            # d is the axis along which we translate.
            for d in dims:
                # We build the full target multipole and then compress it
                # at the very end.
                cur_dim_output_coeffs = \
                    [0] * len(self.get_full_coefficient_identifiers())
                for i, tgt_mi in enumerate(
                        self.get_full_coefficient_identifiers()):

                    # Calling this input_mis instead of src_mis because we
                    # converted the source coefficients to target coefficient
                    # indices beforehand.
                    for mi_i in range(tgt_mi[d]+1):
                        input_mi = mi_set_axis(tgt_mi, d, mi_i)
                        contrib = cur_dim_input_coeffs[tgt_mi_to_index[input_mi]]
                        for n, k, dist in zip(tgt_mi, input_mi, dvec):
                            assert n >= k
                            contrib /= factorial(n-k)
                            contrib *= \
                                sym.UnevaluatedExpr(dist/tgt_rscale)**(n-k)

                        cur_dim_output_coeffs[i] += contrib
                # cur_dim_output_coeffs is the input in the next iteration
                cur_dim_input_coeffs = cur_dim_output_coeffs

            # }}}

            for i in range(len(cur_dim_output_coeffs)):
                result[i] += cur_dim_output_coeffs[i]

        # {{{ simpler, functionally equivalent code
        if not _fast_version:
            src_mi_to_index = dict((mi, i) for i, mi in enumerate(
                src_expansion.get_coefficient_identifiers()))
            result = [0] * len(self.get_full_coefficient_identifiers())

            for i, mi in enumerate(src_expansion.get_coefficient_identifiers()):
                src_coeff_exprs[i] *= mi_factorial(mi)

            from pytools import generate_nonnegative_integer_tuples_below as gnitb

            for i, tgt_mi in enumerate(
                    self.get_full_coefficient_identifiers()):

                tgt_mi_plus_one = tuple(mi_i + 1 for mi_i in tgt_mi)

                for src_mi in gnitb(tgt_mi_plus_one):
                    try:
                        src_index = src_mi_to_index[src_mi]
                    except KeyError:
                        # Omitted coefficients: not life-threatening
                        continue

                    contrib = src_coeff_exprs[src_index]

                    for idim in range(self.dim):
                        n = tgt_mi[idim]
                        k = src_mi[idim]
                        assert n >= k
                        from sympy import binomial
                        contrib *= (binomial(n, k)
                                * sym.UnevaluatedExpr(dvec[idim]/tgt_rscale)**(n-k))

                    result[i] += (contrib
                        * sym.UnevaluatedExpr(src_rscale/tgt_rscale)**sum(src_mi))

                result[i] /= mi_factorial(tgt_mi)
        # }}}

        logger.info("building translation operator: done")
        return (
            self.expansion_terms_wrangler.get_stored_mpole_coefficients_from_full(
                result, tgt_rscale, sac=sac))
Ejemplo n.º 10
0
    def evaluate(self, coeffs, bvec, rscale):
        from pytools import factorial

        evaluated_coeffs = (
            self.derivative_wrangler.get_full_kernel_derivatives_from_stored(
                coeffs, rscale))

        bvec = [b * rscale**-1 for b in bvec]
        mi_to_index = dict(
            (mi, i)
            for i, mi in enumerate(self.get_full_coefficient_identifiers()))

        # Sort multi-indices so that last dimension varies fastest
        sorted_target_mis = sorted(self.get_full_coefficient_identifiers())
        dim = self.dim

        # Start with an invalid "seen" multi-index
        seen_mi = [-1] * dim
        # Local sum keep the sum of the terms that differ by each dimension
        local_sum = [0] * dim
        # Local multiplier keep the scalar that the sum has to be multiplied by
        # when adding to the sum of the preceding dimension.
        local_multiplier = [0] * dim

        # For the multi-indices in 3D, local_sum looks like this:
        #
        # Multi-index | coef | local_sum                              | local_mult
        # (0, 0, 0)   |  c0  | 0, 0,                c0                | 0, 1, 1
        # (0, 0, 1)   |  c1  | 0, 0,                c0+c1*dz          | 0, 1, 1
        # (0, 0, 2)   |  c2  | 0, 0,                c0+c1*dz+c2*dz^2  | 0, 1, 1
        # (0, 1, 0)   |  c3  | 0, c0+c1*dz+c2*dz^2, c3                | 0, 1, dy
        # (0, 1, 1)   |  c4  | 0, c0+c1*dz+c2*dz^2, c3+c4*dz          | 0, 1, dy
        # (0, 1, 2)   |  c5  | 0, c0+c1*dz+c2*dz^2, c3+c4*dz+c5*dz^2  | 0, 1, dy
        # (0, 2, 0)   |  c6  | 0, c0+c1*dz+c2*dz^2, c6                | 0, 1, dy^2
        #             |      |    +dy*(c3+c4*dz+c5*dz^2)              |
        # (0, 2, 1)   |  c7  | 0, c0+c1*dz+c2*dz^2, c6+c7*dz          | 0, 1, dy^2
        #             |      |    +dy*(c3+c4*dz+c5*dz^2)              |
        # (0, 2, 2)   |  c8  | 0, c0+c1*dz+c2*dz^2, c6+c7*dz+x8*dz^2  | 0, 1, dy^2
        #             |      |    +dy*(c3+c4*dz+c5*dz^2)              |
        # (1, 0, 0)   |  c8  | c0+c1*dz+c2*dz^2,         0, 0         | 0, dx, 1
        #             |      |  +dy*(c3+c4*dz+c5*dz^2)                |
        #             |      |  +dy^2*(c6+c7*dz+c8*dz^2)              |

        for mi in sorted_target_mis:

            # {{{ handle the case where a not-last dimension "clicked over"

            # (where d will be that not-last dimension)

            # Iterate in reverse order of dimensions to properly handle a
            # "double click-over".

            for d in reversed(range(dim - 1)):
                if seen_mi[d] != mi[d]:
                    # If the dimension d of mi changed from the previous value
                    # then the sum for dimension d+1 is complete, add it to
                    # dimension d after multiplying and restart.

                    local_sum[d] += local_sum[d + 1] * local_multiplier[d + 1]
                    local_sum[d + 1] = 0
                    local_multiplier[d + 1] = bvec[d]**mi[d] / factorial(mi[d])

            # }}}

            local_sum[dim-1] += evaluated_coeffs[mi_to_index[mi]] * \
                                    bvec[dim-1]**mi[dim-1] / factorial(mi[dim-1])
            seen_mi = mi

        for d in reversed(range(dim - 1)):
            local_sum[d] += local_sum[d + 1] * local_multiplier[d + 1]

        # {{{ simpler, functionally equivalent code

        if 0:
            from sumpy.tools import mi_power, mi_factorial

            return sum(
                coeff * mi_power(bvec, mi, evaluate=False) / mi_factorial(mi)
                for coeff, mi in zip(evaluated_coeffs,
                                     self.get_full_coefficient_identifiers()))

        # }}}

        return local_sum[0]
Ejemplo n.º 11
0
    def translate_from(self,
                       src_expansion,
                       src_coeff_exprs,
                       src_rscale,
                       dvec,
                       tgt_rscale,
                       sac=None):
        if not isinstance(src_expansion, type(self)):
            raise RuntimeError("do not know how to translate %s to "
                               "Taylor multipole expansion" %
                               type(src_expansion).__name__)

        if not self.use_rscale:
            src_rscale = 1
            tgt_rscale = 1

        logger.info("building translation operator: %s(%d) -> %s(%d): start" %
                    (type(src_expansion).__name__, src_expansion.order,
                     type(self).__name__, self.order))

        from sumpy.tools import mi_factorial

        src_mi_to_index = dict((mi, i) for i, mi in enumerate(
            src_expansion.get_coefficient_identifiers()))

        tgt_mi_to_index = dict(
            (mi, i)
            for i, mi in enumerate(self.get_full_coefficient_identifiers()))

        src_coeff_exprs = list(src_coeff_exprs)
        for i, mi in enumerate(src_expansion.get_coefficient_identifiers()):
            src_coeff_exprs[i] *= sym.UnevaluatedExpr(src_rscale /
                                                      tgt_rscale)**sum(mi)

        result = [0] * len(self.get_full_coefficient_identifiers())

        # This algorithm uses the observation that M2M coefficients
        # have the following form in 2D
        #
        # $B_{m, n} = \sum_{i\le m, j\le n} A_{i, j}
        #             d_x^i d_y^j \binom{m}{i} \binom{n}{j}$
        # and can be rewritten as follows.
        #
        # Let $T_{m, n} = \sum_{i\le m} A_{i, n} d_x^i \binom{m}{i}$.
        #
        # Then, $B_{m, n} = \sum_{j\le n} T_{m, j} d_y^j \binom{n}{j}$.
        #
        # $T_{m, n}$ are $p^2$ number of temporary variables that are
        # reused for different M2M coefficients and costs $p$ per variable.
        # Total cost for calculating $T_{m, n}$ is $p^3$ and similar
        # for $B_{m, n}$

        # In other words, we're better off computing the translation
        # one dimension at a time. This is realized here by using
        # the output from one dimension as the input to the next.
        # per_dim_coeffs_to_translate serves as the array of input
        # coefficients for each dimension's translation.

        dim_coeffs_to_translate = src_coeff_exprs

        mi_to_index = src_mi_to_index
        for d in range(self.dim):
            result = [0] * len(self.get_full_coefficient_identifiers())
            for i, tgt_mi in enumerate(
                    self.get_full_coefficient_identifiers()):

                src_mis_per_dim = []
                for mi_i in range(tgt_mi[d] + 1):
                    new_mi = list(tgt_mi)
                    new_mi[d] = mi_i
                    src_mis_per_dim.append(tuple(new_mi))

                for src_mi in src_mis_per_dim:
                    try:
                        src_index = mi_to_index[src_mi]
                    except KeyError:
                        # Omitted coefficients: not life-threatening
                        continue

                    contrib = dim_coeffs_to_translate[src_index]
                    for idim in range(self.dim):
                        n = tgt_mi[idim]
                        k = src_mi[idim]
                        assert n >= k
                        contrib /= mi_factorial((n - k, ))
                        contrib *= sym.UnevaluatedExpr(dvec[idim] /
                                                       tgt_rscale)**(n - k)

                    result[i] += contrib

            dim_coeffs_to_translate = result[:]
            mi_to_index = tgt_mi_to_index

        logger.info("building translation operator: done")
        return (self.expansion_terms_wrangler.
                get_stored_mpole_coefficients_from_full(result,
                                                        tgt_rscale,
                                                        sac=sac))