def solve_mod(eqns, modulus, solution_dict=False): r""" Return all solutions to an equation or list of equations modulo the given integer modulus. Each equation must involve only polynomials in 1 or many variables. By default the solutions are returned as `n`-tuples, where `n` is the number of variables appearing anywhere in the given equations. The variables are in alphabetical order. INPUT: - ``eqns`` - equation or list of equations - ``modulus`` - an integer - ``solution_dict`` - bool (default: False); if True or non-zero, return a list of dictionaries containing the solutions. If there are no solutions, return an empty list (rather than a list containing an empty dictionary). Likewise, if there's only a single solution, return a list containing one dictionary with that solution. EXAMPLES:: sage: var('x,y') (x, y) sage: solve_mod([x^2 + 2 == x, x^2 + y == y^2], 14) [(4, 2), (4, 6), (4, 9), (4, 13)] sage: solve_mod([x^2 == 1, 4*x == 11], 15) [(14,)] Fermat's equation modulo 3 with exponent 5:: sage: var('x,y,z') (x, y, z) sage: solve_mod([x^5 + y^5 == z^5], 3) [(0, 0, 0), (0, 1, 1), (0, 2, 2), (1, 0, 1), (1, 1, 2), (1, 2, 0), (2, 0, 2), (2, 1, 0), (2, 2, 1)] We can solve with respect to a bigger modulus if it consists only of small prime factors:: sage: [d] = solve_mod([5*x + y == 3, 2*x - 3*y == 9], 3*5*7*11*19*23*29, solution_dict = True) sage: d[x] 12915279 sage: d[y] 8610183 For cases where there are relatively few solutions and the prime factors are small, this can be efficient even if the modulus itself is large:: sage: sorted(solve_mod([x^2 == 41], 10^20)) [(4538602480526452429,), (11445932736758703821,), (38554067263241296179,), (45461397519473547571,), (54538602480526452429,), (61445932736758703821,), (88554067263241296179,), (95461397519473547571,)] We solve a simple equation modulo 2:: sage: x,y = var('x,y') sage: solve_mod([x == y], 2) [(0, 0), (1, 1)] .. warning:: The current implementation splits the modulus into prime powers, then naively enumerates all possible solutions (starting modulo primes and then working up through prime powers), and finally combines the solution using the Chinese Remainder Theorem. The interface is good, but the algorithm is very inefficient if the modulus has some larger prime factors! Sage *does* have the ability to do something much faster in certain cases at least by using Groebner basis, linear algebra techniques, etc. But for a lot of toy problems this function as is might be useful. At least it establishes an interface. TESTS: Make sure that we short-circuit in at least some cases:: sage: solve_mod([2*x==1], 2*next_prime(10^50)) [] Try multi-equation cases:: sage: x, y, z = var("x y z") sage: solve_mod([2*x^2 + x*y, -x*y+2*y^2+x-2*y, -2*x^2+2*x*y-y^2-x-y], 12) [(0, 0), (4, 4), (0, 3), (4, 7)] sage: eqs = [-y^2+z^2, -x^2+y^2-3*z^2-z-1, -y*z-z^2-x-y+2, -x^2-12*z^2-y+z] sage: solve_mod(eqs, 11) [(8, 5, 6)] Confirm that modulus 1 now behaves as it should:: sage: x, y = var("x y") sage: solve_mod([x==1], 1) [(0,)] sage: solve_mod([2*x^2+x*y, -x*y+2*y^2+x-2*y, -2*x^2+2*x*y-y^2-x-y], 1) [(0, 0)] """ from sage.rings.all import Integer, Integers, crt_basis from sage.symbolic.expression import is_Expression from sage.misc.all import cartesian_product_iterator from sage.modules.all import vector from sage.matrix.all import matrix if not isinstance(eqns, (list, tuple)): eqns = [eqns] eqns = [eq if is_Expression(eq) else (eq.lhs() - eq.rhs()) for eq in eqns] modulus = Integer(modulus) if modulus < 1: raise ValueError("the modulus must be a positive integer") vars = list(set(sum([list(e.variables()) for e in eqns], []))) vars.sort(key=repr) if modulus == 1: # degenerate case ans = [tuple(Integers(1)(0) for v in vars)] return ans factors = modulus.factor() crt_basis = vector(Integers(modulus), crt_basis([p**i for p, i in factors])) solutions = [] has_solution = True for p, i in factors: solution = _solve_mod_prime_power(eqns, p, i, vars) if len(solution) > 0: solutions.append(solution) else: has_solution = False break ans = [] if has_solution: for solution in cartesian_product_iterator(solutions): solution_mat = matrix(Integers(modulus), solution) ans.append( tuple( c.dot_product(crt_basis) for c in solution_mat.columns())) # if solution_dict == True: # Relaxed form suggested by Mike Hansen (#8553): if solution_dict: sol_dict = [dict(zip(vars, solution)) for solution in ans] return sol_dict else: return ans
def solve_mod(eqns, modulus, solution_dict = False): r""" Return all solutions to an equation or list of equations modulo the given integer modulus. Each equation must involve only polynomials in 1 or many variables. By default the solutions are returned as `n`-tuples, where `n` is the number of variables appearing anywhere in the given equations. The variables are in alphabetical order. INPUT: - ``eqns`` - equation or list of equations - ``modulus`` - an integer - ``solution_dict`` - bool (default: False); if True or non-zero, return a list of dictionaries containing the solutions. If there are no solutions, return an empty list (rather than a list containing an empty dictionary). Likewise, if there's only a single solution, return a list containing one dictionary with that solution. EXAMPLES:: sage: var('x,y') (x, y) sage: solve_mod([x^2 + 2 == x, x^2 + y == y^2], 14) [(4, 2), (4, 6), (4, 9), (4, 13)] sage: solve_mod([x^2 == 1, 4*x == 11], 15) [(14,)] Fermat's equation modulo 3 with exponent 5:: sage: var('x,y,z') (x, y, z) sage: solve_mod([x^5 + y^5 == z^5], 3) [(0, 0, 0), (0, 1, 1), (0, 2, 2), (1, 0, 1), (1, 1, 2), (1, 2, 0), (2, 0, 2), (2, 1, 0), (2, 2, 1)] We can solve with respect to a bigger modulus if it consists only of small prime factors:: sage: [d] = solve_mod([5*x + y == 3, 2*x - 3*y == 9], 3*5*7*11*19*23*29, solution_dict = True) sage: d[x] 12915279 sage: d[y] 8610183 For cases where there are relatively few solutions and the prime factors are small, this can be efficient even if the modulus itself is large:: sage: sorted(solve_mod([x^2 == 41], 10^20)) [(4538602480526452429,), (11445932736758703821,), (38554067263241296179,), (45461397519473547571,), (54538602480526452429,), (61445932736758703821,), (88554067263241296179,), (95461397519473547571,)] We solve a simple equation modulo 2:: sage: x,y = var('x,y') sage: solve_mod([x == y], 2) [(0, 0), (1, 1)] .. warning:: The current implementation splits the modulus into prime powers, then naively enumerates all possible solutions (starting modulo primes and then working up through prime powers), and finally combines the solution using the Chinese Remainder Theorem. The interface is good, but the algorithm is very inefficient if the modulus has some larger prime factors! Sage *does* have the ability to do something much faster in certain cases at least by using Groebner basis, linear algebra techniques, etc. But for a lot of toy problems this function as is might be useful. At least it establishes an interface. TESTS: Make sure that we short-circuit in at least some cases:: sage: solve_mod([2*x==1], 2*next_prime(10^50)) [] Try multi-equation cases:: sage: x, y, z = var("x y z") sage: solve_mod([2*x^2 + x*y, -x*y+2*y^2+x-2*y, -2*x^2+2*x*y-y^2-x-y], 12) [(0, 0), (4, 4), (0, 3), (4, 7)] sage: eqs = [-y^2+z^2, -x^2+y^2-3*z^2-z-1, -y*z-z^2-x-y+2, -x^2-12*z^2-y+z] sage: solve_mod(eqs, 11) [(8, 5, 6)] Confirm that modulus 1 now behaves as it should:: sage: x, y = var("x y") sage: solve_mod([x==1], 1) [(0,)] sage: solve_mod([2*x^2+x*y, -x*y+2*y^2+x-2*y, -2*x^2+2*x*y-y^2-x-y], 1) [(0, 0)] """ from sage.rings.all import Integer, Integers, crt_basis from sage.symbolic.expression import is_Expression from sage.misc.all import cartesian_product_iterator from sage.modules.all import vector from sage.matrix.all import matrix if not isinstance(eqns, (list, tuple)): eqns = [eqns] eqns = [eq if is_Expression(eq) else (eq.lhs()-eq.rhs()) for eq in eqns] modulus = Integer(modulus) if modulus < 1: raise ValueError("the modulus must be a positive integer") vars = list(set(sum([list(e.variables()) for e in eqns], []))) vars.sort(key=repr) if modulus == 1: # degenerate case ans = [tuple(Integers(1)(0) for v in vars)] return ans factors = modulus.factor() crt_basis = vector(Integers(modulus), crt_basis([p**i for p,i in factors])) solutions = [] has_solution = True for p,i in factors: solution =_solve_mod_prime_power(eqns, p, i, vars) if len(solution) > 0: solutions.append(solution) else: has_solution = False break ans = [] if has_solution: for solution in cartesian_product_iterator(solutions): solution_mat = matrix(Integers(modulus), solution) ans.append(tuple(c.dot_product(crt_basis) for c in solution_mat.columns())) # if solution_dict == True: # Relaxed form suggested by Mike Hansen (#8553): if solution_dict: sol_dict = [dict(zip(vars, solution)) for solution in ans] return sol_dict else: return ans