gp-inline -- run Pari/GP code inline in a document
gp-inline [--options] filename...
gp-inline
extracts and executes Pari/GP code from comments written inline in a document source such as TeX or POD or in program source code. This can be used to check calculations or formulas alongside their statement in a document or use in a program. For example in TeX
From this proposition it follows $1+1 = 2$. % GP-Test 1+1 == 2
which is checked by running
gp-inline foo.tex
Pari/GP has bignum integers, fractions, complex numbers, vectors, and more, which can be used for simple arithmetic and complicated calculations. It has a lot of number theory and other mathematics for sophisticated mathematical checks.
The command line options are
--run
Run the inline tests in each given file. This is the default action.
--stdin
Read a document from standard input instead of named files.
--extract
Extract the inline gp
code from each file and print to standard output. This output is what --run
would pass to gp -f
.
Usually --extract
should be used on just one input file, otherwise the tests of each file are output one after the other and globals left by the first might upset later tests.
--defines
Extract just the GP-DEFINE
parts of the given files and print to standard output.
This is good for using separately in further calculations or experiments. (It's also possible to go the other way, have definitions in a separate file which the document loads with read()
. Usually it avoids mistakes to keep a definition with the formula etc in the document. But generic or very large code could be separate.)
--help
Print a brief help message.
--verbose
Run gp
with command echoing to show the progress it's making or where an error occurs.
--version
Print the program version number and exit.
A GP-Test
line must evaluate to 0 or 1. Usually it will be an ==
or compare etc, but can be anything 0 or 1. The evaluation is inside a function body so semicolons can separate a sequence of expressions and the last is the result.
% GP-Test my(n=5); 2*n^2 + n == 55
Requiring result 0 or 1 helps avoid mistakes like forgetting "== 123" etc. The suggestion is no final semicolon on a GP-Test
so it can be copied into GP to see the result when experimenting, but gp-inline
works with or without.
The suggestion is also to keep variables local with my
to avoid one test depending on another accidentally, but that's not enforced. See "GP-DEFINE" below for making global variables.
Multi-line tests can be written with GP style backslashing
% GP-Test some_thing() \ % GP-Test == 123
Comments can be included in a test in GP /* ... */
style. Don't use \\
style as the expressions gp-inline
constructs don't work properly with that yet.
% GP-Test 105 == 3*5*7 /* its prime factors */
A comment should not be the sole content of a test, since gp
takes an empty expression as 0 so the test fails.
% GP-Test /* don't put a comment alone */
A GP comment can be part of a test with backslashing if desired.
% GP-Test /* some comment */ \ % GP-Test 1 + 1 == 2
This could just as easily be a document comment, but making it part of the test expression puts it though in gp-inline --extract
for human readability or debugging (but doesn't show in --verbose
since gp
collapses comments from its echos).
Tests are run with gp -f
so any user ~/.gprc or $GPRC
file is not evaluated. This is designed to give consistent test results, avoiding personal preferences wanted for gp
interactively etc.
gp
has features to load other GP code, write files, and spawn shell commands for almost arbitrary system actions, so only run gp-inline
on trusted documents etc.
The following prefixes are recognised for a GP-Test
line (etc)
GP-Test 1+1==2 # GP-Test 1+1==2 % GP-Test 1+1==2 /* GP-Test 1+1==2 */ * GP-Test 1+1==2 // GP-Test 1+1==2 \\ GP-Test 1+1==2 =for GP-Test 1+1==2
These are comments in Perl, TeX, C, C++, GP, and Perl POD directive =for
. In C style /*
an optional trailing */
is stripped. Or /*
and */
could be their own lines if preferred. This suits a block of several tests
/* GP-Test 1+1==2 */ /* * GP-Test 1+1==2 * GP-Test 2+2==4 */
A Perl POD =for
should be a single line and will usually need a blank line before and after to be valid POD. Those blanks can be tedious when writing a few tests and in that case the suggestion is to =cut
and write a block of tests
=cut # GP-Test 2+2==4 # GP-Test 4+4==8 =pod
The #
prefix here is not needed if already after an __END__
and thus not evaluated by Perl, but it's a good way for human readers to distinguish those lines from the POD text.
The prefixes include GP's own \\
comment, or it has C syntax too. GP tests inline in GP source code are good for the same reasons as other languages, ie. they're close to relevant code or help text and don't slow down normal execution.
Definition lines can create new GP functions or globals
% GP-DEFINE my_func(n) = 2*n + 3; % GP-DEFINE my_vector = [ 1, 2, 3, 5 ];
These lines are arbitrary code passed directly to GP. Generally they should end with a ;
to suppress result printing from GP (depending how it's run), but that's not enforced. Multi-line functions or expressions can use either backslashing or braces
% GP-DEFINE long_func(n) = \ % GP-DEFINE some + long \ % GP-DEFINE - expression; % GP-DEFINE my_matrix = {[ % GP-DEFINE 1, 2; % GP-DEFINE 2, 1 % GP-DEFINE ]};
A definition is usually something used a few times but it can be convenient to make a definition even when used just once, especially if it shortens a long test expression.
Definition lines can use GP default()
to make settings. For example strictargs
is a good way to guard against mistakes in function arguments (assuming you're not deliberately lazy or using default zeros)
% GP-DEFINE default(strictargs,1);
External GP code modules can be loaded with the usual read()
.
% GP-DEFINE read("my-library.gp");
If you mistakenly write GP-DEFINE
instead of GP-Test
then that expression is of course not a test. There's no way for gp-inline
to identify that, but if you leave off ;
semicolons from test lines then it shows as a stray result print.
GP-Test-Last
are tests run at the end of the document after the rest of the input file. This lets a test precede the definition of some formula or data. This doesn't happen often, usually only when a document gives an example of a formula's use before the full statement. If you keep the GP-DEFINE
with the full statement then GP-Test-Last
allows tests using it to be written earlier.
We will find below that f(6)=10 ... % GP-Test-Last f(6) == 10 The unique function satisfying is thus f(n) = 2n - 2. % GP-DEFINE f(n) = 2*n - 2;
Care should be taken not to redefine globals which GP-Test-Last
will need. It's wise anyway not to change the meaning of globals so that document sections can be rearranged etc without upsetting checks, and so definitions can be extracted and used for other experiments or tests.
For testing a function on a set of values the suggestion is to use vector
to evaluate something like
% GP-Test vector(100,k, fibonacci(k)) == \ % GP-Test vector(100,k, fibonacci(k-1) + fibonacci(k-2))
Or for 2 variables matrix
similarly (or a vector of vectors for varying second range). If a test fails then the relevant parts can be copied into GP to see the values on each side. Changing "==" to a subtraction "-" can show differences, to see perhaps only a few are wrong, or everything off by a constant.
The index variable or variables can be manipulated to test over some range other than 1 to n. The intention is to have sort of inline test form which could use for()
or forstep()
since they make test ranges clearer, but don't know yet how that should look or how to print which index fails etc.
Another approach suitable for powers and linear recurrences is to express sequences of values in polynomial generating functions (type t_RFRAC
). Equalities within a sequence or between sequences become equalities on the polynomials with suitable shifts, spreads, etc. A generating function effectively encapsulates an entire sequence so equality verifies an identity for all n. This suits sums, index changes, and multiplications by constants, but term-wise multiplication may be difficult.
Syntax errors and type errors in tests and definitions are fatal. The current implementation runs gp --default recover=0
so such problems cause an immediate non-zero exit. A location string is included in the test expression so the backtrace has something like
*** at top-level: ...inline("foo.tex:153",(()->bar())()) ...
which means input file foo.tex line 153 was the offending GP-Test
.
Errors in GP-DEFINE
statements don't have this location in the backtrace (since they're a "top-level" evaluation). If the offending part is not obvious then try gp-inline --verbose
to see a GP \e
trace of each expression. It includes some "foo.tex:150"
etc location strings. The usual GP default(log,1)
is available too (writing to file pari.log by default).
This location printing is not very good. An equivalent of #line
in GP could help tell it an original location. Or could a print go before an error backtrace? An iferr()
trap to do that loses the backtrace.
Numbers in the document text can be extracted as GP definitions. For example a constant foo=123
,
% GP-CONSTANT foo 123 % GP-END
Or vector bar=[1,2,3]
,
% GP-VECTOR bar 1, 2, 3 % GP-END
Or matrix quux=[1,2; 3,4]
,
% GP-MATRIX quux 1 & 2 \\ 3 & 4 % GP-END
These GP definitions can be used in subsequent tests, and the numbers are also document text or program code, etc. The number forms accepted are
123 integer -1, {+}1 signs, optionally with TeX {} 1.42 decimal fraction \frac58 TeX \frac, \tfrac, \dfrac \dfrac{12}{34} -3-4i complex number, lower case i \tfrac{5}{2+i} fractions with complex numbers , {,} & vector separator commas \\ matrix row separator
Multiple commas etc are treated as just one. The matrix row separator \\
is treated like a comma in VECTOR
. There should be only one value in CONSTANT
. Leading and trailing commas are ignored.
Decimal fractions 12.45
become rationals (type t_FRAC
) like 1245/100
to preserve the exact value. If it's actually some irrational which has been truncated then staying exact lets you make an exact check of the decimals given, independent of the GP float precision()
. Presently there's nothing to have comma as decimal point, nor to have comma or other for a thousands separator.
Complex numbers use lower-case i for the imaginary part, either alone or after a number. They become type t_COMPLEX
. Presently there's nothing to have a different letter like say j.
The number syntax accepted is quite strict. This is designed to ensure gp-inline
doesn't quietly ignore something it shouldn't.
Some bits of TeX are ignored. These are things often wanted in a list of numbers. However in general it's best to confine GP-CONSTANT
etc to just the numbers and keep TeXisms outside.
= initial = sign &= initial TeX align and = sign \, \> \: \; \quad \qquad various TeX spacing and macros \kern1.5em \mkern1.5mu measures em,ex,pt,mm,cm,in,mu \hspace{5pt} \phantom{...} \vphantom{...} \degree \dotsc \dotsb
\kern
should be a single numbered measure. Don't use a comma for the decimal. \phantom{}
cannot contain further nested { }
braces (though it could contain equivalent \begingroup
and \endgroup
if really needed).
Comments, both TeX and other styles, cannot be in a list of numbers.
This number extraction system is not enough for all purposes. There will always be some expression, markup, or layout, which is too specific for gp-inline
to recognise. The suggestion in that case is to write a suitable corresponding GP-DEFINE
beside the values. Duplicating expressions like that is not nice, but done once and with the define right beside the numbers it's not too bad.
% GP-DEFINE sqrt5 = quadgen(20); % GP-Test sqrt5^2 == 5 The golden ratio is $\phi = \frac{1+\sqrt{5}}{2}$. % GP-DEFINE phi = ( 1+ sqrt5 )/2;
When including numbers in a document or program there's a bit of a choice between writing them in the document and applying checks, versus generating the numbers externally and #include
or equivalent to bring them in. An include
has the disadvantage of several little files (probably). Inline is a bit tedious to insert manually but then isn't vulnerable to breakage in generator programs.
Calculations in floating point often require some sort of nearly_equal()
test. It might go by difference or by ratio, and how much accuracy to expect depends on how much error is likely to accumulate and how much GP precision is selected. Such tests can be useful for trigonometry or polynomial roots to at least guard against a wildly wrong formula. For a single polynomial root GP nfinit()
etc can do exact calculations.
Various computer arithmetic or computer algebra systems could be used in a similar way to gp-inline
. GP has the attraction of a compact syntax for expressions and new functions, and a range of arbitrary precision basic types, even quads for exact square roots, plus a lot of number theory for higher mathematics.
An Emacs gp-inline.el
is included in the gp-inline
sources. Its M-x gp-inline-eval runs a test line with GP in a PariEmacs subprocess. If the result is false then you might correct either the test or the text. This eval works on ordinary comment lines too so can be used for GP expressions left as experiments in a document.
M-x gp-inline-defines extracts and passes all GP-DEFINE
, GP-CONSTANT
etc to PariEmacs (all of gp-inline --defines
). This can load definitions needed to run test lines or experiment.
Generally Emacs' parenthesis matching doesn't work across multiple %
or #
etc comment lines. When editing a multi-line GP-Test
or GP-DEFINE
it can help to temporarily uncomment (C-u M-x comment-region
) in order to fix or apply parens to a deeply nested expression. Usually GP-Test
etc keywords can be left while doing this, just the comment prefix removed.
A pattern for the error message in "Errors" above is added to compilation-mode
(until perhaps hopefully in the future that message could become simply file:line:
). Alas however a filename longer than 19 characters is truncated by GP in its output so is not matched.
gp-inline
exits with 0 if all tests in all files are successful, or non-zero if any problems.
gp-inline
removes its temporary files on normal exit and also on exit for SIGINT
or SIGTERM
. gp-inline
forcibly kills its gp
subprocess too, if that hasn't already been killed by the same SIGINT
say.
It's possible for gp
to run a further subprocess (its system()
etc), which gp-inline
doesn't know and doesn't wait for. Not sure that works very well, but hope such an interrupt is uncommon.
TMPDIR
, TEMP
, TMP
, etcUsual directory for temporary files per File::Temp (which in turn File::Spec tmpdir()
).
There's no support for a multi-file document where defines would be carried over from one part to the next. The suggestion is either cat
all together to pass to gp-inline
; or use --defines
to extract the definitions from one file and have a read()
of them in the next. The latter approach is good for getting main document definitions into separate work-in-progress too.
Some sort of GP-POLYNOMIAL
to pick a polynomial out of some TeX could be good, though the syntax recognised would have to be fairly restricted. It could include polynomial fractions (type t_RFRAC
).
Some sort of --secure
to run gp
with secure=1
might be good for executing tests not fully trusted.
http://user42.tuxfamily.org/pari-gp-inline/index.html
Copyright 2015, 2016, 2017, 2018 Kevin Ryde
gp-inline is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version.
gp-inline is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with gp-inline. If not, see http://www.gnu.org/licenses/.