(* $Id: Util.m,v 1.1 2000/11/28 18:42:43 dreeves Exp dreeves $ Daniel Reeves Miscellaneous useful mathematica functions. TODO: goofy failures if a symbol is referenced before it is defined. The solution is probably to define all the usage statements first. PS: Equilibria.m has a solution to this problem. *) BeginPackage["Util`", {"MathIO`"}]; Begin["`Private`"]; Util`verboseTiming::usage = "verboseTiming[predictedTime_][expression_] is like Timing[expression] but with other stuff printed."; SetAttributes[verboseTiming, HoldRest]; verboseTiming[predictedTime_, expression_] := Module[{t}, Print["BEGIN run for ", predictedTime, "s starting at ", ToDate[AbsoluteTime[]], " till ", ToDate[AbsoluteTime[] + predictedTime]]; t = Timing[expression]; Print["END at ", ToDate[AbsoluteTime[]]]; t] (* TODO: the name "body" interferes with mail[]; what the hell is that about *) Util`doWhile::usage = "doWhile[body,test] implements a C-style do loop."; SetAttributes[doWhile, HoldAll]; doWhile[bodyTODO_, test_] := While[True, Evaluate[bodyTODO]; If[!Evaluate[test], Break[]]] (* TODO: the path to lockfile could change *) Util`lockFile::usage = "Locks the specified file for atomic writes."; lockFile[filename_String] := Module[{returnVal}, returnVal = Run[(*"/usr/um/bin/" <> *) "lockfile " <> filename <> ".lock"]; If[returnVal =!= 0, pout["WARNING: lockfile didn't work; trying a different path...\n"]; returnVal = Run["/usr/um/bin/lockfile " <> filename <> ".lock"]; ]; If[returnVal =!= 0, pout["WARNING: lockfile still didn't work; trying a different path...\n"]; returnVal = Run["/usr/bin/lockfile " <> filename <> ".lock"]; ]; If[returnVal =!= 0, pout["WARNING: lockfile *still* didn't work; giving up...\n"]; ]; pout["DIAGNOSTICS: lockfile returned ", returnVal, "\n"]; returnVal ] Util`unlockFile::usage = "Unlocks the specified file."; unlockFile[filename_String] := Run["rm -f " <> filename <> ".lock"]; Util`registerProcess::usage = "registerProcess[proccesIdFile, info] appends the process id and machine name and info string to the processIdFile. My perl script signal-all.pl can be run with this file as an arg to send all the processes in the file a signal. It's a good idea to call this function at the beginning of a program that runs for a long time and may be invoked on many machines."; registerProcess[processIdFile_String, info_String] := Module[{r}, lockFile[processIdFile]; r = OpenAppend[processIdFile]; (* TODO: make sure no newlines in info. *) WriteString[r, strout[$MachineName, If[$MachineDomain =!= "", ".", ""], $MachineDomain, " ", $ProcessID, " ", info, "\n"]]; Close[r]; unlockFile[processIdFile]; ] Util`shuffle::usage = "Takes a list and returns a random permutation of it."; shuffle[l_List] := #2& @@@ Sort[{Random[],#}& /@ l] Util`getFile::usage = "getFile[filename] returns the contents of the file as a string."; getFile[f_String] := Module[{s = Import[f, "Text"]}, If[s === $Failed, "[ERROR OPENING " <> f <> "]", s] ] Util`runThruString::usage = "Sends a string as stdin to a command and returns the commands output as a string."; runThruString[cmd_String, input_String] := Module[{in, out}, in = "!echo \"" <> input <> "\" | " <> cmd; out = Import[in, "Text"]; If[out === $Failed, "[ERROR PIPING FROM " <> i n<> "]", out] ] (* runThru[cmd_String, input_String] := Module[{s}, WriteString[s = OpenTemporary[], input]; Import["!"<>cmd<>" < " <> Close[s], "Text"] ] *) Util`tupleMax::usage = "tupleMax[list] returns the tuple that is lexicographically maximal."; tupleMax[l_List] := Fold[If[OrderedQ[{#1, #2}], #2, #1]&, First[l], Rest[l]] Util`tupleMin::usage = "tupleMin[list] returns the tuple that is lexicographically minimal."; tupleMin[l_List] := Fold[If[OrderedQ[{#1, #2}], #1, #2]&, First[l], Rest[l]] Util`argMax::usage = "argMax[f, domain] returns the element of domain for which f of that element is maximal -- breaks ties in favor of first occurrence."; SetAttributes[argMax, HoldFirst]; (* argMax[f_, dom_List] := tupleMax[{f[#],#}& /@ dom][[2]] *) argMax[f_, dom_List] := Fold[If[f[#1]>=f[#2], #1, #2]&, First[dom], Rest[dom]] Util`argMin::usage = "argMin[f, domain] returns the element of domain for which f of that element is minimal."; SetAttributes[argMin, HoldFirst]; argMin[f_, dom_List] := argMax[-f[#]&, dom] (* TODO: this serendipitously works if niceValue is either an int or a string, but that's probably being too sloppy. *) Util`reniceMyself::usage = "reniceMyself[niceValue] renices this process and its parent process."; reniceMyself[niceValue_] := Run[strout["renice ", niceValue, " ", $ProcessID, " ", $ParentProcessID]] Util`lockPut::usage = "Locks a file, then does a normal put, then unlocks the file."; lockPut[stuff__, filename_String] := ( Get["!lockfile " <> filename <> ".lock"]; (* TODO: use lockFile... *) Put[stuff, filename]; Get["!rm -f " <> filename <> ".lock"]; ) Util`lockSave::usage = "Locks a file, then does a normal Save, then unlocks the file."; SetAttributes[lockSave, HoldRest]; lockSave[filename_String, stuff_] := ( Run["lockfile " <> filename <> ".lock"]; (* TODO: use lockfile *) Save[filename, stuff]; Run["rm -f " <> filename <> ".lock"]; ) Util`SYSTEMINFO::usage = "A string with things like machine and user name and process IDs."; SYSTEMINFO := strout[ "$MachineName: ", $MachineName, "\n$MachineDomain: ", $MachineDomain, "\n$UserName: ", $UserName, "\n$ProcessID: ", $ProcessID, "\n$ParentProcessID: ", $ParentProcessID, "\n$CommandLine: ", $CommandLine, "\nARGV: ", Global`ARGV, "\n" ] Util`ENV::usage = "A string showing all operating system environment variables."; ENV = Read["!/usr/bin/env", Record, RecordSeparators -> {}]; Close["!/usr/bin/env"]; Util`intersperse::usage = "Like Perl's join function."; intersperse[delim_String, l_List]:= Module[{s}, StringJoin @@ Drop[s[#,delim]& /@ l /. s->Sequence, -1]] (* TODO: decide which is better, this or the version above *) Util`intersperse2::usage = "Like Perl's join function."; intersperse2[delim_String, l:{__String}] := StringJoin @@ Drop[Flatten@Transpose[{l, Table[delim, {Length[l]}]}], -1] Util`mail::usage = "\n mail[\n to -> {list of addresses}, \n from -> \"Some Name \", (* or just email adr *) \n subject -> \"string\", \n body -> \"string\", (* see SYSTEMINFO and BODY *)\n headers -> {list of \"Header: stuff\" strings} (* addit'l headers *)].\n Use Options[mail] to see default options."; Util`to::usage = "Option for mail; gives list of email addresses to send to."; Util`from::usage = "Option for mail; see usage for mail."; Util`subject::usage = "Option for mail; see usage for mail."; Util`body::usage = "Option for mail; see usage for mail."; Util`headers::usage = "Option for mail; see usage for mail."; Options[mail] := { to -> {"dreeves@eecs.umich.edu"}, from -> "MY-MMA-AGENT ", subject -> strout["OUTPUT from process ", $ProcessID, " on ", $MachineName], body -> strout[SYSTEMINFO, "\nEnvironment:\n\n", ENV], headers -> {} }; mail[opts___] := Module[ {opt, allHeaders, stream}, opt[x_] := x /. {opts} /. Options[mail]; allHeaders = Join[opt[headers], { "Subject: " <> opt[subject], "From: " <> opt[from], "To: " <> intersperse[", ", opt[to]]} ]; stream = OpenWrite["!/usr/lib/sendmail -oi -t"]; WriteString[stream, intersperse["\n", allHeaders], "\n\n", opt[body]]; Close[stream]; ] (* TODO: don't have this tied up with mail. Add an optional parameter for a function to call on assertion failure, which could do the mail-sending. *) Util`assert::usage = "assert[test_,info_String] makes sure that test evaluates to True, otherwise it sends mail and calls Exit[1]. Also, prints to STDERR. Info is just extra info about what we were doing or why this assertion should be true."; SetAttributes[assert, HoldFirst]; assert[test_, info_String:""] := If[test =!= True, With[{error = strout["FAILED ASSERTION: ", HoldForm[test]]}, pout[error, "\n"]; If[info != "", pout["INFO: ", info, "\n"]]; mail[subject -> strout[error, " (", info, ")"]]; Exit[1] ] ] Util`domain::usage = "List of keys (as lists) for a hash."; SetAttributes[removeHead, {HoldAll}]; removeHead[h_[args___]] := {args} domain[s_] := removeHead @@@ First /@ DownValues[s] Util`ni::usage = "Shows a number as a decimal number, and on one line."; ni[x_Integer] := x ni[x_] := InputForm[N[x]] Util`abort::usage = "A constant used as a possible parameter to interruptedWhile."; Util`interruptedWhile::usage = "Like a While loop except for the 1st 2 args, which say: every so many seconds (and once at the end regardless) pause and execute some code, presumably with the purpose of announcing some partial results or the status of the computation."; SetAttributes[interruptedWhile, HoldRest]; interruptedWhile[abort, interruptBody_, whileTest_, whileBody_] := While[whileTest, CheckAbort[While[whileTest, AbortProtect[whileBody]], interruptBody]] interruptedWhile[interruptFreq_, interruptBody_, whileTest_, whileBody_] := While[whileTest, TimeConstrained[ While[whileTest, AbortProtect[whileBody]], interruptFreq]; interruptBody; ] Util`fileAbsoluteTime::usage = "Give the AbsoluteTime of the file modification date."; fileAbsoluteTime[fileName_String] := (Date/.FileInformation[fileName]) (* TODO: see setFirstCheckPoint in the perl version (util.pl) *) Util`checkPoint::usage = "Call this in a loop and it will return true the " <> "first time and false the subsequent times except every specified " <> "number of seconds thereafter."; nextCheckPoint = 0; checkPoint[seconds_] := With[{t = AbsoluteTime[]}, If[t >= nextCheckPoint, nextCheckPoint = t + seconds; Return[True]; ]; Return[False]; ] (* TODO combine these functions to one, but keep the separate names for external use *) Util`StringPadRight::usage = "StringPadRight[str,n,x] pads a string on the right with char x (defaults to space) such that the length is n (returns original string if longer than n). If the first arg is not a string, does ToString on it."; StringPadRight[str_String, n_Integer, x_String:" "] := If[StringLength[str] >= n, str, StringJoin[PadRight[Characters[str], n, x]]] StringPadRight[stuff_, n_Integer, x_String:" "] := StringPadRight[ToString[stuff], n, x] Util`StringPadLeft::usage = "StringPadLeft[str,n,x] pads a string on the left with char x (defaults to space) such that the length is n (returns original string if longer than n). If the first arg is not a string, does ToString on it."; StringPadLeft[str_String, n_Integer, x_String:" "] := If[StringLength[str] >= n, str, StringJoin[PadLeft[Characters[str], n, x]]] StringPadLeft[stuff_, n_Integer, x_String:" "] := StringPadLeft[ToString[stuff], n, x] Util`nspl::usage = "nspl is number-string-pad-left."; nspl[num_, n_] := StringPadLeft[num,n,"0"] Util`dd::usage = "dd is double-digit, for taking a number from 0-99 and returning a 2-character string like \"03\" or \"42\"."; dd[num_?(#<100&)] := nspl[num, 2] daysInYear = 365.242198781; (* lightyear / c *) weeksInYear = daysInYear / 7; daysInMonth = daysInYear / 12; weeksInMonth = daysInMonth / 7; sS = 1; mS = 60*sS; hS = 60*mS; dS = 24*hS; wS = 7*dS; oS = daysInMonth*dS; yS = daysInYear*dS; (* TODO: handle negative number of seconds *) Util`seconds2str::usage = "Convert a number of seconds to years,months,weeks,days,hrs,mins,secs."; seconds2str[secs_] := Module[ {y, yQ= False, o, oQ= False, w, wQ= False, d, dQ= False, h, hQ= False, m, mQ= False, s= secs}, y = Floor[s / yS]; If[y>0, yQ = oQ = wQ = dQ = hQ = mQ = True]; s -= y * yS; o = Floor[s / oS]; If[o>0, oQ = wQ = dQ = hQ = mQ = True]; s -= o * oS; w = Floor[s / wS]; If[w>0, wQ = dQ = hQ = mQ = True]; s -= w * wS; d = Floor[s / dS]; If[d>0, dQ = hQ = mQ = True]; s -= d * dS; h = Floor[s / hS]; If[h>0, hQ = mQ = True]; s -= h * hS; m = Floor[s / mS]; If[m>0, mQ = True]; s -= m * mS; Return[strout[ If[yQ, strout[y," years "], ""], If[oQ, strout[o," months "], ""], If[wQ, strout[w," weeks "], ""], If[dQ, strout[d," days "], ""], If[hQ, strout[dd[h],":"], ""], If[mQ, strout[dd[m],":",dd[Round[s]],"s"], strout[s,"s"]]]]; ] (* TODO: seconds2str is a better version of this now. *) Util`secondsToStr::usage = "Convert a number of seconds to days,hrs,mins,secs."; secondsToStr[0, _] := "" secondsToStr[seconds_, {label_}] := dd[seconds] <> label secondsToStr[seconds_, {rest__, {unit_, label_}}] := secondsToStr[Quotient[seconds - Mod[seconds, unit], unit], {rest}] <> ToString[Mod[seconds, unit]] <> label secondsToStr[seconds_] := secondsToStr[seconds, {"days ", {24, ":"}, {60, ":"}, {60, "s"}}] secondsToStr[0] := "0s" (* TODO: TimeZone[] is reported as 0 on mynah, making this give GMT time *) Util`time2str::usage = "Convert an absolute number of seconds (add absolute time of 1970-1-1 00:00:00 for Unix (timezone issues?)) to a date/time string."; time2str[t_:AbsoluteTime[]] := Module[ {y,m,d,h,min,s}, {y,m,d,h,min,s} = ToDate[t]; strout[y,"-",dd[m],"-",dd[d]," ", dd[h],":",dd[min],":",dd[s]] ] End[]; (* Private context *) EndPackage[];