close Warning: Can't synchronize with repository "(default)" (The repository directory has changed, you should resynchronize the repository with: trac-admin $ENV repository resync '(default)'). Look in the Trac log for more information.

source: branches/f4grobner/division.lisp@ 4057

Last change on this file since 4057 was 4056, checked in by Marek Rychlik, 9 years ago

* empty log message *

File size: 11.0 KB
Line 
1;;; -*- Mode: Lisp -*-
2;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
3;;;
4;;; Copyright (C) 1999, 2002, 2009, 2015 Marek Rychlik <rychlik@u.arizona.edu>
5;;;
6;;; This program is free software; you can redistribute it and/or modify
7;;; it under the terms of the GNU General Public License as published by
8;;; the Free Software Foundation; either version 2 of the License, or
9;;; (at your option) any later version.
10;;;
11;;; This program is distributed in the hope that it will be useful,
12;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
13;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14;;; GNU General Public License for more details.
15;;;
16;;; You should have received a copy of the GNU General Public License
17;;; along with this program; if not, write to the Free Software
18;;; Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19;;;
20;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
21
22(defpackage "DIVISION"
23 (:use :cl :utils :monom :polynomial :grobner-debug)
24 (:export "$POLY_TOP_REDUCTION_ONLY"
25 "POLY-PSEUDO-DIVIDE"
26 "POLY-EXACT-DIVIDE"
27 "NORMAL-FORM-STEP"
28 "NORMAL-FORM"
29 "POLY-NORMALIZE"
30 "POLY-NORMALIZE-LIST"
31 "BUCHBERGER-CRITERION"
32 "GROBNER-TEST"
33 ))
34
35(in-package :division)
36
37(defvar $poly_top_reduction_only nil
38 "If not FALSE, use top reduction only whenever possible.
39Top reduction means that division algorithm stops after the first reduction.")
40
41;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
42;;
43;; An implementation of the division algorithm
44;;
45;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
46
47(defun grobner-op (c1 c2 m f g)
48 "Returns C2*F-C1*M*G, where F and G are polynomials M is a monomial.
49Assume that the leading terms will cancel."
50 (declare (type monom m)
51 (type poly f g))
52 #+grobner-check(universal-zerop
53 (subtract
54 (multiply c2 (leading-coefficient f))
55 (multiply c1 (leading-coefficient g))))
56 #+grobner-check(universal-equalp (leading-monomial f) (multiply m (leading-monomial g)))
57 ;; Note that below we can drop the leading terms of f ang g for the
58 ;; purpose of polynomial arithmetic.
59 ;;
60 ;; TODO: Make sure that the sugar calculation is correct if leading
61 ;; terms are dropped.
62 (subtract
63 (multiply c2 f)
64 (multiply c1 (multiply m g))))
65
66(defun check-loop-invariant (c f a fl r p
67 &aux
68 (p-zero 0)
69 (a (mapcar #'poly-reverse a))
70 (r (poly-reverse r)))
71 "Check loop invariant of division algorithms, when we divide a
72polynomial F by the list of polynomials FL. The invariant is the
73identity C*F=SUM AI*FI+R+P, where F0 is the initial value of F, A is
74the list of partial quotients, R is the intermediate value of the
75remainder, and P is the intermediate value which eventually becomes
760. A thing to remember is that the terms of polynomials in A and
77the polynomial R have their terms in reversed order. Hence, before
78the arithmetic is performed, we need to fix the order of terms"
79 #|
80 (format t "~&----------------------------------------------------------------~%")
81 (format t "#### Loop invariant check ####:~%C=~A~%F=~A~%A=~A~%FL=~A~%R=~A~%P=~A~%"
82 c f a fl r p)
83 |#
84 (let* ((prod (inner-product a fl #'add #'multiply 0))
85 (succeeded-p
86 (universal-zerop
87 (subtract
88 (multiply c f)
89 (reduce #'add (list prod r p))))))
90 (unless succeeded-p
91 (error "#### Polynomial division Loop invariant failed ####:~%C=~A~%F=~A~%A=~A~%FL=~A~%R=~A~%P=~A~%"
92 c f a fl r p))
93 succeeded-p))
94
95
96(defun poly-pseudo-divide (f fl)
97 "Pseudo-divide a polynomial F by the list of polynomials FL. Return
98multiple values. The first value is a list of quotients A. The second
99value is the remainder R. The third argument is a scalar coefficient
100C, such that C*F can be divided by FL within the ring of coefficients,
101which is not necessarily a field. Finally, the fourth value is an
102integer count of the number of reductions performed. The resulting
103objects satisfy the equation: C*F= sum A[i]*FL[i] + R. The sugar of
104the quotients is initialized to default."
105 (declare (type poly f) (list fl))
106 ;; Loop invariant: c*f=sum ai*fi+r+p, where p must eventually become 0
107 (do ((r (make-zero-for f))
108 (c (make-unit-for f))
109 (a (make-list (length fl) :initial-element (make-zero-for f)))
110 (division-count 0)
111 (p f))
112 ((universal-zerop p)
113 #+grobner-check(check-loop-invariant c f a fl r p)
114 (debug-cgb "~&~3T~d reduction~:p" division-count)
115 (when (universal-zerop r) (debug-cgb " ---> 0"))
116 ;; We obtained the terms in reverse order, so must fix that
117 (setf a (mapcar #'poly-nreverse a)
118 r (poly-nreverse r))
119 ;; Initialize the sugar of the quotients
120 ;; (mapc #'poly-reset-sugar a) ;; TODO: Sugar is currently unimplemented
121 (values a r c division-count))
122 (declare (fixnum division-count))
123 ;; Check the loop invariant here
124 #+grobner-check(check-loop-invariant c f a fl r p)
125 (do ((fl fl (rest fl)) ;scan list of divisors
126 (b a (rest b)))
127 ((cond
128 ((endp fl) ;no division occurred
129 (push (leading-term p) (poly-termlist r)) ;move lt(p) to remainder
130 ;;(setf (poly-sugar r) (max (poly-sugar r) (term-sugar (poly-lt p))))
131 (pop (poly-termlist p)) ;remove lt(p) from p
132 t)
133 ((divides-p (leading-monomial (car fl)) (leading-monomial p)) ;division occurred
134 (incf division-count)
135 (multiple-value-bind (gcd c1 c2)
136 (universal-ezgcd (leading-coefficient (car fl)) (leading-coefficient p))
137 (declare (ignore gcd))
138 (let ((m (divide (leading-monomial p) (leading-monomial (car fl)))))
139 ;; Multiply the equation c*f=sum ai*fi+r+p by c1.
140 (mapl #'(lambda (x)
141 (setf (car x) (multiply c1 (car x))))
142 a)
143 (setf r (multiply c1 r)
144 c (multiply c c1)
145 p (grobner-op c2 c1 m p (car fl)))
146 (push (make-term :monom m :coeff c2) (poly-termlist (car b))))
147 t))))
148 )))
149
150(defun poly-exact-divide (f g)
151 "Divide a polynomial F by another polynomial G. Assume that exact division
152with no remainder is possible. Returns the quotient."
153 (declare (type poly f g))
154 (multiple-value-bind (quot rem coeff division-count)
155 (poly-pseudo-divide f (list g))
156 (declare (ignore division-count coeff)
157 (list quot)
158 (type poly rem)
159 (type fixnum division-count))
160 (unless (universal-zerop rem) (error "Exact division failed."))
161 (car quot)))
162
163;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
164;;
165;; An implementation of the normal form
166;;
167;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
168
169(defun normal-form-step (fl p r c division-count
170 &aux
171 (g (find (leading-monomial p) fl
172 :test #'divisible-by-p
173 :key #'leading-monomial)))
174 (cond
175 (g ;division possible
176 (incf division-count)
177 (multiple-value-bind (gcd cg cp)
178 (universal-ezgcd (leading-coefficient g) (leading-coefficient p))
179 (declare (ignore gcd))
180 (let ((m (divide (leading-monomial p) (leading-monomial g))))
181 ;; Multiply the equation c*f=sum ai*fi+r+p by cg.
182 (setf r (multiply cg r)
183 c (multiply c cg)
184 ;; p := cg*p-cp*m*g
185 p (grobner-op cp cg m p g))))
186 (debug-cgb "/"))
187 (t ;no division possible
188 (push (leading-term p) (poly-termlist r)) ;move lt(p) to remainder
189 ;;(setf (poly-sugar r) (max (poly-sugar r) (term-sugar (poly-lt p))))
190 (pop (poly-termlist p)) ;remove lt(p) from p
191 (debug-cgb "+")))
192 (values p r c division-count))
193
194;;
195;; Merge NORMAL-FORM someday with POLY-PSEUDO-DIVIDE.
196;;
197;; TODO: It is hard to test normal form as there is no loop invariant,
198;; like for POLY-PSEUDO-DIVIDE. Is there a testing strategy? One
199;; method would be to test NORMAL-FORM using POLY-PSEUDO-DIVIDE.
200;;
201(defun normal-form (f fl
202 &optional
203 (top-reduction-only $poly_top_reduction_only))
204 #+grobner-check(when (null fl) (warn "normal-form: empty divisor list."))
205 (do ((r (make-zero-for f))
206 (c (make-zero-for f))
207 (division-count 0))
208 ((or (universal-zerop f)
209 ;;(endp fl)
210 (and top-reduction-only (not (universal-zerop r))))
211 (progn
212 (debug-cgb "~&~3T~D reduction~:P" division-count)
213 (when (universal-zerop r)
214 (debug-cgb " ---> 0")))
215 (setf (poly-termlist f) (nreconc (poly-termlist r) (poly-termlist f)))
216 (values f c division-count))
217 (declare (fixnum division-count)
218 (type poly r))
219 (multiple-value-setq (f r c division-count)
220 (normal-form-step fl f r c division-count))))
221
222(defun spoly (f g)
223 "It yields the S-polynomial of polynomials F and G."
224 (declare (type poly f g))
225 (let* ((lcm (universal-lcm (leading-monomial f) (leading-monomial g)))
226 (mf (divide lcm (leading-monomial f)))
227 (mg (divide lcm (leading-monomial g))))
228 (declare (type monom mf mg))
229 (multiple-value-bind (c cf cg)
230 (universal-ezgcd (leading-coefficient f) (leading-coefficient g))
231 (declare (ignore c))
232 (subtract
233 (multiply cg (multiply mf f))
234 (multiply cf (multiply mg g))))))
235
236(defun buchberger-criterion (g)
237 "Returns T if G is a Grobner basis, by using the Buchberger
238criterion: for every two polynomials h1 and h2 in G the S-polynomial
239S(h1,h2) reduces to 0 modulo G."
240 (every #'universal-zerop
241 (makelist (normal-form (spoly (elt g i) (elt g j)) g nil)
242 (i 0 (- (length g) 2))
243 (j (1+ i) (1- (length g))))))
244
245
246(defun poly-normalize (p &aux (c (leading-coefficient p)))
247 "Divide a polynomial by its leading coefficient. It assumes
248that the division is possible, which may not always be the
249case in rings which are not fields. The exact division operator
250is assumed to be provided by the RING structure."
251 (mapc #'(lambda (term)
252 (setf (term-coeff term) (divide (term-coeff term) c)))
253 (poly-termlist p))
254 p)
255
256(defun poly-normalize-list (plist)
257 "Divide every polynomial in a list PLIST by its leading coefficient. "
258 (mapcar #'(lambda (x) (poly-normalize x)) plist))
259
260;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
261;;
262;; The function GROBNER-TEST is provided primarily for debugging purposes. To
263;; enable verification of grobner bases with BUCHBERGER-CRITERION, do
264;; (pushnew :grobner-check *features*) and compile/load this file.
265;; With this feature, the calculations will slow down CONSIDERABLY.
266;;
267;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
268
269(defun grobner-test (g f)
270 "Test whether G is a Grobner basis and F is contained in G. Return T
271upon success and NIL otherwise."
272 (debug-cgb "~&GROBNER CHECK: ")
273 (let (($poly_grobner_debug nil)
274 (stat1 (buchberger-criterion g))
275 (stat2
276 (every #'universal-zerop
277 (makelist (normal-form (copy-tree (elt f i)) g nil)
278 (i 0 (1- (length f)))))))
279 (unless stat1 (error "~&Buchberger criterion failed, not a grobner basis: ~A" g))
280 (unless stat2
281 (error "~&Original polynomials not in ideal spanned by Grobner basis: ~A" f)))
282 (debug-cgb "~&GROBNER CHECK END")
283 t)
Note: See TracBrowser for help on using the repository browser.