-
Notifications
You must be signed in to change notification settings - Fork 0
/
groebner.dtx
2055 lines (2055 loc) · 74.4 KB
/
groebner.dtx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
% \iffalse
%<*driver>
\documentclass{mtmtcl}
\begin{document}
\DocInput{groebner.dtx}
\end{document}
%</driver>
% \fi
%
% \title{Gr\"obner basis utilities}
% \author{Lars Hellstr\"om}
% \maketitle
%
%
% \section{The monoid of power products}
%
% An important building block in any library for Gr\"obner basis
% computations is the monoid of power products, since much of the
% control logic is focused on the monomials. From the power products,
% it is a simple matter of applying
% |mtmtcl::rings::semigroup_algebra| to produce the actual ring of
% polynomials.
%
% In the long run, it will probably be appropriate to define an
% interface for such power product monoids, but for now it is
% perfectly adequate to informally specify the expected interface.
% \begin{APIdescription}{monoid}
% \begin{APImethod}{=}
% \word{element} \word{element}
% \end{APImethod}
% \begin{APImethod}{*}
% \word{element} \word{element}
% \end{APImethod}
% \begin{APImethod}{1}
% \end{APImethod}
% First, the |=|, |*|, and |1| methods are as expected for the
% \APIref+{monoid}{1.0} interface. In addition, the |*| operation
% is supposed to be commutative. (Commutativity is needed for
% Dickson's Lemma, which in the classical proof provides for
% termination of the Buchberger algorithm. There are
% generalisations---classes of rings where the monoid of monomials
% satisfy the conclusion without being commutative---but those
% are better left for future extensions, if supporting them is at
% all feasible. Commutativity also simplifies the statement of other
% things in the framework.)
%
% \begin{APImethod}{/}
% \word{element} \word{element}
% \end{APImethod}
% Second, the |/| method is as expected for the
% \APIref{division}{1.0} interface. In particular, the error
% conditions will be relied upon when testing whether one element
% divides another.
%
% \begin{APImethod}{3gcd}
% \word{element} \word{element}
% \end{APImethod}
% Third, there is a three-part greatest common divisor operation,
% returning a list of four\footnote{
% The reason for the initial keyword element is that if
% generalising this operation to noncommutative monoids, then
% there arise a couple of additional possibilities for how the
% operand arguments may be composed from the result factors:
% $ld$ might be the second \word{element}, or it might happen
% that one \word{element} is $ldr$ and the other just $d$. The
% most natural way of capturing such variations is with a
% separate keyword element, so by including one also in the
% easier commutative case, a route to generalisation lies open.
% Note however that in the free
% associative case, this kind of operation may want to return
% several results ($ab^3$ and $b^3a$ have \emph{four} different
% overlaps\Ldash $ab^3a$, $ab^4a$, $ab^5a$, and $b^3ab^3$\Rdash
% each of which must be investigated); thus future-proofing this
% is not trivial.
% } elements
% \begin{displaysyntax}
% commutative $l$ $d$ $r$
% \end{displaysyntax}
% where $d$ is the $\gcd$, $ld$ is the left \word{element}, and
% $rd$ is the right \word{element}. This is useful when computing
% S-polynomials.
%
% \begin{APImethod}{multidegree}
% \word{element}
% \end{APImethod}
% Finally, the |multidegree| method returns the list of integers
% that constitute the multidegree vector of the \word{element}.
% This is used for ordering the monomials for identifying the
% leading monomial, but that order is not a built-in property of
% the power product monoid (doing things that way would create a
% formal mess out of any operation changing the order).
% \end{APIdescription}
%
% Those should be the only methods needed to carry out the Buchberger
% algorithm, but other operations may of course need additional
% methods.
%
%
% \subsection{Dense encoding power product monoid}
%
% \setnamespace{mtmtcl::groups::power_products}
% \begin{ensemble}{dense}
% The |dense| implementation of power products encodes elements as
% lists of exponents. This mostly simplifies implementation, but
% does make the intermediate values a bit tricky to read.
%
% \begin{displaysyntax}
% |::mtmcl::groups::power_products::dense| \word{varlist}
% \word{method} \word{arg}\regstar
% \end{displaysyntax}
% The \word{varlist} parameter is the list of names of the
% variables (as used for the |OMV|s when |export|ing elements).
%
% \begin{tcl}
%<*docstrip.tcl::catalogue>
pkgProvide mtmtcl::groups::power_products::dense 1.0 ppdense
%</docstrip.tcl::catalogue>
%<*ppdense>
package require Tcl 8.6
package require mtmtcl::openmath 1.1
package require API 1.0
package require mtmtcl::rings::integers 1.2
namespace eval ::mtmtcl::groups::power_products::dense {
% \end{tcl}
%
% \begin{tcl}
namespace ensemble create -parameters {varlist} -subcommands {
= * 1 / canonise
3gcd multidegree
named {* decomposition} ^ *^
no.components index tuple component
export
API
} -map {
API {::API::staticn 1 {
equality 1.0 canonise 1.1
monoid 1.0 semigroup 1.0
division 1.0
export 2.0
named 1.0
"direct product" 1.1
% \end{tcl}
% Supporting \APIref+{direct product}{1.1} requires that the
% components have equality.
% \begin{tcl}
"commutative *" 1.0
"autocanonical *" 1.0
"autocanonical 1" 1.0
}}
}
% \end{tcl}
%
%
% \subsubsection{The Buchberger methods}
%
% \begin{proc}{=}
% Testing for equality is merely a matter of looping over the
% exponents. It could be made variadic without too much work, by
% using the variadiacy of |tcl::mathop::==|, but this doesn't seem
% overly needed.
% \begin{tcl}
proc = {varlist a b} {
foreach ai $a bi $b {
if {$ai != $bi} then {return 0}
}
return 1
}
% \end{tcl}
% \end{proc}
%
% \begin{proc}{*}
% This, too, could be made variadic, but for now it is strictly
% binary.
% \begin{tcl}
proc * {varlist a b} {
set res {}
foreach ai $a bi $b {
lappend res [expr {$ai+$bi}]
}
return $res
}
% \end{tcl}
% \end{proc}
%
% \begin{proc}{1}
% The unit is the first method that needs to use the
% \word{varlist}.
% \begin{tcl}
proc 1 {varlist} {lrepeat [llength $varlist] 0}
% \end{tcl}
% \end{proc}
%
% \begin{proc}{/}
% Division is similar to multiplication, with checks for the
% result being nonnegative.
% \begin{tcl}
proc / {varlist a b} {
set res {}
foreach ai $a bi $b {
lappend res [expr {$ai-$bi}]
if {[lindex $res end] < 0} then {
return -code error -errorcode {API division nosolution}\
"Does not divide at index [expr {[llength $res]-1}]"
}
}
return $res
}
% \end{tcl}
% \end{proc}
%
% \begin{proc}{canonise}
% Canonisation uses the fact that the unary |tcl::mathop::+|
% canonises integers.
% \begin{tcl}
proc canonise {varlist a} {
lmap ai $a {::tcl::mathop::+ $ai}
}
% \end{tcl}
% \end{proc}
%
% \begin{proc}{multidegree}
% With this encoding, the |multidegree| operation is merely an
% identity operation.
% \begin{tcl}
proc multidegree {varlist a} {return $a}
% \end{tcl}
% \end{proc}
%
% \begin{proc}{3gcd}
% The three-way $\gcd$ is quite straightforward, since the same
% test as determines what the minimum of two exponent is also
% tells which calculation should be made for the two remainder
% factors.
% \begin{tcl}
proc 3gcd {varlist a b} {
set d {}; set l {}; set r {}
foreach ai $a bi $b {
if {$ai <= $bi} then {
lappend d $ai
lappend l 0
lappend r [expr {$bi-$ai}]
} else {
lappend d $bi
lappend l [expr {$ai-$bi}]
lappend r 0
}
}
return [list commutative $l $d $r]
}
% \end{tcl}
% \end{proc}
%
%
% \subsubsection{Named elements}
%
% One expected way of obtaining elements in the power product
% monoid is to use the variable names.
%
% \begin{proc}{named}
% One could use |lsearch| to locate the specified variable, but
% it is anyway necessary to build the whole value, so a loop is
% not unreasonable.
% \begin{tcl}
proc named {varlist name} {
set res {}
set found 0
foreach var $varlist {
if {$name eq $var} then {
lappend res [set found 1]
} else {
lappend res 0
}
}
if {$found} then {
return $res
} else {
return -code error "Unknown element name \"$name\",\
must be one of: [join $varlist ", "]"
}
}
% \end{tcl}
% \end{proc}
%
% \begin{proc}{* decomposition}
% It is also expected to provide a \verb*"* decomposition"
% method, even though this structure cannot claim support for
% the \APIref{generated group}{1.0} interface since it is only a
% monoid. The return value does not include generators with $0$
% exponent, since a variant which presents data that way seems
% useful.
% \begin{tcl}
proc {* decomposition} {varlist a} {
set res {}
foreach var $varlist exponent $a {
if {$exponent} then {
lappend res $var $exponent
}
}
return $res
}
% \end{tcl}
%
% \end{proc}
%
% \begin{proc}{^}
% \begin{proc}{*^}
% Having implemented decomposition, it is natural to also provide
% the almost-inverse |*^| operation, and then the plain power |^|
% becomes a given as well.
% \begin{tcl}
proc ^ {varlist a n} {
lmap ai $a {expr {$ai*$n}}
}
proc *^ {varlist args} {
set i 0
lmap var $varlist {
set sum 0
foreach {base exp} $args {
incr sum [expr {[lindex $base $i] * $exp}]
}
incr i
set sum
}
}
% \end{tcl}
% \end{proc}\end{proc}
%
%
% \subsubsection{Tuple methods}
%
% It is useful to also let the user describe a power product as a
% tuple of natural numbers, by way of the \APIref{direct
% product}{1.0} interface.
%
% \begin{proc}{no.components}
% The number of components is equal to the number of variables.
% \begin{tcl}
proc no.components {varlist} {llength $varlist}
% \end{tcl}
% \end{proc}
%
% \begin{proc}{index}
% The |index| method is of course just an |lindex|.
% \begin{tcl}
proc index {varlist i a} {lindex $a $i}
% \end{tcl}
% \end{proc}
%
% \begin{proc}{tuple}
% The |tuple| method would correspondingly be roughly |list|, but
% the |args| mechanism of a proc does that for us, so most of the
% actual implementation is about sanity checking the data.
% \begin{tcl}
proc tuple {varlist args} {
if {[llength $args] != [llength $varlist]} then {
return -code error "Got [llength $args] exponents, expected\
[llength $varlist]"
}
foreach a $args {
if {$a < 0} then {
return -code error "Negative exponents not permitted"
}
}
return $args
}
% \end{tcl}
% \end{proc}
%
% \begin{proc}{component}
% The |component| method maps everything to
% |mtmtcl::rings::integers::nonnegative| regardless of which
% component is selected. The proc is mostly for getting rid of
% ignored arguments.
% \begin{tcl}
proc component {varlist index args} {
tailcall ::mtmtcl::rings::integers::nonnegative {*}$args
}
% \end{tcl}
% \end{proc}
%
%
% \subsubsection{Export and import}
%
% The exported form of an element is an \OMSref{arith1}{times} of
% factors that are either an explicit variable, or a
% \OMSref{arith1}{power}. However for the unity element, it is
% \OMSref{alg1}{one}, since it can be argued that
% \OMSref{arith1}{times} is specified as not having a nullary form.
%
% \begin{proc}{export}
% This is adapted from the |export| method of the old
% |power_product_monoid_1.3| structure. It probably inserts too
% many attributes.
% \begin{tcl}
proc export {varlist a attr} {
set pattr [dict replace $attr cd arith1 name power]
set mattr [dict replace $attr cd arith1 name times]
dict lappend pattr mtmtcl:path ^
dict lappend mattr mtmtcl:path *
set pow [list OMS $pattr {}]
set childL [list [list OMS $mattr {}]]
foreach name $varlist exponent $a {
set omv [::mtmtcl::openmath::OM V $name]
if {$exponent == 1} then {
lappend childL $omv
} elseif {$exponent > 1} then {
lappend childL [list OMA $attr [list $pow $omv [
::mtmtcl::openmath::OM I $exponent
]]]
}
}
if {[llength $childL]} then {
return [list OMA $attr $childL]
} else {
return [::mtmtcl::openmath::OM S alg1 one]
}
}
% \end{tcl}
% \end{proc}
%
% Let's hold off on |import|, for now.
% \begin{tcl}
}
% \end{tcl}
% \end{ensemble}
%
% \begin{proc}{make_dense}
% The |make_dense| procedure is a utility that constructs the
% command prefix for a dense encoding power product structure. The
% call syntax is
% \begin{displaysyntax}
% |make_dense| \word{varlist}
% \end{displaysyntax}
% \begin{tcl}
proc mtmtcl::groups::power_products::make_dense {varlist} {
list [namespace which dense] $varlist
}
%</ppdense>
% \end{tcl}
% It probably would make sense to have a |power_products::make|
% command which just takes a switch for whether the encoding should
% be dense or sparse, but that's a later matter.
% \end{proc}
%
%
% \section{Polynomial accumulators}
%
% In reduction (or generalised division, as it is sometimes called in
% the Gr\"obner basis literature), it is useful to keep polynomials
% not as plain dicts mapping power products to their coefficients,
% but as maps where the terms are ordered so that the leading one is
% readily available. A convenient data structure for this is the
% |heap::skiplist|.
%
% When doing this, you're not treating the polynomials as values, but
% rather as mutable objects. The purpose of these objects may be to
% store a value, but only so that it can be efficiently operated
% upon, and not all of the operations are algebraic in flavour. This
% kind of thingie is called an \emph{accumulator}, and it is supposed
% to have the interface:
% \begin{APIdescription}{accumulator}
% \begin{APImethod}{clear}
% \end{APImethod}
% This method sets the value of the accumulator to $0$. There is
% no particular return value.
%
% \begin{APImethod}{value}
% \end{APImethod}
% This method returns the polynomial currently stored in the
% accumulator, as a dictionary mapping monomials to their
% coefficients. The accumulator contents are not affected.
%
% \begin{APImethod}{add}
% \word{polynomial}
% \end{APImethod}
% \begin{APImethod}{addmult}
% \word{scalar} \word{monomial} \word{polynomial}
% \word{monomial}\regopt
% \end{APImethod}
% The \word{polynomial} is a dictionary mapping monomials to
% coefficients. The \word{scalar} is an element of the coeffient
% ring. The \word{monomial}s is are elements of the underlying
% monoid of monomials; the second \word{monomial} defaults to the
% unit. In the case of |add| the \word{polynomial} is added to the
% current contents of the accumulator. In the case of |addmult|
% the thing added is the \word{scalar} times the first
% \word{monomial} times the \word{polynomial} times the second
% \word{monomial}. There is no particular return value.
%
% \begin{APImethod}{pop}
% \end{APImethod}
% \begin{APImethod}{peek}
% \end{APImethod}
% These two methods return the leading term of the accumulator,
% and the |pop| method additionally removes that term (thus
% modifying the accumulator value). The return value is a list
% \begin{displaysyntax}
% \begin{regblock}[\regopt]
% \word{monomial} \word{coefficient}
% \end{regblock}
% \end{displaysyntax}
% which is empty iff the value of the accumulator was already $0$.
% \end{APIdescription}
%
%
% \subsection{A skiplist accumulator}
%
% \setnamespace{mtmtcl::rings::semigroup_algebra}
% \begin{tclcommand}{class}{accumulator}
% The objects of the |accumulator| class are accumulators in the
% sense explained above. The constructor syntax is
% \begin{displaysyntax}
% accumulator new \word{scalar ring} \word{monomial monoid}
% \word{heap-prefix} \word{order-prefix}
% \end{displaysyntax}
% where the \word{scalar ring} is some associative and commutative
% \APIref{ring}{2.0} with unit, \word{monomial monoid} is some
% \APIref{monoid}{1.0} with |multidegree| operation, the
% \word{heap-prefix} is a command prefix for accessing a heap, and
% the \word{order-prefix} is a command prefix that expects a
% |multidegree| list as argument and returns the sort key (a list
% of numbers) that the ring element are to be ordered by.
%
% \begin{tcl}
%<*docstrip.tcl::catalogue>
pkgProvide mtmtcl::rings::semigroup_algebra::accumulator 1.0 sgacc
%</docstrip.tcl::catalogue>
%<*sgacc>
package require Tcl 8.6
package require heap 1.0
package require heap::skiplist 1.1
namespace eval mtmtcl::rings::semigroup_algebra {}
oo::class create mtmtcl::rings::semigroup_algebra::accumulator
% \end{tcl}
% \setnamespace{\meta{accumulator}}
%
% \begin{variable}{Ring}
% \begin{variable}{Monoid}
% \begin{variable}{HeapPrefix}
% \begin{variable}{KeyPrefix}
% The |Ring|, |Monoid|, |HeapPrefix|, and |KeyPrefix| variables
% hold those arguments of the constructor.
% \begin{tcl}
oo::define mtmtcl::rings::semigroup_algebra::accumulator variable\
Ring Monoid HeapPrefix KeyPrefix
% \end{tcl}
% These are effectively constants in the accumulator object.
% \end{variable}\end{variable}\end{variable}\end{variable}
%
% \begin{variable}{Skiplist}
% The (pointer to the header node of the) skiplist is stored in
% the |Skiplist| variable.
% \begin{tcl}
oo::define mtmtcl::rings::semigroup_algebra::accumulator variable\
Skiplist
% \end{tcl}
% The sort key of the skiplist is precisely what is returned by
% the |KeyPrefix| command, and each element is compared using
% |::tcl::mathop::-|. The entries in the skiplist are pairs
% \begin{displaysyntax}
% \word{coefficient} \word{monomial}
% \end{displaysyntax}
% where the \word{coefficient} must be nonzero. The \word{monomial}
% is included because we do not (in general) have a way of
% recreating it from the sort key (or even from the
% |multidegree|).
% \end{variable}
%
% \begin{variable}{IsZero}
% The |IsZero| variable contains a command prefix with the syntax
% \begin{displaysyntax}
% \meta{IsZero} \word{ring element}
% \end{displaysyntax}
% which tests whether a \word{ring element} is $0$. This is done
% using |iszero| if that is available, and using |=| otherwise.
% \begin{tcl}
oo::define mtmtcl::rings::semigroup_algebra::accumulator variable\
IsZero
% \end{tcl}
% \end{variable}
%
% \begin{tclcommand}{constructor}{}
% This brings us to the constructor.
% \begin{tcl}
oo::define mtmtcl::rings::semigroup_algebra::accumulator constructor\
{ring monoid heap order} {
if {![{*}$ring API ring 2.0]} then {
return -code error "This is not a ring: $ring"
}
set Ring $ring
if {![{*}$monoid API monoid 1.0]} then {
return -code error "This is not a monoid: $monoid"
}
set Monoid $monoid
set HeapPrefix $heap
set KeyPrefix $order
set Skiplist [heap::skiplist::create $HeapPrefix {*}[
lrepeat [llength [
{*}$KeyPrefix [{*}$Monoid multidegree [{*}$Monoid 1]]
]] [list ::tcl::mathop::-]
]]
if {[{*}$Ring API "additive group" 2.1]} then {
set IsZero [list {*}$Ring iszero]
} else {
set IsZero [list {*}$Ring = [{*}$Ring 0]]
}
}
% \end{tcl}
% \end{tclcommand}
%
% \begin{tclcommand}{destructor}{}
% Since the constructor creates a skiplist in some external heap,
% the destructor must destroy it.
% \begin{tcl}
oo::define mtmtcl::rings::semigroup_algebra::accumulator destructor {
heap::skiplist::destroy $HeapPrefix $Skiplist
}
% \end{tcl}
% \end{tclcommand}
%
% \begin{tclcommand}{method}{clear}
% The |clear| method makes use of the |empty| operation for
% skiplists. That is slightly faster than popping off elements
% until the list is empty.
% \begin{tcl}
oo::define mtmtcl::rings::semigroup_algebra::accumulator method clear {} {
heap::skiplist::empty $HeapPrefix $Skiplist
}
% \end{tcl}
% \end{tclcommand}
%
% \begin{tclcommand}{method}{value}
% The |value| method is mostly a matter of putting data in the
% right format.
% \begin{tcl}
oo::define mtmtcl::rings::semigroup_algebra::accumulator method value {} {
set res [dict create]
foreach {key entry} [
heap::skiplist::contents $HeapPrefix $Skiplist
] {
dict set res [lindex $entry 1] [lindex $entry 0]
}
return $res
}
% \end{tcl}
% \end{tclcommand}
%
% \begin{tclcommand}{method}{pop}
% The |pop| method similarly is a matter of putting data in the
% right format.
% \begin{tcl}
oo::define mtmtcl::rings::semigroup_algebra::accumulator method pop {} {
set raw [heap::skiplist::pop $HeapPrefix $Skiplist]
if {[llength $raw]} then {
return [list [lindex $raw 1 1] [lindex $raw 1 0]]
} else {
return
}
}
% \end{tcl}
% \end{tclcommand}
%
% \begin{tclcommand}{method}{peek}
% The |peek| method is the same, except in using |peek| to access
% the skiplist.
% \begin{tcl}
oo::define mtmtcl::rings::semigroup_algebra::accumulator method peek {} {
set raw [heap::skiplist::peek $HeapPrefix $Skiplist]
if {[llength $raw]} then {
return [list [lindex $raw 1 1] [lindex $raw 1 0]]
} else {
return
}
}
% \end{tcl}
% \end{tclcommand}
%
% \begin{tclcommand}{method}{add}
% The |add| method is an application of |heap::skiplist::update|.
% \begin{tcl}
oo::define mtmtcl::rings::semigroup_algebra::accumulator method\
add {poly} {
dict for {mu r} $poly {
heap::skiplist::update $HeapPrefix $Skiplist\
[{*}$KeyPrefix [{*}$Monoid multidegree $mu]] entry {
if {[info exists entry]} then {
set r [{*}$Ring + $r [lindex $entry 0]]
if {[{*}$IsZero $r]} then {
unset entry
} else {
lset entry 0 $r
}
} elseif {![{*}$IsZero $r]} then {
set entry [list $r $mu]
}
}
}
}
% \end{tcl}
% \end{tclcommand}
%
% \begin{tclcommand}{method}{addmult}
% The |addmult| method is almost the same as |add|, except that
% the entries in the \word{polynomial} are multiplied by their
% respective factors before the skiplist entry is |update|d.
% \begin{tcl}
oo::define mtmtcl::rings::semigroup_algebra::accumulator method\
addmult {s nu poly args} {
if {[llength $args] > 1} then {
return -code error "Wrong \# of arguments, must be:\
addmult s nu1 poly ?nu2?"
}
dict for {mu r} $poly {
set mu [{*}$Monoid * $nu $mu]
if {[llength $args]} then {
set mu [{*}$Monoid * $mu [lindex $args 0]]
}
set r [{*}$Ring * $s $r]
heap::skiplist::update $HeapPrefix $Skiplist\
[{*}$KeyPrefix [{*}$Monoid multidegree $mu]] entry {
if {[info exists entry]} then {
set r [{*}$Ring + $r [lindex $entry 0]]
if {[{*}$IsZero $r]} then {
unset entry
} else {
lset entry 0 $r
}
} elseif {![{*}$IsZero $r]} then {
set entry [list $r $mu]
}
}
}
}
% \end{tcl}
% \end{tclcommand}
%
% And that's all there is to it!
% \begin{tcl}
%</sgacc>
% \end{tcl}
% \end{tclcommand}
%
%
% \section{Monomial orders}
%
% It is convenient to have some predefined \meta{order-prefix}
% commands to throw in for experimentation, even if ``production''
% work might require facilities for defining customised orders. Then
% again, it could also turn out that the following standard
% orders suffice quite far.
%
% \begin{tcl}
%<*Groebner>
namespace eval Groebner {}
% \end{tcl}
% \setnamespace{Groebner}
% For lack of a better home for them, the procedures are placed in
% the |Groebner| namespace.
%
% \begin{tclcommand}{alias}{lexicographic}
% Simply returning the multidegree as sort key would be a
% lexicographic order, but reversing the multidegree might yield
% more convenient results. The rationale for this is that it makes
% the last variable the largest, which is desirable for two
% reasons. First, the rightmost position in a product of variables
% is often the most significant one (earlier variables are more
% coefficient-like). Second, variables are often spontaneously being
% enumerated with the most interesting first and conversely least
% interesting last; hence if one wishes to eliminate variables
% (which is the primary application of a lexicographic order) then
% one wants the last variable to be eliminated first, which means
% it must be the largest.
% \begin{tcl}
interp alias {} Groebner::lexicographic {} lreverse
% \end{tcl}
% \end{tclcommand}
%
% \begin{proc}{deglex}
% The degree-lexicographic order compares first by total degree,
% and then lexicographically as above. Since total degree and the
% first $n-1$ variable degrees determine the last variable degree,
% it becomes unnecessary to include that in the return value.
% \begin{tcl}
proc Groebner::deglex {degL} {
list [::tcl::mathop::+ {*}$degL] {*}[lreverse [lreplace $degL 0 0]]
}
% \end{tcl}
% \end{proc}
%
% \begin{proc}{degrevlex}
% The degree-reverse-lexicographic order compares first by total
% degree, and then by the negative of the variable degrees. Not
% reversing the multidegree list here results in the same relative
% order between the variables as for |deglex| and |lexicographic|,
% but the variable that should not contribute an element to the
% sort key here is the last (largest) one.
% \begin{tcl}
proc Groebner::degrevlex {degL} {
list [::tcl::mathop::+ {*}$degL]\
{*}[lmap d [lreplace $degL end end] {expr {-$d}}]
}
%</Groebner>
% \end{tcl}
% \end{proc}
%
%
% \section{Rewriting}
%
% Instead of plain polynomials, the elements of the (incomplete)
% Gr\"obner basis are kept as rewrite rules, i.e., pairs
% \begin{displaysyntax}
% \word{monomial} \word{polynomial}
% \end{displaysyntax}
% where the rule is that \word{monomial}${}\to{}$\word{polynomial}.
%
% Even if a rewrite system is perfectly fine to treat as a simple
% list of rules, there are certain advantages of storing it in a
% mutable object. One is that the most common use of a rewrite system
% is to have it \emph{act} upon a value, rather than to treat the
% rewrite system as a value itself. Another is that this set-up allows
% for advanced implementations where information is collected about
% which rules are most likely to be useful in which situations; this
% can have a notable effect on the practical performance, even if it
% likely does not change the asymptotics of anything.
%
% Turning a rewrite system into an opaque object does however raise
% the issue that this object has sufficient methods that one can run
% an efficient completion procedure against it. Minimum requirements
% are that rules can be added at any time and that the list of all
% rules can be retrieved (so that one may compute the new critical
% pairs), but in practice one also wants to be able to \emph{remove}
% rules that are subsumed by more recently added rules. This suggests
% that rules are assigned identifiers---not only to eliminate the
% problem of deciding when two rules are equal, but more importantly
% to make it practical to discard critical pairs formed using rules
% that have been removed.
%
% Therefore, a rewrite system is an object with the following methods:
% \begin{APIdescription}{rewrite system}
% \begin{APImethod}{reduce}
% \word{polynomial}
% \end{APImethod}
% This method reduces the \word{polynomial} to normal form modulo
% the rewrite system, and returns that normal form.
%
% \begin{APImethod}{addrule}
% \word{monomial} \word{polynomial}
% \end{APImethod}
% This method adds a new rule, whose left hand side is the
% \word{monomial} and whose right hand side is the
% \word{polynomial}. The return value is an identifier, which
% will be used in future calls to uniquely identify the rule just
% added; identifiers for rules that are removed will not be
% reused unless the rewrite system is |clear|ed in between.
%
% In addition, rewrite systems that make use of an order on the
% monomials should check that the proposed rule is compatible
% with that order, and throw an error if it is not.
%
% The purpose of assigning permanent identifiers to rules is to
% support running completion on the rewrite system; weeding out
% critical pairs corresponding to a dropped rule can be costly,
% but observing that the next critical pair on the list was
% formed from a rule that has subsequently been removed is fairly
% cheap.
%
% \begin{APImethod}{exists}
% \word{rule identifier}
% \end{APImethod}
% Returns |1| if the rule specified by the \word{rule identifier}
% still exists, |0| if it does not.
%
% \begin{APImethod}{get}
% \word{rule identifier}
% \end{APImethod}
% Returns the pair \word{monomial} \word{polynomial} of the rule
% specified by that identifier, or throws an error if that rule
% no longer exists.
%
% \begin{APImethod}{dump}
% \end{APImethod}
% This method returns the complete rewrite system as a list
% \begin{displaysyntax}
% \begin{regblock}[\regstar] \word{identifier} \word{monomial}
% \word{polynomial} \end{regblock}
% \end{displaysyntax}
%
% \begin{APImethod}{removerule}
% \word{rule identifier}
% \end{APImethod}
% This method makes sure the rule with that identifier are no
% longer part of the rewrite system. It returns |1| if the rule
% was removed, |0| if it didn't exist.
%
% \begin{APImethod}{clear}
% \end{APImethod}
% This method removes all rules from a rewrite system. Rule
% identifiers \emph{may} be reused again after clearing the
% rewrite system.
% \end{APIdescription}
%
% For didactical and proof\slash certificate purposes, it is also
% useful to have a method that records the history of a reduction. A
% natural format of this can be
% \begin{APIdescription}{rewrite system}
% \begin{APImethod}{reducerecord}
% \word{polynomial}
% \end{APImethod}
% Returns a pair
% \begin{displaysyntax}
% \word{reduced-polynomial} \word{record}
% \end{displaysyntax}
% where the \word{record} in turn is a list
% \begin{displaysyntax}
% \begin{regblock}[\regstar] \word{scalar} \word{monomial}
% \word{rule identifier} \end{regblock}
% \end{displaysyntax}
% The \word{reduced-polynomial} $g$ is the normal form of the
% \word{polynomial} $f$ modulo the \meta{rewrite system}, as with
% the |reduce| method. Writing $r_i$ for the $i$th \word{scalar},
% $\mu_i$ for the $i$th \word{monomial}, and $p_i$ for the
% difference $\mathit{lhs} - \mathit{rhs}$ of the rule with the
% $i$th \word{rule identifier}, the identity these data satisfy is
% \[
% f = g + \sum_i r_i \mu_i p_i
% \text{.}
% \]
% \end{APIdescription}
%
% Another useful thing is a method that builds a rule from just a
% polynomial, by separating the leading term to make a left hand
% side. In theory this does not require an integrated method, as the
% same end can be reached by making an |addrule| call, but that
% would mean the term order comparisons would unnecessarily be done
% twice. Hence there should also be a method
% \begin{APIdescription}{rewrite system}
% \begin{APImethod}{addrulefrompoly}
% \word{polynomial}
% \end{APImethod}
% This method adds a new rule, whose left hand side is the
% leading monomial of the \word{polynomial} and whose right hand
% side is such that the difference between left and right hand
% side is a scalar multiple of the \word{polynomial}. The return
% value is a pair
% \begin{displaysyntax}
% \word{rule identifier} \word{coefficient}
% \end{displaysyntax}
% where the first element is the identifier for the new rule and
% the second element is the coefficient that one would have to
% multiply the difference $\mathit{rhs} - \mathit{lhs}$ by to
% recover the \word{polynomial}. Errors are thrown
% only if the \word{polynomial} is zero or the leading
% coefficient of it is noninvertible.
%
% The reason the right hand side of the rule is taken as primary
% is that in this direction \emph{applying} the rule amounds to
% \emph{adding} (a multiple of) that polynomial; it's fewer
% negations, at least conceptually (whereas computationally those
% negations are typically folded into multiplications anyway).
% \end{APIdescription}
%
%
% \subsection{A rewrite system class}
%
% \setnamespace{Groebner}
% \begin{tclcommand}{class}{rewrite_system}
% The |Groebner::rewrite_system| class implements the above
% interface for rewrite systems. Objects are created with
% \begin{displaysyntax}
% |Groebner::rewrite_system| new \word{ring} \word{monoid}
% \word{heap} \word{order}
% \end{displaysyntax}
% where all four arguments are command prefixes implementing
% underlying structures or operations. The \word{ring} is a
% \APIref{ring}{2.0}. The \word{monoid} is a
%
% \begin{tcl}
%<*Groebner>
package require heap::skiplist 1.1
package require mtmtcl::rings::semigroup_algebra::accumulator 1.0
package require mtmtcl::rings::semigroup_algebra 1.1
namespace eval Groebner {}
oo::class create Groebner::rewrite_system
% \end{tcl}
%
% \setnamespace{\meta{rewrite~system}}
%
%
% \begin{variable}{Ring}
% \begin{variable}{Monoid}
% \begin{variable}{HeapPrefix}
% \begin{variable}{KeyPrefix}
% The |Ring|, |Monoid|, |HeapPrefix|, and |KeyPrefix| variables
% hold those arguments of the constructor.
% \begin{tcl}
oo::define Groebner::rewrite_system variable\
Ring Monoid HeapPrefix KeyPrefix
% \end{tcl}
% These are effectively constants in the rewrite system object,
% just as they are in the |semigroup_algebra::accumulator| class.