def eliminate(polys, elim_variables, **kwds): r""" Compute a Groebner basis with respect to an elimination order defined by the given variables. INPUT: - ``polys`` -- an ideal or a polynomial sequence. - ``elim_variables`` -- the variables to eliminate. - ``force_elim`` -- integer (default: `1`). - ``kwds`` -- same as in :func:`groebner_basis`. OUTPUT: a Groebner basis of the elimination ideal. EXAMPLES:: sage: R.<x,y,t,s,z> = PolynomialRing(QQ,5) sage: I = R * [x-t,y-t^2,z-t^3,s-x+y^3] sage: import fgb_sage # optional - fgb_sage sage: gb = fgb_sage.eliminate(I, [t,s], verbosity=0) # optional - fgb_sage, random open simulation sage: gb # optional - fgb_sage [x^2 - y, x*y - z, y^2 - x*z] sage: gb.is_groebner() # optional - fgb_sage True sage: gb.ideal() == I.elimination_ideal([t,s]) # optional - fgb_sage True .. NOTE:: In some cases, this function fails to set the correct elimination order, see :trac:`24981`. This was fixed in Sage 8.7. """ kwds.setdefault('force_elim', 1) polyseq = PolynomialSequence(polys) ring = polyseq.ring() elim_variables = set(elim_variables) block1 = [x for x in ring.gens() if x in elim_variables] block2 = [x for x in ring.gens() if x not in elim_variables] from sage.rings.polynomial.term_order import TermOrder if len(block1) == 0 or len(block2) == 0: t = TermOrder("degrevlex", ring.ngens()) else: t = TermOrder("degrevlex", len(block1)) + TermOrder( "degrevlex", len(block2)) if t == ring.term_order() and set( ring.gens()[:len(block1)]) == elim_variables: return groebner_basis(polyseq, **kwds) else: block_ring = ring.change_ring(names=block1 + block2, order=t) gb = groebner_basis(PolynomialSequence(block_ring, polyseq), **kwds) return PolynomialSequence(ring, gb, immutable=True)
def _reduce(self,plist,A,B,max_degree): r""" Reduce all appearences of A with B in plist, up to a result of degree max_degree """ result = PS([],plist.ring()) for pol in plist: pNew = plist[0]-plist[0] # subs() is buggy in Polybori - otherwise, just use that if type(pol)!=BooleanPolynomial: pNew = pol.subs({A.monomials()[0]: B}) if pNew != plist[0]-plist[0] and pNew.degree()<=max_degree: result.append(pNew) elif pNew != plist[0]-plist[0]: result.append(pol) continue for monomial in pol.monomials(): reducable = False reducable = monomial.reducible_by(A.monomials()[0]) if reducable: monNew = 1 try: monNew = monomial/A.monomials()[0] except: for v in monomial.variables(): if v!=A.monomials()[0].variables()[0]: monNew = monNew * v pNew = pNew + monNew*B else: pNew = pNew + monomial if pNew != plist[0]-plist[0] and pNew.degree()<=max_degree: result.append(pNew) elif pNew != plist[0]-plist[0]: result.append(pol) return result
def small_roots(f, bounds, m=1, d=None): if not d: d = f.degree() R = f.base_ring() N = R.cardinality() f /= f.coefficients().pop(0) f = f.change_ring(ZZ) G = PolynomialSequence([], f.parent()) for i in range(m+1): power = (N ** (m-i)) * (f ** i) for shifts in itertools.product(range(d), repeat=f.nvariables()): g = power for variable, shift in zip(f.variables(), shifts): g *= variable ** shift G.append(g) B, monomials = G.coefficient_matrix() monomials = vector(monomials) factors = [monomial(*bounds) for monomial in monomials] for i, factor in enumerate(factors): B.rescale_col(i, factor) B = B.dense_matrix().LLL() B = B.change_ring(QQ) for i, factor in enumerate(factors): B.rescale_col(i, 1/factor) B = B.change_ring(ZZ) H = Sequence([], f.parent().change_ring(QQ)) for h in B*monomials: if h.is_zero(): continue H.append(h.change_ring(QQ)) I = H.ideal() if I.dimension() == -1: H.pop() elif I.dimension() == 0: V = I.variety(ring=ZZ) if V: roots = [] for root in V: root = map(R, map(root.__getitem__, f.variables())) roots.append(tuple(root)) return roots return []
def reverse_map(item): if isinstance(item, Ideal_generic): return Ideal([reverse_map(g) for g in item.gens()]) elif isinstance(item, Polynomial): return item.map_coefficients(morphism) elif isinstance(item, MPolynomial): return item.map_coefficients(morphism) elif is_PolynomialSequence(item): return PolynomialSequence(map(reverse_map, item), immutable=item.is_immutable()) elif isinstance(item, list): return list(map(reverse_map, item)) elif isinstance(item, tuple): return tuple(map(reverse_map, item)) elif isinstance(item, set): return set(map(reverse_map, list(item))) else: return item
def forward_map(item): if isinstance(item, Ideal_generic): return Ideal([forward_map(g) for g in item.gens()]) elif isinstance(item, Polynomial): return item.map_coefficients(elem_dict.__getitem__, new_base_ring=numfield) elif isinstance(item, MPolynomial): return item.map_coefficients(elem_dict.__getitem__, new_base_ring=numfield) elif is_PolynomialSequence(item): return PolynomialSequence(map(forward_map, item), immutable=item.is_immutable()) elif isinstance(item, list): return list(map(forward_map, item)) elif isinstance(item, dict): return {k: forward_map(v) for k, v in item.items()} elif isinstance(item, tuple): return tuple(map(forward_map, item)) elif isinstance(item, set): return set(map(forward_map, list(item))) else: return item
def learn(F, converter=None, solver=None, max_learnt_length=3, interreduction=False, **kwds): """ Learn new polynomials by running SAT-solver ``solver`` on SAT-instance produced by ``converter`` from ``F``. INPUT: - ``F`` - a sequence of Boolean polynomials - ``converter`` - an ANF to CNF converter class or object. If ``converter`` is ``None`` then :class:`sage.sat.converters.polybori.CNFEncoder` is used to construct a new converter. (default: ``None``) - ``solver`` - a SAT-solver class or object. If ``solver`` is ``None`` then :class:`sage.sat.solvers.cryptominisat.CryptoMiniSat` is used to construct a new converter. (default: ``None``) - ``max_learnt_length`` - only clauses of length <= ``max_length_learnt`` are considered and converted to polynomials. (default: ``3``) - ``interreduction`` - inter-reduce the resulting polynomials (default: ``False``) .. NOTE:: More parameters can be passed to the converter and the solver by prefixing them with ``c_`` and ``s_`` respectively. For example, to increase CryptoMiniSat's verbosity level, pass ``s_verbosity=1``. OUTPUT: A sequence of Boolean polynomials. EXAMPLES:: sage: from sage.sat.boolean_polynomials import learn as learn_sat # optional - cryptominisat We construct a simple system and solve it:: sage: set_random_seed(2300) # optional - cryptominisat sage: sr = mq.SR(1,2,2,4,gf2=True,polybori=True) # optional - cryptominisat sage: F,s = sr.polynomial_system() # optional - cryptominisat sage: H = learn_sat(F) # optional - cryptominisat sage: H[-1] # optional - cryptominisat k033 + 1 """ try: len(F) except AttributeError: F = F.gens() len(F) P = next(iter(F)).parent() K = P.base_ring() # instantiate the SAT solver if solver is None: from sage.sat.solvers.cryptominisat import CryptoMiniSat as solver solver_kwds = {} for k, v in six.iteritems(kwds): if k.startswith("s_"): solver_kwds[k[2:]] = v solver = solver(**solver_kwds) # instantiate the ANF to CNF converter if converter is None: from sage.sat.converters.polybori import CNFEncoder as converter converter_kwds = {} for k, v in six.iteritems(kwds): if k.startswith("c_"): converter_kwds[k[2:]] = v converter = converter(solver, P, **converter_kwds) phi = converter(F) rho = dict((phi[i], i) for i in range(len(phi))) s = solver() if s: learnt = [x + K(s[rho[x]]) for x in P.gens()] else: learnt = [] try: lc = solver.learnt_clauses() except (AttributeError, NotImplementedError): # solver does not support recovering learnt clauses lc = [] for c in lc: if len(c) <= max_learnt_length: try: learnt.append(converter.to_polynomial(c)) except (ValueError, NotImplementedError, AttributeError): # the solver might have learnt clauses that contain CNF # variables which have no correspondence to variables in our # polynomial ring (XOR chaining variables for example) pass learnt = PolynomialSequence(P, learnt) if interreduction: learnt = learnt.ideal().interreduced_basis() return learnt
def learn(F, converter=None, solver=None, max_learnt_length=3, interreduction=False, **kwds): """ Learn new polynomials by running SAT-solver ``solver`` on SAT-instance produced by ``converter`` from ``F``. INPUT: - ``F`` - a sequence of Boolean polynomials - ``converter`` - an ANF to CNF converter class or object. If ``converter`` is ``None`` then :class:`sage.sat.converters.polybori.CNFEncoder` is used to construct a new converter. (default: ``None``) - ``solver`` - a SAT-solver class or object. If ``solver`` is ``None`` then :class:`sage.sat.solvers.cryptominisat.CryptoMiniSat` is used to construct a new converter. (default: ``None``) - ``max_learnt_length`` - only clauses of length <= ``max_length_learnt`` are considered and converted to polynomials. (default: ``3``) - ``interreduction`` - inter-reduce the resulting polynomials (default: ``False``) .. NOTE:: More parameters can be passed to the converter and the solver by prefixing them with ``c_`` and ``s_`` respectively. For example, to increase CryptoMiniSat's verbosity level, pass ``s_verbosity=1``. OUTPUT: A sequence of Boolean polynomials. EXAMPLES:: sage: from sage.sat.boolean_polynomials import learn as learn_sat # optional - cryptominisat We construct a simple system and solve it:: sage: set_random_seed(2300) # optional - cryptominisat sage: sr = mq.SR(1,2,2,4,gf2=True,polybori=True) # optional - cryptominisat sage: F,s = sr.polynomial_system() # optional - cryptominisat sage: H = learn_sat(F) # optional - cryptominisat sage: H[-1] # optional - cryptominisat k033 + 1 """ try: len(F) except AttributeError: F = F.gens() len(F) P = next(iter(F)).parent() K = P.base_ring() # instantiate the SAT solver if solver is None: from sage.sat.solvers.cryptominisat import CryptoMiniSat as solver solver_kwds = {} for k, v in kwds.items(): if k.startswith("s_"): solver_kwds[k[2:]] = v solver = solver(**solver_kwds) # instantiate the ANF to CNF converter if converter is None: from sage.sat.converters.polybori import CNFEncoder as converter converter_kwds = {} for k, v in kwds.items(): if k.startswith("c_"): converter_kwds[k[2:]] = v converter = converter(solver, P, **converter_kwds) phi = converter(F) rho = dict((phi[i], i) for i in range(len(phi))) s = solver() if s: learnt = [x + K(s[rho[x]]) for x in P.gens()] else: learnt = [] try: lc = solver.learnt_clauses() except (AttributeError, NotImplementedError): # solver does not support recovering learnt clauses lc = [] for c in lc: if len(c) <= max_learnt_length: try: learnt.append(converter.to_polynomial(c)) except (ValueError, NotImplementedError, AttributeError): # the solver might have learnt clauses that contain CNF # variables which have no correspondence to variables in our # polynomial ring (XOR chaining variables for example) pass learnt = PolynomialSequence(P, learnt) if interreduction: learnt = learnt.ideal().interreduced_basis() return learnt
def solve(F, converter=None, solver=None, n=1, target_variables=None, **kwds): """ Solve system of Boolean polynomials ``F`` by solving the SAT-problem -- produced by ``converter`` -- using ``solver``. INPUT: - ``F`` - a sequence of Boolean polynomials - ``n`` - number of solutions to return. If ``n`` is +infinity then all solutions are returned. If ``n <infinity`` then ``n`` solutions are returned if ``F`` has at least ``n`` solutions. Otherwise, all solutions of ``F`` are returned. (default: ``1``) - ``converter`` - an ANF to CNF converter class or object. If ``converter`` is ``None`` then :class:`sage.sat.converters.polybori.CNFEncoder` is used to construct a new converter. (default: ``None``) - ``solver`` - a SAT-solver class or object. If ``solver`` is ``None`` then :class:`sage.sat.solvers.cryptominisat.CryptoMiniSat` is used to construct a new converter. (default: ``None``) - ``target_variables`` - a list of variables. The elements of the list are used to exclude a particular combination of variable assignments of a solution from any further solution. Furthermore ``target_variables`` denotes which variable-value pairs appear in the solutions. If ``target_variables`` is ``None`` all variables appearing in the polynomials of ``F`` are used to construct exclusion clauses. (default: ``None``) - ``**kwds`` - parameters can be passed to the converter and the solver by prefixing them with ``c_`` and ``s_`` respectively. For example, to increase CryptoMiniSat's verbosity level, pass ``s_verbosity=1``. OUTPUT: A list of dictionaries, each of which contains a variable assignment solving ``F``. EXAMPLES: We construct a very small-scale AES system of equations:: sage: sr = mq.SR(1,1,1,4,gf2=True,polybori=True) sage: while True: # workaround (see :trac:`31891`) ....: try: ....: F, s = sr.polynomial_system() ....: break ....: except ZeroDivisionError: ....: pass and pass it to a SAT solver:: sage: from sage.sat.boolean_polynomials import solve as solve_sat # optional - cryptominisat sage: s = solve_sat(F) # optional - cryptominisat sage: F.subs(s[0]) # optional - cryptominisat Polynomial Sequence with 36 Polynomials in 0 Variables This time we pass a few options through to the converter and the solver:: sage: s = solve_sat(F, s_verbosity=1, c_max_vars_sparse=4, c_cutting_number=8) # optional - cryptominisat c ... ... sage: F.subs(s[0]) # optional - cryptominisat Polynomial Sequence with 36 Polynomials in 0 Variables We construct a very simple system with three solutions and ask for a specific number of solutions:: sage: B.<a,b> = BooleanPolynomialRing() # optional - cryptominisat sage: f = a*b # optional - cryptominisat sage: l = solve_sat([f],n=1) # optional - cryptominisat sage: len(l) == 1, f.subs(l[0]) # optional - cryptominisat (True, 0) sage: l = solve_sat([a*b],n=2) # optional - cryptominisat sage: len(l) == 2, f.subs(l[0]), f.subs(l[1]) # optional - cryptominisat (True, 0, 0) sage: sorted((d[a], d[b]) for d in solve_sat([a*b],n=3)) # optional - cryptominisat [(0, 0), (0, 1), (1, 0)] sage: sorted((d[a], d[b]) for d in solve_sat([a*b],n=4)) # optional - cryptominisat [(0, 0), (0, 1), (1, 0)] sage: sorted((d[a], d[b]) for d in solve_sat([a*b],n=infinity)) # optional - cryptominisat [(0, 0), (0, 1), (1, 0)] In the next example we see how the ``target_variables`` parameter works:: sage: from sage.sat.boolean_polynomials import solve as solve_sat # optional - cryptominisat sage: R.<a,b,c,d> = BooleanPolynomialRing() # optional - cryptominisat sage: F = [a+b,a+c+d] # optional - cryptominisat First the normal use case:: sage: sorted((D[a], D[b], D[c], D[d]) for D in solve_sat(F,n=infinity)) # optional - cryptominisat [(0, 0, 0, 0), (0, 0, 1, 1), (1, 1, 0, 1), (1, 1, 1, 0)] Now we are only interested in the solutions of the variables a and b:: sage: solve_sat(F,n=infinity,target_variables=[a,b]) # optional - cryptominisat [{b: 0, a: 0}, {b: 1, a: 1}] Here, we generate and solve the cubic equations of the AES SBox (see :trac:`26676`):: sage: from sage.rings.polynomial.multi_polynomial_sequence import PolynomialSequence # optional - cryptominisat, long time sage: from sage.sat.boolean_polynomials import solve as solve_sat # optional - cryptominisat, long time sage: sr = sage.crypto.mq.SR(1, 4, 4, 8, allow_zero_inversions = True) # optional - cryptominisat, long time sage: sb = sr.sbox() # optional - cryptominisat, long time sage: eqs = sb.polynomials(degree = 3) # optional - cryptominisat, long time sage: eqs = PolynomialSequence(eqs) # optional - cryptominisat, long time sage: variables = map(str, eqs.variables()) # optional - cryptominisat, long time sage: variables = ",".join(variables) # optional - cryptominisat, long time sage: R = BooleanPolynomialRing(16, variables) # optional - cryptominisat, long time sage: eqs = [R(eq) for eq in eqs] # optional - cryptominisat, long time sage: sls_aes = solve_sat(eqs, n = infinity) # optional - cryptominisat, long time sage: len(sls_aes) # optional - cryptominisat, long time 256 TESTS: Test that :trac:`26676` is fixed:: sage: varl = ['k{0}'.format(p) for p in range(29)] sage: B = BooleanPolynomialRing(names = varl) sage: B.inject_variables(verbose=False) sage: keqs = [ ....: k0 + k6 + 1, ....: k3 + k9 + 1, ....: k5*k18 + k6*k18 + k7*k16 + k7*k10, ....: k9*k17 + k8*k24 + k11*k17, ....: k1*k13 + k1*k15 + k2*k12 + k3*k15 + k4*k14, ....: k5*k18 + k6*k16 + k7*k18, ....: k3 + k26, ....: k0 + k19, ....: k9 + k28, ....: k11 + k20] sage: from sage.sat.boolean_polynomials import solve as solve_sat sage: solve_sat(keqs, n=1, solver=SAT('cryptominisat')) # optional - cryptominisat [{k28: 0, k26: 1, k24: 0, k20: 0, k19: 0, k18: 0, k17: 0, k16: 0, k15: 0, k14: 0, k13: 0, k12: 0, k11: 0, k10: 0, k9: 0, k8: 0, k7: 0, k6: 1, k5: 0, k4: 0, k3: 1, k2: 0, k1: 0, k0: 0}] sage: solve_sat(keqs, n=1, solver=SAT('picosat')) # optional - pycosat [{k28: 0, k26: 1, k24: 0, k20: 0, k19: 0, k18: 0, k17: 0, k16: 0, k15: 0, k14: 0, k13: 1, k12: 1, k11: 0, k10: 0, k9: 0, k8: 0, k7: 0, k6: 1, k5: 0, k4: 1, k3: 1, k2: 1, k1: 1, k0: 0}] .. NOTE:: Although supported, passing converter and solver objects instead of classes is discouraged because these objects are stateful. """ assert (n > 0) try: len(F) except AttributeError: F = F.gens() len(F) P = next(iter(F)).parent() K = P.base_ring() if target_variables is None: target_variables = PolynomialSequence(F).variables() else: target_variables = PolynomialSequence(target_variables).variables() assert (set(target_variables).issubset(set(P.gens()))) # instantiate the SAT solver if solver is None: from sage.sat.solvers import CryptoMiniSat as solver if not isinstance(solver, SatSolver): solver_kwds = {} for k, v in kwds.items(): if k.startswith("s_"): solver_kwds[k[2:]] = v solver = solver(**solver_kwds) # instantiate the ANF to CNF converter if converter is None: from sage.sat.converters.polybori import CNFEncoder as converter if not isinstance(converter, ANF2CNFConverter): converter_kwds = {} for k, v in kwds.items(): if k.startswith("c_"): converter_kwds[k[2:]] = v converter = converter(solver, P, **converter_kwds) phi = converter(F) rho = dict((phi[i], i) for i in range(len(phi))) S = [] while True: s = solver() if s: S.append(dict((x, K(s[rho[x]])) for x in target_variables)) if n is not None and len(S) == n: break exclude_solution = tuple(-rho[x] if s[rho[x]] else rho[x] for x in target_variables) solver.add_clause(exclude_solution) else: try: learnt = solver.learnt_clauses(unitary_only=True) if learnt: S.append(dict((phi[abs(i) - 1], K(i < 0)) for i in learnt)) else: S.append(s) break except (AttributeError, NotImplementedError): # solver does not support recovering learnt clauses S.append(s) break if len(S) == 1: if S[0] is False: return False if S[0] is None: return None elif S[-1] is False: return S[0:-1] return S
def Sequence(x, universe=None, check=True, immutable=False, cr=False, cr_str=None, use_sage_types=False): """ A mutable list of elements with a common guaranteed universe, which can be set immutable. A universe is either an object that supports coercion (e.g., a parent), or a category. INPUT: - ``x`` - a list or tuple instance - ``universe`` - (default: None) the universe of elements; if None determined using canonical coercions and the entire list of elements. If list is empty, is category Objects() of all objects. - ``check`` -- (default: True) whether to coerce the elements of x into the universe - ``immutable`` - (default: True) whether or not this sequence is immutable - ``cr`` - (default: False) if True, then print a carriage return after each comma when printing this sequence. - ``cr_str`` - (default: False) if True, then print a carriage return after each comma when calling ``str()`` on this sequence. - ``use_sage_types`` -- (default: False) if True, coerce the built-in Python numerical types int, float, complex to the corresponding Sage types (this makes functions like vector() more flexible) OUTPUT: - a sequence EXAMPLES:: sage: v = Sequence(range(10)) sage: v.universe() <type 'int'> sage: v [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] We can request that the built-in Python numerical types be coerced to Sage objects:: sage: v = Sequence(range(10), use_sage_types=True) sage: v.universe() Integer Ring sage: v [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] You can also use seq for "Sequence", which is identical to using Sequence:: sage: v = seq([1,2,1/1]); v [1, 2, 1] sage: v.universe() Rational Field Note that assignment coerces if possible,:: sage: v = Sequence(range(10), ZZ) sage: a = QQ(5) sage: v[3] = a sage: parent(v[3]) Integer Ring sage: parent(a) Rational Field sage: v[3] = 2/3 Traceback (most recent call last): ... TypeError: no conversion of this rational to integer Sequences can be used absolutely anywhere lists or tuples can be used:: sage: isinstance(v, list) True Sequence can be immutable, so entries can't be changed:: sage: v = Sequence([1,2,3], immutable=True) sage: v.is_immutable() True sage: v[0] = 5 Traceback (most recent call last): ... ValueError: object is immutable; please change a copy instead. Only immutable sequences are hashable (unlike Python lists), though the hashing is potentially slow, since it first involves conversion of the sequence to a tuple, and returning the hash of that.:: sage: v = Sequence(range(10), ZZ, immutable=True) sage: hash(v) == hash(tuple(range(10))) True If you really know what you are doing, you can circumvent the type checking (for an efficiency gain):: sage: list.__setitem__(v, int(1), 2/3) # bad circumvention sage: v [0, 2/3, 2, 3, 4, 5, 6, 7, 8, 9] sage: list.__setitem__(v, int(1), int(2)) # not so bad circumvention You can make a sequence with a new universe from an old sequence.:: sage: w = Sequence(v, QQ) sage: w [0, 2, 2, 3, 4, 5, 6, 7, 8, 9] sage: w.universe() Rational Field sage: w[1] = 2/3 sage: w [0, 2/3, 2, 3, 4, 5, 6, 7, 8, 9] The default universe for any sequence, if no compatible parent structure can be found, is the universe of all Sage objects. This example illustrates how every element of a list is taken into account when constructing a sequence.:: sage: v = Sequence([1,7,6,GF(5)(3)]); v [1, 2, 1, 3] sage: v.universe() Finite Field of size 5 TESTS:: sage: Sequence(["a"], universe=ZZ) Traceback (most recent call last): ... TypeError: unable to convert a to an element of Integer Ring """ from sage.rings.polynomial.multi_polynomial_ideal import MPolynomialIdeal if isinstance(x, Sequence_generic) and universe is None: universe = x.universe() x = list(x) if isinstance(x, MPolynomialIdeal) and universe is None: universe = x.ring() x = x.gens() if universe is None: orig_x = x x = list( x) # make a copy even if x is a list, we're going to change it if len(x) == 0: from sage.categories.objects import Objects universe = Objects() else: import sage.structure.element if use_sage_types: # convert any Python built-in numerical types to Sage objects x = [sage.structure.coerce.py_scalar_to_element(e) for e in x] # start the pairwise coercion for i in range(len(x) - 1): try: x[i], x[i + 1] = sage.structure.element.canonical_coercion( x[i], x[i + 1]) except TypeError: from sage.categories.objects import Objects universe = Objects() x = list(orig_x) check = False # no point break if universe is None: # no type errors raised. universe = sage.structure.element.parent(x[len(x) - 1]) from sage.rings.polynomial.multi_polynomial_sequence import PolynomialSequence from sage.rings.polynomial.pbori.pbori import BooleanMonomialMonoid from sage.rings.polynomial.multi_polynomial_ring import is_MPolynomialRing from sage.rings.quotient_ring import is_QuotientRing if is_MPolynomialRing(universe) or isinstance( universe, BooleanMonomialMonoid) or (is_QuotientRing(universe) and is_MPolynomialRing( universe.cover_ring())): return PolynomialSequence(x, universe, immutable=immutable, cr=cr, cr_str=cr_str) else: return Sequence_generic(x, universe, check, immutable, cr, cr_str, use_sage_types)
def solve(F, converter=None, solver=None, n=1, target_variables=None, **kwds): """ Solve system of Boolean polynomials ``F`` by solving the SAT-problem -- produced by ``converter`` -- using ``solver``. INPUT: - ``F`` - a sequence of Boolean polynomials - ``n`` - number of solutions to return. If ``n`` is +infinity then all solutions are returned. If ``n <infinity`` then ``n`` solutions are returned if ``F`` has at least ``n`` solutions. Otherwise, all solutions of ``F`` are returned. (default: ``1``) - ``converter`` - an ANF to CNF converter class or object. If ``converter`` is ``None`` then :class:`sage.sat.converters.polybori.CNFEncoder` is used to construct a new converter. (default: ``None``) - ``solver`` - a SAT-solver class or object. If ``solver`` is ``None`` then :class:`sage.sat.solvers.cryptominisat.CryptoMiniSat` is used to construct a new converter. (default: ``None``) - ``target_variables`` - a list of variables. The elements of the list are used to exclude a particular combination of variable assignments of a solution from any further solution. Furthermore ``target_variables`` denotes which variable-value pairs appear in the solutions. If ``target_variables`` is ``None`` all variables appearing in the polynomials of ``F`` are used to construct exclusion clauses. (default: ``None``) - ``**kwds`` - parameters can be passed to the converter and the solver by prefixing them with ``c_`` and ``s_`` respectively. For example, to increase CryptoMiniSat's verbosity level, pass ``s_verbosity=1``. OUTPUT: A list of dictionaries, each of which contains a variable assignment solving ``F``. EXAMPLE: We construct a very small-scale AES system of equations:: sage: sr = mq.SR(1,1,1,4,gf2=True,polybori=True) sage: F,s = sr.polynomial_system() and pass it to a SAT solver:: sage: from sage.sat.boolean_polynomials import solve as solve_sat # optional - cryptominisat sage: s = solve_sat(F) # optional - cryptominisat sage: F.subs(s[0]) # optional - cryptominisat Polynomial Sequence with 36 Polynomials in 0 Variables This time we pass a few options through to the converter and the solver:: sage: s = solve_sat(F, s_verbosity=1, c_max_vars_sparse=4, c_cutting_number=8) # optional - cryptominisat c Flit... ... sage: F.subs(s[0]) # optional - cryptominisat Polynomial Sequence with 36 Polynomials in 0 Variables We construct a very simple system with three solutions and ask for a specific number of solutions:: sage: B.<a,b> = BooleanPolynomialRing() # optional - cryptominisat sage: f = a*b # optional - cryptominisat sage: l = solve_sat([f],n=1) # optional - cryptominisat sage: len(l) == 1, f.subs(l[0]) # optional - cryptominisat (True, 0) sage: l = sorted(solve_sat([a*b],n=2)) # optional - cryptominisat sage: len(l) == 2, f.subs(l[0]), f.subs(l[1]) # optional - cryptominisat (True, 0, 0) sage: sorted(solve_sat([a*b],n=3)) # optional - cryptominisat [{b: 0, a: 0}, {b: 0, a: 1}, {b: 1, a: 0}] sage: sorted(solve_sat([a*b],n=4)) # optional - cryptominisat [{b: 0, a: 0}, {b: 0, a: 1}, {b: 1, a: 0}] sage: sorted(solve_sat([a*b],n=infinity)) # optional - cryptominisat [{b: 0, a: 0}, {b: 0, a: 1}, {b: 1, a: 0}] In the next example we see how the ``target_variables`` parameter works:: sage: from sage.sat.boolean_polynomials import solve as solve_sat # optional - cryptominisat sage: R.<a,b,c,d> = BooleanPolynomialRing() # optional - cryptominisat sage: F = [a+b,a+c+d] # optional - cryptominisat First the normal use case:: sage: sorted(solve_sat(F,n=infinity)) # optional - cryptominisat [{d: 0, c: 0, b: 0, a: 0}, {d: 0, c: 1, b: 1, a: 1}, {d: 1, c: 0, b: 1, a: 1}, {d: 1, c: 1, b: 0, a: 0}] Now we are only interested in the solutions of the variables a and b:: sage: solve_sat(F,n=infinity,target_variables=[a,b]) # optional - cryptominisat [{b: 0, a: 0}, {b: 1, a: 1}] .. NOTE:: Although supported, passing converter and solver objects instead of classes is discouraged because these objects are stateful. """ assert (n > 0) try: len(F) except AttributeError: F = F.gens() len(F) P = next(iter(F)).parent() K = P.base_ring() if target_variables is None: target_variables = PolynomialSequence(F).variables() else: target_variables = PolynomialSequence(target_variables).variables() assert (set(target_variables).issubset(set(P.gens()))) # instantiate the SAT solver if solver is None: from sage.sat.solvers.cryptominisat import CryptoMiniSat as solver if not isinstance(solver, SatSolver): solver_kwds = {} for k, v in kwds.iteritems(): if k.startswith("s_"): solver_kwds[k[2:]] = v solver = solver(**solver_kwds) # instantiate the ANF to CNF converter if converter is None: from sage.sat.converters.polybori import CNFEncoder as converter if not isinstance(converter, ANF2CNFConverter): converter_kwds = {} for k, v in kwds.iteritems(): if k.startswith("c_"): converter_kwds[k[2:]] = v converter = converter(solver, P, **converter_kwds) phi = converter(F) rho = dict((phi[i], i) for i in range(len(phi))) S = [] while True: s = solver() if s: S.append(dict((x, K(s[rho[x]])) for x in target_variables)) if n is not None and len(S) == n: break exclude_solution = tuple(-rho[x] if s[rho[x]] else rho[x] for x in target_variables) solver.add_clause(exclude_solution) else: try: learnt = solver.learnt_clauses(unitary_only=True) if learnt: S.append(dict((phi[abs(i) - 1], K(i < 0)) for i in learnt)) else: S.append(s) break except (AttributeError, NotImplementedError): # solver does not support recovering learnt clauses S.append(s) break if len(S) == 1: if S[0] is False: return False if S[0] is None: return None elif S[-1] is False: return S[0:-1] return S
def learn(F, converter=None, solver=None, max_learnt_length=3, interreduction=False, **kwds): """ Learn new polynomials by running SAT-solver ``solver`` on SAT-instance produced by ``converter`` from ``F``. INPUT: - ``F`` - a sequence of Boolean polynomials - ``converter`` - an ANF to CNF converter class or object. If ``converter`` is ``None`` then :class:`sage.sat.converters.polybori.CNFEncoder` is used to construct a new converter. (default: ``None``) - ``solver`` - a SAT-solver class or object. If ``solver`` is ``None`` then :class:`sage.sat.solvers.cryptominisat.CryptoMiniSat` is used to construct a new converter. (default: ``None``) - ``max_learnt_length`` - only clauses of length <= ``max_length_learnt`` are considered and converted to polynomials. (default: ``3``) - ``interreduction`` - inter-reduce the resulting polynomials (default: ``False``) .. NOTE:: More parameters can be passed to the converter and the solver by prefixing them with ``c_`` and ``s_`` respectively. For example, to increase CryptoMiniSat's verbosity level, pass ``s_verbosity=1``. OUTPUT: A sequence of Boolean polynomials. EXAMPLE:: sage: from sage.sat.boolean_polynomials import learn as learn_sat # optional - cryptominisat We construct a simple system and solve it:: sage: set_random_seed(2300) # optional - cryptominisat sage: sr = mq.SR(1,2,2,4,gf2=True,polybori=True) # optional - cryptominisat sage: F,s = sr.polynomial_system() # optional - cryptominisat sage: H = learn_sat(F) # optional - cryptominisat sage: H[-1] # optional - cryptominisat k033 + 1 We construct a slightly larger equation system and recover some equations after 20 restarts:: sage: set_random_seed(2303) # optional - cryptominisat sage: sr = mq.SR(1,4,4,4,gf2=True,polybori=True) # optional - cryptominisat sage: F,s = sr.polynomial_system() # optional - cryptominisat sage: from sage.sat.boolean_polynomials import learn as learn_sat # optional - cryptominisat sage: H = learn_sat(F, s_maxrestarts=20, interreduction=True) # optional - cryptominisat sage: H[-1] # optional - cryptominisat, output random k001200*s031*x011201 + k001200*x011201 .. NOTE:: This function is meant to be called with some parameter such that the SAT-solver is interrupted. For CryptoMiniSat this is max_restarts, so pass 'c_max_restarts' to limit the number of restarts CryptoMiniSat will attempt. If no such parameter is passed, then this function behaves essentially like :func:`solve` except that this function does not support ``n>1``. TESTS: We test that :trac:`17351` is fixed, by checking that the following doctest does not raise an error:: sage: P.<a,b,c> = BooleanPolynomialRing() sage: F = [a*c + a + b*c + c + 1, a*b + a*c + a + c + 1, a*b + a*c + a + b*c + 1] sage: from sage.sat.boolean_polynomials import learn as learn_sat # optional - cryptominisat sage: learn_sat(F, s_maxrestarts=0, interreduction=True) # optional - cryptominisat [] """ try: len(F) except AttributeError: F = F.gens() len(F) P = next(iter(F)).parent() K = P.base_ring() # instantiate the SAT solver if solver is None: from sage.sat.solvers.cryptominisat import CryptoMiniSat as solver solver_kwds = {} for k, v in kwds.iteritems(): if k.startswith("s_"): solver_kwds[k[2:]] = v solver = solver(**solver_kwds) # instantiate the ANF to CNF converter if converter is None: from sage.sat.converters.polybori import CNFEncoder as converter converter_kwds = {} for k, v in kwds.iteritems(): if k.startswith("c_"): converter_kwds[k[2:]] = v converter = converter(solver, P, **converter_kwds) phi = converter(F) rho = dict((phi[i], i) for i in range(len(phi))) s = solver() if s: learnt = [x + K(s[rho[x]]) for x in P.gens()] else: learnt = [] for c in solver.learnt_clauses(): if len(c) <= max_learnt_length: try: learnt.append(converter.to_polynomial(c)) except (ValueError, NotImplementedError, AttributeError): # the solver might have learnt clauses that contain CNF # variables which have no correspondence to variables in our # polynomial ring (XOR chaining variables for example) pass learnt = PolynomialSequence(P, learnt) if interreduction: learnt = learnt.ideal().interreduced_basis() return learnt
def groebner_basis(gens, proba_epsilon=None, threads=None, prot=False, *args, **kwds): """ Computes a Groebner Basis of an ideal using giacpy_sage. The result is automatically converted to sage. INPUT: - ``gens`` - an ideal (or a list) of polynomials over a prime field of characteristic 0 or p<2^31 - ``proba_epsilon`` - (default: None) majoration of the probability of a wrong answer when probabilistic algorithms are allowed. * if ``proba_epsilon`` is None, the value of ``sage.structure.proof.all.polynomial()`` is taken. If it is false then the global ``giacpy_sage.giacsettings.proba_epsilon`` is used. * if ``proba_epsilon`` is 0, probabilistic algorithms are disabled. - ``threads`` - (default: None) Maximal number of threads allowed for giac. If None, the global ``giacpy_sage.giacsettings.threads`` is considered. - ``prot`` - (default: False) if True print detailled informations OUTPUT: Polynomial sequence of the reduced Groebner basis. EXAMPLES:: sage: from sage.libs.giac import groebner_basis as gb_giac # optional - giacpy_sage sage: P = PolynomialRing(GF(previous_prime(2**31)), 6, 'x') # optional - giacpy_sage sage: I = sage.rings.ideal.Cyclic(P) # optional - giacpy_sage sage: B=gb_giac(I.gens());B # optional - giacpy_sage <BLANKLINE> // Groebner basis computation time ... Polynomial Sequence with 45 Polynomials in 6 Variables sage: B.is_groebner() # optional - giacpy_sage True Computations over QQ can benefit from * a probabilistic lifting:: sage: P = PolynomialRing(QQ,5, 'x') # optional - giacpy_sage sage: I = ideal([P.random_element(3,7) for j in range(5)]) # optional - giacpy_sage sage: B1 = gb_giac(I.gens(),1e-16) # optional - giacpy_sage, long time (1s) Running a probabilistic check for the reconstructed Groebner basis. If successfull, error probability is less than 1e-16 ... sage: sage.structure.proof.all.polynomial(True) # optional - giacpy_sage sage: B2 = gb_giac(I.gens()) # optional - giacpy_sage, long time (4s) <BLANKLINE> // Groebner basis computation time... sage: B1==B2 # optional - giacpy_sage, long time True sage: B1.is_groebner() # optional - giacpy_sage, long time (20s) True * multi threaded operations:: sage: P = PolynomialRing(QQ, 8, 'x') # optional - giacpy_sage sage: I=sage.rings.ideal.Cyclic(P) # optional - giacpy_sage sage: time B = gb_giac(I.gens(),1e-6,threads=2) # doctest: +SKIP Running a probabilistic check for the reconstructed Groebner basis... Time: CPU 168.98 s, Wall: 94.13 s You can get detailled information by setting ``prot=True`` :: sage: I=sage.rings.ideal.Katsura(P) # optional - giacpy_sage sage: gb_giac(I,prot=True) # optional - giacpy_sage, random, long time (3s) 9381383 begin computing basis modulo 535718473 9381501 begin new iteration zmod, number of pairs: 8, base size: 8 ...end, basis size 74 prime number 1 G=Vector [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,... ...creating reconstruction #0 ... ++++++++basis size 74 checking pairs for i=0, j= checking pairs for i=1, j=2,6,12,17,19,24,29,34,39,42,43,48,56,61,64,69, ... checking pairs for i=72, j=73, checking pairs for i=73, j= Number of critical pairs to check 373 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++... Successfull check of 373 critical pairs 12380865 end final check Polynomial Sequence with 74 Polynomials in 8 Variables TESTS:: sage: from giacpy_sage import libgiac # optional - giacpy_sage sage: libgiac("x2:=22; x4:='whywouldyoudothis'") # optional - giacpy_sage 22,whywouldyoudothis sage: gb_giac(I) # optional - giacpy_sage Traceback (most recent call last): ... ValueError: Variables names ['x2', 'x4'] conflict in giac. Change them or purge them from in giac with libgiac.purge('x2') sage: libgiac.purge('x2'),libgiac.purge('x4') # optional - giacpy_sage (22, whywouldyoudothis) sage: gb_giac(I) # optional - giacpy_sage, long time (3s) <BLANKLINE> // Groebner basis computation time... Polynomial Sequence with 74 Polynomials in 8 Variables sage: I=ideal(P(0),P(0)) # optional - giacpy_sage sage: I.groebner_basis() == gb_giac(I) # optional - giacpy_sage True """ try: from giacpy_sage import libgiac, giacsettings except ImportError: raise ImportError( """One of the optional packages giac or giacpy_sage is missing""") try: iter(gens) except TypeError: gens = gens.gens() # get the ring from gens P = next(iter(gens)).parent() K = P.base_ring() p = K.characteristic() # check if the ideal is zero. (giac 1.2.0.19 segfault) from sage.rings.ideal import Ideal if (Ideal(gens)).is_zero(): return PolynomialSequence([P(0)], P, immutable=True) # check for name confusions blackgiacconstants = ['i', 'e'] # NB e^k is expanded to exp(k) blacklist = blackgiacconstants + [str(j) for j in libgiac.VARS()] problematicnames = list(set(P.gens_dict().keys()).intersection(blacklist)) if (len(problematicnames) > 0): raise ValueError( "Variables names %s conflict in giac. Change them or purge them from in giac with libgiac.purge(\'%s\')" % (problematicnames, problematicnames[0])) if K.is_prime_field() and p == 0: F = libgiac(gens) elif K.is_prime_field() and p < 2**31: F = (libgiac(gens) % p) else: raise NotImplementedError( "Only prime fields of cardinal < 2^31 are implemented in Giac for Groebner bases." ) if P.term_order() != "degrevlex": raise NotImplementedError( "Only degrevlex term orderings are supported in Giac Groebner bases." ) # proof or probabilistic reconstruction if proba_epsilon is None: if proof_polynomial(): giacsettings.proba_epsilon = 0 else: giacsettings.proba_epsilon = 1e-15 else: giacsettings.proba_epsilon = proba_epsilon # prot if prot: libgiac('debug_infolevel(2)') # threads if threads is not None: giacsettings.threads = threads # compute de groebner basis with giac gb_giac = F.gbasis([P.gens()], "revlex") return PolynomialSequence(gb_giac, P, immutable=True)
def learn(F, converter=None, solver=None, max_learnt_length=3, interreduction=False, **kwds): """ Learn new polynomials by running SAT-solver ``solver`` on SAT-instance produced by ``converter`` from ``F``. INPUT: - ``F`` - a sequence of Boolean polynomials - ``converter`` - an ANF to CNF converter class or object. If ``converter`` is ``None`` then :class:`sage.sat.converters.polybori.CNFEncoder` is used to construct a new converter. (default: ``None``) - ``solver`` - a SAT-solver class or object. If ``solver`` is ``None`` then :class:`sage.sat.solvers.cryptominisat.CryptoMiniSat` is used to construct a new converter. (default: ``None``) - ``max_learnt_length`` - only clauses of length <= ``max_length_learnt`` are considered and converted to polynomials. (default: ``3``) - ``interreduction`` - inter-reduce the resulting polynomials (default: ``False``) .. NOTE:: More parameters can be passed to the converter and the solver by prefixing them with ``c_`` and ``s_`` respectively. For example, to increase CryptoMiniSat's verbosity level, pass ``s_verbosity=1``. OUTPUT: A sequence of Boolean polynomials. EXAMPLE:: sage: from sage.sat.boolean_polynomials import learn as learn_sat # optional - cryptominisat We construct a simple system and solve it:: sage: set_random_seed(2300) # optional - cryptominisat sage: sr = mq.SR(1,2,2,4,gf2=True,polybori=True) # optional - cryptominisat sage: F,s = sr.polynomial_system() # optional - cryptominisat sage: H = learn_sat(F) # optional - cryptominisat sage: H[-1] # optional - cryptominisat k033 + 1 We construct a slightly larger equation system and recover some equations after 20 restarts:: sage: set_random_seed(2303) # optional - cryptominisat sage: sr = mq.SR(1,4,4,4,gf2=True,polybori=True) # optional - cryptominisat sage: F,s = sr.polynomial_system() # optional - cryptominisat sage: from sage.sat.boolean_polynomials import learn as learn_sat # optional - cryptominisat sage: H = learn_sat(F, s_maxrestarts=20, interreduction=True) # optional - cryptominisat sage: H[-1] # optional - cryptominisat, output random k001200*s031*x011201 + k001200*x011201 .. NOTE:: This function is meant to be called with some parameter such that the SAT-solver is interrupted. For CryptoMiniSat this is max_restarts, so pass 'c_max_restarts' to limit the number of restarts CryptoMiniSat will attempt. If no such parameter is passed, then this function behaves essentially like :func:`solve` except that this function does not support ``n>1``. TESTS: We test that :trac:`17351` is fixed, by checking that the following doctest does not raise an error:: sage: P.<a,b,c> = BooleanPolynomialRing() sage: F = [a*c + a + b*c + c + 1, a*b + a*c + a + c + 1, a*b + a*c + a + b*c + 1] sage: from sage.sat.boolean_polynomials import learn as learn_sat # optional - cryptominisat sage: learn_sat(F, s_maxrestarts=0, interreduction=True) # optional - cryptominisat [] """ try: len(F) except AttributeError: F = F.gens() len(F) P = next(iter(F)).parent() K = P.base_ring() # instantiate the SAT solver if solver is None: from sage.sat.solvers.cryptominisat import CryptoMiniSat as solver solver_kwds = {} for k, v in kwds.iteritems(): if k.startswith("s_"): solver_kwds[k[2:]] = v solver = solver(**solver_kwds) # instantiate the ANF to CNF converter if converter is None: from sage.sat.converters.polybori import CNFEncoder as converter converter_kwds = {} for k, v in kwds.iteritems(): if k.startswith("c_"): converter_kwds[k[2:]] = v converter = converter(solver, P, **converter_kwds) phi = converter(F) rho = dict((phi[i], i) for i in range(len(phi))) s = solver() if s: learnt = [x + K(s[rho[x]]) for x in P.gens()] else: learnt = [] for c in solver.learnt_clauses(): if len(c) <= max_learnt_length: try: learnt.append(converter.to_polynomial(c)) except (ValueError, NotImplementedError, AttributeError): # the solver might have learnt clauses that contain CNF # variables which have no correspondence to variables in our # polynomial ring (XOR chaining variables for example) pass learnt = PolynomialSequence(P, learnt) if interreduction: learnt = learnt.ideal().interreduced_basis() return learnt
def groebner_basis(polys, **kwds): r""" Compute a Groebner basis of an ideal using FGb. Supported term orders of the underlying polynomial ring are ``degrevlex`` orders, as well as block orders with two ``degrevlex`` blocks (elimination orders). Supported coefficient fields are QQ and finite prime fields of size up to ``MAX_PRIME`` `= 65521 < 2^16`. INPUT: - ``polys`` -- an ideal or a polynomial sequence, the generators of an ideal. - ``threads`` -- integer (default: `1`); only seems to work in positive characteristic. - ``force_elim`` -- integer (default: `0`); if ``force_elim=1``, then the computation will return only the result of the elimination, if an elimination order is used. - ``verbosity`` -- integer (default: `1`), display progress info. - ``matrix_bound`` -- integer (default: `500000`); this is is the maximal size of the matrices generated by F4. This value can be increased according to available memory. - ``max_base`` -- integer (default: `100000`); maximum number of polynomials in output. OUTPUT: the Groebner basis. EXAMPLES: This example computes a Groebner basis with respect to an elimination order:: sage: R = PolynomialRing(QQ, 5, 'x', order="degrevlex(2),degrevlex(3)") sage: I = sage.rings.ideal.Cyclic(R) sage: import fgb_sage # optional fgb_sage sage: gb = fgb_sage.groebner_basis(I) # optional fgb_sage, random ... sage: gb.is_groebner(), gb.ideal() == I # optional fgb_sage (True, True) Over finite fields, parallel computations are supported:: sage: R = PolynomialRing(GF(fgb_sage.MAX_PRIME), 4, 'x') # optional fgb_sage sage: I = sage.rings.ideal.Katsura(R) # optional fgb_sage sage: gb = fgb_sage.groebner_basis(I, threads=2, verbosity=0) # optional fgb_sage, random sage: gb.is_groebner(), gb.ideal() == I # optional fgb_sage (True, True) If :func:`fgb_sage.groebner_basis` is called with an ideal, the result is cached on :meth:`MPolynomialIdeal.groebner_basis` so that other computations on the ideal do not need to recompute a Groebner basis:: sage: I.groebner_basis.is_in_cache() # optional fgb_sage True sage: I.groebner_basis() is gb # optional fgb_sage True However, note that ``gb.ideal()`` returns a new ideal and, thus, does not have a Groebner basis in cache:: sage: gb.ideal().groebner_basis.is_in_cache() # optional fgb_sage False TESTS: Check that the result is not cached if it is only a basis of an elimination ideal:: sage: R = PolynomialRing(QQ, 5, 'x', order="degrevlex(2),degrevlex(3)") sage: I = sage.rings.ideal.Cyclic(R) sage: import fgb_sage sage: gb = fgb_sage.groebner_basis(I, force_elim=1) # optional fgb_sage, random ... sage: I.groebner_basis.is_in_cache() # optional fgb_sage False """ kwds.setdefault('force_elim', 0) kwds.setdefault('threads', 1) kwds.setdefault('matrix_bound', 500000) kwds.setdefault('verbosity', 1) kwds.setdefault('max_base', 100000) polyseq = PolynomialSequence(polys) ring = polyseq.ring() field = ring.base_ring() if not field.is_prime_field(): raise NotImplementedError("base ring must be QQ or finite prime field") if field.characteristic() > MAX_PRIME: raise NotImplementedError("maximum prime field size is %s" % MAX_PRIME) blocks = ring.term_order().blocks() if not (len(blocks) <= 2 and all(order.name() == 'degrevlex' for order in blocks)): raise NotImplementedError( "term order must be Degree-Reverse-Lexicographic block order with at most 2 blocks" ) n_elim_variables = len(blocks[0]) gb = None if field.characteristic() == 0: from ._fgb_sage_int import fgb_eliminate gb = fgb_eliminate(polyseq, n_elim_variables, **kwds) else: from ._fgb_sage_modp import fgb_eliminate gb = fgb_eliminate(polyseq, n_elim_variables, **kwds) from sage.rings.polynomial.multi_polynomial_ideal import MPolynomialIdeal if isinstance(polys, MPolynomialIdeal) and not kwds['force_elim']: if not polys.groebner_basis.is_in_cache(): polys.groebner_basis.set_cache(gb) return gb
def shrink_system(self,plist,keyvars,polybori=False,max_degree=0x7fffffff): r""" Shrinks the provided polynomial system through quadratic substitution INPUT:: - ``plist`` -- a PolynomialSequence containing the polynomials that are to be shrinked - ``keyvars`` -- a list of variables that should not be substituted - ``polybori`` (default False) whether polynomials sare in GF(2) and should be autoreduced (if exponents >1 are automatically 1) - ``max_degree`` (default 0x7fffffff) the degree that the resulting polynomials are allowed to have. If above, the substitution is reversed for this polynomial. OUTPUT:: A PolynomialSequence containing the shrinked polynomials EXAMPLES: sage: from sage.borderbasis.generator import BBGenerator sage: sr = mq.SR(2,1,1,4,gf2=True,polybori=False) sage: F,s = sr.polynomial_system() sage: gen = BBGenerator('none') sage: key_vars = F.ring().gens()[len(F.ring().gens())-4:len(F.ring().gens())] sage: G = gen.shrink_system(F,key_vars,polybori=True) sage: F Polynomial Sequence with 104 Polynomials in 36 Variables sage: G Polynomial Sequence with 48 Polynomials in 16 Variables """ # if we should autoreduce the system, we port it to BooleanPolynomial, where this stuff gets done automatically if polybori and type(plist[0])!=BooleanPolynomial: newRing = BooleanPolynomialRing(len(plist.ring().variable_names()),plist.ring().variable_names()) plistNew = PS([],newRing) for p in plist: pNew = BooleanPolynomial(newRing) for m in p.monomials(): mNew = BooleanPolynomial(newRing) + 1 for v in m.variables(): mNew = mNew * v pNew = pNew + m plistNew.append(pNew) plist = plistNew # now we shrink the system variables = plist.ring().gens() for v in variables: useful = True for kv in keyvars: if kv == v: useful = False if not useful: continue index = self._find_reduceable_index(plist,v) if index!=-1: try: for k in range(0,len(plist[index].monomials())): m = plist[index].monomials()[k] try: reducible = m.reducible_by(v.monomials()[0]) except: reducible = v.monomials()[0].divides(m) if(reducible): factor = plist[index].coefficients()[k] break B = plist[index]/factor B = v - B except: # when this is a boolean polynomial B = plist[index]+v plist = self._reduce(plist,v,B,max_degree) return plist
def groebner_basis(gens, proba_epsilon=None, threads=None, prot=False, elim_variables=None, *args, **kwds): """ Compute a Groebner Basis of an ideal using ``giacpy_sage``. The result is automatically converted to sage. Supported term orders of the underlying polynomial ring are ``lex``, ``deglex``, ``degrevlex`` and block orders with 2 ``degrevlex`` blocks. INPUT: - ``gens`` - an ideal (or a list) of polynomials over a prime field of characteristic 0 or p<2^31 - ``proba_epsilon`` - (default: None) majoration of the probability of a wrong answer when probabilistic algorithms are allowed. * if ``proba_epsilon`` is None, the value of ``sage.structure.proof.all.polynomial()`` is taken. If it is false then the global ``giacpy_sage.giacsettings.proba_epsilon`` is used. * if ``proba_epsilon`` is 0, probabilistic algorithms are disabled. - ``threads`` - (default: None) Maximal number of threads allowed for giac. If None, the global ``giacpy_sage.giacsettings.threads`` is considered. - ``prot`` - (default: False) if True print detailled informations - ``elim_variables`` - (default: None) a list of variables to eliminate from the ideal. * if ``elim_variables`` is None, a Groebner basis with respect to the term ordering of the parent polynomial ring of the polynomials ``gens`` is computed. * if ``elim_variables`` is a list of variables, a Groebner basis of the elimination ideal with respect to a ``degrevlex`` term order is computed, regardless of the term order of the polynomial ring. OUTPUT: Polynomial sequence of the reduced Groebner basis. EXAMPLES:: sage: from sage.libs.giac import groebner_basis as gb_giac sage: P = PolynomialRing(GF(previous_prime(2**31)), 6, 'x') sage: I = sage.rings.ideal.Cyclic(P) sage: B=gb_giac(I.gens());B <BLANKLINE> // Groebner basis computation time ... Polynomial Sequence with 45 Polynomials in 6 Variables sage: B.is_groebner() True Elimination ideals can be computed by passing ``elim_variables``:: sage: P = PolynomialRing(GF(previous_prime(2**31)), 5, 'x') sage: I = sage.rings.ideal.Cyclic(P) sage: B = gb_giac(I.gens(), elim_variables=[P.gen(0), P.gen(2)]) <BLANKLINE> // Groebner basis computation time ... sage: B.is_groebner() True sage: B.ideal() == I.elimination_ideal([P.gen(0), P.gen(2)]) True Computations over QQ can benefit from * a probabilistic lifting:: sage: P = PolynomialRing(QQ,5, 'x') sage: I = ideal([P.random_element(3,7) for j in range(5)]) sage: B1 = gb_giac(I.gens(),1e-16) # long time (1s) ... sage: sage.structure.proof.all.polynomial(True) sage: B2 = gb_giac(I.gens()) # long time (4s) <BLANKLINE> // Groebner basis computation time... sage: B1 == B2 # long time True sage: B1.is_groebner() # long time (20s) True * multi threaded operations:: sage: P = PolynomialRing(QQ, 8, 'x') sage: I = sage.rings.ideal.Cyclic(P) sage: time B = gb_giac(I.gens(),1e-6,threads=2) # doctest: +SKIP ... Time: CPU 168.98 s, Wall: 94.13 s You can get detailled information by setting ``prot=True`` :: sage: I = sage.rings.ideal.Katsura(P) sage: gb_giac(I,prot=True) # random, long time (3s) 9381383 begin computing basis modulo 535718473 9381501 begin new iteration zmod, number of pairs: 8, base size: 8 ...end, basis size 74 prime number 1 G=Vector [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,... ...creating reconstruction #0 ... ++++++++basis size 74 checking pairs for i=0, j= checking pairs for i=1, j=2,6,12,17,19,24,29,34,39,42,43,48,56,61,64,69, ... checking pairs for i=72, j=73, checking pairs for i=73, j= Number of critical pairs to check 373 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++... Successful... check of 373 critical pairs 12380865 end final check Polynomial Sequence with 74 Polynomials in 8 Variables TESTS:: sage: from sage.libs.giac.giac import libgiac sage: libgiac("x2:=22; x4:='whywouldyoudothis'") 22,whywouldyoudothis sage: gb_giac(I) Traceback (most recent call last): ... ValueError: Variables names ['x2', 'x4'] conflict in giac. Change them or purge them from in giac with libgiac.purge('x2') sage: libgiac.purge('x2'),libgiac.purge('x4') (22, whywouldyoudothis) sage: gb_giac(I) # long time (3s) <BLANKLINE> // Groebner basis computation time... Polynomial Sequence with 74 Polynomials in 8 Variables sage: I = ideal(P(0),P(0)) sage: I.groebner_basis() == gb_giac(I) True Test the supported term orderings:: sage: from sage.rings.ideal import Cyclic sage: P = PolynomialRing(QQ, 'x', 4, order='lex') sage: B = gb_giac(Cyclic(P)) ... sage: B.is_groebner(), B.ideal() == Cyclic(P) (True, True) sage: P = P.change_ring(order='deglex') sage: B = gb_giac(Cyclic(P)) ... sage: B.is_groebner(), B.ideal() == Cyclic(P) (True, True) sage: P = P.change_ring(order='degrevlex(2),degrevlex(2)') sage: B = gb_giac(Cyclic(P)) ... sage: B.is_groebner(), B.ideal() == Cyclic(P) (True, True) """ try: iter(gens) except TypeError: gens = gens.gens() # get the ring from gens P = next(iter(gens)).parent() K = P.base_ring() p = K.characteristic() # check if the ideal is zero. (giac 1.2.0.19 segfault) from sage.rings.ideal import Ideal if (Ideal(gens)).is_zero(): return PolynomialSequence([P(0)], P, immutable=True) # check for name confusions blackgiacconstants = ['i', 'e'] # NB e^k is expanded to exp(k) blacklist = blackgiacconstants + [str(j) for j in libgiac.VARS()] problematicnames = sorted(set(P.gens_dict()).intersection(blacklist)) if problematicnames: raise ValueError( "Variables names %s conflict in giac. Change them or purge them from in giac with libgiac.purge(\'%s\')" % (problematicnames, problematicnames[0])) if K.is_prime_field() and p == 0: F = libgiac(gens) elif K.is_prime_field() and p < 2**31: F = (libgiac(gens) % p) else: raise NotImplementedError( "Only prime fields of cardinal < 2^31 are implemented in Giac for Groebner bases." ) # proof or probabilistic reconstruction if proba_epsilon is None: if proof_polynomial(): giacsettings.proba_epsilon = 0 else: giacsettings.proba_epsilon = 1e-15 else: giacsettings.proba_epsilon = proba_epsilon # prot if prot: libgiac('debug_infolevel(2)') # threads if threads is not None: giacsettings.threads = threads if elim_variables is None: var_names = P.variable_names() order_name = P.term_order().name() if order_name == "degrevlex": giac_order = "revlex" elif order_name == "lex": giac_order = "plex" elif order_name == "deglex": giac_order = "tdeg" else: blocks = P.term_order().blocks() if (len(blocks) == 2 and all(order.name() == "degrevlex" for order in blocks)): giac_order = "revlex" var_names = var_names[:len(blocks[0])] else: raise NotImplementedError( "%s is not a supported term order in " "Giac Groebner bases." % P.term_order()) # compute de groebner basis with giac gb_giac = F.gbasis(list(var_names), giac_order) else: gb_giac = F.eliminate(list(elim_variables), 'gbasis') return PolynomialSequence(gb_giac, P, immutable=True)