for the
Float Versions, 2.1
Robert H. Lewis
http://www.bway.net/~lewis/
© 1992, 1995-1999. all rights reserved
Fermat is an interactive system for mathematical experimentation. It
is a super calculator - computer algebra system, in which the basic items
being computed can be real numbers, rational numbers, complex numbers,
modular numbers, multivariable polynomials, rational functions, or polynomials
modulo other polynomials.
There are several versions of Fermat, in two different senses of the
word "version." Originally Fermat ran under Apple's MPW development system,
which provides a Unix-like interface. Later, stand-alone versions were
produced as well with Metrowerks CodeWarrior. Since MPW is now freely available
from Apple by ftp, the MPW versions remain a good choice for their ease
of use. The stand-alone versions compiled for PPC processors are faster,
however. The other sense of "version" involves the basic "type" one is
computing with. One can work over the rationals, in which numerical values
are ratios of integers of unlimited size, or over the reals, in which case
numerical values are the classic "floats" of about 18 significant digits.
One version of Fermat, called QFermat, works over the rationals, and the
other, called FFermat, works over floats. Originally there was a single
program that allowed one to convert back and forth between these types,
but that became unwieldy.
In FFermat one can extend the basic real type to complex arithmetic
- arithmetic of ordered pairs in which multiplication follows the defining
property of the field of complex numbers; the ground ring becomes C. In
QFermat one may choose to work modulo a specified integer n, thereby in
effect changing the ground ring from Q to Z/n.
Choosing modular or complex mode establishes the ground field
(or ground ring) F. On top of this may be attached any number of
unevaluated variables t1, t2, ..., tn,
thereby creating the polynomial ring F[t1, t2, ...,
tn] and its quotient field, the field of rational functions,
whose elements are called quolynomials. Further, polynomials p,
q,... can be chosen to mod out with, creating the quotient ring F(t1,
t2, ...) / < p, q, ... > , whose elements are called
polymods.
Finally, it is possible to allow Laurent polynomials, those with
negative as well as positive exponents. Once the computational ring is
established in this way, all computations are of elements of this ring.
Fermat has extensive built-in primitives for array and matrix manipulations,
such as submatrix, sparse matrix, determinant, normalize, column reduce,
and matrix inverse. It is consistently faster than some well known computer
algebra systems - orders of magnitude faster in many cases.
Fermat is a complete programming language. Programs are called functions,
and their use is typical of that in many languages. Programs and data can
be saved to an ordinary file that can be examined as any other file, read
during a later session, or read by some other software system.
FFermat has powerful, simple, but extremely flexible graphics capabilities.
Any
numerical values created by any Fermat program can be plotted as easily
as they can be written as digits. It is easy to produce two- and three-
dimensional
graphs of functions, with real or complex domain and range.
Objects
consisting of lines, points, or polygons can be defined and then rotated,
translated, cut up, or strung together to form animation sequences,
or movies. Numerical solutions of differential equations, such as planetary
motions, can be displayed as movies. Surfaces can be graphed as hidden-line
objects or shaded objects. Every point, line, or polygon can
be colored.
For the sake of speed, one can write subprograms in C or Pascal and
invoke them from Fermat.
Fermat has several unique features that enhance debugging. It is possible
to interrupt Fermat by pressing the command key, examine the state of a
computation, and then resume it. Error diagnostics are very specific -
much more so than in some other systems, which struggle to tell you that
an error has occurred somewhere before the last semicolon.
The features in Fermat reflect my own interests. It began as a kind
of arbitrary precision APL. Surprised that it was much faster in manipulating
matrices than the computer algebra system that I had access to at the time
(on a VAX), I extended it to help in my research in low dimensional algebraic
topology. Polynomial variables were added for the same reason. Research
in computational group theory and algebraic number theory suggested several
new features and changes in old ones (see Appendix 3, Example 1). I also
used it to teach numerical analysis to graduate students, where the complex
number and polynomial data types were extremely useful.
I envision the users of Fermat to be rather sophisticated in both mathematics
and programming. (Of course, you don't have to do any programming
to use Fermat as a matrix and polynomial calculator, or to produce stunning
graphics.) At many places in the design and implementation of Fermat I
had to balance the conflicting goals of flexibilty and safety. That is,
whether to allow the user certain freedoms or language features that might
perhaps be abused, or to circumscribe the user in the name of safety. Since
I regard the users as sophisticated, I have usually chosen freedom.
Fermat is by no means complete. Future extensions will include more
factoring, Grobner bases, and graphics of higher dimensional objects.
**** This is the FFermat manual, for the float versions. ****
Basic features - a simple example:
When the user invokes the system he sees the prompt character
> ; the interpreter is waiting for a command of some sort. The user
could enter: 8+23 < enter > , and the system responds immediately
with 31, and the prompt character again. The user could enter any arithmetic
expression following the usual syntax, such as (8-3)*(178-96)/2 <
enter >. ( < enter > means press the enter key.)
Fermat always responds with a number after every user command. If there
is no obvious number associated with a command, it responds with 0. For
example, the creation of an array or function yields a 0.
The result of a computation may be stored for later use. The syntax
of "set x equal to 8+23" is
> x := 8 + 23 [blanks may be inserted for clarity]
31
>
Fermat ignores blanks inside constants, as 222 333 444 = 222333444.
The name x may or may not have been used before. The user
can now compute expressions in x, like
> y : = (x-29)*x - 60
2
>
If the formula is going to be used frequently, the user will want to
give it a name, say F:
> Function F(t) = (t-29)*t - 60.
0 [0 is the computed result of a function definition]
>
The t is called a formal parameter or just parameter. Any number of parameters is allowed.
The user can now enter commands like
> y : = F(x)
2
>
Notice that F(31)
has been computed, stored in y, and the answer displayed.
> F(27+y+1)
-30
Here F(30) has been computed and displayed, but not
stored anywhere by the user. The latest computed result arising from a
terminal command is stored in the system variable, where it can
be accessed via the symbol f , until
a later result supplants it (f is a
built-in
function described in the later chapter on built-in functions).
When the Fermat interpreter is awaiting user commands, we say that it
is in the lowest level. When it is executing functions, it is in
a higher level.
Computed values and function definitions can be saved from one session
to the next. They are stored in an ordinary ASCII file that can be edited
with an ordinary editor [i.e., MPW]. This file is called the save file
or, when being read, the input file.
Fermat uses the ordinary character set of most languages, plus several
special symbols on the MacIntosh keyboard, like p
(which is option-p) and · (which is option-8).
These symbols will be described in this manual as they are encountered.
They are summarized in Appendix 2.
In the MPW versions, the user must press the enter key, not the
return
key at the end of a line. In the stand-alone versions, either one works.
You may enter long commands that occupy more than one line by putting the
continuation
character ,, on the end of each line (except the last, of course).
Then use the mouse to highlight the entire command, and hit
enter.
(",," is a single character, namely shift-option-w). This continuation
character essentially means "ignore the invisible end-of-line character."
There are two occasions when it is not needed, inside multi-line comments
and inside multi-line character strings (also called literals).
See the later chapters on "Comments" and "Character Strings."
Don't erase the prompt character > - if you do accidentally,
you have to put it back. But you can change the prompt, to the empty string
if desired, with the command &M.
Let's continue the example of a Fermat session. Suppose the user has
a file of previously saved data called "stuff". He uses the imperative
read command
> &(R = `stuff')
to open the file for reading and bring the data into this session. Suppose
there is a 3 X 3 matrix [x]. To see it, he can use the short
form of array display,
> ![x
which results in
| > [x] := [[ 1, | 3, | 12, ,, |
| 2, | 7, | -5, ,, |
| 0, | 24, | -4 ]] |
To compute the determinant the command is
> D[x] [or Det[x]; the result
is:]
692
The user decides to compute the characteristic polynomial of [x].
First he adjoins a polynomial variable t:
> & ¶
Change of polynomial variable. [Fermat prompts for the name.]
Enter variable name:
> t
The user subtracts the variable t from every element in the main diagonal. This is done with:
> [y] : = [x] - [t]
[ [t] means a diagonal matrix, 3 X 3 since [x] is.]
Just to look at it, he displays [y]:
> ![y
| > [y] : = [[ -t+1, | 3, | 12, ,, |
| 2, | -t + 7, | -5, ,, |
| 0, | 24, | -t - 4 ]] |
Then invoking determinant computes the desired polynomial:
> w : = D[y]
-t3 + 4t2 - 89t + 692
(There is a built-in function Chpoly to quickly compute characteristic
polynomials.)
To check the Cayley-Hamilton Theorem, the user decides to evaluate this
polynomial at the matrix [x], using the built-in command ·
for polynomial evaluation:
> w ·[x]
| [[ 0, | 0, | 0, ,, |
| 0, | 0, | 0, ,, |
| 0, | 0, | 0 ]] |
The user decides to change the ground ring from the present Z[t] to Z[t]/ < t2+1 > . He gives the imperative form of the mod-out-by-polynomial command > &(P = t2 + 1, 1)
The extra "1" tells Fermat that t2+1 is irreducible, so an
integral domain results. Then the command
> 1/(t+1) yields:
(-t+1)/2
Everything will be saved to the file "stuff2", via the save command(s):
> &(S = `stuff2')
> &s
The user now exits:
> &q
bye
A well known Julia Set. 26 minutes on a 350 mhz G3 Mac.
[There are many images like this scattered throughout the manual. All were created with FFermat. FFermat runs only on Macs.]
Interpreter commands
Interpreter commands affect system-wide globals, perform I/O,
or display variables or functions. Most begin with the symbol & and
have the syntax & < symbol > . There are also the cancel
commands @ < name > , and the terminal I/O commands
! and ? . (Note: The brackets < and > are not entered by
the user - they designate that < symbol > and < name >
are
elements of syntax of the language. A
< symbol > is a key-stroke.
A < name > is a legal Fermat name, a sequence of up to ten digits
or letters of the alphabet, beginning with a letter.) Many of the &
commands can be used as toggle switches to flip a global back and
forth between two possibilities, or as imperative commands to force
the global into a certain position. There is a later paragraph on the imperative
form of switches.
A Word to the Wise
Don't leave a toggle switch on unless you wish to use it. Don't be in
complex mode unless you really need it. Don't adjoin a polynomial variable
unless you are going to use it. Every capability costs a little in performance.
To delete a list of items, say variables x, y, array [x], and functions F and G, enter @(x, y, [x], F, G). The list may be in any order.
Occasionally, after many computations, one develops a large number of now useless arrays, often many of the same name. (This can't happen with ordinary variables.) To delete all arrays named [x], enter @[*x]. To delete all arrays (a powerful command!) enter @[**].
Similarly, during a session of active experimentation and creation of functions, one often develops many versions of the same function. To erase all but the latest version, the delete command has a purge option, indicated syntactically by angle brackets, < and > (less than and greater than). To purge the function G, enter @ < G > . Mnemonically, you are erasing a "vector full" of G's. This can also be done with arrays, and can be done as part of a list of deletions.
@ can also be used to break out of certain modes, if you change your mind. See under &r, &s, and ?.
The & < symbol > commands &a: Toggle switch to change array initial indexing value. By default it is 1, which means the first entry in array [x] is x[1], uniformly for all arrays. For two-dimensional arrays, the first element is x[1,1]. Entering this command changes the constant to 0, so the first element of array [x] is x[0] or x[0,0]. Note that the creation of arrays is unaffected - indeed the arrays themselves are unaffected, it is only the accessing of arrays that changes. See the later chapter on "Variables and Arrays" for more information. &c: Toggle switch into or out of complex mode, for complex arithmetic. More on this later under "Complex Arithmetic."
&C: Toggle switch into or out of color mode. On by default.
&d: Change the display constant. The display constant is the number of significant digits displayed (up to 16). This can also be affected by the options after a display "!" command (see below). If you enter &d, you will be prompted to enter the new display constant. Alternatively, use the imperative form described below.
&D: Set the determinant cutoff. There are two basic ways to compute the determinant: expansion by minors and Gaussian elimination. As everyone knows, the first is an O(n!) method and the second is an O(n3) method, so is "of course" superior. But 5! = 120 and 53 = 125. Furthermore, it is far easier to multiply polynomials than to divide them. Fermat runs a hybrid of these two, and the determinant cutoff is an integer that controls the switchoff point. Setting &D = -1 lets Fermat decide where to switch off. Setting &D = n means begin with Gaussian elimination and do the last n rows by minors. Therefore, &D = 0 means Gaussian elimination all the way. Type &D, and you will be prompted for the value. The imperative form &(D = ...) can be placed inside a function. The default cutoff is not always the best if there are lots of complex quolynomials in the matrix.
&e: Toggle switch to change error handling in higher command levels. When turned on, Fermat will attempt to recover from certain errors in a certain situation. See the later chapter "Popping, Pushing, Debugging, and Panic Stops". Entering &e again turns this feature off.
&E: Toggle switch to eliminate the extra blank line that Fermat puts on the screen between lines of input and output. Mostly of use when graphing, when the worksheet is small.
&f, &F: List all current functions. Only the names and parameter lists of each will be displayed. To list the complete body of the functions as well, enter &F* or &f*. To list the body of only the function named G, type &F,G , &f,G ,&F < G , or &f < G. These all have the same effect. The choices exist so that the user doesn't have to release the shift key when entering the command. ( < and comma are on the same key.) To facilitate the editing of functions, Fermat will display the function with the continuation character ,, (shift-option-w) at the end of each line. You can easily change part of the function on the screen, cancel the function with @, and then enter the amended version from the screen. Alternatively, you don't have to cancel the old version - the new version is stacked on top of the old. [You can also purge the old version(s), keeping only the most recent, as explained in the chapter "Functions."] Note well: this character ,, should not appear in an input file and is not part of the function definition.
&g: Report the status of the system-wide "globals". If you can't remember that you are in complex mode or not, what file you are saving to, what the array initial index is, etc., enter this command. Several globals, such as namelength (&n), error-push (&e), and random divisor (&?`), are not mentioned unless they have been changed from the default.
&h: Report the heap size - the number of bytes of memory in MPW's heap that have not yet been allocated. Reasonably, one hopes the heap size to be at least 200,000.
&H: Toggle switch to control how colors are presented on shaded polygons. If off (the default) colored patterns are shown. If on, solid colors of varying shade (computed by Fermat) are shown. See also &p.
&m: Change the command interrupt level, the ability to interrupt a computation by pressing the command key. See below under "Popping, Pushing, Debugging, and Panic Stops".
&M: Change the prompt. If you don't like > for a prompt, change it to something else. &M will ask you for the new prompt. If you wish no visible prompt, change it to either the empty string, by hitting < enter > immediately, or change it to the end-of-line character (ASCII 13) by typing one blank and then hitting < enter > . These two will seem equivalent until the interpreter enters a higher command mode (see the chapter on "Popping, Pushing, Debugging, and Panic Stops".)
&n: Change the maximum possible length for names. See the chapter on "Names".
&N: Toggle switch to cut off "noise," suppress the displaying of all results (until &N is entered again). Displaying of individual results can be suppressed by typing colon before < enter > . For example, x : = 1000!: will correctly assign x, but nothing will be displayed on the terminal screen.
&o: Used to save specified variables to the output file. For example, !(&o, x, y) writes the value of x and y to the previously defined output file. See &s, below.
&p: First, see &H. &p ("paint factor") prompts for a real number between 0 and 1, to control the shade of a polygon. If a polygon has been given a color, &H is on, and &C is on, Fermat shades the polygon according to the angle it makes with the "Window." The darkest color possible is this factor times the original color. The default is 1/8.
&P: Polynomial mod-out. You will be prompted to enter the polynomial to mod out by. For example, you could enter t2 + 1 to simulate the field of complex numbers. &P can also be used to stop modding out, by entering -t after the prompt. See the chapter on "Polymods".
&q: Quit.
&r, &R: read from input file, loading previously saved functions and variables. An input file may be created using an ordinary editor [i.e., MPW] or the save command (see &s below). Each line of text will be treated as if the user had entered it during a Fermat session (except that end-of-line will be ignored). There must be a semicolon after each complete command, except after comments or multiline literals in functions. Fermat will not read beyond the first semicolon it sees on the line. Reading ceases when the &x command is met. The file should end with the &x command (see &x below).
The input file must not itself contain a read command. The first time you enter &r, if you do so from the keyboard, you will be prompted for the name of the file to be read from. If the first read command is made from a function, use the imperative form: &(R = < filename > ) . Later calls to &r will take up where the last one stopped - i.e., after the &x. (More information below under "Imperative form of switches".) It is possible to read from another, different file by entering &R. You will be prompted for the name of the file. The old file will be closed and data will be read from the new file. If you wish to put this command in a function, use the imperative form &(R = < filename > ). For the rules of filename formation, see the later chapter on "Names". To close the file without opening a new one, use the command &(R = ·). If you entered &R by mistake, just enter "@" as the name of the file, and the read will be aborted.
In MPW it is not an error to try to read from an empty file; nothing happens. In practice this occurs when the user mistypes the name of the file and discovers the problem much later after a lot of frustration. To prevent this annoying situation, Fermat issues a warning message when it tries to read from an empty file.
&s, &S: Specify and save to an output file. The current variables
and functions will be written to (the end of) an ordinary text file, which
can be edited and later read during another Fermat session. The previous
contents of the file are not erased.
&s and &S are not strict analogs or duals of &r and &R.
It is possible to do a "dumb save" that has no analog in reading.
The first time you enter &s, if you do so from the keyboard, you will be prompted for the name of the file to be saved to. Nothing will be written to the file you name. You have so far only specified its name. Upon the second &s command, all of the current functions and variables will be written to the file in human readable (and Fermat readable) form. Each succeeding &s likewise saves all the current functions and variables.
If you have been saving data to one file and wish to change to another, enter &S. Nothing will be written to the file you name. You have so far only specified its name. You will be prompted for the name of the new file. The old file will be closed (which means that if MPW or Fermat crashes, the saved material should survive1). Subsequent commands &s will save data to the new file. If you wish to put the &S command in a function, use the imperative form &(S = < filename > ). For the rules of filename formation, see the later chapter on "Names."
Each call of &s adds more stuff to the previous contents of the file.
To close the file without opening a new one, use the command &(S = ·).
Inserted in the saved file will be the commands &c, &a, &¶, etc., reflecting complex mode, initial array index, polynomial variables, etc. In this way, if you read the saved file in your next Fermat session, all these globals will be restored to their status at the moment you saved the file. (More information on this below under "Imperative form of switches".)
It is possible to do a "dumb save" in which raw data only is appended to the save file. Use the display command ! along with &o, as in !(&o, x, y,x+y), which writes the current values of x, y, and x+y to the file that has already been specified as the save file.
If you entered &S or &s by mistake, just enter "@" as the name of the file, and the command will be aborted.
Note: input and output cannot be the same file.
&t: Toggle switch to turn automatic timing on/off. When on, the length of time in seconds that each command or calculation takes will be displayed right after the result of that command or calculation is displayed. The measured time does not include the time it takes to display the result - which, for very large numbers, can be substantial. The accuracy is to within 1/60th of a second. Most useful when entered from the keyboard; if included in a function, a flood of data will likely result.
&T: Report the time since the host computer was turned on. The number returned is actually the number of "ticks" that have elapsed - a tick is 1/60 th of a second. This command can be used to measure how long part of a function takes to run, by setting a variable equal to &T, then performing the computation, then computing the difference between &T (a second call) and the variable.
&U: Toggle switch to enable ugly display. When on, Fermat will display long integers and polynomials in the style of other computer algebra systems (Maple). This facilitates communication between Fermat and the others.
&v: List all current variables. Their values as well as their names will be displayed unless &N (see above) has been set. In QFermat, follow with a colon to suppress the values.
&V: Turn on "verbose display." Will display the progress of the more involved and time-consuming procedures, such as Chpoly, Intnormal, or matrix inverse. A good thing to turn on; try it. Only in QFermat, where it is off by default.
&x: Stop reading from the input file. This command provides a break, allowing the input file to contain data in blocks, whose reading can be interrupted by computation. The next read command causes reading to begin right after the &x. There should be an &x at the end of the file.
&?`: Change the default divisor for the random number generator. More on this below under built-in functions, ?` = random number. ?` is shift-option-/.
&: list-of-monomials display. See Appendix Four.
&P: Push new command level. Begin new computation. More on this later in the chapter "Popping, Pushing, Debugging, and Panic Stops".
&p: Pop command level - return to lower level. Return to computation on that level.
&·: Panic stop - return to lowest command level after an interrupt.
&¶: Adjoin a new polynomial variable: allow manipulation of polynomials in an unevaluated variable. The user is prompted to enter the variable name. If entered within a function, use the imperative form &(t) to directly supply the name. (The "paragraph sign" ¶ is option-7 on the keyboard.) There can be up to 80 such variables. Names cannot be repeated. You can drop the polynomial variable t by entering -t after the prompt. See the later chapter on polynomials for another use of the symbol ¶, "polynomial read-in."
&±: toggle switch to change Laurent. When on, Laurent polynomials are allowed - those with negative exponents. When this switch is changed, all the current variables are scanned and their format changed if appropriate. For example, 1/t will become t-1 if Laurent is now true. See the later chapter on Laurent polynomials.
&|: toggle switch to affect the value returned by the built-in function mod. When on (the default) mod returns non-negative values, so that -2 | 3 = 1. When off, n | m simply returns the remainder produced by dividing n by m, so -2 | 3 = -2. The second choice executes very slightly faster; it takes an extra step to ensure that a non-negative is returned.
Imperative Form of Switches The switches &±, &a, &c, &e, &p, &n, &N, &|, &t and a few others can be used in an imperative sense rather than the toggle sense described above. &?`, &m, &M, &d, &D, &P, &R, and &S can also be used imperatively, i.e., in a non-interactive way. To turn complex mode on, whatever the current status, enter &(c=1). Similarly, to turn it off, enter &(c=0). 1 means on, 0 means off. Similarly for the others.
&(S = < filename > ) is equivalent to entering &S, and then responding with < filename > when prompted. This form does it all at once without the prompt. It can therefore be placed in a function. Similarly for &(R = < filename > ). For the rules of filename formation, see the later chapter on "Names." You may also use a character string stored in an array to name the file, as in &(S = [x]). This allows file names to be computed within functions.
&(?` = < expression > ) sets the random divisor.
When you save to a file using &s or &S, the appropriate commands will be inserted to record the status of complex mode, polynomials, etc. It is this imperative form of these commands that is placed in the saved file.
Terminal Input/Output, List Output, and Dumb Save !(variable, array): write to terminal; display on the CRT screen. If F(x) = x*x + !x, F returns x2 and displays x on the screen. This could also be written F(x) = x*x; !x. The first version will return the value x2 (since !x returns value 0), the second will return 0 (the last value computed - displaying returns value 0). Another possibility is F(x) = !x; Return(x*x).
You can display written messages, such as:
|
!x does not move up to a new line, so the next use of this command will continue displaying on the same line. Using ! by itself, !; will start a new line of output. Alternatively, to display and move up all at once, use the double exclamation: !!x.
!x will not necessarily display x immediately; rather, the data will be displayed when MPW decides that its buffer is full. To force the immediate display, use !!x.
List Output: You can use the syntax
|
Note that the spacing feature applies only to list output - !x:8 is illegal.
!!(x,y, ...) will display a list and then move up to a new line.
Dumb Save: You can append raw data (as opposed to the "prepared" data the &s command creates) to a save file by using &o as the first argument in a list output command, such as !!(&o, x, y, x+y). &o means the previously specified save file.
Note that !!(&o, x, y, ...) is fine but !!(&T, x, y, ...) is an error. If the first argument in a list display starts with & then it must be &o.
Displaying Arrays: Two formats are available for both ordinary and Sparse arrays, "short form" and "long form." The short form is the same for both, but the long form is not. [See the chapter "Variables and Arrays" below for more about Sparse arrays.]
First, for ordinary arrays: Since array entries could be long polynomials, the long format display ![a] conveniently shows one value of the array per line, in column-major order. If you expect a matrix to be composed of small integers, however, the short form ![a is nice: it displays the matrix in the familiar square pattern. Analogously with list output, the command ![x:8 will add spaces after every displayed number. However, ![x]:8 is illegal - indeed, it makes no sense.
The long form displays the name and coordinates as well as the value of each entry. For example, if you have x[2,2] with x[i,j] = i+j, ![x] produces:
|
|
|
|
The short form: Fermat displays the name of the matrix, as in:
| > [x] := [[ 1, | 3, | 12, ,, |
| 0, | 24, | -5 ]] |
(Note the commas, the double square brackets, and the continuation character.) You may change any of the components, change the name if you wish, and enter the entire matrix (by highlighting it with the mouse), such as:
| > [y] := [[ 2, | 3, | 13, ,, |
| 0, | 24, | -5 ]] |
where x ® y and 1 ® 2.
Sparse arrays:
The long form displays the array as a list of rows, only showing those entries that are not zero. For example, if [x] is Sparse, ![x] may produce:
| > [x] := [ [ 1, | [2, -1], [3, 19] ] | ,, |
| [2, | [1, 8], [2, -2] ] | ,, |
| [ 3, | [2, -1], [3, 9]] ] |
This [x] has in row 1 a -1 in column 2 and a 19 in column 3, in row 2 an 8 in column 1 and a -2 in column 2, etc. The dimensions of [x] are not apparent from the long form; it may have more than 3 rows, so long as all entries are zero there.
The short form is the same as for ordinary matrices. If the above [x] is 3 X 3, ![x produces:
| > [x] := [[ 0, | -1, | 19, ,, |
| 8, | -2, | 0, ,, |
| 0, | -1, | 9 ]] |
?(variable, array): get input from the terminal; interrogate user. If F(x) = ?y; x - y. F asks the user to enter a value for y, then computes x - y. Fermat halts when it encounters ?y and prompts the user. Nothing will happen until the user enters a value for y, which may be any legal expression.
When used with an array, such as ?[x] , Fermat displays the name and coordinates of each entry in the matrix in row-major order and waits for the user to enter a value for each successive coordinate. If you've made a mistake or for any reason want to break out of this array input mode, just enter '@', the cancel symbol.
Interrogation cannot be used with Sparse arrays.
Intersection of 3 cylinders, resembling a dradle.
Built-in functions
The built-in functions operate on numbers, arrays, or polynomials in
arithmetic or algebraic fashion, compute standard transcendental ("calculus")
functions, or perform graphics functions. Initially I intended each function
to have a special one-character symbol as its name, but this gets to be
a bit unwieldy. Most have such symbols, and many also have "ordinary" mnemonic
names, which always start with a capital letter. These names are reserved
and cannot be used by the programmer for any other purpose. With very few
exceptions, the name of the function precedes its argument, as in Ùx
= Ù(x) ( = ex).
Also with very few exceptions, the parentheses around the argument are
optional if the symbolic name is used but must be there if the ordinary
name is used, unless the argument is an array. Putting the parentheses
in when not necessary costs a very tiny additional amount of time to parse
them.
The graphics functions are all described in the later chapter on "Graphics."
By number I mean a member of the basic float arithmetic type, or a complex number. By scalar I allow in addition a polynomial or quolynomial.
Return This built-in allows a user's function (see the chapter on "Functions") to terminate and pass back a specified value. Syntax of use is Return(x), where x is any expression. It is required in a function that uses it Real.
Basic Arithmetic +, -, *, / : obvious, except they can be applied to arrays as well as scalars, in which case they often act component-wise. More on this later under "array expressions".
The Increment Command: A surprisingly large proportion of all commands written in programs are of the form x : = x + y, and a great many of these are of the form x : = x + 1. To obviate the need to look up x twice or read the 1, Fermat allows the programmer to write x :+ to increment x by 1, and x :+(...) to increment x by whatever is between the parentheses. Similarly, x :- to decrement x by 1, and x :-(...) to decrement by (...). If x is complex, the increment works fine but it returns the new value of the real part.
Implicit Multiplication: If two names are juxtaposed Fermat assumes that they are to be multiplied, as in ordinary mathematical notation. Thus, 5x means 5 times x. If you have variables x and y, x y will be interpreted as multiplication. Note: this assumed multiplication does not apply within array expressions or within functions with the Real option, where the multiplication sign * must always be used. See the later chapters on array expressions and on the "dangerous commands".
More Basic Arithmetic: \ = divide and truncate.
\ can also be used with arrays, as in [x] \3.
If x and y are polynomials, x \y acts as one would expect in the one variable case. For several variables, sometimes only a "pseudo-quotient" can be computed. See the later chapter on polynomials.
| = modulo. Naively, n|m is the remainder produced by the division of n by m. In FFermat, x | y means x - (x \y) * y.
| can also be used with arrays, as in [x] | 3.
If x and y are polynomials, x | y acts as one would expect in the one variable case or when y is monic, otherwise only a "pseudo-remainder" can be computed.
Numerical Functions $ = greatest integer function. Can be applied to polynomials, in which case it is done to the coefficients. Ùx = ex
The form x Ùn means xn. This will be computed as en ln x unless n is a small integer. If n is extremely large, you may get error messages referring to ln(x) or ex. In complex mode, because of the ambiguities of logarithm, exponents have to be real integers. _x, or Ln(x) = logarithm(x) `x, or Sin(x) = sine(x) "x, or Cos(x) = cosine(x) %x, or Tan(x) = tangent(x) bx, or Arctan(x) = arctangent(x) b = option-s. Arctanh, Sinh, and Cosh are implemented in the stand-alone version. p = the number p This is option-p on the keyboard. Ö, or Sqrt(x) = square root. It computes the (non-negative) square root to the specified accuracy.
In complex mode, the square root with non-negative imaginary part is returned. Ö = option-v on the keyboard. n! = n factorial. If n is not a real integer, the complex part is ignored and the real part is truncated first. C ¢ = binomial coefficient. If necessary, n and r are rounded to integers. C ¢ = option-shift-c. © complex conjugate. This is option-g on the keyboard. \bigcirc R = Re = real part of a complex number. This is option-r on the keyboard. Ï = Im = imaginary part of a complex number, the coefficient of !`. This is shift-option-f. Conjugate, real part, and imaginary part applied to polynomials act on the coefficients. !` = the complex number Ö[(-1)]. This is option-1 on the keyboard. ?` = random number. This function (which has no argument) returns the next in a sequence of pseudo-random numbers. The numbers are evenly distributed integers between 0 and 228 - 1. The precise sequence is always the same. This sameness can be useful in debugging, but is also inconvenient at times. Fermat therefore has a variation, indicated by ?`?`, which factors in the time of day to produce a "truly" random sequence. ?` is shift-option-/ on the keyboard. The array form [x] : = ?` or [x] : = ?`?` fills a previously created array with random numbers. Additionally, one can set a global variable divisor for random, which is 1 by default. The integers returned by random are divided by this. For example, if you set this constant equal to 228-1, random will return decimals evenly distributed between 0 and 1. To set this variable, use either the command &?`, which will prompt you for the value, or the imperative form &(?` = < expression > ). The divisor can be a complex number but cannot be a polynomial of degree greater than 0.
f last computed value. Fermat has a hidden "system variable" where all computed scalars are put automatically. This function accesses that variable. Typical use: x := f to move the value to the variable x. f is option-t. The array form [f] is discussed below under "Array Functions."
Rint(x) returns the nearest integer to the number x.
S = add up. As in mathematical notation. Here are some examples: S { i = 1,n } [ 1 / i ] = 1 + 1/2 + 1/3 + ...+ 1/n S { i = 1,n } { j = 1,m } ( a[i,j] ) = the sum of the elements in the matrix [a] (assuming that the global initial index variable has been set to 1). Note the set brackets and the square brackets surrounding the expression to be added up. In the second example, round parentheses were used instead of square brackets for clarity; Fermat allows the user to choose either. Any number of indexing assignments (inside the set brackets) listed one after the other, is allowed. The index variables i and j are actual variables in the Fermat session. S is option-w on the keyboard.
The syntax here resembles that of loops, which are described under "functions" below. P = multiply out. Standard mathematical notation. Just like S; see above. P is option-shift-p on the keyboard.
True. Always returns 1. False. Always returns 0.
Polynomial and Quolynomial Functions ¶ = Deg. Two distinct uses: degree of a polynomial (or quolynomial), or number of elements in an array. Here we discuss the former. There are two variants: (1) ¶x computes the highest exponent in x (any expression) of the highest precedence polynomial variable. (2) ¶(x, i) computes the highest exponent in x of the ith polynomial variable, where the highest level variable has the ordinal 1. For a quolynomial, it returns the degree of the numerator. ¶ is option-d on the keyboard. There is a difference between ¶x and ¶(x). The second form (where x can be any expression) is slower because it will spend time and space duplicating x before it computes the degree. In the first form, x must be a variable name.
o = Codeg; "codegree," just like ¶ except computes the lowest exponent. o is option-k on the keyboard. · = polynomial evaluation. x ·y replaces the highest precedence variable everywhere in x with y. x ·(u = y) allows for replacing other variables (here u) than the highest. x could be a quolynomial but y must be a polynomial only. See the later chapter on "Polynomials". Fermat also allows evaluation of polynomials at a square matrix. The syntax is x·[y]. The highest precedence polynomial variable in x is replaced with the matrix [y] and the resulting expression simplified. [y] can contain entries that are quolynomials. This command was used in the example in the opening chapter of this manual.
Numb = is the argument a number? If so (as opposed to a polynomial or quolynomial), the result is 1, else it's 0.
Numer = numerator of a quolynomial. Denom = denominator of a quolynomial.
Remquot = remainder and quotient. Remquot(x, d, q) returns the (pseudo) remainder (r, say) of dividing d into x and assigns the (pseudo)quotient to q. Much faster than using x/d because no g.c.d. algorithm is called. ck x = q d + r, where c is the leading coefficient of d and k = deg(x) - deg(d) + 1 (unless d is a number or c is invertible; then k = 0).
ç = coefficient in a polynomial (or quolynomial), Coef.
Suppose first that only one polynomial variable t has been adjoined. Then the syntax of use is either ç(x) or ç(x, n). (ç is option-c on the keyboard.) x can be any expression. n must be a number. If it is complex, only the real part is considered, which must be an integer, else an error results. In the first form, without n, the leading coefficient is computed. When used as an expression, ç(x, n) returns the coefficient of tn in the polynomial x. If x is a quolynomial, the denominator is ignored. If there are several polynomial variables, the exact coefficient desired is specified by listing the exponents of the variables in precedence order, highest first. For example, if t, u, and v have been adjoined in that order, and
|
then ç(x, 1, 2) = 7, ç(x, 1, 1, 0) = -8, ç(x, 0, 2, 1) = 5, ç(x, 2) = 3u - 3t - 3, ç(x, 0, 0) = -t3 - 3t - 1, and ç(x, 0, 0, 0) = -1.
$, or Lterm = leading term of a polynomial. $ is
option-3 on the keyboard. Follow it with a polynomial expression, whose
leading term will be computed.
Deriv(x, t, n) returns the nth derivative of x with respect to t. x is any (scalar) expression, t is a polynomial variable, and n 3 0.
Lcoef = leading numerical coefficient of a polynomial. Unlike ç, always returns a number.
¢ = derivative of a polynomial (or quolynomial) with respect to the highest precedence variable (the last attached). This is option-shift-] on the keyboard. Don't confuse it with apostrophe. Precede it with an expression to be differentiated, such as x¢. See also Deriv above.
GCD = greatest common divisor, as in GCD(x,y), x and y can be numbers or polynomials, but not quolynomials. If numbers, the result is always positive, except that GCD(0,0) = 0. GCD(0,x) = x if x is not 0. If they are both polynomials, the result always has positive leading coefficient.
Content = the content of a polynomial; i.e., the GCD of all its coefficients.
Numcon = numerical content, the GCD of all its numerical coefficients. This function just returns 1 in FFermat; it's designed for QFermat.
Y - = Var. Followed by an expression that evaluates to a positive integer, as in Var(i) or Y - i, returns the ith polynomial variable, counting the highest (last created) as 1.
Height = the difference between the levels (ordinals) of the polynomial variables in an expression. For example, if three variables have been attached, say t, u, and v in that order, and x = v+t, then Height(x) = 3. If y = u+t, then Height(y) = 2. Height(t) = 1 = Height(u).
Level = the ordinal position of the highest precedence polynomial variable in an expression. For example, if three variables have been attached, say t, u, and v in that order, and x = v+t, then Level(x) = 1. If y = u+t, then Level(y) = 2. Level(t+2) = 3. Level(u) = 2.
Raise = Two forms: Raise(x) and Raise(x, i). In the first, replace each polynomial variable with the variable one level higher. In the situation of Level, above, Raise(y) = v+u. Raise(t+2) = u+2. Raise(x) is an error. The second form allows the user to provide an expression i that evaluates to a positive integer, and raises x that many levels, if possible.
Lower = The inverse of Raise. See above.
Rcoef = change the coefficient of a term within an existing variable. For example, if only polynomial variable t exists, Rcoef(x, n) : = y will change the coefficient of tn in x to the expression y. y must be a number.
With several polynomial variables, the idea is similar to ç, Coef. In Rcoef (x, n1, n2, ...) = y, y must be suitable to be such a coefficient, else an error occurs. For example, if t, u, and v have been adjoined in that order, and
|
Array Functions Most of the ordinary arithmetic built-in functions
can be applied to arrays, as in [y] : = $ [x]. See Appendix
2, last column.
Sparse arrays are implemented in Fermat. This is an alternate mode of storing the data that constitute the array. In an "ordinary" n ?m array, nm adjacent spots in memory are allocated to hold the entries in the array. If an array consists of mostly 0's, this is wasteful of space. In a Sparse implementation, the non-zero entries only are stored via list structures. There is no limitation on the size of sparse arrays. The fact that an array has the Sparse or the "ordinary" storage structure is often transparent to the user; however, some of the functions listed below do not work on Sparse arrays. More on Sparse arrays later, under "Variables and Arrays," "Expressions and Assignment," and "Array Expressions."
D, or Det, is used in several ways to compute a scalar from an array argument. (D = option-j.) If used by itself on a square matrix, D is determinant. D#([x] = a) returns the number of entries in [x] that equal a.
Similarly D#([x] > a) and D#([x] < a) compute the number of entries of [x] larger or smaller than a. If a or any of the entries of [x] are complex, only the real parts are considered. If any entry is a polynomial, an error results. DÙ[x] returns the index of the largest element of [x] (in column major order if [x] is a matrix). D_[x] returns the index of the smallest element of [x]. D_ + [x] returns the index of the smallest nonzero element of [x], or -1 if there is no such element. (Typical use: D_ + |[x]| . [x] here can be replaced by any array expression.)
A few more comments about determinant. Fermat allows a wide range of data types for the entries of a matrix - integers, rationals, floats, modular numbers, complex numbers, polynomials, quolynomials, etc. No single method is most efficient for all cases. FFermat uses two basic methods: expansion by minors and Gaussian elimination. Gaussian elimination is applicable in all situations - all one needs is the ability to invert any nonzero element in a matrix. If the matrix is small enough, expansion by minors is faster (see the discussion for the interpreter command &D.) Gaussian elimination can be nontrivial and even problematical in polynomial rings, and in polynomial rings modulo a polynomial. Fermat has heuristics to guide its choice of method.
Chpoly([x]) computes the characteristic polynomial of the square matrix [x] in terms of the polynomial variable of highest precedence, using LaGrange interpolation when the matrix consists of all numbers. Chpoly can lead to loss of accuracy via roundoff errors.
Sumup = add up the elements of an array.
Trace = trace of a matrix.
Altmult. Multiply two matrices using the algorithm of Knuth volume II, p. 481. A big time saver when multiplication in the ring is much slower than addition. Especially good for Polymods (see that chapter). Syntax is Altmult([x],[y]).
Altpower. Use Altmult to take a matrix [x] to the power n. Syntax is Altpower ([x],n).
Adjoint[x] is the adjoint of matrix [x].
Root1(n, m) returns the canonical nth root of 1 raised to the mth power. Must be in complex mode.
TM = transpose matrix, as in [y] : = TM[x]. This symbol is option-2 on the keyboard. ¨ refers to the diagonal of a matrix, as in ¨[y]: = [x]. [x] is considered a linear array. The diagonal of [y] becomes the entries of [x]. If the name [y] does not yet exist, a new square matrix will be created with off-diagonal entries 0. If square matrix [y] of the right size (i.e., rows equal to the number of entries of [x]) does exist then the off-diagonal elements are not changed.
Dually, ¨ can be used on the right side of an assignment, as in [y] : = ¨[x], which sets [y] equal to a linear array consisting of the diagonal elements of [x]. [x] does not have to be square.
There is a small subtlety in nonsquare arrays. ¨ expects the number of diagonal entries to equal the rows of the matrix. For example, if [e] is a 4 X 3 matrix, ¨[e] : = [(9,9,9,8)] is ok - but only the three 9's will go into [e].
¨ is option-shift-v on the keyboard.
To create a diagonal matrix with all entries equal to a constant, say 1, you can use the easier form [x] : = [1], if [x] already exists as a square matrix.
c / [x] = Cols[x] = number of columns of array [x]. c / is option-4 on the keyboard.
¶ = Deg = degree of a polynomial (or quolynomial), or number of elements in an array. In the first case, follow it with any expression. If that expression is a number, then of course the result is 0. For a quolynomial, it returns the difference in degrees of the numerator and denominator.
When used with arrays the next character must be the square bracket, [. ¶[x] = total size of array [x] (rows X columns).
[f] = last computed array. Analogous with the preceding use of f, Fermat has a hidden system array. If you type the command [x] + [y], arrays [x] and [y] will be added and, since you didn't provide an assignment of the result, the result will go into the system array. You can later access it by typing, for example, [z]: = [z] + [f]. Subarrays cannot be used with [f].
Note that the command [z] + 2 will display 2 plus every element in [z], but typing 2 + [z] will produce an error. In reading from left to right, Fermat encounters the 2 first and cannot later switch to array parsing.
f = concatenate arrays; glue two arrays together to form a larger one, as in [z] : = [x] f [y]. Neither array can be Sparse. See the chapter on array expressions.
Iszero = is the argument (an array) entirely 0? If so, return 1, else return 0. Syntax: Iszero[x].
Switchrow = Interchange two rows in an array. Syntax: Switchrow([x], n, m). The matrix itself will be changed.
Switchcol = Interchange two columns in an array. Syntax: Switchcol([x], n, m). The matrix itself will be changed.
Normalize = Normalize a matrix. The matrix must not be Sparse. Convert it to a diagonal matrix, i.e., all off-diagonal entries will be 0. The matrix itself will be changed. If requested, Fermat will also return the change of basis matrices that it used in normalizing. Possible invocations include Normalize([x]) and Normalize([x], [a], [b], [c], [d]). In the second case, matrices [a], [b], [c], and [d] will be returned that satisfy [a]*[x¢]*[b] = [x], where [x¢] is the original [x], and where [c] = [a]-1 and [d] = [b]-1. The value returned by Normalize is the rank of [x].
The inverse change of basis matrices [c] and [d] are provided because it is far faster to compute the inverses of [a] and [b] "along the way" then it is to use the built-in matrix inversion command after Normalize finishes. However, the computation of each of these matrices adds to the total execution time, and your application may not need them, or perhaps may need only [b] and [d]. Therefore, Fermat allows you to omit any one(s) you wish. For example, Normalize([x], ,[b], , [d]) and Normalize([x], [a], , [c]) are possible. Every comma promises that an argument will eventually follow. Therefore, Normalize([x], [a], [b],[c], ) is illegal.
Colred = Column reduce a matrix. The matrix must not be Sparse. By column manipulations, the argument is converted to a lower triangular matrix. The matrix itself will be changed. If requested, Fermat will also return the change of basis (or conversion) matrices that it used in normalizing. Possible invocations include Colred([x]) and Colred([x], [a], [b], [c], [d]). In the second case, matrices [a], [b], [c], and [d] will be returned that satisfy [a]*[x¢]*[b] = [x], where [x¢] is the original [x], and where [c] = [a]-1 and [d] = [b]-1. The value returned by Colred is the rank of [x].
The change of basis matrices [c] and [d] are provided because it is far faster to compute the inverses of [a] and [b] "along the way" then it is to use the built-in matrix inversion command after Colred finishes. However, the computation of each of these matrices adds to the total execution time, and your application may not need them, or perhaps may need only [b] and [d]. Therefore, Fermat allows you to omit any one(s) you wish. For example, Colred([x], ,[b], , [d]) and Colred([x], [a], , [c]) are possible. Every comma promises that an argument will eventually follow. Therefore, Colred([x], [a], [b],[c], ) is illegal.
An object colored and displayed with &H off; i.e., shading polygons
with patterns instead of solid colors.
An object colored with &H off; i.e., shading polygons with patterns instead of solid colors. (A "manta")
Names
The user creates names for scalar and array variables, for files, for
polynomial variables, and for functions. All but file names must be less
than or equal to 10 characters long and consist entirely of lower case
letters, upper case letters, the underline "_", and the 10 digits. Spaces
and tab marks are ignored. Fermat distinguishes between upper and lower
case letters.
The names of variables (scalar, polynomial, or array) must begin with
a lower case letter. When referring to an entire array, array names are
surrounded by the brackets "[" and "]".
The names of functions must begin with an upper case letter.
The maximum length of 10 may be lowered by using the change name length command, &n. Upon entering this command, the user is prompted to enter an integer between 1 and 10. The main reason for doing so is that in Fermat, if two names are juxtaposed with no arithmetic operator between, it is assumed that they are to be multiplied, as in mathematical writing. Thus, no matter what the maximum name length, one can write expressions like
|
|
Note that if name length is set too small, many built-in functions like
Numer
can't be recognized.
Changing the namelength is really a somewhat archaic holdover from early
versions of Fermat.
Names of files can be just like variable names, or, alternatively, can conform to MPW's file name rules. In the latter case in Fermat, the name must be surrounded by (single) quotes. For example, if you enter the save command &(S = stuff), Fermat will save your stuff to a file called `stuff' in the default directory. To save to a file in another directory, enter the entire name surrounded by quotes, such as `HD:MPW:algebra:stuff'. Any file name that is an illegal Fermat variable name (for example, one containing a colon or the underline _) must be surrounded by quotes. If you wish, you can simply adopt the policy of always putting file names in quotes.
Blanks are not allowed in a name. Nor, for certain technical reasons, can any file name contain W, option-z.
Variables and Arrays
The name of a variable (see preceding chapter) must begin with a lower case letter. A variable is created by assigning a value to the name.
Fermat allows one or two dimensional arrays. The name must begin with a lower case letter. To create an array called b of 10 elements, use Array b[10]; to create a 3 X 3 matrix c use Array c[3,3]. Several arrays can be declared on one line, such as Array x[3,3], y[4,4], z[60,60] Sparse, a[7,7]. When an array is created, its entries are undefined. There can be both a scalar variable and an array of the same name.
The maximum array size is 40000. The maximum total size of all arrays is 204800. (This does not apply to Sparse arrays. Each sparse array must satisfy rows X cols < 231.)
Sparse arrays are implemented in Fermat. This is an alternate mode of storing the data that constitute the array. In an "ordinary" n ?m array, nm adjacent spots in memory are allocated to hold the entries in the array. If an array consists of mostly 0's, this is wasteful of space. Furthermore, multiplying two such matrices [x]* [y] is wasteful of time, since almost all individual multiplications x[i,k] * y[k,j] result in 0. In a Sparse implementation, only the non-zero entries are stored in a linked list structure, in which each node contains a pointer to the actual data item and the row and column of the item.
A Sparse array is created by following the creation command with the keyword "Sparse," as in Array x[5,5] Sparse. Sparse arrays do not contribute to the total array size limit of 204800. An array [x] already created can be converted to Sparse format with the command Sparse [x]. The fact that an array has the Sparse or the "ordinary" storage structure is often transparent to the user.
Arrays are accessed, or indexed, with the syntax x[i], where i can be any expression evaluating to a real integer. One has a choice of how to label the first element of an array - usually one thinks of the first element as x[1], but sometimes x[0] is more convenient. The default in Fermat is x[1]. This can be changed by entering the command &a, which switches the initial array index to 0. Entering &a again switches back to 1. Note that this is not a property of any particular array, but of how all arrays are indexed. Creation of arrays is not affected - it's still x[8] for an array with 8 elements. For two dimensional arrays, the first element is x[0,0].
Dynamic Allocation of Arrays Arrays that are no longer needed can be freed to provide space. This is done with the cancel command, @[x], or, to free several, @([x],[y],[z]). As arrays are created and destroyed, space is allocated and freed within a linear list of available space in the order that the commands are received. If you have several arrays and free the first one created, the others are moved up to occupy the empty slots. This moving is not especially time consuming since only pointers need to be changed. Nonetheless, if you are going to do a lot of creating and cancelling of arrays, it is best to follow the last in, first out policy, thereby treating the linear list as a stack.
Expressions and Assignment
An expression is any algebraic formula following the usual rules of precedence and involving scalar variables, constants, function calls, or interpreter commands. Example:
|
The syntax for assignment is
|
There is a shortcut increment command. Fermat allows the programmer
to write x :+ to increment x by 1, and x :+ (...) to increment x by whatever
is between the parentheses. Similarly, x :- to decrement x by 1, and x
:-(...) to decrement by (...).
When a variable is assigned a value, it is put on an "environment stack"
of all active names. Any previous value is lost. The cancel or rubout command
@ removes the name from the stack and destroys the value. The command &v
displays the active variables.
Similarly, arrays are put on a stack of active array names. With arrays,
however, every act of creation, such as Array a[3,3] puts a new array called
a of dimension 3 X 3 on the stack. Previous arrays called a are not lost
- they are just pushed down. Any reference to array [a] is to the
top most one. To reaccess the lower one(s), cancel the top one or rename
it using the "rename command", : ¢
[c]¢ = ¢[d]¢.
This command is described more under "arrays as parameters" below.
Mixing arrays and scalars in an expressiosn is sometimes legal. This is clearly not:
|
You can mix arrays and scalars in an array assignment statement,
|
To set [c] equal to [b], the command is [c] : =
[b]. In this case, [c] does not have to have been explicitly
created before; it will get the same dimensions as [b]. If an array
[c] of the same dimensions as [b] already exists and is on
the top of the stack of [c] names, its old entries will be destroyed
and new ones created equal to those of [b]. Otherwise, a new version
of [c] will be created.
Constant and Diagonal Matrices:
[b] : = 2 will set every component of b to 2. [b] : = [1] will set [b] equal to the identity matrix, if [b] has been declared to be a square matrix. (If there is no [b] yet, it is an error. If there is a non-square [b], a new square [b] will be created having the number of rows of the other [b].) Any constant may be substituted for the 1, including p, complex numbers, and polynomials. However, a polynomial inside the brackets must be written using Fermat's conventions of parenthesation and precedence, such as [(5t2+2t+1)u2 + (3t-1)u + 7t2-3t+1], assuming that polynomial variables t and u have been created, in that order. For more information, see the later chapter on polynomials, about "polynomial read-in."
Diagonal matrices are Sparse arrays. [b] : = [1]
is an error if [b] has not been declared Sparse.
Here is a further example. Suppose [a] has been declared to be
4 X 4, but [b] has not been created. Then this will work:
[b] : = [a] + [1]. The interpreter will figure out that the
identity matrix [1] should be 4 X 4, because [a] is. But just saying
[b] : = [1] won't work - the interpreter has no way of knowing
how big [1] is supposed to be.
You can't assign an ordinary array to a sparse array. However, you can
do it the other way around.
Graphics objects can be created by assignment, but such statements are
very complicated and are meant to be done only by the ODump command.
See the Graphics chapter.
Rotated toruses; a group of tires.
Array Expressions
In Fermat one can write expressions involving matrices and scalars that follow familiar mathematical syntax, such as
|
Fermat returns the value 0 after an array assignment. It does not automatically
display the new array.
In array expressions the multiplication sign * must be used to effect
multiplication.
In ordinary expressions, if two variables or factors are juxtaposed, multiplication
is assumed. That won't work here. The reason is ambiguities like s[x].
Is this s times [x], or is it entry x in array [s]?
Ordinary and Sparse matrices can be mixed in expressions. If
any term in a matrix expression is an ordinary matrix or is a scalar, the
result will be ordinary (unless it would too big - then it's an error);
otherwise it will be Sparse. This provides an inelegant mechanism
for converting a Sparse matrix, say [a], to an ordinary one:
[x] : = [a] + 0.
Fermat allows subarray expressions. That is, part of an array [c] can be assigned part of an array [a]. For example,
|
Subarrays can be used in either the source or target of an assignment
statement (right side or left side).
In subarray assignments, a vector declared to be one-dimensional (like
a[5]) is treated as a column vector, i.e., a[5,1].
Both ![x[...]] and ?[x[...]] work with subarrays.
¨
and D work with subarrays.
Subarray cannot be used with Sparse matrices.
Getting Data Into Arrays
Besides subarray expressions and the interrogation command ?[x], another way to easily get data into an array is with list input, similar to list output, described above. For example,
|
Yet another way to get data into a matrix, if it is not Sparse, is by setting up the data on the screen in rectangular array in the same format as Fermat's short form display of matrices (which has been discussed previously under the `display' built-in function). For example,
| > [y] := [[ 1, | 3, | 12, ,, |
| 0, | 24, | -5 ]] |
Note the commas, the double square brackets, and the continuation character. Any expression may be used to specify an entry, not just constants. Highlight both lines with the mouse and then press < enter > .
For a Sparse array, the analagous procedure is to use the long form, as in:
| > [x] := [ [ 1, [2, -1], [3, 19]]; ,, |
| [ 2, [1, 8], [2, -2]] ,, |
| [ 3, [2, -1], [3,9]] ] |
The entries do not have to be in increasing column order within the rows. Furthermore, repeated columns are allowed, in which case the sum of all the given terms ends up in that column.
In the MPW version, highlight all with the mouse and press < enter
> . In the stand-alone version, just put the cursor at the end and
hit return.
Yet another way to easily get data into an array is with "pseudo-loops". For example,
|
|
Arithmetic of Arrays
Multiplication of arrays [a]*[b] follows this hierarchy:
If [a] is n X m and [b] is m X p, an n X p is created, as
is usual in matrix multiplication. If [a] and [b] do not
match this way but they are of the same total size, then component-wise
multiplication is done. Otherwise it's an error.
Similarly for division. If [b] is square and [a] has the
same number of columns, [a] / [b] means [a]
times the inverse matrix of [b]. Otherwise it means componentwise
division, if the sizes are the same.
[a] | 3 takes each entry
of [a] modulo 3. If [a] and [b] have the same total
size, [a] | [b] returns each entry
of [a] modulo the corresponding entry of [b] (otherwise it's
an error). Similarly for [a] \ [b] and [a]
\ 3 .
2*[a], or [a]*2, multiplies every component of [a]
by 2. [a] + 3 adds 3 to every component of [a], and so forth.
Suppose you want a 4 X 4 square matrix [y] with 7's on the diagonal
and 1's everywhere else. Use [y] : = ¨[(6,
6, 6, 6)] + 1.
You can add or subtract arrays of the same total size. If they aren't
of the same declared dimensions, such as adding a 2 X 3 to a 3 X 2, the
result may not be what you thought (try it).
Array exponentiation is implemented. The syntax is just like scalars,
[x]Ùn. n must be a
real integer. [x] must be a square matrix. If n is negative, [x]
must be invertible.
Other Miscellaneous Array Operations
All arrays can be accessed via the syntax x[number], even if
they were declared to be 2 dimensional. This is occasionally useful. For
example, if you have an array x[4,4], x[7] means x[2,3]. (Assuming that
the array initial index is 1. See the built-in function &a.)
Matrices can be normalized with the built-in functions Normalize
and Intnormal. Their kernels can be computed with Colred
and Intcolred. See the chapter on built-in functions for other array
built-ins.
Arrays can be "adjoined" or "concatenated" with the operator f. (This
is option-f on the keyboard; its resemblance to an integral sign is appropriate
because the two have been "integrated.") The syntax is [z]
: = [x] f [y]. If two arrays have the same number
of columns, the result is as if one array were put "under" the other. If
not but they have the same number of rows, it is as if they were put next
to each other. Otherwise an error results. It is also legal to write [w]
: = [x] f 12, in which case 12 is appended to the end
of [x] and a one-dimensional [w] is the result. Similarly,
the scalar could be the first argument. This command is mostly of use for
manipulating strings (see the next chapter on character strings).
It's very convenient to write expressions like
|
There are syntactic restrictions on array expressions that do not apply
to scalar expressions. For example, array expressions may not contain unlimited
nesting of parentheses. An expression like x*(x+x*(x+x*(x+x*(x+y)))) may
very well produce an error if x and y are replaced by arrays. Such complex
syntax is not necessary. Instead, first set [d] = [x]+[y],
then multiply [x]*[d], etc.
With either syntax [y] : = 1/[x] or [y]
: = [x]Ù-1, the inverse
of [x] is computed via Gaussian elimination or the Leverrier-Faddeev
algorithm (See example 6, Appendix 3). The various versions of Fermat have
heuristics for choosing a method.
The exponential-cosine sombrero.
The Array of Arrays
Fermat does not allow general three-dimensional arrays, but there are
times when they are very convenient. For example, a group may be represented
as a set of matrices, and one would naturally wish to access the ith element
of the group as G[i], or something like that. To provide for this, Fermat
has a system wide array of arrays. Two thousand pointers are provided,
each of which can be set to "point to" or "be an alias for" an array that
has already been created in the usual manner. These pointers are accessed
by the name f (option-shift-7).
Suppose that x[3,3] and y[2,2] have been created. To associate the first
pointer with [x] use the command f[1]
: = [x]. Similarly f[2]
: = [y] associates the second with [y]. Then f[1][2,2]
is the same as x[2,2],
f[2][1,2]
is the same as y[1,2], etc. Note Carefully: x[2,2] is not
duplicated
to become f[1][2,2], rather
f[1][2,2]
is
x[2,2]. The assignment f[1][2,2]
: = 3 changes x[2,2].
Note that the array of arrays allows a more general data structure than
typical three-dimensional arrays, because the arrays being indexed are
arbitrary.
The pointer f[n] cannot
appear by itself on the right in any assignment statement. Of course, an
element of an array, such as f[n][k,m]
can appear there. In other words, you can't do [x] : = f[1].
When assigning to a pointer, the right side must be a simple array name,
no expressions and no subarrays.
Display and subarray work with array of arrays. Changing the array initial
index with &a affects f.
Interrogation from the terminal (the ? command) does not work with f.
Beware of dangling pointers! If f[1]
is associated with [x], and then [x] is cancelled, further
use of f[1] will probably
crash the system. Perhaps f
belongs in the chapter on "Dangerous Commands"!
Example two in Appendix 3 would have been cleaner using f.
Functions
All programming languages have the idea of "subroutines" that perform specific, often repeated tasks. In Fermat, these are called "functions."
Every function returns a value, the last expression it computes. (Recall that assignment statements within functions yield the result 0.) One can also use the Return built-in function to exit a function with a certain value, as in Return(x).
Unlimited recursion is allowed.
The syntax of a function definition is
|
The left hand side must look like < func name > (x,y,z, ...) or just < func name > . A < func name > is a name that starts with a capital letter. The right hand side is explained below under "The Main Body of the Function."
The name of a function must begin with a capital letter. The user can redefine a function F simply by typing a new definition. The function definitions are put on a stack, so any reference to F is to the topmost. @F destroys the topmost F. @ < F > destroys, or purges, all previous definitions of F. The commands &f, or &F display the current function definitions. To also see the body of the functions, enter &f*.
It is recommended that most functions be created in a file with a text editor before Fermat is invoked (save the file as "text only"). However, it is possible to edit your function definition from the terminal within a Fermat session, have it occupy several lines, and enter it all at once by selecting it with the mouse and hitting < enter > . For this reason, when Fermat displays a function it adds the continuation character to each line.
The order that the functions are defined (in the input file or the Fermat session) is completely irrelevant. Of course, if you invoke F and F invokes G, G must have been defined.
In F(x,y,z, ...), x, y, z, ... are the parameters. When the function is called, the interpreter evaluates the arguments and puts the parameters on the environment stack with those values. This mechanism of parameter - argument assignment is called call by value. When the function ends, those names (and their values) are popped off the stack. This is the same basic strategy used by APL, Lisp, and other languages, but is unlike Pascal or Fortran. If you have a variable called x whose value is 3, and then you call a function F with parameters x,y, while F is executing x means F's parameter. When F is finished, its parameter x is popped off, allowing your original x with value 3 to become visible again.
Fermat allows another kind of parameter - argument correspondence called call by value-result. Syntactically, a value-result parameter is indicated by putting the exponentiation sign ^ in front of the name in the parameter list of the function, as in F(x, Ùy). The argument passed to y must be a single variable name, not an expression. This provides a mechanism for allowing the changes made to y to survive the termination of F. Suppose you invoke F with F(3,z). Then as the parameters are matched with their arguments, y gets z's value. As the function executes, any change to y, such as y : = y+1, is done only to y. When the function ends, z gets y's value. (Therefore, you must not have cancelled z, although Fermat should be able to catch this mistake.) For more information, consult any text on the theory of programming languages.
In QFermat, the same syntax in some built-in functions implements call-by-reference. For example, time and compare Terms(x) and Terms(^x) for a large x.
Fermat allows graphical objects to be parameters. See the later chapter
on Graphics.
Local Variables A parameter not matched by an argument when the function is called becomes a local variable with initial value 0. For example, if you define F(x,y,z) and invoke F with F(a,b+c), z is initialized to 0 and cancelled when F ends. In the meantime, z is on the environment stack, suppressing or hiding any earlier z's. Any function that F calls has automatic access to z, as long as it (or some other function) doesn't create another z. If z is a value-result parameter, it must be matched with an argument at function invocation time, and so cannot become a local variable.
The Main Body of the Function
The right hand side of a function (abbreviated < r.h.s. > ) is either an expression, an assignment, an array expression, a for-loop, a while-loop, an if-statement, or a sequence of these separated with semicolons. The function definition ends with a period. The only other places were periods are allowed in a function definition are inside literals, inside comments, and in floating point constants, like 2.334. Such constants cannot start with a period, however. Use a leading 0 inside a function definition.
If-Statements The syntax of an if-statement is
|
where the first alternative is chosen if the condition is true, and the second (optional) alternative if it is false. Note reserved word "fi". This syntax originated in Algol68. If the second is absent and the condition is false, nothing happens - it's as if the statement were not there (except for possible side effects introduced by the evaluation of the condition).
There are two ways to form a < condition > . The first is simply to provide any expression. If the expression evaluates to 1, that's interpreted as true; anything else is false. You can set a variable, say, condition to 1, and write loops like
|
Secondly, (and more conventionally) define a simple condition as:
|
|
While-Loops The syntax of a while-loop is
|
For-Loops The syntax of a for-loop is
|
|
|
|
The first two expressions are the initial and final values for the index
variable. The optional third expression is the size of the increment. It
can be negative. At the beginning of the loop, these three expressions
are evaluated and truncated to integers, if necessary. The resulting integer
in all three cases must be less than 228 in absolute value.
After each pass, the index variable is incremented. If it exceeds (or is
less than, for a negative increment) the terminating value, the loop is
over. After the conclusion of the for-loop, the index variable has value
one more than the terminating value (or one less if the increment was negative).
It is possible for the " < r.h.s. > " to contain a statement that (apparently)
changes the index variable. This confusing practise should be avoided.
It may cause a crash.
The words if, then, else, for, from, by, fi, od, while, and do
are reserved, and cannot be used for any other purpose inside functions.
Early Termination of Loops Fermat has no "goto" statement; instead there is a "leave loop" command & > , a "cycle" command &], and an "exit function" command & > > . > > is the single character shift-option-\. "Leave loop" and "exit function" are self-explanatory. The cycle command causes Fermat to skip the remaining statements in the loop body and return to the condition or incrementation heading the loop. Note that the leave loop command & > is independent of function structure. That is, if F calls G and & > appears in G after any loop in G, then upon return to F, the first loop that Fermat enters (or had entered) will be immediately exited. In general, & > forces immediate termination of the next (or present) loop that Fermat enters (or has entered).
Examples
|
|
|
Arrays as parameters and arguments
|
Comments Comments within functions are implemented with the angle
brackets < < and > > . (These symbols are option-\ and shift-option-\.)
Any character string is allowed between them, but note that the first
> > terminates the comment. A comment may extend over more than one
line. When editing a function definition on the screen, a multiline comment
does not need to be terminated with the continuation character. Indeed,
if it is, something different will result - try it. It is possible to have
literals (quoted strings) inside comments and visa-versa, but this very
confusing practice should be avoided.
The best practice is to devote an entire line to a comment. However,
it is possible to use less - for example, a comment may be placed between
the end of an expression and the semicolon at the end of the line. But
- do not place a comment immediately before the period terminating a
function. In particular, one cannot have a function consist only of
comments. A comment is not a statement, so do not place a semicolon after
a comment.
One can also have comments of a different sort placed anywhere in an
input file (between function definitions, for example) for documentation.
Start such a line with a semicolon, such as:
; This is the factorial function. W(n) = if n = 0 then ...
Having seen a semicolon, the interpreter simply skips over such a line when reading the input file. Therefore there is no way within the Fermat session to read these comments.
More on Arrays as Parameters
Scalar parameters are evaluated at the time of call and their values
put on the environment stack. This is "call by value". But array parameters
are not passed that way - it would be too wasteful of both time and space.
Instead arrays are passed "by reference". This implies, first of all, that
the argument must be simply an array name, not an array expression, and
secondly, that any change made by the function to its parameter will survive
the termination of the function; i.e., it will really change the argument.
A subtle problem sometimes arises in passing arrays as parameters to
functions. If the function is changing an array, sometimes the size
of the output array is not known before the function call. For example,
a function Add could be written to add two polynomials represented as arrays
of coefficients. The size of the answer could be much smaller than that
of either of the input arguments because of terms adding to zero. The caller
wants to write Add([x],[y],[z]), meaning [z]
is to be set to the sum of [x] and [y]. The [z] existing
at that moment may be of inappropriate size, yet the calling function must
be written assuming the name of the sum is [z]. A solution is to
have the function Add create the array [z], and have the caller
know this. But then the problem is what to do about repeated calls to Add
(in a loop, say), especially when this [z] is later passed as one
of the inputs to a different invocation of Add. To provide an efficient
way out of this, Fermat has a change of array name feature. The
command Rname[c] : = ¢ [z]¢
will change the name of array [c] to [z]. The solution to
the above problem is to have the function Add create an array [c]
to store the sum. Then the caller of Add changes the name to [z].
You cannot cancel an array parameter. (There is one arcane exception
to this - if you somehow find yourself in the lowest command level with
array parameters still on the list of currently active names, you can and
should delete them. This situation is barely possible.) If you cancel within
a function the argument that an array parameter is matched with, the argument
will indeed be cancelled, but in the course of bookkeeping its symbol table,
Fermat will notice that the array parameter is now referring to nothing.
An error will be generated. If you had not set the command &e,
everything will be fine after the error. But if you had set &e,
then in the new level of the command language you must do a panic stop
&· immediately, else Fermat
may crash.
You cannot change the name of an array parameter. You can change
the name of some other array to that of an array parameter. This is a bizarre
thing to do, especially since at the end of the function's execution, Fermat
pops off all the array parameter names, so access to the genuine arrays's
name is lost.
As one can see from the above paragraphs, array name changing and array cancelling must be done carefully, especially inside functions. For these reasons, I do not recommend using the purge-array command inside a function.
The System Function
Just as there is a system variable (f) and a system array([f]), there is a system function. One frequently wishes to write a loop to compute something, for example,
|
The system function can consist of only one loop or if-statement.
No function can call §.
Fermat's Cut command applied to the earlier "manta" object.
Arithmetic Modes
This feature does not apply to FFermat.

A torus cut by a plane through its center, tangent to opposite " sides."
This viewing angle shows clearly that the cutting edge is two intersecting
circles. See
The College Mathematics Journal, 28: 381, November
1997. This image is shaded with colored patterns. Another image, below,
is shaded with pure color.
Complex Arithmetic
It is possible to extend arithmetic to ordered pairs added componentwise
and multiplied according to (a,b)*(c,d) = (ac-bd, ad+bc). This simulates
the field of complex numbers. Entering the command &c
will shift Fermat to this mode. The square root of -1, the pair (0,1),
is denoted by the key option-1, which resembles this: !`. Complex numbers
are displayed as a+b!`.
The built-in functions absolute value, sin(x), cos(x), tan(x),
ex, ln(x), and Öx will
provide complex results. Öx always returns
a number with imaginary part at least 0. ln(x) always returns a value with
imaginary part between -p and p.
In any arithmetic mode, complex conjugation = option-g, real
part = option-r, and imaginary part = shift-option-f are implemented.
So are greatest integer, modulo, and integer division, which are applied
to both real and imaginary parts.
Since the field of complex numbers is not orderable, the boolean connectives
< , > , £ , 3
examine only the real parts - there is no attempt at lexicographic order.
Of course, = and 1 do just what you think.
When leaving complex mode all imaginary parts of variables are discarded.
Therefore, as a safeguard against accidentally typing &c
and destroying a lot of work, you must enter &(c=0), the imperative
form, to leave complex mode.

A torus cut by a plane through its center, tangent to opposite " sides."
The cutting edge is two intersecting circles. See the preceding image.
This is an example of an object shaded with solid color, not colored patterns.
(On a color monitor it is red.)
Polynomials
Having chosen an arithmetic mode and, perhaps, complex mode, the user may change the ground field (or ring) by converting it into a polynomial ring. This is done by adjoining variables - as many as desired - that remain unevaluated. The names of these variables follow the usual rules of variable names in Fermat. To adjoin the variable "t", enter the adjoin polynomial command, & ¶. You will be prompted for the name of the variable. ¶ is option-7 on the keyboard. Variables added later have higher precedence than those earlier. Among other things, this means that if you adjoin t and then u, the polynomial u*t is displayed as (t)u, not (u)t.
To place this command in an input file, use the imperative form & ( t ). If you have attached this polynomial variable and later enter the save command, the command & ( t ) will be automatically included in the saved file. Repeat for other variables.
A variable name can be read from an array, just as the name of a file can be read from an array during read and save commands. This allows the easy creation of many names. For example, to create variables x1, x2, x3, ..., x9, set an array, say [x] equal to 'x ' (note the blank). Then write a loop for i = 1,9 do x[2] : = i+48; & ( [x] ). (The 48 converts integers to ASCII.)
The polynomial variable t can be dropped by entering & ¶ , followed by -t.
A name once chosen for a polynomial variable cannot be used for any other purpose. If a variable previously existed called "t", it will be inaccessible.
Exponents must be at least 0, unless the Laurent option has been chosen - see that chapter for more information. In any case, they must have absolute value less than 228. (Technically, an exponent can be up to 231 - 1, but Fermat will not let you directly assign an exponent larger than 228 - 1.)
Producing an exponent larger than 231 by multiplication or exponentiation will probably not result in an error message. But later results will be garbage!
Letting F denote the ground ring, suppose variables have been adjoined to create the polynomial ring F[t,u,v, ...]. Arithmetic is done in the obvious way, with the ordinary signs +, -, *, ^ . The div operator \ and the mod operator | work about as one would expect, at least if there is only one variable. Division, using / as in x/y, reduces the quotient by dividing out the greatest common divisor of the two polynomials. The result may be a polynomial, if y divides evenly into x, or, more likely, will be an element of the quotient field F(t,u,v, ...). Such elements are called quolynomials, and are written as fractions or quotients of polynomials. This topic is discussed in greater detail in the following chapter.
The operator "div", \ is essentially obvious when applied to single variable polynomials, but not to multivariable ones. For in that case, there is no division, there is only pseudo-division. (See any text on abstract algebra, or Knuth volume 2.) Fermat will yield the result of pseudo-division in this case. Similarly, "mod", |, returns the pseudo-remainder. Ideally, x \y = q and x | y = r mean ck x = q y + r, where c = the leading coefficient of y and k 3 0 is an integer. (Fermat does not adopt the convention that k = deg(x) - deg(y) + 1. Rather k is the smallest integer that works. But see Remquot below.) However, if y is at a lower level than x, the equation ckx = q y + r will probably fail because y is divided into each coefficient of x and a different k will probably arise in each case.
Polynomial Built-in Functions Many of the built-in functions cannot be given polynomials of positive degree (such as Sin, Ln, etc.). Those that can, like greatest integer or complex conjugation, work upon all the coefficients.
There is a special operator for polynomial evaluation, denoted ·, which is option-8 on the keyboard. Suppose first that only one polynomial variable, say t, exists. Then x ·y replaces every occurrence of t in x with y. For example, if x = t3 + 3t2 + 3t + 1, then x ·2 = 27. y can be any expression evaluating to a polynomial.
If several polynomial variables exist, then x ·y replaces the top-most polynomial variable in x with y, i.e., the latest created. To replace one of the other variables, say u, use the alternate form x ·(u = y). The name of any polynomial variable may be used instead of u, and any expression may be used for y.
A fast shortcut form of evaluation called total evaluation exists. To evaluate x at every variable, use the syntax x ·(v1, v2, ...), where all the vi are numbers. There must be a number corresponding to each polynomial variable, in the precedence order - highest precedence (last attached) listed first.
Fermat has a built-in function for polynomial coefficients. Syntax of use is ç(x, n), or ç(x, n1, n2, ...) if several polynomial variables have been adjoined. ç is option-c on the keyboard. x can be any expression. The ni must be numbers. If they are complex, only the real parts are considered. That real part must be an integer. In modular mode, modular arithmetic is ignored while the ni are evaluated. ç(x, n) can be used on the right- or left-hand side of an assignment. See the earlier chapter on built-in functions for more details.
There is a built-in function to compute the degree of a polynomial, using the symbol ¶. Syntax: ¶x or ¶(x, i). In the second form, the i names the ith polynomial variable. ¶x returns the largest exponent within x of the specified polynomial variable. If no specification is given, the highest precedence variable is assumed. In modular mode, it returns an actual integer, not reduced modulo the modulus. ¶ is option-d on the keyboard.
Similarly, there is a built-in function to compute the "codegree" of a polynomial, using the symbol o. It returns the smallest exponent of the specified polynomial variable. Syntax as with ¶. o is option-k on the keyboard.
There is a built-in function to compute the leading term of a polynomial, using the symbol $. $ is option-3 on the keyboard. Follow it with the expression whose leading term is to be computed. By definition, the leading term of a polynomial is coef * un where u is the variable of highest precedence, n is the highest degree, and coef is the coefficient of un.
Fermat has a built-in function to compute the derivative of a polynomial, using the symbol ¢. This is option-shift-] on the keyboard. Don't confuse it with apostrophe. Precede it with the expression whose derivative is to be computed, such as x¢. Differentiation is with respect to the variable of highest precedence.
There is a function Remquot to return the (pseudo)remainder (r, say) of dividing d into x and the (pseudo)quotient, q. ck x = q d + r, where c is the leading coefficient of d and k = deg(x) - deg(d)+ 1 (unless d is a number or c is invertible; then k = 0).
See the earlier chapter on built-in functions for descriptions of Level, Height, Raise, Lower, Lcoef, Content, and Numcon.
Polynomial Read-in
Fermat has a feature that facilitates the reading of large polynomial constants, the polynomial read-in, ¶. It is used by simply putting ¶ in front of a polynomial constant that is written out fully following Fermat's conventions.
Example:
|
where the two polynomial variables t and u have been attached in that
order (u has higher precedence, but there could be polynomial variables
between them).
The point of this feature is speed. If the ¶ is omitted in
the above example, Fermat will square t, multiply by 5, multiply 2 times
t, add to the previous result 5t2, then add 1, then square u,
then multiply by the previous result, etc. - in other words, it will evaluate
the expression normally. But since the expression is written in the canonical
order, y can be pieced together without any calls to addition or multiplication
at all. The ¶ signals Fermat to expect the canonical order and
simply "attach" the terms as they come to y. This provides a very large
saving of time, which is very noticeable even for polynomials of moderate
size.
The canonical order is that of decreasing exponents, no zero exponents, coefficients written first in each term, and nesting according to the precedence of the polynomial variables. There must be no spurious parentheses, no multiplication signs *, no division, no extra plus signs, like +7t-3, and no 0's (as stand-alone constants). Constants can use decimal or scientific notation, like 7.3231 or 7.321 e+3, and the symbol p( = 3.14159 ...) can appear. There must be no variables or any other built-in functions. Complex numbers must be written with real part first, as 2+5!`. Here is another example:
s : = ¶ v3 + (3u - 2t + 3 + 3!`)v2 + (3u2 + (-4t + 6 + 6!`)u +4/3t2 + (-4 - 4!`)t + 6!`)v + u3 + (-2t + 3 + 3!`)u2 + (4/3t2+ (-4 - 4!`)t + 6!`)u - 8/27t3 + (4/3 + 4/3!`)t2 - 4 !` t -2 + 2!` If you are unsure of some of the fine points of canonical form, create some polynomials and watch how Fermat displays them.
You can pretty much ignore polynomial read-in, especially if you use only one polynomial variable and have no polynomials of more than 15 or 20 terms. However, Fermat will often insert it in saved files and on the screen when it dumps a list of variables, so you must at least be aware of its existence.
Quolynomials
Letting F denote the ground ring, suppose variables have been adjoined
to create the polynomial ring F[t,u,v, ...]. Division, using / as in x/y,
reduces the quotient by dividing out the greatest common divisor of the
two polynomials. The result may be a polynomial, if y divides evenly into
x, or, more likely, will be an element of the quotient field F(t,u,v, ...).
Such elements are called quolynomials, and are written as fractions
or quotients of polynomials. Fermat automatically creates quolynomials
whenever it is necessary.
Basic arithmetic of quolynomials is essentially like high school algebra.
There are two mathematical complications. A problem arises when polynomials
p, q, r, ... are used to mod out and form the quotient ring F[t,u,v, ...]
/ < p, q, r, ... > of polymods. If the polynomials p,q, r, ...
are not all irreducible, then this ring is not a field, and dividing and
forming quolynomials is a sham. Fermat will not stop you from trying to
do this, and there is no guarantee of the results. In particular, there
will no longer be canonical forms, i.e., different looking expressions
may in fact be equal. If they are subtracted, the result may or may not
be an honest zero. The following chapter describes polymods.
Secondlyly, with floats inevitable rounding errors lead to imprecision.
Then division of polynomials x and y in which theoretically x/y is a polynomial
may instead produce a quolynomial quotient. If this is an intermediate
result in a computation, the errors will compound, and, perhaps even worse,
the fact that quolynomials have been introduced instead of polynomials
will greatly slow down the ensuing computations. For this reason, great
care must be used with division in these two modes.
Div and mod applied to quolynomials act only on the numerators.
There are built-in functions Numer and Denom that return the numerator and denominator of a quoly. The built-in function degree ¶ returns the difference between the degrees of the numerator and denominator. Leading coefficient ç ignores any denominator. Leading term $ produces an error if called on a non-polynomial.
Polymods
On top of the ground ring F = R R or C C may be attached any number of unevaluated variables t, u, ..., thereby creating the polynomial ring F[t, u,...] and its quotient field, the field of rational functions or quolynomials. Further, certain polynomials p, q, ... can be chosen to mod out with, creating the quotient ring F[t, u, ...] / < p, q, ... > , whose elements are called polymods, and, implicitly, quolymods, which are expressed as quotients of polynomials, superficially the same as ordinary quolynomials. If the polynomial p involves the variable t, we say that t has a relation imposed on it.
The basic command to mod out by a polynomial is &P. You will be prompted for the quotient polynomial, which must be monic, say tn + c1tn-1 + .... The imperative form of this command is &(P = tn + c1tn-1 + ...).
To later rescind modding out on t, enter &P again and then -t. To change the modding polynomial to another, you must first rescind the current one.
Note that polynomial evaluation (substitution) of a variable involved in a modding relation is not a homomorphism, and may not make sense. This is the user's responsibility.
This concept works much better in QFermat than in FFermat.

Newton's method in the complex plane, applied to z3-1
= 0, a well known fractal image. Color shows the root each point converges
to, and darkness indicates speed of convergence. 17 minutes on a 200 mhz
PowerMac.
Laurent Polynomials
In Fermat a Laurent polynomial is one with negative exponents. If you
wish to allow this, activate the toggle switch &±.
(± is option-shift-= on the keyboard.)
All of the variables you have created up to that point will be converted
to the new format. For example, 1/(t2+2t) will become t-1/(t+2).
No negative exponents are left in the denominator of a quolynomial and
all positives are factored out and moved to the numerator.
Unlike polymods, Laurent polynomials present no inherent mathematical
difficulty. Be aware that in Laurent mode, the function GCD treats variables
t as invertibles, and always presents answers with no negative exponents.
Laurent mode should be used if you expect a fair amount of results to
have denominators that a variable could be factored out of. If a matrix
has several entries such as 1/t, use this mode to compute its determinant
or inverse.
Laurent polynomials implement computations in the group ring Z(Z).
Character Strings
Fermat allows the assignment of characters to scalar variables and character
strings or literals to arrays. In the first case the syntax
is y : = ¢a ¢
to assign the character ¢a ¢
to the variable y. In the second case the syntax is [x] :
= ¢sample ¢.
(Use the single quote, or apostrophe.)
What actually happens is that y gets the ASCII code of ¢a
¢.
It is still therefore "really" a number. If one simply types y or
¢a ¢, the
ASCII code is displayed. To see the character `a' instead, use the display
command !(y: char). Note the reserved word char.
Strings are more useful than characters, for which one needs arrays.
If the user first creates an array [x] of size, say, six, and then
enters the command [x] : = ¢sample
¢,
each character is stored in the six slots of [x]. Entering ![x will
display the ASCII codes of the six letters. To see the letters, one needs
the
char attribute, the syntax of which is ![x: char.
(Note that this is array short form.) In the above example, this will produce
the original string sample. A spacing attribute can be added, as in
![x: char : n , which places each character in the left of
a field n units wide. Compare the display command form, !([x: char).
One can also use array long form ![x]: char, which
produces one character per line, or !([x]: char). Try
them both.
Array features discussed in earlier chapters enable one to work with
substrings, concatenate, find the length of a string, etc. For example,
to concatenate a string stored in [x] with a string stored in [y],
enter [z] : = [x]f [y]. (This could also
be executed more cumbersomely with subarrays.)
There are various fine points, which are here explained:
(1) If the array [x] has some undefined elements, they will appear
as
¢*
¢
upon execution of ![x: char. Note the space after the *.
(2) If [x] has not yet been created, [x] : = ¢sample
¢
creates a 1 X 6 array x[1,6], not an array x[6]. Predefining [x]
as one-dimensional x[6] solves the problem (see "array assignments" above).
(3) The command to display as a character string can be given to any
array. Values which do not correspond to legal ASCII codes will result
in the printing of a blank or of a little square box.
(4) These two are equivalent: [x] : = ¢abc¢
and [x] : = [( ¢a ¢,
¢b
¢,
¢c
¢)].
(5) When typing from the terminal, the continuation character ,, should not be in a literal. For comparison, enter these two statements: [x] : = ¢abc,, def ¢
and
[y] : = ¢abc def
¢.
(Both will have to be selected with the mouse and then entered.) Then
display each as a character string.
(6) Displaying a two-dimensional array x[n,m] as a character string
produces n rows of length m character strings.
(7) Constants like ¢e ¢
have a somewhat ambiguous existence in Fermat - are they scalars or arrays?
If you simply enter ¢e ¢,
the ASCII code for the letter e will be displayed, so it seems to be a
scalar. If you enter [x] : = ¢e¢
you will get a 1 X 1 array. If you enter [w] : = ¢e
¢
f [x]. where [x] is any array, the ¢e
¢ will be treated as a scalar, so [w]
will be a one dimensional array with first character e.

Graphics
Graphing in Fermat is simple: Set up a coordinate system by locating the origin on the terminal screen and specifying scaling factors. Use the commands Plot, Line, or Polygon (or others) to graph points, lines, or polygons, or to create geometric objects. These objects can be rotated, translated, or viewed from other angles. The graphics commands are built-in functions, just like Sin, Ln, or matrix multiply. They can be inserted anywhere in any Fermat function.
Where the graph appears depends on whether you are using the MPW version or the stand-alone version. With MPW, begin by erasing the screen (with the Erase command) before initializing the coordinate axes, and then make sure that nothing extraneous is written to the graphics area. The obvious thing that would mess up the graphics is the typing of commands on the MPW Worksheet. The cure is to simply resize and reposition the Worksheet to move it out of the way, say to the lower right corner, about 6 inches wide by 2 inches high. The entire screen is used for graphing. The menu bar and windows opened by other applications are not respected. Every pixel is usable by Fermat. If you click on the desktop and move to another application, you will destroy the graph, and it cannot be redrawn except by starting from scratch, or by using the Show command if you had created objects.
With the stand-alone version, Fermat will open a window for graphing. It does this right away as soon as it starts up. You should then move and resize the console window down and out of the way. All graphing will go to Fermat's graphing window, and the windows of other applications will not be touched. One can go to another application, return later, and redo the graph with the Redraw command. (However, the shaded colors may not be the same.)
A function to initialize the screen could be:
Setup = Erase; Setaxes(300,250,350); Setscale(60,60).
Erase, Setaxes, and Setscale are built-in functions (see the list of graphics built-ins below). The numerical arguments to the functions are in pixels. A pixel is a basic, tiny square or point on the screen. It takes about 640 of them to fill the width of the standard 14 inch display screen (if your screen is larger, Quickdraw and Fermat will automatically accomodate to it). Think of the screen as a large matrix of pixels. Every pixel has absolute coordinates that are simply its row and column positions counted from the upper left corner. Pixel (30,40) is the one in the 30th row from the top, 40th column in. Setaxes(300,250,350) draws the y-axis 300 pixels in from the left, the x-axis 250 pixels down, and makes the length of the y-axis 350 pixels, so that it stops about three quarters of the way down. The x-axis is drawn all the way across the screen. 350 was chosen so the y-axis won't impinge on the (now shrunken) Worksheet. Setscale(60,60) means that 1 unit on each axis is 60 pixels long.
Fermat is now ready to do two-dimensional plotting. (Three-dimensions can be similarly set up with the Sethree command; see list of built-ins below.) Suppose you wish to sketch the graph of the function y = 1/x. You could do this by plotting the point (x, 1/x) for a lot of x's, say n per unit from -4 to 4. The function
P(n,i) = for i = -4n, 4n do Plot(i/n, n/i) od.
would do this when invoked by, say, P(50), except that at i = 0 an error will be generated by n/i. This problem could easily be overcome with an if-statement. A better solution is to use the built-in command Funcplot, as in Funcplot(0, -4, 4, 1/n, F), assuming that F has been defined to be 1/x. Funcplot is explained in detail below. The point for now is that if the function being plotted, F, becomes undefined for some x in the plotting interval, Funcplot ignores that x and goes on to finish the plot.
The resulting graph is a large set of points. Often a better picture is obtained by using fewer points but drawing a line segment connecting successive points. This could be done with the function
P2(n,i) = for i = -4n, 4n do Line(i/n, n/i, (i+1)/n, n/(i+1) ) od.
that invokes the built-in command Line. This function suffers from the same error at i = 0 or i+1 = 0. It is cured again by invoking Funcplot, as in Funcplot(1, -4, 4, 1/n, F). Note the first argument. When it is 0, a point is plotted. When it is 1, a line segment is drawn between successive points.
Color
With a color monitor, you can create and see color images with Fermat. Use the switch &C to enable coloring (it is on by default), and &H and &p to control shading. See the commands SColor, OColor, and OIColor below.
Objects
Any collection of points, line segments, or polygons may be formalized as an object, so that it can be erased, reshown, translated, rotated, etc.
An object is created with the dual commands Mark and Setobj. After executing Mark, Fermat records all points, lines, and polygons created with the commands Plot, CPlot, Line, CLine, Funcplot, Cfuncplot, Rect, Polygon, ZSphere or Threeplot, until one of two things happens: the user enters Unmark, which cancels or ''unrecords" the points and lines, or the user enters Setobj, which creates an object to consist of the recorded points, lines, and polygons. The exact syntax is to follow Setobj with parentheses enclosing the name of the object, an ordinary Fermat variable name, such as Setobj(x). Names of objects are kept in their own symbol table, so this x will not conflict with any ordinary variable x. A later call to Setobj(x) will result in two objects called x, only the second of which is accessible (the first is "pushed down").
Objects can be deleted with the Delete command, hidden from view with Hide, brought into view with Show, cut up or copied (with Cut or Copy). These commands and many others are explained in more detail below.
The interpreter command &g lists the user's objects and their properties.
Here is an example of a function that creates an object. The domain { x0 £ x £x1, y0£y£y1 } is divided up into squares of length inc. For each side of each little square, the function F is evaluated at the endpoints, say (x1, y1) and (x2, y2), and a line is drawn in three-space connecting (x1, y1, F(x1, y1)) to (x2, y2, F(x2, y2)).
Grid(x0, x1, y0, y1, inc, i, j) =
i := x0;
while i < x1 do
j := y0;
while j < y1 do
Line(i,
j, F(i, j), i, j+inc, F(i, j+inc));
Line(i,
j, F(i, j), i+inc, j, F(i+inc, j));
j := j
+ inc
od;
Line(i, j, F(i, j), i+inc,
j, F(i+inc, j));
i := i + inc
od;
j := y0;
while j < y1 do
Line(i, j, F(i, j), i, j+inc,
F(i, j+inc));
j := j + inc
od.
Fermat allows arrays of objects. The command Objarray (see list of built-ins below) creates an array of specified size, but empty. Individual entry objects must be created like any other object. The primary use of this feature is in the Animate command (details below), which takes an object array and quickly displays and hides the successive entries, creating the illusion of motion. The following user-created function also displays and hides the entries in the object array z[120], but would appear jerky and strobe-like:
Cartoon(i) = for i from 1,120 do Show(z[i]); Hide(z[i]) od.
Here is an example of a movie-creating function. It assumes that an object y and an object array z already exist. ERotate (see list of built-ins below) erases an object, then rotates it the given angle about the given axis. After filling in z with Movie, invoke Animate(z) to see the movie.
Movie(n, axis, i) =
for i = 1, 2pn
do
Copy(y, z[i];
ERotate(y, 1/n,
axis)
od.
Hidden-Line Objects
There are two ways in Fermat to depict solid three-dimensional figures, with hidden-line objects and with shaded objects. In this section we discuss the former.
The object created by the function Grid in the example above is a one-dimensional stick figure object in three-space, like the frame of a geodesic dome. Such objects become hard to interpret visually when the number of lines, or their density, is large. One often desires instead a two-dimensional surface, whose "front" portions hide the "rear" portions. This can be simulated in Fermat with the Surface command, which creates a hidden-line object, a two-dimensional complex of small quadrilaterals, sort of like a covered geodesic dome. For example, Surface(obj, 0, -3, 3,-3, 3, 1/6, F) will divide the region on the xy-plane -3 £ x £ 3, -3 £ y £ 3 into small squares 1/6 of a unit on a side, compute F of each grid point, and graph all visible parts of the quadrilaterals with vertices (xi, yi,F(xi, yi)), i = 1,...,4, whose coordinates xi and yi define one of the grid squares. "Near" quadrilaterals can hide "far" quadrilaterals, producing a realistic image.
Hidden-line objects can be created only with the Surface command or by copying such objects that already exist. Otherwise, they are dealt with in the same manner as other objects, although for technical reasons animation sequences of hidden-line objects are often disappointing. Syntactic details of Surface are discussed below.
Shaded Objects
It is necessary to discuss first the concept of polygon. This should be intuitively clear: a polygon is a collection of line segments (in a plane or in space) that link up and bound a region. The polygon is the vertices, the lines, and the region. Its boundary can be traced in either of two directions. Specifying a direction provides an orientation for the polygon. The vertices defining the polygon do not necessarily lie all in one plane, although if they are wildly non-planar the pictures created by Fermat will suffer. Assuming for the sake of simplicity that the polygon is planar, it has a unit normal vector, a vector of length one perpendicular to the plane it lies in. In fact, it has two; choosing one is another way of specifying orientation.
A polygon is created in Fermat with the command Polygon. The argument to the command is either an array, as in Polygon[v], or an array followed by an expression that evaluates to ±1, as in Polygon([v], n). In either case, the array contains the coordinates of the vertices, in order, that trace out the boundary of the polygon. The array should have 2 (two-dimensional plotting) or 3 (three-dimensional) rows. Each column specifies a vertex. A line segment will be drawn from point i to point i+1 for each i, and from the last point to the first. Do not rewrite the first point as the last point. In the second form of the command, n should be ±1; n = -1 specifies that the polygon be given the orientation opposite to the order of the vertices.
If Mark had previously been set, the points will be grouped together and recorded by Fermat as a polygon. When Setobj is executed, the object will contain polygons, as well as any points or lines that may have been created by other commands. When Show displays the object, the line segments defining each polygon will be traced last.
But the real point of Polygon is not merely to trace out the boundary - a series of Line commands would suffice to do that. Polygons can be shaded.
A polygon is the region bounded by its edges, not just the edges. Imagine that this region is made of some material, and that there is a window allowing light into the 3-dimensional world of objects viewed on the terminal screen. Light strikes the material of each polygon and reflects to the observer's eye. The angle formed by the light source and the polygon's normal vector determines how bright the polygon will look. Furthermore, in an object consisting of many polygons, the "closer" ones to the observer can hide the "farther" ones, resulting in a realistic image of the 3-dimensional object.
To see this image of the object x, the user simply enters the
command Shade(x). x is now a shaded object.
Appendix 3 (example 5) contains an example of a function Torus that
creates a torus. The shaded object is shown here:

Once shaded, the object cannot be ''unshaded."
Unlike hidden-line objects, which are created only by the Surface command, any collection of polygons can become a shaded object. A hidden-line object can be shaded by using the last argument to Surface.
The following commands, all described in detail below, are especially relevant to shaded objects: Contrast, Carve, Cut, Reverse, Window.
Solid Objects
The function Torus mentioned above creates a polygonal approximation to a surface in 3-space. The Cut command is a useful tool to further visualize and understand such objects. If the torus object is cut, we see its inside surface through the resulting hole, just as if the torus were indeed a 2-dimensional surface. It may be desirable instead to think of the torus as solid; in that case the cut will result in a new plane surface, and we will not be able to see inside. Fermat implements this with the command Solid(x), or its variant Solid(x, n), where n evaluates to 1 or 0. n = 1 means to make the object solid, n = 0 to make it "hollow."
Here is an example of the same cut on a hollow torus and a solid torus:

The actual result of Solid(x) is to group together all the polygons of x as a polyhedron, a three-dimensional solid analogue of a polygon. More precisely, the polygons are the faces of the polyhedron. The polyhedron must be "closed" or "complete;" i.e., there must be no "gaps" in it, otherwise Cut will almost certainly generate error and warning messages and produce a very strange looking image. Objects can consist of more than one polyhedron; see the commands Union and Join below.
A small problem sometimes arises when cutting solid objects. To display shaded objects, Fermat sorts their polygons according to the distance of the polygon from the observer, i.e., the point where your eye seems to be in 3-space. But the polygon is not a point, so a representative point on the polygon must be selected for the distance computation, usually its centroid. If the centroid of polygon A is closer to the observer than the centroid of polygon B, A will be displayed afterB. If the images of A and B overlap, A will therefore look to be in front. This simple strategy succeeds 99% of the time; it sometimes fails with the solid objects created by Cut or Carve.
The problem can be largely removed with the Warp command. It is not the object that is warped, rather the viewing direction. Warp(x, 0,0,1) forces a re-sorting of the polygons of x in which the given vector (0,0,1) is added to the standard viewing direction to modify the fictitious observer location. In other words, Fermat "pretends" that the observer is higher up above the object, but only for the sort of the polygons.
Here is an actual example. A torus was cut through its center with a
horizontal plane, producing:

Note the five anamolies where a lower polygon shows through because
it was displayed later than it should have been. After Warp(x,0,0,1),
Show(x)
produces:

Object Parameters
Fermat allows functions to have parameters that are objects. Such a parameter must begin with the symbol Ø, which is option-shift-o on the keyboard, and continue with an ordinary Fermat variable name. The name including the Ø must be at most 10 characters long. For example,
F(Øx1, z, Øy) = Copy(a, Øx1); G(Øy,z+1); ...
is a function with two object parameters, Øx1 and Øy. Note that F invokes another function G that evidently has an object parameter, and passes it the parameter Øy as an argument.
Among other uses, object parameters allow the user to write alternate versions of built-in functions. For example,
ER(Øx, a, b) = Erase; Rotate(Øx, a, b).
is an alternative to ERotate that often works faster.
Object arrays can be passed to object parameters. For example,
G(Øx, i) = Show(Øx[i]).
can be invoked with G(a,n) if a is an object array and n evaluates to an integer in the right range.
Object parameters work by being aliases of the arguments passed to them. They are implemented as pointers to pointers, otherwise known as handles. For those unfamiliar with these concepts, the point is that it is possible to unwittingly break the association of parameter to argument. This is a variant of the famous dangling pointer problem and can lead to system crashes or other bizarre behavior.
For example, consider the following function F and the way it is invoked:
> F(Ø x, Ø y, Ø z) Union(a, Ø x, Ø z); Union(Ø z,Ø y,Ø z).
> F(b,a,c)
*** Inappropriate symbol: & # Å
* &# & !..
object is unsuitable for Union, Join, or Solid.
*** Error occurred at this point:
F(b,a,c);
*** The most recent function evaluations were:
F
*** Function was interrupted at:
Union(a, Ø x, Ø z);
Union( Ø z, Ø y, Ø z)
Union (see list of graphics functions below) combines two objects and gives the combination the name of the third, destroying any object that may have that name already. It also destroys the first two objects; they are consumed into the third. When F was invoked, Øx became an alias for b, Øy became an alias for a, and Øz became an alias for c. The first Union was fine. As a side effect, a was deleted. But the second Union involves Øy = a! There is no way for Fermat to know that Øy now points to garbage. All hell breaks loose.
Here is another example.
G(Øx) = ... Setobj(a); ...Shade(Øx); ...
produced an error message when invoked with G(a), because creating a new object named a broke the association of Øx with the original a.
To avoid these problems:
(1) Avoid refering to the argument of an object parameter (like a in the above example).
(2) Few graphics built-in functions destroy objects (like Union). Make sure you understand the ones that do.
(3) Be very careful when creating new objects within a function with object parameters.
Here is a final example. Fermat does not automatically display coordinate axes. This function will incorporate the axes into a shaded object, so that if the object is rotated, say, the rotated axes will follow along. The strange name ''yzxsew" was made up to minimize the possibility of conflicting with the real name of the object that will be passed to Ø x.
AddAxes(Ø x; i, j) =
Array vv[6];
Mark;
for j from 1 to 3 do
<< Make 30 small
line segments from 0 to 10 on axis j. >>
[vv] := 0;
for i = 0, 30 do
vv[j] := i/3;
vv[j+3] := (i+1)/3;
Polygon[vv]
od
od
@[vv];
Setobj(yzxsew);
Union(yzxsew, Ø x, Ø x);
Shade(Ø x).
List of Graphics Built-in Functions
Every point on the screen has two sets of coordinates, its absolute coordinates in pixels relative to the upper left corner, and its relative coordinates in units specified by the user relative to the axes created by the user. Unless otherwise noted, all the coordinates in the following list of functions are relative coordinates. In complex mode graphing, a point on the plane may be specified by giving only one complex number.
Note carefully the spelling of these functions, especially the capital letters. Most of the functions require one or several arguments surrounded by parentheses. When referring to an object array in its entirety, do not put brackets around the name.
Animate: Follow with ( < name > ) or ( < name > , < num > ). < name > is the name of an object array (see Objarray), whose entries will be alternately shown and erased in sequence. If < num > is present, it should be a small integer specifying a delay factor between the successive frames of an animation that would otherwise be too fast.
Carve: An extension of Cut, below. Follow with ( < name > , < knife > , [w]). < name > is the name of the object (not an object array) to be carved. < knife > is the name of another object (not an object array) whose polygons will "carve" the first. [w] specifies the coordinates of a point on the side of the planes of < knife > to be kept. Carve invokes Cut, using the planes defined by the < knife > 's polygons one-by-one to cut the given object. If < knife > is convex, the result is the intersection of the two objects. < name > should be solid. Both objects must be 3-dimensional and shaded. If < knife > is not convex, the object being carved might be entirely cut away. If so, it will be deleted.
See the discussion above (earlier in this chapter) about carving and cutting solid objects, and the Wash command.
CFuncplot: Plot a function from C -> C. In two-dimensional plotting, only the range is shown. In three-dimensions, the absolute value of the range is on the z-axis. Syntax of use is Cfuncplot ( < style > , < init > , < final > , < increm > , F, G). < style > should be either 0 (plot individual points) or 1 (draw line segments between the points). F is an ordinary user-defined function specifying a path in the domain and parameterized by a real-valued variable, presumably one of its parameters, say x. x begins at < init > and ends at < final > with increment < increm > . Each computed value of F(..., x, ...) is fed to G to produce the range value. A typical invocation could be
|
where as x moves from 0 to 1, F(x,y) = y*^(2pix) traces out a circle of radius y (here set to 8). Note in the invocation the use of ... (option-;) to indicate which parameter of F is tracing the path. If it is the only parameter, you can omit (...) and just say F.
CLine: Draw a line segment between two points specified with complex numbers. Syntax of use is CLine(w1, w2) (in two-dimensions) or CLine(w1, z1, w2, z2) (in three-dimensions). wi is a complex number and zi is a real number.
Contrast: Follow with (n), an expression that evaluates to an integer from 0 - 3. Applies to 3-dimensional shaded objects only. Lowering the contrast is like adjusting the contrast knob on a TV set. The range of whiteness and blackness is changed. 0 makes all polygons white. The default is 3.
Copy: Follow with ( < name1 > , < name2 > ). The object with the first name will be copied to (a new version of) one with the second. Either can be simply x or z[i], an entry in an existing object array.
CPlot: Plot the given point in complex mode. Syntax of use is CPlot(w1) (in two-dimensions) or CPlot(w1, z1) (in three-dimensions). w1 is a complex number and z1 is a real number. Affected by Penstyle.
Cut: Follow with ( < name > , [y], [w]). < name > is the name of the object (or object array) to be cut. [y] is an array of 3 (in two dimensional plots) or 4 (in three dimensional plots) entries that specify the equation of the line (or plane) that cuts the object. For example, if ax + by = c is the equation of a cut line, put a, b, and c into [y]. [w] specifies the coordinates of a point on the side of the line (or plane) to be kept. You can't cut a hidden line object. See Wash.
Delete: Follow with ( < name > ), the name of an object or object array. Erase it from the symbol table of names and free up the space it used. Do not put brackets around the name of an object array.
Erase: With no arguments, erase the designated part of the screen. The default is to erase the entire screen. With an argument, as Erase(360), erase the screen down to the 360th row of pixels from the top, and designate this value as the default. Although not really necessary, changing the default allows one to never erase the Worksheet (if it has been moved to the bottom).
ERotate: See Rotate. Only difference here is that the old position of the object will be erased smoothly as the new position is drawn, unless it is a hidden-line or shaded object, in which case the erasure comes first.
ETrans: See Trans. Only difference here is that the old position of the object will be erased smoothly as the new position is drawn, unless it is a hidden-line or shaded object, in which case the erasure comes first.
Funcplot: Similar to CFuncplot but simpler. Plot a function from R -> R, in two-dimensional mode. Syntax of use is Funcplot( < style > , < init > , < final > , < increm > , F). < style > should be either 0 (plot individual points) or 1 (draw line segments between the points). F is an ordinary user-defined function, one of whose parameters, say x, will run from < init > to < final > with increment < increm > . (x, F(..., x, ...)) is graphed. A typical invocation could be
|
where as x moves from -4 to 4, F(x,y) = y/(1+x2) traces out a curve whose height can be controlled by the second parameter y (here set to 2). Note in the invocation the use of ... (option-;) to indicate which parameter of F is tracing the curve. If it is the only parameter, you can omit (...) and just say F. If F becomes undefined at some point in the graphing interval, Fermat will courteously ignore the error, informing the user that at least one error occurred.2
Hide: Follow with ( < name > ), the name of a single object (not an object array). Erase the object from view.
Join: Follow with ( < name1 > , < name2 > , < name3 > ). Geometrically join each pair of corresponding polygons in the first two objects to form polyhedra; call the object consisting of the new polyhedra < name3 > . If the third name is part of an object array, like z[3], the previous z[3], if any, will be deleted. But if it's just z, a new z will be created. The first two are consumed by the join and cease to exist. They must consist entirely of polygons and be compatible: same dimensionality, same viewing angle, etc.
The geometric join of two polygons is the polyhedron that results by joining corresponding vertices with line segments. Presumably, the polygons are close to each other but in different planes. For example, the join of a square with another one just below it is a cubic or rectangular box.
To produce a useful result, the two objects must have the same number of polygons, and corresponding polygons must be listed in exactly the same order within the objects. This is really not hard to arrange - just create the objects in similar ways. But make sure you don't shade an object that will later be joined! Shading sorts the polygons into display order. That will undoubtedly mess up any correspondence the object may have had to another object.
Line: Draw a line segment between two specified points. Syntax of use is Line(x1,y1, x2,y2) (in two-dimensions) or Line(x1,y1,z1, x2,y2,z2) (in three-dimensions).
Mark: No arguments. Begin "marking," i.e., Fermat is to tabulate until further notice all points, lines, and polygons graphed. Marking is usually ended with Setobj. See also Unmark.
Objarray: Follow with ( < name > [ < num > ]). < name > is the name of the array to be created, < num > its size.
OColor: Follow with ( < name > , the name of an object, and then < red > , < green > , < blue > , the values for the three RGB primary colors. Each should be an integer from 0 (black) to 65535 (palest); see SColor. Color the object. Note that if &H is on, the actual color of a polygon displayed on your monitor will be shaded to look "realistic" according to the angle between the unit normal and the Window.
OIColor: Just like OColor, except the already colored object will have the color of each polygon, line, and point incremented. (Might not have any effect in the MPW version, due to MPW.)
ODump: Follow with ( < name > ), the name of an object (or object array). Writes to the output file the internal data about an object in a form that can be read later. You must have already specified an output file. If the name of the object is x, the first line of the dump will start fx : = .... Be warned that an object can often occupy more than 500K in a file.
Penstyle: Select the appearance of a plotted point. Both size and style can be chosen. The default is a black dot occupying one pixel. Syntax of use is Penstyle( < shade > , < penpoint > ) or Penstyle( < shade > , < penpoint > , < char > ). The first argument is an integer from 0 - 4, with 0 being white and 4 being black. The second is an integer from 0 - 4, with 0 specifying a dot of one pixel, 1 meaning a dot 2 pixels square, 2 a small letter "x", and 3 a small letter "o". If the second is 4, you must specify a third argument, a character, and the penpoint becomes that character, as in Penstyle(4, 4, 'J'). Line is not affected by the second argument.
Plot: Plot the given point. Syntax of use is Plot(x1,y1) (in two-dimensions) or Plot(x1,y1,z1) (in three-dimensions). Affected by Penstyle.
Polygon: Follow with an array [ < name > ], or alternatively, parentheses, an array, and an expression evaluating to ±1, as in Polygon([v], -1). The array should have 2 (two-dimensional plotting) or 3 (three-dimensional) rows.3 Each column specifies a vertex. A line segment will be drawn from point i to point i+1 for each i, and from the last point to the first. The optional expression specifies the orientation of the polygon (useful only in 3-dimensional graphics). If omitted, the orientation is the one implied by the order of the vertices in [ < name > ]. If Mark has been set, the named vertices will be grouped to constitute a polygon, the basic atom of shaded objects. The orientation affects the darkness of shading on the polygon when it is displayed. See Reverse.
Rect: Draw a rectangle. Should be in two-dimensional graphing. Follow by four numbers in parentheses separated by commas, the coordinates of the lower left corner and of the upper right corner.
Redraw: In the stand-alone version, redraw the graphing window when returning to Fermat from another application. Does not remember colors well.
Reverse: Reverse the orientation on all the polygons in an object. Only of use in 3-dimensional graphics. Follow with ( < name > ), the name of a single object (not an object array).
Rotate: Follow with ( < name > , < angle > , < axis > ), the name of the object, the angle of rotation (in radians), and the axis of rotation, 1 = x-axis, 2 = y-axis, 3 = z-axis. If the axis scale factors are not all equal, there will be visual distortion. In 2-dimensional graphing, set axis = 3. At finish, both old and new positions will be displayed. See ERotate and Transform.
SColor: Follow with ( < red > , < green > , < blue > ), the values for the three RGB primary colors. (Each should be an integer from 0 (black) to 65535 (palest).) Set the ''system color." All lines, points, and polygons that are graphed will have this color (assuming that color has been turned on with &C). The MPW version seems to permit fewer colors (this is MPW's fault), in that all values between 32769 and 65535 yield the same color. You get shades of gray if the three integers are identical.
Setaxes: Initialize the screen for two-dimensional graphing. Follow with (x1, x2) or (x1, x2, x3). (x1, x2) are the absolute coordinates of the origin of the Cartesian axes. The optional x3 is the length in pixels of the y-axis, starting down from the top of the screen. If omitted, the axis will go to the bottom of the screen. See also Setthree.
Setobject: Follow with ( < name > ), an ordinary name by Fermat conventions. < name > could also be of the form z[i] where z is an already created object array. Takes all the marked points and lines (since the latest Mark command) and associates them with the given name. If the name has been used before for an object, a new object by that name is created and the old one is pushed down on the stack of names. It doesn't matter that the name was also used for an ordinary variable.
Setscale: Set the scaling on the two or three axes. Follow with (x, y) or (x, y, z), according to dimension. The arguments give the number of pixels that equal one unit.
Setscale can be quite time-consuming if you have created a lot of objects, because data within the objects will be modified.
Setthree: Initialize the screen for three-dimensional graphing. Follow with (x1, x2), the absolute coordinates of the origin of the Cartesian axes. You will be viewing the axes from a canonical position looking in and slightly down to the first octant. This position can be adjusted with View. The positive x-axis goes off to the left, the positive y-axis goes off to the right, and the positive z-axis goes up.
Setthree can be quite time-consuming if you have created a lot of objects, because data within the objects will be modified.
Shade: Follow with ( < name > ), the name of a single object (not an object array). Shade the object. Will affect only the polygons in the object, and only in 3-dimensional graphing. See discussion of shaded objects earlier in this chapter.
Show: Follow with ( < name > ), the name of a single object (not an object array). Display the object.
Solid: Make a 3-dimensional object solid or hollow. Two variants: Solid(x) (forcing x to be solid), or Solid(x, n), where n evaluates to 1 or 0. n = 1 means to make the object solid, n = 0 to make it "hollow." See the discussion earlier in this chapter about solid objects. Meaningful only if you will Cut or Carve the object.
Surface: Create a three-dimensional hidden line object. Mark must not have been set; the lines created by Surface cannot be included in any other object. Follow with ( < name > , < shade > , < low x > , < high x > , < low y > , < high y > , < increm > , F). < name > is the name of the object being created. x and y are initialized to their low values and successively incremented by < increm > until both exceed the high values. At every point of the resulting grid, F is evaluated.4 Neighboring points in three-space (x, y, F(x,y)) are connected by line segments. Think of tightly covering the resulting skeleton with canvas and you have the final surface.
< shade > is an expression evaluating to 1 (make a shaded object) or 0 (not shaded).
Surface can be quite time consuming if < increm > is realistically small. If < shade > is 1, the shaded polygons will be displayed almost immediately, but it takes much longer to show the lines. Times around 40 seconds on a IIci are common.
Threeplot: Three dimensional analog of Funcplot. Similar to Surface, but does not compute a hidden line object and can be used with Mark. Follow with ( < polygs > , < low x > , < high x > , < low y > , < high y > , < increm > , F). x and y are initialized to their low values and successively incremented by < increm > until both exceed the high values. At every point of the resulting grid, F is evaluated.4 The resulting points in three-space (x, y, F(x,y)) are connected by line segments. If < polygs > is set to 0, only the ''stick figure" line segments are created; if < polygs > is 1, polygons are created, so that the object may later be shaded.
Trans: Translate an object. Follow with ( < name > , x, y) (two dimensions) or ( < name > , x, y, z) (three dimensions). < name > is the name of an object or object array. The last two (or three) arguments are the coordinates of the vector to be translated along. See ETrans.
Transform: Apply a linear transformation (i.e., a matrix) to an object. Follow with ( < name > , < matrix expr > ). < name > is the name of an object or object array. < matrix expr > is an array expression that evaluates to a dim ?dim matrix of real numbers, where dim is the dimension of the object. Generalizes both Rotate and Zoom. Does not erase or display the object.
Union: Follow with ( < name1 > , < name2 > , < name3 > ). Unite the first two objects into a new object with the name of the third. If the third name is part of an object array, like z[3], the previous z[3], if any, will be deleted. But if it's just z, a new z will be created. The first two are consumed by the union and cease to exist. They must have no points and be compatible: same dimensionality, same viewing angle, etc.
Unmark: No arguments. Stop marking and discard the list of points and lines so far noted. See Mark.
View: Change the viewing angle, or the height above the graphing world where the observer seems to be. Follow with ( < angle > ) or ( < angle > , < name > ). < angle > should be between p/2 and -p/2. In the second form, only the object (or object array) named will be changed. In the first case, all objects will be changed. Be warned: that might take quite a bit of time.
Warp: Explained earlier in this chapter under Solid Objects. Changes the observer point used to sort polygons.
Wash: Objects that have been Cut or Carved sometimes contain degenerate polygons. They may have vertices that are equal or extremely close together, or they may have only two vertices. Further cutting could lead to some nasty errors. Wash tries to clean up these bad polygons. Follow with ( < name > ), the name of a single object (not an object array).
Window: Only used in 3-dimensional graphing. Sets the direction of the imaginary window that allows light into the viewing world. Example: Window(-1,1,4) puts the window up and a bit to the right. The length of the specified vector is irrelevant.
Zoom: Follow with ( < name > , < expr > ), the name of a single object or an object array, and the multiplying factor. Multiply all coordinates in the object by the factor. See Transform.
ZLine: A blend of CLine and ZPlot. Syntax of use is ZLine(z,w) or ZLine(z,w,r). z and w are complex numbers and r is the radius of the Riemann sphere (see ZPlot below). A straight line is drawn between the the images of z and w on the Riemann sphere.
ZPlot: Plot the given complex number on the Riemann sphere, via stereographic projection. Must be in three dimensions. Syntax of use is ZPlot(z) or ZPlot(z,r). z is a complex number and r is the radius of the Riemann sphere, assumed to be tangent to the x-y plane at the origin and lie in the upper half-space. If r is omitted it is assumed to be 1. Affected by Penstyle.
ZSphere: Similar to Cfuncplot; plot the image of a function
from
C -> C, but on the Riemann sphere. Must be in three
dimensions. Fermat just follows Cfuncplot with the inverse of stereographic
projection. Syntax of use is ZSphere( < style > , < init
> , < final > , < increm > , F, G, < radius > ). For most
of these, see Cfuncplot; the last parameter is the radius of the
sphere (tangent to the x-y plane at (0,0)) you wish to map
onto.
In plotting points or lines, beware of coordinates that are outlandish - too excessive and Quickdraw goes bonkers. For example, if the scale has been set to, say, 60 pixels per unit, the command Line(10000,10000,200000,200000) will produce bizarre results.
Objects can be saved from session to session via the ODump command and the object assignment command, fx : = .....
No method of generating hard copy is provided here. However, there are readily available inexpensive programs that take ''snapshots" of the screen and produce printable files. Under system 7, this utility is built into the Mac operating system via comand-shift-3. The graphs of Appendix 3 were produced that way.
Many examples of graphing functions are given in Appendix 3, example
5.
The Dangerous Commands
This chapter describes several commands that should not be used by novices nor the timid. Each one can produce impressive speedups in execution time. Each one, if misapplied, can crash the system, or worse, produce reasonable but erroneous output.
Nothing is free. The cost of speed is loss of flexibility, loss of error checking, and a certain amount of fragility.
Real
Real is a directive to Fermat that promises that every scalar quantity encountered during the execution of a function will be a real number, not a polynomial. Real should be placed at the beginning of a function definition, and once executed, applies throughout that function.
Fermat does not check to see that quantities encountered really are (real) numbers; if one is not, the system may crash.
Setting real in function F has no effect on a function G that F may invoke. If F calls itself recursively, the later invocations will not automatically inherit Real from the earlier. For it to hold in any invocation, the command Real must be executed in that invocation.
With Real, the one character symbolic forms of built-in functions usually execute much faster than the spelled out English word forms (for example, " instead of Cos.)
To further enhance speed, while Real is in effect most range checking is disabled. If you access an array via x[20] and [x] has less than 20 elements, the ordinary error message will not be generated. What actually will happen is completely unpredictable.
Real will not catch the error of treating an array parameter as if it were a scalar parameter.
Implicit multiplication doesn't work under Real. You must write 2*x, not 2x.
With Real in a function, the function must explicitly pass back its results with Return.
Ideally, one should modularize a computation so that segments that can take advantage of this feature are moved into their own functions. Very impressive speedups are possible. This is especially true in the fast modular versions.
Compile
Like Real, Compile is a directive to Fermat, and applies only to the function in which it is located. Compile implements one of the many functionalities of a genuine compiler. It is a promise to Fermat that the variables given to Compile will not change their identity throughout all the executions of the function. Note carefully: the identity must not change; it is perfectly fine to change the value of the variable. Essentially, ''identity" here means ''address" or ''access path."
To use this feature, the command Compile must be the very first command in the function. (If Real is to be used also, Real should be the second command.)
For example, the command Compile(x, [y], p[2], z) promises that x, [y], p[2], and z will always refer to the same variables, everywhere within the function and in every call of the function. The first time the function is executed, when Compile is seen, Fermat looks up the names x, [y], p[2], and z. It then scans the entire function definition and augments every occurrence of these names with a pointer to the symbol table record of the name that exists at that instant. When a reference to one of the variables is encountered during execution, the need to look up the name in the symbol table is therefore obviated. This can provide a very impressive speedup. (Note: there is a speed advantage to compiling p[2], not just [p]. But make sure p[2] has been initialized before the Compile statement is executed. Unfortunately, two dimensional references like p[2,3] cannot be compiled. Such a reference can be converted to one dimensional form using column-major order.)
Only the programmer can tell for sure that it is safe to compile a certain name, say z. If z is a global variable one time the function is executed, but is some other function's local variable another time, it must not be listed. Furthermore, if z is another function's parameter or local variable, it is not the same z during different invocations of that function - recall that the symbol table entry for local variables is removed after invocation ends - and cannot be compiled. This is the most common mistake in using Compile. It is an insidious error.
The list of names following Compile must under no circumstances contain the parameters and local variables of the function. These are automatically compiled, when Compile is specified (but see next paragraph). Rather, the list should be of names created elsewhere, outside the function. If the list (and the parentheses) are omitted, only the parameters and local variables will be compiled.
It is possible not to compile the parameters, by using the syntax Compile*, i.e., placing a * after the word "Compile" and before the parenthesis. Then only the variables in the following list will be compiled. Make sure the list does not contain the name of a parameter! The system may crash if it does. A compiled function cannot be called recursively, unless you use Compile*. (Indeed, that is why Compile* exists as an option.) There will be no error message if you try it. Rather, garbage will be generated or the system will crash. Beware of this mistake when using Compile!
A subtlety arises involving compiled array references and the array initial indexing value, 0 or 1, which can be changed by the command &a. If you place x[5] in the list of variables to be compiled, the address of x[5] will be determined according to the array initial indexing value that exists at the instant of compilation. If you execute &a within the function before a reference to x[5], you have a problem - at the very least, the function will not execute the way it would without compilation.
This problem does not arise from compiled references of the type [x], as in Compile([x]). Later executions of &a are irrelevant. Therefore, a cure to the problem of the previous paragraph is to use Compile([x]) instead of Compile(x[5]) when &a occurs within the function being compiled. (Compile(x[5]) is, of course, slightly more efficient.)
Range checking of compiled array references is disabled. If you access an array via x[20] and [x] has less than 20 elements, the ordinary error message will not be generated. What actually will happen is completely unpredictable.
A compiled function cannot be an argument to the graphing routines CFuncplot, Funcplot, Threeplot, Surface, or ZSphere.
Compile and Sparse do not work together: Sparse arrays cannot be passed as arguments to a compiled array parameter. Nor can Sparse arrays appear in the list of variables after the Compile command.
Changes to a variable's identity before the compiled function is executed the first time are irrelevant. Compile is an executable statement, and is not noted at function definition time.
There is no point incurring the cost of compilation unless the function will be executed often, or contains a loop that will be executed often. If a function is dominated by polynomial or matrix operations, little relative improvement will result from compilation.
I have seen the use of Real, Compile, and the increment command eliminate 90% of the execution time of some functions.
Object Parameters
These have already been discussed in the graphics chapter.
(See Am. Math. Monthly, 100:590. Use sin(tan(i*k)).)
The Interface with C and Pascal
Complex algorithms often have significant parts that don't use any of Fermat's unlimited precision, polynomial or matrix features. Those subtasks could be written in simpler, compiled languages, such as Pascal and C. Assigning such a subtask its own function in Fermat, using Compile, and using Integer, will speed it up tremendously. Nonetheless, the subtask may still dominate the execution time of the entire program.
In these situations the MPW versions of Fermat allows the user to write the subtask in C or Pascal, where its execution will be much faster, and invoke the resulting exterior function from Fermat. For example, the arithmetic of complex numbers is easy to code up in Pascal, and becomes tedious only if there are many variables of complex type involved. Fermat programs investigating Julia sets and the Mandelbrot set could do most of the arithmetic in Pascal, computing and storing points in a file. Then the Fermat function reads the file and displays the points with Fermat's graphics commands. [See illustration below.]
The user must of course be a C or Pascal programmer and must have a fair familiarity with MPW's Linker. The user codes up the subtask as a, let us say, Pascal program, basically as she would write any other Pascal program. She creates a make file that will link together her Pascal file with the Fermat object code files. Building the make file produces a "customized" version of Fermat able to invoke the user's function.
There are several technicalities and protocols. The command in Fermat to invoke the external Pascal function is Pfunc; for the C function it is Cfunc. One longint parameter can be passed back and forth. Otherwise Fermat and the function communicate by reading and writing to files.
In the 68XXX architecture, the total size of all global variables must be less than 32K, a bound imposed by the Linker.5 The several versions of Fermat use about 15 - 18K, leaving 14 - 17K for the user's variables. The Linker displays (or can be made to display) this number.
I have provided an example of all of this in Appendix 3, example 3.
Again, this feature is available only in the MPW versions of Fermat.
Errors and Warnings
When an error occurs (dividing by 0, array index out of bounds, etc.) execution of the function containing the offending statement stops and the interpreter returns to the lowest interactive level and displays a message describing the error. If the error occurred during or because of a command entered from the terminal, the portion of the command successfully parsed is displayed. Then a function trace dump is displayed: the stack of function calls from most recent to earliest. However this feature is not perfect: if the stack of calls is very deep, only the first 500 (i.e, the 500 most recent) are to be believed.
If the error occurred during a function evaluation, the interpreter then displays two statements, the last one successfully executed followed by the expression it was working on when the interrupt occurred, up to the offending character. Usually the offending character is not displayed, but sometimes it is the last thing displayed. Sometimes the first of these statements (the last one successfully executed) is not displayed, usually because the interrupt occurred in the first line of a function. Keep in mind that the two statements may not be physically adjacent in the function definition.
If a great many function calls have been stacked up when an error occurs, then if the interpreter simply returned to the lowest level, all of those functions' parameters would be on the environment stack. The user would find this extremely inconvenient: his "real" variables would be buried. Thus, the interpreter also pops off all the now useless parameters, up to around 500 calls deep.
The command &e can be used to change the way errors are handled in some cases - see the next chapter.
Here is an example of an error that occurred during a function evaluation,
taken from a Fermat session. Line numbers [n] added:
>G2(10)
*** Inappropriate symbol: '
[1] error in function definition. `:' or `;'
expected.
*** Error occurred at this point:
[2] G2(10);
*** The most recent function evaluations
were:
[3] Undo
Next
G2
*** Function was interrupted at:
[4] n<0;
varcount[n] '
The user invoked G2(10). In line [1], Fermat reports the basic
problem, apparently a quote in place of a colon in some assignment statement.
Line [2] reports the offending place in the input line (read from the terminal)
that caused the error. Line [3] begins the trace dump of function evaluation
calls. G2 called Next which called Undo. The error
is therefore inside Undo. Line [4] and the following are the last
two commands that Fermat executed. n < 0 was executed, and execution
terminated at the end of the next line, where we can see the offending
quote.
Warnings
There are a few situations that produce warning messages. No error occurs, zero is returned, and normal execution proceeds. The more serious of these are called "Fermat Warnings," which occur when some internal condition that "must" be true is in fact false. For example, dividing polynomials that Fermat expects to yield a polynomial quotient, but which instead produce a quolynomial because of roundoff errors. (This could result from computing x/y for any polynomials x and y. Fermat computes g = gcd(x, y) and then divides x/g and y/g. It expects each of these divisions to come out even.)
Please report all "Fermat Warnings" to the author!
A commonly encountered but usually harmless warning message is ''end
of line found inside a comment." It occurs whenever Fermat reads a function
definition and sees a multi-line comment. It is certainly allowable to
have multi-line comments. Yet the warning message must be produced in order
to catch the error of entirely omitting the end-of-comment symbol ( >>
). If you do that, Fermat will be stuck in an infinite loop, and your only
notification of that fact will be the above warning message.

A torus cut by a plane through its center, tangent to opposite ''sides."
The cutting edge forms two intersecting circles. Two other images of this
idea are earlier in the manual.
Popping, Pushing, Debugging, and Panic Stops
Let's say that a computation seems to be going on too long, or for some other reason you want to stop it. Just press the command key - Fermat will halt. The interpreter will push on a new command level. You can now examine any of the variables, or do anything else - it's just as good as the original command level (almost - more later). This is a useful debugging feature. To resume the computation (and fall back to the original command level), enter the command &p ("pop"). To stop the computation completely and fall back to the lowest level (in effect, a panic stop), enter &·.
The ability to interrupt with the command key is convenient but can be a bit expensive in terms of execution time. Therefore, the user may disable this feature.
When off, the user's programs will execute most quickly, but the user cannot interrupt them.
When on, the user's program will almost always halt immediately after the command key is pressed. Just keep holding it down until Fermat halts. Excepted from this are some basic arithmetic operators. You will not be able to interrupt something like 6 ^ 99999.
To set the interrupt level, use the command &m, or its imperative form &(m= < level > ). With the first form, you will be prompted to enter a level. Enter 0 or 1, to stand for off or on. As usual, an expression may be entered that evaluates to 0 or 1.
Execution time savings vary. Typically you will save 20% by disabling the interrupt facility. Experiment!
An interrupt subtlety: if in the new command level you delete the function you were executing, when you fall back to that command level, the function will still be able to resume, i.e., it still exists internally until its execution terminates. However, since its name has been erased from the list of functions, no function can call it. Therefore, an attempt at recursion will result in an error.
If you interrupt a Fermat built-in function, do not invoke the SAME built-in at the higher command level.
The commands &p and &· can be inserted in a program, as can &P ("push"), which has the same effect as pressing the command key.
Command levels can be stacked up to level 9.
In command levels beyond the first errors are handled differently. When an error occurs, the interpreter tries to recover from it, displays a warning, and continues the computation, with nonguaranteed results. For example, division by 0 causes Fermat to give a warning and use 0 for the result. Obviously, this may spawn further errors. If no reasonable recovery is apparent, the Fermat interpreter is forced to bomb back to the lowest command level. There is then no way to restart the computation at the point that originally created the interrupt.
To somewhat overcome this annoyance, the command &e has been added to Fermat. This sets a flag that changes the way errors are handled during function executions and at higher command levels than the first. If you have previously executed &e, when an error occurs, Fermat will push on a new command level and halt. You can then examine the variables and try to rectify the error yourself, and then pop down to the previous level to allow the computation to continue. (You may find that new errors are being spawned. It may take several &p commands to get you down to the previous level, so keep trying.) Note however that this is still less then perfect. For instance, if the problem was a syntax error in a function, it will do no good to cancel the function and enter a patched up version - upon popping down to continue the previous level, Fermat will still be reading the previous version of the function. Furthermore, if your attempted patch is not good enough, Fermat may crash. It is safer, therefore, to do a panic stop instead of trying to resume the lower level.
See "Full Interrupt" under "The Dangerous Commands."
Mt. Ranier
(See Am. Math. Monthly, 100:590. Use sin(tan(1.3i)).)
Initializing with Ferstartup
Just as MPW has Startup and Userstartup files, Fermat has a "Ferstartup" file to provide for user-defined initializations. The last thing Fermat does while booting up is to read this file as if it were an input file and execute the statements it finds there. The file can contain any Fermat statements, including read commands, each followed by a semicolon. It should not have &x. It can be empty. Lines can be commented out by starting them with a semicolon.
Besides mere convenience, the initialization file provides the capability of invoking Fermat from MPW execution files, since it is now possible to direct Fermat without ever typing a command on the keyboard. For example, some program (another Fermat?) could create data for Fermat and store it in a file in Fermat-readable form, then insert the right read command in Ferstartup, then invoke Fermat.
The fast modular versions (see earlier chapter) need to know the modulus before doing anything else. This can be accomplished via Ferstartup by having the modulus (an integer constant) and a semicolon occupy the first line. If the first line is anything else, the user will be prompted for the modulus before Ferstartup is executed.
Ferstartup must be at the top level inside the Fermat folder.
Hints and Observations
These comments resulted from questions by users of Fermat.
· All versions of Fermat needs a lot of stack space to run. With MPW, use Resedit to reset the MPW Shell resource HEXA 128 (or just plain HEXA in early versions of MPW) to a large value, say 00064000. In versions of MPW before 3.2, the largest possible is 00032000, which is sometimes not enough6. Fermat will run on a machine with only eight meg main memory, but memory is so cheap these days that there is really no excuse to have so little memory.
· When functions are defined, they are not parsed completely. Only certain key features are looked for, such as comments, loops, and if-statements. Therefore, many syntax errors are not detected until the function is executed.
· The order that the functions are defined in the input file is completely irrelevant, unless you put a statement in the input file that actually invokes one of the functions. Then, of course, that function must have been previously defined, and any function it calls must have been previously defined.
· The order in which the various mode-changing commands are given is irrelevant. You may change precision, then add a polynomial variable, then go into complex mode, then set the Laurent flag, then mod out by a polynomial - in this order or any other.
· Fermat is an interactive language, reading and writing to the terminal, i.e., to the same file. This is somewhat difficult to arrange. Other interactive languages require the user to type a special character, usually a semicolon, at the end of each line. I have used some of those languages and been continually annoyed by the necessity of adding semicolons. Therefore, I arranged it that Fermat does not require them - although they are required at the end of statements in an input file. But nothing is free, and hence the necessity for the continuation character ,, when entering more than one line from the terminal. The only place you may find this annoying is when you are editing a function and you forget to put a ,, at the end of every line. When you attempt to enter the new amended function, Fermat will detect this omission and show the offending line.
· Functions can contain anything that can be entered from the terminal. In particular, they can contain the definitions of other functions, or even redefine "themselves."
· Very large numbers (those occupying ten or more lines) or polynomials take time to display on the screen. For example, if you enter 790!, more time will be spent computing the digits of this number in base 10 then in actually multiplying it out in the first place. For comparison, compute 790!/789! The timer command &t never counts the time it takes to display something.
· Every time a line is displayed on the terminal, roughly 10 bytes are used up from the memory available to MPW. This is beyond Fermat's control or ability to garbage collect. Therefore, if you leave a program running all night, don't produce hundreds of pages of output.
· With MPW, the first command that you execute after starting Fermat takes about a half second longer that it otherwise would. This seems to be due to MPW initializing something or loading segments.
· Suppose you want to invert matrix [a]. Typing [a]^-1 right after the prompt " > " will work, but typing 1/[a] will not. In the first case, the initial "[" alerts Fermat to expect an array expression.
· If you execute a save command &s, and later the machine crashes for some reason, the material that was written to the file will not survive, because the file was never closed. To overcome this, enter &S and name a new file. The original file will be closed.
However, sometimes material is still lost after a bad crash. Under System 7 or multifinder you can absolutely save something by interrupting Fermat with the command key, displaying the variables you want, grabbing the data with Copy (command-c), and then transferring to some word processor. Open a document there and save the material. Or use the Scrapbook.
· With MPW, an annoying bug sometimes arises when highlighting several lines of text with the mouse and then hitting enter, as one would do in order to define a function or matrix on the Worksheet during a Fermat session. If an error occurs, due to, for example, not supplying the continuation character, the next attempt to enter anything almost always yields an inappropriate error message. Apparently MPW misreads the prompt character. Hitting enter a few times will straighten it all out.
· Remember that 3121 e+2 means 31.21 not 312.1. If you want 312.1 you can put in the decimal point, 3.121e+2. When no decimal point is given, the normalized form is assumed.
· In reading and saving, the difference between &r and &R, and &s and &S, may be confusing. Use the small letter form either the first time you give a read or save command, or to continue reading or saving to the same file as you previously specified. Use the capital letter form to change to a different file, thereby closing the former one.
In either case, unless you give the imperative form of the command, Fermat will prompt you for the name of the file. This means that if you put &S in the middle of a function, you cannot leave the terminal until the function has gotten to that point in the program; someone must be there to respond to the prompt. The problem is solved by using the imperative form: &(S = < filename > ). This command can be used whether or not it is the first save. You can simply start up the function and leave it to run overnight.
You may also use a character string stored in an array to name the file, as in &(S = [x]). This allows file names to be computed within functions.
· Fermat has been designed to catch all sorts of errors, but no program can allow for all eventualities. Besides the features described in the chapter "The Dangerous Commands," one thing that will cause Fermat to crash is too much recursion. If a program calls itself several hundred times without terminating, all stack memory will be exhausted.
There are also a few strange things you can do with object parameters to cause a crash.
There's an old Henny Youngman joke: A man goes to see his doctor. He
lifts his arm up and down in an odd fashion, saying "Doc, it hurts when
I do this." The doctor says, "Don't do that."
Please send constructive criticisms to Robert H. Lewis, Department of
Mathematics, Fordham University, Bronx NY 10458, rlewis@fordham.edu.
January 1998
January 2001
2Actually, all errors encountered during Funcplot are seamlessly recovered from. As long as Funcplot is called from the terminal, the error recovery mechanism will work. But if Funcplot is called from inside a function C and an error occurs in F, the rest of C is lost, and stranger things are possible.
3 Actually, it suffices to have [v] with 2n or 3n entries, n = number of vertices.
4 See Funcplot for use of ... in arguments to F
5 This changed in MPW version 3.2. If you want to try it, have fun.
6 If you try setting a larger value, you don't get any kind of error message, but it won't work!