def makeEncoderDecoder(n, k, p):
    if not k <= n <= p:
        raise Exception(
            "Must have k <= n <= p but instead had (n,k,p) == (%r, %r, %r)" %
            (n, k, p))

    Fp = FiniteField(p)
    Poly = polynomialsOver(Fp)
    maxE = ((n - k) // 2)  # maximum allowed number of errors

    # message is a list of integers at most p
    def encode(message):
        if not all(x < p for x in message):
            raise Exception(
                "Message is improperly encoded as integers < p. It was:\n%r" %
                message)

        thePoly = Poly(message)
        return [[Fp(i), thePoly(Fp(i))] for i in range(n)]

    def solveSystem(encodedMessage, debug=False):
        for e in range(maxE, 0, -1):
            ENumVars = e + 1
            QNumVars = e + k

            def row(i, a, b):
                return ([b * a**j for j in range(ENumVars)] +
                        [-1 * a**j for j in range(QNumVars)] + [0]
                        )  # the "extended" part of the linear system

            system = (
                [row(i, a, b) for (i, (a, b)) in enumerate(encodedMessage)] +
                [[0] * (ENumVars - 1) + [1] + [0] * (QNumVars) + [1]])
            # ensure coefficient of x^e in E(x) is 1

            if debug:
                print("\ne is %r" % e)
                print("\nsystem is:\n\n")
                for row in system:
                    print("\t%r" % (row, ))

            solution = someSolution(system, freeVariableValue=1)
            E = Poly([solution[j] for j in range(e + 1)])
            Q = Poly([solution[j] for j in range(e + 1, len(solution))])

            if debug:
                print("\nreduced system is:\n\n")
                for row in system:
                    print("\t%r" % (row, ))

                print("solution is %r" % (solution, ))
                print("Q is %r" % (Q, ))
                print("E is %r" % (E, ))

            P, remainder = Q.__divmod__(E)
            if remainder == 0:
                return Q, E

        raise Exception("found no divisors!")

    def decode(encodedMessage):
        Q, E = solveSystem(encodedMessage)

        P, remainder = Q.__divmod__(E)
        if remainder != 0:
            raise Exception("Q is not divisibly by E!")

        return P.coefficients

    return encode, decode, solveSystem
def makeEncoderDecoder(n, k, p):
   if not k <= n <= p:
      raise Exception("Must have k <= n <= p but instead had (n,k,p) == (%r, %r, %r)" % (n,k,p))

   Fp = FiniteField(p)
   Poly = polynomialsOver(Fp)
   maxE = ((n - k) // 2)  # maximum allowed number of errors

   # message is a list of integers at most p
   def encode(message):
      if not all(x < p for x in message):
         raise Exception("Message is improperly encoded as integers < p. It was:\n%r" % message)

      thePoly = Poly(message)
      return [[Fp(i), thePoly(Fp(i))] for i in range(n)]


   def solveSystem(encodedMessage, debug=False):
      for e in range(maxE, 0, -1):
         ENumVars = e+1
         QNumVars = e+k
         def row(i, a, b):
            return ([b * a**j for j in range(ENumVars)] +
                    [-1 * a**j for j in range(QNumVars)] +
                    [0]) # the "extended" part of the linear system

         system = ([row(i, a, b) for (i, (a,b)) in enumerate(encodedMessage)] +
                   [[0] * (ENumVars-1) + [1] + [0] * (QNumVars) + [1]])
                     # ensure coefficient of x^e in E(x) is 1

         if debug:
            print("\ne is %r" % e)
            print("\nsystem is:\n\n")
            for row in system:
               print("\t%r" % (row,))

         solution = someSolution(system, freeVariableValue=1)
         E = Poly([solution[j] for j in range(e + 1)])
         Q = Poly([solution[j] for j in range(e + 1, len(solution))])

         if debug:
            print("\nreduced system is:\n\n")
            for row in system:
               print("\t%r" % (row,))

            print("solution is %r" % (solution,))
            print("Q is %r" % (Q,))
            print("E is %r" % (E,))

         P, remainder = Q.__divmod__(E)
         if remainder == 0:
            return Q, E

      raise Exception("found no divisors!")


   def decode(encodedMessage):
      Q,E = solveSystem(encodedMessage)

      P, remainder = Q.__divmod__(E)
      if remainder != 0:
         raise Exception("Q is not divisibly by E!")

      return P.coefficients


   return encode, decode, solveSystem
    def to_dense(self):
        mat = np.empty((self.m, self.n), dtype='O')
        mat.fill(self.zero)
        for (i, j), val in self.items():
            mat[i, j] = val
        return mat

    def __repr__(self):
        return repr(self.rowdicts)


#-
# Examples
if __name__ == '__main__':
    Fp = FiniteField(
        52435875175126190479447740508185965837690552500527637822603658699938581184513,
        1)  # (# noqa: E501)
    Poly = polynomialsOver(Fp)

    n = 8
    omega = get_omega(Fp, n)
    PolyEvalRep = polynomialsEvalRep(Fp, omega, n)

    f = Poly([1, 2, 3, 4, 5])
    xs = tuple([omega**i for i in range(n)])
    ys = tuple(map(f, xs))
    # print('xs:', xs)
    # print('ys:', ys)

    assert f == PolyEvalRep(xs, ys).to_coeffs()
def polynomialsEvalRep(field, omega, n):
    assert n & n - 1 == 0, "n must be a power of 2"

    # Check that omega is an n'th primitive root of unity
    assert type(omega) is field
    omega = field(omega)
    assert omega**(n) == 1
    _powers = [omega**i for i in range(n)]
    assert len(set(_powers)) == n

    _poly_coeff = polynomialsOver(field)

    class PolynomialEvalRep(object):
        def __init__(self, xs, ys):
            # Each element of xs must be a power of omega.
            # There must be a corresponding y for every x.
            if type(xs) is not tuple:
                xs = tuple(xs)
            if type(ys) is not tuple:
                ys = tuple(ys)

            assert len(xs) <= n + 1
            assert len(xs) == len(ys)
            for x in xs:
                assert x in _powers
            for y in ys:
                assert type(y) is field

            self.evalmap = dict(zip(xs, ys))

        @classmethod
        def from_coeffs(cls, poly):
            assert type(poly) is _poly_coeff
            assert poly.degree() <= n
            padded_coeffs = poly.coefficients + [field(0)] * (
                n - len(poly.coefficients))
            ys = fft_helper(padded_coeffs, omega, field)
            xs = [omega**i for i in range(n) if ys[i] != 0]
            ys = [y for y in ys if y != 0]
            return cls(xs, ys)

        def to_coeffs(self):
            # To convert back to the coefficient form, we use polynomial interpolation.
            # The non-zero elements stored in self.evalmap, so we fill in the zero values
            # here.
            ys = [
                self.evalmap[x] if x in self.evalmap else field(0)
                for x in _powers
            ]
            coeffs = [b / field(n) for b in fft_helper(ys, 1 / omega, field)]
            return _poly_coeff(coeffs)

        _lagrange_cache = {}

        def __call__(self, x):
            if type(x) is int:
                x = field(x)
            assert type(x) is field
            xs = _powers

            def lagrange(x, xi):
                # Let's cache lagrange values
                if (x, xi) in PolynomialEvalRep._lagrange_cache:
                    return PolynomialEvalRep._lagrange_cache[(x, xi)]

                mul = lambda a, b: a * b
                num = reduce(mul, [x - xj for xj in xs if xj != xi], field(1))
                den = reduce(mul, [xi - xj for xj in xs if xj != xi], field(1))
                PolynomialEvalRep._lagrange_cache[(x, xi)] = num / den
                return PolynomialEvalRep._lagrange_cache[(x, xi)]

            y = field(0)
            for xi, yi in self.evalmap.items():
                y += yi * lagrange(x, xi)
            return y

        def __mul__(self, other):
            # Scale by integer
            if type(other) is int:
                other = field(other)
            if type(other) is field:
                return PolynomialEvalRep(
                    self.evalmap.keys(),
                    [other * y for y in self.evalmap.values()])

            # Multiply another polynomial in the same representation
            if type(other) is type(self):
                xs = []
                ys = []
                for x, y in self.evalmap.items():
                    if x in other.evalmap:
                        xs.append(x)
                        ys.append(y * other.evalmap[x])
                return PolynomialEvalRep(xs, ys)

        @typecheck
        def __iadd__(self, other):
            # Add another polynomial to this one in place.
            # This is especially efficient when the other polynomial is sparse,
            # since we only need to add the non-zero elements.
            for x, y in other.evalmap.items():
                if x not in self.evalmap:
                    self.evalmap[x] = y
                else:
                    self.evalmap[x] += y
            return self

        @typecheck
        def __add__(self, other):
            res = PolynomialEvalRep(self.evalmap.keys(), self.evalmap.values())
            res += other
            return res

        def __sub__(self, other):
            return self + (-other)

        def __neg__(self):
            return PolynomialEvalRep(self.evalmap.keys(),
                                     [-y for y in self.evalmap.values()])

        def __truediv__(self, divisor):
            # Scale by integer
            if type(divisor) is int:
                other = field(divisor)
            if type(divisor) is field:
                return self * (1 / divisor)
            if type(divisor) is type(self):
                res = PolynomialEvalRep((), ())
                for x, y in self.evalmap.items():
                    assert x in divisor.evalmap
                    res.evalmap[x] = y / divisor.evalmap[x]
                return res
            return NotImplemented

        def __copy__(self):
            return PolynomialEvalRep(self.evalmap.keys(),
                                     self.evalmap.values())

        def __repr__(self):
            return f'PolyEvalRep[{hex(omega.n)[:15]}...,{n}]({len(self.evalmap)} elements)'

        @classmethod
        def divideWithCoset(cls, p, t, c=field(3)):
            """
              This assumes that p and t are polynomials in coefficient representation,
            and that p is divisible by t.
               This function is useful when t has roots at some or all of the powers of omega,
            in which case we cannot just convert to evalrep and use division above
            (since it would cause a divide by zero.
               Instead, we evaluate p(X) at powers of (c*omega) for some constant cofactor c.
            To do this efficiently, we create new polynomials, pc(X) = p(cX), tc(X) = t(cX),
            and evaluate these at powers of omega. This conversion can be done efficiently
            on the coefficient representation.
               See also: cosetFFT in libsnark / libfqfft.
               https://github.com/scipr-lab/libfqfft/blob/master/libfqfft/evaluation_domain/domains/extended_radix2_domain.tcc
            """
            assert type(p) is _poly_coeff
            assert type(t) is _poly_coeff
            # Compute p(cX), t(cX) by multiplying coefficients
            c_acc = field(1)
            pc = _poly_coeff(list(p.coefficients))  # make a copy
            for i in range(p.degree() + 1):
                pc.coefficients[-i - 1] *= c_acc
                c_acc *= c
            c_acc = field(1)
            tc = _poly_coeff(list(t.coefficients))  # make a copy
            for i in range(t.degree() + 1):
                tc.coefficients[-i - 1] *= c_acc
                c_acc *= c

            # Divide using evalrep
            pc_rep = cls.from_coeffs(pc)
            tc_rep = cls.from_coeffs(tc)
            hc_rep = pc_rep / tc_rep
            hc = hc_rep.to_coeffs()

            # Compute h(X) from h(cX) by dividing coefficients
            c_acc = field(1)
            h = _poly_coeff(list(hc.coefficients))  # make a copy
            for i in range(hc.degree() + 1):
                h.coefficients[-i - 1] /= c_acc
                c_acc *= c

            # Correctness checks
            # assert pc == tc * hc
            # assert p == t * h
            return h

    return PolynomialEvalRep
def makeEncoderDecoder(n, k, p):
    if not k <= n <= p:
        raise Exception(
            "Must have k <= n <= p but instead had (n,k,p) == (%r, %r, %r)" %
            (n, k, p))

    Fp = FiniteField(p)
    Poly = polynomialsOver(Fp)
    maxE = ((n - k) // 2)  # maximum allowed number of errors

    # message is a list of integers at most p
    def encode(message):
        if not all(x < p for x in message):
            raise Exception(
                "Message is improperly encoded as integers < p. It was:\n%r" %
                message)

        def row(i, b):
            return [Fp(i**(j)) for j in range(k)] + [Fp(b)]

        system = [row(i, message[i]) for i in range(k)]

        interpolated = someSolution(system, freeVariableValue=1)
        thePoly = Poly(interpolated)
        print("Original message is:")
        print(message)
        print("The polynomial encoding the message is:")
        print(thePoly)
        return [thePoly(Fp(i)) for i in range(n)]

    def solveSystem(encodedMessage, debug=True):
        for e in range(maxE, 0, -1):
            ENumVars = e
            QNumVars = e + k

            def row(i, x, r):
                return ([r * x**j for j in range(ENumVars)] +
                        [-1 * x**j
                         for j in range(QNumVars)] + [-r * x**ENumVars]
                        )  # the "extended" part of the linear system

            system = ([
                row(i, a, b) for (i, (a, b)) in enumerate(encodedMessage)
            ])
            # Add one more row in the end ensure coefficient of x^e in E(x) is 1

            if debug:
                print("\nSystem of equations is:\n\n")
                for row in system:
                    print("\t%r" % (row, ))

            solution = someSolution(system, freeVariableValue=1)
            E = Poly([solution[j] for j in range(ENumVars)] + [Fp(1)])
            Q = Poly([solution[j] for j in range(ENumVars, len(solution))])

            if debug:
                print("\nReduced system is:\n\n")
                for row in system:
                    print("\t%r" % (row, ))

                print("Solution is %r" % (solution, ))
                print("Q is %r" % (Q, ))
                print("E is %r" % (E, ))

            P, remainder = Q.__divmod__(E)
            if remainder == 0:
                return Q, E

        raise Exception("found no divisors!")

    def decode(encodedMessage):
        encodedMessage = [[Fp(i), encodedMessage[i]]
                          for i in range(len(encodedMessage))]
        Q, E = solveSystem(encodedMessage)

        Pcoefs, remainder = Q.__divmod__(E)
        if remainder != 0:
            raise Exception("Q is not divisibly by E!")
        P = Poly(Pcoefs)
        print("Decoded polynomial r(x) = Q(x) / E(x) is: ")
        print(P)
        return [P(Fp(i)) for i in range(k)]

    return encode, decode, solveSystem