The purpose of this article is to document how to setup log files on PARI/GP, SageMath and Magma. The motivations of this article are:
- The desire to automatically save all your interactions with each computer algebra system.
- The desire to properly and quickly recover data which took hours of computation from each computer algebra system.
The first motivation is useful, for example, to go back to computations you thought were not useful before but suddenly realized was actually useful. With all the logs saved automatically, finding the code you used to recover something you computed once months ago is just a matter of figuring out when you computed it (and maybe using
grep to find it). It is also useful when you switch back and forth between the computer algebra systems and forget proper syntax (i.e. How do you declare an elliptic curve in PARI/SAGE/Magma again?). The second motivation is useful to save time, in order to not need to recompute something that has been computed before.
This article assumes that you already have a working installation of the above computer algebra systems and are looking for a quick way to get things working. It also assumes that you are working in Ubuntu/Linux.
The default location of the PARI/GP startup script is in
~/.gprc. If you compiled PARI/GP yourself, you should find a
misc folder on the directory where the PARI installation files are located. There is a
gprc.dft file which is basically a sample
gprc file. If you ever need to look something up, an HTML documentation and PDF documentations are available.
One can also set log files in
gprc. To do so, append these lines to your
logfile = "~/logs/%Y%m%d-%H%M.parilog" log = 1
Every time you open a GP session, PARI creates a file whose filename is of the form
logfile. My system saves the log file in the directory
~/logs/ with a filename depending on the system date and time (i.e. it will be of the form
Another useful option that one can set in
gprc is the number of lines of the output results. To set the limit, one puts the following in
lines = 20
One would typically set this low for readability. However, you might want to change this to a huge number (or
0 for no limit) so that GP prints the output completely and thus is also recorded in the log file.
Additionally, output in PARI is quite transparent. Initializing a number field via
bnfinit returns a vector containing all the data PARI needs to efficiently compute most number field computations you require. If you ever need to work with the same number field again, you can simply copy the vector and paste it onto another PARI session to get almost exactly the same number field structure (pay attention to precision, etc).
The default location for the magma startup script is
The way I made log files work in Magma is to append the following scirpt to
~/.magmarc (or wherever your startup script is).
D := POpen("date +'~/logs/%Y%m%d-%H%M.magmalog'", "r"); F := Gets(D); SetLogFile(F);
The log file records the input and output, as it appears on the console. If you are curious on why this works,
POpen executes the shell command
date and outputs our desired filename for the log file. The command
Gets interprets this output into a Magma string and
SetLogFile does what you expect it to do.
As noted in its documentation, the default location for the SAGE startup script is
The script to generate the log file is based on this stackexchange answer. I leave it up to the reader to install the necessary python imports. Without further ado, here is what you would need to append to your
import atexit import os import time ip = get_ipython() LIMIT = 0 # limit the size of the history def save_history(): """save the IPython history to a plaintext file""" F = time.strftime('/home/guissmo/logs/%Y%m%d-%H%M.sagelog'); histfile = os.path.join(os.path.expanduser(F);) print("Saving plaintext history to %s" % histfile) lines =  # get previous lines # this is only necessary because we truncate the history, # otherwise we chould just open with mode='a' if os.path.exists(histfile): with open(histfile, 'r') as f: lines = f.readlines() # add any new lines from this session lines.extend(record + '\n' for record in ip.history_manager.get_range()) with open(histfile, 'w') as f: # limit to LIMIT entries f.writelines(lines[-LIMIT:]) # do the save at exit atexit.register(save_history)
As you can see, this code is much more involved. And it even only records the input! If you ran a command that takes a long time to receive the output, then you must run it again to recover the data. At least you have the commands to redo it!
Saving and Loading Hours of Work
I did not bother to figure out how to copy everything in SAGE console and save it in a log file. In any case, simply copying what is visible in the console is not enough to recover output which took hours to find.
hardtocompute contains data which took a long time to compute, one can execute
dumps(hardtocompute) and save the result to a file. In a future SAGE session, accessing the result (by reading the saved file) and using
loads then recovers whatever
hardtocompute used to be. I have not tested extensively to what extent the structure is recovered. Perhaps let me know if you figure out some of the intricacies.
loads strategy is automated in this script.
import atexit import os import time ip = get_ipython() LIMIT = 0 # limit the size of the history F = time.strftime('~/logs/%Y%m%d-%H%M.sagelog'); D = time.strftime('~/logs/%Y%m%d-%H%M.sagedump'); histfile = os.path.join(os.path.expanduser(F)); dumpfile = os.path.join(os.path.expanduser(D)); print("Recording input history to %s" % histfile); print("Recording output history to %s" % dumpfile); def dumper(oh, i): if i in oh.keys(): return dumps(oh[i]) else: return 0 def undumper(l, i): if l[i] != 0: return loads(l[i]) else: return 0 def save_history(): """save the IPython history to a plaintext file""" lines =  # get previous lines # this is only necessary because we truncate the history, # otherwise we chould just open with mode='a' if os.path.exists(histfile): with open(histfile, 'r') as f: lines = f.readlines() # add any new lines from this session lines.extend(record + '\n' for record in ip.history_manager.get_range()) with open(histfile, 'w') as f: # limit to LIMIT entries f.writelines(lines[-LIMIT:]) print("Saving input history to %s" % histfile) with open(dumpfile, 'w') as f: l =  + [dumper(_oh, i) for i in range(1,max(_oh)+1)]; f.write(dumps(l)); print("Saving output dumps to %s" % dumpfile) def read_dump(s): f = open(s, 'r'); l = loads(f.read()); f.close(); return [ undumper(l, i) for i in range(1, len(l)) ]; # do the save at exit atexit.register(save_history)
This script automatically saves the input history and the output history into two separate files. To recover the output as an array later on, use
s is the complete file path and file name of the dump of the session you wish to recover. The output of
read_dump is an array whose
ith entry is the corresponding output of the
ith input, if any. Otherwise, it is
0 (in the case where the
ith input does not return any data).
Do let me know if you found this useful. If you have suggestions or comments to improve this, please feel free to share.