Parse negative numbers better
[libyakmo.git] / groebner.myr
blob04598a28d80587f6de0544cf33b8cb2fb2319e75
1 use std
3 use t
5 use "traits"
6 use "Z"
7 use "Q"
8 use "polyring"
11    Algorithms for dealing with Gröbner bases. Everything is from Cox,
12    Little, O'Shea "Ideals, Varieties, and Algorithms" (4th ed.), which
13    is optimized for being readable, not for speed.
14  */
16 pkg yakmo =
17         const groebner_basis_of : (F : polynomial#[:] -> polynomial#[:])
19         const positive_monomial_division : (a : monomial#, b : monomial# -> std.option(monomial))
21         impl lcm_struct polynomial#
25    As of this moment, I don't want to encourage using other monomial
26    orderings. That's strictly a hack for lcm calculation via
27    elimination.
29    Also, this really wouldn't work with negative exponents.
30  */
31 const lex_order = {a : monomial, b : monomial
32         var ak = a.vars.len - 1
33         var bk = b.vars.len - 1
35         while ak >= 0 && bk >= 0
36                 match strnumcmp(a.vars[ak].variable, b.vars[bk].variable)
37                 | `std.Before: -> `std.Before
38                 | `std.After : -> `std.After
39                 | `std.Equal:
40                         match t.cmp(a.vars[ak].power, b.vars[bk].power)
41                         | `std.Before: -> `std.Before
42                         | `std.After : -> `std.After
43                         | `std.Equal:
44                         ;;
45                 ;;
47                 ak--
48                 bk--
49         ;;
51         if ak >= 0
52                 -> `std.After
53         ;;
55         if bk >= 0
56                 -> `std.Before
57         ;;
59         -> `std.Equal
62 impl lcm_struct polynomial# =
63         lcm = {f, g
64                 /*
65                    Early case. If f or g are just constants, return the other, or maybe 1
66                  */
67                 var f_trivial = f.terms.len == 1 && f.terms[0].vars.len == 0
68                 var g_trivial = g.terms.len == 1 && g.terms[0].vars.len == 0
69                 if f_trivial && g_trivial
70                         -> rid()
71                 elif f_trivial
72                         var ret = t.dup(g)
73                         ensure_monic(ret)
74                         -> ret
75                 elif g_trivial
76                         var ret = t.dup(f)
77                         ensure_monic(ret)
78                         -> ret
79                 ;;
81                 /*
82                    Slightly more involved case. Evaluate f and g with a
83                    bunch of random constants, then see if they have an
84                    lcm. If not, then just return f*g.
85                  */
86                 if no_chance_of_lcm(f, g)
87                         var ret = t.dup(f)
88                         yakmo.rmul_ip(ret, g)
89                         ensure_monic(ret)
90                         -> ret
91                 ;;
93                 /*
94                    Build t and (1-t) to multiply f and g. In particular,
95                    we need t to be large enough that the standard
96                    grevlex ordering we use has the same effect as lex
97                    ordering: any monomial containing t is larger than
98                    any monomial not containing t.
99                  */
100                 var fake_var : byte[:] = make_var_larger_than(f, g)
101                 var vp_t : var_power = [
102                         .variable = fake_var,
103                         .power = rid(), // gadd(auto deg(f), auto deg(g)),
104                 ]
105                 var mon_t : monomial = [
106                         .coeff = rid(),
107                         .vars = std.sldup([ vp_t ][:])
108                 ]
109                 var mon_one : monomial = [
110                         .coeff = rid(),
111                         .vars = [][:]
112                 ]
113                 var ft = t.dup(f)
114                 mul_poly_mon_ip(ft, &mon_t)
116                 gneg_ip(mon_t.coeff)
117                 var poly_1mt : polynomial = [
118                         .terms = [ mon_one, mon_t ][:]
119                 ]
121                 var g1mt = t.dup(g)
122                 rmul_ip(g1mt, &poly_1mt)
123                 auto mon_one
124                 auto mon_t // This kills fake_var as well
126                 /*
127                    Force lex ordering. This will percolate all the way
128                    through the Gröbner basis calculation, since we do it
129                    all with dups.
130                  */
131                 ft.monomial_cmp = `std.Some lex_order
132                 g1mt.monomial_cmp = `std.Some lex_order
133                 reducepoly(ft)
134                 reducepoly(g1mt)
136                 /* Now, compute a Gröbner basis for ⟨ ft, g(1-t) ⟩. */
137                 var groebner_basis = groebner_basis_of( [ ft, g1mt ][:] )
139                 /*
140                    The promise is that, if we remove all the elements of
141                    the groebner basis that contain fake_var, we'll be
142                    left with at most one element, and it will be the
143                    lcm.
144                  */
145                 for var j = 0; j < groebner_basis.len; ++j
146                         if contains_var(groebner_basis[j], fake_var)
147                                 __dispose__(groebner_basis[j])
148                                 std.sldel(&groebner_basis, j)
149                                 j--
150                         ;;
151                 ;;
152                 if groebner_basis.len == 1
153                         var ret = groebner_basis[0]
154                         std.slfree(groebner_basis)
155                         ret.monomial_cmp = `std.None
156                         reducepoly(ret)
157                         ensure_monic(ret)
158                         -> ret
159                 elif groebner_basis.len == 0
160                         std.slfree(groebner_basis)
161                         var ret = rmul(f,g)
162                         ensure_monic(ret)
163                         -> ret
164                 ;;
166                 /* This should be impossible */
167                 var ret = groebner_basis[0]
168                 for var j = 1; j < groebner_basis.len; ++j
169                         __dispose__(groebner_basis[j])
170                 ;;
171                 std.slfree(groebner_basis)
172                 ret.monomial_cmp = `std.None
173                 reducepoly(ret)
174                 ensure_monic(ret)
175                 -> ret
176         }
179 const no_chance_of_lcm = {f, g
180         /* If f or g aren't entirely positive, the following fails */
181         for var j = 0; j < f.terms.len; ++j
182                 if f.terms[j].coeff.p.sign < 0
183                         -> false
184                 ;;
185         ;;
186         for var j = 0; j < g.terms.len; ++j
187                 if g.terms[j].coeff.p.sign < 0
188                         -> false
189                 ;;
190         ;;
192         /*
193            Now, assuming f and g are totally positive, suppose they factor into
195                f = p1^e1 ... pn^en
196                g = q1^d1 ... qm^dm
198            All pi and qi are also positive, so evaluated on numbers
199            (strictly greater than 1), they'll return results >= 2.
200            Therefore, if f and g share a common factor pi = qj,
201            evaluating f and g will result in numbers with a common
202            factor >= 2.
204            So, if we evaluate f and g on some random numbers and they
205            don't share any common factors numerically, they can't share
206            any abstractly.
208            The number 5 was chosen after about three minutes of testing
209            with one specific program. It should probably be calculated
210            based on number of terms and variables.
211          */
212         for var j = 0; j < 5; ++j
213                 var rng : std.rng# = std.mksrng(j)
214                 var int_from_var : std.htab(byte[:], int)# = std.mkht()
216                 var fq : yakmo.Q# = eval_polynomial_by_seed(f, rng, int_from_var)
217                 var gq : yakmo.Q# = eval_polynomial_by_seed(g, rng, int_from_var)
218                 auto fq
219                 auto gq
220                 std.freerng(rng)
222                 var fzb : std.bigint# = std.bigdup(fq.p)
223                 std.bigmul(fzb, gq.q)
224                 var fz : yakmo.Z# = (fzb : yakmo.Z#)
225                 var gzb : std.bigint# = std.bigdup(gq.p)
226                 std.bigmul(gzb, fq.q)
227                 var gz : yakmo.Z# = (gzb : yakmo.Z#)
228                 auto fz
229                 auto gz
231                 /* cleanup */
232                 for (k, _) : std.byhtkeyvals(int_from_var)
233                         std.slfree(k)
234                 ;;
236                 std.htfree(int_from_var)
238                 var fgz = yakmo.rmul(fz, gz)
239                 var fglcm = yakmo.lcm(fz, gz)
240                 auto fgz
241                 auto fglcm
243                 if std.eq(fgz, fglcm)
244                         -> true
245                 ;;
246         ;;
248         -> false
252    TODO: all of this needs to be subsumed in some sort of "evaluable"
253    trait. But traits are borked right now.
254  */
255 const eval_polynomial_by_seed = { f : yakmo.polynomial#, rng : std.rng#, vars : std.htab(byte[:], int)#
256         var ret : yakmo.Q# = yakmo.gid()
258         for var j = 0; j < f.terms.len; ++j
259                 var temp : yakmo.Q# = eval_monomial_by_seed(f.terms[j], rng, vars)
260                 auto temp
261                 yakmo.gadd_ip(ret, temp)
262         ;;
264         -> ret
267 const eval_monomial_by_seed = { m : yakmo.monomial, rng : std.rng#, vars : std.htab(byte[:], int)#
268         var ret : yakmo.Q# = t.dup(m.coeff)
270         for var j = 0; j < m.vars.len; ++j
271                 var temp : yakmo.Q# = eval_var_power_by_seed(m.vars[j], rng, vars)
272                 auto temp
273                 yakmo.rmul_ip(ret, temp)
274         ;;
276         -> ret
279 const primes : int[:] = [
280         2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59,
281         61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127,
282         131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193,
283         197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269,
284         271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349,
285         353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431,
286         433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499
287 ][:]
289 const eval_var_power_by_seed = {vp : yakmo.var_power, rng : std.rng#, vars : std.htab(byte[:], int)#
290         var var_name : byte[:] = vp.variable
291         var var_value : int = 0
292         var zret : std.bigint# = std.mkbigint(1)
294         match std.htget(vars, var_name)
295         | `std.None:
296                 var_value = primes[std.rngrand(rng, 0, primes.len)]
297                 std.htput(vars, std.sldup(var_name), var_value)
298         | `std.Some z: var_value = z
299         ;;
301         var np : std.bigint# = std.bigdup((vp.power : std.bigint#))
302         var double = std.mkbigint(var_value)
303         while !std.bigiszero(np)
304                 if !std.bigiseven(np)
305                         std.bigmul(zret, double)
306                 ;;
308                 std.bigmul(double, double)
309                 std.bigshri(np, 1)
310         ;;
312         std.bigfree(double)
313         std.bigfree(np)
314         var qret : yakmo.Q = [ .p = zret, .q = std.mkbigint(1) ]
316         -> std.mk(qret)
319 const make_var_larger_than = {a, b
320         var c : char = 'x'
321         var n : int = 1
322         for var j = 0; j < a.terms.len; ++j
323                 for var k = 0; k < a.terms[j].vars.len; ++k
324                         var ct, st
325                         (ct, st) = std.charstep(a.terms[j].vars[k].variable)
326                         c = std.max(c, std.max(ct, ct + 1))
327                         while c == ct
328                                 n++
329                                 (ct, st) = std.charstep(st)
330                         ;;
331                 ;;
332         ;;
334         for var j = 0; j < b.terms.len; ++j
335                 for var k = 0; k < b.terms[j].vars.len; ++k
336                         var ct, st
337                         (ct, st) = std.charstep(b.terms[j].vars[k].variable)
338                         c = std.max(c, std.max(ct, ct + 1))
339                         while c == ct
340                                 n++
341                                 (ct, st) = std.charstep(st)
342                         ;;
343                 ;;
344         ;;
346         var sb : std.strbuf# = std.mksb()
347         while n > 0
348                 std.sbputc(sb, c)
349                 n--
350         ;;
352         -> std.sbfin(sb)
356    Return whether p contains a variable with given name, given the
357    assumption that such a variable is the largest variable occuring in
358    p, and that it only occurs with powers large enough to place it in
359    the leading term.
360  */
361 const contains_var = {p : polynomial#, name : byte[:]
362         if p.terms.len == 0
363                 -> false
364         ;;
366         var mon : yakmo.monomial# = &p.terms[p.terms.len - 1]
367         if mon.vars.len == 0
368                 -> false
369         ;;
371         -> std.eq(mon.vars[mon.vars.len - 1].variable, name)
375    Buchberger's Algorithm, specifically as described in [CLO] Chapter 2,
376    Section 9, Theorem 9, with the incorporated reduction definition.
377  */
378 const groebner_basis_of = {F : polynomial#[:]
379         var G : polynomial#[:] = t.dup(F)
381         /*
382            Initial reduction step. Kill every monomial of any polynomial
383            that appears as the leading term of any other polynomial.
384            Repeat until done.
385          */
386         reduce_ideal(&G, false)
387         remove_trivial(&G)
389         /* Now run Buchberger's */
390         var test_these : (std.size, std.size)[:] = [][:]
391         for var j = 0; j < G.len; ++j
392                 for var k = 0; k < j; ++k
393                         std.slpush(&test_these, (j, k))
394                 ;;
395         ;;
397         while test_these.len != 0
398                 var j, k
399                 (j, k) = std.slpop(&test_these)
400                 if !common_factors_in_LT(G[j], G[k])
401                         continue
402                 ;;
404                 if proposition_8_criterion(G, test_these, j, k)
405                         continue
406                 ;;
408                 var r = S_bar(G, G[j], G[k])
409                 if eq_gid(r)
410                         __dispose__(r)
411                 else
412                         std.slpush(&G, r)
413                         reduce_ideal(&G, true)
414                         for var i = 0; i < G.len - 1; ++i
415                                 std.slpush(&test_these, (i, G.len - 1))
416                         ;;
417                 ;;
418         ;;
419         std.slfree(test_these)
421         /*
422            We now have a Gröbner basis. Make it slightly prettier by
423            making all entries monic.
424          */
425         for var j = 0; j < G.len; ++j
426                 ensure_monic(G[j])
427         ;;
428         remove_trivial(&G)
429         std.sort(G, t.cmp)
431         /* Now, remove redundant entries */
432         /*
433            TODO: I'm pretty sure this is handled by the reduction step
434            during the algorithm, but I'm too tired to check.
435          */
436         minimize_groebner_basis(&G)
438         -> G
441 const ensure_monic = {p : polynomial#
442         if p.terms.len == 0
443                 /* How did this get in here? */
444                 -> void
445         ;;
447         if eqQ(p.terms[p.terms.len - 1].coeff, 1, 1)
448                 -> void
449         ;;
451         match finv(p.terms[p.terms.len - 1].coeff)
452         | `std.None:
453         | `std.Some ci:
454                 for var k = 0; k < p.terms.len; ++k
455                         rmul_ip(p.terms[k].coeff, ci)
456                 ;;
457                 __dispose__(ci)
458         ;;
461 const reduce_ideal = {ps : polynomial#[:]#, only_check_end : bool
462         /*
463            Start at the end, because in the process of Buchberger's,
464            that's where the new, interesting polynomials get slotted.
465          */
466         var test_these = [][:]
467         if only_check_end
468                 std.slpush(&test_these, ps#.len - 1)
469         else
470                 for var k = 0; k < ps#.len - 1; ++k
471                         std.slpush(&test_these, k)
472                 ;;
473         ;;
474         while test_these.len != 0
475                 var j = std.slpop(&test_these)
477                 var p = ps#[j]
479                 if p.terms.len == 0
480                         continue
481                 ;;
483                 /* Apply everything to p */
484                 for var k = 0; k < ps#.len; ++k
485                         if k == j
486                                 continue
487                         ;;
489                         var q = ps#[k]
491                         if q.terms.len == 0
492                                 continue
493                         ;;
494                         var ltq = &q.terms[q.terms.len - 1]
496                         /*
497                            We want to loop down through p, from the top.
498                            If we ever change p, then we might add/remove
499                            terms and change the indexing. But 1) we don't
500                            want to check the top terms over and over,
501                            and 2) as soon as we skip through a term, we
502                            know it will never be affected by any future
503                            action.
504                          */
505                         var good_terms = 1
506                         for var l = p.terms.len - 1; l >= 0; --l
507                                 match positive_monomial_division(&p.terms[l], ltq)
508                                 | `std.None: good_terms++
509                                 | `std.Some r:
510                                         auto r
511                                         var summand = t.dup(q)
512                                         mul_poly_mon_ip(summand, &r)
513                                         gneg_ip(summand)
514                                         auto summand
515                                         gadd_ip(p, summand)
516                                         l = p.terms.len - 1 // good_terms
517                                         k = -1
518                                 ;;
519                         ;;
520                 ;;
522                 if p.terms.len == 0
523                         continue
524                 ;;
526                 /* Now apply p to everything else */
527                 var ltp : monomial# = &p.terms[p.terms.len - 1]
528                 for var k = 0; k < ps#.len; ++k
529                         if k == j
530                                 continue
531                         ;;
533                         var q = ps#[k]
535                         if q.terms.len == 0
536                                 continue
537                         ;;
539                         var good_terms = 1
540                         for var l = q.terms.len - 1; l >= 0; --l
541                                 match positive_monomial_division(&q.terms[l], ltp)
542                                 | `std.None:
543                                 | `std.Some r:
544                                         auto r
545                                         var summand = t.dup(p)
546                                         mul_poly_mon_ip(summand, &r)
547                                         gneg_ip(summand)
548                                         auto summand
549                                         gadd_ip(q, summand)
550                                         l = q.terms.len - 1 // good_terms
551                                         ensure_contains(&test_these, k)
552                                         k = -1
553                                 ;;
554                         ;;
555                 ;;
556         ;;
557         std.slfree(test_these)
560 const ensure_contains = {s, e
561         if e < 0
562                 /*
563                    This can only happen if we're in the second loop for
564                    a particular q, and in that case, the index for q has
565                    already been added to the list.
566                  */
567                 -> void
568         ;;
569         for var k = 0; k < s#.len; ++k
570                 if s#[k] == e
571                         -> void
572                 ;;
573         ;;
574         std.slpush(s, e)
577 /* Weed out zeroes */
578 const remove_trivial = {ps : polynomial#[:]#
579         for var j = 0; j < ps#.len; ++j
580                 if ps#[j].terms.len == 0
581                         std.sldel(ps, j)
582                         j--
583                 ;;
584         ;;
588 /* True if LCM(LT(f), LT(g)) != LT(f) LT(g) */
589 const common_factors_in_LT = {f : polynomial#, g : polynomial#
590         if f.terms.len == 0 || g.terms.len == 0
591                 -> false
592         ;;
593         var ltf = &f.terms[f.terms.len - 1]
594         var ltg = &f.terms[f.terms.len - 1]
596         for var j = 0; j < ltf.vars.len; ++j
597                 for var k = 0; k < ltg.vars.len; ++k
598                         match strnumcmp(ltg.vars[k].variable, ltf.vars[j].variable)
599                         | `std.Before:
600                         | `std.Equal: -> true
601                         | `std.After: break
602                         ;;
603                 ;;
604         ;;
606         -> false
610    True if there is some l such that
612     - l \notin { j, k }
614     - both (j, l) and (k, l) are not in B (up to ordering)
616     - LT(f_l) divides lcm(LT(f_j), LT(f_k))
617  */
618 const proposition_8_criterion = {G : polynomial#[:], B : (std.size, std.size)[:], j : std.size, k : std.size
619         var fj = G[j]
620         var fk = G[k]
621         if fj.terms.len == 0 || fk.terms.len == 0
622                 -> true
623         ;;
625         var ltj = &fj.terms[fj.terms.len - 1]
626         var ltk = &fk.terms[fk.terms.len - 1]
628         for var l = 0; l < G.len; ++l
629                 if l == j || l == k
630                         continue
631                 ;;
633                 var already_in_b = false
634                 var jl1 = std.min(j, l)
635                 var jl2 = std.max(j, l)
636                 var kl1 = std.min(k, l)
637                 var kl2 = std.max(k, l)
638                 for (s, t) : B
639                         if (s == jl1 && t == jl2) || (s == kl1 && t == kl2)
640                                 already_in_b = true
641                                 break
642                         ;;
643                 ;;
645                 if already_in_b
646                         continue
647                 ;;
649                 var fl = G[l]
650                 if fl.terms.len == 0
651                         continue
652                 ;;
654                 var ltl = &fl.terms[fl.terms.len - 1]
655                 var this_l_works = true
657                 for vpl : ltl.vars
658                         var this_var_in_lcm = false
659                         for vpj : ltj.vars
660                                 match strnumcmp(vpj.variable, vpl.variable)
661                                 | `std.Before:
662                                 | `std.Equal:
663                                         match t.cmp(vpj.power, vpl.power)
664                                         | `std.Before:
665                                         | _:
666                                                 this_var_in_lcm = true
667                                                 break
668                                         ;;
669                                 | `std.After: break
670                                 ;;
671                         ;;
673                         if this_var_in_lcm
674                                 continue
675                         ;;
677                         for vpk : ltk.vars
678                                 match strnumcmp(vpk.variable, vpl.variable)
679                                 | `std.Before:
680                                 | `std.Equal:
681                                         match t.cmp(vpk.power, vpl.power)
682                                         | `std.Before:
683                                         | _:
684                                                 this_var_in_lcm = true
685                                                 break
686                                         ;;
687                                 | `std.After: break
688                                 ;;
689                         ;;
691                         if !this_var_in_lcm
692                                 this_l_works = false
693                                 break
694                         ;;
695                 ;;
697                 if this_l_works
698                         -> true
699                 ;;
700         ;;
702         -> false
706    Compute \overbar{S(f, g)}^{F}; the remainder of S(f, g) when reducing
707    by everything in F.
709    TODO: maybe break this out into a general polynomial div_maybe or div
710    or reduce or something?
711  */
712 const S_bar = {F, f, g
713         var Sfg : polynomial# = S_polynomial(f, g)
715         var again = true
716         while again
717                 again = false
718                 for var j = 0; j < F.len; ++j
719                         if F[j].terms.len == 0
720                                 continue
721                         ;;
723                         if Sfg.terms.len == 0
724                                 -> Sfg
725                         ;;
727                         match positive_monomial_division(&Sfg.terms[Sfg.terms.len - 1], &F[j].terms[F[j].terms.len - 1])
728                         | `std.None:
729                         | `std.Some q:
730                                 auto q
731                                 var summand = t.dup(F[j])
732                                 mul_poly_mon_ip(summand, &q)
733                                 gneg_ip(summand)
734                                 auto summand
735                                 gadd_ip(Sfg, summand)
736                                 again = true
737                         ;;
738                 ;;
739         ;;
741         -> Sfg
745    Let X be the lcm of the leading terms of f and g (ignoring coefficients). Then
747      S(f, g) := X/LT(f) * f - X/LT(g) * g
748  */
749 const S_polynomial = {f : polynomial#, g : polynomial#
750         var ltf : monomial# = &f.terms[f.terms.len - 1]
751         var ltg : monomial# = &g.terms[g.terms.len - 1]
752         var f_factor : monomial = [ .coeff = rid(), .vars = [][:] ]
753         var g_factor : monomial = [ .coeff = rid(), .vars = [][:] ]
755         /*
756            First, construct X/LT(f) and X/LT(g). ltf.vars and ltg.vars
757            are ordered, so we may walk through LT(f) and LT(g). A power
758            of a variable which we can prove occurs only in LT(f) should
759            be placed in X/LT(g), and vice-versa.
760          */
761         var fj = 0
762         var gj = 0
763         while fj < ltf.vars.len && gj < ltg.vars.len
764                 var fx = &ltf.vars[fj]
765                 var gx = &ltg.vars[gj]
766                 match strnumcmp(fx.variable, gx.variable)
767                 | `std.Before:
768                         std.slpush(&g_factor.vars, t.dup(fx#))
769                         fj++
770                 | `std.After:
771                         std.slpush(&f_factor.vars, t.dup(gx#))
772                         gj++
773                 | `std.Equal:
774                         /* x^a and x^b. Add x^|a-b| to one side or the other */
775                         var ngp : Z# = gneg(gx.power)
776                         var p = gadd(fx.power, ngp)
777                         __dispose__(ngp)
778                         match cmp_zero(p)
779                         | `std.Equal: __dispose__(p)
780                         | `std.Before:
781                                 gneg_ip(p)
782                                 std.slpush(&f_factor.vars, [
783                                         .variable = std.sldup(fx.variable),
784                                         .power = p
785                                 ])
786                         | `std.After:
787                                 std.slpush(&g_factor.vars, [
788                                         .variable = std.sldup(gx.variable),
789                                         .power = p
790                                 ])
791                         ;;
792                         fj++
793                         gj++
794                 ;;
795         ;;
797         /* Clean up any extra, trailing terms */
798         while fj < ltf.vars.len
799                 std.slpush(&g_factor.vars, t.dup(ltf.vars[fj]))
800                 fj++
801         ;;
803         while gj < ltg.vars.len
804                 std.slpush(&f_factor.vars, t.dup(ltg.vars[gj]))
805                 gj++
806         ;;
808         /* Now construct (X / LT(f)) * f - (X / LT(g)) * g */
809         var f_term = t.dup(f)
810         var g_term = auto t.dup(g)
811         mul_poly_mon_ip(f_term, &f_factor)
812         __dispose__(f_factor)
813         mul_poly_mon_ip(g_term, &g_factor)
814         __dispose__(g_factor)
815         gneg_ip(g_term)
816         gadd_ip(f_term, g_term)
818         -> f_term
822    Assuming a and b have only non-negative exponents, try and build a/b,
823    similarly with non-negative exponents.
825    This is not a division_struct monomial because monomials in general
826    are allowed to have negative exponents.
827  */
828 const positive_monomial_division = { a : monomial#, b : monomial#
829         var quotient : monomial = [ .coeff = t.dup(a.coeff), .vars = [][:] ]
831         match finv(b.coeff)
832         | `std.Some bci:
833                 rmul_ip(quotient.coeff, bci)
834                 __dispose__(bci)
835         | `std.None:
836                 __dispose__(quotient)
837                 -> `std.None
838         ;;
840         var ak = 0
841         var bk = 0
842         while ak < a.vars.len && bk < b.vars.len
843                 var ax = &a.vars[ak]
844                 var bx = &b.vars[bk]
845                 match strnumcmp(ax.variable, bx.variable)
846                 | `std.Before:
847                         std.slpush(&quotient.vars, t.dup(ax#))
848                         ak++
849                 | `std.After:
850                         __dispose__(quotient)
851                         -> `std.None
852                 | `std.Equal:
853                         match t.cmp(ax.power, bx.power)
854                         | `std.Before:
855                                 __dispose__(quotient)
856                                 -> `std.None
857                         | _:
858                         ;;
860                         var bp : Z# = t.dup(bx.power)
861                         gneg_ip(bp)
862                         auto bp
863                         std.slpush(&quotient.vars, [
864                                 .variable = std.sldup(ax.variable),
865                                 .power = gadd(ax.power, bp)
866                         ])
867                         ak++
868                         bk++
869                 ;;
870         ;;
872         if bk < b.vars.len
873                 __dispose__(quotient)
874                 -> `std.None
875         ;;
877         while ak < a.vars.len
878                 std.slpush(&quotient.vars, t.dup(a.vars[ak]))
879                 ak++
880         ;;
882         reducemonomial(&quotient)
883         -> `std.Some quotient
886 const minimize_groebner_basis = { G : polynomial#[:]#
887         /*
888            Following [CLO] Chapter 2, Section 7, Lemma 3, try to remove
889            any p in G such that LT(p) is generated by the leading terms
890            of G - { p }.
891          */
892         std.sort(G#, t.cmp)
893         for var j = G#.len - 1; j >= 0; --j
894                 var lt = t.dup(G#[j].terms[G#[j].terms.len - 1])
895                 for var k = 0; k < G#.len; ++k
896                         if k == j
897                                 continue
898                         ;;
900                         match positive_monomial_division(&lt, &G#[k].terms[G#[k].terms.len - 1])
901                         | `std.None:
902                         | `std.Some q:
903                                 __dispose__(q)
904                                 __dispose__(G#[j])
905                                 std.sldel(G, j)
906                                 break
907                         ;;
908                 ;;
909         ;;