import numpy as np from finite_fields import finitefield class Variable: def __init__(self, name, fp): self.name = name self.fp = fp def __pow__(self, n): expr = MultiplyExpression(self.fp) expr.set_symbol(self.name, n) return expr def __eq__(self, other): return self.name == other.name def __hash__(self): return hash(self.name) def termify(self): expr = MultiplyExpression(self.fp) expr.set_symbol(self.name, 1) return expr class MultiplyExpression: def __init__(self, fp): self.coeff = fp(1) self.symbols = {} self.fp = fp def copy(self): result = MultiplyExpression(self.fp) result.coeff = self.coeff result.symbols = self.symbols.copy() return result def clean(self): for symbol in list(self.symbols.keys()): if self.symbols[symbol] == 0: del self.symbols[symbol] def matches(self, other): return self.symbols == other.symbols def set_symbol(self, var_name, power): self.symbols[var_name] = power def __eq__(self, other): return (self.coeff == other.coeff and self.symbols == other.symbols) def __neg__(self): result = self.copy() result.coeff *= -1 return result def __mul__(self, expr): result = MultiplyExpression(self.fp) result.coeff = self.coeff result.symbols = self.symbols.copy() if isinstance(expr, np.int64) or isinstance(expr, int): expr = self.fp(int(expr)) if hasattr(expr, "field"): result.coeff *= expr return result if isinstance(expr, Variable): expr = expr.termify() for var_name, power in expr.symbols.items(): if var_name in result.symbols: result.symbols[var_name] += power else: result.symbols[var_name] = power # Remember to multiply the coefficients result.coeff *= expr.coeff return result def __add__(self, expr): if isinstance(expr, Variable): expr = expr.termify() if self.matches(expr): result = self.copy() result.coeff += expr.coeff return result return MultivariatePolynomial([self, expr]) def __sub__(self, expr): expr = -expr return self + expr def evaluate(self, symbol_map): result = MultiplyExpression(self.fp) for symbol, power in self.symbols.items(): if symbol in symbol_map: value = symbol_map[symbol] result *= value**power else: result *= Variable(symbol, self.fp)**power return result def __str__(self): repr = "" first = True if self.coeff != 1: repr += str(self.coeff) first = False for var_name, power in self.symbols.items(): if first: first = False else: repr += " " if power == 1: repr += var_name else: repr += var_name + "^" + str(power) return repr class MultivariatePolynomial: def __init__(self, terms=[]): self.terms = terms def copy(self): terms = [term.copy() for term in self.terms] return MultivariatePolynomial(terms) # Operations can accept Variables and constants # so we make sure to convert them to MultiplyExpression types def _convert_term(self, term): if isinstance(term, Variable): term = term.termify() if hasattr(term, "field"): expr = MultiplyExpression(term.field) expr.coeff = term term = expr return term def __bool__(self): return bool(self.terms) def __eq__(self, other): return self.terms == other.terms def __neg__(self): terms = [-term for term in self.terms] return MultivariatePolynomial(terms) def __add__(self, term): term = self._convert_term(term) if isinstance(term, MultivariatePolynomial): # Recursively apply addition operation result = self.copy() for other_term in term.terms: result += other_term return result assert isinstance(term, MultiplyExpression) # Delete ^0 variables term.clean() # Skip terms where the coeff is 0 if term.coeff == 0: return self result = self.copy() result_term = result._find(term) if result_term is None: result.terms.append(term) else: result_term.coeff += term.coeff return result def __sub__(self, term): term = -term return self + term def __mul__(self, term): term = self._convert_term(term) if isinstance(term, MultivariatePolynomial): # Recursively apply addition operation result = MultivariatePolynomial() for other_term in term.terms: result += self * other_term return result assert isinstance(term, MultiplyExpression) # Delete ^0 variables term.clean() # Skip terms where the coeff is 0 if term.coeff == 0: return self terms = [self_term * term for self_term in self.terms] result = MultivariatePolynomial(terms) return result def divmod(self, poly): assert isinstance(poly, MultivariatePolynomial) # https://www.win.tue.nl/~aeb/2WF02/groebner.pdf def _find(self, other): for term in self.terms: if term.matches(other): return term return None def evaluate(self, variable_map): p = MultivariatePolynomial() for term in self.terms: assert isinstance(term, MultiplyExpression) p += term.evaluate(variable_map) return p def _assert_unique_terms(self): for i, term1 in enumerate(self.terms): for q, term2 in enumerate(self.terms): if i == q: continue assert not term1.matches(term2) def filter(self, variables): p = MultivariatePolynomial() for term in self.terms: assert isinstance(term, MultiplyExpression) skip = False for variable in variables: symbol = variable.name if symbol in term.symbols: skip = True if not skip: p += term return p def __str__(self): if not self.terms: return "0" repr = "" first = True for term in self.terms: if first: first = False else: repr += " + " repr += str(term) return repr if __name__ == "__main__": from finite_fields import finitefield p = 0x40000000000000000000000000000000224698fc094cf91b992d30ed00000001 fp = finitefield.IntegersModP(p) x = Variable("X") y = Variable("Y") z = Variable("Z") print(y**2 + y**2) p = x**3 * y**2 * x**2 * fp(5) * fp(2) + x**3 * y + z + fp(6) q = x**3 * y * fp(3) + y print(p) print(q) print(p + q) print(p * q) print(-q) print(p - q)