randpoly.red 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414
  1. module randpoly; % A random (generalized) polynomial generator
  2. % F.J.Wright@Maths.QMW.ac.uk, 14 July 1994
  3. % Based on a port of the Maple randpoly function.
  4. % See RANDPOLY.TEX for documentation, and RANDPOLY.TST for examples.
  5. create!-package('(randpoly),'(contrib misc));
  6. symbolic smacro procedure apply_c c;
  7. % Apply a coefficient generator function c that returns
  8. % a prefix form and convert it to standard quotient form.
  9. simp!* apply(c, nil);
  10. symbolic procedure apply_e e;
  11. % Apply an exponent generator function e
  12. % and check that it has returned an integer.
  13. if fixp(e := apply(e, nil)) then e else
  14. RedErr "randpoly expons function must return an integer";
  15. put('randpoly, 'simpfn, 'randpoly);
  16. flag('(randpoly), 'listargp); % allow single list argument
  17. symbolic procedure randpoly u;
  18. % Use: randpoly(vars, options)
  19. % vars: expression or list of expressions -- usually indeterminates
  20. % options: optional equations (of the form `option = value')
  21. % or keywords specifying properties:
  22. %
  23. % Option Use Default Value
  24. % ------ --- -------------
  25. % coeffs = procedure to generate a coefficient rand(-99 .. 99)
  26. % expons = procedure to generate an exponent rand(6)
  27. % (must return an integer)
  28. % degree, deg or maxdeg = maximum total degree 5
  29. % ord or mindeg = minimum total degree 0 (or 1)
  30. % (defaults to 1 if any "variable" is an equation)
  31. % terms = number of terms generated if sparse 6
  32. % dense the polynomial is to be dense sparse
  33. % sparse the polynomial is to be sparse sparse
  34. begin scalar v, univar, c, e, trms, d, o, s, p, sublist;
  35. % Default values for options:
  36. %% c := rand {-99 .. 99}; % rand is a psopfn
  37. c := function(lambda(); random(199) - 99);
  38. d := 5; o := 0; trms := 6; s := 'sparse;
  39. % Evaluate arguments and process "variables":
  40. begin scalar wtl!*;
  41. % Ignore weights when evaluating args, including in revlis.
  42. v := car(u := revlis u);
  43. v := if eqcar(v, 'list) then cdr v else
  44. << univar := t; v . nil >>;
  45. v := for each vv in v collect
  46. begin scalar tmpvar;
  47. if eqexpr vv then << vv := !*eqn2a vv; o := 1 >>
  48. else if kernp simp!* vv then return vv;
  49. tmpvar := gensym();
  50. sublist := {'equal, tmpvar, vv} . sublist;
  51. return tmpvar
  52. end;
  53. if univar then v := car v
  54. end;
  55. % Process options:
  56. for each x in cdr u do
  57. if x eq 'dense or x eq 'sparse then s := x
  58. else if not (eqexpr x and
  59. (if cadr x eq 'coeffs and functionp caddr x then
  60. c := caddr x
  61. else if cadr x eq 'expons and functionp caddr x then
  62. e := caddr x
  63. else if cadr x memq '(degree deg maxdeg) and
  64. natnump caddr x then d := caddr x
  65. else if cadr x memq '(ord mindeg) and
  66. natnump caddr x then o := caddr x
  67. else if cadr x eq 'terms and natnump caddr x then
  68. trms := caddr x))
  69. then typerr(x, "optional randpoly argument");
  70. % Generate the random polynomial:
  71. p := nil ./ 1;
  72. if o <= d then
  73. if s eq 'sparse then
  74. if null e then
  75. for each x in rand!-mons!-sparse(v,trms,d,o,univar) do
  76. p := addsq(p, multsq(apply_c c, x ./ 1))
  77. else
  78. if univar then
  79. for i := 1 : trms do
  80. p := addsq(p, multsq(apply_c c,
  81. !*kp2q(v, apply_e e)))
  82. else
  83. for i := 1 : trms do
  84. (if numr cc then p := addsq(p, <<
  85. for each vv in v do
  86. cc := multsq(cc, !*kp2q(vv, apply_e e));
  87. cc >> ))
  88. where cc = apply_c c
  89. else % s eq 'dense
  90. if univar then << p := apply_c c;
  91. if o > 0 then p := multsq(p, mksq(v,o));
  92. for i := o + 1 : d do
  93. p := addsq(p, multsq(apply_c c, mksq(v,i))) >>
  94. else
  95. for each x in rand!-mons!-dense(v,d,o) do
  96. p := addsq(p, multsq(apply_c c, x ./ 1));
  97. return
  98. % Make any necessary substitutions for temporary variables:
  99. if sublist then
  100. simp!* subeval append(sublist, {mk!*sq p})
  101. else p
  102. end;
  103. symbolic procedure functionp f;
  104. % Returns t if f can be applied as a function.
  105. getd f or eqcar(f,'lambda);
  106. symbolic procedure natnump n;
  107. % Returns t if n is a natural number.
  108. fixp n and n >= 0;
  109. symbolic smacro procedure kp2f(k, p);
  110. % k : unique kernel, p : natural number > 0
  111. % Returns k^p as a standard form, taking account of
  112. % both asymptotic let rules and weightings.
  113. numr mksq(k, p);
  114. symbolic procedure !*kp2f(k, p);
  115. % k : unique kernel, p : natural number
  116. % Returns k^p as a standard form, taking account of
  117. % both asymptotic let rules and weightings.
  118. if p > 0 then kp2f(k, p) else 1;
  119. symbolic procedure !*kp2q(k, p);
  120. % k : unique kernel, p : any integer
  121. % Returns k^p as a standard quotient, taking account of
  122. % both asymptotic let rules and weightings.
  123. if p > 0 then mksq(k, p)
  124. else if zerop p then 1 ./ 1
  125. else
  126. % Is this the right behaviour?
  127. % cf. part of procedure invsq in POLY.RED
  128. if null numr(k := mksq(k, -p)) then RedErr "Zero divisor"
  129. else revpr k;
  130. symbolic procedure rand!-mons!-dense(v, d, o);
  131. % v : list of variables,
  132. % d : max total degree, o : min total degree.
  133. % Recursively returns a dense list of multivariate monomials
  134. % with total degree in [o, d] as STANDARD FORMS.
  135. begin scalar v_1; v_1 := car v; v := cdr v;
  136. return
  137. if null v then % single variable
  138. (if o > 0 then kp2f(v_1,o) else 1)
  139. . for i := o + 1 : d collect kp2f(v_1,i)
  140. else append( rand!-mons!-dense(v, d, o),
  141. for i := 1 : d join
  142. (for each x in rand!-mons!-dense(v, d - i, max(0, o - i))
  143. collect multf(v_1!^i, x))
  144. where v_1!^i = kp2f(v_1,i) )
  145. end;
  146. symbolic procedure rand!-mons!-sparse(v, trms, d, o, univar);
  147. % v : (list of) variable(s), trms: number of terms,
  148. % d : max total degree, o : min total degree.
  149. % Returns a sparse list of at most trms monomials
  150. % with total degree in [o, d] as STANDARD FORMS.
  151. begin scalar n, v_1, maxtrms, otrms, s;
  152. if univar then maxtrms := d + 1 - o else <<
  153. n := length v; v_1 := car v;
  154. otrms := if zerop o then 0 else binomial(n + o - 1, n);
  155. % max # terms to degree o-1:
  156. maxtrms := binomial(n + d, n) - otrms
  157. % max # terms in poly := max # terms to degree d - otrms
  158. >>;
  159. % Choose a random subset of the maxtrms terms by "index":
  160. s := rand!-comb(maxtrms, min(maxtrms,trms));
  161. return
  162. if univar then
  163. for each ss in s collect !*kp2f(v, ss + o)
  164. else
  165. for each ss in s collect
  166. begin scalar p; p := 1;
  167. % Convert term "index" to exponent vector:
  168. ss := nil . inttovec(ss + otrms, n);
  169. for each vv in v do
  170. p := multf(!*kp2f(vv, car(ss := cdr ss)), p);
  171. return p
  172. end
  173. end;
  174. % Support procedures for randpoly
  175. % ===============================
  176. global '(!_BinomialK !_BinomialB !_BinomialN);
  177. % binomial in the specfn package is implemented as an algebraic
  178. % operator, and I suspect is not very efficient. It will not clash
  179. % with the following implementation, which is about 50% faster on
  180. % my PC for binomial(200, 100) (with its caching disabled);
  181. symbolic procedure binomial(n, k);
  182. % Returns the binomial coefficient ASSUMING n, k integer >= 0.
  183. begin scalar n1, b;
  184. % Global !_BinomialK, !_BinomialB, !_BinomialN
  185. if k = 0 then return 1;
  186. if n < 2*k then return binomial(n,n-k);
  187. n1 := n+1;
  188. if !_BinomialN = n then << % Partial result exits ...
  189. b := !_BinomialB;
  190. if !_BinomialK <= k then
  191. for i := !_BinomialK+1 : k do
  192. b := quotient((n1-i)*b,i)
  193. else
  194. for i := !_BinomialK step -1 until k+1 do
  195. b := quotient(i*b,n1-i) >>
  196. else << % First binomial computation
  197. b := 1;
  198. for i := 1 : k do
  199. b := quotient((n1-i)*b,i);
  200. !_BinomialN := n >>;
  201. !_BinomialK := k;
  202. return !_BinomialB := b
  203. end;
  204. symbolic procedure rand!-comb(n, m);
  205. % Returns a list containing a random combination of m of the
  206. % first n NATURAL NUMBERS, ASSUMING integer n >= m >= 0.
  207. % (The values returned are 1 less than those
  208. % returned by the Maple randcomb function.)
  209. if m = n then for i := 0 : m - 1 collect i
  210. else begin scalar s;
  211. if n - m < m then
  212. begin scalar r;
  213. r := rand!-comb(n, n - m);
  214. for rr := 0 : n - 1 do
  215. if not(rr member r) then s := rr . s
  216. end
  217. else
  218. for i := 0 : m - 1 do
  219. begin scalar rr;
  220. while (rr := random n) member s do; % nothing
  221. s := rr . s
  222. end;
  223. return s
  224. end;
  225. symbolic procedure inttovec(m, n);
  226. % Returns the m'th (in lexicographic order) list of n
  227. % non-negative integers, ASSUMING integer m >= 0, n > 0.
  228. inttovec1(n, inttovec!-solve(n,m));
  229. symbolic procedure inttovec1(n, dm);
  230. % n > 0 : integer; dm : dotted pair, d . m', where
  231. % d = norm of vector in N^n and m' = index of its tail in N^{n-1}.
  232. % Returns list representing vector in N^n, constructed recursively.
  233. % First vector element v_1 satisfies v_1 = d - norm of tail vector.
  234. if n = 1 then car dm . nil else
  235. ( (car dm - car dm1) . inttovec1(n - 1, dm1) )
  236. where dm1 = inttovec!-solve(n - 1, cdr dm); % dotted pair
  237. symbolic procedure inttovec!-solve(n, m);
  238. % n > 0, m >= 0 : integer
  239. % Main subalgorithm to compute the vector in N^n with index m.
  240. % Returns as a dotted pair d . m' the norm (total degree) d in N^n
  241. % and the index m' of the tail sub-vector in N^{n-1}.
  242. % d is computed to satisfy ^{d-1+n}C_n <= m < ^{d+n}C_n,
  243. % where ^{d+n}C_n = number of n-vectors of norm d.
  244. if m = 0 or n = 1 then m . 0 else
  245. begin scalar d, c, cc;
  246. d := 0; cc := 1; % cc = ^{d+n}C_n
  247. repeat <<
  248. c := cc; d := d + 1; % c = ^{d+n}C_n
  249. cc := quotient((n + d)*c, d); % cc = ^{d+1+n}C_n
  250. >> until cc > m;
  251. return d . (m - c)
  252. end;
  253. % Support for anonymous procedures (`proc's),
  254. % ==========================================
  255. % especially random number generators.
  256. % ===================================
  257. % Based partly on Maple's proc and rand function, and intended mainly
  258. % for use with the randpoly operator.
  259. % Interval code based on numeric.red and gnuplot.red by Herbert Melenk.
  260. % Create .. infix operator, avoiding warning if already defined.
  261. % (It is pre-defined via plot/plothook.sl at least in PSL-REDUCE.)
  262. newtok '( (!. !.) !*interval!*);
  263. precedence .., or;
  264. algebraic operator ..;
  265. put('!*interval!*, 'PRTCH, '! !.!.! );
  266. put('rand, 'psopfn, 'rand);
  267. symbolic procedure rand u;
  268. % Returns a random number generator, and compiles it if COMP is on.
  269. % Optional second argument generates a named procedure.
  270. if null u or (cdr u and cddr u) then
  271. RedErr "rand takes 1 or 2 arguments"
  272. else begin scalar fname, fn;
  273. if cdr u and not idp(fname := reval cadr u) then
  274. typerr(fname, "procedure name");
  275. fn := if fixp(u := reval car u) and u > 0 then {'random, u}
  276. else if eqcar(u,'!*interval!*) then
  277. begin scalar a, b;
  278. if not(fixp(a := cadr u) and fixp(b := caddr u) and a<b) then
  279. RedErr
  280. "rand range argument a .. b must have integer a,b with a < b";
  281. return if zerop a then {'random, b + 1}
  282. else {'plus2, a, {'random, b - a + 1}}
  283. end
  284. else typerr(u, "integer or integer range");
  285. fn := {'lambda, nil, fn};
  286. %% putd compiles the function if !*comp is non-nil.
  287. return if fname then
  288. << flag({fname}, 'opfn); putd(fname, 'expr, fn) >>
  289. else if !*comp then putd(gensym(), 'expr, fn)
  290. else fn
  291. end;
  292. % Redefine the algebraic-mode random operator.
  293. remflag('(random), 'opfn);
  294. put('random, 'psopfn, 'evalrandom);
  295. symbolic procedure evalrandom u;
  296. % More flexible interface to the random function.
  297. if null u or cdr u then RedErr "random takes a single argument"
  298. else if eqcar(u := reval car u,'!*interval!*) then
  299. begin scalar a, b;
  300. if not(fixp(a := cadr u) and fixp(b := caddr u) and a < b) then
  301. RedErr
  302. "random range argument a .. b must have integer a,b with a < b";
  303. return if zerop a then random(b + 1) else a + random(b - a + 1)
  304. end
  305. else if fixp u and u > 0 then random u
  306. % N.B. random also makes this check, but does not accept a range
  307. else typerr(u, "integer or integer range");
  308. % Proc turns its argument expression into a lambda expression
  309. % and compiles it if COMP is ON. Provides a general version of rand.
  310. % Some such mechanism is necessary to prevent expressions containing
  311. % random number generators being evaluated too early.
  312. put('proc, 'psopfn, 'proc);
  313. symbolic procedure proc u;
  314. % Returns an anonymous procedure definition,
  315. % compiled if COMP is ON.
  316. if null u then RedErr "proc requires at least a body argument"
  317. else <<
  318. % aeval!* necessary instead of aeval here to avoid caching
  319. % and hence loss of possible randomness within loops:
  320. u := {'lambda, nil, {'aeval!*, mkquote car u}};
  321. if !*comp then putd(gensym(), 'expr, u) else u
  322. >>;
  323. % User access procedures to evaluate and display procs etc.
  324. % ========================================================
  325. % Not necessary -- provided only for test purposes and curious users!
  326. put('evalproc, 'psopfn, 'evalproc);
  327. symbolic procedure evalproc r;
  328. % r : proc, arg_1, ..., arg_n; args optional
  329. % Evaluates a proc applied to the subsequent arguments.
  330. apply(getproc car r, revlis cdr r);
  331. put('showproc, 'psopfn, 'showproc);
  332. symbolic procedure showproc r;
  333. % Displays a proc.
  334. (if codep rr then
  335. RedErr "Argument is a compiled proc -- cannot display"
  336. else << terpri(); rprint subst('plus, 'plus2, rr); >>)
  337. where rr = getproc car r;
  338. symbolic procedure getproc r;
  339. % Support procedure: get a proc body.
  340. ( if idp r then
  341. getfnbody r or
  342. ( (r := get(r, 'avalue)) and car r eq 'scalar
  343. and eqcar(r := cadr r, 'lambda) and r ) or
  344. getfnbody r
  345. else if pairp r then
  346. if car r eq 'lambda then r % share variable
  347. else if eqcar(r := ( (if x then apply(x, {cdr r}))
  348. where x = get(car r, 'psopfn) ), 'lambda) then r )
  349. or RedErr "Argument is not a proc";
  350. symbolic procedure getfnbody r;
  351. (r := getd r) and cdr r;
  352. endmodule;
  353. end;