with(plots): with(plottools): with(LinearAlgebra): with(Groebner): with(Optimization): _EnvExplicit := true; Digits := 100; ###################################################################### # This block of code defines the function _ASSERT(p,s). The normal # setup is that executing _ASSERT(p,s) continues silently if p # evaluates to true, but generates an error including the string s # if p is false. If assert_verbosely is true then a message is # generated even if the test is passed. If assert_stop_on_fail is # false then failed assertions still generate a message but the # maple error() command is not used, so execution will usually # continue. kernelopts(assertlevel=1): assert_count := 0; assert_verbosely := false; assert_stop_on_fail := true; #@ _ASSERT _ASSERT := proc(p,s::string) global assert_count,assert_verbosely,assert_stop_on_fail,assert_fail_data; assert_count := assert_count+1; if assert_verbosely then printf("Checking: %s\n",s); fi; if not p then assert_fail_data := args[3..-1]; if assert_stop_on_fail then error cat("Assertion failed: ",s); else printf(cat("Assertion failed: ",s,"\n")); fi; fi; end: ###################################################################### # When this code is executed, Maple may consider the current directory # to be the directory where all the worksheets are saved, or the # directory where this text file is saved, or the common parent of # those two directories. The next block of code tries to determine # which of these cases applies, and sets the global variables # genus2_dir,maple_dir,worksheets_dir and latex_dir accordingly. #@ find_directories find_directories := proc() local olddir; global genus2_dir,data_dir,maple_dir,maple_output_dir, worksheets_dir,latex_dir,plots_dir,thumbs_dir,images_dir,doc_dir,lib_dir; genus2_dir := NULL; if FileTools[Exists]("../maple") and FileTools[Exists]("../worksheets") and FileTools[IsDirectory]("../maple") and FileTools[IsDirectory]("../worksheets") then olddir := currentdir(".."); genus2_dir := currentdir(); currentdir(olddir); elif FileTools[Exists]("maple") and FileTools[Exists]("worksheets") and FileTools[IsDirectory]("maple") and FileTools[IsDirectory]("worksheets") then genus2_dir := currentdir(); fi: if genus2_dir = NULL then error("genus2 directory not found"); fi; data_dir := cat(genus2_dir,"/data"); maple_dir := cat(genus2_dir,"/maple"); maple_output_dir := cat(genus2_dir,"/maple_output"); worksheets_dir := cat(genus2_dir,"/worksheets"); latex_dir := cat(genus2_dir,"/latex"); plots_dir := cat(genus2_dir,"/plots"); thumbs_dir := cat(genus2_dir,"/plots/thumbs"); images_dir := cat(genus2_dir,"/images"); doc_dir := cat(genus2_dir,"/doc"); lib_dir := cat(genus2_dir,"/lib"); end: find_directories(); ###################################################################### # Simplify a complex number by setting real and imaginary parts # to zero if they are smaller than some threshhold (10^(-12) by # default). This is useful because some calculations produce a # tiny spurious imaginary part when the result should be real. #@ trim trim := proc(t,e::numeric := 10^(-12)) local v,x,y; if type(t,list) or type(t,set) then return(map(trim,t,e)); fi; v := evalf(t); x := Re(v); y := Im(v); if (abs(x - round(x)) < e) then x := round(x); fi; if (abs(y - round(y)) < e) then y := round(y); fi; return(x + I * y); end: # This is a similar function that works on algebraic expressions. # It should be consolidated with trim() at some point. #@ tidy tidy := proc(u,e::numeric := 10.^(-90)) local aa,a,c,u0; if type(u,`+`) or type(u,`*`) or type(u,list) or type(u,set) then return(map(tidy,u,e)); elif type(u,`^`) then return(tidy(op(1,u),e)^op(2,u)); elif type(u,float) then if abs(24*u - round(24*u)) < e then return round(24*u)/24; else return u; fi; elif type(u,complex) then return tidy(Re(u)) + I * tidy(Im(u)); else return u; fi; end: #@ strip_sign strip_sign := proc(v) local a,b; if type(v,rational) then return abs(v); elif type(v,`*`) then a,b := selectremove(type,v,rational); return(abs(a) * b); else return v; fi; end: ###################################################################### #@ is_inexact is_inexact := (x) -> hastype(x,float); ###################################################################### # For some reason, Maple does not always apply the obvious algebraic # rules for complex conjugation. The function below is designed to # fix this. #@ conj conj := proc(u) if type(u,`*`) or type(u,`+`) or type(u,list) then return map(conj,u); elif type(u,`^`) then return conj(op(1,u))^op(2,u); else return(conjugate(u)); fi; end: ###################################################################### # This function should be called with some additional arguments # (say t_1,..,t_r) as well as the named arguments f and n. It # assumes that f depends on t_1,..,t_r and returns a polynomial in # these variables of total degree less than n, which is an # approximation to f. #@ multi_series multi_series := proc(f,n::posint) local e,g,R; if type(f,list) or type(f,set) or type(f,Vector) or type(f,Matrix) then return map(multi_series,f,args[2..-1]); fi; R := [args[3..-1]]; R := map(t -> (t = e*t),R); g := subs(R,f); g := series(g,e=0,n); g := expand(subs(e=1,convert(g,polynom,e))); return g; end: ###################################################################### # revert_series(p,z,m) expects p to be a polynomial in z, with # p(0) = 0 and p'(0) <> 0. It returns another polynomial q such that # p(q(z)) = q(p(z)) = z mod z^(m+1). If m is not supplied, it # defaults to the degree of p. # # Maple can do this as a special case of solve/series, but that # seems to be extremely slow. #@ revert_series revert_series := proc(p,z,m_) local i,m,a,pp,q,qp; if nargs > 2 then m := m_; else m := degree(p,z); fi; if coeff(p,z,0) <> 0 then error("constant term is nonzero"); fi; a := coeff(p,z,1); if a = 0 then error("linear term is zero"); fi; q := z/a; pp := rem(p,z^(m+1),z); qp := expand(pp/a); for i from 2 to m do pp := rem(p * pp,z^(m+1),z); a := -coeff(qp,z,i)/coeff(pp,z,i); q := q + a*z^i; qp := expand(qp + a * pp); od; return q; end: ###################################################################### # simp(x) returns x if x only involves exact numbers, but if x # contains any floating point terms then simp(x) attempts to convert # everything to floating point. Thus, if a is an unassigned symbol then # have simp(a + Pi) = a + Pi but simp(a + Pi + 0.1) = a + 3.24159... #@ simp simp := proc(x) if hastype(x,float) then return(evalf(x)); else return(simplify(x)); fi; end: ###################################################################### # if a = \sum_{i\in A} 2^i and b = \sum_{i\in B} 2^i then # bitwise_and(a,b) = \sum_{i\in A\cap B} 2^i. # # This is only included to allow us to write certain parts of the # code in a way that translates easily into C (where bitwise and # is a primitive operation and is often used to calculate # intersections of small sets). #@ bitwise_and bitwise_and := proc(a,b) local aa,bb,c,x; aa := a; bb := b; c := 0; x := 1; while aa + bb > 0 do if type(aa,odd) and type(bb,odd) then c := c + x; fi; aa := iquo(aa,2); bb := iquo(bb,2); x := 2*x; od; return(c); end: ###################################################################### # Find the n'th order Fourier series for a function of t # that is periodic of period 2 Pi. #@ fourier_series fourier_series := proc(u,n_,e_) local n,e; n := `if`(nargs > 1, n_, 10); e := `if`(nargs > 2, e_, 10^(-12)); trim(Re(Int(u/(2*Pi),t=0..2*Pi))) + add(Re(trim(Int(u*cos(k*t)/Pi,t=0..2*Pi),e))*cos(k*t),k=1..n) + add(Re(trim(Int(u*sin(k*t)/Pi,t=0..2*Pi),e))*sin(k*t),k=1..n); end: ###################################################################### # Given matrices M1 and M2 of the same height, this function returns # [v,w] where M1.w is close to M2.v and |v| = 1. This is useful if we # want to approximate an arbitrary function (usually of two of # variables) by a rational function, possibly with constraints on # the numerator and denominator. In that context, M1 will be a matrix # that sends a vector of coefficients to a vector of values at some # sample points, and similarly for M2. #@ quot_approx quot_approx := proc(M1,M2) local Q1,Q2,R1,R2,QQ,V,v,w,vv,ww; userinfo(5,genus2,"Finding QR decompositions"): Q1,R1 := QRDecomposition(M1): Q2,R2 := QRDecomposition(M2): QQ := Transpose(Q1).Q2: userinfo(5,genus2,"Finding SVD decompositions"): V := SingularValues(QQ,output=['Vt']): v := Column(Transpose(V),1): w := QQ.v: vv := (1/R2).v: ww := (1/R1).w: return([vv,ww]); end: ###################################################################### #@ prime_factors prime_factors := proc(n::posint) local F; F := ifactor(n); if type(F,`*`) then F := {op(F)}; else F := {F}; fi; F := map(u -> `if`(type(u,`^`),op(1,u),u),F); F := map(op,F); return F; end: ###################################################################### # This function can be used to process a chunk of LaTeX code # generated by Maple. If we call process_latex(s) then the string s # is combined with the contents of the file maple_output/maple_blank.tex # (which provides headers and footers) to produce the file # maple_output/maple_output.tex. This is then processed to produce # maple_output/maple_output.pdf. #@ process_latex process_latex := proc(s::string) local olddir,fd,header,footer,in_header,line,full_latex; global maple_dir; olddir := currentdir(maple_output_dir); fd := fopen("maple_blank.tex",READ); header := ""; footer := ""; in_header := true; line := readline(fd); while line <> 0 do if length(line) >= 15 and substring(line,1..15) = "%%% INSERT HERE" then in_header := false; elif in_header then header := cat(header,"\n",line); else footer := cat(footer,"\n",line); fi; line := readline(fd); od; fclose(fd); full_latex := cat(header,"\n",s,"\n",footer); fd := fopen("maple_output.tex",WRITE); fprintf(fd,"%s",full_latex); fclose(fd); ssystem("pdflatex maple_output"); currentdir(olddir); end: ###################################################################### util_stripbackquotes := proc(s::string) local t; t := s; if t <> "" and substring(t,1..1) = "`" then t := substring(t,2..-1); fi; if t <> "" and substring(t,-1..-1) = "`" then t := substring(t,1..-2); fi; RETURN(t); end: util_unixify := proc(s::string) convert(subs(13 = NULL,convert(s,bytes)),bytes); end: ###################################################################### util_splitstring := proc(s::string,c::integer) local b,n,t,i; b := convert(s,bytes): n := nops(b): t := select((i,x,d) -> x[i] = d,[seq(i,i=1..n)],b,c): t := [0,op(t),n+1]: seq(convert(b[(t[i]+1)..(t[i+1]-1)],bytes),i=1..nops(t)-1); end: util_commasplit := proc(s) util_splitstring(s, 44); end: util_semisplit := proc(s) util_splitstring(s, 59); end: util_colonsplit := proc(s) util_splitstring(s, 58); end: util_barsplit := proc(s) util_splitstring(s,124); end: util_tabsplit := proc(s) util_splitstring(s, 9); end: # This splits at forward slashes and backslashes, and ignores # slashes at the end util_slashsplit := proc(s::string) local b,n,t,i; b := subs(92 = 47,convert(s,bytes)): n := nops(b): while (n > 0 and b[n] = 47) do n := n - 1; od; if (n = 0) then RETURN(); fi; t := select((i,x) -> x[i] = 47,[seq(i,i=1..n)],b): t := [0,op(t),n+1]: seq(convert(b[(t[i]+1)..(t[i+1]-1)],bytes),i=1..nops(t)-1); end: util_joinstring := proc(c::string) local l; if nargs = 1 then RETURN(""); fi; l := map((t) -> op([sprintf("%A",t),c]),[args[2..-1]]); RETURN(cat(op(l[1..-2]))); end: util_commajoin := proc() util_joinstring(",",args) end: util_colonjoin := proc() util_joinstring(":",args) end: util_barjoin := proc() util_joinstring("|",args) end: util_tabjoin := proc() util_joinstring(",",args) end: util_slashjoin := proc() util_joinstring("/",args) end: util_linejoin := proc() util_joinstring("\n",args) end: ###################################################################### util_startswith := proc(s::string,t::string) evalb(length(s) >= length(t) and substring(s,1..length(t)) = t); end: ###################################################################### util_splitfilename := proc(file::string, # optional suffix_::string) local pieces,m,dirname,basename; pieces := [util_slashsplit(file)]; if pieces = [] then ERROR("Empty file name"); fi; dirname := util_slashjoin(op(pieces[1..-2])); basename := pieces[-1]; if nargs > 1 then m := length(suffix_); if length(basename) >= m and substring(basename,(-m)..(-1)) = suffix_ then basename := substring(basename,1..(-m-1)); fi; fi; RETURN([dirname,basename]); end: util_dirname := proc(file::string, # optional suffix_::string) util_splitfilename(args)[1]; end: util_basename := proc(file::string, # optional suffix_::string) util_splitfilename(args)[2]; end: ###################################################################### #@ strip_worksheet # This removes all output from a worksheet (typically making it much # smaller) and saves the result in the 'small' subdirectory of the # 'worksheets' directory strip_worksheet := proc(file) local dir,doc; doc := Worksheet[ReadFile](cat(worksheets_dir,"/",file,".mw")); doc := XMLTools[ApplyElement](doc,"Output",() -> NULL): dir := cat(worksheets_dir,"/small"); if not(FileTools[Exists](dir)) then mkdir(dir); fi; Worksheet[WriteFile](cat(dir,"/",file,".mw"),doc); NULL; end: #@ strip_worksheets strip_worksheets := proc() map(strip_worksheet,[ "build_data", "build_data_toy", "check_all", "checks", "genus2", "genus2_pics", "genus2_talk_pics", "text_check" ]); NULL; end: