2

Suppose I had the expression:

v = ((((x * 3) * y) * 5) * z) + 5

And I want to reduce the amount of operations as much as possible in v by moving sub expressions into different variables, yet maintaining that y must only occur in v, such as:

a = x * 3
b = 5 * z
v = ((a * y) * b) + 5

Is there an algorithm that can achieve this?

David Eisenstat
  • 64,237
  • 7
  • 60
  • 120
Tom
  • 1,235
  • 9
  • 22

1 Answers1

3

If the programs are limited to straight-line programs with plus, minus, times, then you could determine the value of v as a polynomial in y and evaluate it using Horner's method.

Here's some rough Python by way of illustration.

import operator


class Formula:
    def __init__(self, s=0):
        self.s = str(s)

    def __repr__(self):
        return "Formula({!r})".format(self.s)

    def __str__(self):
        return self.s

    def __add__(self, other):
        if isinstance(other, int):
            other = Formula(str(other))
        if not isinstance(other, Formula):
            return NotImplemented
        if other.s == "0":
            return self
        return Formula("({}) + ({})".format(self, other))

    def __radd__(self, other):
        return self + other

    def __mul__(self, other):
        if isinstance(other, int):
            other = Formula(str(other))
        if not isinstance(other, Formula):
            return NotImplemented
        if other.s == "0":
            return 0
        if other.s == "1":
            return self
        return Formula("({}) * ({})".format(self, other))

    def __rmul__(self, other):
        return self * other


class Polynomial:
    def __init__(self, *coefs):
        self.coefs = coefs

    def print_program(self):
        v = 0
        for i, coef in enumerate(reversed(self.coefs)):
            c = Formula("c{}".format(i))
            print("{} = {}".format(c, coef))
            v *= Formula("y")
            v += c
        print("v = {}".format(v))

    def __add__(self, other):
        if isinstance(other, int):
            other = Formula(other)
        if isinstance(other, Formula):
            other = Polynomial(other)
        if not isinstance(other, Polynomial):
            return NotImplemented
        coefs = list(map(operator.add, self.coefs, other.coefs))
        if len(self.coefs) > len(other.coefs):
            coefs.extend(self.coefs[len(other.coefs) :])
        if len(other.coefs) > len(self.coefs):
            coefs.extend(other.coefs[len(self.coefs) :])
        return Polynomial(*coefs)

    def __radd__(self, other):
        return self + other

    def __mul__(self, other):
        if isinstance(other, int):
            other = Formula(other)
        if isinstance(other, Formula):
            other = Polynomial(other)
        if not isinstance(other, Polynomial):
            return NotImplemented
        coefs = [0] * (len(self.coefs) + len(other.coefs) - 1)
        for i, ci in enumerate(self.coefs):
            for j, cj in enumerate(other.coefs):
                coefs[i + j] += ci * cj
        return Polynomial(*coefs)

    def __rmul__(self, other):
        return self * other


x = Formula("x")
y = Polynomial(0, 1)
z = Formula("z")

v = ((((x * 3) * y) * 5) * z) + 5
v.print_program()

The output is

c0 = (((x) * (3)) * (5)) * (z)
c1 = 5
v = ((c0) * (y)) + (c1)
David Eisenstat
  • 64,237
  • 7
  • 60
  • 120
  • Thanks a lot for the detailed response. This works great for the example. Two questions: Can I still use this method if I 1. would like to introduce the divide operator? and 2. can I also use this method if it has multiple values of `y` for example `((((x * 3) * y) * 5) * z) + y) + 5` – Tom Dec 27 '20 at 01:36
  • 1
    @Tom Multiple occurrences of `y` are fine. Division involves extending the polynomial logic to polynomial fractions -- not too hard, follows the logic for integer fractions, but I'm not sure if polynomial fraction is always optimal. You may want to implement polynomial GCD to eliminate common factors. – David Eisenstat Dec 27 '20 at 01:57
  • Thank you. Have you got any tips for getting this to work with multiple special values e.g. `y, q`? So the end result is also a expression containing just `y` and `q` – Tom Dec 27 '20 at 12:56
  • @Tom Then you have to implement support for multivariate polynomials, which you can think of in a way as univariate polynomials with univariate (in a different variable) polynomial coefficients. The more power you add, though, the harder it is to optimize. This might be the point at which you want to try to rearrange what exists using standard compiler optimization techniques rather than smash it down to monomials and putting it back together. – David Eisenstat Dec 27 '20 at 15:06