Coverage for /usr/lib/python3/dist-packages/sympy/polys/groebnertools.py: 9%

374 statements  

« prev     ^ index     » next       coverage.py v7.9.1, created at 2025-06-14 15:55 +0200

1"""Groebner bases algorithms. """ 

2 

3 

4from sympy.core.symbol import Dummy 

5from sympy.polys.monomials import monomial_mul, monomial_lcm, monomial_divides, term_div 

6from sympy.polys.orderings import lex 

7from sympy.polys.polyerrors import DomainError 

8from sympy.polys.polyconfig import query 

9 

10def groebner(seq, ring, method=None): 

11 """ 

12 Computes Groebner basis for a set of polynomials in `K[X]`. 

13 

14 Wrapper around the (default) improved Buchberger and the other algorithms 

15 for computing Groebner bases. The choice of algorithm can be changed via 

16 ``method`` argument or :func:`sympy.polys.polyconfig.setup`, where 

17 ``method`` can be either ``buchberger`` or ``f5b``. 

18 

19 """ 

20 if method is None: 

21 method = query('groebner') 

22 

23 _groebner_methods = { 

24 'buchberger': _buchberger, 

25 'f5b': _f5b, 

26 } 

27 

28 try: 

29 _groebner = _groebner_methods[method] 

30 except KeyError: 

31 raise ValueError("'%s' is not a valid Groebner bases algorithm (valid are 'buchberger' and 'f5b')" % method) 

32 

33 domain, orig = ring.domain, None 

34 

35 if not domain.is_Field or not domain.has_assoc_Field: 

36 try: 

37 orig, ring = ring, ring.clone(domain=domain.get_field()) 

38 except DomainError: 

39 raise DomainError("Cannot compute a Groebner basis over %s" % domain) 

40 else: 

41 seq = [ s.set_ring(ring) for s in seq ] 

42 

43 G = _groebner(seq, ring) 

44 

45 if orig is not None: 

46 G = [ g.clear_denoms()[1].set_ring(orig) for g in G ] 

47 

48 return G 

49 

50def _buchberger(f, ring): 

51 """ 

52 Computes Groebner basis for a set of polynomials in `K[X]`. 

53 

54 Given a set of multivariate polynomials `F`, finds another 

55 set `G`, such that Ideal `F = Ideal G` and `G` is a reduced 

56 Groebner basis. 

57 

58 The resulting basis is unique and has monic generators if the 

59 ground domains is a field. Otherwise the result is non-unique 

60 but Groebner bases over e.g. integers can be computed (if the 

61 input polynomials are monic). 

62 

63 Groebner bases can be used to choose specific generators for a 

64 polynomial ideal. Because these bases are unique you can check 

65 for ideal equality by comparing the Groebner bases. To see if 

66 one polynomial lies in an ideal, divide by the elements in the 

67 base and see if the remainder vanishes. 

68 

69 They can also be used to solve systems of polynomial equations 

70 as, by choosing lexicographic ordering, you can eliminate one 

71 variable at a time, provided that the ideal is zero-dimensional 

72 (finite number of solutions). 

73 

74 Notes 

75 ===== 

76 

77 Algorithm used: an improved version of Buchberger's algorithm 

78 as presented in T. Becker, V. Weispfenning, Groebner Bases: A 

79 Computational Approach to Commutative Algebra, Springer, 1993, 

80 page 232. 

81 

82 References 

83 ========== 

84 

85 .. [1] [Bose03]_ 

86 .. [2] [Giovini91]_ 

87 .. [3] [Ajwa95]_ 

88 .. [4] [Cox97]_ 

89 

90 """ 

91 order = ring.order 

92 

93 monomial_mul = ring.monomial_mul 

94 monomial_div = ring.monomial_div 

95 monomial_lcm = ring.monomial_lcm 

96 

97 def select(P): 

98 # normal selection strategy 

99 # select the pair with minimum LCM(LM(f), LM(g)) 

100 pr = min(P, key=lambda pair: order(monomial_lcm(f[pair[0]].LM, f[pair[1]].LM))) 

101 return pr 

102 

103 def normal(g, J): 

104 h = g.rem([ f[j] for j in J ]) 

105 

106 if not h: 

107 return None 

108 else: 

109 h = h.monic() 

110 

111 if h not in I: 

112 I[h] = len(f) 

113 f.append(h) 

114 

115 return h.LM, I[h] 

116 

117 def update(G, B, ih): 

118 # update G using the set of critical pairs B and h 

119 # [BW] page 230 

120 h = f[ih] 

121 mh = h.LM 

122 

123 # filter new pairs (h, g), g in G 

124 C = G.copy() 

125 D = set() 

126 

127 while C: 

128 # select a pair (h, g) by popping an element from C 

129 ig = C.pop() 

130 g = f[ig] 

131 mg = g.LM 

132 LCMhg = monomial_lcm(mh, mg) 

133 

134 def lcm_divides(ip): 

135 # LCM(LM(h), LM(p)) divides LCM(LM(h), LM(g)) 

136 m = monomial_lcm(mh, f[ip].LM) 

137 return monomial_div(LCMhg, m) 

138 

139 # HT(h) and HT(g) disjoint: mh*mg == LCMhg 

140 if monomial_mul(mh, mg) == LCMhg or ( 

141 not any(lcm_divides(ipx) for ipx in C) and 

142 not any(lcm_divides(pr[1]) for pr in D)): 

143 D.add((ih, ig)) 

144 

145 E = set() 

146 

147 while D: 

148 # select h, g from D (h the same as above) 

149 ih, ig = D.pop() 

150 mg = f[ig].LM 

151 LCMhg = monomial_lcm(mh, mg) 

152 

153 if not monomial_mul(mh, mg) == LCMhg: 

154 E.add((ih, ig)) 

155 

156 # filter old pairs 

157 B_new = set() 

158 

159 while B: 

160 # select g1, g2 from B (-> CP) 

161 ig1, ig2 = B.pop() 

162 mg1 = f[ig1].LM 

163 mg2 = f[ig2].LM 

164 LCM12 = monomial_lcm(mg1, mg2) 

165 

166 # if HT(h) does not divide lcm(HT(g1), HT(g2)) 

167 if not monomial_div(LCM12, mh) or \ 

168 monomial_lcm(mg1, mh) == LCM12 or \ 

169 monomial_lcm(mg2, mh) == LCM12: 

170 B_new.add((ig1, ig2)) 

171 

172 B_new |= E 

173 

174 # filter polynomials 

175 G_new = set() 

176 

177 while G: 

178 ig = G.pop() 

179 mg = f[ig].LM 

180 

181 if not monomial_div(mg, mh): 

182 G_new.add(ig) 

183 

184 G_new.add(ih) 

185 

186 return G_new, B_new 

187 # end of update ################################ 

188 

189 if not f: 

190 return [] 

191 

192 # replace f with a reduced list of initial polynomials; see [BW] page 203 

193 f1 = f[:] 

194 

195 while True: 

196 f = f1[:] 

197 f1 = [] 

198 

199 for i in range(len(f)): 

200 p = f[i] 

201 r = p.rem(f[:i]) 

202 

203 if r: 

204 f1.append(r.monic()) 

205 

206 if f == f1: 

207 break 

208 

209 I = {} # ip = I[p]; p = f[ip] 

210 F = set() # set of indices of polynomials 

211 G = set() # set of indices of intermediate would-be Groebner basis 

212 CP = set() # set of pairs of indices of critical pairs 

213 

214 for i, h in enumerate(f): 

215 I[h] = i 

216 F.add(i) 

217 

218 ##################################### 

219 # algorithm GROEBNERNEWS2 in [BW] page 232 

220 

221 while F: 

222 # select p with minimum monomial according to the monomial ordering 

223 h = min([f[x] for x in F], key=lambda f: order(f.LM)) 

224 ih = I[h] 

225 F.remove(ih) 

226 G, CP = update(G, CP, ih) 

227 

228 # count the number of critical pairs which reduce to zero 

229 reductions_to_zero = 0 

230 

231 while CP: 

232 ig1, ig2 = select(CP) 

233 CP.remove((ig1, ig2)) 

234 

235 h = spoly(f[ig1], f[ig2], ring) 

236 # ordering divisors is on average more efficient [Cox] page 111 

237 G1 = sorted(G, key=lambda g: order(f[g].LM)) 

238 ht = normal(h, G1) 

239 

240 if ht: 

241 G, CP = update(G, CP, ht[1]) 

242 else: 

243 reductions_to_zero += 1 

244 

245 ###################################### 

246 # now G is a Groebner basis; reduce it 

247 Gr = set() 

248 

249 for ig in G: 

250 ht = normal(f[ig], G - {ig}) 

251 

252 if ht: 

253 Gr.add(ht[1]) 

254 

255 Gr = [f[ig] for ig in Gr] 

256 

257 # order according to the monomial ordering 

258 Gr = sorted(Gr, key=lambda f: order(f.LM), reverse=True) 

259 

260 return Gr 

261 

262def spoly(p1, p2, ring): 

263 """ 

264 Compute LCM(LM(p1), LM(p2))/LM(p1)*p1 - LCM(LM(p1), LM(p2))/LM(p2)*p2 

265 This is the S-poly provided p1 and p2 are monic 

266 """ 

267 LM1 = p1.LM 

268 LM2 = p2.LM 

269 LCM12 = ring.monomial_lcm(LM1, LM2) 

270 m1 = ring.monomial_div(LCM12, LM1) 

271 m2 = ring.monomial_div(LCM12, LM2) 

272 s1 = p1.mul_monom(m1) 

273 s2 = p2.mul_monom(m2) 

274 s = s1 - s2 

275 return s 

276 

277# F5B 

278 

279# convenience functions 

280 

281 

282def Sign(f): 

283 return f[0] 

284 

285 

286def Polyn(f): 

287 return f[1] 

288 

289 

290def Num(f): 

291 return f[2] 

292 

293 

294def sig(monomial, index): 

295 return (monomial, index) 

296 

297 

298def lbp(signature, polynomial, number): 

299 return (signature, polynomial, number) 

300 

301# signature functions 

302 

303 

304def sig_cmp(u, v, order): 

305 """ 

306 Compare two signatures by extending the term order to K[X]^n. 

307 

308 u < v iff 

309 - the index of v is greater than the index of u 

310 or 

311 - the index of v is equal to the index of u and u[0] < v[0] w.r.t. order 

312 

313 u > v otherwise 

314 """ 

315 if u[1] > v[1]: 

316 return -1 

317 if u[1] == v[1]: 

318 #if u[0] == v[0]: 

319 # return 0 

320 if order(u[0]) < order(v[0]): 

321 return -1 

322 return 1 

323 

324 

325def sig_key(s, order): 

326 """ 

327 Key for comparing two signatures. 

328 

329 s = (m, k), t = (n, l) 

330 

331 s < t iff [k > l] or [k == l and m < n] 

332 s > t otherwise 

333 """ 

334 return (-s[1], order(s[0])) 

335 

336 

337def sig_mult(s, m): 

338 """ 

339 Multiply a signature by a monomial. 

340 

341 The product of a signature (m, i) and a monomial n is defined as 

342 (m * t, i). 

343 """ 

344 return sig(monomial_mul(s[0], m), s[1]) 

345 

346# labeled polynomial functions 

347 

348 

349def lbp_sub(f, g): 

350 """ 

351 Subtract labeled polynomial g from f. 

352 

353 The signature and number of the difference of f and g are signature 

354 and number of the maximum of f and g, w.r.t. lbp_cmp. 

355 """ 

356 if sig_cmp(Sign(f), Sign(g), Polyn(f).ring.order) < 0: 

357 max_poly = g 

358 else: 

359 max_poly = f 

360 

361 ret = Polyn(f) - Polyn(g) 

362 

363 return lbp(Sign(max_poly), ret, Num(max_poly)) 

364 

365 

366def lbp_mul_term(f, cx): 

367 """ 

368 Multiply a labeled polynomial with a term. 

369 

370 The product of a labeled polynomial (s, p, k) by a monomial is 

371 defined as (m * s, m * p, k). 

372 """ 

373 return lbp(sig_mult(Sign(f), cx[0]), Polyn(f).mul_term(cx), Num(f)) 

374 

375 

376def lbp_cmp(f, g): 

377 """ 

378 Compare two labeled polynomials. 

379 

380 f < g iff 

381 - Sign(f) < Sign(g) 

382 or 

383 - Sign(f) == Sign(g) and Num(f) > Num(g) 

384 

385 f > g otherwise 

386 """ 

387 if sig_cmp(Sign(f), Sign(g), Polyn(f).ring.order) == -1: 

388 return -1 

389 if Sign(f) == Sign(g): 

390 if Num(f) > Num(g): 

391 return -1 

392 #if Num(f) == Num(g): 

393 # return 0 

394 return 1 

395 

396 

397def lbp_key(f): 

398 """ 

399 Key for comparing two labeled polynomials. 

400 """ 

401 return (sig_key(Sign(f), Polyn(f).ring.order), -Num(f)) 

402 

403# algorithm and helper functions 

404 

405 

406def critical_pair(f, g, ring): 

407 """ 

408 Compute the critical pair corresponding to two labeled polynomials. 

409 

410 A critical pair is a tuple (um, f, vm, g), where um and vm are 

411 terms such that um * f - vm * g is the S-polynomial of f and g (so, 

412 wlog assume um * f > vm * g). 

413 For performance sake, a critical pair is represented as a tuple 

414 (Sign(um * f), um, f, Sign(vm * g), vm, g), since um * f creates 

415 a new, relatively expensive object in memory, whereas Sign(um * 

416 f) and um are lightweight and f (in the tuple) is a reference to 

417 an already existing object in memory. 

418 """ 

419 domain = ring.domain 

420 

421 ltf = Polyn(f).LT 

422 ltg = Polyn(g).LT 

423 lt = (monomial_lcm(ltf[0], ltg[0]), domain.one) 

424 

425 um = term_div(lt, ltf, domain) 

426 vm = term_div(lt, ltg, domain) 

427 

428 # The full information is not needed (now), so only the product 

429 # with the leading term is considered: 

430 fr = lbp_mul_term(lbp(Sign(f), Polyn(f).leading_term(), Num(f)), um) 

431 gr = lbp_mul_term(lbp(Sign(g), Polyn(g).leading_term(), Num(g)), vm) 

432 

433 # return in proper order, such that the S-polynomial is just 

434 # u_first * f_first - u_second * f_second: 

435 if lbp_cmp(fr, gr) == -1: 

436 return (Sign(gr), vm, g, Sign(fr), um, f) 

437 else: 

438 return (Sign(fr), um, f, Sign(gr), vm, g) 

439 

440 

441def cp_cmp(c, d): 

442 """ 

443 Compare two critical pairs c and d. 

444 

445 c < d iff 

446 - lbp(c[0], _, Num(c[2]) < lbp(d[0], _, Num(d[2])) (this 

447 corresponds to um_c * f_c and um_d * f_d) 

448 or 

449 - lbp(c[0], _, Num(c[2]) >< lbp(d[0], _, Num(d[2])) and 

450 lbp(c[3], _, Num(c[5])) < lbp(d[3], _, Num(d[5])) (this 

451 corresponds to vm_c * g_c and vm_d * g_d) 

452 

453 c > d otherwise 

454 """ 

455 zero = Polyn(c[2]).ring.zero 

456 

457 c0 = lbp(c[0], zero, Num(c[2])) 

458 d0 = lbp(d[0], zero, Num(d[2])) 

459 

460 r = lbp_cmp(c0, d0) 

461 

462 if r == -1: 

463 return -1 

464 if r == 0: 

465 c1 = lbp(c[3], zero, Num(c[5])) 

466 d1 = lbp(d[3], zero, Num(d[5])) 

467 

468 r = lbp_cmp(c1, d1) 

469 

470 if r == -1: 

471 return -1 

472 #if r == 0: 

473 # return 0 

474 return 1 

475 

476 

477def cp_key(c, ring): 

478 """ 

479 Key for comparing critical pairs. 

480 """ 

481 return (lbp_key(lbp(c[0], ring.zero, Num(c[2]))), lbp_key(lbp(c[3], ring.zero, Num(c[5])))) 

482 

483 

484def s_poly(cp): 

485 """ 

486 Compute the S-polynomial of a critical pair. 

487 

488 The S-polynomial of a critical pair cp is cp[1] * cp[2] - cp[4] * cp[5]. 

489 """ 

490 return lbp_sub(lbp_mul_term(cp[2], cp[1]), lbp_mul_term(cp[5], cp[4])) 

491 

492 

493def is_rewritable_or_comparable(sign, num, B): 

494 """ 

495 Check if a labeled polynomial is redundant by checking if its 

496 signature and number imply rewritability or comparability. 

497 

498 (sign, num) is comparable if there exists a labeled polynomial 

499 h in B, such that sign[1] (the index) is less than Sign(h)[1] 

500 and sign[0] is divisible by the leading monomial of h. 

501 

502 (sign, num) is rewritable if there exists a labeled polynomial 

503 h in B, such thatsign[1] is equal to Sign(h)[1], num < Num(h) 

504 and sign[0] is divisible by Sign(h)[0]. 

505 """ 

506 for h in B: 

507 # comparable 

508 if sign[1] < Sign(h)[1]: 

509 if monomial_divides(Polyn(h).LM, sign[0]): 

510 return True 

511 

512 # rewritable 

513 if sign[1] == Sign(h)[1]: 

514 if num < Num(h): 

515 if monomial_divides(Sign(h)[0], sign[0]): 

516 return True 

517 return False 

518 

519 

520def f5_reduce(f, B): 

521 """ 

522 F5-reduce a labeled polynomial f by B. 

523 

524 Continuously searches for non-zero labeled polynomial h in B, such 

525 that the leading term lt_h of h divides the leading term lt_f of 

526 f and Sign(lt_h * h) < Sign(f). If such a labeled polynomial h is 

527 found, f gets replaced by f - lt_f / lt_h * h. If no such h can be 

528 found or f is 0, f is no further F5-reducible and f gets returned. 

529 

530 A polynomial that is reducible in the usual sense need not be 

531 F5-reducible, e.g.: 

532 

533 >>> from sympy.polys.groebnertools import lbp, sig, f5_reduce, Polyn 

534 >>> from sympy.polys import ring, QQ, lex 

535 

536 >>> R, x,y,z = ring("x,y,z", QQ, lex) 

537 

538 >>> f = lbp(sig((1, 1, 1), 4), x, 3) 

539 >>> g = lbp(sig((0, 0, 0), 2), x, 2) 

540 

541 >>> Polyn(f).rem([Polyn(g)]) 

542 0 

543 >>> f5_reduce(f, [g]) 

544 (((1, 1, 1), 4), x, 3) 

545 

546 """ 

547 order = Polyn(f).ring.order 

548 domain = Polyn(f).ring.domain 

549 

550 if not Polyn(f): 

551 return f 

552 

553 while True: 

554 g = f 

555 

556 for h in B: 

557 if Polyn(h): 

558 if monomial_divides(Polyn(h).LM, Polyn(f).LM): 

559 t = term_div(Polyn(f).LT, Polyn(h).LT, domain) 

560 if sig_cmp(sig_mult(Sign(h), t[0]), Sign(f), order) < 0: 

561 # The following check need not be done and is in general slower than without. 

562 #if not is_rewritable_or_comparable(Sign(gp), Num(gp), B): 

563 hp = lbp_mul_term(h, t) 

564 f = lbp_sub(f, hp) 

565 break 

566 

567 if g == f or not Polyn(f): 

568 return f 

569 

570 

571def _f5b(F, ring): 

572 """ 

573 Computes a reduced Groebner basis for the ideal generated by F. 

574 

575 f5b is an implementation of the F5B algorithm by Yao Sun and 

576 Dingkang Wang. Similarly to Buchberger's algorithm, the algorithm 

577 proceeds by computing critical pairs, computing the S-polynomial, 

578 reducing it and adjoining the reduced S-polynomial if it is not 0. 

579 

580 Unlike Buchberger's algorithm, each polynomial contains additional 

581 information, namely a signature and a number. The signature 

582 specifies the path of computation (i.e. from which polynomial in 

583 the original basis was it derived and how), the number says when 

584 the polynomial was added to the basis. With this information it 

585 is (often) possible to decide if an S-polynomial will reduce to 

586 0 and can be discarded. 

587 

588 Optimizations include: Reducing the generators before computing 

589 a Groebner basis, removing redundant critical pairs when a new 

590 polynomial enters the basis and sorting the critical pairs and 

591 the current basis. 

592 

593 Once a Groebner basis has been found, it gets reduced. 

594 

595 References 

596 ========== 

597 

598 .. [1] Yao Sun, Dingkang Wang: "A New Proof for the Correctness of F5 

599 (F5-Like) Algorithm", https://arxiv.org/abs/1004.0084 (specifically 

600 v4) 

601 

602 .. [2] Thomas Becker, Volker Weispfenning, Groebner bases: A computational 

603 approach to commutative algebra, 1993, p. 203, 216 

604 """ 

605 order = ring.order 

606 

607 # reduce polynomials (like in Mario Pernici's implementation) (Becker, Weispfenning, p. 203) 

608 B = F 

609 while True: 

610 F = B 

611 B = [] 

612 

613 for i in range(len(F)): 

614 p = F[i] 

615 r = p.rem(F[:i]) 

616 

617 if r: 

618 B.append(r) 

619 

620 if F == B: 

621 break 

622 

623 # basis 

624 B = [lbp(sig(ring.zero_monom, i + 1), F[i], i + 1) for i in range(len(F))] 

625 B.sort(key=lambda f: order(Polyn(f).LM), reverse=True) 

626 

627 # critical pairs 

628 CP = [critical_pair(B[i], B[j], ring) for i in range(len(B)) for j in range(i + 1, len(B))] 

629 CP.sort(key=lambda cp: cp_key(cp, ring), reverse=True) 

630 

631 k = len(B) 

632 

633 reductions_to_zero = 0 

634 

635 while len(CP): 

636 cp = CP.pop() 

637 

638 # discard redundant critical pairs: 

639 if is_rewritable_or_comparable(cp[0], Num(cp[2]), B): 

640 continue 

641 if is_rewritable_or_comparable(cp[3], Num(cp[5]), B): 

642 continue 

643 

644 s = s_poly(cp) 

645 

646 p = f5_reduce(s, B) 

647 

648 p = lbp(Sign(p), Polyn(p).monic(), k + 1) 

649 

650 if Polyn(p): 

651 # remove old critical pairs, that become redundant when adding p: 

652 indices = [] 

653 for i, cp in enumerate(CP): 

654 if is_rewritable_or_comparable(cp[0], Num(cp[2]), [p]): 

655 indices.append(i) 

656 elif is_rewritable_or_comparable(cp[3], Num(cp[5]), [p]): 

657 indices.append(i) 

658 

659 for i in reversed(indices): 

660 del CP[i] 

661 

662 # only add new critical pairs that are not made redundant by p: 

663 for g in B: 

664 if Polyn(g): 

665 cp = critical_pair(p, g, ring) 

666 if is_rewritable_or_comparable(cp[0], Num(cp[2]), [p]): 

667 continue 

668 elif is_rewritable_or_comparable(cp[3], Num(cp[5]), [p]): 

669 continue 

670 

671 CP.append(cp) 

672 

673 # sort (other sorting methods/selection strategies were not as successful) 

674 CP.sort(key=lambda cp: cp_key(cp, ring), reverse=True) 

675 

676 # insert p into B: 

677 m = Polyn(p).LM 

678 if order(m) <= order(Polyn(B[-1]).LM): 

679 B.append(p) 

680 else: 

681 for i, q in enumerate(B): 

682 if order(m) > order(Polyn(q).LM): 

683 B.insert(i, p) 

684 break 

685 

686 k += 1 

687 

688 #print(len(B), len(CP), "%d critical pairs removed" % len(indices)) 

689 else: 

690 reductions_to_zero += 1 

691 

692 # reduce Groebner basis: 

693 H = [Polyn(g).monic() for g in B] 

694 H = red_groebner(H, ring) 

695 

696 return sorted(H, key=lambda f: order(f.LM), reverse=True) 

697 

698 

699def red_groebner(G, ring): 

700 """ 

701 Compute reduced Groebner basis, from BeckerWeispfenning93, p. 216 

702 

703 Selects a subset of generators, that already generate the ideal 

704 and computes a reduced Groebner basis for them. 

705 """ 

706 def reduction(P): 

707 """ 

708 The actual reduction algorithm. 

709 """ 

710 Q = [] 

711 for i, p in enumerate(P): 

712 h = p.rem(P[:i] + P[i + 1:]) 

713 if h: 

714 Q.append(h) 

715 

716 return [p.monic() for p in Q] 

717 

718 F = G 

719 H = [] 

720 

721 while F: 

722 f0 = F.pop() 

723 

724 if not any(monomial_divides(f.LM, f0.LM) for f in F + H): 

725 H.append(f0) 

726 

727 # Becker, Weispfenning, p. 217: H is Groebner basis of the ideal generated by G. 

728 return reduction(H) 

729 

730 

731def is_groebner(G, ring): 

732 """ 

733 Check if G is a Groebner basis. 

734 """ 

735 for i in range(len(G)): 

736 for j in range(i + 1, len(G)): 

737 s = spoly(G[i], G[j], ring) 

738 s = s.rem(G) 

739 if s: 

740 return False 

741 

742 return True 

743 

744 

745def is_minimal(G, ring): 

746 """ 

747 Checks if G is a minimal Groebner basis. 

748 """ 

749 order = ring.order 

750 domain = ring.domain 

751 

752 G.sort(key=lambda g: order(g.LM)) 

753 

754 for i, g in enumerate(G): 

755 if g.LC != domain.one: 

756 return False 

757 

758 for h in G[:i] + G[i + 1:]: 

759 if monomial_divides(h.LM, g.LM): 

760 return False 

761 

762 return True 

763 

764 

765def is_reduced(G, ring): 

766 """ 

767 Checks if G is a reduced Groebner basis. 

768 """ 

769 order = ring.order 

770 domain = ring.domain 

771 

772 G.sort(key=lambda g: order(g.LM)) 

773 

774 for i, g in enumerate(G): 

775 if g.LC != domain.one: 

776 return False 

777 

778 for term in g.terms(): 

779 for h in G[:i] + G[i + 1:]: 

780 if monomial_divides(h.LM, term[0]): 

781 return False 

782 

783 return True 

784 

785def groebner_lcm(f, g): 

786 """ 

787 Computes LCM of two polynomials using Groebner bases. 

788 

789 The LCM is computed as the unique generator of the intersection 

790 of the two ideals generated by `f` and `g`. The approach is to 

791 compute a Groebner basis with respect to lexicographic ordering 

792 of `t*f` and `(1 - t)*g`, where `t` is an unrelated variable and 

793 then filtering out the solution that does not contain `t`. 

794 

795 References 

796 ========== 

797 

798 .. [1] [Cox97]_ 

799 

800 """ 

801 if f.ring != g.ring: 

802 raise ValueError("Values should be equal") 

803 

804 ring = f.ring 

805 domain = ring.domain 

806 

807 if not f or not g: 

808 return ring.zero 

809 

810 if len(f) <= 1 and len(g) <= 1: 

811 monom = monomial_lcm(f.LM, g.LM) 

812 coeff = domain.lcm(f.LC, g.LC) 

813 return ring.term_new(monom, coeff) 

814 

815 fc, f = f.primitive() 

816 gc, g = g.primitive() 

817 

818 lcm = domain.lcm(fc, gc) 

819 

820 f_terms = [ ((1,) + monom, coeff) for monom, coeff in f.terms() ] 

821 g_terms = [ ((0,) + monom, coeff) for monom, coeff in g.terms() ] \ 

822 + [ ((1,) + monom,-coeff) for monom, coeff in g.terms() ] 

823 

824 t = Dummy("t") 

825 t_ring = ring.clone(symbols=(t,) + ring.symbols, order=lex) 

826 

827 F = t_ring.from_terms(f_terms) 

828 G = t_ring.from_terms(g_terms) 

829 

830 basis = groebner([F, G], t_ring) 

831 

832 def is_independent(h, j): 

833 return not any(monom[j] for monom in h.monoms()) 

834 

835 H = [ h for h in basis if is_independent(h, 0) ] 

836 

837 h_terms = [ (monom[1:], coeff*lcm) for monom, coeff in H[0].terms() ] 

838 h = ring.from_terms(h_terms) 

839 

840 return h 

841 

842def groebner_gcd(f, g): 

843 """Computes GCD of two polynomials using Groebner bases. """ 

844 if f.ring != g.ring: 

845 raise ValueError("Values should be equal") 

846 domain = f.ring.domain 

847 

848 if not domain.is_Field: 

849 fc, f = f.primitive() 

850 gc, g = g.primitive() 

851 gcd = domain.gcd(fc, gc) 

852 

853 H = (f*g).quo([groebner_lcm(f, g)]) 

854 

855 if len(H) != 1: 

856 raise ValueError("Length should be 1") 

857 h = H[0] 

858 

859 if not domain.is_Field: 

860 return gcd*h 

861 else: 

862 return h.monic()