1 (* rationals, i.e. fractions of multivariate polynomials over the real field
4 Use is subject to license terms.
5 1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890
6 10 20 30 40 50 60 70 80 90 100
9 theory GCD_Poly_ML imports Complex_Main begin
13 F. Winkler, Polynomial algorithyms in computer algebra. Springer 1996.
14 by page 93 and 95. This is the final version of the Isabelle theory,
15 which conforms with Isabelle's coding standards and with requirements
16 in terms of logics appropriate for a theorem prover.
19 section {* Predicates for the specifications *}
21 This thesis provides program code to be integrated into the Isabelle
22 distribution. Thus the code will be verified against specifications, and
23 consequently the code, as \emph{explicit} specification of how to calculate
24 results, is accompanied by \emph{implicit} specifications describing the
25 properties of the program code.
27 For describing the properties of code the format of Haskell will be used.
28 Haskell \cite{TODO} is front-of-the-wave in research on functional
29 programming and as such advances beyond SML. The advancement relevant for
30 this thesis is the concise combination of explicit and implicit specification
31 of a function. The final result of the code developed within this thesis is
32 described as follows in Haskell format:
34 \begin{verbatim} % TODO: find respective Isabelle/LaTeX features
35 gcd_poly :: int poly \<Rightarrow> int poly \<Rightarrow> int poly
37 assumes and p1 \<noteq> [:0:] and p2 \<noteq> [:0:]
38 yields d dvd p1 \<and> d dvd p2 \<and> \<forall>d'. (d' dvd p1 \<and> d' dvd p2) \<longrightarrow> d' \<le> d
41 The above specification combines Haskell format with the term language of
42 Isabelle\HOL. The latter provides a large and ever growing library of
43 mechanised mathematics knowledge, which this thesis re-uses in order to
44 prepare for integration of the code.
46 In the sequel there is a brief account of the notions required for the
47 specification of the functions implemented within this thesis. *}
49 subsection {* The Isabelle types required *}
51 bool, nat, int, option, THE, list, 'a poly
54 subsection {* The connectives required *}
56 logic ~~/doc/tutorial.pdf p.203
57 arithmetic < \<le> + - * ^ div \<bar>_\<bar>
60 subsection {* The Isabelle functions required *}
65 Primes.next_prime_bound
69 List.length, List.member
71 List.nth, List.take, List.drop
78 section {* gcd for univariate polynomials *}
80 type unipoly = int list;
81 val a = [~18, ~15, ~20, 12, 20, ~13, 2];
82 val b = [8, 28, 22, ~11, ~14, 1, 2];
85 subsection {* auxiliary functions *}
88 5 div 2 = 2; ~5 div 2 = ~3; BUT WEE NEED ~5 div2 2 = ~2; *)
91 if a div b < 0 then (abs a) div (abs b) * ~1 else a div b
93 (* why has "last" been removed since 2002, although "last" is in List.thy ? *)
94 fun last ls = (hd o rev) ls
96 (* drop tail elements equal 0 *)
97 fun drop_hd_zeros (0 :: ps) = drop_hd_zeros ps
99 fun drop_tl_zeros p = (rev o drop_hd_zeros o rev) p;
101 (* swaps the arguments of a function *)
102 fun swapf f a b = f b a
105 subsection {* modulo calculations for integers *}
107 (* mod_inv :: int \<Rightarrow> nat
110 yields r * s mod m = 1 *)
113 fun modi (r, rold, m, rinv) =
114 if m <= rinv then 0 else
117 else modi (rold * (rinv + 1), rold, m, rinv + 1)
118 in modi (r, r, m, 1) end
120 (* mod_div :: int \<Rightarrow> int \<Rightarrow> nat \<Rightarrow> natO
123 yields a * b ^(-1) mod m = c <\<Longrightarrow> a mod m = (b * c) mod m*)
124 fun mod_div a b m = a * (mod_inv b m) mod m
126 (* required just for one approximation:
127 approx_root :: nat \<Rightarrow> nat
130 yields r * r \<ge> a *)
133 fun root1 a n = if n * n < a then root1 a (n + 1) else n
136 (* chinese_remainder :: int * nat \<Rightarrow> int * nat \<Rightarrow> nat
137 chinese_remainder (r1, m1) (r2, m2) = r
139 yields r = r1 mod m1 \<and> r = r2 mod m2 *)
140 fun chinese_remainder (r1, m1) (r2, m2) =
142 (((r2 - (r1 mod m1)) * (mod_inv m1 m2)) mod m2) *
146 subsection {* creation of lists of primes for efficiency *}
148 (* is_prime :: int list \<Rightarrow> int \<Rightarrow> bool
150 assumes max ps < n \<and> n \<le> (max ps)^2 \<and>
151 (* FIXME: really ^^^^^^^^^^^^^^^? *)
152 (\<forall>p. List.member ps p \<longrightarrow> Primes.prime p) \<and> (\<forall>p. p < n \<and> Primes.prime p \<longrightarrow> List.member ps p)
153 yields Primes.prime n *)
154 fun is_prime [] _ = true
156 if n mod (List.hd prs) <> 0 then is_prime (List.tl prs) n else false
158 (* make_primes is just local to primes_upto only:
159 make_primes :: int list \<Rightarrow> int \<Rightarrow> int \<Rightarrow> int list
160 make_primes ps last_p n = pps
161 assumes last_p = maxs ps \<and> (\<forall>p. List.member ps p \<longrightarrow> Primes.prime p) \<and>
162 (\<forall>p. p < last_p \<and> Primes.prime p \<longrightarrow> List.member ps p)
163 yields n \<le> maxs pps \<and> (\<forall>p. List.member pps p \<longrightarrow> Primes.prime p) \<and>
164 (\<forall>p. (p < maxs pps \<and> Primes.prime p) \<longrightarrow> List.member pps p)*)
165 fun make_primes ps last_p n =
166 if n <= last ps then ps else
167 if is_prime ps (last_p + 2)
168 then make_primes (ps @ [(last_p + 2)]) (last_p + 2) n
169 else make_primes ps (last_p + 2) n
171 (* primes_upto :: nat \<Rightarrow> nat list
174 yields (\<forall>p. List.member ps p \<longrightarrow> Primes.prime p) \<and>
175 n \<le> maxs ps \<and> maxs ps \<le> Fact.fact n + 1 \<and>
176 (\<forall>p. p \<le> maxs ps \<and> Primes.prime p \<longrightarrow> List.member ps p) *)
177 fun primes_upto n = if n < 3 then [2] else make_primes [2, 3] 3 n
179 (* find the next prime greater p not dividing the number n:
180 next_prime_not_dvd :: int \<Rightarrow> int \<Rightarrow> int (infix)
181 n1 next_prime_not_dvd n2 = p
183 yields p is_prime \<and> n1 < p \<and> \<not> p dvd n2 \<and> (* smallest with these properties... *)
184 (\<forall> p'. (p' is_prime \<and> n1 < p' \<and> \<not> p' dvd n2) \<longrightarrow> p \<le> p') *)
185 infix next_prime_not_dvd;
186 fun n1 next_prime_not_dvd n2 =
188 val ps = primes_upto (n1 + 1)
189 val nxt = nth ps (List.length ps - 1);
191 if n2 mod nxt <> 0 then nxt else nxt next_prime_not_dvd n2
195 subsection {* basic notions for univariate polynomials *}
197 (* leading coefficient, includes drop_lc0_up ?FIXME130507 *)
198 fun lcoeff_up (p: unipoly) = (last o drop_tl_zeros) p
200 (* drop leading coefficients equal 0 *)
201 fun drop_lc0_up (p: unipoly) =drop_tl_zeros p : unipoly;
203 (* degree, includes drop_lc0_up ?FIXME130507 *)
204 fun deg_up (p: unipoly) = ((Integer.add ~1) o length o drop_lc0_up) p;
206 (* normalise a unipoly such that lcoeff_up mod m = 1.
207 normalise :: unipoly \<Rightarrow> nat \<Rightarrow> unipoly
208 normalise [p_0, .., p_k] m = [q_0, .., q_k]
210 yields \<exists> t. 0 \<le> i \<le> k \<Rightarrow> (p_i * t) mod m = q_i \<and> (p_k * t) mod m = 1 *)
211 fun normalise (p: unipoly) (m: int) =
213 val p = drop_lc0_up p
214 val lcp = lcoeff_up p;
217 then [mod_div (nth p i) lcp m] @ nrm
218 else norm ([mod_div (nth p i) lcp m] @ nrm) (i - 1) ;
220 if List.length p = 0 then p else norm [] (List.length p - 1)
224 subsection {* addition, multiplication, division *}
226 (* scalar multiplication *)
228 fun (p: unipoly) %* n = map (Integer.mult n) p : unipoly
232 fun (p: unipoly) %/ n = map (swapf (curry op div2) n) p : unipoly
236 fun (p1: unipoly) %-% (p2: unipoly) =
237 map2 (curry op -) p1 (p2 @ replicate (List.length p1 - List.length p2) 0) : unipoly
239 (* dvd for univariate polynomials *)
241 fun [d] %|% [p] = (p mod d = 0)
242 | (ds: unipoly) %|% (ps: unipoly) =
244 val ds = drop_lc0_up ds;
245 val ps = drop_lc0_up ps;
246 val d000 = (replicate (List.length ps - List.length ds) 0) @ ds ;
247 val quot = (lcoeff_up ps) div2 (lcoeff_up d000);
248 val remd = drop_lc0_up (ps %-% (d000 %* quot));
250 if remd = [] then true else
251 if remd <> [] andalso quot <> 0 andalso List.length ds <= List.length remd
257 subsection {* normalisation and Landau-Mignotte bound *}
259 (* normalise :: centr_up \<Rightarrow> unipoly => int \<Rightarrow> unipoly
260 normalise [p_0, .., p_k] m = [q_0, .., q_k]
262 yields 0 \<le> i \<le> k \<Rightarrow> |^ ~m/2 ^| <= q_i <=|^ m/2 ^|
263 (where |^ x ^| means round x up to the next greater integer) *)
264 fun centr_up (p: unipoly) (m: int) =
267 val mid = if m mod mi = 0 then mi else mi + 1;
268 fun centr m mid p_i = if mid < p_i then p_i - m else p_i;
269 in map (centr m mid) p : unipoly end;
271 (*** landau mignotte bound (Winkler p91)
272 every coefficient of the gcd of a and b in Z[x] is bounded in absolute value by
273 2^{min(m,n)} * gcd(a_m,b_n) * min(1/|a_m|
274 * root(sum_{i=0}^{m}a_i^2) ,1/|b_n| * root( sum_{i=0}^{n}b_i^2 ) ***)
276 (* sum_lmb :: centr_up \<Rightarrow> unipoly \<Rightarrow> int \<Rightarrow> int
277 sum_lmb [p_0, .., p_k] e = s
279 yields. p_0^e + p_1^e + ... + p_k^e *)
280 fun sum_lmb (p: unipoly) exp = fold ((curry op +) o (Integer.pow exp)) p 0;
282 (* LANDAU_MIGNOTTE_bound :: centr_up \<Rightarrow> unipoly \<Rightarrow> unipoly \<Rightarrow> int
283 LANDAU_MIGNOTTE_bound [a_0, ..., a_m] [b_0, .., b_n] = lmb
285 yields lmb = 2^min(m,n) * gcd(a_m,b_n) *
286 min( 1/|a_m| * root(sum_lmb [a_0,...a_m] 2 , 1/|b_n| * root(sum_lmb [b_0,...b_n] 2)*)
287 fun LANDAU_MIGNOTTE_bound (p1: unipoly) (p2: unipoly) =
288 (Integer.pow (Integer.min (deg_up p1) (deg_up p2)) 2) * (abs (Integer.gcd (lcoeff_up p1) (lcoeff_up p2))) *
289 (Int.min (abs((approx_root (sum_lmb p1 2)) div2 ~(lcoeff_up p1)),
290 abs((approx_root (sum_lmb p2 2)) div2 ~(lcoeff_up p2))));
293 subsection {* modulo calculations on polynomials *}
295 (* chinese_remainder_up :: int * int \<Rightarrow> unipoly * unipoly \<Rightarrow> unipoly
296 chinese_remainder_up (m1, m2) (p1, p2) = p
297 assume m1, m2 relatively prime
298 yields p1 = p mod m1 \<and> p2 = p mod m2 *)
299 fun chinese_remainder_up (m1, m2) (p1: unipoly, p2: unipoly) =
301 fun chinese_rem (m1, m2) (p_1i, p_2i) = chinese_remainder (p_1i, m1) (p_2i, m2)
302 in map (chinese_rem (m1, m2)) (p1 ~~ p2): unipoly end
304 (* mod_up :: unipoly \<Rightarrow> int \<Rightarrow> unipoly
305 mod_up [p1, p2, ..., pk] m = up
307 yields up = [p1 mod m, p2 mod m, ..., pk mod m]*)
309 fun (p : unipoly) mod_up m =
311 fun mod' m p = (curry op mod) p m
312 in map (mod' m) p : unipoly end
314 (* euclidean algoritm in Z_p[x].
315 mod_up_gcd :: unipoly \<Rightarrow> unipoly \<Rightarrow> int \<Rightarrow> unipoly
316 mod_up_gcd p1 p2 m = g
318 yields gcd ((p1 mod m), (p2 mod m)) = g *)
319 fun mod_up_gcd (p1: unipoly) (p2: unipoly) (m: int) =
321 val p1m = p1 mod_up m;
322 val p2m = drop_lc0_up (p2 mod_up m);
323 val p2n = (replicate (List.length p1 - List.length p2m) 0) @ p2m;
324 val quot = mod_div (lcoeff_up p1m) (lcoeff_up p2n) m;
325 val remd = drop_lc0_up ((p1m %-% (p2n %* quot)) mod_up m)
327 if remd = [] then p2 else
328 if length remd < List.length p2
329 then mod_up_gcd p2 remd m
330 else mod_up_gcd remd p2 m
333 (* primitive_up :: unipoly \<Rightarrow> unipoly
336 yields \<forall>p1, p2. (List.member pp p1 \<and> List.member pp p2 \<and> p1 \<noteq> p2) \<longrightarrow> gcd p1 p2 = 1 *)
337 fun primitive_up ([c] : unipoly) =
338 if c = 0 then ([0] : unipoly) else ([1] : unipoly)
341 val d = abs (Integer.gcds p)
343 if d = 1 then p else p %/ d
347 subsection {* gcd_up, code from [1] p.93 *}
349 (* try_new_prime_up :: unipoly \<Rightarrow> unipoly \<Rightarrow> int \<Rightarrow> int \<Rightarrow> int \<Rightarrow> unipoly \<Rightarrow> int \<Rightarrow> unipoly
350 try_new_prime_up p1 p2 d M P g p = new_g
351 assumes d = gcd (lcoeff_up p1, lcoeff_up p2) \<and>
352 M = LANDAU_MIGNOTTE_bound \<and> p = prime \<and> p ~| d \<and> P \<ge> p \<and>
353 p1 is primitive \<and> p2 is primitive
354 yields new_g = [1] \<or> (new_g \<ge> g \<and> P > M)
356 argumentns "a b d M P g p" named according to [1] p.93 *)
357 fun try_new_prime_up a b d M P g p =
360 val p = p next_prime_not_dvd d
361 val g_p = centr_up ( ( (normalise (mod_up_gcd a b p) p)
366 if deg_up g_p < deg_up g
368 if (deg_up g_p) = 0 then [1]: unipoly else try_new_prime_up a b d M p g_p p
370 if deg_up g_p <> deg_up g then try_new_prime_up a b d M P g p else
373 val g = centr_up ((chinese_remainder_up (P, p) (g, g_p)) mod_up P) P
374 in try_new_prime_up a b d M P g p end
377 (* HENSEL_lifting_up :: unipoly \<Rightarrow> unipoly \<Rightarrow> int \<Rightarrow> int \<Rightarrow> int \<Rightarrow> unipoly
378 HENSEL_lifting_up p1 p2 d M p = gcd
379 assumes d = gcd (lcoeff_up p1, lcoeff_up p2) \<and>
380 M = LANDAU_MIGNOTTE_bound \<and> p = prime \<and> p ~| d \<and>
381 p1 is primitive \<and> p2 is primitive
382 yields gcd | p1 \<and> gcd | p2 \<and> gcd is primitive
384 argumentns "a b d M p" named according to [1] p.93 *)
385 fun HENSEL_lifting_up a b d M p =
387 val p = p next_prime_not_dvd d
388 val g_p = centr_up (((normalise (mod_up_gcd a b p) p) %* (d mod p)) mod_up p) p
389 (*.. nesting of functions see above*)
391 if deg_up g_p = 0 then [1] else
393 val g = primitive_up (try_new_prime_up a b d M p g_p p)
395 if (g %|% a) andalso (g %|% b) then g:unipoly else HENSEL_lifting_up a b d M p
399 (* gcd_up :: unipoly \<Rightarrow> unipoly \<Rightarrow> unipoly
401 assumes not (a = [] \<or> a = [0]) \<and> not (b = []\<or> b = [0]) \<and>
402 a is primitive \<and> b is primitive
403 yields c dvd a \<and> c dvd b \<and> (\<forall>c'. (c' dvd a \<and> c' dvd b) \<longrightarrow> c' \<le> c) *)
405 let val d = abs (Integer.gcd (lcoeff_up a) (lcoeff_up b))
408 HENSEL_lifting_up a b d (2 * d * LANDAU_MIGNOTTE_bound a b) 1
412 section {* gcd for multivariate polynomials *}
414 type monom = (int * int list);
415 type poly = monom list;
418 subsection {* auxiliary functions *}
420 fun land a b = a andalso b
421 fun all_geq a b = fold land (map2 (curry op >=) a b) true
422 fun all_geq' i es = fold land (map ((curry op <=) i) es) true
423 fun maxs is = fold Integer.max is (hd is);
424 fun div2_swapped d i = i div2 d
425 fun nth_swapped i ls = nth ls i;
426 fun drop_last ls = (rev o tl o rev) ls
427 fun nth_ins n i xs = (*analogous to nth_drop: why not in library.ML?*)
428 List.take (xs, n) @ [i] @ List.drop (xs, n);
429 fun coeffs (p : poly) = map fst p
432 subsection {* basic notions for multivariate polynomials *}
434 fun lcoeff (p: poly) = (fst o hd o rev) p
435 fun lexp (p: poly) = (snd o hd o rev) p
436 fun lmonom (p: poly) = (hd o rev) p
438 fun zero_poly (nro_vars: int) = [(0, replicate nro_vars 0)] : poly;
440 (* drop leading coefficients equal 0 TODO: signature?*)
441 fun drop_lc0 (p: poly as [(c, es)]) = if c = 0 then zero_poly (length es) else p
442 | drop_lc0 p = if lcoeff p = 0 then drop_lc0 (drop_last p) else p
444 (* gcd of coefficients WN find right location in file *)
445 fun gcd_coeff (up: unipoly) = abs (Integer.gcds up)
447 (* given p = 3x+4z = [(3,[1,0]),(4,[0,1])] with x<z
448 add_variable p 2 => [(3,[1,0,0]),(4,[0,0,1])] with x<y<z *)
449 fun add_variable pos (p: poly) = map (apsnd (nth_ins pos 0)) p : poly
452 subsection {* ordering and other auxiliary funcions *}
454 (* monomial order: lexicographic order on exponents *)
455 fun lex_ord ((_, a): monom) ((_, b): monom) =
456 let val d = drop_tl_zeros (a %-% b)
458 if d = [] then false else last d > 0
460 fun lex_ord' ((_, a): monom, (_, b): monom) =
461 let val d = drop_tl_zeros (a %-% b)
464 else if last d > 0 then GREATER else LESS
467 (* add monomials in ordered polynomal *)
468 fun add_monoms (p: poly) =
470 fun add [(0, es)] [] = zero_poly (length es)
471 | add [(0, _)] (new: poly) = new
472 | add [m] (new: poly) = new @ [m]
473 | add ((c1, es1) :: (c2, es2) :: ms) (new: poly) =
475 then add ([(c1 + c2, es1)] @ ms) new
478 then add ((c2, es2) :: ms) new
479 else add ((c2, es2) :: ms) (new @ [(c1, es1)])
480 in add p [] : poly end
482 (* brings the polynom in correct order and adds monomials *)
483 fun order (p: poly) = (add_monoms o (sort lex_ord')) p;
485 (* largest exponent of variable at location var *)
486 fun max_deg_var (p: poly) var = (maxs o (map ((nth_swapped var) o snd))) p;
489 subsection {* evaluation, translation uni--multivariate, etc *}
491 (* evaluate a polynomial in a certain variable and remove this var from the exp-list *)
492 fun eval_monom var value ((c, es): monom) =
493 (c * (Integer.pow (nth es var) value), nth_drop var es) : monom
494 fun eval (p: poly) var value = order (map (eval_monom var value) p)
496 (* transform a multivariate to a univariate polynomial TODO map?*)
497 fun multi_to_uni (p as (_, es)::_ : poly) =
500 let fun transfer ([]: poly) (up: unipoly) = up
501 | transfer (mp as (c, es)::ps) up =
503 then transfer ps (up @ [c])
504 else transfer mp (up @ [0])
505 in transfer (order p) [] end
506 else error "Polynom has more than one variable!";
508 (* transform a univariate to a multivariate polynomial TODO map *)
509 fun uni_to_multi (up: unipoly) =
510 let fun transfer ([]: unipoly) (mp: poly) (_: int) = add_monoms mp
511 | transfer up mp var =
512 transfer (tl up) (mp @ [(hd up, [var])]) (var + 1)
513 in transfer up [] 0 end
515 (* multiply a poly with a variable represented by a unipoly (at position var)
516 e.g new z: (3x + 2y)*(z - 4) TODO mp %%*%% (uni_to_multi up) !TODO equal es ! *)
517 fun mult_with_new_var ([]: poly) (_: unipoly) (_: int) = []
518 | mult_with_new_var mp up var =
520 fun mult ([]: poly) (_: unipoly) (_: int) (new: poly) (_: int) = add_monoms new
521 | mult mp up var new _ =
524 fun mult' (_: poly) ([]: unipoly) (_) (new': poly) (_: int) = order new'
525 | mult' (mp' as (c, es)::ps) up' var new' c2' =
526 let val (first, last) = chop var es
527 in mult' mp' (tl up') var
528 (new' @ [(c * hd up', first @ [c2'] @ last)]) (c2' + 1)
530 in mult (tl mp) up var (new @ (mult' mp up var [] 0)) (0) end
531 in mult mp up var [] 0 : poly end
533 fun greater_deg (es1: int list) (es2: int list) =
534 if es1 = [] andalso es2 =[] then 0
535 else if (nth es1 (length es1 -1)) = (nth es2 (length es1 -1) )
536 then greater_deg (nth_drop (length es1 -1) es1) (nth_drop (length es1 -1) es2)
537 else if (nth es1 (length es1 -1)) > (nth es2 (length es1 -1))
541 fun find_new_r (p1 as (_, es1)::_ : poly) (p2 as (_, es2)::_ : poly) old =
542 let val p1' as (_, es1')::_ = eval p1 (length es1 - 2) (old + 1)
543 val p2' as (_, es2')::_ = eval p2 (length es2 - 2) (old + 1);
545 if max_deg_var p1' (length es1' - 1) = max_deg_var p1 (length es1 - 1) orelse
546 max_deg_var p2' (length es2' - 1) = max_deg_var p2 (length es1 - 1)
548 else find_new_r p1 p2 (old + 1)
552 subsection {* addition, multiplication, division *}
554 (* resulting 0 coefficients are removed by add_monoms *)
556 fun (p: poly) %%/ d = (add_monoms o (map (apfst (div2_swapped d)))) p;
559 fun (p: poly) %%* i = (add_monoms o (map (apfst (Integer.mult i)))) p;
562 (* the same algorithms as done by hand
563 fun ([]: poly) %%+%% (mvp2: poly) = mvp2
567 fun plus [] p2 new = order (new @ p2)
568 | plus p1 [] new = order (new @ p1)
569 | plus (p1 as (c1, es1)::ps1) (p2 as (c2, es2)::ps2) new =
570 if greater_deg es1 es2 = 0
571 then plus ps1 ps2 (new @ [((c1 + c2), es1)])
573 if greater_deg es1 es2 = 1
574 then plus p1 ps2 (new @ [(c2, es2)])
575 else plus ps1 p2 (new @ [(c1, es1)])
578 fun (p1 : poly) %%+%% (p2 : poly) = order (p1 @ p2)
581 fun p1 %%-%% (p2: poly) = p1 %%+%% (map (apfst (Integer.mult ~1)) p2)
584 fun mult_monom ((c1, es1): monom) ((c2, es2): monom) =
585 (c1 * c2, map2 Integer.add es1 es2): monom;
586 fun mult_monoms (ms: poly) m = map (mult_monom m) ms : poly
587 fun p1 %%*%% (p2 : poly) = order (fold (curry op @) (map (mult_monoms p1) p2) [])
590 (* the same algorithms as done by hand; TODO fold / map ? *)
591 fun ([(c1, es1)]: poly) %%|%% ([(c2, es2)]: poly) = all_geq es2 es1 andalso c2 mod c1 = 0
592 | _ %%|%% [(0,_)] = true
594 if [lmonom p1] %%|%% [lmonom p2]
598 ([((lcoeff p2) div2 (lcoeff p1), (lexp p2) %-% (lexp p1))]
602 (* %%/%% :: poly \<Rightarrow> poly \<Rightarrow> (poly \<times> poly)
604 assumes p2 \<noteq> [] \<and> deg p1 \<ge> deg p2
605 yields THE q r. p1 = q *\<^isub>p p2 +\<^isub>p r *)
607 fun (p1: poly) %%/%% (p2: poly) =
609 fun div_up p1 p2 quot_old =
611 val p1 as (c, es)::_ = drop_lc0 (order p1);
612 val p2 = drop_lc0 (order p2);
613 val [c] = zero_poly (length es);
614 val d = (replicate (List.length p1 - List.length p2) c) @ p2 ;
615 val quot = [(lcoeff p1 div2 lcoeff d, lexp p1 %-% lexp p2)];
616 val remd = drop_lc0 (p1 %%-%% (d %%*%% quot));
618 if remd <> [c] andalso all_geq' 0 (lexp remd %-% lexp p2)
619 then div_up remd p2 (quot @ quot_old)
620 else (quot @ quot_old : poly, remd)
622 in div_up p1 p2 [] end
625 subsection {* Newton interpolation *}
626 ML{* (* first step *)
627 fun newton_first (x: int list) (f: poly list) (pos: int) =
629 val new_vals = [(hd x) * ~1, 1];
630 val p = (add_variable pos (hd f)) %%+%%
632 (((nth f 1) %%-%% (hd f)) %%/ (nth x 1) - (hd x))) new_vals pos);
633 val steps = [((nth f 1) %%-%% (hd f)) %%/ ((nth x 1) - (hd x))]
634 in (p, new_vals, steps) end
636 fun next_step ([]: poly list) (new_steps: poly list) (_: int list) = new_steps
637 | next_step steps new_steps x =
640 [((last new_steps) %%-%% (hd steps)) %%/ ((last x) - (nth x (length x - 3)))])
641 (nth_drop (length x -2) x)
643 fun NEWTON x f (steps: poly list) (up: unipoly) (p: poly) pos =
646 let val (p', new_vals, steps) = newton_first x [(hd f), (nth f 1)] pos
647 in (p', new_vals, steps) end
651 multi_to_uni ((uni_to_multi up) %%*%% (uni_to_multi [(nth x (length x -2) ) * ~1, 1]));
652 val new_steps = [((nth f (length f - 1)) %%-%% (nth f (length f - 2))) %%/
653 ((last x) - (nth x (length x - 2)))];
654 val steps = next_step steps new_steps x;
655 val p' = p %%+%% (mult_with_new_var (last steps) new_vals pos);
656 in (order p', new_vals, steps) end
659 subsection {* gcd_poly algorithm, code for [1] p.95 *}
661 fun gcd_monom (c1, es1) (c2, es2) = (Integer.gcd c1 c2, map2 Integer.min es1 es2)
663 (* naming of n, M, m, r, ... follows Winkler p. 95
664 assumes: n = length es1 = length es2
666 fun gcd_poly' a [monom as (c, es)] _ _ = [fold gcd_monom a monom]
667 | gcd_poly' [monom as (c, es)] b _ _ = [fold gcd_monom b monom]
668 | gcd_poly' (a as (_, es1)::_ : poly) (b as (_, es2)::_ : poly) n r =
669 if lex_ord (lmonom b) (lmonom a) then gcd_poly' b a n r else
672 let val M = 1 + Int.min (max_deg_var a (length es1 - 2), max_deg_var b (length es2 - 2));
673 in try_new_r a b n M r [] [] end
675 let val (a', b') = (multi_to_uni a, multi_to_uni b)
677 (* a b are made primitive and this is compensated by multiplying with gcd of divisors *)
678 uni_to_multi ((gcd_up (primitive_up a') (primitive_up b')) %*
679 (abs (Integer.gcd (gcd_coeff a') (gcd_coeff b'))))
682 (* fn: poly -> poly -> int ->
683 int -> int -> int list -> poly list -> poly
684 assumes length a > 1 \<and> length b > 1
687 and try_new_r a b n M r r_list steps =
689 val r = find_new_r a b r;
690 val r_list = r_list @ [r];
691 val g_r = gcd_poly' (order (eval a (n - 2) r))
692 (order (eval b (n - 2) r)) (n - 1) 0
693 val steps = steps @ [g_r];
694 in HENSEL_lifting a b n M 1 r r_list steps g_r ( zero_poly n ) [1] end
695 (* fn: poly -> poly -> int ->
696 int -> int -> int -> int list -> poly list ->
697 | poly -> poly -> int list -> poly
698 assumes length a > 1 \<and> length b > 1
701 and HENSEL_lifting a b n M m r r_list steps g g_n mult =
702 if m > M then if g_n %%|%% a andalso g_n %%|%% b then g_n else
703 try_new_r a b n M r r_list steps
706 val r = find_new_r a b r;
707 val r_list = r_list @ [r];
708 val g_r = gcd_poly' (order (eval a (n - 2) r))
709 (order (eval b (n - 2) r)) (n - 1) 0
711 if lex_ord (lmonom g) (lmonom g_r)
712 then HENSEL_lifting a b n M 1 r r_list steps g g_n mult
713 else if (lexp g) = (lexp g_r)
715 let val (g_n, new, steps) = NEWTON r_list [g, g_r] steps mult g_n (n - 2)
717 if (nth steps (length steps - 1)) = (zero_poly (n - 1))
718 then HENSEL_lifting a b n M (M + 1) r r_list steps g_r g_n new
719 else HENSEL_lifting a b n M (m + 1) r r_list steps g_r g_n new
721 else (* \<not> lex_ord (lmonom g) (lmonom g_r) *)
722 HENSEL_lifting a b n M (m + 1) r r_list steps g g_n mult
725 fun majority_of_coefficients_is_negative a b c =
726 let val cs = (coeffs a) @ (coeffs b) @ (coeffs c)
727 in length (filter (curry op < 0) cs) < length cs div 2 end
729 (* gcd_poly :: poly \<Rightarrow> poly \<Rightarrow> poly
730 gcd_poly a b = ((a', b'), c)
731 assumes a \<noteq> [] \<and> b \<noteq> [] \<and>
732 yields a = a' *\<^isub>p c \<and> b = b' *\<^isub>p c \<and> (\<forall>c'. (c' dvd\<^isub>p a \<and> c' dvd\<^isub>p b) \<longrightarrow> c' \<le>\<^isub>p c) \<and>
733 \<not> gcds a' < 0 \<and> gcds a' < 0 *)
734 fun gcd_poly (a as (_, es)::_ : poly) b =
735 let val c = gcd_poly' a b (length es) 0
736 val (a': poly, _) = a %%/%% c
737 val (b': poly, _) = b %%/%% c
739 if majority_of_coefficients_is_negative a' b' c
740 then ((a' %%* ~1, b' %%* ~1), c %%* ~1) (*makes results look nicer*)
745 section {* example from the last mail to Tobias Nipkow *}
746 ML {* (* test, see text below *)
747 val a = [(1,[2, 0]), (~1,[0, 2])]; (* x^2 - y^2 *)
748 val b = [(1,[2, 0]), (~1,[1, 1])]; (* x^2 - x*y *)
749 val ((a', b'), c) = gcd_poly a b;
751 a' = [(~1, [1, 0]), (~1, [0, 1])];
753 c = [(~1, [1, 0]), (1, [0, 1])];
754 (* checking the postcondition: *)
755 a = (a' %%*%% c) andalso b = (b' %%*%% c); (* where "%%*%%" is "*\<^isub>p" *)
756 (* \<forall>c'. (c' dvd\<^isub>p a \<and> c' dvd\<^isub>p b) \<longrightarrow> c' \<le>\<^isub>p c) can NOT be checked this way, of course *)
759 text {* last mail to Tobias Nipkow:
760 Eine erste Idee, wie die Integration der Diplomarbeit für einen Benutzer
761 von Isabelle aussehen könnte, wäre zum Beispiel im
764 assumes asm3: "x^2 - x*y \<noteq> 0" and asm4: "x \<noteq> 0"
765 shows "(x^2 - y^2) / (x^2 - x*y) = (x + y) / (x::real)"
766 apply (insert asm3 asm4)
772 asm1: "(x^2 - y^2) = (x + y) * (x - y)" and asm2: "x^2 - x*y = x * (x - y)"
774 im Hintergrund automatisch zu erzeugen (mit der Garantie, dass "(x - y)" der GCD ist)
775 und sie dem Simplifier (für die Rule nonzero_mult_divide_mult_cancel_right) zur
776 Verfügung zu stellen, sodass anstelle von "sorry" einfach "done" stehen kann.
777 Und weiters wäre eventuell asm3 zu "x - y \<noteq> 0" zu vereinfachen, eine
778 Rewriteorder zum Herstellen einer Normalform festzulegen, etc.
781 section {* Preparations for Lucas-Interpretation *}
783 Lucas-Interpretation shall make "gcd_poly" transparent to the user,
784 such that step-wise interpretation gives some intuition of the algorithm
785 and opportunities for interactive learning by trial and error.
786 The vision is to provide a learning environment similar to a good
787 chess program, where the matter (chess) is selfcontained and
788 learning happens by interaction with the system.
790 GDC_Poly gives raise to a major case study on the above aims.
791 Below pedagogical questions come first and respective technical
792 requirements subsequently.
795 subsection {* Questions of learners *}
797 The earlier the phase in appropaching a new topic for a student, the more
798 divergent the questions are. So here are just some which suggest themselves.
800 subsubsection {* Why does naive transfer from Z to polynomials not work? *}
802 Given the Eucliden Algorithm in Z ...
805 fun euclid_int a 0 = a
807 if a < b then euclid_int b a else
809 val (q, r) = (a div b, a mod b)
810 val _ = writeln (PolyML.makestring a ^ " = " ^ PolyML.makestring q ^
811 " * " ^ PolyML.makestring b ^ " + " ^ PolyML.makestring r)
812 in euclid_int b r : int end;
813 euclid_int 192 162; (* ..stepwise execution is essential for comprehension *)
815 text {* ... let it naively transfer to the polynomial ring Z[x]: *}
817 fun deg p = max_deg_var (p: poly) 0 (*TODO?*)
818 fun EUCLID_naive a [(0, [0])] = a (*TODO: for Z[x,y] we would need [(0, [0, 0])] here*)
820 if deg a < deg b then EUCLID_naive b a else
822 val (q, r) = a %%/%% b
823 val _ = writeln (PolyML.makestring a ^ " == " ^ PolyML.makestring q ^
824 " ** " ^ PolyML.makestring b ^ " ++ " ^ PolyML.makestring r)
825 in EUCLID_naive b r : poly end;
828 (*The example used in the inital mail to Tobias Nipkow, simplified to univariate*)
829 "--------------------------------";
830 val a = uni_to_multi [0,~1,1]; (* -x + x^2 = x *(-1 + x) *)
831 val b = uni_to_multi [~1,0,1]; (* -1 + x^2 = (1+x)*(-1 + x) *)
832 "--------------------------------";
833 EUCLID_naive a b = [(1, [0]), (~1, [1])]; (* 1 - x .. is acceptable*)
836 "--------------------------------";
837 val a = uni_to_multi [~1,0,0,1]; (* -1 + x^3 = (1+x+x^2)*(-1 + x) *)
838 val b = uni_to_multi [0,~1,1]; (* -x + x^2 = x *(-1 + x) *)
839 "--------------------------------";
840 EUCLID_naive a b = [(~1, [0]), (1, [1])]; (*-1 + x *)
843 EUCLID_naive, however, does not work in general, because division is
844 not as general as in Z:
847 "--------------------------------";
848 val a = uni_to_multi [0,~1,0,0,1]; (* -x + x^4 = x*(1+x+x^2)*(-1 + x) *)
849 val b = uni_to_multi [~2,2]; (* -x + x^2 = 2 *(-1 + x) *)
850 "--------------------------------";
851 (*EUCLID_naive a b = [(~1, [0]), (1, [1])] happens to loop*)
854 (* (x^4 - x) : (2*x - 2) = 1/2 * x^4 ...
855 This division, equal to above division, is impossible in Z[x], rather, it requires Q[x].
856 This problem of division with integer coefficients proceeds down from the least
857 coefficient to the other coefficients.
858 Nevertheless, below we are going to generalise division such that the
859 Euclidean Algorithm can work on Z[x].
862 subsubsection {* Can division of polynomials be adapted to Euclid? *}
864 In principle it is simplest to go from Z[x] to Q[x], polynamials over rational numbers
865 and thus, for instance, allow
866 (x^4 - x) : (2*x - 2) = 1/2 * x^4 ...
867 However, Q is computationally less efficient and the terms are more complicated.
868 Fortunately, [1].p.83/84 tells, that division in Z[x] can be generalised for the
870 In order to comprehend stepwise execution of the division algorithm below
871 (which is exactly as done by hand) we adapt to our representation of (univariate)
872 polynomials as lists and take an instructuve example:
874 (2*x^4 + 2*x^3 + 2*x^2 + 2*x + 2) : (3*x + 4) = 2/3 * x^2 ...
876 is translated to a reversed arrangement of monomials
878 (2 + 2*x + 2*x^2 + 2*x^3 + 2*x^4) : (1 + 2*x + 3*x^2) = ...
880 and further to lists:
882 [ 2, 2, 2, 2, 2] : [1, 2, 3] =
883 3*[ 2, 2, 2, 2, 2] *3
884 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~:
886 --[ 0, 0, 2, 4, 6] <--........ * [ 2]
887 ------------------------ :
888 [ 6, 6, 4, 2, 0] = [ 2]*3
890 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~:
892 --[ 0, 2, 4, 6] <-------........--- * [2]
893 ------------------------ :
894 [ 18, 16, 8, 0] = [ 6, 2]
896 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~:
898 --[ 8, 16, 24] <------------........-------- * [8]
899 ------------------------ :
900 [46, 32, 0] = [18, 6, 8]
901 =======================================
902 27*[ 2, 2, 2, 2, 2] : [1, 2, 3] = [18, 6, 8], remd [46, 32]
904 We implement this algorithm below and check it with these and other values.
906 First we need some auxiliary functions.
908 ML {* (* auxiliary functions *)
909 fun for_quot_regarding p1 p2 p1' quot remd =
911 val zeros = deg_up p1' - deg_up remd - 1(*length [q]*)
912 val max_qot_deg = deg_up p1 - deg_up p2
914 if zeros + 1(*length [q]*) + deg_up quot > max_qot_deg (*possible in last step of div*)
915 then max_qot_deg - deg_up quot - 1(*length [q]*) else zeros
918 (* multiply polynomial p such that it has degree d with d = deg p*)
919 fun mult_to_deg d p =
920 let val d' = deg_up p
921 in if d >= d' then (replicate (d - d') 0) @ p : unipoly else p end;
923 (* find a factor n and a quotient q such that n > 0 \<and> n*c1 = q*c2 *)
924 fun fact_quot c1 c2 =
926 val d = abs (Integer.gcd c1 c2)
929 in if n > 0 then (n, q) else (~1 * n, ~1 * q) end;
933 if length p1 > length p2
934 then map2 (curry op +) p1 (p2 @ (replicate (List.length p1 - List.length p2) 0))
935 else map2 (curry op +) (p1 @ (replicate (List.length p2 - List.length p1) 0)) p2;
938 fun p1 %*% p2 =multi_to_uni (uni_to_multi p1 %%*%% uni_to_multi p2);
939 ([1,2,1] %*% [1,3,3,1]) = [1, 5, 10, 10, 5, 1];
941 replicate 3 0 = [0, 0, 0];
944 val trace_div = Unsynchronized.ref true;
945 val trace_div_invariant = Unsynchronized.ref false; (*.. true expects !trace_div = false *)
946 fun div_invariant2 p1 p2 n ns zeros q quot remd =
947 (PolyML.makestring p1 ^ " * " ^ PolyML.makestring (n * ns) ^ " = " ^
948 PolyML.makestring (mult_to_deg (deg_up p1 - deg_up p2)
949 ((zeros @ [q] @ (quot %* n)))) ^
950 " ** " ^ PolyML.makestring p2 ^ " ++ " ^
951 PolyML.makestring ((rev o (mult_to_deg (deg_up p1)) o rev) remd))
952 fun writeln_trace p1 p1' p2 quot n q (zeros:int) remd (ns: int) =
953 (if (! trace_div) then
954 ( writeln (" div " ^ PolyML.makestring p1' ^ " * " ^ PolyML.makestring n);
955 writeln " div ~~~~~~~~~~~~~~~~~~~~~~~~~~~";
956 writeln (" div " ^ PolyML.makestring (p1' %* n));
957 writeln (" div-- " ^ PolyML.makestring (mult_to_deg (deg_up p1') p2 %* q) ^
958 " <--- " ^ PolyML.makestring p2 ^ " * " ^ PolyML.makestring q);
959 writeln " div ---------------------------";
960 writeln (" div " ^ PolyML.makestring ((p1' %* n)%-%(mult_to_deg (deg_up p1') p2 %* q)) ^
962 PolyML.makestring (replicate zeros 0) ^ " @ [" ^
963 PolyML.makestring q ^ "] @ (" ^ PolyML.makestring n ^
964 " * " ^ PolyML.makestring quot ^ ")" );
965 if (p1' %* n) = ((mult_to_deg (deg_up p1') (p2 %* q)) %+% remd) then ()
966 else error ("invariant 1 does not hold: " ^ PolyML.makestring (p1' %* n) ^ " = " ^
967 PolyML.makestring (mult_to_deg (deg_up p1') p2) ^ " * " ^ PolyML.makestring q ^ " ++ " ^
968 PolyML.makestring (remd @ []))
970 (*invariant 2: initial p1 *\<^isub>s ns = quot *\<^isub>p p2 +\<^isub>p remd *)
971 if (p1 %* (n * ns)) =
972 ((mult_to_deg (deg_up p1 - deg_up p2)
973 ((replicate zeros 0) @ [q] @ (quot %* n)) %*% p2 %+% remd))
975 if (! trace_div_invariant)
976 then writeln (" div " ^ div_invariant2 p1 p2 n ns (replicate zeros 0) q quot remd)
978 else error ("invariant 2 does not hold: " ^ div_invariant2 p1 p2 n ns (replicate zeros 0) q quot remd)
981 (* generalized division: divide in Z[x] with multiplication of the dividend
982 such that the quotient is again in Z[x], i.e. has integer coefficients.
983 fact_div :: unipoly \<Rightarrow> unipoly \<Rightarrow> (unipoly, unipoly, nat)
984 fact_div p1 p2 = (n, q, r)
985 assumes p2 \<noteq> [0] \<and> deg p1 \<ge> deg p2
986 yields p1 *\<^isub>s n = q *\<^isub>p p2 +\<^isub>p r
988 note 1: p1' changes with iterations, p1 remains equal -- only required for invariant 2
989 note 2: test -- fun %*/% Subscript raised (line 509 of lib-- shows a bug. *)
991 fun div_int_up p1 p1' p2 quot ns =
993 val (n, q) = fact_quot (lcoeff_up p1') (lcoeff_up p2);
994 val remd = drop_tl_zeros ((p1' %* n) %-% (mult_to_deg (deg_up p1') p2 %* q));
995 val zeros = for_quot_regarding p1 p2 p1' quot remd
996 val _ = writeln_trace p1 p1' p2 quot n q zeros remd ns
997 val quot = (replicate zeros 0) @ [q] @ (quot %* n)
998 (*div by hand uses q; * n multiplies the previous quot*)
1001 if deg_up remd >= deg_up p2
1002 then div_int_up p1 remd p2 quot ns
1003 else (ns, quot, remd)
1008 val (n, q, r) = div_int_up p1 p1 p2 [] 1
1009 val _ = if (! trace_div) then
1010 (writeln " div .....................................................................";
1011 writeln (" div " ^ PolyML.makestring n ^ " * " ^ PolyML.makestring p1 ^ " = " ^
1012 PolyML.makestring q ^ " ** " ^ PolyML.makestring p2 ^ " ++ " ^ PolyML.makestring r);
1013 writeln " div ....................................................................."
1017 text {* Here is the check of the algorithm with the values from above: *}
1020 trace_div_invariant := false;
1021 val p1 = [2, 2, 2, 2, 2];
1024 q (* = [8, 6, 18]*),
1025 r (* = [46, 32]*)) = p1 %*/% p2;
1026 if (p1 %* n) = ((q %*% p2) %+% r) then () else error "fun %*/% changed"
1028 text {* However, intermediate values increase exponentially: *}
1029 ML {* (* example from [1].p.84 *)
1030 val p1 = [~5,2,8,~3,~3,0,1,0,1];
1031 val p2 = [21,~9,~4,5,0,3];
1033 q (* = [22, ~6, ~6, 9]*),
1034 r (* = [~597, 378, 376, ~458, 6]*)) = p1 %*/% p2;
1035 if (p1 %* n) = ((q %*% p2) %+% r) then () else error "fun %*/% changed 1"
1037 ML {* (* example used to develop algorithm by hand *)
1038 val p1 = [2,2,2, 2,2,2];
1041 q (* = [55, 15, ~18, 54]*),
1042 r (* = [~61, ~221]*)) = p1 %*/% p2;
1043 if (p1 %* n) = ((q %*% p2) %+% r) then () else error "fun %*/% changed 2"
1046 subsubsection {* The Euclidean Algorithm really works with polynomials? *}
1048 The generalized division implemented above really can replace the
1049 division of integers in the Eucliden Algorithm:
1052 val trace_Euclid = Unsynchronized.ref true;
1053 fun writeln_trace_Euclid (a: int list) (b: int list) (n: int) (q: int list) (r: int list) =
1054 if (! trace_Euclid) then
1055 writeln ("Euclid " ^ PolyML.makestring a ^ " * " ^ PolyML.makestring n ^
1056 " == " ^ PolyML.makestring q ^
1057 " ** " ^ PolyML.makestring b ^ " ++ " ^ PolyML.makestring r)
1060 fun EUCLID_naive_up a [] = primitive_up a
1061 | EUCLID_naive_up a b =
1062 if deg_up a < deg_up b then EUCLID_naive_up b a else
1064 val (n, q, r) = a %*/% b
1065 val _ = writeln_trace_Euclid a b n q r
1066 in EUCLID_naive_up b r : unipoly end;
1069 (*The example used in the inital mail to Tobias Nipkow made univariate: y -> 1*)
1071 trace_div_invariant := false;
1072 trace_Euclid := true;
1073 "--------------------------------";
1074 val a = [0,~1,1]: unipoly; (* -x + x^2 = x *(-1 + x) *)
1075 val b = [~1,0,1]: unipoly; (* -1 + x^2 = (1+x)*(-1 + x) *)
1076 "--------------------------------";
1077 EUCLID_naive_up a b; (* = [1, ~1]*) (*( 1 - x) *)
1080 "--------------------------------";
1081 val a = [~1,0,0,1]: unipoly; (* -1 + x^3 = (1+x+x^2)*(-1 + x) *)
1082 val b = [0,~1,1]: unipoly; (* -x + x^2 = x *(-1 + x) *)
1083 "--------------------------------";
1084 EUCLID_naive_up a b; (* = [~1, 1]*) (*(-1 + x) *)
1086 ML {* (*NOW this works with generalized division*)
1087 "--------------------------------";
1088 val a = [0,~1,0,0,1]: unipoly; (* -x + x^4 = x*(1+x+x^2)*(-1 + x) *)
1089 val b = [~2,2]: unipoly; (* -x + x^2 = 2 *(-1 + x) *)
1090 "--------------------------------";
1091 EUCLID_naive_up a b; (* = [~1, 1]*)
1094 (* from [1].p.84; the generated coefficients are different for an unknown reason *)
1095 "--------------------------------";
1096 val a = [~5,2,8,~3,~3,0,1,0,1]: unipoly;
1097 val b = [21,~9,~4,5,0,3]: unipoly;
1098 "--------------------------------";
1099 EUCLID_naive_up a b; (* = [1]*)
1103 "--------------------------------";
1104 val a = [~18, ~15, ~20, 12, 20, ~13, 2]: unipoly;
1105 val b = [8, 28, 22, ~11, ~14, 1, 2]: unipoly;
1106 "--------------------------------";
1107 EUCLID_naive_up a b; (* = [~2, ~1, 1]*)
1110 Note: The above algorithm still needs to take into account the case, where
1111 constants can be factored out from polynomials, see [1].p.82-84.
1114 subsubsection {* Interactivity on Euclids Algorithm *}
1116 Euclids Algorithm in modern algebraic notation explains itself if presented
1121 trace_div_invariant := false;
1122 trace_Euclid := true;
1123 "--------------------------------";
1124 val a = [~1,0,0,1]: unipoly; (* -1 + x^3 = (1+x+x^2)*(-1 + x) *)
1125 val b = [0,~1,1]: unipoly; (* -x + x^2 = x *(-1 + x) *)
1126 "--------------------------------";
1127 EUCLID_naive_up a b; (* = [~1, 1]*) (*(-1 + x) *)
1129 Euclid [~1, 0, 0, 1] * 1 == [1, 1] ** [0, ~1, 1] ++ [~1, 1]
1130 Euclid [0, ~1, 1] * 1 == [0, 1] ** [~1, 1] ++ []
1134 trace_Euclid := true;
1136 "--------------------------------";
1137 val a = [~18, ~15, ~20, 12, 20, ~13, 2]: unipoly;
1138 val b = [8, 28, 22, ~11, ~14, 1, 2]: unipoly;
1139 "--------------------------------";
1140 EUCLID_naive_up a b; (* = [~2, ~1, 1]*)
1142 Euclid [~18, ~15, ~20, 12, 20, ~13, 2] * 1 == [1] ** [8, 28, 22, ~11, ~14, 1, 2] ++ [~26, ~43, ~42, 23, 34, ~14]
1143 Euclid [8, 28, 22, ~11, ~14, 1, 2] * 98 == [~41, ~14] ** [~26, ~43, ~42, 23, 34, ~14] ++ [~282, 617, ~168, ~723, 344]
1144 Euclid [~26, ~43, ~42, 23, 34, ~14] * 59168 == [787, ~2408] ** [~282, 617, ~168, ~723, 344] ++ [~1316434, ~3708859, ~867104, 1525321]
1145 Euclid [~282, 617, ~168, ~723, 344] * 6783102487 == [~2345549, 1529768] ** [~1316434, ~3708859, ~867104, 1525321] ++ [~5000595353600, ~2500297676800, 2500297676800]
1146 Euclid [~1316434, ~3708859, ~867104, 1525321] * 7289497600 == [1919, 4447] ** [~5000595353600, ~2500297676800, 2500297676800] ++ []
1150 A student only needs to observe, how the second factor on the right-hand side
1151 shifts to the left-hand side; and he or she has to observe the remainder []
1152 in the last line and take the preceeding remainder as the gcd.
1154 In order to see that this recursion really computes the gcd, one needs to
1155 know the fixpoint (or loop invariant):
1158 lemma "a = (q *\<^isub>P b +\<^isub>P r) *\<^isub>s n \<and> degree b > degree r \<longrightarrow> gcd a b = gcd b r"
1162 The above lemma is analogous to
1163 @{thm gcd_add_mult_int} = "gcd ?m (?k * ?m + ?n) = gcd ?m ?n" from HOL/GCD.thy
1166 subsubsection {* Interactivity and/or understanding of polynomial division *}
1168 Division raises two requirements wrt. interactivity:
1169 (1) a student wants to roughly understand what happens in the algorithm
1170 (2) a student wants to reproduce the algorithm by hand.
1172 Case (1) again calls for the fixpoint:
1176 trace_div_invariant := true;
1177 val p1 = [2, 2, 2, 2, 2];
1180 q (* = [8, 6, 18]*),
1181 r (* = [46, 32]*)) = p1 %*/% p2;
1183 div [2, 2, 2, 2, 2] * 3 = [0, 0, 2] ** [1, 2, 3] ++ [6, 6, 4, 2, 0]
1184 div [2, 2, 2, 2, 2] * 9 = [0, 2, 6] ** [1, 2, 3] ++ [18, 16, 8, 0, 0]
1185 div [2, 2, 2, 2, 2] * 27 = [8, 6, 18] ** [1, 2, 3] ++ [46, 32, 0, 0, 0]
1189 The trace shows how the quotient, i.e. the first factor on the right-hand side,
1190 is being increased step by step until the remainder has a degree lower than the
1193 This representation, however, is inappropriate for reproduction by hand.
1194 Dividing polynomials by hand is taught in this format:
1198 trace_div_invariant := false;
1199 val p1 = [2, 2, 2, 2, 2];
1202 q (* = [8, 6, 18]*),
1203 r (* = [46, 32]*)) = p1 %*/% p2;
1205 div [2, 2, 2, 2, 2] * 3
1206 div ~~~~~~~~~~~~~~~~~~~~~~~~~~~
1208 div-- [0, 0, 2, 4, 6] <--- [1, 2, 3] * 2
1209 div ---------------------------
1210 div [6, 6, 4, 2, 0] quot = [] @ [2] @ (3 * [])
1211 div [6, 6, 4, 2] * 3
1212 div ~~~~~~~~~~~~~~~~~~~~~~~~~~~
1214 div-- [0, 2, 4, 6] <--- [1, 2, 3] * 2
1215 div ---------------------------
1216 div [18, 16, 8, 0] quot = [] @ [2] @ (3 * [2])
1218 div ~~~~~~~~~~~~~~~~~~~~~~~~~~~
1220 div-- [8, 16, 24] <--- [1, 2, 3] * 8
1221 div ---------------------------
1222 div [46, 32, 0] quot = [] @ [8] @ (3 * [2, 6])
1223 div .....................................................................
1224 div 27 * [2, 2, 2, 2, 2] = [8, 6, 18] ** [1, 2, 3] ++ [46, 32]
1225 div .....................................................................
1229 The above algorithm is unusual in that the dividend first is multiplied
1230 such that the quotient can have integer coefficients ("generalised division").
1231 Thus the quotient needs to be multiplied subsequently as well.
1232 The algorithm also has to handle cases, where zeros occur in the quotient:
1236 val p1 = [3,2,2,2,1,1,1,1];
1239 q (* = [2, 0, 0, 0, 1]*),
1240 r (* = [1]*)) = p1 %*/% p2;
1242 div [3, 2, 2, 2, 1, 1, 1, 1] * 1
1243 div ~~~~~~~~~~~~~~~~~~~~~~~~~~~
1244 div [3, 2, 2, 2, 1, 1, 1, 1]
1245 div-- [0, 0, 0, 0, 1, 1, 1, 1] <--- [1, 1, 1, 1] * 1
1246 div ---------------------------
1247 div [3, 2, 2, 2, 0, 0, 0, 0] quot = [0, 0, 0] @ [1] @ (1 * [])
1248 div [3, 2, 2, 2] * 1
1249 div ~~~~~~~~~~~~~~~~~~~~~~~~~~~
1251 div-- [2, 2, 2, 2] <--- [1, 1, 1, 1] * 2
1252 div ---------------------------
1253 div [1, 0, 0, 0] quot = [] @ [2] @ (1 * [0, 0, 0, 1])
1254 div .....................................................................
1255 div 1 * [3, 2, 2, 2, 1, 1, 1, 1] = [2, 0, 0, 0, 1] ** [1, 1, 1, 1] ++ [1]
1256 div .....................................................................
1268 subsection {* Technical requirements raised by pedagogical questions *}
1269 subsubsection {* Calculations might be different from interpretation *}
1271 Until now the demonstration cases for Lucas-Interpretation were
1272 simplification, equation solving and ad-hoc algorithms in Signal Processing
1273 and Structural Engineering. In these cases execution of the functional program
1274 coincided with the calculation generated as a side-effect of interpretation:
1275 The tactics "Take", "Rewrite", "Rewrite_Set" and "Substitute", when executed,
1276 immediately created respective output and handed over control to the user.
1277 Appropriate user-input finally created a "calculation" very close to
1278 what would be written on paper.
1280 Now, in Computer Algebra, the programs contain lots of sophisticated functions.
1281 Simple traces, for instance arguments and values of function calls, comprise too
1282 many data to be intuitively comprehensible.
1284 The examples in the case study suggest to have mechanisms for creating
1285 steps for calculations, which are somehow abstracted from pure execution.
1286 Interestingly these abstractions are close to the fixpoints/invariants
1287 of the respective recursive functions --- see
1288 section "Interactivity on Euclids Algorithm" and section "Interactivity and/or
1289 understanding of polynomial division" above.
1291 Such mechanisms might be specific tactics, which take data from the
1292 local environment and the local context for presentation to the student.
1293 Thus one such tactic is related to several lines of code in a function.
1294 These tactics would not contribute to automated execution as done by "value",
1296 However, the effect of these tactics for resuming execution upon completion
1297 of input needs to be clarified.
1300 subsubsection {* One function might generate various representations *}
1302 The section "Interactivity and/or understanding of polynomial division" suggests
1303 to have at least two kinds of representation for "calculations":
1304 (1) a student wants to roughly understand what happens in the algorithm
1305 (2) a student wants to reproduce the algorithm by hand.
1307 A solution to this requirement of variety is to create related functions
1308 with different combinations of the specific tactics as mentioned above.
1309 These functions can be selected by the dialogue module. The computational
1310 effect of such related functions is the same, the interactive behaviour
1311 is different (much more different as variations of "Rewrite", for instance.)
1314 subsubsection {* One "specific tactic" might create several lines of calculation *}
1316 The case of "division of polynomial" suggests to have one "specific tactic"
1317 as mentioned above, which has one location in the function (e.g. "writeln_trace"
1318 in "div_int_up"), but creates several lines of a calculation; see the example in
1319 section "Can division of polynomials be adapted":
1321 [ 2, 2, 2, 2, 2] : [1, 2, 3] =
1322 3*[ 2, 2, 2, 2, 2] *3
1323 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~:
1325 --[ 0, 0, 2, 4, 6] <--........ * [ 2]
1326 ------------------------ :
1327 [ 6, 6, 4, 2, 0] = [ 2]*3
1331 Interaction on these lines should resemble calculation with paper and pencil.